Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e5139b237 | |||
| d880faaefa | |||
| cc00a10c2f | |||
| 0a2e798894 | |||
| 5c4259023f | |||
| 57bb088c7d | |||
| ccf19a3668 | |||
| db13fce53b | |||
| 0071566097 | |||
| d8653bc6f0 | |||
| 603b102932 | |||
| 7470b1613a | |||
| 4310cc0f9a | |||
| 87ed7b2338 | |||
| 7866d8d53e | |||
| 744238fd77 | |||
| bbb186f95e | |||
| c89cc5acd0 | |||
| 2294c1cc3a | |||
| 65e201db0f | |||
| b571f81007 | |||
| 0a36a37ae3 | |||
| 3463d109b2 | |||
| 014b7ee6fd | |||
| 498de4c10d | |||
| 98b038c873 | |||
| fab9d25b0a | |||
| bb80551324 | |||
| 9e20ecfe55 | |||
| fac83c60fb | |||
| 914f9bee0d | |||
| 83de59983f | |||
| e21395c934 | |||
| 935fea0868 | |||
| dd4b1c60a4 | |||
| 74a50f0f0c | |||
| 86d482b38d | |||
| edf74871ba | |||
| b11d5c409b | |||
| 52107281df | |||
| bcef792f69 | |||
| 10a7ead932 | |||
| 54ce90d51d | |||
| 3905104b52 | |||
| 97e15abcee | |||
| da7e7af109 | |||
| 6e96087b93 | |||
| f3bb858a2f | |||
| ac72797d1f | |||
| fdd3b8730a | |||
| d51ec0a225 |
@@ -0,0 +1,5 @@
|
|||||||
|
[advisories]
|
||||||
|
ignore = [
|
||||||
|
"RUSTSEC-2020-0071", # time
|
||||||
|
"RUSTSEC-2020-0159", # chrono
|
||||||
|
]
|
||||||
@@ -36,13 +36,13 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
deps: true
|
deps: true
|
||||||
- platform: ubuntu-latest
|
- platform: ubuntu-latest
|
||||||
toolchain: 1.51.0 # MSRV
|
toolchain: 1.60.0 # MSRV
|
||||||
deps: sudo apt-get install libpcsclite-dev
|
deps: sudo apt-get install libpcsclite-dev
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
toolchain: 1.51.0 # MSRV
|
toolchain: 1.60.0 # MSRV
|
||||||
deps: true
|
deps: true
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
toolchain: 1.51.0 # MSRV
|
toolchain: 1.60.0 # MSRV
|
||||||
deps: true
|
deps: true
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.51.0 # MSRV
|
toolchain: 1.65.0
|
||||||
components: clippy
|
components: clippy
|
||||||
override: true
|
override: true
|
||||||
- run: sudo apt-get install libpcsclite-dev
|
- run: sudo apt-get install libpcsclite-dev
|
||||||
|
|||||||
@@ -4,6 +4,74 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 0.7.0 (2022-11-14)
|
||||||
|
### Added
|
||||||
|
- Display inner PC/SC errors ([#420])
|
||||||
|
- Support for metadata command ([#371])
|
||||||
|
- Better `certificate::Serial` inspection ([#437])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- MSRV 1.60.0 ([#423])
|
||||||
|
- Bump `rsa` to v0.7.1 ([#440])
|
||||||
|
- Switch from `lazy_static` to `once_cell` ([#442])
|
||||||
|
- Switch from `subtle-encoding` to `base16ct` ([#443])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Use `chrono` v0.4.23 or newer ([#436])
|
||||||
|
- `Certificate::issuer` was returning the subject instead ([#437])
|
||||||
|
|
||||||
|
[#371]: https://github.com/iqlusioninc/yubikey.rs/pull/371
|
||||||
|
[#420]: https://github.com/iqlusioninc/yubikey.rs/pull/420
|
||||||
|
[#423]: https://github.com/iqlusioninc/yubikey.rs/pull/423
|
||||||
|
[#436]: https://github.com/iqlusioninc/yubikey.rs/pull/436
|
||||||
|
[#437]: https://github.com/iqlusioninc/yubikey.rs/pull/437
|
||||||
|
[#440]: https://github.com/iqlusioninc/yubikey.rs/pull/440
|
||||||
|
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
|
||||||
|
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
|
||||||
|
|
||||||
|
## 0.6.0 (2022-08-10)
|
||||||
|
### Changed
|
||||||
|
- 2021 edition upgrade ([#343])
|
||||||
|
- RustCrypto crate upgrades; MSRV 1.57 ([#378])
|
||||||
|
- `des` v0.8
|
||||||
|
- `elliptic-curve` v0.12
|
||||||
|
- `hmac` v0.12
|
||||||
|
- `num-bigint-dig` v0.8
|
||||||
|
- `pbkdf2` v0.11
|
||||||
|
- `p256` v0.11
|
||||||
|
- `p384` v0.11
|
||||||
|
- `rsa` v0.6
|
||||||
|
- `sha1` v0.10 (replacing `sha-1`)
|
||||||
|
- `sha2` v0.10
|
||||||
|
- Bump `uuid` to v1.0 ([#376])
|
||||||
|
- Bump `der-parser` to v8.0 ([#402])
|
||||||
|
- Bump `x509-parser` to v0.14 ([#402])
|
||||||
|
|
||||||
|
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
|
||||||
|
[#376]: https://github.com/iqlusioninc/yubikey.rs/pull/376
|
||||||
|
[#378]: https://github.com/iqlusioninc/yubikey.rs/pull/378
|
||||||
|
[#402]: https://github.com/iqlusioninc/yubikey.rs/pull/402
|
||||||
|
|
||||||
|
## 0.5.0 (2021-11-21)
|
||||||
|
### Changed
|
||||||
|
- Update `rsa` dependency to 0.5 ([#315])
|
||||||
|
- Update `pbkdf2` dependency to 0.9 ([#315])
|
||||||
|
- Update `x509-parser` dependency to 0.12 ([#315], [#322])
|
||||||
|
- Update `nom` to v7.0 ([#322])
|
||||||
|
|
||||||
|
[#315]: https://github.com/iqlusioninc/yubikey.rs/pull/315
|
||||||
|
[#322]: https://github.com/iqlusioninc/yubikey.rs/pull/322
|
||||||
|
|
||||||
|
## 0.4.2 (2021-07-13)
|
||||||
|
### Added
|
||||||
|
- Make `yubikey::Buffer` a pub type ([#290])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Have `YubiKey::block_puk` take `&mut self` as argument ([#289])
|
||||||
|
|
||||||
|
[#289]: https://github.com/iqlusioninc/yubikey.rs/pull/289
|
||||||
|
[#290]: https://github.com/iqlusioninc/yubikey.rs/pull/290
|
||||||
|
|
||||||
## 0.4.1 (2021-07-12)
|
## 0.4.1 (2021-07-12)
|
||||||
### Changed
|
### Changed
|
||||||
- Rename `SettingValue` to `Setting` ([#286])
|
- Rename `SettingValue` to `Setting` ([#286])
|
||||||
|
|||||||
Generated
+617
-336
File diff suppressed because it is too large
Load Diff
+25
-22
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "yubikey"
|
name = "yubikey"
|
||||||
version = "0.4.1" # Also update html_root_url in lib.rs when bumping this
|
version = "0.7.0"
|
||||||
description = """
|
description = """
|
||||||
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
|
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
|
||||||
support for hardware-backed public-key decryption and digital signatures using
|
support for hardware-backed public-key decryption and digital signatures using
|
||||||
@@ -8,47 +8,50 @@ the Personal Identity Verification (PIV) application. Supports RSA (1024/2048)
|
|||||||
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
|
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
|
||||||
"""
|
"""
|
||||||
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
|
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
|
||||||
edition = "2018"
|
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
repository = "https://github.com/iqlusioninc/yubikey.rs"
|
repository = "https://github.com/iqlusioninc/yubikey.rs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = ["api-bindings", "cryptography", "hardware-support"]
|
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
|
||||||
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
|
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.60"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "cli"]
|
members = [".", "cli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "0.4.23"
|
||||||
cookie-factory = "0.3"
|
cookie-factory = "0.3"
|
||||||
der-parser = "5"
|
der-parser = "8"
|
||||||
des = "0.7"
|
des = "0.8"
|
||||||
elliptic-curve = "0.10"
|
elliptic-curve = "0.12"
|
||||||
hmac = "0.11"
|
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
|
||||||
|
hmac = "0.12"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
nom = "6"
|
nom = "7"
|
||||||
num-bigint-dig = { version = "0.7", features = ["rand"] }
|
num-bigint-dig = { version = "0.8", features = ["rand"] }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
num-integer = "0.1"
|
num-integer = "0.1"
|
||||||
pbkdf2 = { version = "0.8", default-features = false }
|
pbkdf2 = { version = "0.11", default-features = false }
|
||||||
p256 = "0.9"
|
p256 = "0.11"
|
||||||
p384 = "0.8"
|
p384 = "0.11"
|
||||||
pcsc = "2"
|
pcsc = "2"
|
||||||
rand_core = { version = "0.6", features = ["std"] }
|
rand_core = { version = "0.6", features = ["std"] }
|
||||||
rsa = "0.4"
|
rsa = "0.7"
|
||||||
secrecy = "0.7"
|
secrecy = "0.8"
|
||||||
sha-1 = "0.9"
|
sha1 = { version = "0.10", features = ["oid"] }
|
||||||
sha2 = "0.9"
|
sha2 = { version = "0.10", features = ["oid"] }
|
||||||
subtle = "2"
|
subtle = "2"
|
||||||
subtle-encoding = "0.5"
|
uuid = { version = "1.2", features = ["v4"] }
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
|
||||||
x509 = "0.2"
|
x509 = "0.2"
|
||||||
x509-parser = "0.9"
|
x509-parser = "0.14"
|
||||||
zeroize = "1"
|
zeroize = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
lazy_static = "1"
|
once_cell = "1"
|
||||||
|
rsa = { version = "0.7.1", features = ["hazmat"] }
|
||||||
|
signature = { version = "1.6.4", features = ["hazmat-preview"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
untested = []
|
untested = []
|
||||||
|
|||||||
@@ -4,15 +4,19 @@
|
|||||||
|
|
||||||
[![crate][crate-image]][crate-link]
|
[![crate][crate-image]][crate-link]
|
||||||
[![Docs][docs-image]][docs-link]
|
[![Docs][docs-image]][docs-link]
|
||||||
[![2-Clause BSD Licensed][license-image]][license-link]
|
|
||||||
![Rust Version][rustc-image]
|
|
||||||
[![Safety Dance][safety-image]][safety-link]
|
|
||||||
[![Build Status][build-image]][build-link]
|
[![Build Status][build-image]][build-link]
|
||||||
|
[![Safety Dance][safety-image]][safety-link]
|
||||||
|
[![Dependency Status][deps-image]][deps-link]
|
||||||
|
[![2-Clause BSD Licensed][license-image]][license-link]
|
||||||
|
![MSRV][msrv-image]
|
||||||
|
|
||||||
Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
|
Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
|
||||||
with support for public-key encryption and digital signatures using the
|
with support for public-key encryption and digital signatures using the
|
||||||
[Personal Identity Verification (PIV)][PIV] application.
|
[Personal Identity Verification (PIV)][PIV] application.
|
||||||
|
|
||||||
|
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
|
||||||
|
access provided by the [`pcsc` crate].
|
||||||
|
|
||||||
[Documentation][docs-link]
|
[Documentation][docs-link]
|
||||||
|
|
||||||
## About
|
## About
|
||||||
@@ -25,17 +29,44 @@ encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
|
|||||||
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
||||||
on which devices support PIV and the available functionality.
|
on which devices support PIV and the available functionality.
|
||||||
|
|
||||||
If you've been wanting to use Rust to sign and/or encrypt stuff using a
|
If you've been wanting to use Rust to sign and/or encrypt data using a
|
||||||
private key generated and stored on a Yubikey (with option PIN-based access),
|
private key generated and stored on a YubiKey (with option PIN-based access),
|
||||||
this is the crate you've been after!
|
this is the crate you've been after!
|
||||||
|
|
||||||
Note that while this project started as a fork of a [Yubico] project,
|
Note that while this project started as a fork of a [Yubico] project,
|
||||||
this fork is **NOT** an official Yubico project and is in no way supported or
|
this fork is **NOT** an official Yubico project and is in no way supported or
|
||||||
endorsed by Yubico.
|
endorsed by Yubico.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Personal Identity Verification (PIV)
|
||||||
|
|
||||||
|
[PIV] is a [NIST] standard for both *signing* and *encryption*
|
||||||
|
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
|
||||||
|
|
||||||
|
PIV-related functionality can be found in the [`piv`] module.
|
||||||
|
|
||||||
|
This library natively implements the protocol used to manage and
|
||||||
|
utilize PIV encryption and signing keys which can be generated, imported,
|
||||||
|
and stored on YubiKey devices.
|
||||||
|
|
||||||
|
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
||||||
|
on which devices support PIV and the available functionality.
|
||||||
|
|
||||||
|
### Supported Algorithms
|
||||||
|
- **Authentication**: `3DES`
|
||||||
|
- **Encryption**:
|
||||||
|
- RSA: `RSA1024`, `RSA2048`
|
||||||
|
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
|
||||||
|
- **Signatures**:
|
||||||
|
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
|
||||||
|
- ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
|
||||||
|
|
||||||
|
NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
|
||||||
|
|
||||||
## Minimum Supported Rust Version
|
## Minimum Supported Rust Version
|
||||||
|
|
||||||
Rust **1.51** or newer.
|
Rust **1.60** or newer.
|
||||||
|
|
||||||
## Supported YubiKeys
|
## Supported YubiKeys
|
||||||
|
|
||||||
@@ -58,6 +89,17 @@ an experimental stage and may still contain high-severity issues.
|
|||||||
|
|
||||||
USE AT YOUR OWN RISK!
|
USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Functionality which has been successfully tested is available by default.
|
||||||
|
|
||||||
|
Any functionality which is gated on the `untested` feature has not been
|
||||||
|
properly tested and is not known to function correctly.
|
||||||
|
|
||||||
|
Please see the [`untested` functionality tracking issue] for current status.
|
||||||
|
We would appreciate any help testing this functionality and removing the
|
||||||
|
`untested` gating as well as writing more automated tests.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
|
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
|
||||||
@@ -67,7 +109,7 @@ Tests which run live against a YubiKey device are marked as `#[ignore]` by
|
|||||||
default in order to pass when running in a CI environment. To run these
|
default in order to pass when running in a CI environment. To run these
|
||||||
tests locally, invoke the following command:
|
tests locally, invoke the following command:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
cargo test -- --ignored
|
cargo test -- --ignored
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,14 +118,14 @@ information about what is happening. If you'd like to print this logging
|
|||||||
information while running the tests, set the `RUST_LOG` environment variable
|
information while running the tests, set the `RUST_LOG` environment variable
|
||||||
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
|
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
|
||||||
|
|
||||||
```
|
```shell
|
||||||
RUST_LOG=info cargo test -- --ignored
|
RUST_LOG=info cargo test -- --ignored
|
||||||
```
|
```
|
||||||
|
|
||||||
To trace every message sent to/from the card i.e. the raw
|
To trace every message sent to/from the card i.e. the raw
|
||||||
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
|
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
|
||||||
|
|
||||||
```
|
```text
|
||||||
running 1 test
|
running 1 test
|
||||||
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
|
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
|
||||||
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
|
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
|
||||||
@@ -109,6 +151,14 @@ Yubico, which was originally written in C. It was mechanically translated
|
|||||||
from C into Rust using [Corrode], and then subsequently heavily
|
from C into Rust using [Corrode], and then subsequently heavily
|
||||||
refactored into safer, more idiomatic Rust.
|
refactored into safer, more idiomatic Rust.
|
||||||
|
|
||||||
|
For more information on [yubico-piv-tool] and background information on how
|
||||||
|
the YubiKey implementation of PIV works in general, see the
|
||||||
|
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
|
||||||
|
|
||||||
|
## ⚠️ Security Warning
|
||||||
|
|
||||||
|
No security audits of this crate have ever been performed.
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
||||||
@@ -122,7 +172,7 @@ Yubico's [yubico-piv-tool], a C library/CLI program. The original library
|
|||||||
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
|
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
|
||||||
as a derived work.
|
as a derived work.
|
||||||
|
|
||||||
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
|
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -158,24 +208,29 @@ or conditions.
|
|||||||
|
|
||||||
[//]: # (badges)
|
[//]: # (badges)
|
||||||
|
|
||||||
[crate-image]: https://img.shields.io/crates/v/yubikey.svg
|
[crate-image]: https://buildstats.info/crate/yubikey
|
||||||
[crate-link]: https://crates.io/crates/yubikey
|
[crate-link]: https://crates.io/crates/yubikey
|
||||||
[docs-image]: https://docs.rs/yubikey/badge.svg
|
[docs-image]: https://docs.rs/yubikey/badge.svg
|
||||||
[docs-link]: https://docs.rs/yubikey/
|
[docs-link]: https://docs.rs/yubikey/
|
||||||
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
|
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
|
||||||
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
|
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
|
||||||
[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg
|
[msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
|
||||||
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
|
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
|
||||||
[safety-link]: https://github.com/rust-secure-code/safety-dance/
|
[safety-link]: https://github.com/rust-secure-code/safety-dance/
|
||||||
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
|
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
|
||||||
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
|
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
|
||||||
|
[deps-image]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs/status.svg
|
||||||
|
[deps-link]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs
|
||||||
|
|
||||||
[//]: # (general links)
|
[//]: # (general links)
|
||||||
|
|
||||||
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
|
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
|
||||||
[PIV]: https://piv.idmanagement.gov/
|
|
||||||
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
|
||||||
[Yubico]: https://www.yubico.com/
|
[Yubico]: https://www.yubico.com/
|
||||||
|
[PIV]: https://piv.idmanagement.gov/
|
||||||
|
[NIST]: https://www.nist.gov/
|
||||||
|
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC
|
||||||
|
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
|
||||||
|
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||||
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
||||||
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
||||||
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
||||||
@@ -184,6 +239,7 @@ or conditions.
|
|||||||
[cc-web]: https://contributor-covenant.org/
|
[cc-web]: https://contributor-covenant.org/
|
||||||
[cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
|
[cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
|
||||||
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
||||||
|
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
|
||||||
|
|
||||||
[//]: # (github issues)
|
[//]: # (github issues)
|
||||||
|
|
||||||
|
|||||||
+31
-1
@@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 0.7.0 (2022-11-14)
|
||||||
|
### Changed
|
||||||
|
- Bump `clap` to v4.0 ([#438])
|
||||||
|
- Bump `x509-parser` to v0.14 ([#441])
|
||||||
|
- Switch from `lazy_static` to `once_cell` ([#442])
|
||||||
|
- Switch from `subtle-encoding` to `base16ct` ([#443])
|
||||||
|
- Bump `yubikey` dependency to v0.7 ([#444])
|
||||||
|
|
||||||
|
[#438]: https://github.com/iqlusioninc/yubikey.rs/pull/438
|
||||||
|
[#441]: https://github.com/iqlusioninc/yubikey.rs/pull/441
|
||||||
|
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
|
||||||
|
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
|
||||||
|
[#444]: https://github.com/iqlusioninc/yubikey.rs/pull/444
|
||||||
|
|
||||||
|
## 0.6.0 (2022-08-10)
|
||||||
|
### Changed
|
||||||
|
- 2021 edition upgrade; MSRV 1.57 ([#343])
|
||||||
|
- Migrate from `gumdrop` to `clap` v3 ([#379])
|
||||||
|
- Bump `yubikey` dependency to v0.6 ([#403])
|
||||||
|
|
||||||
|
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
|
||||||
|
[#379]: https://github.com/iqlusioninc/yubikey.rs/pull/379
|
||||||
|
[#403]: https://github.com/iqlusioninc/yubikey.rs/pull/403
|
||||||
|
|
||||||
|
## 0.5.0 (2021-11-21)
|
||||||
|
### Changed
|
||||||
|
- Bump `yubikey` dependency to v0.5 ([#327])
|
||||||
|
|
||||||
|
[#327]: https://github.com/iqlusioninc/yubikey.rs/pull/327
|
||||||
|
|
||||||
## 0.4.0 (2021-07-12)
|
## 0.4.0 (2021-07-12)
|
||||||
### Changed
|
### Changed
|
||||||
- Switch to renamed `yubikey` crate ([#283])
|
- Switch to renamed `yubikey` crate ([#283])
|
||||||
@@ -30,7 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `status` command ([#72], [#74])
|
- `status` command ([#72], [#74])
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Bump `yubikey-piv` to v0.1.0 ([#180])
|
- Bump `yubikey-piv` to v0.1 ([#180])
|
||||||
- Bump `x509-parser` to v0.8 ([#181])
|
- Bump `x509-parser` to v0.8 ([#181])
|
||||||
- Bump `sha2` to v0.9 ([#182])
|
- Bump `sha2` to v0.9 ([#182])
|
||||||
- Rename `list` command to `readers`; improve usage ([#71])
|
- Rename `list` command to `readers`; improve usage ([#71])
|
||||||
|
|||||||
+10
-9
@@ -1,25 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "yubikey-cli"
|
name = "yubikey-cli"
|
||||||
version = "0.4.0"
|
version = "0.7.0"
|
||||||
description = """
|
description = """
|
||||||
Command-line interface for performing encryption and signing using RSA/ECC keys
|
Command-line interface for performing encryption and signing using RSA/ECC keys
|
||||||
stored on YubiKey devices.
|
stored on YubiKey devices.
|
||||||
"""
|
"""
|
||||||
authors = ["Tony Arcieri <tony@iqlusion.io>"]
|
authors = ["Tony Arcieri <tony@iqlusion.io>"]
|
||||||
edition = "2018"
|
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
repository = "https://github.com/iqlusioninc/yubikey.rs"
|
repository = "https://github.com/iqlusioninc/yubikey.rs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = ["command-line-utilities", "cryptography", "hardware-support"]
|
categories = ["command-line-utilities", "cryptography", "hardware-support"]
|
||||||
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
|
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.56"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gumdrop = "0.8"
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
lazy_static = "1"
|
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
sha2 = "0.9"
|
once_cell = "1"
|
||||||
subtle-encoding = "0.5"
|
sha2 = "0.10"
|
||||||
termcolor = "1"
|
termcolor = "1"
|
||||||
x509-parser = "0.9"
|
x509-parser = "0.14"
|
||||||
yubikey = { version = "0.4", path = ".." }
|
yubikey = { version = "0.7", path = ".." }
|
||||||
|
|||||||
+3
-7
@@ -18,7 +18,7 @@ utility with general-purpose public-key encryption and signing support.
|
|||||||
|
|
||||||
## Minimum Supported Rust Version
|
## Minimum Supported Rust Version
|
||||||
|
|
||||||
Rust **1.51** or newer.
|
Rust **1.60** or newer.
|
||||||
|
|
||||||
## Supported YubiKeys
|
## Supported YubiKeys
|
||||||
|
|
||||||
@@ -35,10 +35,6 @@ an experimental stage and may still contain high-severity issues.
|
|||||||
|
|
||||||
USE AT YOUR OWN RISK!
|
USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
WIP. Check back later.
|
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
||||||
@@ -47,7 +43,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
|
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -88,7 +84,7 @@ or conditions.
|
|||||||
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
|
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
|
||||||
[docs-link]: https://docs.rs/yubikey-cli/
|
[docs-link]: https://docs.rs/yubikey-cli/
|
||||||
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
|
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
|
||||||
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
|
[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
|
||||||
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
|
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
|
||||||
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
|
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
|
||||||
[safety-link]: https://github.com/rust-secure-code/safety-dance/
|
[safety-link]: https://github.com/rust-secure-code/safety-dance/
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
unused_qualifications
|
unused_qualifications
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use gumdrop::Options;
|
use clap::Parser;
|
||||||
use yubikey_cli::commands::YubiKeyCli;
|
use yubikey_cli::commands::YubiKeyCli;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
YubiKeyCli::parse_args_default_or_exit().run();
|
YubiKeyCli::parse().run()
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-80
@@ -4,62 +4,25 @@ pub mod readers;
|
|||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use self::{readers::ReadersCmd, status::StatusCmd};
|
use self::{readers::ReadersCmd, status::StatusCmd};
|
||||||
use crate::terminal::{self, STDOUT};
|
use crate::terminal;
|
||||||
use gumdrop::Options;
|
use clap::Parser;
|
||||||
use std::{
|
use std::{env, process::exit};
|
||||||
env,
|
use termcolor::ColorChoice;
|
||||||
io::{self, Write},
|
|
||||||
process::exit,
|
|
||||||
};
|
|
||||||
use termcolor::{ColorChoice, ColorSpec, WriteColor};
|
|
||||||
use yubikey::{Serial, YubiKey};
|
use yubikey::{Serial, YubiKey};
|
||||||
|
|
||||||
/// The `yubikey` CLI utility
|
/// The `yubikey` CLI utility
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct YubiKeyCli {
|
pub struct YubiKeyCli {
|
||||||
/// Obtain help about the current command
|
/// Serial number of the YubiKey to connect to
|
||||||
#[options(short = "h", help = "print help message")]
|
#[clap(short = 's', long = "serial")]
|
||||||
pub help: bool,
|
|
||||||
|
|
||||||
/// Specify the serial number of the YubiKey to connect to
|
|
||||||
#[options(
|
|
||||||
short = "s",
|
|
||||||
long = "serial",
|
|
||||||
help = "serial number of the YubiKey to connect to"
|
|
||||||
)]
|
|
||||||
pub serial: Option<Serial>,
|
pub serial: Option<Serial>,
|
||||||
|
|
||||||
/// Subcommand to execute.
|
/// Subcommand to execute.
|
||||||
#[options(command)]
|
#[clap(subcommand)]
|
||||||
pub command: Option<Commands>,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YubiKeyCli {
|
impl YubiKeyCli {
|
||||||
/// Print usage information
|
|
||||||
pub fn print_usage() -> io::Result<()> {
|
|
||||||
let mut stdout = STDOUT.lock();
|
|
||||||
stdout.reset()?;
|
|
||||||
|
|
||||||
let mut bold = ColorSpec::new();
|
|
||||||
bold.set_bold(true);
|
|
||||||
|
|
||||||
stdout.set_color(&bold)?;
|
|
||||||
writeln!(
|
|
||||||
stdout,
|
|
||||||
"{} {}",
|
|
||||||
env!("CARGO_PKG_NAME"),
|
|
||||||
env!("CARGO_PKG_VERSION")
|
|
||||||
)?;
|
|
||||||
stdout.reset()?;
|
|
||||||
|
|
||||||
writeln!(stdout, "{}", env!("CARGO_PKG_AUTHORS"))?;
|
|
||||||
writeln!(stdout, "{}", env!("CARGO_PKG_DESCRIPTION").trim())?;
|
|
||||||
writeln!(stdout)?;
|
|
||||||
writeln!(stdout, "{}", Commands::usage())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the underlying command type or print usage info and exit
|
/// Run the underlying command type or print usage info and exit
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
// TODO(tarcieri): make this more configurable
|
// TODO(tarcieri): make this more configurable
|
||||||
@@ -70,10 +33,7 @@ impl YubiKeyCli {
|
|||||||
env_logger::builder().format_timestamp(None).init();
|
env_logger::builder().format_timestamp(None).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.command {
|
self.command.run(self.yubikey_init())
|
||||||
Some(cmd) => cmd.run(self.yubikey_init()),
|
|
||||||
None => Self::print_usage().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the YubiKey client driver
|
/// Initialize the YubiKey client driver
|
||||||
@@ -92,22 +52,18 @@ impl YubiKeyCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Subcommands of this application
|
/// Subcommands of this application
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Parser)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// `help` subcommand
|
|
||||||
#[options(help = "show help for a command")]
|
|
||||||
Help(HelpOpts),
|
|
||||||
|
|
||||||
/// `version` subcommand
|
/// `version` subcommand
|
||||||
#[options(help = "display version information")]
|
#[clap(about = "display version information")]
|
||||||
Version(VersionOpts),
|
Version(VersionOpts),
|
||||||
|
|
||||||
/// `readers` subcommand
|
/// `readers` subcommand
|
||||||
#[options(help = "list detected readers")]
|
#[clap(about = "list detected readers")]
|
||||||
Readers(ReadersCmd),
|
Readers(ReadersCmd),
|
||||||
|
|
||||||
/// `status` subcommand
|
/// `status` subcommand
|
||||||
#[options(help = "show yubikey status")]
|
#[clap(about = "show yubikey status")]
|
||||||
Status(StatusCmd),
|
Status(StatusCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +71,6 @@ impl Commands {
|
|||||||
/// Run the given command
|
/// Run the given command
|
||||||
pub fn run(&self, yubikey: YubiKey) {
|
pub fn run(&self, yubikey: YubiKey) {
|
||||||
match self {
|
match self {
|
||||||
Commands::Help(help) => help.run(),
|
|
||||||
Commands::Version(version) => version.run(),
|
Commands::Version(version) => version.run(),
|
||||||
Commands::Readers(list) => list.run(),
|
Commands::Readers(list) => list.run(),
|
||||||
Commands::Status(status) => status.run(yubikey),
|
Commands::Status(status) => status.run(yubikey),
|
||||||
@@ -123,28 +78,8 @@ impl Commands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Help options
|
|
||||||
#[derive(Debug, Options)]
|
|
||||||
pub struct HelpOpts {
|
|
||||||
#[options(free, help = "subcommand to get help for")]
|
|
||||||
free: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HelpOpts {
|
|
||||||
fn run(&self) {
|
|
||||||
if let Some(command) = self.free.first() {
|
|
||||||
if let Some(usage) = Commands::command_usage(command) {
|
|
||||||
println!("{}", usage);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", Commands::usage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Version options
|
/// Version options
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct VersionOpts {}
|
pub struct VersionOpts {}
|
||||||
|
|
||||||
impl VersionOpts {
|
impl VersionOpts {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! List detected readers
|
//! List detected readers
|
||||||
|
|
||||||
use crate::terminal::STDOUT;
|
use crate::terminal::STDOUT;
|
||||||
use gumdrop::Options;
|
use clap::Parser;
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
process::exit,
|
process::exit,
|
||||||
@@ -10,7 +10,7 @@ use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
|
|||||||
use yubikey::{Context, Serial};
|
use yubikey::{Context, Serial};
|
||||||
|
|
||||||
/// The `readers` subcommand
|
/// The `readers` subcommand
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct ReadersCmd {}
|
pub struct ReadersCmd {}
|
||||||
|
|
||||||
impl ReadersCmd {
|
impl ReadersCmd {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Print device status
|
//! Print device status
|
||||||
|
|
||||||
use crate::terminal::{print_cert_info, STDOUT};
|
use crate::terminal::{print_cert_info, STDOUT};
|
||||||
use gumdrop::Options;
|
use clap::Parser;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
|
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
|
||||||
use yubikey::{piv::*, YubiKey};
|
use yubikey::{piv::*, YubiKey};
|
||||||
@@ -10,7 +10,7 @@ use yubikey::{piv::*, YubiKey};
|
|||||||
const NONE_STR: &str = "<none>";
|
const NONE_STR: &str = "<none>";
|
||||||
|
|
||||||
/// The `status` subcommand
|
/// The `status` subcommand
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct StatusCmd {}
|
pub struct StatusCmd {}
|
||||||
|
|
||||||
impl StatusCmd {
|
impl StatusCmd {
|
||||||
|
|||||||
+17
-14
@@ -1,14 +1,13 @@
|
|||||||
//! Status messages
|
//! Status messages
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
str,
|
str,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
use subtle_encoding::hex;
|
|
||||||
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
|
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
|
||||||
use x509_parser::parse_x509_certificate;
|
use x509_parser::parse_x509_certificate;
|
||||||
use yubikey::{certificate::Certificate, piv::*, YubiKey};
|
use yubikey::{certificate::Certificate, piv::*, YubiKey};
|
||||||
@@ -59,16 +58,14 @@ macro_rules! status_err {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// Color configuration
|
/// Color configuration
|
||||||
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
|
static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
/// Standard output
|
/// Standard output
|
||||||
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice());
|
pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
|
||||||
|
|
||||||
/// Standard error
|
/// Standard error
|
||||||
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice());
|
pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain the color configuration.
|
/// Obtain the color configuration.
|
||||||
///
|
///
|
||||||
@@ -140,14 +137,12 @@ impl Status {
|
|||||||
|
|
||||||
/// Print the given message to stdout
|
/// Print the given message to stdout
|
||||||
pub fn print_stdout(self, msg: impl AsRef<str>) {
|
pub fn print_stdout(self, msg: impl AsRef<str>) {
|
||||||
self.print(&*STDOUT, msg)
|
self.print(&STDOUT, msg).expect("error printing to stdout!")
|
||||||
.expect("error printing to stdout!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the given message to stderr
|
/// Print the given message to stderr
|
||||||
pub fn print_stderr(self, msg: impl AsRef<str>) {
|
pub fn print_stderr(self, msg: impl AsRef<str>) {
|
||||||
self.print(&*STDERR, msg)
|
self.print(&STDERR, msg).expect("error printing to stderr!")
|
||||||
.expect("error printing to stderr!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the given message
|
/// Print the given message
|
||||||
@@ -204,17 +199,25 @@ pub fn print_cert_info(
|
|||||||
print_cert_attr(
|
print_cert_attr(
|
||||||
stream,
|
stream,
|
||||||
"Fingerprint",
|
"Fingerprint",
|
||||||
str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(),
|
&hex::upper::encode_string(&fingerprint),
|
||||||
)?;
|
)?;
|
||||||
print_cert_attr(
|
print_cert_attr(
|
||||||
stream,
|
stream,
|
||||||
"Not Before",
|
"Not Before",
|
||||||
cert.tbs_certificate.validity.not_before.to_rfc2822(),
|
cert.tbs_certificate
|
||||||
|
.validity
|
||||||
|
.not_before
|
||||||
|
.to_rfc2822()
|
||||||
|
.unwrap(),
|
||||||
)?;
|
)?;
|
||||||
print_cert_attr(
|
print_cert_attr(
|
||||||
stream,
|
stream,
|
||||||
"Not After",
|
"Not After",
|
||||||
cert.tbs_certificate.validity.not_after.to_rfc2822(),
|
cert.tbs_certificate
|
||||||
|
.validity
|
||||||
|
.not_after
|
||||||
|
.to_rfc2822()
|
||||||
|
.unwrap(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -195,6 +195,9 @@ pub enum Ins {
|
|||||||
/// Get device serial
|
/// Get device serial
|
||||||
GetSerial,
|
GetSerial,
|
||||||
|
|
||||||
|
/// Get slot metadata
|
||||||
|
GetMetadata,
|
||||||
|
|
||||||
/// Other/unrecognized instruction codes
|
/// Other/unrecognized instruction codes
|
||||||
Other(u8),
|
Other(u8),
|
||||||
}
|
}
|
||||||
@@ -219,6 +222,7 @@ impl Ins {
|
|||||||
Ins::SetPinRetries => 0xfa,
|
Ins::SetPinRetries => 0xfa,
|
||||||
Ins::Attest => 0xf9,
|
Ins::Attest => 0xf9,
|
||||||
Ins::GetSerial => 0xf8,
|
Ins::GetSerial => 0xf8,
|
||||||
|
Ins::GetMetadata => 0xf7,
|
||||||
Ins::Other(code) => code,
|
Ins::Other(code) => code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +247,7 @@ impl From<u8> for Ins {
|
|||||||
0xfa => Ins::SetPinRetries,
|
0xfa => Ins::SetPinRetries,
|
||||||
0xf9 => Ins::Attest,
|
0xf9 => Ins::Attest,
|
||||||
0xf8 => Ins::GetSerial,
|
0xf8 => Ins::GetSerial,
|
||||||
|
0xf7 => Ins::GetMetadata,
|
||||||
code => Ins::Other(code),
|
code => Ins::Other(code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-7
@@ -32,12 +32,7 @@
|
|||||||
|
|
||||||
use crate::{Error, Result, YubiKey};
|
use crate::{Error, Result, YubiKey};
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use std::{
|
use std::fmt::{self, Debug, Display};
|
||||||
convert::TryInto,
|
|
||||||
fmt::{self, Debug, Display},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
use subtle_encoding::hex;
|
|
||||||
|
|
||||||
/// CCCID offset
|
/// CCCID offset
|
||||||
const CCC_ID_OFFS: usize = 9;
|
const CCC_ID_OFFS: usize = 9;
|
||||||
@@ -115,8 +110,14 @@ impl CccId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for CccId {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for CccId {
|
impl Display for CccId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
|
f.write_str(&hex::upper::encode_string(self.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-32
@@ -45,11 +45,10 @@ use log::error;
|
|||||||
use num_bigint_dig::BigUint;
|
use num_bigint_dig::BigUint;
|
||||||
use p256::NistP256;
|
use p256::NistP256;
|
||||||
use p384::NistP384;
|
use p384::NistP384;
|
||||||
use rsa::{PublicKeyParts, RSAPublicKey};
|
use rsa::{PublicKeyParts, RsaPublicKey};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::convert::TryFrom;
|
use std::fmt::Display;
|
||||||
use std::fmt;
|
use std::{fmt, ops::DerefMut};
|
||||||
use std::ops::DerefMut;
|
|
||||||
use x509::{der::Oid, RelativeDistinguishedName};
|
use x509::{der::Oid, RelativeDistinguishedName};
|
||||||
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
|
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -85,7 +84,7 @@ impl TryFrom<&[u8]> for Serial {
|
|||||||
|
|
||||||
fn try_from(bytes: &[u8]) -> Result<Serial> {
|
fn try_from(bytes: &[u8]) -> Result<Serial> {
|
||||||
if bytes.len() <= 20 {
|
if bytes.len() <= 20 {
|
||||||
Ok(Serial(BigUint::from_bytes_be(&bytes)))
|
Ok(Serial(BigUint::from_bytes_be(bytes)))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::ParseError)
|
Err(Error::ParseError)
|
||||||
}
|
}
|
||||||
@@ -96,10 +95,30 @@ impl Serial {
|
|||||||
fn to_bytes(&self) -> Vec<u8> {
|
fn to_bytes(&self) -> Vec<u8> {
|
||||||
self.0.to_bytes_be()
|
self.0.to_bytes_be()
|
||||||
}
|
}
|
||||||
|
/// Returns itself formatted as x509 compatible hex string
|
||||||
|
pub fn as_x509_hex(&self) -> String {
|
||||||
|
let data = self.to_bytes();
|
||||||
|
let raw_hex_string = format!("{:02X?}", data);
|
||||||
|
raw_hex_string
|
||||||
|
.replace(", ", ":")
|
||||||
|
.replace([']', '['], "")
|
||||||
|
.to_lowercase()
|
||||||
|
}
|
||||||
|
/// Returns itself formatted as x509 compatible int string
|
||||||
|
pub fn as_x509_int(&self) -> String {
|
||||||
|
let Serial(buint) = self;
|
||||||
|
format!("{}", buint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Serial {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.as_x509_hex())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about how a [`Certificate`] is stored within a YubiKey.
|
/// Information about how a [`Certificate`] is stored within a YubiKey.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum CertInfo {
|
pub enum CertInfo {
|
||||||
/// The certificate is uncompressed.
|
/// The certificate is uncompressed.
|
||||||
Uncompressed,
|
Uncompressed,
|
||||||
@@ -172,7 +191,7 @@ pub enum PublicKeyInfo {
|
|||||||
algorithm: AlgorithmId,
|
algorithm: AlgorithmId,
|
||||||
|
|
||||||
/// Public key
|
/// Public key
|
||||||
pubkey: RSAPublicKey,
|
pubkey: RsaPublicKey,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// EC P-256 keys
|
/// EC P-256 keys
|
||||||
@@ -192,7 +211,7 @@ impl PublicKeyInfo {
|
|||||||
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
|
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
|
||||||
match subject_pki.algorithm.algorithm.to_string().as_str() {
|
match subject_pki.algorithm.algorithm.to_string().as_str() {
|
||||||
OID_RSA_ENCRYPTION => {
|
OID_RSA_ENCRYPTION => {
|
||||||
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?;
|
let pubkey = read_pki::rsa_pubkey(&subject_pki.subject_public_key.data)?;
|
||||||
|
|
||||||
Ok(PublicKeyInfo::Rsa {
|
Ok(PublicKeyInfo::Rsa {
|
||||||
algorithm: match pubkey.n().bits() {
|
algorithm: match pubkey.n().bits() {
|
||||||
@@ -212,10 +231,10 @@ impl PublicKeyInfo {
|
|||||||
.ok_or(Error::InvalidObject)?;
|
.ok_or(Error::InvalidObject)?;
|
||||||
|
|
||||||
match read_pki::ec_parameters(algorithm_parameters)? {
|
match read_pki::ec_parameters(algorithm_parameters)? {
|
||||||
AlgorithmId::EccP256 => EcPublicKey::from_bytes(key_bytes)
|
AlgorithmId::EccP256 => EcPublicKey::<NistP256>::from_bytes(key_bytes)
|
||||||
.map(PublicKeyInfo::EcP256)
|
.map(PublicKeyInfo::EcP256)
|
||||||
.map_err(|_| Error::InvalidObject),
|
.map_err(|_| Error::InvalidObject),
|
||||||
AlgorithmId::EccP384 => EcPublicKey::from_bytes(key_bytes)
|
AlgorithmId::EccP384 => EcPublicKey::<NistP384>::from_bytes(key_bytes)
|
||||||
.map(PublicKeyInfo::EcP384)
|
.map(PublicKeyInfo::EcP384)
|
||||||
.map_err(|_| Error::InvalidObject),
|
.map_err(|_| Error::InvalidObject),
|
||||||
_ => Err(Error::AlgorithmError),
|
_ => Err(Error::AlgorithmError),
|
||||||
@@ -320,6 +339,7 @@ impl x509::AlgorithmIdentifier for SignatureId {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
|
#[allow(dead_code)]
|
||||||
issuer: String,
|
issuer: String,
|
||||||
subject: String,
|
subject: String,
|
||||||
subject_pki: PublicKeyInfo,
|
subject_pki: PublicKeyInfo,
|
||||||
@@ -364,12 +384,12 @@ impl Certificate {
|
|||||||
&serial.to_bytes(),
|
&serial.to_bytes(),
|
||||||
&signature_algorithm,
|
&signature_algorithm,
|
||||||
// Issuer and subject are the same in self-signed certificates.
|
// Issuer and subject are the same in self-signed certificates.
|
||||||
&subject,
|
subject,
|
||||||
Utc::now(),
|
Utc::now(),
|
||||||
not_after,
|
not_after,
|
||||||
&subject,
|
subject,
|
||||||
&subject_pki,
|
&subject_pki,
|
||||||
&extensions,
|
extensions,
|
||||||
),
|
),
|
||||||
tbs_cert.deref_mut(),
|
tbs_cert.deref_mut(),
|
||||||
)
|
)
|
||||||
@@ -513,7 +533,7 @@ impl Certificate {
|
|||||||
|
|
||||||
/// Returns the Issuer field of the certificate.
|
/// Returns the Issuer field of the certificate.
|
||||||
pub fn issuer(&self) -> &str {
|
pub fn issuer(&self) -> &str {
|
||||||
&self.subject
|
&self.issuer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the SubjectName field of the certificate.
|
/// Returns the SubjectName field of the certificate.
|
||||||
@@ -588,13 +608,13 @@ pub(crate) fn write_certificate(
|
|||||||
|
|
||||||
mod read_pki {
|
mod read_pki {
|
||||||
use der_parser::{
|
use der_parser::{
|
||||||
|
asn1_rs::Any,
|
||||||
ber::BerObjectContent,
|
ber::BerObjectContent,
|
||||||
der::{parse_der_integer, DerObject},
|
der::{parse_der_integer, parse_der_sequence_defined_g, DerObject},
|
||||||
error::BerError,
|
error::BerError,
|
||||||
*,
|
|
||||||
};
|
};
|
||||||
use nom::{combinator, IResult};
|
use nom::{combinator, sequence::pair, IResult};
|
||||||
use rsa::{BigUint, RSAPublicKey};
|
use rsa::{BigUint, RsaPublicKey};
|
||||||
|
|
||||||
use super::{OID_NIST_P256, OID_NIST_P384};
|
use super::{OID_NIST_P256, OID_NIST_P384};
|
||||||
use crate::{piv::AlgorithmId, Error, Result};
|
use crate::{piv::AlgorithmId, Error, Result};
|
||||||
@@ -606,21 +626,18 @@ mod read_pki {
|
|||||||
/// publicExponent INTEGER -- e
|
/// publicExponent INTEGER -- e
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey> {
|
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> {
|
||||||
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> {
|
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> {
|
||||||
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer)
|
parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
|
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
|
||||||
combinator::map(parse_rsa_pubkey, |object| {
|
combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| {
|
||||||
let seq = object.as_sequence().expect("is DER sequence");
|
let n = match modulus.content {
|
||||||
assert_eq!(seq.len(), 2);
|
|
||||||
|
|
||||||
let n = match seq[0].content {
|
|
||||||
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
|
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
|
||||||
_ => panic!("expected DER integer"),
|
_ => panic!("expected DER integer"),
|
||||||
};
|
};
|
||||||
let e = match seq[1].content {
|
let e = match public_exponent.content {
|
||||||
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
|
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
|
||||||
_ => panic!("expected DER integer"),
|
_ => panic!("expected DER integer"),
|
||||||
};
|
};
|
||||||
@@ -634,7 +651,7 @@ mod read_pki {
|
|||||||
_ => return Err(Error::InvalidObject),
|
_ => return Err(Error::InvalidObject),
|
||||||
};
|
};
|
||||||
|
|
||||||
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
|
RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
|
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
|
||||||
@@ -645,8 +662,8 @@ mod read_pki {
|
|||||||
/// -- specifiedCurve SpecifiedECDomain
|
/// -- specifiedCurve SpecifiedECDomain
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId> {
|
pub(super) fn ec_parameters(parameters: &Any<'_>) -> Result<AlgorithmId> {
|
||||||
let curve_oid = parameters.as_oid_val().map_err(|_| Error::InvalidObject)?;
|
let curve_oid = parameters.as_oid().map_err(|_| Error::InvalidObject)?;
|
||||||
|
|
||||||
match curve_oid.to_string().as_str() {
|
match curve_oid.to_string().as_str() {
|
||||||
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
|
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
|
||||||
@@ -658,7 +675,7 @@ mod read_pki {
|
|||||||
|
|
||||||
mod write_pki {
|
mod write_pki {
|
||||||
use cookie_factory::{SerializeFn, WriteContext};
|
use cookie_factory::{SerializeFn, WriteContext};
|
||||||
use rsa::{BigUint, PublicKeyParts, RSAPublicKey};
|
use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use x509::der::write::{der_integer, der_sequence};
|
use x509::der::write::{der_integer, der_sequence};
|
||||||
|
|
||||||
@@ -675,7 +692,7 @@ mod write_pki {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
|
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
|
||||||
pubkey: &'a RSAPublicKey,
|
pubkey: &'a RsaPublicKey,
|
||||||
) -> impl SerializeFn<W> + 'a {
|
) -> impl SerializeFn<W> + 'a {
|
||||||
der_sequence((
|
der_sequence((
|
||||||
der_integer_biguint(pubkey.n()),
|
der_integer_biguint(pubkey.n()),
|
||||||
|
|||||||
+8
-7
@@ -31,12 +31,7 @@
|
|||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
use crate::{Error, Result, YubiKey};
|
use crate::{Error, Result, YubiKey};
|
||||||
use std::{
|
use std::fmt::{self, Debug, Display};
|
||||||
convert::TryInto,
|
|
||||||
fmt::{self, Debug, Display},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
use subtle_encoding::hex;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// FASC-N offset
|
/// FASC-N offset
|
||||||
@@ -131,8 +126,14 @@ impl ChuId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for ChuId {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ChuId {
|
impl Display for ChuId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
|
f.write_str(&hex::upper::encode_string(self.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -41,10 +41,7 @@ use crate::{
|
|||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::{
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
convert::TryInto,
|
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
const CB_ADMIN_TIMESTAMP: usize = 0x04;
|
const CB_ADMIN_TIMESTAMP: usize = 0x04;
|
||||||
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
|
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
|
||||||
|
|||||||
+24
-18
@@ -121,31 +121,37 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error message
|
/// Error message
|
||||||
pub fn msg(self) -> &'static str {
|
pub fn msg(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::AlgorithmError => "algorithm error",
|
Error::AlgorithmError => f.write_str("algorithm error"),
|
||||||
Error::AppletError => "applet error",
|
Error::AppletError => f.write_str("applet error"),
|
||||||
Error::ArgumentError => "argument error",
|
Error::ArgumentError => f.write_str("argument error"),
|
||||||
Error::AuthenticationError => "authentication error",
|
Error::AuthenticationError => f.write_str("authentication error"),
|
||||||
Error::GenericError => "generic error",
|
Error::GenericError => f.write_str("generic error"),
|
||||||
Error::InvalidObject => "invalid object",
|
Error::InvalidObject => f.write_str("invalid object"),
|
||||||
Error::KeyError => "key error",
|
Error::KeyError => f.write_str("key error"),
|
||||||
Error::MemoryError => "memory error",
|
Error::MemoryError => f.write_str("memory error"),
|
||||||
Error::NotSupported => "not supported",
|
Error::NotSupported => f.write_str("not supported"),
|
||||||
Error::NotFound => "not found",
|
Error::NotFound => f.write_str("not found"),
|
||||||
Error::ParseError => "parse error",
|
Error::ParseError => f.write_str("parse error"),
|
||||||
Error::PcscError { .. } => "PC/SC error",
|
|
||||||
Error::PinLocked => "PIN locked",
|
Error::PcscError {
|
||||||
Error::RangeError => "range error",
|
inner: Some(pcsc_error),
|
||||||
Error::SizeError => "size error",
|
} => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)),
|
||||||
Error::WrongPin { .. } => "wrong pin",
|
|
||||||
|
Error::PcscError { .. } => f.write_str("PC/SC error"),
|
||||||
|
|
||||||
|
Error::PinLocked => f.write_str("PIN locked"),
|
||||||
|
Error::RangeError => f.write_str("range error"),
|
||||||
|
Error::SizeError => f.write_str("size error"),
|
||||||
|
Error::WrongPin { .. } => f.write_str("wrong pin"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(self.msg())
|
self.msg(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-108
@@ -1,102 +1,10 @@
|
|||||||
//! **yubikey.rs**: pure Rust cross-platform host-side driver for [YubiKey]
|
#![doc = include_str!("../README.md")]
|
||||||
//! devices from [Yubico] using the Personal Computer/Smart Card ([PC/SC])
|
#![doc(
|
||||||
//! interface as provided by the [`pcsc` crate].
|
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
|
||||||
//!
|
)]
|
||||||
//! # Features
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
//! ## Personal Identity Verification (PIV)
|
#![forbid(unsafe_code)]
|
||||||
//! [PIV] is a [NIST] standard for both *signing* and *encryption*
|
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
||||||
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
|
|
||||||
//!
|
|
||||||
//! PIV-related functionality can be found in the [`piv`] module.
|
|
||||||
//!
|
|
||||||
//! This library natively implements the protocol used to manage and
|
|
||||||
//! utilize PIV encryption and signing keys which can be generated, imported,
|
|
||||||
//! and stored on YubiKey devices.
|
|
||||||
//!
|
|
||||||
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
|
||||||
//! on which devices support PIV and the available functionality.
|
|
||||||
//!
|
|
||||||
//! # Minimum Supported Rust Version
|
|
||||||
//! Rust **1.51** or newer.
|
|
||||||
//!
|
|
||||||
//! # Supported YubiKeys
|
|
||||||
//! - [YubiKey 4] series
|
|
||||||
//! - [YubiKey 5] series
|
|
||||||
//!
|
|
||||||
//! NOTE: Nano and USB-C variants of the above are also supported.
|
|
||||||
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
|
|
||||||
//!
|
|
||||||
//! # Supported Operating Systems
|
|
||||||
//! - Linux
|
|
||||||
//! - macOS
|
|
||||||
//! - Windows
|
|
||||||
//!
|
|
||||||
//! # Supported Algorithms
|
|
||||||
//! - **Authentication**: `3DES`
|
|
||||||
//! - **Encryption**:
|
|
||||||
//! - RSA: `RSA1024`, `RSA2048`
|
|
||||||
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
|
|
||||||
//! - **Signatures**:
|
|
||||||
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
|
|
||||||
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
|
|
||||||
//!
|
|
||||||
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
|
|
||||||
//!
|
|
||||||
//! # Status
|
|
||||||
//! Functionality which has been successfully tested is available by default.
|
|
||||||
//!
|
|
||||||
//! Any functionality which is gated on the `untested` feature has not been
|
|
||||||
//! properly tested and is not known to function correctly.
|
|
||||||
//!
|
|
||||||
//! Please see the [`untested` functionality tracking issue] for current status.
|
|
||||||
//! We would appreciate any help testing this functionality and removing the
|
|
||||||
//! `untested` gating as well as writing more automated tests.
|
|
||||||
//!
|
|
||||||
//! # History
|
|
||||||
//! This library is a Rust translation of the [yubico-piv-tool] utility by
|
|
||||||
//! Yubico, which was originally written in C. It was mechanically translated
|
|
||||||
//! from C into Rust using [Corrode], and then subsequently heavily
|
|
||||||
//! refactored into safer, more idiomatic Rust.
|
|
||||||
//!
|
|
||||||
//! For more information on [yubico-piv-tool] and background information on how
|
|
||||||
//! the YubiKey implementation of PIV works in general, see the
|
|
||||||
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide].
|
|
||||||
//!
|
|
||||||
//! # Security Warning
|
|
||||||
//! No security audits of this crate have ever been performed. Presently it is in
|
|
||||||
//! an experimental stage and may still contain high-severity issues.
|
|
||||||
//!
|
|
||||||
//! USE AT YOUR OWN RISK!
|
|
||||||
//!
|
|
||||||
//! # Code of Conduct
|
|
||||||
//! We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
|
||||||
//!
|
|
||||||
//! For more information, please see [CODE_OF_CONDUCT.md][cc-md].
|
|
||||||
//!
|
|
||||||
//! # License
|
|
||||||
//! **yubikey.rs** is a fork of and originally a mechanical translation from
|
|
||||||
//! Yubico's [yubico-piv-tool], a C library/CLI program.
|
|
||||||
//!
|
|
||||||
//! The original library was licensed under a [2-Clause BSD License][BSDL],
|
|
||||||
//! which this library inherits as a derived work.
|
|
||||||
//!
|
|
||||||
//! [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
|
|
||||||
//! [PIV]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
|
|
||||||
//! [Yubico]: https://www.yubico.com/
|
|
||||||
//! [PC/SC]: https://en.wikipedia.org/wiki/PC/SC
|
|
||||||
//! [`pcsc` crate]: https://github.com/bluetech/pcsc-rust
|
|
||||||
//! [NIST]: https://www.nist.gov/
|
|
||||||
//! [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
|
||||||
//! [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
|
||||||
//! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
|
||||||
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
|
||||||
//! [`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
|
|
||||||
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
|
|
||||||
//! [Corrode]: https://github.com/jameysharp/corrode
|
|
||||||
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
|
|
||||||
//! [cc-web]: https://contributor-covenant.org/
|
|
||||||
//! [cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
|
|
||||||
//! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
|
||||||
|
|
||||||
// Adapted from yubico-piv-tool:
|
// Adapted from yubico-piv-tool:
|
||||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||||
@@ -128,14 +36,6 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
||||||
#![doc(
|
|
||||||
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png",
|
|
||||||
html_root_url = "https://docs.rs/yubikey/0.4.1"
|
|
||||||
)]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
|
||||||
|
|
||||||
mod apdu;
|
mod apdu;
|
||||||
mod cccid;
|
mod cccid;
|
||||||
pub mod certificate;
|
pub mod certificate;
|
||||||
@@ -180,4 +80,4 @@ pub use uuid::Uuid;
|
|||||||
pub type ObjectId = u32;
|
pub type ObjectId = u32;
|
||||||
|
|
||||||
/// Buffer type (self-zeroizing byte vector)
|
/// Buffer type (self-zeroizing byte vector)
|
||||||
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
|
pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
|
||||||
|
|||||||
+4
-5
@@ -33,7 +33,6 @@
|
|||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use log::error;
|
use log::error;
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
@@ -43,7 +42,7 @@ use crate::{
|
|||||||
yubikey::YubiKey,
|
yubikey::YubiKey,
|
||||||
};
|
};
|
||||||
use des::{
|
use des::{
|
||||||
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, NewBlockCipher},
|
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit},
|
||||||
TdesEde3,
|
TdesEde3,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
@@ -138,7 +137,7 @@ impl MgmKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut mgm = [0u8; DES_LEN_3DES];
|
let mut mgm = [0u8; DES_LEN_3DES];
|
||||||
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm);
|
pbkdf2::<Hmac<Sha1>>(pin, salt, ITER_MGM_PBKDF2, &mut mgm);
|
||||||
MgmKey::from_bytes(mgm)
|
MgmKey::from_bytes(mgm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +190,7 @@ impl MgmKey {
|
|||||||
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
|
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
|
||||||
let txn = yubikey.begin_transaction()?;
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
txn.set_mgm_key(&self, require_touch).map_err(|e| {
|
txn.set_mgm_key(self, require_touch).map_err(|e| {
|
||||||
// Log a warning, since the device mgm key is corrupt or we're in a state
|
// Log a warning, since the device mgm key is corrupt or we're in a state
|
||||||
// where we can't set the mgm key.
|
// where we can't set the mgm key.
|
||||||
error!("could not set new derived mgm key, err = {}", e);
|
error!("could not set new derived mgm key, err = {}", e);
|
||||||
@@ -405,7 +404,7 @@ fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
|
|||||||
c = (c & 0x0F) + ((c >> 4) & 0x0F);
|
c = (c & 0x0F) + ((c >> 4) & 0x0F);
|
||||||
|
|
||||||
// if count is even, set low key bit to 1, otherwise 0
|
// if count is even, set low key bit to 1, otherwise 0
|
||||||
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 });
|
tmp[i] = (key[i] & 0xFE) | u8::from(c & 0x01 != 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check odd parity key against table by DES key block
|
// check odd parity key against table by DES key block
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
|
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
|
|
||||||
const OBJ_MSCMAP: u32 = 0x005f_ff10;
|
const OBJ_MSCMAP: u32 = 0x005f_ff10;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -111,7 +111,7 @@ impl MsRoots {
|
|||||||
let mut data_chunk: usize;
|
let mut data_chunk: usize;
|
||||||
let data = &self.0;
|
let data = &self.0;
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
let n_objs: usize;
|
|
||||||
let txn = yubikey.begin_transaction()?;
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
if data_len == 0 {
|
if data_len == 0 {
|
||||||
@@ -119,7 +119,7 @@ impl MsRoots {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate number of objects required to store blob
|
// Calculate number of objects required to store blob
|
||||||
n_objs = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
|
let n_objs: usize = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
|
||||||
|
|
||||||
if n_objs > 5 {
|
if n_objs > 5 {
|
||||||
return Err(Error::SizeError);
|
return Err(Error::SizeError);
|
||||||
|
|||||||
+442
-99
@@ -45,6 +45,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
apdu::{Ins, StatusWords},
|
apdu::{Ins, StatusWords},
|
||||||
certificate::{self, Certificate, PublicKeyInfo},
|
certificate::{self, Certificate, PublicKeyInfo},
|
||||||
|
consts::CB_OBJ_MAX,
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
policy::{PinPolicy, TouchPolicy},
|
policy::{PinPolicy, TouchPolicy},
|
||||||
serialization::*,
|
serialization::*,
|
||||||
@@ -54,12 +55,16 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
|
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use rsa::{BigUint, RSAPublicKey};
|
use p256::NistP256;
|
||||||
use std::{convert::TryFrom, str::FromStr};
|
use p384::NistP384;
|
||||||
|
use rsa::{BigUint, RsaPublicKey};
|
||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use {
|
use {
|
||||||
crate::consts::CB_OBJ_MAX,
|
|
||||||
num_bigint_dig::traits::ModInverse,
|
num_bigint_dig::traits::ModInverse,
|
||||||
num_integer::Integer,
|
num_integer::Integer,
|
||||||
num_traits::{FromPrimitive, One},
|
num_traits::{FromPrimitive, One},
|
||||||
@@ -83,7 +88,7 @@ const KEYDATA_RSA_EXP: u64 = 65537;
|
|||||||
|
|
||||||
/// Slot identifiers.
|
/// Slot identifiers.
|
||||||
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum SlotId {
|
pub enum SlotId {
|
||||||
/// This certificate and its associated private key is used to authenticate the card
|
/// This certificate and its associated private key is used to authenticate the card
|
||||||
/// and the cardholder. This slot is used for things like system login. The end user
|
/// and the cardholder. This slot is used for things like system login. The end user
|
||||||
@@ -121,6 +126,9 @@ pub enum SlotId {
|
|||||||
/// attestation of other keys generated on device with instruction `f9`. This slot is
|
/// attestation of other keys generated on device with instruction `f9`. This slot is
|
||||||
/// not cleared on reset, but can be overwritten.
|
/// not cleared on reset, but can be overwritten.
|
||||||
Attestation,
|
Attestation,
|
||||||
|
|
||||||
|
/// Thse slots are used for management. PIN PUK and Management Key.
|
||||||
|
Management(ManagementSlotId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for SlotId {
|
impl TryFrom<u8> for SlotId {
|
||||||
@@ -133,7 +141,9 @@ impl TryFrom<u8> for SlotId {
|
|||||||
0x9d => Ok(SlotId::KeyManagement),
|
0x9d => Ok(SlotId::KeyManagement),
|
||||||
0x9e => Ok(SlotId::CardAuthentication),
|
0x9e => Ok(SlotId::CardAuthentication),
|
||||||
0xf9 => Ok(SlotId::Attestation),
|
0xf9 => Ok(SlotId::Attestation),
|
||||||
_ => RetiredSlotId::try_from(value).map(SlotId::Retired),
|
_ => RetiredSlotId::try_from(value)
|
||||||
|
.map(SlotId::Retired)
|
||||||
|
.or_else(|_| ManagementSlotId::try_from(value).map(SlotId::Management)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +157,17 @@ impl From<SlotId> for u8 {
|
|||||||
SlotId::CardAuthentication => 0x9e,
|
SlotId::CardAuthentication => 0x9e,
|
||||||
SlotId::Retired(retired) => retired.into(),
|
SlotId::Retired(retired) => retired.into(),
|
||||||
SlotId::Attestation => 0xf9,
|
SlotId::Attestation => 0xf9,
|
||||||
|
SlotId::Management(mgmt) => mgmt.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SlotId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SlotId::Management(r) => write!(f, "{:?}", r),
|
||||||
|
SlotId::Retired(r) => write!(f, "{:?}", r),
|
||||||
|
_ => write!(f, "{:?}", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +182,10 @@ impl FromStr for SlotId {
|
|||||||
"9d" => Ok(SlotId::KeyManagement),
|
"9d" => Ok(SlotId::KeyManagement),
|
||||||
"9e" => Ok(SlotId::CardAuthentication),
|
"9e" => Ok(SlotId::CardAuthentication),
|
||||||
"f9" => Ok(SlotId::Attestation),
|
"f9" => Ok(SlotId::Attestation),
|
||||||
_ => s.parse().map(SlotId::Retired),
|
_ => s
|
||||||
|
.parse()
|
||||||
|
.map(SlotId::Management)
|
||||||
|
.or_else(|_| s.parse().map(SlotId::Retired)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +199,7 @@ impl SlotId {
|
|||||||
SlotId::KeyManagement => 0x005f_c10b,
|
SlotId::KeyManagement => 0x005f_c10b,
|
||||||
SlotId::CardAuthentication => 0x005f_c101,
|
SlotId::CardAuthentication => 0x005f_c101,
|
||||||
SlotId::Retired(retired) => retired.object_id(),
|
SlotId::Retired(retired) => retired.object_id(),
|
||||||
|
SlotId::Management(mgmt) => mgmt.object_id(),
|
||||||
SlotId::Attestation => 0x005f_ff01,
|
SlotId::Attestation => 0x005f_ff01,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +207,7 @@ impl SlotId {
|
|||||||
|
|
||||||
/// Retired slot IDs.
|
/// Retired slot IDs.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub enum RetiredSlotId {
|
pub enum RetiredSlotId {
|
||||||
R1,
|
R1,
|
||||||
R2,
|
R2,
|
||||||
@@ -293,6 +318,12 @@ impl From<RetiredSlotId> for u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for RetiredSlotId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RetiredSlotId {
|
impl RetiredSlotId {
|
||||||
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
|
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
|
||||||
pub(crate) fn object_id(self) -> ObjectId {
|
pub(crate) fn object_id(self) -> ObjectId {
|
||||||
@@ -321,8 +352,90 @@ impl RetiredSlotId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Management slot IDs.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum ManagementSlotId {
|
||||||
|
/// Personal Identification Number (PIN).
|
||||||
|
Pin,
|
||||||
|
|
||||||
|
/// PIN Unblocking Key (PUK).
|
||||||
|
Puk,
|
||||||
|
|
||||||
|
/// Management Key.
|
||||||
|
Management,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for ManagementSlotId {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0x80 => Ok(ManagementSlotId::Pin),
|
||||||
|
0x81 => Ok(ManagementSlotId::Puk),
|
||||||
|
0x9b => Ok(ManagementSlotId::Management),
|
||||||
|
_ => Err(Error::InvalidObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ManagementSlotId {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
"80" => Ok(ManagementSlotId::Pin),
|
||||||
|
"81" => Ok(ManagementSlotId::Puk),
|
||||||
|
"9b" => Ok(ManagementSlotId::Management),
|
||||||
|
_ => Err(Error::InvalidObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ManagementSlotId> for u8 {
|
||||||
|
fn from(slot: ManagementSlotId) -> u8 {
|
||||||
|
match slot {
|
||||||
|
ManagementSlotId::Pin => 0x80,
|
||||||
|
ManagementSlotId::Puk => 0x81,
|
||||||
|
ManagementSlotId::Management => 0x9b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ManagementSlotId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagementSlotId {
|
||||||
|
/// Returns the [`ObjectId`] that corresponds to a given [`ManagementSlotId`].
|
||||||
|
///
|
||||||
|
/// These correspond to the "BER-TLV Tag" values from Table 3 of [NIST SP 800-73-4]:
|
||||||
|
/// "Object Identifiers of the PIV Data Objects for Interoperable Use".
|
||||||
|
///
|
||||||
|
/// [NIST SP 800-73-4]: https://csrc.nist.gov/publications/detail/sp/800-73/4/final
|
||||||
|
pub(crate) fn object_id(self) -> ObjectId {
|
||||||
|
match self {
|
||||||
|
// Data Object: "X.509 Certificate for Key Management"
|
||||||
|
// OID: 2.16.840.1.101.3.7.2.1.2
|
||||||
|
// BER-TLV Tag: '5FC10B'
|
||||||
|
ManagementSlotId::Pin => 0x005f_c10b,
|
||||||
|
|
||||||
|
// Data Object: "Key History Object"
|
||||||
|
// OID: 2.16.840.1.101.3.7.2.96.96
|
||||||
|
// BER-TLV Tag: '5FC10C'
|
||||||
|
ManagementSlotId::Puk => 0x005f_c10c,
|
||||||
|
|
||||||
|
// Data Object: "Retired X.509 Certificate for Key Management 3"
|
||||||
|
// OID: 2.16.840.1.101.3.7.2.16.3
|
||||||
|
// BER-TLV Tag: '5FC10F'
|
||||||
|
ManagementSlotId::Management => 0x005f_c10f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Personal Identity Verification (PIV) key slots
|
/// Personal Identity Verification (PIV) key slots
|
||||||
pub const SLOTS: [SlotId; 24] = [
|
pub const SLOTS: [SlotId; 27] = [
|
||||||
SlotId::Authentication,
|
SlotId::Authentication,
|
||||||
SlotId::Signature,
|
SlotId::Signature,
|
||||||
SlotId::KeyManagement,
|
SlotId::KeyManagement,
|
||||||
@@ -347,6 +460,9 @@ pub const SLOTS: [SlotId; 24] = [
|
|||||||
SlotId::Retired(RetiredSlotId::R19),
|
SlotId::Retired(RetiredSlotId::R19),
|
||||||
SlotId::Retired(RetiredSlotId::R20),
|
SlotId::Retired(RetiredSlotId::R20),
|
||||||
SlotId::CardAuthentication,
|
SlotId::CardAuthentication,
|
||||||
|
SlotId::Management(ManagementSlotId::Pin),
|
||||||
|
SlotId::Management(ManagementSlotId::Puk),
|
||||||
|
SlotId::Management(ManagementSlotId::Management),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Algorithm identifiers
|
/// Algorithm identifiers
|
||||||
@@ -574,97 +690,8 @@ pub fn generate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
|
let value = response.data();
|
||||||
//
|
read_public_key(algorithm, value, true)
|
||||||
// 0x7f 0x49 -> Application | Constructed | 0x49
|
|
||||||
match algorithm {
|
|
||||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
|
|
||||||
// It appears that the inner application-specific value returned by the
|
|
||||||
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
|
|
||||||
//
|
|
||||||
// - Use a full ASN.1 parser on the entire datastructure:
|
|
||||||
//
|
|
||||||
// RSA 1024:
|
|
||||||
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
|
|
||||||
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
|
|
||||||
//
|
|
||||||
// RSA 2048:
|
|
||||||
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
|
|
||||||
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
|
|
||||||
//
|
|
||||||
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
|
|
||||||
// parsing (which treats 128 as a single-byte definite length instead of an
|
|
||||||
// indefinite length):
|
|
||||||
//
|
|
||||||
// RSA 1024:
|
|
||||||
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
|
|
||||||
// | |0x81|len128| modulus |0x82|len3| exp |
|
|
||||||
//
|
|
||||||
// RSA 2048:
|
|
||||||
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
|
|
||||||
// | |0x81| len:256 | modulus |0x82|len3| exp |
|
|
||||||
//
|
|
||||||
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
|
|
||||||
// 3-byte length.
|
|
||||||
let data = &response.data()[5..];
|
|
||||||
|
|
||||||
let (data, modulus_tlv) = Tlv::parse(data)?;
|
|
||||||
if modulus_tlv.tag != TAG_RSA_MODULUS {
|
|
||||||
error!("Failed to parse public key structure (modulus)");
|
|
||||||
return Err(Error::ParseError);
|
|
||||||
}
|
|
||||||
let modulus = modulus_tlv.value.to_vec();
|
|
||||||
|
|
||||||
let (_, exp_tlv) = Tlv::parse(data)?;
|
|
||||||
if exp_tlv.tag != TAG_RSA_EXP {
|
|
||||||
error!("failed to parse public key structure (public exponent)");
|
|
||||||
return Err(Error::ParseError);
|
|
||||||
}
|
|
||||||
let exp = exp_tlv.value.to_vec();
|
|
||||||
|
|
||||||
Ok(PublicKeyInfo::Rsa {
|
|
||||||
algorithm,
|
|
||||||
pubkey: RSAPublicKey::new(
|
|
||||||
BigUint::from_bytes_be(&modulus),
|
|
||||||
BigUint::from_bytes_be(&exp),
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::InvalidObject)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
|
|
||||||
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
|
|
||||||
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
|
|
||||||
let data = &response.data()[3..];
|
|
||||||
|
|
||||||
let len = if let AlgorithmId::EccP256 = algorithm {
|
|
||||||
CB_ECC_POINTP256
|
|
||||||
} else {
|
|
||||||
CB_ECC_POINTP384
|
|
||||||
};
|
|
||||||
|
|
||||||
let (_, tlv) = Tlv::parse(data)?;
|
|
||||||
|
|
||||||
if tlv.tag != TAG_ECC_POINT {
|
|
||||||
error!("failed to parse public key structure");
|
|
||||||
return Err(Error::ParseError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the curve point should always be determined by the curve
|
|
||||||
if tlv.value.len() != len {
|
|
||||||
error!("unexpected length");
|
|
||||||
return Err(Error::AlgorithmError);
|
|
||||||
}
|
|
||||||
|
|
||||||
let point = tlv.value.to_vec();
|
|
||||||
|
|
||||||
if let AlgorithmId::EccP256 = algorithm {
|
|
||||||
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP256)
|
|
||||||
} else {
|
|
||||||
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP384)
|
|
||||||
}
|
|
||||||
.map_err(|_| Error::InvalidObject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
@@ -891,3 +918,319 @@ pub fn decrypt_data(
|
|||||||
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
|
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
|
||||||
txn.authenticated_command(input, algorithm, key, true)
|
txn.authenticated_command(input, algorithm, key, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read metadata
|
||||||
|
pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result<SlotMetadata> {
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
let templ = [0, Ins::GetMetadata.code(), 0, slot.into()];
|
||||||
|
|
||||||
|
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
if response.status_words() == StatusWords::NotSupportedError {
|
||||||
|
return Err(Error::NotSupported); // Requires firmware 5.2.3
|
||||||
|
} else {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf = Buffer::new(response.data().into());
|
||||||
|
|
||||||
|
SlotMetadata::try_from(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata from a slot
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SlotMetadata {
|
||||||
|
/// Algorithm / Type of key
|
||||||
|
pub algorithm: ManagementAlgorithmId,
|
||||||
|
/// PIN and touch policy
|
||||||
|
pub policy: Option<(PinPolicy, TouchPolicy)>,
|
||||||
|
/// Imported or generated key
|
||||||
|
pub origin: Option<Origin>,
|
||||||
|
/// Pub key of the key
|
||||||
|
pub public: Option<PublicKeyInfo>,
|
||||||
|
/// Whether PIN PUK and management key are default
|
||||||
|
pub default: Option<bool>,
|
||||||
|
/// Number of retries left
|
||||||
|
pub retries: Option<Retries>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Buffer> for SlotMetadata {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(buf: Buffer) -> Result<Self> {
|
||||||
|
use nom::{
|
||||||
|
combinator::{eof, map_res},
|
||||||
|
multi::fold_many1,
|
||||||
|
number::complete::u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = fold_many1(
|
||||||
|
|input| Tlv::parse(input).map_err(|_| nom::Err::Error(())),
|
||||||
|
|| {
|
||||||
|
Ok(SlotMetadata {
|
||||||
|
algorithm: ManagementAlgorithmId::PinPuk,
|
||||||
|
policy: None,
|
||||||
|
origin: None,
|
||||||
|
public: None,
|
||||||
|
default: None,
|
||||||
|
retries: None,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|acc: Result<SlotMetadata>, tlv| match acc {
|
||||||
|
Ok(mut metadata) => match tlv.tag {
|
||||||
|
1 => {
|
||||||
|
metadata.algorithm = ManagementAlgorithmId::try_from(tlv.value[0])?;
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
fn policy_parser(
|
||||||
|
i: &[u8],
|
||||||
|
) -> nom::IResult<&[u8], (PinPolicy, TouchPolicy)> {
|
||||||
|
let (i, pin) = map_res(u8, PinPolicy::try_from)(i)?;
|
||||||
|
let (i, touch) = map_res(u8, TouchPolicy::try_from)(i)?;
|
||||||
|
let (i, _) = eof(i)?;
|
||||||
|
|
||||||
|
Ok((i, (pin, touch)))
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.policy =
|
||||||
|
Some(policy_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
fn origin_parser(i: &[u8]) -> nom::IResult<&[u8], Origin> {
|
||||||
|
let (i, origin) = map_res(u8, Origin::try_from)(i)?;
|
||||||
|
let (i, _) = eof(i)?;
|
||||||
|
|
||||||
|
Ok((i, origin))
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.origin =
|
||||||
|
Some(origin_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
match metadata.algorithm {
|
||||||
|
ManagementAlgorithmId::Asymmetric(alg) => {
|
||||||
|
metadata.public = Some(read_public_key(alg, tlv.value, false)?);
|
||||||
|
}
|
||||||
|
_ => Err(Error::ParseError)?,
|
||||||
|
}
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
fn default_parser(i: &[u8]) -> nom::IResult<&[u8], bool> {
|
||||||
|
let (i, default) = u8(i)?;
|
||||||
|
let (i, _) = eof(i)?;
|
||||||
|
|
||||||
|
Ok((i, default == 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.default =
|
||||||
|
Some(default_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
6 => {
|
||||||
|
fn retries_parser(i: &[u8]) -> nom::IResult<&[u8], Retries> {
|
||||||
|
let (i, retry_count) = u8(i)?;
|
||||||
|
let (i, remaining_count) = u8(i)?;
|
||||||
|
let (i, _) = eof(i)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
i,
|
||||||
|
Retries {
|
||||||
|
retry_count,
|
||||||
|
remaining_count,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.retries =
|
||||||
|
Some(retries_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
_unsupported => {
|
||||||
|
// New unsupported tags
|
||||||
|
// https://docs.yubico.com/yesdk/users-manual/application-piv/apdu/metadata.html
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => err,
|
||||||
|
},
|
||||||
|
)(buf.as_ref());
|
||||||
|
|
||||||
|
match out {
|
||||||
|
Ok((_, res)) => res,
|
||||||
|
_ => Err(Error::ParseError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of retries used and remaining.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Retries {
|
||||||
|
/// TODO
|
||||||
|
pub retry_count: u8,
|
||||||
|
/// Remaining attempts
|
||||||
|
pub remaining_count: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Origin of a slot
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Origin {
|
||||||
|
/// The key has been imported
|
||||||
|
Imported,
|
||||||
|
/// The key has been generated on the YubiKey
|
||||||
|
Generated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Origin {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
1 => Ok(Origin::Generated),
|
||||||
|
2 => Ok(Origin::Imported),
|
||||||
|
_ => Err(Error::GenericError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_public_key(
|
||||||
|
algorithm: AlgorithmId,
|
||||||
|
input: &[u8],
|
||||||
|
skip_asn1_tag: bool,
|
||||||
|
) -> Result<PublicKeyInfo> {
|
||||||
|
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
|
||||||
|
//
|
||||||
|
// 0x7f 0x49 -> Application | Constructed | 0x49
|
||||||
|
match algorithm {
|
||||||
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
|
||||||
|
// It appears that the inner application-specific value returned by the
|
||||||
|
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
|
||||||
|
//
|
||||||
|
// - Use a full ASN.1 parser on the entire datastructure:
|
||||||
|
//
|
||||||
|
// RSA 1024:
|
||||||
|
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
|
||||||
|
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
|
||||||
|
//
|
||||||
|
// RSA 2048:
|
||||||
|
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
|
||||||
|
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
|
||||||
|
//
|
||||||
|
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
|
||||||
|
// parsing (which treats 128 as a single-byte definite length instead of an
|
||||||
|
// indefinite length):
|
||||||
|
//
|
||||||
|
// RSA 1024:
|
||||||
|
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
|
||||||
|
// | |0x81|len128| modulus |0x82|len3| exp |
|
||||||
|
//
|
||||||
|
// RSA 2048:
|
||||||
|
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
|
||||||
|
// | |0x81| len:256 | modulus |0x82|len3| exp |
|
||||||
|
//
|
||||||
|
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
|
||||||
|
// 3-byte length.
|
||||||
|
let data = if skip_asn1_tag { &input[5..] } else { input };
|
||||||
|
|
||||||
|
let (data, modulus_tlv) = Tlv::parse(data)?;
|
||||||
|
if modulus_tlv.tag != TAG_RSA_MODULUS {
|
||||||
|
error!("Failed to parse public key structure (modulus)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
let modulus = modulus_tlv.value.to_vec();
|
||||||
|
|
||||||
|
let (_, exp_tlv) = Tlv::parse(data)?;
|
||||||
|
if exp_tlv.tag != TAG_RSA_EXP {
|
||||||
|
error!("failed to parse public key structure (public exponent)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
let exp = exp_tlv.value.to_vec();
|
||||||
|
|
||||||
|
Ok(PublicKeyInfo::Rsa {
|
||||||
|
algorithm,
|
||||||
|
pubkey: RsaPublicKey::new(
|
||||||
|
BigUint::from_bytes_be(&modulus),
|
||||||
|
BigUint::from_bytes_be(&exp),
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::InvalidObject)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
|
||||||
|
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
|
||||||
|
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
|
||||||
|
let data = if skip_asn1_tag { &input[3..] } else { input };
|
||||||
|
|
||||||
|
let len = if let AlgorithmId::EccP256 = algorithm {
|
||||||
|
CB_ECC_POINTP256
|
||||||
|
} else {
|
||||||
|
CB_ECC_POINTP384
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, tlv) = Tlv::parse(data)?;
|
||||||
|
|
||||||
|
if tlv.tag != TAG_ECC_POINT {
|
||||||
|
error!("failed to parse public key structure");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the curve point should always be determined by the curve
|
||||||
|
if tlv.value.len() != len {
|
||||||
|
error!("unexpected length");
|
||||||
|
return Err(Error::AlgorithmError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let point = tlv.value.to_vec();
|
||||||
|
|
||||||
|
match algorithm {
|
||||||
|
AlgorithmId::EccP256 => {
|
||||||
|
EcPublicKey::<NistP256>::from_bytes(point).map(PublicKeyInfo::EcP256)
|
||||||
|
}
|
||||||
|
AlgorithmId::EccP384 => {
|
||||||
|
EcPublicKey::<NistP384>::from_bytes(point).map(PublicKeyInfo::EcP384)
|
||||||
|
}
|
||||||
|
_ => return Err(Error::AlgorithmError),
|
||||||
|
}
|
||||||
|
.map_err(|_| Error::InvalidObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
/// Algorithms as reported by the metadata command.
|
||||||
|
pub enum ManagementAlgorithmId {
|
||||||
|
/// Used on PIN and PUK slots.
|
||||||
|
PinPuk,
|
||||||
|
/// Used on the key management slot.
|
||||||
|
ThreeDes,
|
||||||
|
/// Used on all other slots.
|
||||||
|
Asymmetric(AlgorithmId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for ManagementAlgorithmId {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0xff => Ok(ManagementAlgorithmId::PinPuk),
|
||||||
|
0x03 => Ok(ManagementAlgorithmId::ThreeDes),
|
||||||
|
oth => AlgorithmId::try_from(oth).map(ManagementAlgorithmId::Asymmetric),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ManagementAlgorithmId> for u8 {
|
||||||
|
fn from(id: ManagementAlgorithmId) -> u8 {
|
||||||
|
match id {
|
||||||
|
ManagementAlgorithmId::PinPuk => 0xff,
|
||||||
|
ManagementAlgorithmId::ThreeDes => 0x03,
|
||||||
|
ManagementAlgorithmId::Asymmetric(oth) => oth.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+31
-3
@@ -1,12 +1,12 @@
|
|||||||
//! Enums representing key policies.
|
//! Enums representing key policies.
|
||||||
|
|
||||||
use crate::{serialization::Tlv, Result};
|
use crate::{serialization::Tlv, Error, Result};
|
||||||
|
|
||||||
/// Specifies how often the PIN needs to be entered for access to the credential in a
|
/// Specifies how often the PIN needs to be entered for access to the credential in a
|
||||||
/// given slot.
|
/// given slot.
|
||||||
///
|
///
|
||||||
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum PinPolicy {
|
pub enum PinPolicy {
|
||||||
/// Use the default PIN policy for the slot. See the slot's documentation for details.
|
/// Use the default PIN policy for the slot. See the slot's documentation for details.
|
||||||
Default,
|
Default,
|
||||||
@@ -35,6 +35,20 @@ impl From<PinPolicy> for u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for PinPolicy {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(PinPolicy::Default),
|
||||||
|
1 => Ok(PinPolicy::Never),
|
||||||
|
2 => Ok(PinPolicy::Once),
|
||||||
|
3 => Ok(PinPolicy::Always),
|
||||||
|
_ => Err(Error::GenericError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PinPolicy {
|
impl PinPolicy {
|
||||||
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
|
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
|
||||||
/// importation.
|
/// importation.
|
||||||
@@ -50,7 +64,7 @@ impl PinPolicy {
|
|||||||
/// addition to the [`PinPolicy`].
|
/// addition to the [`PinPolicy`].
|
||||||
///
|
///
|
||||||
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum TouchPolicy {
|
pub enum TouchPolicy {
|
||||||
/// Use the default touch policy for the slot.
|
/// Use the default touch policy for the slot.
|
||||||
Default,
|
Default,
|
||||||
@@ -90,3 +104,17 @@ impl TouchPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for TouchPolicy {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(TouchPolicy::Default),
|
||||||
|
1 => Ok(TouchPolicy::Never),
|
||||||
|
2 => Ok(TouchPolicy::Always),
|
||||||
|
3 => Ok(TouchPolicy::Cached),
|
||||||
|
_ => Err(Error::GenericError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,6 @@
|
|||||||
use crate::{Result, YubiKey};
|
use crate::{Result, YubiKey};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
convert::TryInto,
|
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@@ -45,6 +44,7 @@ impl Context {
|
|||||||
c.list_readers(reader_names)?.collect()
|
c.list_readers(reader_names)?.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::needless_collect)]
|
||||||
let readers: Vec<_> = reader_cstrs
|
let readers: Vec<_> = reader_cstrs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| Reader::new(name, Arc::clone(ctx)))
|
.map(|name| Reader::new(name, Arc::clone(ctx)))
|
||||||
|
|||||||
+2
-11
@@ -65,7 +65,7 @@ impl Default for SettingSource {
|
|||||||
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
|
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
|
||||||
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
|
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
|
||||||
/// variables.
|
/// variables.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub struct Setting {
|
pub struct Setting {
|
||||||
/// Boolean value
|
/// Boolean value
|
||||||
pub value: bool,
|
pub value: bool,
|
||||||
@@ -99,7 +99,7 @@ impl Setting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (name, value) = {
|
let (name, value) = {
|
||||||
let mut parts = line.splitn(1, '=');
|
let mut parts = line.splitn(2, '=');
|
||||||
let name = parts.next();
|
let name = parts.next();
|
||||||
let value = parts.next();
|
let value = parts.next();
|
||||||
match (name, value, parts.next()) {
|
match (name, value, parts.next()) {
|
||||||
@@ -130,12 +130,3 @@ impl Setting {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Setting {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
value: false,
|
|
||||||
source: SettingSource::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-5
@@ -11,7 +11,6 @@ use crate::{
|
|||||||
Buffer, ObjectId,
|
Buffer, ObjectId,
|
||||||
};
|
};
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use std::convert::TryInto;
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
@@ -70,7 +69,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
pub fn select_application(&self) -> Result<()> {
|
pub fn select_application(&self) -> Result<()> {
|
||||||
let response = Apdu::new(Ins::SelectApplication)
|
let response = Apdu::new(Ins::SelectApplication)
|
||||||
.p1(0x04)
|
.p1(0x04)
|
||||||
.data(&PIV_AID)
|
.data(PIV_AID)
|
||||||
.transmit(self, 0xFF)
|
.transmit(self, 0xFF)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("failed communicating with card: '{}'", e);
|
error!("failed communicating with card: '{}'", e);
|
||||||
@@ -110,7 +109,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
// YK4 requires switching to the yk applet to retrieve the serial
|
// YK4 requires switching to the yk applet to retrieve the serial
|
||||||
let sw = Apdu::new(Ins::SelectApplication)
|
let sw = Apdu::new(Ins::SelectApplication)
|
||||||
.p1(0x04)
|
.p1(0x04)
|
||||||
.data(&YK_AID)
|
.data(YK_AID)
|
||||||
.transmit(self, 0xFF)?
|
.transmit(self, 0xFF)?
|
||||||
.status_words();
|
.status_words();
|
||||||
|
|
||||||
@@ -132,7 +131,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
// reselect the PIV applet
|
// reselect the PIV applet
|
||||||
let sw = Apdu::new(Ins::SelectApplication)
|
let sw = Apdu::new(Ins::SelectApplication)
|
||||||
.p1(0x04)
|
.p1(0x04)
|
||||||
.data(&PIV_AID)
|
.data(PIV_AID)
|
||||||
.transmit(self, 0xFF)?
|
.transmit(self, 0xFF)?
|
||||||
.status_words();
|
.status_words();
|
||||||
|
|
||||||
@@ -247,7 +246,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
|
|
||||||
let status_words = Apdu::new(Ins::SetMgmKey)
|
let status_words = Apdu::new(Ins::SetMgmKey)
|
||||||
.params(0xff, p2)
|
.params(0xff, p2)
|
||||||
.data(&data)
|
.data(data)
|
||||||
.transmit(self, 261)?
|
.transmit(self, 261)?
|
||||||
.status_words();
|
.status_words();
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -45,7 +45,6 @@ use log::{error, info};
|
|||||||
use pcsc::Card;
|
use pcsc::Card;
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use std::{
|
use std::{
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
@@ -286,7 +285,7 @@ impl YubiKey {
|
|||||||
// get a challenge from the card
|
// get a challenge from the card
|
||||||
let challenge = Apdu::new(Ins::Authenticate)
|
let challenge = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(ALGO_3DES, KEY_CARDMGM)
|
||||||
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
|
.data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
|
||||||
.transmit(&txn, 261)?;
|
.transmit(&txn, 261)?;
|
||||||
|
|
||||||
if !challenge.is_success() || challenge.data().len() < 12 {
|
if !challenge.is_success() || challenge.data().len() < 12 {
|
||||||
@@ -311,7 +310,7 @@ impl YubiKey {
|
|||||||
|
|
||||||
let authentication = Apdu::new(Ins::Authenticate)
|
let authentication = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(ALGO_3DES, KEY_CARDMGM)
|
||||||
.data(&data)
|
.data(data)
|
||||||
.transmit(&txn, 261)?;
|
.transmit(&txn, 261)?;
|
||||||
|
|
||||||
if !authentication.is_success() {
|
if !authentication.is_success() {
|
||||||
@@ -476,12 +475,12 @@ impl YubiKey {
|
|||||||
/// Block PUK: permanently prevent the PIN from becoming unblocked.
|
/// Block PUK: permanently prevent the PIN from becoming unblocked.
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||||
pub fn block_puk(yubikey: &mut YubiKey) -> Result<()> {
|
pub fn block_puk(&mut self) -> Result<()> {
|
||||||
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
|
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
|
||||||
let mut tries_remaining: i32 = -1;
|
let mut tries_remaining: i32 = -1;
|
||||||
let mut flags = [0];
|
let mut flags = [0];
|
||||||
|
|
||||||
let txn = yubikey.begin_transaction()?;
|
let txn = self.begin_transaction()?;
|
||||||
|
|
||||||
while tries_remaining != 0 {
|
while tries_remaining != 0 {
|
||||||
// 2 -> change puk
|
// 2 -> change puk
|
||||||
@@ -569,7 +568,7 @@ impl YubiKey {
|
|||||||
|
|
||||||
let response = Apdu::new(Ins::Authenticate)
|
let response = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(ALGO_3DES, KEY_CARDMGM)
|
||||||
.data(&[0x7c, 0x02, 0x81, 0x00])
|
.data([0x7c, 0x02, 0x81, 0x00])
|
||||||
.transmit(&txn, 261)?;
|
.transmit(&txn, 261)?;
|
||||||
|
|
||||||
if !response.is_success() {
|
if !response.is_success() {
|
||||||
@@ -595,7 +594,7 @@ impl YubiKey {
|
|||||||
// send the response to the card and a challenge of our own.
|
// send the response to the card and a challenge of our own.
|
||||||
let status_words = Apdu::new(Ins::Authenticate)
|
let status_words = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(ALGO_3DES, KEY_CARDMGM)
|
||||||
.data(&data)
|
.data(data)
|
||||||
.transmit(&txn, 261)?
|
.transmit(&txn, 261)?
|
||||||
.status_words();
|
.status_words();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number:
|
||||||
|
d4:29:8f:df:8a:af:7b:c0:d7:bf:19:9d:90:d5:ef:ca
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
Issuer: CN=Ferdinand Linnenberg CA
|
||||||
|
Validity
|
||||||
|
Not Before: Feb 10 12:25:37 2022 GMT
|
||||||
|
Not After : May 15 12:25:37 2024 GMT
|
||||||
|
Subject: CN=Bob
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:d5:27:9b:99:1b:3a:36:64:36:c8:e5:78:64:b6:
|
||||||
|
9d:70:9d:29:6c:0e:85:91:4b:78:3b:dc:16:c3:09:
|
||||||
|
8c:d3:74:20:8c:6f:ed:c3:90:c9:1b:4d:80:d5:46:
|
||||||
|
da:52:7f:d2:2f:bc:b2:f7:40:8d:ad:dd:24:b9:5c:
|
||||||
|
dc:a2:21:2f:48:ec:06:93:8b:89:f0:cd:63:ff:a1:
|
||||||
|
fd:ce:36:d5:07:7a:1e:0e:cf:68:a8:c1:b3:7f:62:
|
||||||
|
84:b7:e1:cf:25:7b:3f:a8:3c:ac:07:1a:fd:c2:e1:
|
||||||
|
e0:9e:26:24:c1:0d:6d:9d:c6:57:6a:b4:39:28:3d:
|
||||||
|
88:3e:c9:6a:89:90:72:4a:7b:75:c5:5e:1b:5e:5c:
|
||||||
|
32:54:a3:ff:eb:01:68:7f:89:b4:4c:01:3f:08:8e:
|
||||||
|
6c:61:49:60:26:0b:26:58:81:d7:1a:57:ee:52:5c:
|
||||||
|
05:47:de:da:eb:b5:92:9d:5b:ce:26:18:44:59:3e:
|
||||||
|
27:d0:61:86:e2:f4:c6:d9:c7:2b:1f:cb:ea:78:f0:
|
||||||
|
a1:a9:57:d7:98:4c:c1:2f:ae:6a:38:b4:34:53:2e:
|
||||||
|
5a:9e:f8:58:c7:51:e7:fd:b8:27:cd:87:72:26:c1:
|
||||||
|
7d:14:c7:cd:fb:f2:04:8a:c4:8f:61:cf:a8:78:bd:
|
||||||
|
21:be:28:cb:e8:a8:65:29:28:82:46:2f:18:e6:ff:
|
||||||
|
6f:53
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
B5:A5:F0:37:25:97:AD:BE:F1:43:52:45:4D:8B:A0:5E:E9:78:21:B8
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:26:4E:EB:B0:A5:1B:08:A8:90:2A:85:04:73:84:B5:A5:2C:61:D6:91
|
||||||
|
DirName:/CN=Ferdinand Linnenberg CA
|
||||||
|
serial:8C:E0:40:D9:D8:60:E5:77
|
||||||
|
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
19:f3:eb:c1:95:e6:d5:a9:33:d7:2e:02:d8:3a:91:84:81:14:
|
||||||
|
93:fc:03:4d:b1:4b:9d:0b:9b:94:93:9f:1a:0d:87:31:a1:fa:
|
||||||
|
a6:c7:3a:6b:18:24:12:ab:28:fb:c8:e3:09:a2:5d:50:49:00:
|
||||||
|
d9:18:e6:4a:09:18:e0:1c:da:d3:19:96:3d:74:72:fe:e0:8f:
|
||||||
|
ee:59:54:66:2e:57:72:b8:91:55:06:13:e5:9e:89:a2:3a:13:
|
||||||
|
3b:45:30:d3:cd:15:0e:81:eb:4f:b0:6a:a4:6d:00:7d:5b:c0:
|
||||||
|
4a:7f:97:d0:27:27:31:ae:3e:72:f1:74:fe:86:8e:29:a9:42:
|
||||||
|
23:26:22:db:08:8b:df:e9:d3:83:8d:81:10:36:d7:33:68:5e:
|
||||||
|
cb:93:cb:1e:12:c8:cb:be:5e:5c:8e:58:b0:1d:06:5e:c9:98:
|
||||||
|
b7:f1:49:fe:c4:03:de:b4:2b:da:9d:2c:7d:98:37:1c:6c:a8:
|
||||||
|
95:21:6f:23:e3:2e:09:bc:6c:e5:ed:e2:50:d8:f7:da:45:39:
|
||||||
|
d8:34:8a:57:0c:4f:d0:0d:80:06:d6:34:63:72:27:d1:50:d1:
|
||||||
|
d2:21:2c:97:57:17:98:02:95:3a:96:ed:75:9f:cc:f3:b8:f1:
|
||||||
|
3a:85:f9:58:08:9b:a0:75:fd:9b:fd:31:dd:08:dc:14:3d:f4:
|
||||||
|
68:aa:d4:30
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXzCCAkegAwIBAgIRANQpj9+Kr3vA178ZnZDV78owDQYJKoZIhvcNAQELBQAw
|
||||||
|
IjEgMB4GA1UEAwwXRmVyZGluYW5kIExpbm5lbmJlcmcgQ0EwHhcNMjIwMjEwMTIy
|
||||||
|
NTM3WhcNMjQwNTE1MTIyNTM3WjAOMQwwCgYDVQQDDANCb2IwggEiMA0GCSqGSIb3
|
||||||
|
DQEBAQUAA4IBDwAwggEKAoIBAQDVJ5uZGzo2ZDbI5Xhktp1wnSlsDoWRS3g73BbD
|
||||||
|
CYzTdCCMb+3DkMkbTYDVRtpSf9IvvLL3QI2t3SS5XNyiIS9I7AaTi4nwzWP/of3O
|
||||||
|
NtUHeh4Oz2iowbN/YoS34c8lez+oPKwHGv3C4eCeJiTBDW2dxldqtDkoPYg+yWqJ
|
||||||
|
kHJKe3XFXhteXDJUo//rAWh/ibRMAT8IjmxhSWAmCyZYgdcaV+5SXAVH3trrtZKd
|
||||||
|
W84mGERZPifQYYbi9MbZxysfy+p48KGpV9eYTMEvrmo4tDRTLlqe+FjHUef9uCfN
|
||||||
|
h3ImwX0Ux8378gSKxI9hz6h4vSG+KMvoqGUpKIJGLxjm/29TAgMBAAGjgaMwgaAw
|
||||||
|
CQYDVR0TBAIwADAdBgNVHQ4EFgQUtaXwNyWXrb7xQ1JFTYugXul4IbgwUgYDVR0j
|
||||||
|
BEswSYAUJk7rsKUbCKiQKoUEc4S1pSxh1pGhJqQkMCIxIDAeBgNVBAMMF0ZlcmRp
|
||||||
|
bmFuZCBMaW5uZW5iZXJnIENBggkAjOBA2dhg5XcwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||||
|
AwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBAQAZ8+vBlebVqTPXLgLY
|
||||||
|
OpGEgRST/ANNsUudC5uUk58aDYcxofqmxzprGCQSqyj7yOMJol1QSQDZGOZKCRjg
|
||||||
|
HNrTGZY9dHL+4I/uWVRmLldyuJFVBhPlnomiOhM7RTDTzRUOgetPsGqkbQB9W8BK
|
||||||
|
f5fQJycxrj5y8XT+ho4pqUIjJiLbCIvf6dODjYEQNtczaF7Lk8seEsjLvl5cjliw
|
||||||
|
HQZeyZi38Un+xAPetCvanSx9mDccbKiVIW8j4y4JvGzl7eJQ2PfaRTnYNIpXDE/Q
|
||||||
|
DYAG1jRjcifRUNHSISyXVxeYApU6lu11n8zzuPE6hflYCJugdf2b/THdCNwUPfRo
|
||||||
|
qtQw
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIi6DixMpf5PQCAggA
|
||||||
|
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECLgvCsIAjjXRBIIEyDAss0V4NrI5
|
||||||
|
W7KXPRgRJ1tqvQrWZQTIFu4Crzs9Inb4TtSv5mATI9ZU2RMFF6MYXlhIxJng861P
|
||||||
|
5IWcU6VeOjRFej8wcB3uTvD2z7NB2cyA5BZSojrZfX5OIEKL9sBzn0vinqmm5N1z
|
||||||
|
oXhLMgf0FZssA3+zjIf04vtvmk5pxTCE6dq6vlsEIJyQ0xGc39bStIwk2a4E9wvi
|
||||||
|
XayKNJnRFSrTahuI3DvQJPd9TdmM6sBKnrcJrDa6LvH51SrGzW8bBEjDAmC7yJdi
|
||||||
|
mBckFTjZ6rGrSxOI6HdnF9RP+y9OiLE4ou5OJ9MbBtngq9OrTImAMZ5ftWowaqX/
|
||||||
|
Do1UTqixi4ecWrr5fr1+A2Vch0I1drds2e/mmLR+5GEQXQXZPZKjtPMwxM/AYnTZ
|
||||||
|
w/M3T9KtwSj0s5G6Saz4WpzaUL7wATb/UNqMr8Ifl8mHEVoFZhvoRpMWA7Yj1oPb
|
||||||
|
cHz8lsfoSrTnK+zLR8ZK3HRu4MtpdCNVwQIJ67T6Feb8YLwYSccTNHBSqUmWRD95
|
||||||
|
wOOqY33xcfplaQ2Y+/8+mHScGSEPNmC4f7EggDeUnG3ow0f4n95DENO+aYqGLjSF
|
||||||
|
+XdCjhD+NTNdlV0z50B6P2XWUoBZOOnPfgFf4nAgn7xQbkZZOk5bQDKo2Zu9jW9/
|
||||||
|
uJyTHqI58tcopI3cjd5iQXOJUrM4OWpPIu3p+VWaIAA8JzJIfDN6fyQ3qsMr305L
|
||||||
|
30JcjrH6if/6J+2g+DpMAK928JY2hfE8VNWH7096ZnArp42/hLYsNuYnSrL1eS+g
|
||||||
|
F/4mvyZyLLTFyB0Frnic4I1QTuNkNmwSrm/B5wIWLqkS7XAyyDDXAcaTHdZCN6nM
|
||||||
|
O2OuF7DfBsFcNMM4VagG5adPS9CYkvz0EEh6ho5XiP2yL3tZfsHyuB4njAsV3aFi
|
||||||
|
D0Yq7QiCf5iA2d4KsYO6yr1wPfVhlsmPi3++mrHulBwwCQWHTlPgRZnjj1xgmPcQ
|
||||||
|
00KsUVh+CMWlf20O5sKhzjvkzbwUj1K+ZfMDuuq1RbzFRSx+Gx8vIaThFg9kVyoP
|
||||||
|
zuvzsT6qc3BGNHmaGZ3d5Re25AuGRTF4cTpDfjW0UL7Wnvnis7iMrUasDhyF7CFn
|
||||||
|
/KG7eKzxqS08o6D4AM5S/fzZEtszoEgAga6DS2R75FVskDweWuEIsar9UGg3UlmW
|
||||||
|
q3+rRPRf1CzrLtyYenkkLg/ajr8JOnFGqZVaLmMnegZQH6rF3aEzlQLNgbNepcuA
|
||||||
|
ObSmAO6MR3MlQgdsH/lNzOPdj1gKcE25hOjGfmwgbOXSJv9Cz0bcBLFEyLZSNpRk
|
||||||
|
HhNejj6BEz/Cmqg1wm7SOBHsXJGcOTnLO1Y3FBt0I7heWvWmj9rOLG9tvvx9dtrP
|
||||||
|
pQyDbIcWonuXLrXYSPyOjmeWoQSzdH3NsCswBV4G+iOiLCJDkElR+mrwKbhtS88T
|
||||||
|
3YeZnsCTsmH+jZpxGgPTObjIG91U4UE4Pnkwc2df355VuOKrf0rB/NK0A9hZGqsV
|
||||||
|
nxtodjn92P6UFzmfEdt95pMcmurK9wkm1kRkP7cIyAs2lCOIdbgGsszz6Mk16Xqy
|
||||||
|
49RdhLxJrJ4gkYZIAbY+KNGVc7uPhm/T9xGrIstbEsoUM7jy7nHMOCCDgdbwX/4X
|
||||||
|
caMeSMZcZ+RvraDDBEbSbg==
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
+159
-26
@@ -3,37 +3,39 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
|
use rsa::pkcs1v15;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::{convert::TryInto, env, sync::Mutex};
|
use signature::{hazmat::PrehashVerifier, Signature as _};
|
||||||
|
use std::{env, str::FromStr, sync::Mutex};
|
||||||
use x509::RelativeDistinguishedName;
|
use x509::RelativeDistinguishedName;
|
||||||
use yubikey::{
|
use yubikey::{
|
||||||
|
certificate,
|
||||||
certificate::{Certificate, PublicKeyInfo},
|
certificate::{Certificate, PublicKeyInfo},
|
||||||
piv::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
|
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
|
||||||
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey,
|
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
|
||||||
/// Provide thread-safe access to a YubiKey
|
|
||||||
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One-time test initialization and setup
|
|
||||||
fn init_yubikey() -> Mutex<YubiKey> {
|
|
||||||
// Only show logs if `RUST_LOG` is set
|
// Only show logs if `RUST_LOG` is set
|
||||||
if env::var("RUST_LOG").is_ok() {
|
if env::var("RUST_LOG").is_ok() {
|
||||||
env_logger::builder().format_timestamp(None).init();
|
env_logger::builder().format_timestamp(None).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
let yubikey = YubiKey::open().unwrap();
|
let yubikey = if let Ok(serial) = env::var("YUBIKEY_SERIAL") {
|
||||||
|
let serial = Serial::from_str(&serial).unwrap();
|
||||||
|
YubiKey::open_by_serial(serial).unwrap()
|
||||||
|
} else {
|
||||||
|
YubiKey::open().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
trace!("serial: {}", yubikey.serial());
|
trace!("serial: {}", yubikey.serial());
|
||||||
trace!("version: {}", yubikey.version());
|
trace!("version: {}", yubikey.version());
|
||||||
|
|
||||||
Mutex::new(yubikey)
|
Mutex::new(yubikey)
|
||||||
}
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// CCCID support
|
// CCCID support
|
||||||
@@ -194,26 +196,17 @@ fn generate_self_signed_rsa_cert() {
|
|||||||
//
|
//
|
||||||
|
|
||||||
let pubkey = match cert.subject_pki() {
|
let pubkey = match cert.subject_pki() {
|
||||||
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
|
PublicKeyInfo::Rsa { pubkey, .. } => pkcs1v15::VerifyingKey::<Sha256>::from(pubkey.clone()),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = cert.as_ref();
|
let data = cert.as_ref();
|
||||||
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
|
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
|
||||||
let msg = &data[4..8 + tbs_cert_len];
|
let msg = &data[4..8 + tbs_cert_len];
|
||||||
let sig = &data[data.len() - 128..];
|
let sig = pkcs1v15::Signature::from_bytes(&data[data.len() - 128..]).unwrap();
|
||||||
|
|
||||||
let hash = Sha256::digest(msg);
|
let hash = Sha256::digest(msg);
|
||||||
|
|
||||||
assert!(pubkey
|
assert!(pubkey.verify_prehash(&hash, &sig).is_ok());
|
||||||
.verify(
|
|
||||||
PaddingScheme::PKCS1v15Sign {
|
|
||||||
hash: Some(SHA2_256)
|
|
||||||
},
|
|
||||||
&hash,
|
|
||||||
sig
|
|
||||||
)
|
|
||||||
.is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -241,3 +234,143 @@ fn generate_self_signed_ec_cert() {
|
|||||||
use p256::ecdsa::signature::Verifier;
|
use p256::ecdsa::signature::Verifier;
|
||||||
assert!(vk.verify(msg, &sig).is_ok());
|
assert!(vk.verify(msg, &sig).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_slot_id_display() {
|
||||||
|
assert_eq!(format!("{}", SlotId::Authentication), "Authentication");
|
||||||
|
assert_eq!(format!("{}", SlotId::Signature), "Signature");
|
||||||
|
assert_eq!(format!("{}", SlotId::KeyManagement), "KeyManagement");
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", SlotId::CardAuthentication),
|
||||||
|
"CardAuthentication"
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{}", SlotId::Attestation), "Attestation");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R1)), "R1");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R2)), "R2");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R3)), "R3");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R4)), "R4");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R5)), "R5");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R6)), "R6");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R7)), "R7");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R8)), "R8");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R9)), "R9");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R10)), "R10");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R11)), "R11");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R12)), "R12");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R13)), "R13");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R14)), "R14");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R15)), "R15");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R16)), "R16");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R17)), "R17");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R18)), "R18");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R19)), "R19");
|
||||||
|
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R20)), "R20");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", SlotId::Management(ManagementSlotId::Pin)),
|
||||||
|
"PIN"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", SlotId::Management(ManagementSlotId::Puk)),
|
||||||
|
"PUK"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", SlotId::Management(ManagementSlotId::Management)),
|
||||||
|
"Management"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Metadata
|
||||||
|
//
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_read_metadata() {
|
||||||
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
|
|
||||||
|
assert!(yubikey.verify_pin(b"123456").is_ok());
|
||||||
|
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
|
||||||
|
|
||||||
|
let slot = SlotId::Retired(RetiredSlotId::R1);
|
||||||
|
|
||||||
|
// Generate a new key in the selected slot.
|
||||||
|
let generated = piv::generate(
|
||||||
|
&mut yubikey,
|
||||||
|
slot,
|
||||||
|
AlgorithmId::EccP256,
|
||||||
|
PinPolicy::Default,
|
||||||
|
TouchPolicy::Default,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let metadata = piv::metadata(&mut yubikey, slot).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(metadata.public, Some(generated));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_serial_string_conversions() {
|
||||||
|
//2^152+1
|
||||||
|
let serial: [u8; 20] = [
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
let s = certificate::Serial::from(serial);
|
||||||
|
assert_eq!(
|
||||||
|
s.as_x509_int(),
|
||||||
|
"5708990770823839524233143877797980545530986497"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
s.as_x509_hex(),
|
||||||
|
"01:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01"
|
||||||
|
);
|
||||||
|
|
||||||
|
let serial2: [u8; 20] = [
|
||||||
|
0xA1, 0xF3, 0x02, 0x30, 0x76, 0x01, 0x32, 0x48, 0x09, 0x9C, 0x10, 0xAA, 0x3F, 0xA0, 0x54,
|
||||||
|
0x0D, 0xC0, 0xB7, 0x65, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
let s2 = certificate::Serial::from(serial2);
|
||||||
|
assert_eq!(
|
||||||
|
s2.as_x509_int(),
|
||||||
|
"924566785900861696177829411010986812227211191553"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
s2.as_x509_hex(),
|
||||||
|
"a1:f3:02:30:76:01:32:48:09:9c:10:aa:3f:a0:54:0d:c0:b7:65:01"
|
||||||
|
);
|
||||||
|
|
||||||
|
let serial3: [u8; 20] = [
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x3F, 0xA0, 0x54,
|
||||||
|
0x0D, 0xC0, 0xB7, 0x65, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
let s3 = certificate::Serial::from(serial3);
|
||||||
|
assert_eq!(s3.as_x509_int(), "3140531249369331492097");
|
||||||
|
assert_eq!(s3.as_x509_hex(), "aa:3f:a0:54:0d:c0:b7:65:01");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_parse_cert_from_der() {
|
||||||
|
let bob_der = std::fs::read("tests/assets/Bob.der").expect(".der file not found");
|
||||||
|
let cert =
|
||||||
|
certificate::Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
|
||||||
|
assert_eq!(
|
||||||
|
cert.subject(),
|
||||||
|
"CN=Bob",
|
||||||
|
"Subject is {} should be CN=Bob",
|
||||||
|
cert.subject()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cert.issuer(),
|
||||||
|
"CN=Ferdinand Linnenberg CA",
|
||||||
|
"Issuer is {} should be {}",
|
||||||
|
cert.issuer(),
|
||||||
|
"CN=Ferdinand Linnenberg CA"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user