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 = [
"RUSTSEC-2020-0071", # chrono
"RUSTSEC-2021-0145", # atty
"RUSTSEC-2023-0071", # rsa: Marvin Attack: potential key recovery
] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...]
+13 -2
View File
@@ -1,8 +1,19 @@
version: 2
updates:
- package-ecosystem: cargo
- package-ecosystem: cargo
versioning-strategy: lockfile-only
directory: "/"
allow:
- dependency-type: "all"
groups:
all-deps:
patterns:
- "*"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
time: '13:00'
open-pull-requests-limit: 10
+8 -8
View File
@@ -13,7 +13,7 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1
with:
profile: minimal
@@ -36,17 +36,17 @@ jobs:
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.65.0 # MSRV
toolchain: 1.85.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.65.0 # MSRV
toolchain: 1.85.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.65.0 # MSRV
toolchain: 1.85.0 # MSRV
deps: true
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1
with:
profile: minimal
@@ -60,7 +60,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
uses: actions/checkout@v6
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
@@ -78,11 +78,11 @@ jobs:
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.71.0
toolchain: 1.85.0 # MSRV
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
+3 -3
View File
@@ -15,16 +15,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
uses: actions/checkout@v6
- name: Cache cargo registry
uses: actions/cache@v1
uses: actions/cache@v5
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
uses: actions/cache@v5
with:
path: ~/.cargo/git
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/),
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)
### Added
- `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]
name = "yubikey"
version = "0.8.0"
version = "0.9.0-pre.0"
description = """
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
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
"""
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
@@ -14,49 +14,55 @@ readme = "README.md"
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021"
rust-version = "1.65"
rust-version = "1.85"
[workspace]
members = [".", "cli"]
[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]
der = "0.7.1"
des = "0.8"
elliptic-curve = "0.13"
aes = { version = "0.9.0-rc.4", features = ["zeroize"] }
bitflags = "2.5.0"
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"] }
hmac = "0.12"
log = "0.4"
nom = "7"
num-bigint-dig = { version = "0.8", features = ["rand"] }
num-traits = "0.2"
num-integer = "0.1"
ecdsa = { version = "0.16.7", features = ["digest", "pem"] }
p256 = "0.13"
p384 = "0.13"
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
nom = "8"
p256 = "0.14.0-rc.8"
p384 = "0.14.0-rc.8"
pbkdf2 = { version = "0.13.0-rc.10", default-features = false, features = ["hmac"] }
pcsc = "2.3.1"
rand_core = { version = "0.6", features = ["std"] }
rsa = { version = "0.9.2", features = ["sha2"] }
secrecy = "0.8"
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
signature = "2"
rand = "0.10"
rand_core = "0.10"
rsa = { version = "0.10.0-rc.17", features = ["sha2"] }
sha1 = { version = "0.11", features = ["oid"] }
sha2 = { workspace = true, features = ["oid"] }
signature = "3.0.0-rc.10"
subtle = "2"
uuid = { version = "1.2", features = ["v4"] }
x25519-dalek = "3.0.0-pre.6"
x509-cert.workspace = true
zeroize = "1"
[dev-dependencies]
env_logger = "0.10"
env_logger = "0.11"
once_cell = "1"
signature = "2"
[features]
untested = []
[[example]]
name = "change-mode"
required-features = ["untested"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+16 -13
View File
@@ -22,7 +22,7 @@ access provided by the [`pcsc` crate].
## About
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
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.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**:
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- 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
@@ -73,8 +77,7 @@ Rust **1.60** or newer.
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
## Supported Operating Systems
@@ -102,8 +105,8 @@ We would appreciate any help testing this functionality and removing the
## Testing
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
the default state (i.e. default PIN/PUK).
To run the full test suite, you'll need a supported YubiKey device connected
which is in the default state (i.e. default PIN/PUK).
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
@@ -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
as a derived work.
Copyright (c) 2014-2023 Yubico AB, Tony Arcieri
Copyright (c) 2014-2025 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -208,16 +211,16 @@ or conditions.
[//]: # (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
[docs-image]: https://docs.rs/yubikey/badge.svg
[docs-link]: https://docs.rs/yubikey/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[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-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
[deps-image]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs/status.svg
[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
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
[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
[cc-web]: https://contributor-covenant.org/
[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/),
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)
### Changed
- Bump `clap` to v4.0 ([#438])
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "yubikey-cli"
version = "0.8.0-pre"
version = "0.9.0-pre"
description = """
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
@@ -12,15 +12,15 @@ readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
edition = "2021"
rust-version = "1.56"
rust-version = "1.85"
[dependencies]
clap = { version = "4", features = ["derive"] }
env_logger = "0.10"
env_logger = "0.11"
hex = { package = "base16ct", version = "0.2", features = ["alloc"] }
log = "0.4"
once_cell = "1"
sha2 = "0.10"
sha2.workspace = true
termcolor = "1"
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 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
## Security Warning
@@ -84,7 +83,7 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/
[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
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
@@ -98,7 +97,6 @@ or conditions.
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[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 5]: https://www.yubico.com/products/yubikey-5-overview/
[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(
stream,
"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, "Issuer", &cert.tbs_certificate.issuer)?;
print_cert_attr(stream, "Subject", cert.tbs_certificate().subject())?;
print_cert_attr(stream, "Issuer", cert.tbs_certificate().issuer())?;
print_cert_attr(
stream,
"Fingerprint",
&hex::upper::encode_string(&fingerprint),
hex::upper::encode_string(&fingerprint),
)?;
print_cert_attr(
stream,
"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(())
}
+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
}
/// 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
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1;
@@ -198,6 +204,15 @@ pub enum Ins {
/// Get slot metadata
GetMetadata,
/// Management // Read Config
ReadConfig,
/// Management // Write Config
WriteConfig,
/// Management // DeviceReset
DeviceReset,
/// Other/unrecognized instruction codes
Other(u8),
}
@@ -223,6 +238,12 @@ impl Ins {
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
// Management
Ins::ReadConfig => 0x1d,
Ins::WriteConfig => 0x1c,
Ins::DeviceReset => 0x1f,
Ins::Other(code) => code,
}
}
@@ -231,6 +252,11 @@ impl Ins {
impl From<u8> for Ins {
fn from(code: u8) -> Self {
match code {
// Management
0x1d => Ins::ReadConfig,
0x1c => Ins::WriteConfig,
0x1f => Ins::DeviceReset,
0x20 => Ins::Verify,
0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry,
@@ -318,7 +344,7 @@ impl From<Vec<u8>> for Response {
}
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;
@@ -395,6 +421,9 @@ pub(crate) enum StatusWords {
/// Not enough memory
NoSpaceError,
/// Referenced data or reference data not found
ReferenceDataNotFoundError,
//
// Custom Yubico Status Word extensions
//
@@ -428,6 +457,7 @@ impl StatusWords {
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::ReferenceDataNotFoundError => 0x6a88,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
@@ -462,6 +492,7 @@ impl From<u16> for StatusWords {
0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6a88 => StatusWords::ReferenceDataNotFoundError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
+8 -4
View File
@@ -31,7 +31,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{Result, YubiKey};
use rand_core::{OsRng, RngCore};
use cipher::common::Generate;
use rand_core::CryptoRng;
use std::fmt::{self, Debug, Display};
/// CCCID offset
@@ -66,9 +67,12 @@ impl CardId {
/// Generate a random CCC Card ID
pub fn generate() -> Self {
let mut id = [0u8; Self::BYTE_SIZE];
OsRng.fill_bytes(&mut id);
Self(id)
Self(Generate::generate())
}
/// 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))
}
}
+80 -21
View File
@@ -41,8 +41,8 @@ use crate::{
};
use log::error;
use x509_cert::{
builder::{Builder, CertificateBuilder, Profile},
der::{self, referenced::OwnedToRef, Decode, Encode},
builder::{profile::BuilderProfile, Builder, CertificateBuilder},
der::{referenced::OwnedToRef, Decode, Encode},
name::Name,
serial_number::SerialNumber,
spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef},
@@ -109,23 +109,18 @@ impl Certificate {
extensions: F,
) -> Result<Self>
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 mut builder = CertificateBuilder::new(
Profile::Manual { issuer: None },
serial,
validity,
subject,
subject_pki,
&signer,
)
let signer =
yubikey_signer::Signer::<'_, KT>::new(yubikey, key, subject_pki.owned_to_ref())?;
let mut builder =
CertificateBuilder::new(SelfSigned { subject }, serial, validity, subject_pki)
.map_err(|_| Error::KeyError)?;
// Add custom extensions
extensions(&mut builder)?;
let cert = builder.build().map_err(|_| Error::KeyError)?;
let cert = builder.build(&signer).map_err(|_| Error::KeyError)?;
let cert = Self { cert };
cert.write(yubikey, key, CertInfo::Uncompressed)?;
@@ -174,23 +169,55 @@ impl Certificate {
/// Returns the Issuer field of the certificate.
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.
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.
pub fn subject_pki(&self) -> SubjectPublicKeyInfoRef<'_> {
self.cert
.tbs_certificate
.subject_public_key_info
.tbs_certificate()
.subject_public_key_info()
.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
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let object_id = slot.object_id();
@@ -251,7 +278,7 @@ pub mod yubikey_signer {
oid::db::rfc5912,
Encode, Sequence,
};
use sha2::{Digest, Sha256, Sha384};
use sha2::{Digest, Sha256, Sha384, Sha512};
use signature::Keypair;
use std::{cell::RefCell, fmt, io::Write, marker::PhantomData};
use x509_cert::spki::{
@@ -286,6 +313,22 @@ pub mod yubikey_signer {
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 {
const ALGORITHM: AlgorithmId = AlgorithmId::EccP256;
type Error = ecdsa::Error;
@@ -342,6 +385,22 @@ pub mod yubikey_signer {
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
pub struct YubiRsa<N: RsaLength> {
_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;
fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey {
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> {
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> {
let data = KT::prepare(msg)?;
+18 -1
View File
@@ -1,5 +1,7 @@
//! Miscellaneous constant values
#![allow(dead_code)]
/// YubiKey max buffer size
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_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
// Admin tags
@@ -18,3 +19,19 @@ pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
// Protected tags
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
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
AuthenticationError,
/// Error while building a certificate
CertificateBuilder,
/// Generic error
GenericError,
@@ -132,10 +135,11 @@ impl Error {
Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => f.write_str("applet error"),
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::AuthenticationError => f.write_str("authentication error"),
Error::CertificateBuilder => f.write_str("certificate builder error"),
Error::GenericError => f.write_str("generic error"),
Error::InvalidObject => f.write_str("invalid object"),
Error::KeyError => f.write_str("key error"),
@@ -146,7 +150,7 @@ impl Error {
Error::PcscError {
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"),
@@ -192,8 +196,14 @@ impl std::error::Error for Error {
}
}
impl From<x509_cert::der::Error> for Error {
fn from(_err: x509_cert::der::Error) -> Error {
impl From<der::Error> for Error {
fn from(_err: der::Error) -> Error {
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 error;
mod metadata;
mod mgm;
pub mod mgm;
#[cfg(feature = "untested")]
mod mscmap;
#[cfg(feature = "untested")]
@@ -71,7 +71,7 @@ pub use crate::{
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmKey, MgmType},
mgm::{MgmAlgorithmId, MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
+12 -15
View File
@@ -30,16 +30,15 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// 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 crate::{serialization::*, transaction::Transaction, Buffer, Error, Result};
#[cfg(feature = "untested")]
use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
#[cfg(feature = "untested")]
use std::iter;
use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
serialization::*,
transaction::Transaction,
Buffer, Error, Result,
};
const TAG_ADMIN: u8 = 0x80;
const TAG_PROTECTED: u8 = 0x88;
@@ -87,7 +86,6 @@ impl<T: MetadataType> Metadata<T> {
}
/// Write metadata
#[cfg(feature = "untested")]
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
@@ -104,7 +102,6 @@ impl<T: MetadataType> Metadata<T> {
}
/// Delete metadata
#[cfg(feature = "untested")]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[])
}
@@ -127,7 +124,6 @@ impl<T: MetadataType> Metadata<T> {
}
/// Set metadata item
#[cfg(feature = "untested")]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
@@ -157,8 +153,10 @@ impl<T: MetadataType> Metadata<T> {
// We did not find an existing tag, append
assert_eq!(offset, self.inner.len());
self.inner
.extend(iter::repeat(0).take(1 + get_length_size(item.len()) + item.len()));
self.inner.extend(iter::repeat_n(
0,
1 + get_length_size(item.len()) + item.len(),
));
Tlv::write(&mut self.inner[offset..], tag, item)?;
return Ok(());
@@ -193,7 +191,7 @@ impl<T: MetadataType> Metadata<T> {
// Move remaining data
let orig_len = self.inner.len();
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(
next_offset..orig_len,
@@ -214,7 +212,6 @@ impl<T: MetadataType> Metadata<T> {
}
/// Get the size of a length tag for the given length
#[cfg(feature = "untested")]
fn get_length_size(length: usize) -> usize {
if length < 0x80 {
1
+769 -165
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| {
error!("error parsing msroots: {:?}", e);
e
})
MsRoots::new(&data)
.map(Some)
.inspect_err(|e| error!("error parsing msroots: {:?}", e))
}
/// Write `msroots` file to YubiKey
+183 -84
View File
@@ -6,10 +6,10 @@
//! Supported algorithms:
//!
//! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048`
//! - RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
// Adapted from yubico-piv-tool:
@@ -45,35 +45,37 @@
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate},
consts::CB_OBJ_MAX,
error::{Error, Result},
mgm::MgmAlgorithmId,
policy::{PinPolicy, TouchPolicy},
serialization::*,
setting,
yubikey::YubiKey,
Buffer, ObjectId,
};
use elliptic_curve::{sec1::EncodedPoint as EcPublicKey, PublicKey};
use elliptic_curve::{sec1::Sec1Point as EcPublicKey, PublicKey};
use log::{debug, error, warn};
use p256::NistP256;
use p384::NistP384;
use rsa::{pkcs8::EncodePublicKey, BigUint, RsaPublicKey};
use rsa::{pkcs8::EncodePublicKey, BoxedUint, RsaPublicKey};
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use x509_cert::{der::Decode, spki::SubjectPublicKeyInfoOwned};
#[cfg(feature = "untested")]
use {
num_bigint_dig::traits::ModInverse,
num_integer::Integer,
num_traits::{FromPrimitive, One},
use x509_cert::{
der::{asn1::BitString, Decode},
spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoOwned},
};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::consts::CB_OBJ_MAX;
#[cfg(feature = "untested")]
use rsa::{traits::PrivateKeyParts, RsaPrivateKey};
/// PIV Applet Name
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_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")]
const KEYDATA_LEN: usize = 1024;
@@ -172,9 +177,9 @@ impl From<SlotId> for u8 {
impl Display for SlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SlotId::Management(r) => write!(f, "{:?}", r),
SlotId::Retired(r) => write!(f, "{:?}", r),
_ => write!(f, "{:?}", self),
SlotId::Management(r) => write!(f, "{r:?}"),
SlotId::Retired(r) => write!(f, "{r:?}"),
_ => write!(f, "{self:?}"),
}
}
}
@@ -327,7 +332,7 @@ impl From<RetiredSlotId> for u8 {
impl Display for RetiredSlotId {
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 {
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.
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.
EccP256,
/// ECDSA with the NIST P384 curve.
EccP384,
/// ED25519
Ed25519,
/// X25519
X25519,
}
impl TryFrom<u8> for AlgorithmId {
@@ -496,8 +513,12 @@ impl TryFrom<u8> for AlgorithmId {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x05 => Ok(AlgorithmId::Rsa3072),
0x16 => Ok(AlgorithmId::Rsa4096),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
0xE0 => Ok(AlgorithmId::Ed25519),
0xE1 => Ok(AlgorithmId::X25519),
_ => Err(Error::AlgorithmError),
}
}
@@ -508,8 +529,12 @@ impl From<AlgorithmId> for u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::Rsa3072 => 0x05,
AlgorithmId::Rsa4096 => 0x16,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
AlgorithmId::Ed25519 => 0xE0,
AlgorithmId::X25519 => 0xE1,
}
}
}
@@ -525,16 +550,25 @@ impl AlgorithmId {
match self {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
AlgorithmId::Rsa3072 => 192,
AlgorithmId::Rsa4096 => 256,
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
AlgorithmId::Ed25519 => 32,
AlgorithmId::X25519 => 32,
}
}
#[cfg(feature = "untested")]
fn get_param_tag(self) -> u8 {
match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => 0x01,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
AlgorithmId::Ed25519 => 0x07,
AlgorithmId::X25519 => 0x08,
}
}
}
@@ -606,7 +640,10 @@ pub fn generate(
let setting_roca: setting::Setting;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => {
if yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| 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.
/// - `Err(Error::AlgorithmError)` if `secret_p`/`secret_q` are invalid primes.
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Result<Self> {
let p = BigUint::from_bytes_be(secret_p);
let q = BigUint::from_bytes_be(secret_q);
let p = BoxedUint::from_be_slice_vartime(secret_p);
let q = BoxedUint::from_be_slice_vartime(secret_q);
let exp = BoxedUint::from(KEYDATA_RSA_EXP);
let totient = {
let p_t = &p - BigUint::one();
let q_t = &p - BigUint::one();
p_t.lcm(&q_t)
};
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();
let mut private_key = RsaPrivateKey::from_p_q(p.clone(), q.clone(), exp)
.map_err(|_| Error::AlgorithmError)?;
private_key
.precompute()
.map_err(|_| Error::AlgorithmError)?;
Ok(RsaKeyData {
p: Zeroizing::new(p.to_bytes_be()),
q: Zeroizing::new(q.to_bytes_be()),
dp: Zeroizing::new(dp.to_bytes_be()),
dq: Zeroizing::new(dq.to_bytes_be()),
qinv: Zeroizing::new(qinv),
p: Zeroizing::new(p.to_be_bytes().to_vec()),
q: Zeroizing::new(q.to_be_bytes().to_vec()),
dp: Zeroizing::new(
private_key
.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.
///
/// 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")]
pub fn import_rsa_key(
yubikey: &mut YubiKey,
@@ -821,7 +866,10 @@ pub fn import_rsa_key(
pin_policy: PinPolicy,
) -> Result<()> {
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (),
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => (),
_ => return Err(Error::AlgorithmError),
}
@@ -870,6 +918,34 @@ pub fn import_ecc_key(
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.
///
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
@@ -924,28 +1000,15 @@ pub fn decrypt_data(
/// Read metadata
pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result<SlotMetadata> {
let txn = yubikey.begin_transaction()?;
let templ = [0, Ins::GetMetadata.code(), 0, slot.into()];
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported); // Requires firmware 5.2.3
} else {
return Err(Error::GenericError);
}
}
let buf = Buffer::new(response.data().into());
SlotMetadata::try_from(buf)
txn.get_metadata(slot)
}
/// Metadata from a slot
#[derive(Debug)]
pub struct SlotMetadata {
/// Algorithm / Type of key
pub algorithm: ManagementAlgorithmId,
pub algorithm: SlotAlgorithmId,
/// PIN and touch policy
pub policy: Option<(PinPolicy, TouchPolicy)>,
/// Imported or generated key
@@ -966,13 +1029,14 @@ impl TryFrom<Buffer> for SlotMetadata {
combinator::{eof, map_res},
multi::fold_many1,
number::complete::u8,
Parser,
};
let out = fold_many1(
|input| Tlv::parse(input).map_err(|_| nom::Err::Error(())),
|| {
Ok(SlotMetadata {
algorithm: ManagementAlgorithmId::PinPuk,
algorithm: SlotAlgorithmId::PinPuk,
policy: None,
origin: None,
public: None,
@@ -983,15 +1047,15 @@ impl TryFrom<Buffer> for SlotMetadata {
|acc: Result<SlotMetadata>, tlv| match acc {
Ok(mut metadata) => match tlv.tag {
1 => {
metadata.algorithm = ManagementAlgorithmId::try_from(tlv.value[0])?;
metadata.algorithm = SlotAlgorithmId::try_from(tlv.value[0])?;
Ok(metadata)
}
2 => {
fn policy_parser(
i: &[u8],
) -> nom::IResult<&[u8], (PinPolicy, TouchPolicy)> {
let (i, pin) = map_res(u8, PinPolicy::try_from)(i)?;
let (i, touch) = map_res(u8, TouchPolicy::try_from)(i)?;
let (i, pin) = map_res(u8, PinPolicy::try_from).parse(i)?;
let (i, touch) = map_res(u8, TouchPolicy::try_from).parse(i)?;
let (i, _) = eof(i)?;
Ok((i, (pin, touch)))
@@ -1003,7 +1067,7 @@ impl TryFrom<Buffer> for SlotMetadata {
}
3 => {
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)?;
Ok((i, origin))
@@ -1015,7 +1079,7 @@ impl TryFrom<Buffer> for SlotMetadata {
}
4 => {
match metadata.algorithm {
ManagementAlgorithmId::Asymmetric(alg) => {
SlotAlgorithmId::Asymmetric(alg) => {
metadata.public = Some(read_public_key(alg, tlv.value, false)?);
}
_ => Err(Error::ParseError)?,
@@ -1063,7 +1127,8 @@ impl TryFrom<Buffer> for SlotMetadata {
},
err => err,
},
)(buf.as_ref());
)
.parse(buf.as_ref());
match out {
Ok((_, res)) => res,
@@ -1111,7 +1176,40 @@ fn read_public_key(
//
// 0x7f 0x49 -> Application | Constructed | 0x49
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
// 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 pubkey = RsaPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
BoxedUint::from_be_slice_vartime(&modulus),
BoxedUint::from_be_slice_vartime(&exp),
)
.map_err(|_| Error::InvalidObject)?;
Ok(SubjectPublicKeyInfoOwned::from_der(
@@ -1215,33 +1313,34 @@ fn read_public_key(
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// Algorithms as reported by the metadata command.
pub enum ManagementAlgorithmId {
pub enum SlotAlgorithmId {
/// Used on PIN and PUK slots.
PinPuk,
/// Used on the key management slot.
ThreeDes,
Management(MgmAlgorithmId),
/// Used on all other slots.
Asymmetric(AlgorithmId),
}
impl TryFrom<u8> for ManagementAlgorithmId {
impl TryFrom<u8> for SlotAlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0xff => Ok(ManagementAlgorithmId::PinPuk),
0x03 => Ok(ManagementAlgorithmId::ThreeDes),
oth => AlgorithmId::try_from(oth).map(ManagementAlgorithmId::Asymmetric),
0xff => Ok(SlotAlgorithmId::PinPuk),
oth => MgmAlgorithmId::try_from(oth)
.map(SlotAlgorithmId::Management)
.or_else(|_| AlgorithmId::try_from(oth).map(SlotAlgorithmId::Asymmetric)),
}
}
}
impl From<ManagementAlgorithmId> for u8 {
fn from(id: ManagementAlgorithmId) -> u8 {
impl From<SlotAlgorithmId> for u8 {
fn from(id: SlotAlgorithmId) -> u8 {
match id {
ManagementAlgorithmId::PinPuk => 0xff,
ManagementAlgorithmId::ThreeDes => 0x03,
ManagementAlgorithmId::Asymmetric(oth) => oth.into(),
SlotAlgorithmId::PinPuk => 0xff,
SlotAlgorithmId::Management(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.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SettingSource {
/// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
User,
@@ -51,15 +51,10 @@ pub enum SettingSource {
Admin,
/// Default setting.
#[default]
Default,
}
impl Default for SettingSource {
fn default() -> Self {
Self::Default
}
}
/// Setting booleans: configuration values sourced from a file or the environment.
///
/// 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
fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{}", key))
env::var(format!("YUBIKEY_PIV_{key}"))
.ok()
.map(|value| Setting {
source: SettingSource::User,
+152 -65
View File
@@ -5,6 +5,7 @@ use crate::{
apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result},
mgm::MgmKey,
otp,
piv::{self, AlgorithmId, SlotId},
serialization::*,
@@ -15,7 +16,7 @@ use log::{error, trace};
use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES};
use crate::mgm::{DeviceConfig, DeviceInfo, Lock};
const CB_PIN_MAX: usize = 8;
@@ -60,26 +61,32 @@ impl<'tx> Transaction<'tx> {
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.
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)
.p1(0x04)
.data(piv::APPLET_ID)
.data(applet)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
e
})?;
.inspect_err(|e| error!("failed communicating with card: '{}'", e))?;
if !response.is_success() {
error!(
"failed selecting application: {:04x}",
response.status_words().code()
);
error!("{}: {:04x}", error, response.status_words().code());
return Err(match response.status_words() {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: piv::APPLET_NAME,
},
StatusWords::NotFoundError => Error::AppletNotFound { applet_name },
_ => Error::GenericError,
});
}
@@ -92,7 +99,7 @@ impl<'tx> Transaction<'tx> {
// get version from device
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);
}
@@ -104,21 +111,11 @@ impl<'tx> Transaction<'tx> {
match version.major {
// YK4 requires switching to the YK applet to retrieve the serial
4 => {
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(otp::APPLET_ID)
.transmit(self, 0xFF)?
.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,
});
}
self.select_application(
otp::APPLET_ID,
otp::APPLET_NAME,
"failed selecting yk application",
)?;
let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
@@ -132,21 +129,11 @@ impl<'tx> Transaction<'tx> {
}
// reselect the PIV applet
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(piv::APPLET_ID)
.transmit(self, 0xFF)?
.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,
});
}
self.select_application(
piv::APPLET_ID,
piv::APPLET_NAME,
"failed selecting application",
)?;
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.
pub fn verify_pin(&self, pin: &[u8]) -> Result<()> {
if pin.len() > CB_PIN_MAX {
@@ -186,7 +191,7 @@ impl<'tx> Transaction<'tx> {
if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref());
query.data(data.as_slice());
}
let response = query.transmit(self, 261)?;
@@ -243,15 +248,14 @@ impl<'tx> Transaction<'tx> {
}
/// Set the management key (MGM).
#[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
let p2 = if require_touch { 0xfe } else { 0xff };
let mut data = [0u8; DES_LEN_3DES + 3];
data[0] = ALGO_3DES;
data[1] = KEY_CARDMGM;
data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
let mut data = Vec::with_capacity(usize::from(new_key.key_size()) + 3);
data.push(new_key.algorithm_id().into());
data.push(KEY_CARDMGM);
data.push(new_key.key_size());
data.extend_from_slice(new_key.as_ref());
let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2)
@@ -284,14 +288,23 @@ impl<'tx> Transaction<'tx> {
let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let key_len = if let AlgorithmId::Rsa1024 = algorithm {
128
} else {
256
};
if in_len != key_len {
AlgorithmId::Rsa1024 => {
if in_len != 128 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa2048 => {
if in_len != 256 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa3072 => {
if in_len != 384 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa4096 => {
if in_len != 512 {
return Err(Error::SizeError);
}
}
@@ -307,6 +320,19 @@ impl<'tx> Transaction<'tx> {
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 {
@@ -323,7 +349,9 @@ impl<'tx> Transaction<'tx> {
Tlv::write(
&mut buf[2..],
match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
(AlgorithmId::EccP256, true)
| (AlgorithmId::EccP384, true)
| (AlgorithmId::X25519, true) => 0x85,
_ => 0x81,
},
sign_in
@@ -335,10 +363,7 @@ impl<'tx> Transaction<'tx> {
let response = self
.transfer_data(&templ, &indata[..offset], 1024)
.map_err(|e| {
error!("sign command failed to communicate: {}", e);
e
})?;
.inspect_err(|e| error!("sign command failed to communicate: {}", e))?;
if !response.is_success() {
error!("failed sign command with code {:x}", response.code());
@@ -511,4 +536,66 @@ impl<'tx> Transaction<'tx> {
_ => 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},
transaction::Transaction,
};
use cipher::common::getrandom::SysRng;
use log::{error, info};
use pcsc::{Card, Disposition};
use rand_core::{OsRng, RngCore};
use pcsc::Card;
use rand_core::TryRng;
use std::{
cmp::{Ord, Ordering},
fmt::{self, Display},
str::FromStr,
};
@@ -59,7 +61,6 @@ use {
transaction::ChangeRefAction,
Buffer, ObjectId,
},
secrecy::ExposeSecret,
std::time::{SystemTime, UNIX_EPOCH},
};
@@ -67,6 +68,7 @@ use {
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// 3DES authentication
#[cfg(feature = "untested")]
pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key
@@ -75,10 +77,11 @@ pub(crate) const KEY_CARDMGM: u8 = 0x9b;
const TAG_DYN_AUTH: u8 = 0x7c;
/// 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.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct Serial(pub u32);
impl From<u32> for Serial {
@@ -143,6 +146,22 @@ impl Version {
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 {
@@ -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.
///
/// Almost all functionality in this library will require an open session
@@ -269,13 +321,10 @@ impl YubiKey {
pcsc::Disposition::ResetCard,
)?;
let pin = self
.pin
.as_ref()
.map(|p| Buffer::new(p.expose_secret().clone()));
let pin = self.pin.as_ref().map(|p| Buffer::new(p.clone()));
let txn = Transaction::new(&mut self.card)?;
txn.select_application()?;
txn.select_piv_application()?;
if let Some(p) = &pin {
txn.verify_pin(p)?;
@@ -293,7 +342,10 @@ impl YubiKey {
/// `YubiKey` implements `Drop` which automatically disconnects the card using
/// `Disposition::ResetCard`; you only need to call this function if you want to
/// 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 {
card,
name,
@@ -357,37 +409,46 @@ impl YubiKey {
}
/// 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()?;
// get a challenge from the card
let challenge = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
let card_response = Apdu::new(Ins::Authenticate)
.params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
.data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.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);
}
// 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];
data[0] = TAG_DYN_AUTH;
data[1] = 20; // 2 + 8 + 2 +8
data[2] = 0x80;
data[3] = 8;
data[4..12].copy_from_slice(&response);
data[12] = 0x81;
data[13] = 8;
OsRng.fill_bytes(&mut data[14..22]);
// If this exceeds a `u8` then the card is giving us unexpected data.
let auth_len = (2 + challenge_len + 2 + challenge_len)
.try_into()
.map_err(|_| Error::AuthenticationError)?;
let mut challenge = [0u8; 8];
challenge.copy_from_slice(&data[14..22]);
let mut data = Vec::with_capacity(4 + challenge_len + 2 + challenge_len);
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)
.params(ALGO_3DES, KEY_CARDMGM)
.params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
.data(data)
.transmit(&txn, 261)?;
@@ -396,14 +457,7 @@ impl YubiKey {
}
// compare the response from the card with our challenge
let response = mgm_key.encrypt(&challenge);
use subtle::ConstantTimeEq;
if response.ct_eq(&authentication.data()[4..12]).unwrap_u8() != 1 {
return Err(Error::AuthenticationError);
}
Ok(())
mgm_key.check_challenge(&host_challenge, &authentication.data()[4..])
}
/// Get the PIV keys contained in this YubiKey.
@@ -446,7 +500,7 @@ impl YubiKey {
}
if !pin.is_empty() {
self.pin = Some(CachedPin::new(pin.into()))
self.pin = Some(pin.into())
}
Ok(())
@@ -459,7 +513,7 @@ impl YubiKey {
// Force a re-select to unverify, because once verified the spec dictates that
// subsequent verify calls will return a "verification not needed" instead of
// the number of tries left...
txn.select_application()?;
txn.select_piv_application()?;
// WRONG_PIN is expected on successful query.
match txn.verify_pin(&[]) {
@@ -502,7 +556,7 @@ impl YubiKey {
}
if !new_pin.is_empty() {
self.pin = Some(CachedPin::new(new_pin.into()));
self.pin = Some(new_pin.into());
}
Ok(())
@@ -523,15 +577,11 @@ impl YubiKey {
admin_data
.set_item(TAG_ADMIN_TIMESTAMP, &tnow)
.map_err(|e| {
error!("could not set pin timestamp, err = {}", e);
e
})?;
.inspect_err(|e| error!("could not set pin timestamp, err = {}", e))?;
admin_data.write(&txn).map_err(|e| {
error!("could not write admin data, err = {}", e);
e
})?;
admin_data
.write(&txn)
.inspect_err(|e| error!("could not write admin data, err = {}", e))?;
Ok(())
}
@@ -581,7 +631,7 @@ impl YubiKey {
// Attempt to set the "PUK blocked" flag in admin data.
let mut admin_data = AdminData::read(&txn)
.map(|data| {
.inspect(|data| {
if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags.len() {
flags.copy_from_slice(item)
@@ -593,8 +643,6 @@ impl YubiKey {
);
}
}
data
})
.unwrap_or_default();
@@ -703,16 +751,15 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
type Error = Error;
fn try_from(reader: &'a Reader<'_>) -> Result<Self> {
let mut card = reader.connect().map_err(|e| {
error!("error connecting to reader '{}': {}", reader.name(), e);
e
})?;
let mut card = reader
.connect()
.inspect_err(|e| error!("error connecting to reader '{}': {}", reader.name(), e))?;
info!("connected to reader: {}", reader.name());
let mut app_version_serial = || -> Result<(Version, Serial)> {
let txn = Transaction::new(&mut card)?;
txn.select_application()?;
txn.select_piv_application()?;
let v = txn.get_version()?;
let s = txn.get_serial(v)?;
+155 -30
View File
@@ -3,18 +3,16 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use cipher::common::{getrandom::SysRng, Generate};
use log::trace;
use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore};
use rsa::{pkcs1v15, RsaPublicKey};
use sha2::{Digest, Sha256};
use signature::hazmat::PrehashVerifier;
use std::{env, str::FromStr, sync::Mutex, time::Duration};
use x509_cert::{der::Encode, name::Name, serial_number::SerialNumber, time::Validity};
use yubikey::{
certificate,
certificate::yubikey_signer,
certificate::Certificate,
certificate::{yubikey_signer, Certificate},
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
};
@@ -45,12 +43,15 @@ static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
#[test]
#[ignore]
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() {
Ok(cccid) => trace!("CCCID: {:?}", cccid),
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]
#[ignore]
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() {
Ok(chuid) => trace!("CHUID: {:?}", chuid),
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]
#[ignore]
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();
assert!(config_result.is_ok());
trace!("config: {:?}", config_result.unwrap());
@@ -90,7 +97,10 @@ fn test_get_config() {
#[test]
#[ignore]
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);
assert!(keys_result.is_ok());
trace!("keys: {:?}", keys_result.unwrap());
@@ -103,7 +113,10 @@ fn test_list_keys() {
#[test]
#[ignore]
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"123456").is_ok());
}
@@ -116,32 +129,40 @@ fn test_verify_pin() {
#[test]
#[ignore]
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!(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.
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();
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_ok());
assert!(yubikey.authenticate(&default_key).is_err());
assert!(yubikey.authenticate(&protected).is_ok());
// 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!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_err());
assert!(yubikey.authenticate(manual.clone()).is_ok());
assert!(yubikey.authenticate(&default_key).is_err());
assert!(yubikey.authenticate(&protected).is_err());
assert!(yubikey.authenticate(&manual).is_ok());
// Set back to the default management key.
assert!(MgmKey::set_default(&mut yubikey).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(protected).is_err());
assert!(yubikey.authenticate(manual).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
assert!(yubikey.authenticate(&protected).is_err());
assert!(yubikey.authenticate(&manual).is_err());
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 {
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(MgmKey::default()).is_ok());
assert!(yubikey.authenticate(&default_key).is_ok());
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)
// we'll limit ourselves to 19 bytes serial.
let mut serial = [0u8; 19];
OsRng.fill_bytes(&mut serial);
let serial = <[u8; 19]>::generate();
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();
@@ -211,6 +232,35 @@ fn generate_self_signed_rsa_cert() {
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]
#[ignore]
fn generate_self_signed_ec_cert() {
@@ -233,6 +283,31 @@ fn generate_self_signed_ec_cert() {
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]
#[ignore]
fn test_slot_id_display() {
@@ -286,10 +361,14 @@ fn test_slot_id_display() {
#[test]
#[ignore]
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.authenticate(MgmKey::default()).is_ok());
assert!(yubikey.authenticate(&default_key).is_ok());
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]
#[ignore]
fn test_parse_cert_from_der() {
let bob_der = std::fs::read("tests/assets/Bob.der").expect(".der file not found");
let cert =
certificate::Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
let cert = Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
assert_eq!(
cert.subject(),
"CN=Bob",