Compare commits

..

49 Commits

Author SHA1 Message Date
Tony Arcieri (iqlusion) 403632df76 yubikey v0.9.0-pre.0 (#666)
This prerelease includes crates from the new RustCrypto release series
including prereleases for crates which haven't yet received a new stable
release.

It also includes preliminary Curve25519/Ed25519 support.
2026-04-02 13:59:53 -06:00
Tony Arcieri (iqlusion) 95babac2d4 Upgrade dependencies; bump version to v0.9.0-pre (#665)
Note: this is not a release, but bumping the version to reflect breaking
changes that have not yet been released.

The following dependencies have been upgraded to new stable releases:
- `cipher` v0.5
- `der` v0.8
- `sha1` v0.11
- `sha2` v0.11
- `rand(_core)` v0.10

The following dependencies are prereleases which have been upgraded from
older prerelease versions:
- `aes` v0.9.0-rc.4
- `curve25519-dalek` 5.0.0-pre.6
- `des` v0.9.0-rc.3
- `ecdsa` v0.17.0-rc.16
- `ed25519-dalek` v3.0.0-pre.6
- `elliptic-curve` v0.14.0-rc.29
- `p256` v0.14.0-rc.8
- `p384` v0.14.0-rc.8
- `pbkdf2` v0.13.0-rc.10
- `rsa` v0.10.0-rc.17
- `signature` v3.0.0-rc.10
- `x25519-dalek` v3.0.0-pre.6
2026-04-02 13:35:37 -06:00
Andrew Lubawy 872ba35f54 Add Curve25519 support (#577)
Supported in PIV applet since firmware 5.7.X
2026-02-18 17:27:43 -07:00
Tony Arcieri (iqlusion) c96b50bcec mgm: extract MgmAlgorithmId::default_for_version (#633)
Adds a private method for determining the MGM key algorithm to use for a
given YubiKey `Version`
2025-12-15 19:59:48 +01:00
dependabot[bot] abcded88cf Bump actions/checkout from 5 to 6 (#648) 2025-12-15 19:59:04 +01:00
dependabot[bot] ff6d5ee56e Bump actions/cache from 4 to 5 (#651) 2025-12-15 18:08:36 +01:00
Robin Lambertz a039431fc9 Implement Hash on Serial (#647) 2025-11-06 12:59:10 -07:00
Arthur Gautier ec78d6b2f7 chore(deps): bump der/pkcs/x509-cert to latest pre-releases (#641) 2025-09-29 14:51:10 -06:00
Tony Arcieri (iqlusion) 49fc8796ab README.md: fix badges and links (#636) 2025-09-15 14:36:07 -06:00
Arthur Gautier efc587c88d bump dependencies to latest pre-releases (#637) 2025-09-11 12:16:04 -06:00
Carl Wallace 9e75924908 Change MgmKey::algorithm_id from pub(crate) to pub (#635) 2025-09-10 10:27:45 -06:00
Jack Grigg dcaf080ef2 mgm: Support AES management keys (#589) 2025-09-01 16:30:40 -06:00
dependabot[bot] 0072b174b4 Bump actions/checkout from 1 to 5 (#628) 2025-08-22 10:34:27 -06:00
dependabot[bot] 74968cbef0 Bump actions/cache from 1 to 4 (#627) 2025-08-22 10:34:14 -06:00
Tony Arcieri (iqlusion) 80968606b2 dependabot: perform consolidated lockfile updates (#626)
Update all dependencies in Cargo.lock, rather than opening individual
PRs for each of them
2025-08-22 09:48:31 -06:00
Tony Arcieri (iqlusion) 1e1fe34734 mgm: Generalize TDES logic to enable other algorithms (#625)
Co-authored-by: Jack Grigg <thestr4d@gmail.com>
Co-authored-by: Greg Bowyer <gbowyer@fastmail.co.uk>
2025-08-22 09:37:41 -06:00
Tony Arcieri (iqlusion) 7eb7a31a28 mgm: remove untested gating from tested methods (#623)
Removes the `#[cfg(feature = "untested")]` gating from all methods
tested in `tests/integration.rs` and their dependent codepaths.
2025-08-18 13:05:23 -06:00
Nazar Serhiichuk 1fc807fdcb Handle reference data not found in metadata command (#558) 2025-08-14 07:37:57 -06:00
Tony Arcieri (iqlusion) b4be1bb216 mgm: use TdesEde3::weak_key_test (#621)
Replaces the vendored weak key test with the upstream one from the `des`
crate which was added in RustCrypto/block-ciphers#465
2025-08-13 15:36:53 -06:00
Arthur Gautier 7f2b423713 chore(deps): bump nom from 7.1.3 to 8.0.0 (#614) 2025-07-21 20:20:45 -06:00
Arthur Gautier f0dbf9425c bump dependencies to latest rc, bump MSRV to 1.85 (#612) 2025-07-21 15:49:31 -06:00
Joost van Dijk 0d8096f50d add support for rsa3072 and rsa4096 (#598) 2025-02-12 14:48:29 -07:00
Arthur Gautier 13bdf9a585 Applets management (#568) 2025-02-11 19:13:01 -07:00
Jack Grigg 235eb6215e Clean up some of the management key code (#584)
* mgm: Move TDES weak key checking code into a submodule
* piv: Extract management key algorithm into a separate enum
* mgm: Check management key algorithm when fetching from Yubikey
2025-02-11 12:19:53 -07:00
dependabot[bot] 19e1cccfec Bump env_logger from 0.10.2 to 0.11.6 (#593) 2025-01-06 07:35:12 -07:00
dependabot[bot] 1af3cbbf91 Bump clap from 4.5.4 to 4.5.23 (#587) 2025-01-03 11:11:27 -07:00
dependabot[bot] 5955001e00 Bump anstream from 0.6.7 to 0.6.18 (#586) 2025-01-02 20:06:18 -07:00
Tony Arcieri (iqlusion) d204051912 clippy fixes (#585)
Runs `cargo clippy --fix` against both crates in the repo
(including `cli`)
2025-01-02 12:55:36 -07:00
Jack Grigg 626ac3bffd Migrate to current pre-release revisions of dependencies (#583)
The CHANGELOG lists the specific versions currently pinned; it will
be modified to instead reference the public releases once they exist
and this crate uses them.
2025-01-02 12:39:52 -07:00
Jack Grigg 32cd92af50 Bump MSRV to 1.81 (#582)
This is required due to the `hybrid-array` crate, which has become a
transitive dependency of the majority of our dependencies and will be
required in the very near future.
2024-11-25 11:58:24 -07:00
dependabot[bot] 0a90dc3ca8 Bump clap from 4.4.18 to 4.5.4 (#564)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.18 to 4.5.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.18...v4.5.4)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 14:53:26 -06:00
Shella Stephens 69b5404370 Fix clippy (#566) 2024-04-04 14:40:51 -06:00
Shella Stephens 2db3ea55c4 MSRV 1.74 (#565) 2024-04-04 11:50:25 -06:00
dependabot[bot] b07612eb4e Bump x509-cert from 0.2.4 to 0.2.5 (#553)
Bumps [x509-cert](https://github.com/RustCrypto/formats) from 0.2.4 to 0.2.5.
- [Commits](https://github.com/RustCrypto/formats/compare/x509-cert/v0.2.4...x509-cert/v0.2.5)

---
updated-dependencies:
- dependency-name: x509-cert
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 15:35:58 -07:00
dependabot[bot] 01eb42bc60 Bump rsa from 0.9.5 to 0.9.6 (#551)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.5 to 0.9.6.
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.5...v0.9.6)

---
updated-dependencies:
- dependency-name: rsa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 09:23:49 -07:00
dependabot[bot] 82cb78aa95 Bump env_logger from 0.10.1 to 0.10.2 (#552)
Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.10.1 to 0.10.2.
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.10.1...v0.10.2)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 15:29:55 -07:00
dependabot[bot] 1c9f71a989 Bump clap from 4.4.16 to 4.4.18 (#550)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.16 to 4.4.18.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.16...v4.4.18)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 15:28:24 -07:00
dependabot[bot] 853677b2d8 Bump once_cell from 1.18.0 to 1.19.0 (#542)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.18.0 to 1.19.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 08:36:10 -07:00
dependabot[bot] 6189de288b Bump rsa from 0.9.4 to 0.9.5 (#539)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.4 to 0.9.5.
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.4...v0.9.5)

---
updated-dependencies:
- dependency-name: rsa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 08:33:58 -07:00
dependabot[bot] 1f0d42218e Bump clap from 4.4.8 to 4.4.16 (#548)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.8 to 4.4.16.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.8...v4.4.16)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 08:29:37 -07:00
dependabot[bot] 164faac609 Bump termcolor from 1.4.0 to 1.4.1 (#549)
Bumps [termcolor](https://github.com/BurntSushi/termcolor) from 1.4.0 to 1.4.1.
- [Commits](https://github.com/BurntSushi/termcolor/compare/1.4.0...1.4.1)

---
updated-dependencies:
- dependency-name: termcolor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 08:28:49 -07:00
Carl Wallace c0f3a2f841 add length check to get_version (#545) 2023-12-20 07:42:27 -07:00
Shella Stephens 3e31fe8663 .cargo/audit.toml: ignore RUSTSEC-2023-0071 (#541) 2023-12-06 08:49:24 -07:00
Shella Stephens 385db11522 Cargo.lock: Update dependencies (#538)
* Cargo.lock: Update dependencies

* MSRV 1.70.0
2023-11-20 20:10:40 -07:00
dependabot[bot] c1dc4a4319 Bump termcolor from 1.2.0 to 1.3.0 (#525)
Bumps [termcolor](https://github.com/BurntSushi/termcolor) from 1.2.0 to 1.3.0.
- [Commits](https://github.com/BurntSushi/termcolor/compare/1.2.0...1.3.0)

---
updated-dependencies:
- dependency-name: termcolor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:34:15 -06:00
dependabot[bot] 3d78874a3b Bump sha2 from 0.10.7 to 0.10.8 (#527)
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.7 to 0.10.8.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.7...sha2-v0.10.8)

---
updated-dependencies:
- dependency-name: sha2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:34:00 -06:00
dependabot[bot] f04b8592ec Bump sha1 from 0.10.5 to 0.10.6 (#523)
Bumps [sha1](https://github.com/RustCrypto/hashes) from 0.10.5 to 0.10.6.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha1-v0.10.5...sha1-v0.10.6)

---
updated-dependencies:
- dependency-name: sha1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:33:48 -06:00
dependabot[bot] 0c57c06294 Bump elliptic-curve from 0.13.5 to 0.13.6 (#529)
Bumps [elliptic-curve](https://github.com/RustCrypto/traits) from 0.13.5 to 0.13.6.
- [Commits](https://github.com/RustCrypto/traits/compare/elliptic-curve/v0.13.5...elliptic-curve-v0.13.6)

---
updated-dependencies:
- dependency-name: elliptic-curve
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 08:09:18 -06:00
dependabot[bot] 885528a3d6 Bump rsa from 0.9.2 to 0.9.3 (#530)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.2 to 0.9.3.
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: rsa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 08:08:54 -06:00
27 changed files with 2530 additions and 958 deletions
+1
View File
@@ -2,4 +2,5 @@
ignore = [ ignore = [
"RUSTSEC-2020-0071", # chrono "RUSTSEC-2020-0071", # chrono
"RUSTSEC-2021-0145", # atty "RUSTSEC-2021-0145", # atty
"RUSTSEC-2023-0071", # rsa: Marvin Attack: potential key recovery
] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...] ] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...]
+17 -6
View File
@@ -1,8 +1,19 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: cargo - package-ecosystem: cargo
directory: "/" versioning-strategy: lockfile-only
schedule: directory: "/"
interval: weekly allow:
time: '13:00' - dependency-type: "all"
open-pull-requests-limit: 10 groups:
all-deps:
patterns:
- "*"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
+8 -8
View File
@@ -13,7 +13,7 @@ jobs:
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
@@ -36,17 +36,17 @@ jobs:
toolchain: stable toolchain: stable
deps: true deps: true
- platform: ubuntu-latest - platform: ubuntu-latest
toolchain: 1.65.0 # MSRV toolchain: 1.85.0 # MSRV
deps: sudo apt-get install libpcsclite-dev deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest - platform: windows-latest
toolchain: 1.65.0 # MSRV toolchain: 1.85.0 # MSRV
deps: true deps: true
- platform: macos-latest - platform: macos-latest
toolchain: 1.65.0 # MSRV toolchain: 1.85.0 # MSRV
deps: true deps: true
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
@@ -60,7 +60,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v1 uses: actions/checkout@v6
- name: Install stable toolchain - name: Install stable toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -78,11 +78,11 @@ jobs:
clippy: clippy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.71.0 toolchain: 1.85.0 # MSRV
components: clippy components: clippy
override: true override: true
- run: sudo apt-get install libpcsclite-dev - run: sudo apt-get install libpcsclite-dev
+3 -3
View File
@@ -15,16 +15,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v1 uses: actions/checkout@v6
- name: Cache cargo registry - name: Cache cargo registry
uses: actions/cache@v1 uses: actions/cache@v5
with: with:
path: ~/.cargo/registry path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index - name: Cache cargo index
uses: actions/cache@v1 uses: actions/cache@v5
with: with:
path: ~/.cargo/git path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }}
+43
View File
@@ -4,6 +4,49 @@ 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).
## Unreleased
### Added
- `yubikey::certificate::SelfSigned`
- `yubikey::Error::CertificateBuilder`
- `yubikey::MgmAlgorithmId`
- `yubikey::mgm`:
- `MgmKey::generate_for`
- `MgmKey::get_default`
- `impl AsRef<[u8]> for MgmKey`
### Changed
- MSRV is now 1.81.
- Migrated the public API to the following (pre-release) dependencies:
- `der 0.8.0-rc.1`
- `ecdsa 0.17.0-pre.9`
- `p256 0.14.0-pre.2`
- `p384 0.14.0-pre.2`
- `rsa 0.10.0-pre.3`
- `sha2 0.11.0-pre.4`
- `x509-cert 0.3.0-pre.0`
- `yubikey::mgm`:
- `MgmKey::generate` now takes a `rand::TryCryptoRng` argument.
- `MgmKey::generate` now requires the caller to specify the key algorithm via
an `MgmAlgorithmId` parameter.
- Use `MgmKey::generate_for` if you want to generate a key using the
preferred algorithm for a given Yubikey's firmware version.
- `MgmKey::from_bytes` now takes an `Option<MgmAlgorithmId>` argument, to
disambiguate algorithms with the same key length.
- `yubikey::piv`:
- `ManagementAlgorithmId` has been renamed to `SlotAlgorithmId`, and its
`ThreeDes` variant has been replaced by `SlotAlgorithmId::Management`
containing a `yubikey::MgmAlgorithmId`.
- Metadata command returns `Error:NotFound` instead of `Error::GenericError` when the object doesn't exist ([#558]).
### Removed
- `yubikey::mgm`:
- `MgmKey::new` (use `MgmKey::from_bytes(_, Some(MgmAlgorithmId::ThreeDes))`
instead).
- `impl AsRef<[u8; DES_LEN_3DES]> for MgmKey` (use
`impl AsRef<[u8]> for MgmKey` instead).
- `impl Default for MgmKey` (use `MgmKey::get_default` instead).
- `impl TryFrom<&[u8]> for MgmKey` (use `MgmKey::from_bytes` instead).
## 0.8.0 (2023-08-15) ## 0.8.0 (2023-08-15)
### Added ### Added
- `impl Debug for {Context, YubiKey}` ([#457]) - `impl Debug for {Context, YubiKey}` ([#457])
Generated
+814 -399
View File
File diff suppressed because it is too large Load Diff
+30 -24
View File
@@ -1,10 +1,10 @@
[package] [package]
name = "yubikey" name = "yubikey"
version = "0.8.0" version = "0.9.0-pre.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
the Personal Identity Verification (PIV) application. Supports RSA (1024/2048) the Personal Identity Verification (PIV) application. Supports RSA (1024/2048/3072/4096)
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"]
@@ -14,49 +14,55 @@ readme = "README.md"
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"] categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"] keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021" edition = "2021"
rust-version = "1.65" rust-version = "1.85"
[workspace] [workspace]
members = [".", "cli"] members = [".", "cli"]
[workspace.dependencies] [workspace.dependencies]
x509-cert = { version = "0.2.3", features = [ "builder", "hazmat" ] } sha2 = "0.11"
x509-cert = { version = "0.3.0-rc.4", features = ["builder", "hazmat"] }
[dependencies] [dependencies]
der = "0.7.1" aes = { version = "0.9.0-rc.4", features = ["zeroize"] }
des = "0.8" bitflags = "2.5.0"
elliptic-curve = "0.13" cipher = { version = "0.5", features = ["getrandom", "rand_core"] }
curve25519-dalek = "5.0.0-pre.6"
der = "0.8"
des = "0.9.0-rc.3"
ecdsa = { version = "0.17.0-rc.16", features = ["digest", "pem"] }
ed25519-dalek = { version = "3.0.0-pre.6", features = ["alloc", "pkcs8"] }
elliptic-curve = "0.14.0-rc.29"
hex = { package = "base16ct", version = "0.2", features = ["alloc"] } hex = { package = "base16ct", version = "0.2", features = ["alloc"] }
hmac = "0.12"
log = "0.4" log = "0.4"
nom = "7" nom = "8"
num-bigint-dig = { version = "0.8", features = ["rand"] } p256 = "0.14.0-rc.8"
num-traits = "0.2" p384 = "0.14.0-rc.8"
num-integer = "0.1" pbkdf2 = { version = "0.13.0-rc.10", default-features = false, features = ["hmac"] }
ecdsa = { version = "0.16.7", features = ["digest", "pem"] }
p256 = "0.13"
p384 = "0.13"
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
pcsc = "2.3.1" pcsc = "2.3.1"
rand_core = { version = "0.6", features = ["std"] } rand = "0.10"
rsa = { version = "0.9.2", features = ["sha2"] } rand_core = "0.10"
secrecy = "0.8" rsa = { version = "0.10.0-rc.17", features = ["sha2"] }
sha1 = { version = "0.10", features = ["oid"] } sha1 = { version = "0.11", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] } sha2 = { workspace = true, features = ["oid"] }
signature = "2" signature = "3.0.0-rc.10"
subtle = "2" subtle = "2"
uuid = { version = "1.2", features = ["v4"] } uuid = { version = "1.2", features = ["v4"] }
x25519-dalek = "3.0.0-pre.6"
x509-cert.workspace = true x509-cert.workspace = true
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.10" env_logger = "0.11"
once_cell = "1" once_cell = "1"
signature = "2"
[features] [features]
untested = [] untested = []
[[example]]
name = "change-mode"
required-features = ["untested"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
+18 -15
View File
@@ -22,7 +22,7 @@ access provided by the [`pcsc` crate].
## About ## About
YubiKeys are versatile devices and through their PIV support, you can use them YubiKeys are versatile devices and through their PIV support, you can use them
to store a number of RSA (2048/1024) and ECC (NIST P-256/P-384) private keys to store a number of RSA (1024/2048/3072/4096) and ECC (NIST P-256/P-384) private keys
with configurable access control policies. Both the signing (RSASSA/ECDSA) and with configurable access control policies. Both the signing (RSASSA/ECDSA) and
encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type. encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
@@ -54,15 +54,19 @@ 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.
### Supported Algorithms ### Supported Algorithms
- **Authentication**: `3DES` - **Authentication**: `3DES`
- **Encryption**: - **Encryption**:
- RSA: `RSA1024`, `RSA2048` - RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384) - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**: - **Signatures**:
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048` - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384) - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD) NOTE:
- RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
- `RSA3072` and `RSA4096` require a YubiKey with firmware 5.7 or newer.
## Minimum Supported Rust Version ## Minimum Supported Rust Version
@@ -73,8 +77,7 @@ Rust **1.60** or newer.
- [YubiKey 4] series - [YubiKey 4] series
- [YubiKey 5] series - [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported. NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Supported Operating Systems ## Supported Operating Systems
@@ -102,8 +105,8 @@ We would appreciate any help testing this functionality and removing the
## 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 supported YubiKey device connected
the default state (i.e. default PIN/PUK). which is in the default state (i.e. default PIN/PUK).
Tests which run live against a YubiKey device are marked as `#[ignore]` by 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
@@ -172,7 +175,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-2023 Yubico AB, Tony Arcieri Copyright (c) 2014-2025 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
@@ -208,16 +211,16 @@ or conditions.
[//]: # (badges) [//]: # (badges)
[crate-image]: https://buildstats.info/crate/yubikey [crate-image]: https://img.shields.io/crates/v/yubikey?logo=rust
[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
[msrv-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg [msrv-image]: https://img.shields.io/badge/rustc-1.85+-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/actions/workflows/ci.yml/badge.svg
[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-image]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs/status.svg
[deps-link]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs [deps-link]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs
@@ -231,10 +234,10 @@ or conditions.
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC [PC/SC]: https://en.wikipedia.org/wiki/PC/SC
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust [`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html [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 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/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/ [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[piv-tool-guide]: https://docs.yubico.com/software/yubikey/tools/pivtool/Introduction.html
[Corrode]: https://github.com/jameysharp/corrode [Corrode]: https://github.com/jameysharp/corrode
[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
+4
View File
@@ -4,6 +4,10 @@ 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).
## Unreleased
### Changed
- MSRV is now 1.81.
## 0.7.0 (2022-11-14) ## 0.7.0 (2022-11-14)
### Changed ### Changed
- Bump `clap` to v4.0 ([#438]) - Bump `clap` to v4.0 ([#438])
+5 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "yubikey-cli" name = "yubikey-cli"
version = "0.8.0-pre" version = "0.9.0-pre"
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.
@@ -12,15 +12,15 @@ 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" edition = "2021"
rust-version = "1.56" rust-version = "1.85"
[dependencies] [dependencies]
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
env_logger = "0.10" env_logger = "0.11"
hex = { package = "base16ct", version = "0.2", features = ["alloc"] } hex = { package = "base16ct", version = "0.2", features = ["alloc"] }
log = "0.4" log = "0.4"
once_cell = "1" once_cell = "1"
sha2 = "0.10" sha2.workspace = true
termcolor = "1" termcolor = "1"
x509-cert.workspace = true x509-cert.workspace = true
yubikey = { version = "0.8", path = ".." } yubikey = { version = "=0.9.0-pre.0", path = ".." }
+2 -4
View File
@@ -25,8 +25,7 @@ Rust **1.60** or newer.
- [YubiKey 4] series - [YubiKey 4] series
- [YubiKey 5] series - [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported. NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Security Warning ## Security Warning
@@ -84,7 +83,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.60+-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.85+-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/
@@ -98,7 +97,6 @@ or conditions.
[PIV]: https://piv.idmanagement.gov/ [PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/ [Yubico]: https://www.yubico.com/
[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/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/ [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
+13 -6
View File
@@ -188,22 +188,29 @@ pub fn print_cert_info(
print_cert_attr( print_cert_attr(
stream, stream,
"Algorithm", "Algorithm",
cert.tbs_certificate.subject_public_key_info.algorithm.oid, cert.tbs_certificate()
.subject_public_key_info()
.algorithm
.oid,
)?; )?;
print_cert_attr(stream, "Subject", &cert.tbs_certificate.subject)?; print_cert_attr(stream, "Subject", cert.tbs_certificate().subject())?;
print_cert_attr(stream, "Issuer", &cert.tbs_certificate.issuer)?; print_cert_attr(stream, "Issuer", cert.tbs_certificate().issuer())?;
print_cert_attr( print_cert_attr(
stream, stream,
"Fingerprint", "Fingerprint",
&hex::upper::encode_string(&fingerprint), hex::upper::encode_string(&fingerprint),
)?; )?;
print_cert_attr( print_cert_attr(
stream, stream,
"Not Before", "Not Before",
cert.tbs_certificate.validity.not_before, cert.tbs_certificate().validity().not_before,
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate().validity().not_after,
)?; )?;
print_cert_attr(stream, "Not After", cert.tbs_certificate.validity.not_after)?;
Ok(()) Ok(())
} }
+10
View File
@@ -0,0 +1,10 @@
#![cfg(feature = "untested")]
use yubikey::{mgm, YubiKey};
fn main() {
let yubikey = YubiKey::open().unwrap();
let mut mgmt = mgm::Manager::new(yubikey).unwrap();
mgmt.enable_yubihsm().unwrap();
}
+32 -1
View File
@@ -82,6 +82,12 @@ impl Apdu {
self self
} }
/// Set this APDU's second parameter only
pub(crate) fn p2(&mut self, value: u8) -> &mut Self {
self.p2 = value;
self
}
/// Set both parameters for this APDU /// Set both parameters for this APDU
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self { pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1; self.p1 = p1;
@@ -198,6 +204,15 @@ pub enum Ins {
/// Get slot metadata /// Get slot metadata
GetMetadata, GetMetadata,
/// Management // Read Config
ReadConfig,
/// Management // Write Config
WriteConfig,
/// Management // DeviceReset
DeviceReset,
/// Other/unrecognized instruction codes /// Other/unrecognized instruction codes
Other(u8), Other(u8),
} }
@@ -223,6 +238,12 @@ impl Ins {
Ins::Attest => 0xf9, Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8, Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7, Ins::GetMetadata => 0xf7,
// Management
Ins::ReadConfig => 0x1d,
Ins::WriteConfig => 0x1c,
Ins::DeviceReset => 0x1f,
Ins::Other(code) => code, Ins::Other(code) => code,
} }
} }
@@ -231,6 +252,11 @@ impl Ins {
impl From<u8> for Ins { impl From<u8> for Ins {
fn from(code: u8) -> Self { fn from(code: u8) -> Self {
match code { match code {
// Management
0x1d => Ins::ReadConfig,
0x1c => Ins::WriteConfig,
0x1f => Ins::DeviceReset,
0x20 => Ins::Verify, 0x20 => Ins::Verify,
0x24 => Ins::ChangeReference, 0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry, 0x2c => Ins::ResetRetry,
@@ -318,7 +344,7 @@ impl From<Vec<u8>> for Response {
} }
let sw = StatusWords::from( let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16), ((bytes[bytes.len() - 2] as u16) << 8) | (bytes[bytes.len() - 1] as u16),
); );
let len = bytes.len() - 2; let len = bytes.len() - 2;
@@ -395,6 +421,9 @@ pub(crate) enum StatusWords {
/// Not enough memory /// Not enough memory
NoSpaceError, NoSpaceError,
/// Referenced data or reference data not found
ReferenceDataNotFoundError,
// //
// Custom Yubico Status Word extensions // Custom Yubico Status Word extensions
// //
@@ -428,6 +457,7 @@ impl StatusWords {
StatusWords::IncorrectParamError => 0x6a80, StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82, StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84, StatusWords::NoSpaceError => 0x6a84,
StatusWords::ReferenceDataNotFoundError => 0x6a88,
StatusWords::IncorrectSlotError => 0x6b00, StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00, StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00, StatusWords::CommandAbortedError => 0x6f00,
@@ -462,6 +492,7 @@ impl From<u16> for StatusWords {
0x6a80 => StatusWords::IncorrectParamError, 0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError, 0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError, 0x6a84 => StatusWords::NoSpaceError,
0x6a88 => StatusWords::ReferenceDataNotFoundError,
0x6b00 => StatusWords::IncorrectSlotError, 0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError, 0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError, 0x6f00 => StatusWords::CommandAbortedError,
+8 -4
View File
@@ -31,7 +31,8 @@
// 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::{Result, YubiKey}; use crate::{Result, YubiKey};
use rand_core::{OsRng, RngCore}; use cipher::common::Generate;
use rand_core::CryptoRng;
use std::fmt::{self, Debug, Display}; use std::fmt::{self, Debug, Display};
/// CCCID offset /// CCCID offset
@@ -66,9 +67,12 @@ impl CardId {
/// Generate a random CCC Card ID /// Generate a random CCC Card ID
pub fn generate() -> Self { pub fn generate() -> Self {
let mut id = [0u8; Self::BYTE_SIZE]; Self(Generate::generate())
OsRng.fill_bytes(&mut id); }
Self(id)
/// Generate a random CCC Card ID from an [`Rng`]
pub fn generate_from_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
Self(Generate::generate_from_rng(rng))
} }
} }
+81 -22
View File
@@ -41,8 +41,8 @@ use crate::{
}; };
use log::error; use log::error;
use x509_cert::{ use x509_cert::{
builder::{Builder, CertificateBuilder, Profile}, builder::{profile::BuilderProfile, Builder, CertificateBuilder},
der::{self, referenced::OwnedToRef, Decode, Encode}, der::{referenced::OwnedToRef, Decode, Encode},
name::Name, name::Name,
serial_number::SerialNumber, serial_number::SerialNumber,
spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef}, spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef},
@@ -109,23 +109,18 @@ impl Certificate {
extensions: F, extensions: F,
) -> Result<Self> ) -> Result<Self>
where where
F: FnOnce(&mut CertificateBuilder<'_, yubikey_signer::Signer<'_, KT>>) -> der::Result<()>, F: FnOnce(&mut CertificateBuilder<SelfSigned>) -> der::Result<()>,
{ {
let signer = yubikey_signer::Signer::new(yubikey, key, subject_pki.owned_to_ref())?; let signer =
let mut builder = CertificateBuilder::new( yubikey_signer::Signer::<'_, KT>::new(yubikey, key, subject_pki.owned_to_ref())?;
Profile::Manual { issuer: None }, let mut builder =
serial, CertificateBuilder::new(SelfSigned { subject }, serial, validity, subject_pki)
validity, .map_err(|_| Error::KeyError)?;
subject,
subject_pki,
&signer,
)
.map_err(|_| Error::KeyError)?;
// Add custom extensions // Add custom extensions
extensions(&mut builder)?; extensions(&mut builder)?;
let cert = builder.build().map_err(|_| Error::KeyError)?; let cert = builder.build(&signer).map_err(|_| Error::KeyError)?;
let cert = Self { cert }; let cert = Self { cert };
cert.write(yubikey, key, CertInfo::Uncompressed)?; cert.write(yubikey, key, CertInfo::Uncompressed)?;
@@ -174,23 +169,55 @@ impl Certificate {
/// Returns the Issuer field of the certificate. /// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> String { pub fn issuer(&self) -> String {
self.cert.tbs_certificate.issuer.to_string() self.cert.tbs_certificate().issuer().to_string()
} }
/// Returns the SubjectName field of the certificate. /// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> String { pub fn subject(&self) -> String {
self.cert.tbs_certificate.subject.to_string() self.cert.tbs_certificate().subject().to_string()
} }
/// Returns the SubjectPublicKeyInfo field of the certificate. /// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> SubjectPublicKeyInfoRef<'_> { pub fn subject_pki(&self) -> SubjectPublicKeyInfoRef<'_> {
self.cert self.cert
.tbs_certificate .tbs_certificate()
.subject_public_key_info .subject_public_key_info()
.owned_to_ref() .owned_to_ref()
} }
} }
/// A [`BuilderProfile`] for self-signed certificates.
///
/// This profile has no default extensions.
pub struct SelfSigned {
subject: Name,
}
impl BuilderProfile for SelfSigned {
fn get_issuer(&self, subject: &Name) -> Name {
// RFC 5280 Section 3.2:
//
// > Self-issued certificates are CA certificates in which the issuer and subject
// > are the same entity. [..] Self-signed certificates are self-issued
// > certificates where the digital signature may be verified by the public key
// > bound into the certificate.
subject.clone()
}
fn get_subject(&self) -> Name {
self.subject.clone()
}
fn build_extensions(
&self,
_spk: SubjectPublicKeyInfoRef<'_>,
_issuer_spk: SubjectPublicKeyInfoRef<'_>,
_tbs: &x509_cert::TbsCertificate,
) -> x509_cert::builder::Result<Vec<x509_cert::ext::Extension>> {
Ok(vec![])
}
}
/// Read certificate /// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> { pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let object_id = slot.object_id(); let object_id = slot.object_id();
@@ -251,7 +278,7 @@ pub mod yubikey_signer {
oid::db::rfc5912, oid::db::rfc5912,
Encode, Sequence, Encode, Sequence,
}; };
use sha2::{Digest, Sha256, Sha384}; use sha2::{Digest, Sha256, Sha384, Sha512};
use signature::Keypair; use signature::Keypair;
use std::{cell::RefCell, fmt, io::Write, marker::PhantomData}; use std::{cell::RefCell, fmt, io::Write, marker::PhantomData};
use x509_cert::spki::{ use x509_cert::spki::{
@@ -286,6 +313,22 @@ pub mod yubikey_signer {
fn read_signature(input: &[u8]) -> SigResult<Self::Signature>; fn read_signature(input: &[u8]) -> SigResult<Self::Signature>;
} }
impl KeyType for ed25519_dalek::SigningKey {
const ALGORITHM: AlgorithmId = AlgorithmId::Ed25519;
type Error = ed25519_dalek::SignatureError;
type Signature = ed25519_dalek::Signature;
type VerifyingKey = ed25519_dalek::VerifyingKey;
type PublicKey = ed25519_dalek::VerifyingKey;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
Ok(Sha512::digest(input).to_vec())
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::try_from(input)
}
}
impl KeyType for p256::NistP256 { impl KeyType for p256::NistP256 {
const ALGORITHM: AlgorithmId = AlgorithmId::EccP256; const ALGORITHM: AlgorithmId = AlgorithmId::EccP256;
type Error = ecdsa::Error; type Error = ecdsa::Error;
@@ -342,6 +385,22 @@ pub mod yubikey_signer {
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa2048; const ALGORITHM: AlgorithmId = AlgorithmId::Rsa2048;
} }
/// RSA 3072 bits key
pub struct Rsa3072;
impl RsaLength for Rsa3072 {
const BIT_LENGTH: usize = 3072;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa3072;
}
/// RSA 4096 bits key
pub struct Rsa4096;
impl RsaLength for Rsa4096 {
const BIT_LENGTH: usize = 4096;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa4096;
}
/// RSA keys used to sign certificates /// RSA keys used to sign certificates
pub struct YubiRsa<N: RsaLength> { pub struct YubiRsa<N: RsaLength> {
_len: PhantomData<N>, _len: PhantomData<N>,
@@ -428,20 +487,20 @@ pub mod yubikey_signer {
} }
} }
impl<'y, KT: KeyType> Keypair for Signer<'y, KT> { impl<KT: KeyType> Keypair for Signer<'_, KT> {
type VerifyingKey = KT::VerifyingKey; type VerifyingKey = KT::VerifyingKey;
fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey { fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey {
self.public_key.clone() self.public_key.clone()
} }
} }
impl<'y, KT: KeyType> DynSignatureAlgorithmIdentifier for Signer<'y, KT> { impl<KT: KeyType> DynSignatureAlgorithmIdentifier for Signer<'_, KT> {
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> { fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
self.verifying_key().signature_algorithm_identifier() self.verifying_key().signature_algorithm_identifier()
} }
} }
impl<'y, KT: KeyType> signature::Signer<KT::Signature> for Signer<'y, KT> { impl<KT: KeyType> signature::Signer<KT::Signature> for Signer<'_, KT> {
fn try_sign(&self, msg: &[u8]) -> SigResult<KT::Signature> { fn try_sign(&self, msg: &[u8]) -> SigResult<KT::Signature> {
let data = KT::prepare(msg)?; let data = KT::prepare(msg)?;
+18 -1
View File
@@ -1,5 +1,7 @@
//! Miscellaneous constant values //! Miscellaneous constant values
#![allow(dead_code)]
/// YubiKey max buffer size /// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072; pub(crate) const CB_BUF_MAX: usize = 3072;
@@ -7,7 +9,6 @@ pub(crate) const CB_BUF_MAX: usize = 3072;
pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9; pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9;
pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
#[cfg(feature = "untested")]
pub(crate) const CB_OBJ_TAG_MAX: usize = CB_OBJ_TAG_MIN + 2; // 1 byte tag + 3 bytes len pub(crate) const CB_OBJ_TAG_MAX: usize = CB_OBJ_TAG_MIN + 2; // 1 byte tag + 3 bytes len
// Admin tags // Admin tags
@@ -18,3 +19,19 @@ pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
// Protected tags // Protected tags
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81; pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89; pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
// Management
pub(crate) const TAG_USB_SUPPORTED: u8 = 0x01;
pub(crate) const TAG_SERIAL: u8 = 0x02;
pub(crate) const TAG_USB_ENABLED: u8 = 0x03;
pub(crate) const TAG_FORM_FACTOR: u8 = 0x04;
pub(crate) const TAG_VERSION: u8 = 0x05;
pub(crate) const TAG_AUTO_EJECT_TIMEOUT: u8 = 0x06;
pub(crate) const TAG_CHALRESP_TIMEOUT: u8 = 0x07;
pub(crate) const TAG_DEVICE_FLAGS: u8 = 0x08;
pub(crate) const TAG_APP_VERSIONS: u8 = 0x09;
pub(crate) const TAG_CONFIG_LOCK: u8 = 0x0A;
pub(crate) const TAG_UNLOCK: u8 = 0x0B;
pub(crate) const TAG_REBOOT: u8 = 0x0C;
pub(crate) const TAG_NFC_SUPPORTED: u8 = 0x0D;
pub(crate) const TAG_NFC_ENABLED: u8 = 0x0E;
+14 -4
View File
@@ -57,6 +57,9 @@ pub enum Error {
/// Authentication error /// Authentication error
AuthenticationError, AuthenticationError,
/// Error while building a certificate
CertificateBuilder,
/// Generic error /// Generic error
GenericError, GenericError,
@@ -132,10 +135,11 @@ impl Error {
Error::AlgorithmError => f.write_str("algorithm error"), Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => f.write_str("applet error"), Error::AppletError => f.write_str("applet error"),
Error::AppletNotFound { applet_name } => { Error::AppletNotFound { applet_name } => {
f.write_str(&format!("{} applet not found", applet_name)) f.write_str(&format!("{applet_name} applet not found"))
} }
Error::ArgumentError => f.write_str("argument error"), Error::ArgumentError => f.write_str("argument error"),
Error::AuthenticationError => f.write_str("authentication error"), Error::AuthenticationError => f.write_str("authentication error"),
Error::CertificateBuilder => f.write_str("certificate builder error"),
Error::GenericError => f.write_str("generic error"), Error::GenericError => f.write_str("generic error"),
Error::InvalidObject => f.write_str("invalid object"), Error::InvalidObject => f.write_str("invalid object"),
Error::KeyError => f.write_str("key error"), Error::KeyError => f.write_str("key error"),
@@ -146,7 +150,7 @@ impl Error {
Error::PcscError { Error::PcscError {
inner: Some(pcsc_error), inner: Some(pcsc_error),
} => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)), } => f.write_fmt(format_args!("PC/SC error: {pcsc_error}")),
Error::PcscError { .. } => f.write_str("PC/SC error"), Error::PcscError { .. } => f.write_str("PC/SC error"),
@@ -192,8 +196,14 @@ impl std::error::Error for Error {
} }
} }
impl From<x509_cert::der::Error> for Error { impl From<der::Error> for Error {
fn from(_err: x509_cert::der::Error) -> Error { fn from(_err: der::Error) -> Error {
Error::ParseError Error::ParseError
} }
} }
impl From<x509_cert::builder::Error> for Error {
fn from(_err: x509_cert::builder::Error) -> Error {
Error::CertificateBuilder
}
}
+2 -2
View File
@@ -51,7 +51,7 @@ mod config;
mod consts; mod consts;
mod error; mod error;
mod metadata; mod metadata;
mod mgm; pub mod mgm;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
mod mscmap; mod mscmap;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -71,7 +71,7 @@ pub use crate::{
chuid::ChuId, chuid::ChuId,
config::Config, config::Config,
error::{Error, Result}, error::{Error, Result},
mgm::{MgmKey, MgmType}, mgm::{MgmAlgorithmId, MgmKey, MgmType},
piv::Key, piv::Key,
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
reader::Context, reader::Context,
+12 -15
View File
@@ -30,16 +30,15 @@
// (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.
use std::marker::PhantomData; use std::{iter, marker::PhantomData};
use zeroize::Zeroizing; use zeroize::Zeroizing;
use crate::{serialization::*, transaction::Transaction, Buffer, Error, Result}; use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
#[cfg(feature = "untested")] serialization::*,
use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX}; transaction::Transaction,
Buffer, Error, Result,
#[cfg(feature = "untested")] };
use std::iter;
const TAG_ADMIN: u8 = 0x80; const TAG_ADMIN: u8 = 0x80;
const TAG_PROTECTED: u8 = 0x88; const TAG_PROTECTED: u8 = 0x88;
@@ -87,7 +86,6 @@ impl<T: MetadataType> Metadata<T> {
} }
/// Write metadata /// Write metadata
#[cfg(feature = "untested")]
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> { pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX { if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError); return Err(Error::GenericError);
@@ -104,7 +102,6 @@ impl<T: MetadataType> Metadata<T> {
} }
/// Delete metadata /// Delete metadata
#[cfg(feature = "untested")]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> { pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[]) txn.save_object(T::obj_id(), &[])
} }
@@ -127,7 +124,6 @@ impl<T: MetadataType> Metadata<T> {
} }
/// Set metadata item /// Set metadata item
#[cfg(feature = "untested")]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> { pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0; let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0; let mut tag_temp: u8 = 0;
@@ -157,8 +153,10 @@ impl<T: MetadataType> Metadata<T> {
// We did not find an existing tag, append // We did not find an existing tag, append
assert_eq!(offset, self.inner.len()); assert_eq!(offset, self.inner.len());
self.inner self.inner.extend(iter::repeat_n(
.extend(iter::repeat(0).take(1 + get_length_size(item.len()) + item.len())); 0,
1 + get_length_size(item.len()) + item.len(),
));
Tlv::write(&mut self.inner[offset..], tag, item)?; Tlv::write(&mut self.inner[offset..], tag, item)?;
return Ok(()); return Ok(());
@@ -193,7 +191,7 @@ impl<T: MetadataType> Metadata<T> {
// Move remaining data // Move remaining data
let orig_len = self.inner.len(); let orig_len = self.inner.len();
if cb_moved > 0 { if cb_moved > 0 {
self.inner.extend(iter::repeat(0).take(cb_moved as usize)); self.inner.extend(iter::repeat_n(0, cb_moved as usize));
} }
self.inner.copy_within( self.inner.copy_within(
next_offset..orig_len, next_offset..orig_len,
@@ -214,7 +212,6 @@ impl<T: MetadataType> Metadata<T> {
} }
/// Get the size of a length tag for the given length /// Get the size of a length tag for the given length
#[cfg(feature = "untested")]
fn get_length_size(length: usize) -> usize { fn get_length_size(length: usize) -> usize {
if length < 0x80 { if length < 0x80 {
1 1
+797 -193
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -96,10 +96,9 @@ impl MsRoots {
} }
} }
MsRoots::new(&data).map(Some).map_err(|e| { MsRoots::new(&data)
error!("error parsing msroots: {:?}", e); .map(Some)
e .inspect_err(|e| error!("error parsing msroots: {:?}", e))
})
} }
/// Write `msroots` file to YubiKey /// Write `msroots` file to YubiKey
+183 -84
View File
@@ -6,10 +6,10 @@
//! Supported algorithms: //! Supported algorithms:
//! //!
//! - **Encryption**: //! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048` //! - RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384) //! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**: //! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048` //! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384) //! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
@@ -45,35 +45,37 @@
use crate::{ use crate::{
apdu::{Ins, StatusWords}, apdu::{Ins, StatusWords},
certificate::{self, Certificate}, certificate::{self, Certificate},
consts::CB_OBJ_MAX,
error::{Error, Result}, error::{Error, Result},
mgm::MgmAlgorithmId,
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
serialization::*, serialization::*,
setting, setting,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, ObjectId, Buffer, ObjectId,
}; };
use elliptic_curve::{sec1::EncodedPoint as EcPublicKey, PublicKey}; use elliptic_curve::{sec1::Sec1Point as EcPublicKey, PublicKey};
use log::{debug, error, warn}; use log::{debug, error, warn};
use p256::NistP256; use p256::NistP256;
use p384::NistP384; use p384::NistP384;
use rsa::{pkcs8::EncodePublicKey, BigUint, RsaPublicKey}; use rsa::{pkcs8::EncodePublicKey, BoxedUint, RsaPublicKey};
use std::{ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
str::FromStr, str::FromStr,
}; };
use x509_cert::{der::Decode, spki::SubjectPublicKeyInfoOwned}; use x509_cert::{
der::{asn1::BitString, Decode},
#[cfg(feature = "untested")] spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoOwned},
use {
num_bigint_dig::traits::ModInverse,
num_integer::Integer,
num_traits::{FromPrimitive, One},
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::consts::CB_OBJ_MAX;
#[cfg(feature = "untested")]
use rsa::{traits::PrivateKeyParts, RsaPrivateKey};
/// PIV Applet Name /// PIV Applet Name
pub(crate) const APPLET_NAME: &str = "PIV"; pub(crate) const APPLET_NAME: &str = "PIV";
@@ -87,6 +89,9 @@ const TAG_RSA_MODULUS: u8 = 0x81;
const TAG_RSA_EXP: u8 = 0x82; const TAG_RSA_EXP: u8 = 0x82;
const TAG_ECC_POINT: u8 = 0x86; const TAG_ECC_POINT: u8 = 0x86;
/// OID for ed25519 and x25519 algorithms
pub const OID_X25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.110");
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
const KEYDATA_LEN: usize = 1024; const KEYDATA_LEN: usize = 1024;
@@ -172,9 +177,9 @@ impl From<SlotId> for u8 {
impl Display for SlotId { impl Display for SlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
SlotId::Management(r) => write!(f, "{:?}", r), SlotId::Management(r) => write!(f, "{r:?}"),
SlotId::Retired(r) => write!(f, "{:?}", r), SlotId::Retired(r) => write!(f, "{r:?}"),
_ => write!(f, "{:?}", self), _ => write!(f, "{self:?}"),
} }
} }
} }
@@ -327,7 +332,7 @@ impl From<RetiredSlotId> for u8 {
impl Display for RetiredSlotId { impl Display for RetiredSlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self) write!(f, "{self:?}")
} }
} }
@@ -410,7 +415,7 @@ impl From<ManagementSlotId> for u8 {
impl Display for ManagementSlotId { impl Display for ManagementSlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self) write!(f, "{self:?}")
} }
} }
@@ -482,11 +487,23 @@ pub enum AlgorithmId {
/// 2048-bit RSA. /// 2048-bit RSA.
Rsa2048, Rsa2048,
/// 3072-bit RSA. Requires firmware 5.7 or newer
Rsa3072,
/// 4096-bit RSA. Requires firmware 5.7 or newer
Rsa4096,
/// ECDSA with the NIST P256 curve. /// ECDSA with the NIST P256 curve.
EccP256, EccP256,
/// ECDSA with the NIST P384 curve. /// ECDSA with the NIST P384 curve.
EccP384, EccP384,
/// ED25519
Ed25519,
/// X25519
X25519,
} }
impl TryFrom<u8> for AlgorithmId { impl TryFrom<u8> for AlgorithmId {
@@ -496,8 +513,12 @@ impl TryFrom<u8> for AlgorithmId {
match value { match value {
0x06 => Ok(AlgorithmId::Rsa1024), 0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048), 0x07 => Ok(AlgorithmId::Rsa2048),
0x05 => Ok(AlgorithmId::Rsa3072),
0x16 => Ok(AlgorithmId::Rsa4096),
0x11 => Ok(AlgorithmId::EccP256), 0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384), 0x14 => Ok(AlgorithmId::EccP384),
0xE0 => Ok(AlgorithmId::Ed25519),
0xE1 => Ok(AlgorithmId::X25519),
_ => Err(Error::AlgorithmError), _ => Err(Error::AlgorithmError),
} }
} }
@@ -508,8 +529,12 @@ impl From<AlgorithmId> for u8 {
match id { match id {
AlgorithmId::Rsa1024 => 0x06, AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07, AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::Rsa3072 => 0x05,
AlgorithmId::Rsa4096 => 0x16,
AlgorithmId::EccP256 => 0x11, AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14, AlgorithmId::EccP384 => 0x14,
AlgorithmId::Ed25519 => 0xE0,
AlgorithmId::X25519 => 0xE1,
} }
} }
} }
@@ -525,16 +550,25 @@ impl AlgorithmId {
match self { match self {
AlgorithmId::Rsa1024 => 64, AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128, AlgorithmId::Rsa2048 => 128,
AlgorithmId::Rsa3072 => 192,
AlgorithmId::Rsa4096 => 256,
AlgorithmId::EccP256 => 32, AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48, AlgorithmId::EccP384 => 48,
AlgorithmId::Ed25519 => 32,
AlgorithmId::X25519 => 32,
} }
} }
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
fn get_param_tag(self) -> u8 { fn get_param_tag(self) -> u8 {
match self { match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01, AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => 0x01,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6, AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
AlgorithmId::Ed25519 => 0x07,
AlgorithmId::X25519 => 0x08,
} }
} }
} }
@@ -606,7 +640,10 @@ pub fn generate(
let setting_roca: setting::Setting; let setting_roca: setting::Setting;
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => {
if yubikey.version.major == 4 if yubikey.version.major == 4
&& (yubikey.version.minor < 3 && (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5)) || yubikey.version.minor == 3 && (yubikey.version.patch < 5))
@@ -770,36 +807,44 @@ impl RsaKeyData {
/// - `Ok(key_data)` if `secret_p` and `secret_q` are valid primes. /// - `Ok(key_data)` if `secret_p` and `secret_q` are valid primes.
/// - `Err(Error::AlgorithmError)` if `secret_p`/`secret_q` are invalid primes. /// - `Err(Error::AlgorithmError)` if `secret_p`/`secret_q` are invalid primes.
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Result<Self> { pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Result<Self> {
let p = BigUint::from_bytes_be(secret_p); let p = BoxedUint::from_be_slice_vartime(secret_p);
let q = BigUint::from_bytes_be(secret_q); let q = BoxedUint::from_be_slice_vartime(secret_q);
let exp = BoxedUint::from(KEYDATA_RSA_EXP);
let totient = { let mut private_key = RsaPrivateKey::from_p_q(p.clone(), q.clone(), exp)
let p_t = &p - BigUint::one(); .map_err(|_| Error::AlgorithmError)?;
let q_t = &p - BigUint::one(); private_key
.precompute()
p_t.lcm(&q_t) .map_err(|_| Error::AlgorithmError)?;
};
let exp = BigUint::from_u64(KEYDATA_RSA_EXP).ok_or(Error::AlgorithmError)?;
let d = exp.mod_inverse(&totient).ok_or(Error::AlgorithmError)?;
let d = d.to_biguint().ok_or(Error::AlgorithmError)?;
// We calculate the optimization values ahead of time, instead of making the user
// do so.
let dp = &d % (&p - BigUint::one());
let dq = &d % (&q - BigUint::one());
let qinv = q.clone().mod_inverse(&p).ok_or(Error::AlgorithmError)?;
let (_, qinv) = qinv.to_bytes_be();
Ok(RsaKeyData { Ok(RsaKeyData {
p: Zeroizing::new(p.to_bytes_be()), p: Zeroizing::new(p.to_be_bytes().to_vec()),
q: Zeroizing::new(q.to_bytes_be()), q: Zeroizing::new(q.to_be_bytes().to_vec()),
dp: Zeroizing::new(dp.to_bytes_be()), dp: Zeroizing::new(
dq: Zeroizing::new(dq.to_bytes_be()), private_key
qinv: Zeroizing::new(qinv), .dp()
.expect("invariant violation: precompute should fill the field")
.clone()
.to_be_bytes()
.to_vec(),
),
dq: Zeroizing::new(
private_key
.dq()
.expect("invariant violation: precompute should fill the field")
.clone()
.to_be_bytes()
.to_vec(),
),
qinv: Zeroizing::new(
private_key
.qinv()
.expect("invariant violation: precompute should fill the field")
.clone()
.retrieve()
.to_be_bytes()
.to_vec(),
),
}) })
} }
@@ -810,7 +855,7 @@ impl RsaKeyData {
/// Imports a private RSA encryption or signing key into the YubiKey. /// Imports a private RSA encryption or signing key into the YubiKey.
/// ///
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`. /// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048` or `AlgorithmId::Rsa3072` or `AlgorithmId::Rsa4096`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn import_rsa_key( pub fn import_rsa_key(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
@@ -821,7 +866,10 @@ pub fn import_rsa_key(
pin_policy: PinPolicy, pin_policy: PinPolicy,
) -> Result<()> { ) -> Result<()> {
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (), AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => (),
_ => return Err(Error::AlgorithmError), _ => return Err(Error::AlgorithmError),
} }
@@ -870,6 +918,34 @@ pub fn import_ecc_key(
Ok(()) Ok(())
} }
/// Imports a private ECDH/EdDSA encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::Ed25519` or ` AlgorithmId::X25519`.
#[cfg(feature = "untested")]
pub fn import_cv_key(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
key_data: &[u8],
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<()> {
match algorithm {
AlgorithmId::Ed25519 | AlgorithmId::X25519 => (),
_ => return Err(Error::AlgorithmError),
}
if key_data.len() > KEYDATA_LEN {
return Err(Error::SizeError);
}
let params = vec![key_data];
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
Ok(())
}
/// Generate an attestation certificate for a stored key. /// Generate an attestation certificate for a stored key.
/// ///
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html> /// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
@@ -924,28 +1000,15 @@ pub fn decrypt_data(
/// Read metadata /// Read metadata
pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result<SlotMetadata> { pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result<SlotMetadata> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
let templ = [0, Ins::GetMetadata.code(), 0, slot.into()];
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; txn.get_metadata(slot)
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 /// Metadata from a slot
#[derive(Debug)] #[derive(Debug)]
pub struct SlotMetadata { pub struct SlotMetadata {
/// Algorithm / Type of key /// Algorithm / Type of key
pub algorithm: ManagementAlgorithmId, pub algorithm: SlotAlgorithmId,
/// PIN and touch policy /// PIN and touch policy
pub policy: Option<(PinPolicy, TouchPolicy)>, pub policy: Option<(PinPolicy, TouchPolicy)>,
/// Imported or generated key /// Imported or generated key
@@ -966,13 +1029,14 @@ impl TryFrom<Buffer> for SlotMetadata {
combinator::{eof, map_res}, combinator::{eof, map_res},
multi::fold_many1, multi::fold_many1,
number::complete::u8, number::complete::u8,
Parser,
}; };
let out = fold_many1( let out = fold_many1(
|input| Tlv::parse(input).map_err(|_| nom::Err::Error(())), |input| Tlv::parse(input).map_err(|_| nom::Err::Error(())),
|| { || {
Ok(SlotMetadata { Ok(SlotMetadata {
algorithm: ManagementAlgorithmId::PinPuk, algorithm: SlotAlgorithmId::PinPuk,
policy: None, policy: None,
origin: None, origin: None,
public: None, public: None,
@@ -983,15 +1047,15 @@ impl TryFrom<Buffer> for SlotMetadata {
|acc: Result<SlotMetadata>, tlv| match acc { |acc: Result<SlotMetadata>, tlv| match acc {
Ok(mut metadata) => match tlv.tag { Ok(mut metadata) => match tlv.tag {
1 => { 1 => {
metadata.algorithm = ManagementAlgorithmId::try_from(tlv.value[0])?; metadata.algorithm = SlotAlgorithmId::try_from(tlv.value[0])?;
Ok(metadata) Ok(metadata)
} }
2 => { 2 => {
fn policy_parser( fn policy_parser(
i: &[u8], i: &[u8],
) -> nom::IResult<&[u8], (PinPolicy, TouchPolicy)> { ) -> nom::IResult<&[u8], (PinPolicy, TouchPolicy)> {
let (i, pin) = map_res(u8, PinPolicy::try_from)(i)?; let (i, pin) = map_res(u8, PinPolicy::try_from).parse(i)?;
let (i, touch) = map_res(u8, TouchPolicy::try_from)(i)?; let (i, touch) = map_res(u8, TouchPolicy::try_from).parse(i)?;
let (i, _) = eof(i)?; let (i, _) = eof(i)?;
Ok((i, (pin, touch))) Ok((i, (pin, touch)))
@@ -1003,7 +1067,7 @@ impl TryFrom<Buffer> for SlotMetadata {
} }
3 => { 3 => {
fn origin_parser(i: &[u8]) -> nom::IResult<&[u8], Origin> { fn origin_parser(i: &[u8]) -> nom::IResult<&[u8], Origin> {
let (i, origin) = map_res(u8, Origin::try_from)(i)?; let (i, origin) = map_res(u8, Origin::try_from).parse(i)?;
let (i, _) = eof(i)?; let (i, _) = eof(i)?;
Ok((i, origin)) Ok((i, origin))
@@ -1015,7 +1079,7 @@ impl TryFrom<Buffer> for SlotMetadata {
} }
4 => { 4 => {
match metadata.algorithm { match metadata.algorithm {
ManagementAlgorithmId::Asymmetric(alg) => { SlotAlgorithmId::Asymmetric(alg) => {
metadata.public = Some(read_public_key(alg, tlv.value, false)?); metadata.public = Some(read_public_key(alg, tlv.value, false)?);
} }
_ => Err(Error::ParseError)?, _ => Err(Error::ParseError)?,
@@ -1063,7 +1127,8 @@ impl TryFrom<Buffer> for SlotMetadata {
}, },
err => err, err => err,
}, },
)(buf.as_ref()); )
.parse(buf.as_ref());
match out { match out {
Ok((_, res)) => res, Ok((_, res)) => res,
@@ -1111,7 +1176,40 @@ fn read_public_key(
// //
// 0x7f 0x49 -> Application | Constructed | 0x49 // 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { AlgorithmId::X25519 | AlgorithmId::Ed25519 => {
// 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 (_, tlv) = Tlv::parse(data)?;
let pk_data: [u8; 32] = tlv.value.try_into().map_err(|_| Error::InvalidObject)?;
match algorithm {
AlgorithmId::Ed25519 => SubjectPublicKeyInfoOwned::from_der(
ed25519_dalek::VerifyingKey::from_bytes(&pk_data)
.map_err(|_| Error::InvalidObject)?
.to_public_key_der()
.map_err(|_| Error::InvalidObject)?
.as_bytes(),
)
.map_err(|_| Error::InvalidObject),
AlgorithmId::X25519 => Ok(SubjectPublicKeyInfoOwned {
algorithm: AlgorithmIdentifier {
oid: OID_X25519,
parameters: None,
},
subject_public_key: BitString::from_bytes(
x25519_dalek::PublicKey::from(pk_data).as_bytes(),
)
.map_err(|_| Error::InvalidObject)?,
}),
_ => Err(Error::AlgorithmError),
}
}
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => {
// It appears that the inner application-specific value returned by the // It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways: // YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
// //
@@ -1156,8 +1254,8 @@ fn read_public_key(
let exp = exp_tlv.value.to_vec(); let exp = exp_tlv.value.to_vec();
let pubkey = RsaPublicKey::new( let pubkey = RsaPublicKey::new(
BigUint::from_bytes_be(&modulus), BoxedUint::from_be_slice_vartime(&modulus),
BigUint::from_bytes_be(&exp), BoxedUint::from_be_slice_vartime(&exp),
) )
.map_err(|_| Error::InvalidObject)?; .map_err(|_| Error::InvalidObject)?;
Ok(SubjectPublicKeyInfoOwned::from_der( Ok(SubjectPublicKeyInfoOwned::from_der(
@@ -1215,33 +1313,34 @@ fn read_public_key(
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// Algorithms as reported by the metadata command. /// Algorithms as reported by the metadata command.
pub enum ManagementAlgorithmId { pub enum SlotAlgorithmId {
/// Used on PIN and PUK slots. /// Used on PIN and PUK slots.
PinPuk, PinPuk,
/// Used on the key management slot. /// Used on the key management slot.
ThreeDes, Management(MgmAlgorithmId),
/// Used on all other slots. /// Used on all other slots.
Asymmetric(AlgorithmId), Asymmetric(AlgorithmId),
} }
impl TryFrom<u8> for ManagementAlgorithmId { impl TryFrom<u8> for SlotAlgorithmId {
type Error = Error; type Error = Error;
fn try_from(value: u8) -> Result<Self> { fn try_from(value: u8) -> Result<Self> {
match value { match value {
0xff => Ok(ManagementAlgorithmId::PinPuk), 0xff => Ok(SlotAlgorithmId::PinPuk),
0x03 => Ok(ManagementAlgorithmId::ThreeDes), oth => MgmAlgorithmId::try_from(oth)
oth => AlgorithmId::try_from(oth).map(ManagementAlgorithmId::Asymmetric), .map(SlotAlgorithmId::Management)
.or_else(|_| AlgorithmId::try_from(oth).map(SlotAlgorithmId::Asymmetric)),
} }
} }
} }
impl From<ManagementAlgorithmId> for u8 { impl From<SlotAlgorithmId> for u8 {
fn from(id: ManagementAlgorithmId) -> u8 { fn from(id: SlotAlgorithmId) -> u8 {
match id { match id {
ManagementAlgorithmId::PinPuk => 0xff, SlotAlgorithmId::PinPuk => 0xff,
ManagementAlgorithmId::ThreeDes => 0x03, SlotAlgorithmId::Management(oth) => oth.into(),
ManagementAlgorithmId::Asymmetric(oth) => oth.into(), SlotAlgorithmId::Asymmetric(oth) => oth.into(),
} }
} }
} }
+3 -8
View File
@@ -41,7 +41,7 @@ use std::{
}; };
/// Source of how a setting was configured. /// Source of how a setting was configured.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SettingSource { pub enum SettingSource {
/// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars. /// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
User, User,
@@ -51,15 +51,10 @@ pub enum SettingSource {
Admin, Admin,
/// Default setting. /// Default setting.
#[default]
Default, Default,
} }
impl Default for SettingSource {
fn default() -> Self {
Self::Default
}
}
/// Setting booleans: configuration values sourced from a file or the environment. /// Setting booleans: configuration values sourced from a file or the environment.
/// ///
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a /// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
@@ -122,7 +117,7 @@ impl Setting {
/// Get a setting boolean from an environment variable /// Get a setting boolean from an environment variable
fn from_env(key: &str) -> Option<Self> { fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{}", key)) env::var(format!("YUBIKEY_PIV_{key}"))
.ok() .ok()
.map(|value| Setting { .map(|value| Setting {
source: SettingSource::User, source: SettingSource::User,
+152 -65
View File
@@ -5,6 +5,7 @@ use crate::{
apdu::{Apdu, Ins, StatusWords}, apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX}, consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result}, error::{Error, Result},
mgm::MgmKey,
otp, otp,
piv::{self, AlgorithmId, SlotId}, piv::{self, AlgorithmId, SlotId},
serialization::*, serialization::*,
@@ -15,7 +16,7 @@ use log::{error, trace};
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES}; use crate::mgm::{DeviceConfig, DeviceInfo, Lock};
const CB_PIN_MAX: usize = 8; const CB_PIN_MAX: usize = 8;
@@ -60,26 +61,32 @@ impl<'tx> Transaction<'tx> {
Ok(recv_buffer) Ok(recv_buffer)
} }
/// Select PIV application.
pub fn select_piv_application(&self) -> Result<()> {
self.select_application(
piv::APPLET_ID,
piv::APPLET_NAME,
"failed selecting application",
)
}
/// Select application. /// Select application.
pub fn select_application(&self) -> Result<()> { pub fn select_application(
&self,
applet: &[u8],
applet_name: &'static str,
error: &'static str,
) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication) let response = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(piv::APPLET_ID) .data(applet)
.transmit(self, 0xFF) .transmit(self, 0xFF)
.map_err(|e| { .inspect_err(|e| error!("failed communicating with card: '{}'", e))?;
error!("failed communicating with card: '{}'", e);
e
})?;
if !response.is_success() { if !response.is_success() {
error!( error!("{}: {:04x}", error, response.status_words().code());
"failed selecting application: {:04x}",
response.status_words().code()
);
return Err(match response.status_words() { return Err(match response.status_words() {
StatusWords::NotFoundError => Error::AppletNotFound { StatusWords::NotFoundError => Error::AppletNotFound { applet_name },
applet_name: piv::APPLET_NAME,
},
_ => Error::GenericError, _ => Error::GenericError,
}); });
} }
@@ -92,7 +99,7 @@ impl<'tx> Transaction<'tx> {
// get version from device // get version from device
let response = Apdu::new(Ins::GetVersion).transmit(self, 261)?; let response = Apdu::new(Ins::GetVersion).transmit(self, 261)?;
if !response.is_success() { if !response.is_success() || response.data().is_empty() {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
@@ -104,21 +111,11 @@ impl<'tx> Transaction<'tx> {
match version.major { match version.major {
// YK4 requires switching to the YK applet to retrieve the serial // YK4 requires switching to the YK applet to retrieve the serial
4 => { 4 => {
let sw = Apdu::new(Ins::SelectApplication) self.select_application(
.p1(0x04) otp::APPLET_ID,
.data(otp::APPLET_ID) otp::APPLET_NAME,
.transmit(self, 0xFF)? "failed selecting yk application",
.status_words(); )?;
if !sw.is_success() {
error!("failed selecting yk application: {:04x}", sw.code());
return Err(match sw {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: otp::APPLET_NAME,
},
_ => Error::GenericError,
});
}
let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?; let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
@@ -132,21 +129,11 @@ impl<'tx> Transaction<'tx> {
} }
// reselect the PIV applet // reselect the PIV applet
let sw = Apdu::new(Ins::SelectApplication) self.select_application(
.p1(0x04) piv::APPLET_ID,
.data(piv::APPLET_ID) piv::APPLET_NAME,
.transmit(self, 0xFF)? "failed selecting application",
.status_words(); )?;
if !sw.is_success() {
error!("failed selecting application: {:04x}", sw.code());
return Err(match sw {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: piv::APPLET_NAME,
},
_ => Error::GenericError,
});
}
response.data().try_into() response.data().try_into()
} }
@@ -171,6 +158,24 @@ impl<'tx> Transaction<'tx> {
} }
} }
/// Read metadata
pub(crate) fn get_metadata(&self, slot: SlotId) -> Result<piv::SlotMetadata> {
let response = Apdu::new(Ins::GetMetadata)
.p2(slot.into())
.transmit(self, CB_OBJ_MAX)?;
match response.status_words() {
StatusWords::Success => {
let buf = Buffer::new(response.data().into());
piv::SlotMetadata::try_from(buf)
}
StatusWords::ReferenceDataNotFoundError => Err(Error::NotFound),
// Requires firmware 5.2.3
StatusWords::NotSupportedError => Err(Error::NotSupported),
_ => Err(Error::GenericError),
}
}
/// Verify device PIN. /// Verify device PIN.
pub fn verify_pin(&self, pin: &[u8]) -> Result<()> { pub fn verify_pin(&self, pin: &[u8]) -> Result<()> {
if pin.len() > CB_PIN_MAX { if pin.len() > CB_PIN_MAX {
@@ -186,7 +191,7 @@ impl<'tx> Transaction<'tx> {
if !pin.is_empty() { if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]); let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin); data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref()); query.data(data.as_slice());
} }
let response = query.transmit(self, 261)?; let response = query.transmit(self, 261)?;
@@ -243,15 +248,14 @@ impl<'tx> Transaction<'tx> {
} }
/// Set the management key (MGM). /// Set the management key (MGM).
#[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> { pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
let p2 = if require_touch { 0xfe } else { 0xff }; let p2 = if require_touch { 0xfe } else { 0xff };
let mut data = [0u8; DES_LEN_3DES + 3]; let mut data = Vec::with_capacity(usize::from(new_key.key_size()) + 3);
data[0] = ALGO_3DES; data.push(new_key.algorithm_id().into());
data[1] = KEY_CARDMGM; data.push(KEY_CARDMGM);
data[2] = DES_LEN_3DES as u8; data.push(new_key.key_size());
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref()); data.extend_from_slice(new_key.as_ref());
let status_words = Apdu::new(Ins::SetMgmKey) let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2) .params(0xff, p2)
@@ -284,14 +288,23 @@ impl<'tx> Transaction<'tx> {
let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()]; let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { AlgorithmId::Rsa1024 => {
let key_len = if let AlgorithmId::Rsa1024 = algorithm { if in_len != 128 {
128 return Err(Error::SizeError);
} else { }
256 }
}; AlgorithmId::Rsa2048 => {
if in_len != 256 {
if in_len != key_len { return Err(Error::SizeError);
}
}
AlgorithmId::Rsa3072 => {
if in_len != 384 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa4096 => {
if in_len != 512 {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
@@ -307,6 +320,19 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
AlgorithmId::X25519 => {
if !decipher {
return Err(Error::NotSupported);
}
if in_len != 32 {
return Err(Error::SizeError);
}
}
AlgorithmId::Ed25519 => {
if decipher {
return Err(Error::NotSupported);
}
}
} }
let bytes = if in_len < 0x80 { let bytes = if in_len < 0x80 {
@@ -323,7 +349,9 @@ impl<'tx> Transaction<'tx> {
Tlv::write( Tlv::write(
&mut buf[2..], &mut buf[2..],
match (algorithm, decipher) { match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85, (AlgorithmId::EccP256, true)
| (AlgorithmId::EccP384, true)
| (AlgorithmId::X25519, true) => 0x85,
_ => 0x81, _ => 0x81,
}, },
sign_in sign_in
@@ -335,10 +363,7 @@ impl<'tx> Transaction<'tx> {
let response = self let response = self
.transfer_data(&templ, &indata[..offset], 1024) .transfer_data(&templ, &indata[..offset], 1024)
.map_err(|e| { .inspect_err(|e| error!("sign command failed to communicate: {}", e))?;
error!("sign command failed to communicate: {}", e);
e
})?;
if !response.is_success() { if !response.is_success() {
error!("failed sign command with code {:x}", response.code()); error!("failed sign command with code {:x}", response.code());
@@ -511,4 +536,66 @@ impl<'tx> Transaction<'tx> {
_ => Err(Error::GenericError), _ => Err(Error::GenericError),
} }
} }
/// Write configuration to the YubiKey
#[cfg(feature = "untested")]
pub fn write_config(
&mut self,
version: Version,
config: DeviceConfig,
current_lock: Option<Lock>,
new_lock: Option<Lock>,
) -> Result<()> {
if version
< (Version {
major: 5,
minor: 0,
patch: 0,
})
{
return Err(Error::NotSupported);
}
let data = config.as_tlv(true, current_lock, new_lock)?;
let response = Apdu::new(Ins::WriteConfig)
.params(0x00, 0x00)
.data(&data)
.transmit(self, 2)?;
if !response.is_success() {
error!(
"Unable to write_config: {:04x}",
response.status_words().code()
);
return Err(Error::GenericError);
}
Ok(())
}
/// Write configuration to the YubiKey
#[cfg(feature = "untested")]
pub fn read_config(&mut self) -> Result<DeviceInfo> {
let mut data = [0u8; CB_BUF_MAX];
let mut len = data.len();
let data_remaining = &mut data[..];
len -= data_remaining.len();
let response = Apdu::new(Ins::ReadConfig)
.params(0x00, 0x00)
.data(&data[..len])
.transmit(self, CB_BUF_MAX + 2)?;
if !response.is_success() {
error!(
"Unable to read configuration: {:04x}",
response.status_words().code()
);
return Err(Error::GenericError);
}
let data = response.data();
DeviceInfo::parse(data)
}
} }
+102 -55
View File
@@ -41,10 +41,12 @@ use crate::{
reader::{Context, Reader}, reader::{Context, Reader},
transaction::Transaction, transaction::Transaction,
}; };
use cipher::common::getrandom::SysRng;
use log::{error, info}; use log::{error, info};
use pcsc::{Card, Disposition}; use pcsc::Card;
use rand_core::{OsRng, RngCore}; use rand_core::TryRng;
use std::{ use std::{
cmp::{Ord, Ordering},
fmt::{self, Display}, fmt::{self, Display},
str::FromStr, str::FromStr,
}; };
@@ -59,7 +61,6 @@ use {
transaction::ChangeRefAction, transaction::ChangeRefAction,
Buffer, ObjectId, Buffer, ObjectId,
}, },
secrecy::ExposeSecret,
std::time::{SystemTime, UNIX_EPOCH}, std::time::{SystemTime, UNIX_EPOCH},
}; };
@@ -67,6 +68,7 @@ use {
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01; pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// 3DES authentication /// 3DES authentication
#[cfg(feature = "untested")]
pub(crate) const ALGO_3DES: u8 = 0x03; pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key /// Card management key
@@ -75,10 +77,11 @@ pub(crate) const KEY_CARDMGM: u8 = 0x9b;
const TAG_DYN_AUTH: u8 = 0x7c; const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN. /// Cached YubiKey PIN.
pub type CachedPin = secrecy::SecretVec<u8>; // TODO(tarcieri): add a newtype for this with a zeroize impl
pub type CachedPin = Vec<u8>;
/// YubiKey serial number. /// YubiKey serial number.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct Serial(pub u32); pub struct Serial(pub u32);
impl From<u32> for Serial { impl From<u32> for Serial {
@@ -143,6 +146,22 @@ impl Version {
patch: bytes[2], patch: bytes[2],
} }
} }
#[cfg(feature = "untested")]
pub(crate) fn parse(input: &[u8]) -> Result<Self> {
use nom::{combinator::eof, number::complete::u8};
let (i, major) = u8(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (i, minor) = u8(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (i, patch) = u8(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (_i, _) = eof(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
Ok(Self {
major,
minor,
patch,
})
}
} }
impl Display for Version { impl Display for Version {
@@ -151,6 +170,39 @@ impl Display for Version {
} }
} }
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
if self.major > other.major {
return Ordering::Greater;
}
if self.major < other.major {
return Ordering::Less;
}
if self.minor > other.minor {
return Ordering::Greater;
}
if self.minor < other.minor {
return Ordering::Less;
}
if self.patch > other.patch {
return Ordering::Greater;
}
if self.patch < other.patch {
return Ordering::Less;
}
Ordering::Equal
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// YubiKey device: primary API for opening a session and performing various operations. /// YubiKey device: primary API for opening a session and performing various operations.
/// ///
/// Almost all functionality in this library will require an open session /// Almost all functionality in this library will require an open session
@@ -269,13 +321,10 @@ impl YubiKey {
pcsc::Disposition::ResetCard, pcsc::Disposition::ResetCard,
)?; )?;
let pin = self let pin = self.pin.as_ref().map(|p| Buffer::new(p.clone()));
.pin
.as_ref()
.map(|p| Buffer::new(p.expose_secret().clone()));
let txn = Transaction::new(&mut self.card)?; let txn = Transaction::new(&mut self.card)?;
txn.select_application()?; txn.select_piv_application()?;
if let Some(p) = &pin { if let Some(p) = &pin {
txn.verify_pin(p)?; txn.verify_pin(p)?;
@@ -293,7 +342,10 @@ impl YubiKey {
/// `YubiKey` implements `Drop` which automatically disconnects the card using /// `YubiKey` implements `Drop` which automatically disconnects the card using
/// `Disposition::ResetCard`; you only need to call this function if you want to /// `Disposition::ResetCard`; you only need to call this function if you want to
/// handle errors or use a different disposition method. /// handle errors or use a different disposition method.
pub fn disconnect(self, disposition: Disposition) -> core::result::Result<(), (Self, Error)> { pub fn disconnect(
self,
disposition: pcsc::Disposition,
) -> core::result::Result<(), (Self, Error)> {
let Self { let Self {
card, card,
name, name,
@@ -357,37 +409,46 @@ impl YubiKey {
} }
/// Authenticate to the card using the provided management key (MGM). /// Authenticate to the card using the provided management key (MGM).
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<()> { pub fn authenticate(&mut self, mgm_key: &MgmKey) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// get a challenge from the card // get a challenge from the card
let challenge = Apdu::new(Ins::Authenticate) let card_response = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(mgm_key.algorithm_id().into(), 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 !card_response.is_success() || card_response.data().len() < 5 {
return Err(Error::AuthenticationError); return Err(Error::AuthenticationError);
} }
// send a response to the cards challenge and a challenge of our own. // send a response to the cards challenge and a challenge of our own.
let response = mgm_key.decrypt(challenge.data()[4..12].try_into()?); let card_challenge = mgm_key.card_challenge(&card_response.data()[4..])?;
let challenge_len = card_challenge.len();
let mut data = [0u8; 22]; // If this exceeds a `u8` then the card is giving us unexpected data.
data[0] = TAG_DYN_AUTH; let auth_len = (2 + challenge_len + 2 + challenge_len)
data[1] = 20; // 2 + 8 + 2 +8 .try_into()
data[2] = 0x80; .map_err(|_| Error::AuthenticationError)?;
data[3] = 8;
data[4..12].copy_from_slice(&response);
data[12] = 0x81;
data[13] = 8;
OsRng.fill_bytes(&mut data[14..22]);
let mut challenge = [0u8; 8]; let mut data = Vec::with_capacity(4 + challenge_len + 2 + challenge_len);
challenge.copy_from_slice(&data[14..22]); data.push(TAG_DYN_AUTH);
data.push(auth_len);
data.push(0x80);
data.push(challenge_len as u8);
data.extend_from_slice(&card_challenge);
data.push(0x81);
data.push(challenge_len as u8);
let mut host_challenge = vec![0u8; challenge_len];
SysRng
.try_fill_bytes(&mut host_challenge)
.map_err(|_| Error::GenericError)?;
data.extend_from_slice(&host_challenge);
let authentication = Apdu::new(Ins::Authenticate) let authentication = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
.data(data) .data(data)
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
@@ -396,14 +457,7 @@ impl YubiKey {
} }
// compare the response from the card with our challenge // compare the response from the card with our challenge
let response = mgm_key.encrypt(&challenge); mgm_key.check_challenge(&host_challenge, &authentication.data()[4..])
use subtle::ConstantTimeEq;
if response.ct_eq(&authentication.data()[4..12]).unwrap_u8() != 1 {
return Err(Error::AuthenticationError);
}
Ok(())
} }
/// Get the PIV keys contained in this YubiKey. /// Get the PIV keys contained in this YubiKey.
@@ -446,7 +500,7 @@ impl YubiKey {
} }
if !pin.is_empty() { if !pin.is_empty() {
self.pin = Some(CachedPin::new(pin.into())) self.pin = Some(pin.into())
} }
Ok(()) Ok(())
@@ -459,7 +513,7 @@ impl YubiKey {
// Force a re-select to unverify, because once verified the spec dictates that // Force a re-select to unverify, because once verified the spec dictates that
// subsequent verify calls will return a "verification not needed" instead of // subsequent verify calls will return a "verification not needed" instead of
// the number of tries left... // the number of tries left...
txn.select_application()?; txn.select_piv_application()?;
// WRONG_PIN is expected on successful query. // WRONG_PIN is expected on successful query.
match txn.verify_pin(&[]) { match txn.verify_pin(&[]) {
@@ -502,7 +556,7 @@ impl YubiKey {
} }
if !new_pin.is_empty() { if !new_pin.is_empty() {
self.pin = Some(CachedPin::new(new_pin.into())); self.pin = Some(new_pin.into());
} }
Ok(()) Ok(())
@@ -523,15 +577,11 @@ impl YubiKey {
admin_data admin_data
.set_item(TAG_ADMIN_TIMESTAMP, &tnow) .set_item(TAG_ADMIN_TIMESTAMP, &tnow)
.map_err(|e| { .inspect_err(|e| error!("could not set pin timestamp, err = {}", e))?;
error!("could not set pin timestamp, err = {}", e);
e
})?;
admin_data.write(&txn).map_err(|e| { admin_data
error!("could not write admin data, err = {}", e); .write(&txn)
e .inspect_err(|e| error!("could not write admin data, err = {}", e))?;
})?;
Ok(()) Ok(())
} }
@@ -581,7 +631,7 @@ impl YubiKey {
// Attempt to set the "PUK blocked" flag in admin data. // Attempt to set the "PUK blocked" flag in admin data.
let mut admin_data = AdminData::read(&txn) let mut admin_data = AdminData::read(&txn)
.map(|data| { .inspect(|data| {
if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) { if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags.len() { if item.len() == flags.len() {
flags.copy_from_slice(item) flags.copy_from_slice(item)
@@ -593,8 +643,6 @@ impl YubiKey {
); );
} }
} }
data
}) })
.unwrap_or_default(); .unwrap_or_default();
@@ -703,16 +751,15 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
type Error = Error; type Error = Error;
fn try_from(reader: &'a Reader<'_>) -> Result<Self> { fn try_from(reader: &'a Reader<'_>) -> Result<Self> {
let mut card = reader.connect().map_err(|e| { let mut card = reader
error!("error connecting to reader '{}': {}", reader.name(), e); .connect()
e .inspect_err(|e| error!("error connecting to reader '{}': {}", reader.name(), e))?;
})?;
info!("connected to reader: {}", reader.name()); info!("connected to reader: {}", reader.name());
let mut app_version_serial = || -> Result<(Version, Serial)> { let mut app_version_serial = || -> Result<(Version, Serial)> {
let txn = Transaction::new(&mut card)?; let txn = Transaction::new(&mut card)?;
txn.select_application()?; txn.select_piv_application()?;
let v = txn.get_version()?; let v = txn.get_version()?;
let s = txn.get_serial(v)?; let s = txn.get_serial(v)?;
+155 -30
View File
@@ -3,18 +3,16 @@
#![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 cipher::common::{getrandom::SysRng, Generate};
use log::trace; use log::trace;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore};
use rsa::{pkcs1v15, RsaPublicKey}; use rsa::{pkcs1v15, RsaPublicKey};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use signature::hazmat::PrehashVerifier; use signature::hazmat::PrehashVerifier;
use std::{env, str::FromStr, sync::Mutex, time::Duration}; use std::{env, str::FromStr, sync::Mutex, time::Duration};
use x509_cert::{der::Encode, name::Name, serial_number::SerialNumber, time::Validity}; use x509_cert::{der::Encode, name::Name, serial_number::SerialNumber, time::Validity};
use yubikey::{ use yubikey::{
certificate, certificate::{yubikey_signer, Certificate},
certificate::yubikey_signer,
certificate::Certificate,
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId}, piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey, Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
}; };
@@ -45,12 +43,15 @@ static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
#[test] #[test]
#[ignore] #[ignore]
fn test_get_cccid() { fn test_get_cccid() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
match yubikey.cccid() { match yubikey.cccid() {
Ok(cccid) => trace!("CCCID: {:?}", cccid), Ok(cccid) => trace!("CCCID: {:?}", cccid),
Err(Error::NotFound) => trace!("CCCID not found"), Err(Error::NotFound) => trace!("CCCID not found"),
Err(err) => panic!("error getting CCCID: {:?}", err), Err(err) => panic!("error getting CCCID: {err:?}"),
} }
} }
@@ -61,12 +62,15 @@ fn test_get_cccid() {
#[test] #[test]
#[ignore] #[ignore]
fn test_get_chuid() { fn test_get_chuid() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
match yubikey.chuid() { match yubikey.chuid() {
Ok(chuid) => trace!("CHUID: {:?}", chuid), Ok(chuid) => trace!("CHUID: {:?}", chuid),
Err(Error::NotFound) => trace!("CHUID not found"), Err(Error::NotFound) => trace!("CHUID not found"),
Err(err) => panic!("error getting CHUID: {:?}", err), Err(err) => panic!("error getting CHUID: {err:?}"),
} }
} }
@@ -77,7 +81,10 @@ fn test_get_chuid() {
#[test] #[test]
#[ignore] #[ignore]
fn test_get_config() { fn test_get_config() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
let config_result = yubikey.config(); let config_result = yubikey.config();
assert!(config_result.is_ok()); assert!(config_result.is_ok());
trace!("config: {:?}", config_result.unwrap()); trace!("config: {:?}", config_result.unwrap());
@@ -90,7 +97,10 @@ fn test_get_config() {
#[test] #[test]
#[ignore] #[ignore]
fn test_list_keys() { fn test_list_keys() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
let keys_result = Key::list(&mut yubikey); let keys_result = Key::list(&mut yubikey);
assert!(keys_result.is_ok()); assert!(keys_result.is_ok());
trace!("keys: {:?}", keys_result.unwrap()); trace!("keys: {:?}", keys_result.unwrap());
@@ -103,7 +113,10 @@ fn test_list_keys() {
#[test] #[test]
#[ignore] #[ignore]
fn test_verify_pin() { fn test_verify_pin() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
assert!(yubikey.verify_pin(b"000000").is_err()); assert!(yubikey.verify_pin(b"000000").is_err());
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
} }
@@ -116,32 +129,40 @@ fn test_verify_pin() {
#[test] #[test]
#[ignore] #[ignore]
fn test_set_mgmkey() { fn test_set_mgmkey() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut rng = SysRng;
let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
let default_key = MgmKey::get_default(&yubikey).unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err()); assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok()); assert!(yubikey.authenticate(&default_key).is_ok());
// Set a protected management key. // Set a protected management key.
assert!(MgmKey::generate().set_protected(&mut yubikey).is_ok()); assert!(MgmKey::generate_for(&yubikey, &mut rng)
.unwrap()
.set_protected(&mut yubikey)
.is_ok());
let protected = MgmKey::get_protected(&mut yubikey).unwrap(); let protected = MgmKey::get_protected(&mut yubikey).unwrap();
assert!(yubikey.authenticate(MgmKey::default()).is_err()); assert!(yubikey.authenticate(&default_key).is_err());
assert!(yubikey.authenticate(protected.clone()).is_ok()); assert!(yubikey.authenticate(&protected).is_ok());
// Set a manual management key. // Set a manual management key.
let manual = MgmKey::generate(); let manual = MgmKey::generate_for(&yubikey, &mut rng).unwrap();
assert!(manual.set_manual(&mut yubikey, false).is_ok()); assert!(manual.set_manual(&mut yubikey, false).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err()); assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_err()); assert!(yubikey.authenticate(&default_key).is_err());
assert!(yubikey.authenticate(protected.clone()).is_err()); assert!(yubikey.authenticate(&protected).is_err());
assert!(yubikey.authenticate(manual.clone()).is_ok()); assert!(yubikey.authenticate(&manual).is_ok());
// Set back to the default management key. // Set back to the default management key.
assert!(MgmKey::set_default(&mut yubikey).is_ok()); assert!(MgmKey::set_default(&mut yubikey).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err()); assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(protected).is_err()); assert!(yubikey.authenticate(&protected).is_err());
assert!(yubikey.authenticate(manual).is_err()); assert!(yubikey.authenticate(&manual).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok()); assert!(yubikey.authenticate(&default_key).is_ok());
} }
// //
@@ -150,9 +171,10 @@ fn test_set_mgmkey() {
fn generate_self_signed_cert<KT: yubikey_signer::KeyType>() -> Certificate { fn generate_self_signed_cert<KT: yubikey_signer::KeyType>() -> Certificate {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = YUBIKEY.lock().unwrap();
let default_key = MgmKey::get_default(&yubikey).unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok()); assert!(yubikey.authenticate(&default_key).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1); let slot = SlotId::Retired(RetiredSlotId::R1);
@@ -168,8 +190,7 @@ fn generate_self_signed_cert<KT: yubikey_signer::KeyType>() -> Certificate {
// 0x80 0x00 ... (20bytes) is invalid because of high MSB (serial will keep the sign) // 0x80 0x00 ... (20bytes) is invalid because of high MSB (serial will keep the sign)
// we'll limit ourselves to 19 bytes serial. // we'll limit ourselves to 19 bytes serial.
let mut serial = [0u8; 19]; let serial = <[u8; 19]>::generate();
OsRng.fill_bytes(&mut serial);
let serial = SerialNumber::new(&serial[..]).expect("serial can't be more than 20 bytes long"); let serial = SerialNumber::new(&serial[..]).expect("serial can't be more than 20 bytes long");
let validity = Validity::from_now(Duration::new(500000, 0)).unwrap(); let validity = Validity::from_now(Duration::new(500000, 0)).unwrap();
@@ -211,6 +232,35 @@ fn generate_self_signed_rsa_cert() {
assert!(pubkey.verify_prehash(&hash, &sig).is_ok()); assert!(pubkey.verify_prehash(&hash, &sig).is_ok());
} }
#[test]
#[ignore]
fn generate_rsa3072() {
let mut yubikey = YUBIKEY.lock().unwrap();
let version = yubikey.version();
let default_key = MgmKey::get_default(&yubikey).unwrap();
assert!(yubikey.authenticate(&default_key).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1);
// Generate a new key in the selected slot.
let generated = piv::generate(
&mut yubikey,
slot,
AlgorithmId::Rsa3072,
PinPolicy::Default,
TouchPolicy::Default,
);
match generated {
Ok(key) => {
let pubkey = key.subject_public_key;
assert!(pubkey.bit_len() > 3072)
}
Err(e) => assert!((version.major, version.minor) < (5, 7) && e == Error::AlgorithmError),
}
}
#[test] #[test]
#[ignore] #[ignore]
fn generate_self_signed_ec_cert() { fn generate_self_signed_ec_cert() {
@@ -233,6 +283,31 @@ fn generate_self_signed_ec_cert() {
assert!(vk.verify(msg, &sig).is_ok()); assert!(vk.verify(msg, &sig).is_ok());
} }
#[test]
#[ignore]
fn generate_self_signed_cv_cert() {
let cert = generate_self_signed_cert::<ed25519_dalek::SigningKey>();
//
// Verify that the certificate is signed correctly
//
let pubkey =
ed25519_dalek::VerifyingKey::try_from(cert.subject_pki()).expect("ed25519 key expected");
let data = cert.cert.to_der().expect("serialize certificate");
let cert_len = data[2] as usize;
let tbs_cert_len = data[5] as usize;
let sig_algo_len: usize = 64;
let sig_start = cert_len - sig_algo_len + 3;
let msg = &data[3..6 + tbs_cert_len];
let sig =
ed25519_dalek::Signature::from_slice(&data[sig_start..sig_start + sig_algo_len]).unwrap();
use ed25519_dalek::Verifier;
assert!(pubkey.verify(msg, &sig).is_ok());
}
#[test] #[test]
#[ignore] #[ignore]
fn test_slot_id_display() { fn test_slot_id_display() {
@@ -286,10 +361,14 @@ fn test_slot_id_display() {
#[test] #[test]
#[ignore] #[ignore]
fn test_read_metadata() { fn test_read_metadata() {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = match YUBIKEY.lock() {
Ok(yubikey) => yubikey,
Err(poison) => poison.into_inner(),
};
let default_key = MgmKey::get_default(&yubikey).unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok()); assert!(yubikey.authenticate(&default_key).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1); let slot = SlotId::Retired(RetiredSlotId::R1);
@@ -313,12 +392,58 @@ fn test_read_metadata() {
} }
} }
#[test]
#[ignore]
fn test_read_metadata_missing_key() {
let mut yubikey = YUBIKEY.lock().unwrap();
let default_key = MgmKey::get_default(&yubikey).unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(&default_key).is_ok());
// we assume that at least one of these slots is empty
let slots_to_check = [
RetiredSlotId::R10,
RetiredSlotId::R11,
RetiredSlotId::R12,
RetiredSlotId::R13,
RetiredSlotId::R14,
RetiredSlotId::R15,
RetiredSlotId::R16,
RetiredSlotId::R17,
RetiredSlotId::R18,
RetiredSlotId::R19,
RetiredSlotId::R20,
];
for slot in slots_to_check {
let slot = SlotId::Retired(slot);
match piv::metadata(&mut yubikey, slot) {
Ok(_) => {
eprintln!("Key {} exists", slot);
}
Err(Error::NotSupported) => {
// Some YubiKeys don't support metadata
eprintln!("metadata not supported by this YubiKey");
return;
}
Err(Error::NotFound) => {
eprintln!("Key {} doesn't exist, ok.", slot);
return;
}
Err(err) => panic!("{}", err),
}
}
panic!("No empty slots to check");
}
#[test] #[test]
#[ignore] #[ignore]
fn test_parse_cert_from_der() { fn test_parse_cert_from_der() {
let bob_der = std::fs::read("tests/assets/Bob.der").expect(".der file not found"); let bob_der = std::fs::read("tests/assets/Bob.der").expect(".der file not found");
let cert = let cert = Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
certificate::Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
assert_eq!( assert_eq!(
cert.subject(), cert.subject(),
"CN=Bob", "CN=Bob",