Compare commits

...

161 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
Tony Arcieri (iqlusion) ee3702a65e yubikey v0.8.0 (#518) 2023-08-15 19:29:22 -06:00
Tony Arcieri (iqlusion) 45915e5e5a Make RsaKeyData::new fallible (#517)
Replaces unwraps with `Error::AlgorithmError`
2023-08-15 18:33:30 -06:00
Tony Arcieri (iqlusion) 75ce24a3ea Handle metadata command not being supported in test (#516)
Some YubiKeys don't support this command. Instead of failing the test
when it happens, log a warning message instead.
2023-08-15 18:21:45 -06:00
Tony Arcieri (iqlusion) 78313360a1 Add clippy::unwrap_used lint (#515)
Lints for usages of `unwrap()` in the `yubikey` crate (not CLI yet).

Replaces them with `?` or `expect()` as the situation warrants.
2023-08-15 18:02:25 -06:00
Tony Arcieri (iqlusion) d226209ea4 Use doc_auto_cfg (#514)
Removes manual feature annotations for docs.rs
2023-08-15 16:39:29 -06:00
Tony Arcieri (iqlusion) de142256d0 Bump clippy to use Rust 1.71 (#513) 2023-08-15 16:31:13 -06:00
hko-s 485d49a6c8 Make YubiKey::open() more robust (#504)
On systems with a physical card-reader, the previous implementation falsely
reports "multiple YubiKeys detected!", even if only one YubiKey is connected.
This change attempts to actually open each reader as a YubiKey, and only
reports "multiple YubiKeys" if it can actually open more than one.

Additionally, this change avoids resetting the YubiKeys in case we find more
than one.
2023-08-15 16:20:04 -06:00
Tony Arcieri (iqlusion) 9932d05428 Remove chrono dependency (#512)
It's no longer used as of #495
2023-08-15 16:18:08 -06:00
Tony Arcieri (iqlusion) 363648bbc5 Cargo.lock: bump dependencies (#511)
Upgrades the following dependencies:

    $ cargo update
    Updating crates.io index
    Updating aho-corasick v0.7.20 -> v1.0.4
      Adding android-tzdata v0.1.1
      Adding anstream v0.3.2
      Adding anstyle v1.0.1
      Adding anstyle-parse v0.2.1
      Adding anstyle-query v1.0.0
      Adding anstyle-wincon v1.0.2
    Updating base64ct v1.5.3 -> v1.6.0
      Adding bitflags v2.4.0
    Updating block-buffer v0.10.3 -> v0.10.4
    Updating bumpalo v3.11.1 -> v3.13.0
    Updating cc v1.0.78 -> v1.0.82
    Updating chrono v0.4.23 -> v0.4.26
    Updating cipher v0.4.3 -> v0.4.4
    Updating clap v4.0.32 -> v4.3.21
      Adding clap_builder v4.3.21
    Updating clap_derive v4.0.21 -> v4.3.12
    Updating clap_lex v0.3.0 -> v0.5.0
    Removing codespan-reporting v0.11.1
      Adding colorchoice v1.0.0
    Updating const-oid v0.9.2 -> v0.9.5
    Updating core-foundation-sys v0.8.3 -> v0.8.4
    Updating cpufeatures v0.2.5 -> v0.2.9
    Updating crypto-bigint v0.5.1 -> v0.5.2
    Removing cxx v1.0.85
    Removing cxx-build v1.0.85
    Removing cxxbridge-flags v1.0.85
    Removing cxxbridge-macro v1.0.85
    Updating der v0.7.6 -> v0.7.8
    Updating der_derive v0.7.1 -> v0.7.2
    Updating digest v0.10.6 -> v0.10.7
    Updating ecdsa v0.16.7 -> v0.16.8
    Updating elliptic-curve v0.13.4 -> v0.13.5
    Updating errno v0.2.8 -> v0.3.2
    Updating generic-array v0.14.6 -> v0.14.7
    Updating getrandom v0.2.8 -> v0.2.10
    Updating heck v0.4.0 -> v0.4.1
    Updating hermit-abi v0.2.6 -> v0.3.2
    Updating iana-time-zone v0.1.53 -> v0.1.57
    Updating iana-time-zone-haiku v0.1.1 -> v0.1.2
    Removing io-lifetimes v1.0.3
    Updating is-terminal v0.4.2 -> v0.4.9
    Updating js-sys v0.3.60 -> v0.3.64
    Updating libc v0.2.139 -> v0.2.147
    Updating libm v0.2.6 -> v0.2.7
    Removing link-cplusplus v1.0.8
    Updating linux-raw-sys v0.1.4 -> v0.4.5
    Updating log v0.4.17 -> v0.4.20
    Updating nom v7.1.2 -> v7.1.3
    Updating num-bigint-dig v0.8.2 -> v0.8.4
    Updating num-traits v0.2.15 -> v0.2.16
    Updating once_cell v1.17.0 -> v1.18.0
    Removing os_str_bytes v6.4.1
    Updating p256 v0.13.0 -> v0.13.2
    Updating pbkdf2 v0.12.1 -> v0.12.2
    Updating pkg-config v0.3.26 -> v0.3.27
    Updating primeorder v0.13.0 -> v0.13.2
    Removing proc-macro-error v1.0.4
    Removing proc-macro-error-attr v1.0.4
    Updating proc-macro2 v1.0.56 -> v1.0.66
    Updating quote v1.0.26 -> v1.0.32
    Updating regex v1.7.0 -> v1.9.3
      Adding regex-automata v0.3.6
    Updating regex-syntax v0.6.28 -> v0.7.4
    Updating rustix v0.36.6 -> v0.38.8
    Removing scratch v1.0.3
    Updating sec1 v0.7.1 -> v0.7.3
    Updating serde v1.0.152 -> v1.0.183
    Updating sha2 v0.10.6 -> v0.10.7
    Updating smallvec v1.10.0 -> v1.11.0
    Updating subtle v2.4.1 -> v2.5.0
    Removing syn v1.0.107
    Removing syn v2.0.15
      Adding syn v2.0.28
    Updating termcolor v1.1.3 -> v1.2.0
    Updating unicode-ident v1.0.6 -> v1.0.11
    Removing unicode-width v0.1.10
      Adding utf8parse v0.2.1
    Updating uuid v1.2.2 -> v1.4.1
    Updating wasm-bindgen v0.2.83 -> v0.2.87
    Updating wasm-bindgen-backend v0.2.83 -> v0.2.87
    Updating wasm-bindgen-macro v0.2.83 -> v0.2.87
    Updating wasm-bindgen-macro-support v0.2.83 -> v0.2.87
    Updating wasm-bindgen-shared v0.2.83 -> v0.2.87
      Adding windows v0.48.0
    Updating windows-sys v0.42.0 -> v0.48.0
      Adding windows-targets v0.48.2
    Updating windows_aarch64_gnullvm v0.42.0 -> v0.48.2
    Updating windows_aarch64_msvc v0.42.0 -> v0.48.2
    Updating windows_i686_gnu v0.42.0 -> v0.48.2
    Updating windows_i686_msvc v0.42.0 -> v0.48.2
    Updating windows_x86_64_gnu v0.42.0 -> v0.48.2
    Updating windows_x86_64_gnullvm v0.42.0 -> v0.48.2
    Updating windows_x86_64_msvc v0.42.0 -> v0.48.2
    Updating x509-cert v0.2.3 -> v0.2.4
2023-08-14 19:07:13 -06:00
Arthur Gautier 6a1e1603ef Use x509-cert certificate builder (#495)
Co-authored-by: Carl Wallace <carl@redhoundsoftware.com>
2023-08-14 18:31:39 -06:00
Arthur Gautier 8cf18d2986 Bump rsa to 0.9.0 (#502) 2023-05-03 06:22:45 -06:00
dependabot[bot] 07281440c0 Bump rsa from 0.9.0-pre.1 to 0.9.0-pre.2 (#500)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.0-pre.1 to 0.9.0-pre.2.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.0-pre.1...v0.9.0-pre.2)

---
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-04-27 08:54:23 -06:00
dependabot[bot] cd76a55318 Bump rsa from 0.9.0-pre.0 to 0.9.0-pre.1 (#497)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.0-pre.0 to 0.9.0-pre.1.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.0-pre.0...v0.9.0-pre.1)

---
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-04-10 10:10:55 -06:00
dependabot[bot] 23bbf1b783 Bump zeroize from 1.5.7 to 1.6.0 (#496)
Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.5.7 to 1.6.0.
- [Release notes](https://github.com/RustCrypto/utils/releases)
- [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.5.7...zeroize-v1.6.0)

---
updated-dependencies:
- dependency-name: zeroize
  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-04-03 11:33:52 -06:00
Tony Arcieri (iqlusion) cafb0b2c18 v0.8.0-pre.0 (#491) 2023-03-14 17:17:07 -06:00
Tony Arcieri (iqlusion) 0c7441a81e Bump asymmetric crypto dependencies; MSRV 1.65 (#490)
Bumps the following dependencies to the latest versions:

- `elliptic-curve` v0.13
- `k256` v0.13
- `p256` v0.13
- `p384` v0.13
- `pbkdf2` v0.12
- `rsa` v0.9.0-pre.0
- `signature` v2
2023-03-14 11:53:00 -06:00
str4d a50addc15b Fix StatusWords::code output for StatusWords::VerifyFailError (#479)
* Fix `StatusWords::code` output for `StatusWords::VerifyFailError`

Closes iqlusioninc/yubikey.rs#473.

* Refactor `Transaction::transfer_data` to match on `StatusWords`

This makes the code more reliable, such that it would have avoided
the bug in iqlusioninc/yubikey.rs#473.
2023-02-12 12:02:22 -07:00
str4d 0809f300b7 Return errors from YubiKey::open_by_serial that indicate a key may exist (#477)
* Return errors from `YubiKey::open_by_serial` that indicate a key may exist

The only such error at the moment is `pcsc::Error::SharingViolation`, which
indicates a transient failure to access a specific reader that could have
been the one we needed (and so a future retry might succeed).

Closes iqlusioninc/yubikey.rs#458.

* Avoid resetting unused devices in YubiKey::open_by_serial

We only connect to readers so that we can determine their serial. We
now try to ensure that the order in which we connect to them doesn't
have an effect on their state after we are done.
2023-02-12 10:22:05 -07:00
str4d d55079f9a6 Enable library users to detect if a smart card doesn't support PIV (#476)
* Enable library users to detect if a smart card doesn't support PIV

Closes iqlusioninc/yubikey.rs#456.

* Avoid resetting the card if we fail to select PIV or fetch version/serial
2023-02-12 10:20:34 -07:00
str4d 10241230b3 Raise minimum pcsc version to remove workaround (#478)
In iqlusioninc/yubikey.rs#88 we added a workaround for what turned out
to be a bug in `pcsc`, where an error was returned if no readers were
available, instead of returning an empty iterator. `pcsc 2.3.1` was
published in 2019, so we can safely rely on it.
2023-02-12 10:18:55 -07:00
dependabot[bot] 1e02f135f0 Bump env_logger from 0.9.3 to 0.10.0 (#452)
Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.9.3 to 0.10.0.
- [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.9.3...v0.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-08 15:24:30 -07:00
Tony Arcieri (iqlusion) 0c2633ab31 transaction: comment cleanup in get_serial (#467)
Moves comments about each YubiKey version number above the arms of the
`match` expression
2023-01-07 13:35:52 -08:00
Tony Arcieri (iqlusion) f49c617a9d Improve parsing of serial numbers (#466)
Checks the length of the data returned when querying the serial number,
returning an error if it's longer than 4 bytes, and left-padding with
zeroes if it's too short.

This fixes some potential panics due to incorrect slice lengths as were
experienced in #465
2023-01-07 08:35:37 -08:00
Tony Arcieri (iqlusion) 1d33ea1747 Cargo.lock: bump dependencies (#463) 2023-01-02 10:22:07 -08:00
str4d 18eb4bf4f4 Add YubiKey::disconnect (#462)
This exposes `pcsc::Card::disconnect` to allow alternate disposition
methods.
2023-01-02 10:15:31 -08:00
str4d 10941bfb5b Add partial Debug impls for Context and YubiKey (#457)
This enables `yubikey::Result<T>` to be debug-formatted, for example
when wrapping the output of an API method in `dbg!()`.
2023-01-01 11:16:10 -07:00
Tony Arcieri (iqlusion) 002491193e Cargo.lock: bump dependencies + audit config (#451)
This should get the security audit passing again
2022-11-28 10:19:39 -08:00
Tony Arcieri (iqlusion) 2e5139b237 yubikey-cli v0.7.0 (#446) 2022-11-14 17:17:02 -08:00
Tony Arcieri (iqlusion) d880faaefa yubikey v0.7.0 (#444) 2022-11-14 15:53:00 -08:00
Tony Arcieri (iqlusion) cc00a10c2f img: add logo-sq.png (#445)
Square logo for use with rustdoc
2022-11-14 15:15:19 -08:00
Tony Arcieri (iqlusion) 0a2e798894 Switch from subtle-encoding to base16ct (#443) 2022-11-14 14:26:07 -08:00
Tony Arcieri (iqlusion) 5c4259023f Switch from lazy_static to once_cell (#442)
The latter will hopefully eventually be upstreamed into `std`.
2022-11-14 12:52:27 -08:00
Tony Arcieri (iqlusion) 57bb088c7d yubikey-cli: bump x509-parser to v0.14 (#441) 2022-11-14 12:30:55 -08:00
Tony Arcieri (iqlusion) ccf19a3668 Bump rsa to v0.7.1 (#440) 2022-11-14 11:08:05 -08:00
dependabot[bot] db13fce53b Bump clap from 3.2.23 to 4.0.23 (#438)
Bumps [clap](https://github.com/clap-rs/clap) from 3.2.23 to 4.0.23.
- [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/v3.2.23...v4.0.23)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 10:44:39 -08:00
Ferdinand Linnenberg 0071566097 feat: fixed incorrect issuer for certificates & added x509 prints (#437) 2022-11-14 10:42:07 -08:00
Tony Arcieri (iqlusion) d8653bc6f0 Use chrono v0.4.23 or newer (#436)
Addresses RUSTSEC-2020-0159
2022-11-12 15:42:34 -08:00
Tony Arcieri (iqlusion) 603b102932 Followups from #371 (#435)
Implements the changes I suggested in that PR, which are related to
naming conventions and documentation.
2022-11-12 15:41:58 -08:00
Tony Arcieri (iqlusion) 7470b1613a Cargo.lock: bump dependencies (#434) 2022-11-12 13:27:08 -08:00
Tony Arcieri (iqlusion) 4310cc0f9a Fix build and clippy warnings (#433) 2022-11-12 13:15:42 -08:00
Arthur Gautier 87ed7b2338 Adds support for metadata command (#371)
On firmware 5.4.3, yubikey introduced a metadata command. This returns
the policy attached to as slot as well as the public key of the pair in
the slot.

https://docs.yubico.com/yesdk/users-manual/application-piv/apdu/metadata.html
2022-11-12 11:12:10 -08:00
Shella Stephens 7866d8d53e MSRV 1.60.0 (#423) 2022-11-07 08:58:45 -07:00
Shella Stephens 744238fd77 Cargo.lock: update dependencies & fix audit 2022-11-07 07:56:25 -07:00
william light bbb186f95e Display inner PC/SC errors (#420) 2022-10-31 15:00:21 -07:00
dependabot[bot] c89cc5acd0 Bump sha2 from 0.10.2 to 0.10.5 (#407)
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.2 to 0.10.5.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.2...sha2-v0.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 14:29:21 -07:00
dependabot[bot] 2294c1cc3a Bump chrono from 0.4.21 to 0.4.22 (#405)
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.21 to 0.4.22.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.21...v0.4.22)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 14:29:08 -07:00
dependabot[bot] 65e201db0f Bump uuid from 1.1.2 to 1.2.1 (#415)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.1.2 to 1.2.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.1.2...1.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 14:28:56 -07:00
Tony Arcieri (iqlusion) b571f81007 yubikey-cli v0.6.0 (#404) 2022-08-10 17:23:41 -07:00
Tony Arcieri (iqlusion) 0a36a37ae3 yubikey v0.6.0 (#403) 2022-08-10 16:41:26 -07:00
Tony Arcieri (iqlusion) 3463d109b2 Bump der-parser to v8; x509-parser to v0.14 (#402) 2022-08-10 15:19:21 -07:00
dependabot[bot] 014b7ee6fd Bump p384 from 0.10.0 to 0.11.2 (#401)
Bumps [p384](https://github.com/RustCrypto/elliptic-curves) from 0.10.0 to 0.11.2.
- [Release notes](https://github.com/RustCrypto/elliptic-curves/releases)
- [Commits](https://github.com/RustCrypto/elliptic-curves/compare/p384/v0.10.0...p384/v0.11.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-10 14:57:39 -07:00
vdods 498de4c10d Adding some common traits for certain enum types to support maps. (#372) 2022-08-10 14:34:58 -07:00
Tony Arcieri (iqlusion) 98b038c873 Cargo.lock: bump dependencies (#400) 2022-08-10 14:26:33 -07:00
Tony Arcieri (iqlusion) fab9d25b0a cli: migrate from gumdrop to clap v3 (#379)
`gumdrop` was originally chosen for its minimalist set of dependencies,
but `clap` v3 has a slimmed down set of dependencies and better UX.
2022-05-24 21:45:26 -06:00
dependabot[bot] bb80551324 Bump uuid from 0.8.2 to 1.0.0 (#376)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 0.8.2 to 1.0.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/0.8.2...1.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-23 20:18:58 -07:00
Tony Arcieri (iqlusion) 9e20ecfe55 RustCrypto crate upgrades; MSRV 1.57 (#378)
Updates all of the RustCrypto dependencies to the latest versions:

- `des` v0.8
- `elliptic-curve` v0.12
- `hmac` v0.12
- `num-bigint-dig` v0.8
- `pbkdf2` v0.11
- `p256` v0.11
- `p384` v0.10
- `rsa` v0.6
- `sha1` v0.10 (replacing `sha-1`)
- `sha2` v0.10
2022-05-23 20:04:12 -07:00
Tony Arcieri (iqlusion) fac83c60fb Cargo.lock: bump dependencies (#375) 2022-05-23 17:52:53 -07:00
Shella Stephens 914f9bee0d Cargo.lock: update dependencies & fix cargo audit (#365) 2022-03-07 16:29:35 -07:00
Ferdinand Linnenberg 83de59983f Add Display formatter to SlotId (#353) 2022-02-11 13:10:53 -08:00
Shella Stephens e21395c934 Cargo.lock: update dependencies (#347) 2022-01-31 08:00:16 -07:00
Tony Arcieri (iqlusion) 935fea0868 Bump p256 => v0.10; p384 => v0.9 (#344) 2022-01-17 15:08:48 -08:00
Tony Arcieri (iqlusion) dd4b1c60a4 2021 edition upgrade; MSRV 1.56 (#343)
Changes the `edition` to 2021 in both the `yubikey` and `yubikey-cli`
crates.

Removes `TryFrom`/`TryInto` imports, now that they're in the prelude.
2022-01-17 14:54:01 -08:00
Shella Stephens 74a50f0f0c Bump dependencies & fix security audit (#340)
* Bump dependencies & fix security audit

* allow dead code for issuer field #[allow(dead_code)] in Certificates struct
2022-01-10 08:40:58 -07:00
Tony Arcieri (iqlusion) 86d482b38d yubikey-cli v0.5.0 (#328) 2021-11-21 08:10:17 -08:00
Tony Arcieri (iqlusion) edf74871ba yubikey v0.5.0 (#327) 2021-11-21 07:42:39 -08:00
Tony Arcieri (iqlusion) b11d5c409b Cargo.lock: bump dependencies (#326) 2021-11-21 07:06:17 -08:00
str4d 52107281df nom 7 (#322) 2021-10-19 06:38:38 -07:00
Shella Stephens bcef792f69 Update dependencies & add RUSTSEC-2020-0071 to audit.toml (#323) 2021-10-18 17:07:32 -06:00
Tony Arcieri (iqlusion) 10a7ead932 Cargo.lock: bump dependencies (#317) 2021-09-11 13:55:34 -07:00
Benno Rice 54ce90d51d Update dependencies (#315)
* Update rsa dependency to 0.5

* Update pbkdf dependency to 0.9

* Update x509-parser dependency to 0.11

* Update crypto-bigint subdepdendency to 0.2.6
2021-09-10 10:44:59 -07:00
Tony Arcieri (iqlusion) 3905104b52 Cargo.lock: bump dependencies (#308) 2021-08-20 18:09:51 -07:00
Tony Arcieri (iqlusion) 97e15abcee Cargo.lock: bump dependencies (#304) 2021-07-26 14:52:06 -07:00
Shella Stephens da7e7af109 Add deps.rs badge (#299) 2021-07-19 15:07:41 -07:00
Shella Stephens 6e96087b93 Cargo.lock: update deps (#300) 2021-07-19 15:00:16 -07:00
Tony Arcieri (iqlusion) f3bb858a2f Cargo.lock: bump dependencies (#298) 2021-07-19 09:05:32 -07:00
Tony Arcieri (iqlusion) ac72797d1f yubikey v0.4.2 (#291) 2021-07-13 06:35:53 -07:00
Tony Arcieri (iqlusion) fdd3b8730a Make yubikey::Buffer a pub type (#290) 2021-07-13 06:05:24 -07:00
Tony Arcieri (iqlusion) d51ec0a225 Have YubiKey::block_puk take &mut self as argument (#289)
This is effectively the same signature; it just uses `self` instead of a
named argument.
2021-07-13 05:55:24 -07:00
Tony Arcieri (iqlusion) d601c33ba3 yubikey v0.4.1 (#288) 2021-07-12 19:37:12 -07:00
Tony Arcieri (iqlusion) 8e52d75992 Rename Ccc to CccId (#287) 2021-07-12 19:28:46 -07:00
Tony Arcieri (iqlusion) 42ae5fb974 Rename SettingValue to Setting. (#286)
Breaking change, but the crate is fresh and there's time to yank and
republish.
2021-07-12 17:36:42 -07:00
Tony Arcieri (iqlusion) 224d346f09 yubikey-cli v0.4.0 (#284) 2021-07-12 14:33:51 -07:00
Tony Arcieri 01e5bba33f README.md: remove gitter badge 2021-07-12 14:10:11 -07:00
Tony Arcieri 48f42780df README.md: remove maintenance badge 2021-07-12 14:07:08 -07:00
Tony Arcieri (iqlusion) 92f770805f yubikey v0.4.0 (#283) 2021-07-12 14:02:59 -07:00
Tony Arcieri (iqlusion) 563f6f9ccc Extract consts module (#282)
Extracts miscellaneous constants that were floating around in the
toplevel into their own module.
2021-07-12 12:54:54 -07:00
Tony Arcieri (iqlusion) 5f418bbd1d Doc improvements and minor cleanups (#281) 2021-07-12 11:57:42 -07:00
Tony Arcieri (iqlusion) 47776ebf0b Fix parsing local DoS (#279)
Closes #152

Adds additional checks when parsing TLV records to ensure panic-free
operation.
2021-07-12 11:19:26 -07:00
Tony Arcieri (iqlusion) 227518dd1b Rename readers module to reader; Readers => Context (#278)
Renames the `readers` module to be singular: `reader`.

Renames the former `readers::Readers` struct to `reader::Context`.
2021-07-12 11:01:12 -07:00
Tony Arcieri (iqlusion) e6cea2eca6 Rename key module to piv (#277)
Now that the crate is named `yubikey` rather than `yubikey-piv`, it
makes more sense to call this module out as PIV-related functionality.
2021-07-12 10:42:55 -07:00
Tony Arcieri (iqlusion) e249e91297 Replace getrandom with rand_core (#276)
`rand_core::OsRng` provides a facade over `getrandom` which simplifies
error handling.
2021-07-12 09:58:58 -07:00
Tony Arcieri (iqlusion) 1018127843 Fix generate_self_signed_ec_cert integration test (#275)
Unfortunately these tests can't be run in CI as they require a YubiKey
to test against.

The YubiKey generates an ASN.1 DER-encoded ECDSA signature, but the test
was using a fixed-width signature.

The test now passes live against a YubiKey.
2021-07-12 09:05:40 -07:00
Tony Arcieri (iqlusion) 1765e11bc0 Flatten API (#274)
Re-exports types from the toplevel instead of placing them in individual
modules (often which only contain one type).

This makes the API easier for users to navigate, while still retaining
the same module structure internally.

Additionally, this commit uses the `uuid` crate for modeling UUIDs.
2021-07-12 08:40:31 -07:00
Tony Arcieri (iqlusion) 1228d16439 Rename settings::BoolValue => ConfigValue; refactor/cleanup (#272)
Renames the type used for storing a configuration setting.

Also changes the internal functions to use `Option<ConfigValue>` as the
return value, rather than comparing to a default value, which makes them
slightly more idiomatic.
2021-07-11 14:53:54 -07:00
Tony Arcieri (iqlusion) de51b0cc46 Add Result alias (#271)
Adds a `yubikey::Result` alias with `yubikey::Error` as the error type.

Since we only have one `Error` type, this simplifies the return types
where a `Result` is returned.
2021-07-11 09:44:08 -07:00
Tony Arcieri (iqlusion) 1051eaf26d Rename Ccc::cccid => Ccc::card_id (#270)
Better reflects the return type
2021-07-11 09:00:58 -07:00
Tony Arcieri (iqlusion) a1d9c7afc5 Fix clippy::upper_case_acronyms nits; small cleanups (#269)
Renames the following to match Rust idioms:
- `APDU` => `Apdu`
- `CCC` => `Ccc`
- `CHUID` => `ChuId`

Also removes `Copy` from `mscmap::Container`, which fixes a clippy lint
about its usage of `to_bytes`.
2021-07-11 08:51:25 -07:00
Tony Arcieri (iqlusion) 2c06626c25 Bump elliptic-curve to v0.10; MSRV 1.51+ (#268)
Also updates the following:
- `p256` v0.9
- `p384` v0.8
2021-07-11 08:14:14 -07:00
Tony Arcieri (iqlusion) a2a912fc3c Rename to yubikey.rs (#267)
We now have publishing rights to the `yubikey` crate.

This commit renames the project to yubikey.rs

Co-authored-by: Tony Arcieri <bascule@gmail.com>
2021-07-10 17:02:59 -07:00
dependabot[bot] c9e2edc41f Bump sha-1 from 0.9.5 to 0.9.6 (#256)
Bumps [sha-1](https://github.com/RustCrypto/hashes) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha-1-v0.9.5...sha-1-v0.9.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-19 11:35:31 -07:00
dependabot[bot] 20bf9b0679 Bump sha2 from 0.9.4 to 0.9.5 (#257)
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.9.4 to 0.9.5.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.9.4...sha2-v0.9.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-19 11:17:23 -07:00
dependabot[bot] 0d4d4f7f06 Bump p256 from 0.8.0 to 0.8.1 (#255)
Bumps [p256](https://github.com/RustCrypto/elliptic-curves) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/RustCrypto/elliptic-curves/releases)
- [Commits](https://github.com/RustCrypto/elliptic-curves/compare/p256/v0.8.0...p256/v0.8.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 14:17:17 -07:00
Shella Stephens d31872964d Cargo.lock: bump sha-1 & sha2 (#254) 2021-05-10 09:05:28 -07:00
Tony Arcieri (iqlusion) 865353f4da RustCrypto dependency updates; MSRV 1.47+ (#251)
Updates the following dependencies:
- `des` v0.7
- `elliptic-curve` v0.9
- `hmac` v0.11
- `pbkdf2` v0.8
- `p256` v0.8
- `p384` v0.7
2021-04-30 07:09:24 -07:00
Shella Stephens 1ad17bb025 .github/workflows/ci.yml: fix override: true (#250)
* .github/workflows/ci.yml: fix override: true

* cargo update
2021-04-27 14:36:20 -07:00
Shella Stephens d33e80faea Update rsa to v0.4.0 & fix cargo audit (#246)
* Bump rsa to v0.4.0
2021-03-29 09:12:33 -07:00
Tony Arcieri (iqlusion) e61682be43 yubikey-cli v0.3.0 (#241) 2021-03-22 10:23:15 -07:00
Tony Arcieri (iqlusion) 43dfc06875 yubikey-piv v0.3.0 (#240) 2021-03-22 09:45:19 -07:00
Tony Arcieri (iqlusion) e230390e7e Cargo.lock: bump dependencies (#238) 2021-03-22 09:27:28 -07:00
Tony Arcieri (iqlusion) ef3df46ed2 Cargo.lock: bump deps (#227) 2021-02-03 06:13:01 -08:00
str4d 18e3636161 Replace MgmKey::set with MgmKey::{set_default, set_manual} (#224)
* Add MgmKey::set_default method

This wipes any metadata related to derived and PIN-protected management
keys, returning the management key to its default state.

* Transaction::set_mgm_key: Take touch requirement as bool

The Option<u8> was inherited from the original C code's usage of an
unsigned char. We don't need that flexibility, because only two cases
are supported.

* Replace MgmKey::set with MgmKey::set_manual

MgmKey::set_default is now implemented as a wrapper around
MgmKey::set_manual, as they both require clearing metadata related to
derived and PIN-protected management keys.
2021-02-01 09:27:04 -08:00
str4d 9d1da84233 Create typed structs for PIN-protected and admin metadata (#223)
MgmKey::set_protected and YubiKey::set_pin_last_changed both contained
bugs resulting from the conversion of C pointer logic (incorrect buffer
management). The new Metadata struct holds its own buffer, avoiding the
problem.

Also adds a protected management key integration test.
2021-01-31 09:54:13 -08:00
45 changed files with 5503 additions and 3287 deletions
+6
View File
@@ -0,0 +1,6 @@
[advisories]
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", ...]
+12 -1
View File
@@ -1,8 +1,19 @@
version: 2
updates:
- 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
+10 -9
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.46.0 # MSRV
toolchain: 1.85.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.46.0 # MSRV
toolchain: 1.85.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.46.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,12 @@ 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.46.0 # MSRV
toolchain: 1.85.0 # MSRV
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo clippy --all --exclude crypto_box --all-features -- -D warnings
- run: cargo clippy --all --all-features -- -D warnings
+4 -4
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') }}
@@ -42,4 +42,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings --ignore RUSTSEC-2019-0031 # spin
args: --deny warnings
+265 -46
View File
@@ -4,7 +4,226 @@ 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).
## 0.2.0 (2021-01-30)
## 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])
- `YubiKey::disconnect` ([#462])
- `Error::AppletNotFound` ([#476])
### Changed
- `Reader::open` now returns `Error::AppletNotFound` instead of `Error::Generic`
if the PIV applet is not present on the device. This is returned by non-PIV
virtual smart cards like Windows Hello for Business, as well as some smart
card readers when no card is present.
- `Reader::open` now avoids resetting the card if an error occurs (equivalent to
calling `YubiKey::disconnect(pcsc::Disposition::LeaveCard)` if `Reader::open`
succeeds).
- Raise minimum `pcsc` version to 2.3.1 and remove workaround ([#478])
- Bump asymmetric crypto dependencies; MSRV 1.65 ([#490])
- `elliptic-curve` v0.13
- `k256` v0.13
- `p256` v0.13
- `p384` v0.13
- `pbkdf2` v0.12
- `rsa` v0.9 ([#502])
- `signature` v2
- Use `x509-cert` certificate builder ([#495])
- Make `RsaKeyData::new` fallible ([#517])
### Fixed
- `StatusWords::code` now returns the correct code (including embedded `tries`
count) for `StatusWords::VerifyFailError`. Previously the returned code lost
information and was not round-trip compatible with `StatusWords::from(u16)`.
- Parsing of serial numbers ([#466])
- Make `YubiKey::open()` more robust ([#504])
[#457]: https://github.com/iqlusioninc/yubikey.rs/pull/457
[#462]: https://github.com/iqlusioninc/yubikey.rs/pull/462
[#466]: https://github.com/iqlusioninc/yubikey.rs/pull/466
[#476]: https://github.com/iqlusioninc/yubikey.rs/pull/476
[#478]: https://github.com/iqlusioninc/yubikey.rs/pull/478
[#490]: https://github.com/iqlusioninc/yubikey.rs/pull/490
[#495]: https://github.com/iqlusioninc/yubikey.rs/pull/495
[#502]: https://github.com/iqlusioninc/yubikey.rs/pull/502
[#504]: https://github.com/iqlusioninc/yubikey.rs/pull/504
[#517]: https://github.com/iqlusioninc/yubikey.rs/pull/517
## 0.7.0 (2022-11-14)
### Added
- Display inner PC/SC errors ([#420])
- Support for metadata command ([#371])
- Better `certificate::Serial` inspection ([#437])
### Changed
- MSRV 1.60.0 ([#423])
- Bump `rsa` to v0.7.1 ([#440])
- Switch from `lazy_static` to `once_cell` ([#442])
- Switch from `subtle-encoding` to `base16ct` ([#443])
### Fixed
- Use `chrono` v0.4.23 or newer ([#436])
- `Certificate::issuer` was returning the subject instead ([#437])
[#371]: https://github.com/iqlusioninc/yubikey.rs/pull/371
[#420]: https://github.com/iqlusioninc/yubikey.rs/pull/420
[#423]: https://github.com/iqlusioninc/yubikey.rs/pull/423
[#436]: https://github.com/iqlusioninc/yubikey.rs/pull/436
[#437]: https://github.com/iqlusioninc/yubikey.rs/pull/437
[#440]: https://github.com/iqlusioninc/yubikey.rs/pull/440
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
## 0.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade ([#343])
- RustCrypto crate upgrades; MSRV 1.57 ([#378])
- `des` v0.8
- `elliptic-curve` v0.12
- `hmac` v0.12
- `num-bigint-dig` v0.8
- `pbkdf2` v0.11
- `p256` v0.11
- `p384` v0.11
- `rsa` v0.6
- `sha1` v0.10 (replacing `sha-1`)
- `sha2` v0.10
- Bump `uuid` to v1.0 ([#376])
- Bump `der-parser` to v8.0 ([#402])
- Bump `x509-parser` to v0.14 ([#402])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#376]: https://github.com/iqlusioninc/yubikey.rs/pull/376
[#378]: https://github.com/iqlusioninc/yubikey.rs/pull/378
[#402]: https://github.com/iqlusioninc/yubikey.rs/pull/402
## 0.5.0 (2021-11-21)
### Changed
- Update `rsa` dependency to 0.5 ([#315])
- Update `pbkdf2` dependency to 0.9 ([#315])
- Update `x509-parser` dependency to 0.12 ([#315], [#322])
- Update `nom` to v7.0 ([#322])
[#315]: https://github.com/iqlusioninc/yubikey.rs/pull/315
[#322]: https://github.com/iqlusioninc/yubikey.rs/pull/322
## 0.4.2 (2021-07-13)
### Added
- Make `yubikey::Buffer` a pub type ([#290])
### Changed
- Have `YubiKey::block_puk` take `&mut self` as argument ([#289])
[#289]: https://github.com/iqlusioninc/yubikey.rs/pull/289
[#290]: https://github.com/iqlusioninc/yubikey.rs/pull/290
## 0.4.1 (2021-07-12)
### Changed
- Rename `SettingValue` to `Setting` ([#286])
- Rename `Ccc` to `CccId` ([#287])
[#286]: https://github.com/iqlusioninc/yubikey.rs/pull/286
[#287]: https://github.com/iqlusioninc/yubikey.rs/pull/287
## 0.4.0 (2021-07-12) [YANKED]
### Added
- `Result` alias ([#271])
### Changed
- Renamed crate from `yubikey-piv` => `yubikey` ([#267])
- Renamed the following:
- `APDU` => `Apdu` ([#269])
- `CCC` => `Ccc` ([#269])
- `CHUID` => `ChuId` ([#269])
- `Ccc::cccid` => `Ccc::card_id` ([#270])
- `key` => `piv` ([#277])
- `readers` => `reader` ([#278])
- `readers::Readers` => `reader::Context` ([#278])
- Bumped the following dependencies:
- `rsa` => v0.4 ([#246])
- `des` => v0.7 ([#251])
- `elliptic-curve` => v0.10 ([#268])
- `hmac` => v0.11 ([#251])
- `pbkdf2` => v0.8 ([#251])
- `p256` => v0.9 ([#268])
- `p384` => v0.8 ([#268])
- MSRV 1.51+ ([#268])
- Flatten API ([#274])
- Replace `getrandom` with `rand_core` ([#276])
### Fixed
- Potential local DoS in TLV parser ([#279])
[#246]: https://github.com/iqlusioninc/yubikey.rs/pull/246
[#251]: https://github.com/iqlusioninc/yubikey.rs/pull/251
[#267]: https://github.com/iqlusioninc/yubikey.rs/pull/267
[#268]: https://github.com/iqlusioninc/yubikey.rs/pull/268
[#269]: https://github.com/iqlusioninc/yubikey.rs/pull/269
[#270]: https://github.com/iqlusioninc/yubikey.rs/pull/270
[#271]: https://github.com/iqlusioninc/yubikey.rs/pull/271
[#274]: https://github.com/iqlusioninc/yubikey.rs/pull/274
[#276]: https://github.com/iqlusioninc/yubikey.rs/pull/276
[#277]: https://github.com/iqlusioninc/yubikey.rs/pull/277
[#278]: https://github.com/iqlusioninc/yubikey.rs/pull/278
[#279]: https://github.com/iqlusioninc/yubikey.rs/pull/279
## yubikey-piv 0.3.0 (2021-03-22)
### Added
- Typed structs for PIN-protected and admin metadata ([#223])
- `MgmKey::set_default`/`MgmKey::set_manual` methods ([#224])
### Changed
- Have `Transaction::set_mgm_key` take touch requirement as bool ([#224])
### Removed
- `MgmKey::set` method ([#224])
[#223]: https://github.com/iqlusioninc/yubikey.rs/pull/223
[#224]: https://github.com/iqlusioninc/yubikey.rs/pull/224
## yubikey-piv 0.2.0 (2021-01-30)
### Changed
- Bump `der-parser` to v5.0 ([#194])
- Improve self-signed certificates ([#207])
@@ -13,12 +232,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump MSRV to 1.46+ ([#208])
- Bump `pbkdf2` dependency to v0.7 ([#219])
[#194]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/194
[#207]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/207
[#208]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/208
[#219]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/219
[#194]: https://github.com/iqlusioninc/yubikey.rs/pull/194
[#207]: https://github.com/iqlusioninc/yubikey.rs/pull/207
[#208]: https://github.com/iqlusioninc/yubikey.rs/pull/208
[#219]: https://github.com/iqlusioninc/yubikey.rs/pull/219
## 0.1.0 (2020-10-19)
## yubikey-piv 0.1.0 (2020-10-19)
### Added
- `Certificate::generate_self_signed` ([#80])
- `YubiKey::open_by_serial` ([#69])
@@ -43,24 +262,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- YubiKey NEO support ([#63])
[#177]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/177
[#175]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/175
[#128]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/128
[#82]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/82
[#73]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/73
[#88]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/88
[#80]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/80
[#69]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/69
[#68]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/68
[#67]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/67
[#65]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/65
[#64]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/64
[#63]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/63
[#62]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/62
[#61]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/61
[#60]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/60
[#177]: https://github.com/iqlusioninc/yubikey.rs/pull/177
[#175]: https://github.com/iqlusioninc/yubikey.rs/pull/175
[#128]: https://github.com/iqlusioninc/yubikey.rs/pull/128
[#82]: https://github.com/iqlusioninc/yubikey.rs/pull/82
[#73]: https://github.com/iqlusioninc/yubikey.rs/pull/73
[#88]: https://github.com/iqlusioninc/yubikey.rs/pull/88
[#80]: https://github.com/iqlusioninc/yubikey.rs/pull/80
[#69]: https://github.com/iqlusioninc/yubikey.rs/pull/69
[#68]: https://github.com/iqlusioninc/yubikey.rs/pull/68
[#67]: https://github.com/iqlusioninc/yubikey.rs/pull/67
[#65]: https://github.com/iqlusioninc/yubikey.rs/pull/65
[#64]: https://github.com/iqlusioninc/yubikey.rs/pull/64
[#63]: https://github.com/iqlusioninc/yubikey.rs/pull/63
[#62]: https://github.com/iqlusioninc/yubikey.rs/pull/62
[#61]: https://github.com/iqlusioninc/yubikey.rs/pull/61
[#60]: https://github.com/iqlusioninc/yubikey.rs/pull/60
## 0.0.3 (2019-12-02)
## yubikey-piv 0.0.3 (2019-12-02)
### Added
- Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45])
@@ -76,19 +295,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#32])
[#51]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/51
[#45]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/45
[#44]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/44
[#43]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/43
[#42]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/42
[#39]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/39
[#37]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/37
[#36]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/36
[#34]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/34
[#33]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/33
[#32]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/32
[#51]: https://github.com/iqlusioninc/yubikey.rs/pull/51
[#45]: https://github.com/iqlusioninc/yubikey.rs/pull/45
[#44]: https://github.com/iqlusioninc/yubikey.rs/pull/44
[#43]: https://github.com/iqlusioninc/yubikey.rs/pull/43
[#42]: https://github.com/iqlusioninc/yubikey.rs/pull/42
[#39]: https://github.com/iqlusioninc/yubikey.rs/pull/39
[#37]: https://github.com/iqlusioninc/yubikey.rs/pull/37
[#36]: https://github.com/iqlusioninc/yubikey.rs/pull/36
[#34]: https://github.com/iqlusioninc/yubikey.rs/pull/34
[#33]: https://github.com/iqlusioninc/yubikey.rs/pull/33
[#32]: https://github.com/iqlusioninc/yubikey.rs/pull/32
## 0.0.2 (2019-11-25)
## yubikey-piv 0.0.2 (2019-11-25)
### Added
- `untested` Cargo feature to mark untested functionality ([#30])
- Initial connect test and docs ([#19])
@@ -103,16 +322,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `log` crate for logging ([#7])
- Replace `ErrorKind::Ok` with `Result` ([#6])
[#30]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/30
[#19]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/19
[#17]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/17
[#15]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/15
[#13]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/13
[#10]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/10
[#9]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/9
[#8]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/8
[#7]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/7
[#6]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/6
[#30]: https://github.com/iqlusioninc/yubikey.rs/pull/30
[#19]: https://github.com/iqlusioninc/yubikey.rs/pull/19
[#17]: https://github.com/iqlusioninc/yubikey.rs/pull/17
[#15]: https://github.com/iqlusioninc/yubikey.rs/pull/15
[#13]: https://github.com/iqlusioninc/yubikey.rs/pull/13
[#10]: https://github.com/iqlusioninc/yubikey.rs/pull/10
[#9]: https://github.com/iqlusioninc/yubikey.rs/pull/9
[#8]: https://github.com/iqlusioninc/yubikey.rs/pull/8
[#7]: https://github.com/iqlusioninc/yubikey.rs/pull/7
[#6]: https://github.com/iqlusioninc/yubikey.rs/pull/6
## 0.0.1 (2019-11-18)
## yubikey-piv 0.0.1 (2019-11-18)
- Initial release
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
Generated
+1097 -753
View File
File diff suppressed because it is too large Load Diff
+45 -37
View File
@@ -1,60 +1,68 @@
[package]
name = "yubikey-piv"
version = "0.2.0" # Also update html_root_url in lib.rs when bumping this
name = "yubikey"
version = "0.9.0-pre.0"
description = """
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
application providing general-purpose public-key signing and encryption
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
algorithms (e.g, PKCS#1v1.5, ECDSA)
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/3072/4096)
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
"""
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021"
rust-version = "1.85"
[workspace]
members = [".", "cli"]
[badges]
maintenance = { status = "experimental" }
[workspace.dependencies]
sha2 = "0.11"
x509-cert = { version = "0.3.0-rc.4", features = ["builder", "hazmat"] }
[dependencies]
chrono = "0.4"
cookie-factory = "0.3"
der-parser = "5"
des = "0.6"
elliptic-curve = "0.8"
getrandom = "0.1"
hmac = "0.10"
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"] }
log = "0.4"
nom = "6"
num-bigint = { version = "0.6", features = ["rand"], package = "num-bigint-dig" }
num-traits = "0.2"
num-integer = "0.1"
pbkdf2 = { version = "0.7", default-features = false }
p256 = "0.7"
p384 = "0.6"
pcsc = "2"
rsa = "0.3"
secrecy = "0.7"
sha-1 = "0.9"
sha2 = "0.9"
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 = "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"
subtle-encoding = "0.5"
x509 = "0.2"
x509-parser = "0.9"
uuid = { version = "1.2", features = ["v4"] }
x25519-dalek = "3.0.0-pre.6"
x509-cert.workspace = true
zeroize = "1"
[dev-dependencies]
env_logger = "0.8"
ring = "0.16.18"
lazy_static = "1"
env_logger = "0.11"
once_cell = "1"
[features]
untested = []
[[example]]
name = "change-mode"
required-features = ["untested"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+115 -88
View File
@@ -1,50 +1,89 @@
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/main/img/logo.png" width="150" height="110">
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png" width="150" height="110">
# yubikey-piv.rs
# yubikey.rs
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
[![2-Clause BSD Licensed][license-image]][license-link]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link]
[![Safety Dance][safety-image]][safety-link]
[![Dependency Status][deps-image]][deps-link]
[![2-Clause BSD Licensed][license-image]][license-link]
![MSRV][msrv-image]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] driver
with general-purpose public-key encryption and signing support.
Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
with support for public-key encryption and digital signatures using the
[Personal Identity Verification (PIV)][PIV] application.
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
access provided by the [`pcsc` crate].
[Documentation][docs-link]
## 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.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
If you've been wanting to use Rust to sign and/or encrypt stuff using a
private key generated and stored on a Yubikey (with option PIN-based access),
If you've been wanting to use Rust to sign and/or encrypt data using a
private key generated and stored on a YubiKey (with option PIN-based access),
this is the crate you've been after!
Note that while this project started as a fork of a [Yubico] project,
this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico.
## Features
### Personal Identity Verification (PIV)
[PIV] is a [NIST] standard for both *signing* and *encryption*
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
PIV-related functionality can be found in the [`piv`] module.
This library natively implements the protocol used to manage and
utilize PIV encryption and signing keys which can be generated, imported,
and stored on YubiKey devices.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**:
- 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)
- `RSA3072` and `RSA4096` require a YubiKey with firmware 5.7 or newer.
## Minimum Supported Rust Version
- Rust **1.46** or newer
Rust **1.60** or newer.
## Supported YubiKeys
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
## Supported Operating Systems
- Linux
- macOS
- Windows
## Security Warning
@@ -55,49 +94,25 @@ USE AT YOUR OWN RISK!
## Status
This project is a largely incomplete work-in-progress. So far the only
functionality which has actually been tested is connecting to Yubikeys.
Functionality which has been successfully tested is available by default.
If you're interested helping test functionality, the table below documents
the current status of the project and relevant GitHub issues for various
functions of the YubiKey:
Any functionality which is gated on the `untested` feature has not been
properly tested and is not known to function correctly.
| | Module | Issue | Description |
|----|---------------|-------|-------------|
| 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest |
| 🚧 | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs |
| 🚧️ | `certificate` | [#22] | Certificates for stored keys |
| 🚧 | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) |
| ✅️ | `config` | [#24] | Support for reading on-key configuration |
| 🚧 | `key` | [#26] | Crypto key management: list, generate, import |
| 🚧 | `mgm` | [#26] | Management Key (MGM) support: set, get, derive |
| ⚠️ | `mscmap` | [#25] | MS Container Map Records |
| ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots |
Legend:
| | Description |
|----|------------------------------------|
| ✅ | Working |
| 🚧 | Testing and validation in progress |
| ⚠️ | Untested support |
NOTE: Commands marked ⚠️ are disabled by default as they have have not been properly tested and may contain bugs or
not work at all. USE AT YOUR OWN RISK!
Enable the `untested` feature in your `Cargo.toml` to enable features marked ⚠️
above.
Please see the [`untested` functionality tracking issue] for current status.
We would appreciate any help testing this functionality and removing the
`untested` gating as well as writing more automated tests.
## Testing
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
tests locally, invoke the following command:
```
```shell
cargo test -- --ignored
```
@@ -106,26 +121,26 @@ information about what is happening. If you'd like to print this logging
information while running the tests, set the `RUST_LOG` environment variable
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
```
```shell
RUST_LOG=info cargo test -- --ignored
```
To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
```
```text
running 1 test
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [160, 0, 0, 3, 8] }
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [160, 0, 0, 3, 8] }
[TRACE yubikey::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] }
[TRACE yubikey::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok
```
@@ -139,6 +154,14 @@ Yubico, which was originally written in C. It was mechanically translated
from C into Rust using [Corrode], and then subsequently heavily
refactored into safer, more idiomatic Rust.
For more information on [yubico-piv-tool] and background information on how
the YubiKey implementation of PIV works in general, see the
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
## ⚠️ Security Warning
No security audits of this crate have ever been performed.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -147,12 +170,12 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
**yubikey-piv.rs** is a fork of and originally a mechanical translation from
**yubikey.rs** is a fork of and originally a mechanical translation from
Yubico's [yubico-piv-tool], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
as a derived work.
Copyright (c) 2014-2020 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
@@ -188,44 +211,48 @@ or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-piv.svg
[crate-link]: https://crates.io/crates/yubikey-piv
[docs-image]: https://docs.rs/yubikey-piv/badge.svg
[docs-link]: https://docs.rs/yubikey-piv/
[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-piv.rs/blob/main/COPYING
[rustc-image]: https://img.shields.io/badge/rustc-1.46+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
[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-piv.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
[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
[//]: # (general links)
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[Yubico]: https://www.yubico.com/
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[PIV]: https://piv.idmanagement.gov/
[NIST]: https://www.nist.gov/
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey 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-piv.rs/blob/main/CODE_OF_CONDUCT.md
[cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
[//]: # (github issues)
[#18]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/18
[#20]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/20
[#21]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/21
[#22]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/22
[#23]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/23
[#24]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/24
[#25]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/25
[#26]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/26
[#27]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/27
[#28]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/28
[#18]: https://github.com/iqlusioninc/yubikey.rs/issues/18
[#20]: https://github.com/iqlusioninc/yubikey.rs/issues/20
[#21]: https://github.com/iqlusioninc/yubikey.rs/issues/21
[#22]: https://github.com/iqlusioninc/yubikey.rs/issues/22
[#23]: https://github.com/iqlusioninc/yubikey.rs/issues/23
[#24]: https://github.com/iqlusioninc/yubikey.rs/issues/24
[#25]: https://github.com/iqlusioninc/yubikey.rs/issues/25
[#26]: https://github.com/iqlusioninc/yubikey.rs/issues/26
[#27]: https://github.com/iqlusioninc/yubikey.rs/issues/27
[#28]: https://github.com/iqlusioninc/yubikey.rs/issues/28
+57 -10
View File
@@ -4,30 +4,77 @@ 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])
- Bump `x509-parser` to v0.14 ([#441])
- Switch from `lazy_static` to `once_cell` ([#442])
- Switch from `subtle-encoding` to `base16ct` ([#443])
- Bump `yubikey` dependency to v0.7 ([#444])
[#438]: https://github.com/iqlusioninc/yubikey.rs/pull/438
[#441]: https://github.com/iqlusioninc/yubikey.rs/pull/441
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
[#444]: https://github.com/iqlusioninc/yubikey.rs/pull/444
## 0.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade; MSRV 1.57 ([#343])
- Migrate from `gumdrop` to `clap` v3 ([#379])
- Bump `yubikey` dependency to v0.6 ([#403])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#379]: https://github.com/iqlusioninc/yubikey.rs/pull/379
[#403]: https://github.com/iqlusioninc/yubikey.rs/pull/403
## 0.5.0 (2021-11-21)
### Changed
- Bump `yubikey` dependency to v0.5 ([#327])
[#327]: https://github.com/iqlusioninc/yubikey.rs/pull/327
## 0.4.0 (2021-07-12)
### Changed
- Switch to renamed `yubikey` crate ([#283])
- Bump MSRV to 1.51+ ([#283])
[#283]: https://github.com/iqlusioninc/yubikey.rs/pull/283
## 0.3.0 (2021-03-22)
### Changed
- Bump `yubikey-piv` dependency to v0.3 ([#240])
[#240]: https://github.com/iqlusioninc/yubikey.rs/pull/240
## 0.2.0 (2021-01-30)
### Changed
- Bump MSRV to 1.46+ ([#208])
- Bump `yubikey-piv` dependency to v0.2.0 ([#220])
- Bump `yubikey-piv` dependency to v0.2 ([#220])
[#208]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/208
[#220]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/220
[#208]: https://github.com/iqlusioninc/yubikey.rs/pull/208
[#220]: https://github.com/iqlusioninc/yubikey.rs/pull/220
## 0.1.0 (2020-10-19)
### Added
- `status` command ([#72], [#74])
### Changed
- Bump `yubikey-piv` to v0.1.0 ([#180])
- Bump `yubikey-piv` to v0.1 ([#180])
- Bump `x509-parser` to v0.8 ([#181])
- Bump `sha2` to v0.9 ([#182])
- Rename `list` command to `readers`; improve usage ([#71])
[#182]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/182
[#181]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/181
[#180]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/180
[#74]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/74
[#72]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/72
[#71]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/71
[#182]: https://github.com/iqlusioninc/yubikey.rs/pull/182
[#181]: https://github.com/iqlusioninc/yubikey.rs/pull/181
[#180]: https://github.com/iqlusioninc/yubikey.rs/pull/180
[#74]: https://github.com/iqlusioninc/yubikey.rs/pull/74
[#72]: https://github.com/iqlusioninc/yubikey.rs/pull/72
[#71]: https://github.com/iqlusioninc/yubikey.rs/pull/71
## 0.0.1 (2019-12-02)
- Initial release
+11 -10
View File
@@ -1,25 +1,26 @@
[package]
name = "yubikey-cli"
version = "0.2.0"
version = "0.9.0-pre"
description = """
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
"""
authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
edition = "2021"
rust-version = "1.85"
[dependencies]
gumdrop = "0.8"
env_logger = "0.8"
lazy_static = "1"
clap = { version = "4", features = ["derive"] }
env_logger = "0.11"
hex = { package = "base16ct", version = "0.2", features = ["alloc"] }
log = "0.4"
sha2 = "0.9"
subtle-encoding = "0.5"
once_cell = "1"
sha2.workspace = true
termcolor = "1"
x509-parser = "0.9"
yubikey-piv = { version = "0.2", path = ".." }
x509-cert.workspace = true
yubikey = { version = "=0.9.0-pre.0", path = ".." }
+7 -13
View File
@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/main/img/logo.png" width="150" height="110">
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png" width="150" height="110">
# yubikey-cli.rs
@@ -18,15 +18,14 @@ utility with general-purpose public-key encryption and signing support.
## Minimum Supported Rust Version
- Rust **1.39+**
Rust **1.60** or newer.
## Supported YubiKeys
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
NOTE: Nano and USB-C variants of the above are also supported. NEO series is NOT supported.
## Security Warning
@@ -35,10 +34,6 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK!
## Status
WIP. Check back later.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -47,7 +42,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
Copyright (c) 2014-2023 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -88,12 +83,12 @@ 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.39+-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/
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
@@ -102,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/
+2 -2
View File
@@ -8,9 +8,9 @@
unused_qualifications
)]
use gumdrop::Options;
use clap::Parser;
use yubikey_cli::commands::YubiKeyCli;
fn main() {
YubiKeyCli::parse_args_default_or_exit().run();
YubiKeyCli::parse().run()
}
+16 -81
View File
@@ -4,62 +4,25 @@ pub mod readers;
pub mod status;
use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal::{self, STDOUT};
use gumdrop::Options;
use std::{
env,
io::{self, Write},
process::exit,
};
use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey_piv::{Serial, YubiKey};
use crate::terminal;
use clap::Parser;
use std::{env, process::exit};
use termcolor::ColorChoice;
use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility
#[derive(Debug, Options)]
#[derive(Debug, Parser)]
pub struct YubiKeyCli {
/// Obtain help about the current command
#[options(short = "h", help = "print help message")]
pub help: bool,
/// Specify the serial number of the YubiKey to connect to
#[options(
short = "s",
long = "serial",
help = "serial number of the YubiKey to connect to"
)]
/// Serial number of the YubiKey to connect to
#[clap(short = 's', long = "serial")]
pub serial: Option<Serial>,
/// Subcommand to execute.
#[options(command)]
pub command: Option<Commands>,
#[clap(subcommand)]
pub command: Commands,
}
impl YubiKeyCli {
/// Print usage information
pub fn print_usage() -> Result<(), io::Error> {
let mut stdout = STDOUT.lock();
stdout.reset()?;
let mut bold = ColorSpec::new();
bold.set_bold(true);
stdout.set_color(&bold)?;
writeln!(
stdout,
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)?;
stdout.reset()?;
writeln!(stdout, "{}", env!("CARGO_PKG_AUTHORS"))?;
writeln!(stdout, "{}", env!("CARGO_PKG_DESCRIPTION").trim())?;
writeln!(stdout)?;
writeln!(stdout, "{}", Commands::usage())?;
Ok(())
}
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
@@ -70,10 +33,7 @@ impl YubiKeyCli {
env_logger::builder().format_timestamp(None).init();
}
match &self.command {
Some(cmd) => cmd.run(self.yubikey_init()),
None => Self::print_usage().unwrap(),
}
self.command.run(self.yubikey_init())
}
/// Initialize the YubiKey client driver
@@ -92,22 +52,18 @@ impl YubiKeyCli {
}
/// Subcommands of this application
#[derive(Debug, Options)]
#[derive(Debug, Parser)]
pub enum Commands {
/// `help` subcommand
#[options(help = "show help for a command")]
Help(HelpOpts),
/// `version` subcommand
#[options(help = "display version information")]
#[clap(about = "display version information")]
Version(VersionOpts),
/// `readers` subcommand
#[options(help = "list detected readers")]
#[clap(about = "list detected readers")]
Readers(ReadersCmd),
/// `status` subcommand
#[options(help = "show yubikey status")]
#[clap(about = "show yubikey status")]
Status(StatusCmd),
}
@@ -115,7 +71,6 @@ impl Commands {
/// Run the given command
pub fn run(&self, yubikey: YubiKey) {
match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(),
Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
@@ -123,28 +78,8 @@ impl Commands {
}
}
/// Help options
#[derive(Debug, Options)]
pub struct HelpOpts {
#[options(free, help = "subcommand to get help for")]
free: Vec<String>,
}
impl HelpOpts {
fn run(&self) {
if let Some(command) = self.free.first() {
if let Some(usage) = Commands::command_usage(command) {
println!("{}", usage);
exit(1);
}
}
println!("{}", Commands::usage());
}
}
/// Version options
#[derive(Debug, Options)]
#[derive(Debug, Parser)]
pub struct VersionOpts {}
impl VersionOpts {
+5 -5
View File
@@ -1,22 +1,22 @@
//! List detected readers
use crate::terminal::STDOUT;
use gumdrop::Options;
use clap::Parser;
use std::{
io::{self, Write},
process::exit,
};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::{Readers, Serial};
use yubikey::{Context, Serial};
/// The `readers` subcommand
#[derive(Debug, Options)]
#[derive(Debug, Parser)]
pub struct ReadersCmd {}
impl ReadersCmd {
/// Run the `readers` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
let mut readers = Context::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
@@ -53,7 +53,7 @@ impl ReadersCmd {
index: usize,
name: &str,
serial: Serial,
) -> Result<(), io::Error> {
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>3}:", index)?;
stream.reset()?;
+5 -7
View File
@@ -1,18 +1,16 @@
//! Print device status
use crate::terminal::STDOUT;
use gumdrop::Options;
use crate::terminal::{print_cert_info, STDOUT};
use clap::Parser;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::{key::*, YubiKey};
use crate::print_cert_info;
use yubikey::{piv::*, YubiKey};
// String to use for `None`
const NONE_STR: &str = "<none>";
/// The `status` subcommand
#[derive(Debug, Options)]
#[derive(Debug, Parser)]
pub struct StatusCmd {}
impl StatusCmd {
@@ -51,7 +49,7 @@ impl StatusCmd {
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> Result<(), io::Error> {
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
+7 -86
View File
@@ -1,92 +1,13 @@
//! `yubikey` command-line utility
//! `yubikey` command-line utility.
//!
//! The goal of this tool is to provide functionality similar to `yubico-piv-tool`
//! but implemented in pure Rust.
//!
//! It also serves as a demonstration/example of how to use the `yubikey` crate.
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
#[macro_use]
pub mod terminal;
pub mod commands;
use log::debug;
use sha2::{Digest, Sha256};
use std::io::{self, Write};
use std::str;
use subtle_encoding::hex;
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey_piv::{certificate::Certificate, key::*, YubiKey};
///Write information about certificate found in slot a la yubico-piv-tool output.
pub fn print_cert_info(
yubikey: &mut YubiKey,
slot: SlotId,
stream: &mut StandardStreamLock<'_>,
) -> Result<(), io::Error> {
let cert = match Certificate::read(yubikey, slot) {
Ok(c) => c,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
return Ok(());
}
};
let buf = cert.into_buffer();
if !buf.is_empty() {
let fingerprint = Sha256::digest(&buf);
let slot_id: u8 = slot.into();
print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
match parse_x509_certificate(&buf) {
Ok((_rem, cert)) => {
print_cert_attr(
stream,
"Algorithm",
cert.tbs_certificate.subject_pki.algorithm.algorithm,
)?;
print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?;
print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?;
print_cert_attr(
stream,
"Fingerprint",
str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(),
)?;
print_cert_attr(
stream,
"Not Before",
cert.tbs_certificate.validity.not_before.to_rfc2822(),
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate.validity.not_after.to_rfc2822(),
)?;
}
_ => {
println!("Failed to parse certificate");
return Ok(());
}
};
}
Ok(())
}
/// Print a status attribute
fn print_cert_attr(
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> Result<(), io::Error> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
Ok(())
}
+79 -14
View File
@@ -1,9 +1,16 @@
//! Status messages
use lazy_static::lazy_static;
use std::io::{self, Write};
use std::sync::Mutex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use log::debug;
use once_cell::sync::Lazy;
use sha2::{Digest, Sha256};
use std::{
io::{self, Write},
str,
sync::Mutex,
};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_cert::der::Encode;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Print a success status message (in green if colors are enabled)
#[macro_export]
@@ -51,16 +58,14 @@ macro_rules! status_err {
};
}
lazy_static! {
/// Color configuration
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
/// Standard output
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice());
pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
/// Standard error
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice());
}
pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
/// Obtain the color configuration.
///
@@ -132,18 +137,16 @@ impl Status {
/// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg)
.expect("error printing to stdout!")
self.print(&STDOUT, msg).expect("error printing to stdout!")
}
/// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg)
.expect("error printing to stderr!")
self.print(&STDERR, msg).expect("error printing to stderr!")
}
/// Print the given message
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> Result<(), io::Error> {
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> io::Result<()> {
let mut s = stream.lock();
s.reset()?;
s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
@@ -163,3 +166,65 @@ impl Status {
Ok(())
}
}
/// Write information about certificate found in slot a la yubico-piv-tool output.
pub fn print_cert_info(
yubikey: &mut YubiKey,
slot: SlotId,
stream: &mut StandardStreamLock<'_>,
) -> io::Result<()> {
let cert = match Certificate::read(yubikey, slot) {
Ok(c) => c,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
return Ok(());
}
};
let cert = &cert.cert;
let fingerprint = Sha256::digest(cert.to_der().unwrap());
let slot_id: u8 = slot.into();
print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
print_cert_attr(
stream,
"Algorithm",
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,
"Fingerprint",
hex::upper::encode_string(&fingerprint),
)?;
print_cert_attr(
stream,
"Not Before",
cert.tbs_certificate().validity().not_before,
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate().validity().not_after,
)?;
Ok(())
}
/// Print a status attribute
fn print_cert_attr(
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
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();
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+91 -8
View File
@@ -30,7 +30,7 @@
// (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 crate::{error::Error, transaction::Transaction, Buffer};
use crate::{transaction::Transaction, Buffer, Result};
use log::trace;
use zeroize::{Zeroize, Zeroizing};
@@ -41,7 +41,7 @@ const APDU_DATA_MAX: usize = 0xFF;
///
/// These messages are packets used to communicate with the YubiKey.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct APDU {
pub(crate) struct Apdu {
/// Instruction class: indicates the type of command (e.g. inter-industry or proprietary)
cla: u8,
@@ -58,7 +58,7 @@ pub(crate) struct APDU {
data: Vec<u8>,
}
impl APDU {
impl Apdu {
/// Create a new APDU with the given instruction code
pub fn new(ins: impl Into<Ins>) -> Self {
Self {
@@ -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;
@@ -109,7 +115,7 @@ impl APDU {
}
/// Transmit this APDU using the given card transaction
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response> {
trace!(">>> {:?}", self);
let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
@@ -129,13 +135,13 @@ impl APDU {
}
}
impl Drop for APDU {
impl Drop for Apdu {
fn drop(&mut self) {
self.zeroize();
}
}
impl Zeroize for APDU {
impl Zeroize for Apdu {
fn zeroize(&mut self) {
// Only `data` may contain secrets
self.data.zeroize();
@@ -195,6 +201,18 @@ pub enum Ins {
/// Get device serial
GetSerial,
/// Get slot metadata
GetMetadata,
/// Management // Read Config
ReadConfig,
/// Management // Write Config
WriteConfig,
/// Management // DeviceReset
DeviceReset,
/// Other/unrecognized instruction codes
Other(u8),
}
@@ -219,6 +237,13 @@ impl Ins {
Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
// Management
Ins::ReadConfig => 0x1d,
Ins::WriteConfig => 0x1c,
Ins::DeviceReset => 0x1f,
Ins::Other(code) => code,
}
}
@@ -227,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,
@@ -243,6 +273,7 @@ impl From<u8> for Ins {
0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code),
}
}
@@ -313,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;
@@ -348,6 +379,12 @@ pub(crate) enum StatusWords {
/// Successful execution
Success,
/// The requested data was too large for the response, and there is data remaining.
BytesRemaining {
/// The number of bytes remaining, as indicated in the response.
len: u8,
},
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
NoInputDataError,
@@ -384,6 +421,9 @@ pub(crate) enum StatusWords {
/// Not enough memory
NoSpaceError,
/// Referenced data or reference data not found
ReferenceDataNotFoundError,
//
// Custom Yubico Status Word extensions
//
@@ -405,8 +445,9 @@ impl StatusWords {
pub fn code(self) -> u16 {
match self {
StatusWords::None => 0,
StatusWords::BytesRemaining { len } => 0x6100 | len as u16,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::VerifyFailError { tries } => 0x63c0 | tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
@@ -416,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,
@@ -434,6 +476,9 @@ impl From<u16> for StatusWords {
fn from(sw: u16) -> Self {
match sw {
0x0000 => StatusWords::None,
sw if sw & 0xff00 == 0x6100 => Self::BytesRemaining {
len: (sw & 0x00ff) as u8,
},
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
@@ -447,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,
@@ -461,3 +507,40 @@ impl From<StatusWords> for u16 {
sw.code()
}
}
#[cfg(test)]
mod tests {
use super::StatusWords;
#[test]
fn status_words_round_trip() {
let round_trip = |sw: StatusWords| {
assert_eq!(StatusWords::from(sw.code()), sw);
};
round_trip(StatusWords::None);
round_trip(StatusWords::BytesRemaining { len: 1 });
round_trip(StatusWords::BytesRemaining { len: 10 });
round_trip(StatusWords::BytesRemaining { len: 0xFF });
round_trip(StatusWords::Success);
round_trip(StatusWords::NoInputDataError);
round_trip(StatusWords::VerifyFailError { tries: 0x0F });
round_trip(StatusWords::VerifyFailError { tries: 3 });
round_trip(StatusWords::VerifyFailError { tries: 2 });
round_trip(StatusWords::VerifyFailError { tries: 1 });
round_trip(StatusWords::VerifyFailError { tries: 0 });
round_trip(StatusWords::WrongLengthError);
round_trip(StatusWords::SecurityStatusError);
round_trip(StatusWords::AuthBlockedError);
round_trip(StatusWords::DataInvalidError);
round_trip(StatusWords::ConditionsNotSatisfiedError);
round_trip(StatusWords::CommandNotAllowedError);
round_trip(StatusWords::IncorrectParamError);
round_trip(StatusWords::NotFoundError);
round_trip(StatusWords::NoSpaceError);
round_trip(StatusWords::IncorrectSlotError);
round_trip(StatusWords::NotSupportedError);
round_trip(StatusWords::CommandAbortedError);
round_trip(StatusWords::Other(0x1337));
}
}
+36 -43
View File
@@ -30,16 +30,10 @@
// (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 crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Result, YubiKey};
use cipher::common::Generate;
use rand_core::CryptoRng;
use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CCCID size
pub const CCCID_SIZE: usize = 14;
/// CCC size
pub const CCC_SIZE: usize = 51;
/// CCCID offset
const CCC_ID_OFFS: usize = 9;
@@ -55,6 +49,7 @@ const OBJ_CAPABILITY: u32 = 0x005f_c107;
/// - 0xff == Manufacturer ID (dummy)
/// - 0x02 == Card type (javaCard)
/// - next 14 bytes: card ID
#[allow(dead_code)]
const CCC_TMPL: &[u8] = &[
0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4,
@@ -62,48 +57,50 @@ const CCC_TMPL: &[u8] = &[
0x00, 0xfe, 0x00,
];
/// Cardholder Capability Container (CCC) Identifier Card ID
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CardId(pub [u8; CCCID_SIZE]);
/// Cardholder Capability Container (CCC) Identifier Card ID.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct CardId(pub [u8; Self::BYTE_SIZE]);
impl CardId {
/// CCCID size in bytes
pub const BYTE_SIZE: usize = 14;
/// Generate a random CCC Card ID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(Self(id))
pub fn generate() -> Self {
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))
}
}
/// Cardholder Capability Container (CCC) Identifier
#[derive(Copy, Clone)]
pub struct CCC(pub [u8; CCC_SIZE]);
/// Cardholder Capability Container (CCC) Identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CccId(pub [u8; Self::BYTE_SIZE]);
impl CccId {
/// CCC size in bytes
pub const BYTE_SIZE: usize = 51;
impl CCC {
/// Return CardId component of CCC
pub fn cccid(&self) -> Result<CardId, Error> {
let mut cccid = [0u8; CCCID_SIZE];
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CCCID_SIZE)]);
pub fn card_id(&self) -> Result<CardId> {
let mut cccid = [0u8; CardId::BYTE_SIZE];
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CardId::BYTE_SIZE)]);
Ok(CardId(cccid))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
pub fn get(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_CAPABILITY)?;
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
Ok(response[..Self::BYTE_SIZE].try_into().map(Self)?)
}
let mut ccc = [0u8; CCC_SIZE];
ccc.copy_from_slice(&response[0..CCC_SIZE]);
Ok(Self(ccc))
}
/// Get Cardholder Capability Container (CCC) ID
/// Set Cardholder Capability Container (CCC) ID
#[cfg(feature = "untested")]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CCC_TMPL.to_vec();
buf[0..self.0.len()].copy_from_slice(&self.0);
@@ -112,18 +109,14 @@ impl CCC {
}
}
impl Debug for CCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CCC({:?})", &self.0[..])
impl AsRef<[u8]> for CccId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for CCC {
impl Display for CccId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
f.write_str(&hex::upper::encode_string(self.as_ref()))
}
}
+326 -498
View File
@@ -1,4 +1,4 @@
//! YubiKey Certificates
//! X.509 certificate support.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -31,76 +31,31 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
error::Error,
key::{sign_data, AlgorithmId, SlotId},
consts::CB_OBJ_MAX,
error::{Error, Result},
piv::SlotId,
serialization::*,
transaction::Transaction,
yubikey::YubiKey,
Buffer,
};
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error;
use num_bigint::BigUint;
use p256::NistP256;
use p384::NistP384;
use rsa::{PublicKeyParts, RSAPublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
use std::fmt;
use std::ops::DerefMut;
use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use x509_cert::{
builder::{profile::BuilderProfile, Builder, CertificateBuilder},
der::{referenced::OwnedToRef, Decode, Encode},
name::Name,
serial_number::SerialNumber,
spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef},
time::Validity,
};
use zeroize::Zeroizing;
use crate::CB_OBJ_MAX;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE;
/// A serial number for a [`Certificate`].
#[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
}
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
impl TryFrom<&[u8]> for Serial {
type Error = ();
fn try_from(bytes: &[u8]) -> Result<Serial, ()> {
if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(&bytes)))
} else {
Err(())
}
}
}
impl Serial {
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be()
}
}
/// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CertInfo {
/// The certificate is uncompressed.
Uncompressed,
@@ -112,7 +67,7 @@ pub enum CertInfo {
impl TryFrom<u8> for CertInfo {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
fn try_from(value: u8) -> Result<Self> {
match value {
0x00 => Ok(CertInfo::Uncompressed),
0x01 => Ok(CertInfo::Gzip),
@@ -130,209 +85,11 @@ impl From<CertInfo> for u8 {
}
}
impl x509::AlgorithmIdentifier for AlgorithmId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// RSA encryption
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => &[1, 2, 840, 113_549, 1, 1, 1],
// EC Public Key
AlgorithmId::EccP256 | AlgorithmId::EccP384 => &[1, 2, 840, 10045, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
use x509::der::write::der_oid;
// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
// ```text
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
// -- implicitCurve NULL
// -- specifiedCurve SpecifiedECDomain
// }
// ```
match self {
AlgorithmId::EccP256 => der_oid(&[1, 2, 840, 10045, 3, 1, 7][..])(w),
AlgorithmId::EccP384 => der_oid(&[1, 3, 132, 0, 34][..])(w),
_ => Ok(w),
}
}
}
/// Information about a public key within a [`Certificate`].
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Public key
pubkey: RSAPublicKey,
},
/// EC P-256 keys
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKeyInfo({:?})", self.algorithm())
}
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self, Error> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() {
1024 => AlgorithmId::Rsa1024,
2048 => AlgorithmId::Rsa2048,
_ => return Err(Error::AlgorithmError),
},
pubkey,
})
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
let algorithm_parameters = subject_pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::InvalidObject)?;
match read_pki::ec_parameters(algorithm_parameters)? {
AlgorithmId::EccP256 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256)
.map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384)
.map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
_ => Err(Error::InvalidObject),
}
}
/// Returns the algorithm that this public key can be used with.
pub fn algorithm(&self) -> AlgorithmId {
match self {
PublicKeyInfo::Rsa { algorithm, .. } => *algorithm,
PublicKeyInfo::EcP256(_) => AlgorithmId::EccP256,
PublicKeyInfo::EcP384(_) => AlgorithmId::EccP384,
}
}
}
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates
#[derive(Clone, Debug)]
pub struct Certificate {
serial: Serial,
issuer: String,
subject: String,
subject_pki: PublicKeyInfo,
data: Buffer,
}
impl<'a> TryFrom<&'a [u8]> for Certificate {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
Self::from_bytes(bytes.to_vec())
}
/// Inner certificate
pub cert: x509_cert::Certificate,
}
impl Certificate {
@@ -341,119 +98,37 @@ impl Certificate {
///
/// `extensions` is optional; if empty, no extensions will be included. Due to the
/// need for an `O: Oid` type parameter, users who do not have any extensions should
/// use the workaround `let extensions: &[x509::Extension<'_, &[u64]>] = &[];`.
pub fn generate_self_signed<O: Oid>(
/// use the workaround `let extensions: &[x509_cert::Extension<'_, &[u64]>] = &[];`.
pub fn generate_self_signed<F, KT: yubikey_signer::KeyType>(
yubikey: &mut YubiKey,
key: SlotId,
serial: impl Into<Serial>,
not_after: Option<DateTime<Utc>>,
subject: &[RelativeDistinguishedName<'_>],
subject_pki: PublicKeyInfo,
extensions: &[x509::Extension<'_, O>],
) -> Result<Self, Error> {
let serial = serial.into();
serial: SerialNumber,
validity: Validity,
subject: Name,
subject_pki: SubjectPublicKeyInfoOwned,
extensions: F,
) -> Result<Self>
where
F: FnOnce(&mut CertificateBuilder<SelfSigned>) -> der::Result<()>,
{
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)?;
let mut tbs_cert = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
let signature_algorithm = match subject_pki.algorithm() {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => SignatureId::Sha256WithRsaEncryption,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => SignatureId::EcdsaWithSha256,
};
cookie_factory::gen(
x509::write::tbs_certificate(
&serial.to_bytes(),
&signature_algorithm,
// Issuer and subject are the same in self-signed certificates.
&subject,
Utc::now(),
not_after,
&subject,
&subject_pki,
&extensions,
),
tbs_cert.deref_mut(),
)
.expect("can serialize to Vec");
let signature = match signature_algorithm {
SignatureId::Sha256WithRsaEncryption => {
use cookie_factory::{combinator::slice, sequence::tuple};
use x509::{
der::write::{der_octet_string, der_sequence},
write::algorithm_identifier,
};
let em_len = if let AlgorithmId::Rsa1024 = subject_pki.algorithm() {
128
} else {
256
};
let h = Sha256::digest(&tbs_cert);
let t = cookie_factory::gen_simple(
der_sequence((
algorithm_identifier(&DigestId::Sha256),
der_octet_string(&h),
)),
vec![],
)
.expect("can serialize into Vec");
let em = cookie_factory::gen_simple(
tuple((
slice(&[0x00, 0x01]),
slice(&vec![0xff; em_len - t.len() - 3]),
slice(&[0x00]),
slice(t),
)),
vec![],
)
.expect("can serialize to Vec");
sign_data(yubikey, &em, subject_pki.algorithm(), key)
}
SignatureId::EcdsaWithSha256 => sign_data(
yubikey,
&Sha256::digest(&tbs_cert),
subject_pki.algorithm(),
key,
),
}?;
let mut data = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
cookie_factory::gen(
x509::write::certificate(&tbs_cert, &signature_algorithm, &signature),
data.deref_mut(),
)
.expect("can serialize to Vec");
let (issuer, subject) = parse_x509_certificate(&data)
.map(|(_, cert)| {
(
cert.tbs_certificate.issuer.to_string(),
cert.tbs_certificate.subject.to_string(),
)
})
.expect("We just serialized this correctly");
let cert = Certificate {
serial,
issuer,
subject,
subject_pki,
data,
};
// Add custom extensions
extensions(&mut builder)?;
let cert = builder.build(&signer).map_err(|_| Error::KeyError)?;
let cert = Self { cert };
cert.write(yubikey, key, CertInfo::Uncompressed)?;
Ok(cert)
}
/// Read a certificate from the given slot in the YubiKey
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> {
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let buf = read_certificate(&txn, slot)?;
@@ -461,29 +136,25 @@ impl Certificate {
return Err(Error::InvalidObject);
}
Certificate::from_bytes(buf)
Self::from_bytes(buf)
}
/// Write this certificate into the YubiKey in the given slot
pub fn write(
&self,
yubikey: &mut YubiKey,
slot: SlotId,
certinfo: CertInfo,
) -> Result<(), Error> {
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: CertInfo) -> Result<()> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.data), certinfo)
let data = self.cert.to_der().map_err(|_| Error::InvalidObject)?;
write_certificate(&txn, slot, Some(&data), certinfo)
}
/// Delete a certificate located at the given slot of the given YubiKey
#[cfg(feature = "untested")]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> {
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, CertInfo::Uncompressed)
}
/// Initialize a local certificate struct from the given bytebuffer
pub fn from_bytes(cert: impl Into<Buffer>) -> Result<Self, Error> {
pub fn from_bytes(cert: impl Into<Buffer>) -> Result<Self> {
let cert = cert.into();
if cert.is_empty() {
@@ -491,60 +162,64 @@ impl Certificate {
return Err(Error::SizeError);
}
let parsed_cert = match parse_x509_certificate(&cert) {
Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject),
};
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
serial,
issuer,
subject,
subject_pki,
data: cert,
})
}
/// Returns the serial number of the certificate.
pub fn serial(&self) -> &Serial {
&self.serial
x509_cert::Certificate::from_der(&cert)
.map(|cert| Self { cert })
.map_err(|_| Error::InvalidObject)
}
/// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str {
&self.subject
pub fn issuer(&self) -> String {
self.cert.tbs_certificate().issuer().to_string()
}
/// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str {
&self.subject
pub fn subject(&self) -> String {
self.cert.tbs_certificate().subject().to_string()
}
/// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> &PublicKeyInfo {
&self.subject_pki
}
/// Extract the inner buffer
pub fn into_buffer(self) -> Buffer {
self.data
pub fn subject_pki(&self) -> SubjectPublicKeyInfoRef<'_> {
self.cert
.tbs_certificate()
.subject_public_key_info()
.owned_to_ref()
}
}
impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
self.data.as_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, Error> {
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let object_id = slot.object_id();
let buf = match txn.fetch_object(object_id) {
@@ -572,15 +247,10 @@ pub(crate) fn write_certificate(
slot: SlotId,
data: Option<&[u8]>,
certinfo: CertInfo,
) -> Result<(), Error> {
) -> Result<()> {
let object_id = slot.object_id();
if data.is_none() {
return txn.save_object(object_id, &[]);
}
let data = data.unwrap();
if let Some(data) = data {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
@@ -589,102 +259,260 @@ pub(crate) fn write_certificate(
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
} else {
txn.save_object(object_id, &[])
}
}
mod read_pki {
use der_parser::{
ber::BerObjectContent,
der::{parse_der_integer, DerObject},
error::BerError,
*,
pub mod yubikey_signer {
//! Signer implementation for yubikey
use crate::{
error::{Error, Result},
piv::AlgorithmId,
piv::{sign_data, SlotId},
YubiKey,
};
use nom::{combinator, IResult};
use rsa::{BigUint, RSAPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{error::Error, key::AlgorithmId};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey, Error> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> {
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer)
}
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |object| {
let seq = object.as_sequence().expect("is DER sequence");
assert_eq!(seq.len(), 2);
let n = match seq[0].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
use der::{
asn1::{Any, OctetString},
oid::db::rfc5912,
Encode, Sequence,
};
let e = match seq[1].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
use sha2::{Digest, Sha256, Sha384, Sha512};
use signature::Keypair;
use std::{cell::RefCell, fmt, io::Write, marker::PhantomData};
use x509_cert::spki::{
self, AlgorithmIdentifierOwned, DynSignatureAlgorithmIdentifier, EncodePublicKey,
SignatureBitStringEncoding, SubjectPublicKeyInfoRef,
};
(n, e)
})(i)
type SigResult<T> = core::result::Result<T, signature::Error>;
/// Key to be used to sign certificates
pub trait KeyType {
/// Error returned when working with signature
type Error: Into<signature::Error> + fmt::Debug;
/// The signature type returned by the signer
type Signature: SignatureBitStringEncoding
+ for<'s> TryFrom<&'s [u8], Error = Self::Error>
+ fmt::Debug;
/// The public key used to verify signature
type VerifyingKey: EncodePublicKey
+ DynSignatureAlgorithmIdentifier
+ Clone
+ From<Self::PublicKey>;
/// Public key type used to load the SPKI formatted key
type PublicKey: for<'a> TryFrom<SubjectPublicKeyInfoRef<'a>, Error = spki::Error>;
/// The algorithm used when talking with the yubikey
const ALGORITHM: AlgorithmId;
/// Prepare buffer before submitting it for signature
fn prepare(input: &[u8]) -> SigResult<Vec<u8>>;
/// Read back the signature from the device
fn read_signature(input: &[u8]) -> SigResult<Self::Signature>;
}
let (n, e) = match rsa_pubkey_parts(encoded) {
Ok((_, res)) => res,
_ => return Err(Error::InvalidObject),
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;
type Signature = p256::ecdsa::DerSignature;
type VerifyingKey = p256::ecdsa::VerifyingKey;
type PublicKey = p256::ecdsa::VerifyingKey;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
Ok(Sha256::digest(input).to_vec())
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::from_bytes(input)
}
}
impl KeyType for p384::NistP384 {
const ALGORITHM: AlgorithmId = AlgorithmId::EccP384;
type Error = ecdsa::Error;
type Signature = p384::ecdsa::DerSignature;
type VerifyingKey = p384::ecdsa::VerifyingKey;
type PublicKey = p384::ecdsa::VerifyingKey;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
Ok(Sha384::digest(input).to_vec())
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::from_bytes(input)
}
}
/// Trait used to handle subtypes of RSA keys
pub trait RsaLength {
/// The length of the RSA key in bits
const BIT_LENGTH: usize;
/// The algorithm to use when talking with the Yubikey.
const ALGORITHM: AlgorithmId;
}
/// RSA 1024 bits key
pub struct Rsa1024;
impl RsaLength for Rsa1024 {
const BIT_LENGTH: usize = 1024;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa1024;
}
/// RSA 2048 bits key
pub struct Rsa2048;
impl RsaLength for Rsa2048 {
const BIT_LENGTH: usize = 2048;
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>,
}
impl<N: RsaLength> KeyType for YubiRsa<N> {
type Error = signature::Error;
type Signature = rsa::pkcs1v15::Signature;
type VerifyingKey = rsa::pkcs1v15::VerifyingKey<Sha256>;
type PublicKey = rsa::RsaPublicKey;
const ALGORITHM: AlgorithmId = N::ALGORITHM;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
let hashed = Sha256::digest(input).to_vec();
OctetString::new(hashed)
.map_err(|e| e.into())
.and_then(Self::emsa_pkcs1_1_5)
.map_err(signature::Error::from_source)
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::try_from(input)
}
}
impl<N: RsaLength> YubiRsa<N> {
/// https://www.rfc-editor.org/rfc/rfc8017#section-9.2
fn emsa_pkcs1_1_5(digest: OctetString) -> Result<Vec<u8>> {
/// https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4
#[derive(Debug, Sequence)]
struct DigestInfo {
digest_algorithm: AlgorithmIdentifierOwned,
digest: OctetString,
}
let em_len = N::BIT_LENGTH / 8;
let null = Any::null();
let t = DigestInfo {
digest_algorithm: AlgorithmIdentifierOwned {
oid: rfc5912::ID_SHA_256,
parameters: Some(null),
},
digest,
};
let t = t.to_der()?;
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
}
let ps = vec![0xff; em_len - t.len() - 3];
assert!(ps.len() >= 8, "spec violation");
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
/// ```text
/// ECParameters ::= CHOICE {
/// namedCurve OBJECT IDENTIFIER
/// -- implicitCurve NULL
/// -- specifiedCurve SpecifiedECDomain
/// }
/// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId, Error> {
let curve_oid = parameters.as_oid_val().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
OID_NIST_P384 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
let mut out = Vec::with_capacity(em_len);
out.write(&[0x00, 0x01]).map_err(|_| Error::MemoryError)?;
out.write(&ps).map_err(|_| Error::MemoryError)?;
out.write(&[0x00]).map_err(|_| Error::MemoryError)?;
out.write(&t).map_err(|_| Error::MemoryError)?;
Ok(out)
}
}
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RSAPublicKey};
use std::io::Write;
use x509::der::write::{der_integer, der_sequence};
/// Encodes a usize as an ASN.1 integer using DER.
fn der_integer_biguint<'a, W: Write + 'a>(num: &'a BigUint) -> impl SerializeFn<W> + 'a {
move |w: WriteContext<W>| der_integer(&num.to_bytes_be())(w)
/// The entrypoint to sign data with the yubikey.
pub struct Signer<'y, KT: KeyType> {
yubikey: RefCell<&'y mut YubiKey>,
key: SlotId,
public_key: KT::VerifyingKey,
}
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RSAPublicKey,
) -> impl SerializeFn<W> + 'a {
der_sequence((
der_integer_biguint(pubkey.n()),
der_integer_biguint(pubkey.e()),
))
impl<'y, KT: KeyType> Signer<'y, KT> {
/// Create new Signer
pub fn new(
yubikey: &'y mut YubiKey,
key: SlotId,
subject_pki: SubjectPublicKeyInfoRef<'_>,
) -> Result<Self> {
let public_key = KT::PublicKey::try_from(subject_pki).map_err(|_| Error::ParseError)?;
let public_key = public_key.into();
Ok(Self {
yubikey: RefCell::new(yubikey),
key,
public_key,
})
}
}
impl<KT: KeyType> Keypair for Signer<'_, KT> {
type VerifyingKey = KT::VerifyingKey;
fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey {
self.public_key.clone()
}
}
impl<KT: KeyType> DynSignatureAlgorithmIdentifier for Signer<'_, KT> {
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
self.verifying_key().signature_algorithm_identifier()
}
}
impl<KT: KeyType> signature::Signer<KT::Signature> for Signer<'_, KT> {
fn try_sign(&self, msg: &[u8]) -> SigResult<KT::Signature> {
let data = KT::prepare(msg)?;
let out = sign_data(
&mut self.yubikey.borrow_mut(),
&data,
KT::ALGORITHM,
self.key,
)
.map_err(signature::Error::from_source)?;
let out = KT::read_signature(&out)?;
Ok(out)
}
}
}
+33 -65
View File
@@ -30,22 +30,9 @@
// (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 crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Result, YubiKey};
use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CHUID size
pub const CHUID_SIZE: usize = 59;
/// CARDID size
pub const CARDID_SIZE: usize = 16;
/// FASC-N component size
pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
use uuid::Uuid;
/// FASC-N offset
const CHUID_FASCN_OFFS: usize = 2;
@@ -74,6 +61,7 @@ const OBJ_CHUID: u32 = 0x005f_c102;
/// - 0x35: Exp. Date (hard-coded)
/// - 0x3e: Signature (hard-coded, empty)
/// - 0xfe: Error Detection Code (hard-coded)
#[allow(dead_code)]
const CHUID_TMPL: &[u8] = &[
0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58,
0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00,
@@ -81,87 +69,67 @@ const CHUID_TMPL: &[u8] = &[
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
];
/// Cardholder Unique Identifier (CHUID) Card UUID/GUID value
/// Cardholder Unique Identifier (CHUID).
#[derive(Copy, Clone, Debug)]
pub struct Uuid(pub [u8; CARDID_SIZE]);
pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
impl Uuid {
/// Generate a random Cardholder Unique Identifier (CHUID) UUID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(Self(id))
}
}
impl ChuId {
/// CHUID size in bytes
pub const BYTE_SIZE: usize = 59;
/// Cardholder Unique Identifier (CHUID)
#[derive(Copy, Clone)]
pub struct CHUID(pub [u8; CHUID_SIZE]);
/// FASC-N component size
pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
impl CHUID {
/// Return FASC-N component of CHUID
pub fn fascn(&self) -> Result<[u8; FASCN_SIZE], Error> {
let mut fascn = [0u8; FASCN_SIZE];
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + FASCN_SIZE)]);
Ok(fascn)
pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] {
self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)]
.try_into()
.expect("should be FASCN_SIZE")
}
/// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Result<[u8; CARDID_SIZE], Error> {
let mut uuid = [0u8; CARDID_SIZE];
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + CARDID_SIZE)]);
Ok(uuid)
pub fn uuid(&self) -> Uuid {
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)])
.expect("should be UUID-sized")
}
/// Return expiration date component of CHUID
// TODO(tarcieri): parse expiration?
pub fn expiration(&self) -> Result<[u8; EXPIRATION_SIZE], Error> {
let mut expiration = [0u8; EXPIRATION_SIZE];
expiration.copy_from_slice(
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + EXPIRATION_SIZE)],
);
Ok(expiration)
pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
.try_into()
.expect("should be EXPIRATION_SIZE")
}
/// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<CHUID, Error> {
pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_CHUID)?;
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
let mut chuid = [0u8; CHUID_SIZE];
chuid.copy_from_slice(&response[0..CHUID_SIZE]);
let retval = CHUID { 0: chuid };
Ok(retval)
Ok(response[..Self::BYTE_SIZE].try_into().map(Self)?)
}
/// Set Cardholder Unique Identifier (CHUID)
#[cfg(feature = "untested")]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CHUID_TMPL.to_vec();
buf[0..self.0.len()].copy_from_slice(&self.0);
buf[..Self::BYTE_SIZE].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(OBJ_CHUID, &buf)
}
}
impl Display for CHUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
impl AsRef<[u8]> for ChuId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Debug for CHUID {
impl Display for ChuId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CHUID({:?})", &self.0[..])
f.write_str(&hex::upper::encode_string(self.as_ref()))
}
}
+32 -30
View File
@@ -31,56 +31,60 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
error::Error,
metadata,
consts::{
TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_ADMIN_TIMESTAMP, TAG_PROTECTED_FLAGS_1,
TAG_PROTECTED_MGM,
},
metadata::{AdminData, ProtectedData},
mgm::{MgmType, ADMIN_FLAGS_1_PROTECTED_MGM},
yubikey::{YubiKey, ADMIN_FLAGS_1_PUK_BLOCKED},
TAG_ADMIN, TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_ADMIN_TIMESTAMP, TAG_PROTECTED,
TAG_PROTECTED_FLAGS_1, TAG_PROTECTED_MGM,
Result,
};
use log::error;
use std::{
convert::TryInto,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const CB_ADMIN_TIMESTAMP: usize = 0x04;
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
/// Config
/// YubiKey configuration.
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Protected data available
protected_data_available: bool,
pub protected_data_available: bool,
/// PUK blocked
puk_blocked: bool,
pub puk_blocked: bool,
/// No block on upgrade
puk_noblock_on_upgrade: bool,
pub puk_noblock_on_upgrade: bool,
/// PIN last changed
pin_last_changed: Option<SystemTime>,
pub pin_last_changed: Option<SystemTime>,
/// MGM type
mgm_type: MgmType,
pub mgm_type: MgmType,
}
impl Config {
/// Get YubiKey config
pub fn get(yubikey: &mut YubiKey) -> Result<Config, Error> {
let mut config = Config {
impl Default for Config {
fn default() -> Config {
Config {
protected_data_available: false,
puk_blocked: false,
puk_noblock_on_upgrade: false,
pin_last_changed: None,
mgm_type: MgmType::Manual,
};
}
}
}
impl Config {
/// Get YubiKey config.
pub(crate) fn get(yubikey: &mut YubiKey) -> Result<Config> {
let mut config = Self::default();
let txn = yubikey.begin_transaction()?;
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
if let Ok(admin_data) = AdminData::read(&txn) {
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.is_empty() {
error!("empty response for admin flags metadata item! ignoring");
} else {
@@ -94,7 +98,7 @@ impl Config {
}
}
if metadata::get_item(&data, TAG_ADMIN_SALT).is_ok() {
if admin_data.get_item(TAG_ADMIN_SALT).is_ok() {
if config.mgm_type != MgmType::Manual {
error!("conflicting types of MGM key administration configured");
} else {
@@ -102,12 +106,12 @@ impl Config {
}
}
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_TIMESTAMP) {
if let Ok(item) = admin_data.get_item(TAG_ADMIN_TIMESTAMP) {
if item.len() != CB_ADMIN_TIMESTAMP {
error!("pin timestamp in admin metadata is an invalid size");
} else {
// TODO(tarcieri): double-check endianness is correct
let pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
let pin_last_changed = u32::from_le_bytes([item[0], item[1], item[2], item[3]]);
if pin_last_changed != 0 {
config.pin_last_changed =
@@ -117,10 +121,10 @@ impl Config {
}
}
if let Ok(data) = metadata::read(&txn, TAG_PROTECTED) {
if let Ok(protected_data) = ProtectedData::read(&txn) {
config.protected_data_available = true;
if let Ok(item) = metadata::get_item(&data, TAG_PROTECTED_FLAGS_1) {
if let Ok(item) = protected_data.get_item(TAG_PROTECTED_FLAGS_1) {
if item.is_empty() {
error!("empty response for protected flags metadata item! ignoring");
} else if item[0] & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 {
@@ -128,11 +132,9 @@ impl Config {
}
}
if metadata::get_item(&data, TAG_PROTECTED_MGM).is_ok() {
if protected_data.get_item(TAG_PROTECTED_MGM).is_ok() {
if config.mgm_type != MgmType::Protected {
error!(
"conflicting types of mgm key administration configured: protected MGM exists"
);
error!("conflicting MGM key types: protected MGM exists");
}
// Always favor protected MGM
+37
View File
@@ -0,0 +1,37 @@
//! Miscellaneous constant values
#![allow(dead_code)]
/// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072;
/// YubiKey max object size
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_MAX: usize = CB_OBJ_TAG_MIN + 2; // 1 byte tag + 3 bytes len
// Admin tags
pub(crate) const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_ADMIN_SALT: u8 = 0x82;
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;
+120 -80
View File
@@ -32,65 +32,75 @@
use std::fmt::{self, Display};
/// Kinds of errors
/// Result type with [`Error`].
pub type Result<T> = core::result::Result<T, Error>;
/// Kinds of errors.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Algorithm error
AlgorithmError,
/// Applet error
AppletError,
/// We tried to select an applet that could not be found.
AppletNotFound {
/// Human-readable name of the applet.
applet_name: &'static str,
},
/// Argument error
ArgumentError,
/// Authentication error
AuthenticationError,
/// Error while building a certificate
CertificateBuilder,
/// Generic error
GenericError,
/// Invalid object
InvalidObject,
/// Key error
KeyError,
/// Memory error
MemoryError,
/// Not supported
NotSupported,
/// Not found
NotFound,
/// Parse error
ParseError,
/// PCSC error
PcscError {
/// Original PC/SC error
inner: Option<pcsc::Error>,
},
/// PIN locked
PinLocked,
/// Range error
RangeError,
/// Size error
SizeError,
/// Applet error
AppletError,
/// Authentication error
AuthenticationError,
/// Randomness error
RandomnessError,
/// Generic error
GenericError,
/// Key error
KeyError,
/// Parse error
ParseError,
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: u8,
},
/// Invalid object
InvalidObject,
/// Algorithm error
AlgorithmError,
/// PIN locked
PinLocked,
/// Argument error
ArgumentError,
/// Range error
RangeError,
/// Not supported
NotSupported,
/// Not found
NotFound,
}
impl Error {
@@ -98,55 +108,75 @@ impl Error {
///
/// These names map to the legacy names from the Yubico C library, to
/// assist in web searches for relevant information for these errors.
pub fn name(self) -> &'static str {
match self {
Error::MemoryError => "YKPIV_MEMORY_ERROR",
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
Error::SizeError => "YKPIV_SIZE_ERROR",
Error::AppletError => "YKPIV_APPLET_ERROR",
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
Error::RandomnessError => "YKPIV_RANDOMNESS_ERROR",
Error::GenericError => "YKPIV_GENERIC_ERROR",
Error::KeyError => "YKPIV_KEY_ERROR",
Error::ParseError => "YKPIV_PARSE_ERROR",
Error::WrongPin { .. } => "YKPIV_WRONG_PIN",
Error::InvalidObject => "YKPIV_INVALID_OBJECT",
pub fn name(self) -> Option<&'static str> {
Some(match self {
Error::AlgorithmError => "YKPIV_ALGORITHM_ERROR",
Error::PinLocked => "YKPIV_PIN_LOCKED",
Error::AppletError => "YKPIV_APPLET_ERROR",
Error::ArgumentError => "YKPIV_ARGUMENT_ERROR",
Error::RangeError => "YKPIV_RANGE_ERROR",
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
Error::GenericError => "YKPIV_GENERIC_ERROR",
Error::InvalidObject => "YKPIV_INVALID_OBJECT",
Error::KeyError => "YKPIV_KEY_ERROR",
Error::MemoryError => "YKPIV_MEMORY_ERROR",
Error::NotSupported => "YKPIV_NOT_SUPPORTED",
Error::NotFound => "<not found>",
}
Error::ParseError => "YKPIV_PARSE_ERROR",
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
Error::PinLocked => "YKPIV_PIN_LOCKED",
Error::RangeError => "YKPIV_RANGE_ERROR",
Error::SizeError => "YKPIV_SIZE_ERROR",
Error::WrongPin { .. } => "YKPIV_WRONG_PIN",
_ => return None,
})
}
/// Error message
pub fn msg(self) -> &'static str {
pub fn msg(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MemoryError => "memory error",
Error::PcscError { .. } => "PCSC error",
Error::SizeError => "size error",
Error::AppletError => "applet error",
Error::AuthenticationError => "authentication error",
Error::RandomnessError => "randomness error",
Error::GenericError => "generic error",
Error::KeyError => "key error",
Error::ParseError => "parse error",
Error::WrongPin { .. } => "wrong pin",
Error::InvalidObject => "invalid object",
Error::AlgorithmError => "algorithm error",
Error::PinLocked => "PIN locked",
Error::ArgumentError => "argument error",
Error::RangeError => "range error",
Error::NotSupported => "not supported",
Error::NotFound => "not found",
Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => f.write_str("applet error"),
Error::AppletNotFound { 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"),
Error::MemoryError => f.write_str("memory error"),
Error::NotSupported => f.write_str("not supported"),
Error::NotFound => f.write_str("not found"),
Error::ParseError => f.write_str("parse error"),
Error::PcscError {
inner: Some(pcsc_error),
} => f.write_fmt(format_args!("PC/SC error: {pcsc_error}")),
Error::PcscError { .. } => f.write_str("PC/SC error"),
Error::PinLocked => f.write_str("PIN locked"),
Error::RangeError => f.write_str("range error"),
Error::SizeError => f.write_str("size error"),
Error::WrongPin { .. } => f.write_str("wrong pin"),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg())
self.msg(f)
}
}
impl From<std::array::TryFromSliceError> for Error {
fn from(_: std::array::TryFromSliceError) -> Error {
Error::SizeError
}
}
impl From<std::time::SystemTimeError> for Error {
fn from(_: std::time::SystemTimeError) -> Error {
Error::GenericError
}
}
@@ -159,11 +189,21 @@ impl From<pcsc::Error> for Error {
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[allow(trivial_casts)] // why doesn't this work without the cast???
Error::PcscError { inner } => inner
.as_ref()
.map(|err| err as &(dyn std::error::Error + 'static)),
#[allow(trivial_casts)]
Error::PcscError { inner } => inner.as_ref().map(|err| err as &_),
_ => None,
}
}
}
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
}
}
+46 -153
View File
@@ -1,95 +1,17 @@
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC])
//! interface as provided by the [`pcsc` crate].
//!
//! **PIV** is a [NIST] standard for both *signing* and *encryption*
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
//!
//! This library natively implements the protocol used to manage and
//! utilize PIV encryption and signing keys which can be generated, imported,
//! and stored on YubiKey devices.
//!
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
//! on which devices support PIV and the available functionality.
//!
//! ## Minimum Supported Rust Version
//!
//! Rust 1.44+
//!
//! ## Supported YubiKeys
//!
//! - [YubiKey 4] series
//! - [YubiKey 5] series
//!
//! NOTE: Nano and USB-C variants of the above are also supported.
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
//!
//! ## Supported Algorithms
//!
//! - **Authentication**: `3DES`
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
//!
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
//!
//! ## Status
//!
//! This is a work-in-progress effort, and while much of the library-level
//! code from upstream [yubico-piv-tool] has been translated into Rust
//! presenting a safe interface, much of it is still untested.
//!
//! Please see the [project's README.md for a complete status][status].
//!
//! ## History
//!
//! This library is a Rust translation of the [yubico-piv-tool] utility by
//! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode], and then subsequently heavily
//! refactored into safer, more idiomatic Rust.
//!
//! For more information on [yubico-piv-tool] and background information on how
//! the YubiKey implementation of PIV works in general, see the
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide].
//!
//! ## Security Warning
//!
//! No security audits of this crate have ever been performed. Presently it is in
//! an experimental stage and may still contain high-severity issues.
//!
//! USE AT YOUR OWN RISK!
//!
//! ## Code of Conduct
//!
//! We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
//!
//! For more information, please see [CODE_OF_CONDUCT.md][cc-md].
//!
//! ## License
//!
//! **yubikey-piv.rs** is a fork of and originally a mechanical translation from
//! Yubico's [yubico-piv-tool], a C library/CLI program. The original library
//! was licensed under a [2-Clause BSD License][BSDL], which this library inherits
//! as a derived work.
//!
//! [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
//! [PIV]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
//! [Yubico]: https://www.yubico.com/
//! [PC/SC]: https://en.wikipedia.org/wiki/PC/SC
//! [`pcsc` crate]: https://github.com/bluetech/pcsc-rust
//! [NIST]: https://www.nist.gov/
//! [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
//! [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
//! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
//! [status]: https://github.com/iqlusioninc/yubikey-piv.rs#status
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
//! [cc-web]: https://contributor-covenant.org/
//! [cc-md]: https://github.com/iqlusioninc/yubikey-piv.rs/blob/main/CODE_OF_CONDUCT.md
//! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![warn(
clippy::mod_module_files,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -121,78 +43,49 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/main/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.1.0"
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_qualifications
)]
mod apdu;
pub mod cccid;
mod cccid;
pub mod certificate;
pub mod chuid;
pub mod config;
pub mod error;
pub mod key;
mod chuid;
mod config;
mod consts;
mod error;
mod metadata;
pub mod mgm;
#[cfg(feature = "untested")]
pub mod mscmap;
mod mscmap;
#[cfg(feature = "untested")]
pub mod msroots;
pub mod policy;
pub mod readers;
mod msroots;
mod otp;
pub mod piv;
mod policy;
pub mod reader;
mod serialization;
pub mod settings;
mod setting;
mod transaction;
pub mod yubikey;
mod yubikey;
pub use self::{
error::Error,
key::Key,
mgm::MgmKey,
readers::Readers,
yubikey::{Serial, YubiKey},
pub use crate::{
cccid::{CardId, CccId},
certificate::Certificate,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmAlgorithmId, MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
setting::{Setting, SettingSource},
yubikey::{CachedPin, Serial, Version, YubiKey},
};
/// Object identifiers
#[cfg(feature = "untested")]
pub use crate::{mscmap::MsContainer, msroots::MsRoots};
pub use uuid::Uuid;
/// Object identifiers: handles to particular objects stored on a YubiKey.
pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
/// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072;
/// YubiKey max object size
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
pub(crate) const TAG_ADMIN: u8 = 0x80;
pub(crate) const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_ADMIN_SALT: u8 = 0x82;
pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub(crate) const TAG_PROTECTED: u8 = 0x88;
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
/// PIV Applet ID
pub(crate) const PIV_AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// MGMT Applet ID.
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
#[cfg(feature = "untested")]
pub(crate) const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
pub(crate) const YK_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
+127 -72
View File
@@ -30,21 +30,86 @@
// (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 crate::{
error::Error, serialization::*, transaction::Transaction, Buffer, TAG_ADMIN, TAG_PROTECTED,
};
#[cfg(feature = "untested")]
use crate::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
#[cfg(feature = "untested")]
use std::{iter, marker::PhantomData};
use zeroize::Zeroizing;
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;
pub const OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const OBJ_PRINTED: u32 = 0x005f_c109;
pub(crate) trait MetadataType: private::Sealed {}
/// A type variable corresponding to PIN-protected metadata.
pub(crate) enum Protected {}
impl MetadataType for Protected {}
/// A type variable corresponding to administrative metadata.
pub(crate) enum Admin {}
impl MetadataType for Admin {}
/// Metadata stored in a YubiKey.
pub(crate) struct Metadata<T: MetadataType> {
inner: Buffer,
_marker: PhantomData<T>,
}
/// PIN-protected metadata stored in a YubiKey.
pub(crate) type ProtectedData = Metadata<Protected>;
/// Administrative metadata stored in a YubiKey.
pub(crate) type AdminData = Metadata<Admin>;
impl<T: MetadataType> Default for Metadata<T> {
fn default() -> Self {
Metadata {
inner: Zeroizing::new(vec![]),
_marker: PhantomData,
}
}
}
impl<T: MetadataType> Metadata<T> {
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>) -> Result<Self> {
let data = txn.fetch_object(T::obj_id())?;
Ok(Metadata {
inner: Tlv::parse_single(data, T::tag())?,
_marker: PhantomData,
})
}
/// Write metadata
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
if self.inner.is_empty() {
return Self::delete(txn);
}
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
let len = Tlv::write(&mut buf, T::tag(), &self.inner)?;
txn.save_object(T::obj_id(), &buf[..len])
}
/// Delete metadata
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[])
}
/// Get metadata item
pub(crate) fn get_item(mut data: &[u8], tag: u8) -> Result<&[u8], Error> {
pub(crate) fn get_item(&self, tag: u8) -> Result<&[u8]> {
let mut data = &self.inner[..];
while !data.is_empty() {
let (remaining, tlv) = Tlv::parse(data)?;
data = remaining;
@@ -59,26 +124,18 @@ pub(crate) fn get_item(mut data: &[u8], tag: u8) -> Result<&[u8], Error> {
}
/// Set metadata item
#[cfg(feature = "untested")]
pub(crate) fn set_item(
data: &mut [u8],
pcb_data: &mut usize,
cb_data_max: usize,
tag: u8,
p_item: &[u8],
) -> Result<(), Error> {
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let cb_item = p_item.len();
let mut offset = 0;
while offset < *pcb_data {
tag_temp = data[offset];
while offset < self.inner.len() {
tag_temp = self.inner[offset];
offset += 1;
cb_len = get_length(&data[offset..], &mut cb_temp);
cb_len = get_length(&self.inner[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
@@ -89,13 +146,18 @@ pub(crate) fn set_item(
}
if tag_temp != tag {
if cb_item == 0 {
if item.is_empty() {
// We've been asked to delete an existing item that isn't in the blob
return Ok(());
}
// We did not find an existing tag, append
*pcb_data += Tlv::write(&mut data[*pcb_data..], tag, p_item)?;
assert_eq!(offset, self.inner.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(());
}
@@ -103,84 +165,53 @@ pub(crate) fn set_item(
// Found tag
// Check length, if it matches, overwrite
if cb_temp == cb_item {
data[offset..offset + cb_item].copy_from_slice(p_item);
if cb_temp == item.len() {
self.inner[offset..offset + item.len()].copy_from_slice(item);
return Ok(());
}
// Length doesn't match, expand/shrink to fit
let next_offset = offset + cb_temp;
// Must be signed to have negative offsets
let cb_moved: isize = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 {
get_length_size(cb_item) as isize
} else {
let cb_moved: isize = (item.len() as isize - cb_temp as isize)
+ if item.is_empty() {
// For tag, if deleting
-1
} else {
get_length_size(item.len()) as isize
}
// Accounts for different length encoding
- cb_len as isize;
// If length would cause buffer overflow, return error
if (*pcb_data as isize + cb_moved) as usize > cb_data_max {
if (self.inner.len() as isize + cb_moved) as usize > CB_OBJ_MAX {
return Err(Error::GenericError);
}
// Move remaining data
data.copy_within(
next_offset..*pcb_data,
let orig_len = self.inner.len();
if cb_moved > 0 {
self.inner.extend(iter::repeat_n(0, cb_moved as usize));
}
self.inner.copy_within(
next_offset..orig_len,
(next_offset as isize + cb_moved) as usize,
);
*pcb_data = (*pcb_data as isize + cb_moved) as usize;
self.inner
.resize((orig_len as isize + cb_moved) as usize, 0);
// Re-encode item and insert
if cb_item != 0 {
if !item.is_empty() {
offset -= cb_len;
offset += set_length(&mut data[offset..], cb_item)?;
data[offset..offset + cb_item].copy_from_slice(p_item);
offset += set_length(&mut self.inner[offset..], item.len())?;
self.inner[offset..offset + item.len()].copy_from_slice(item);
}
Ok(())
}
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
let obj_id = match tag {
TAG_ADMIN => OBJ_ADMIN_DATA,
TAG_PROTECTED => OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
let data = txn.fetch_object(obj_id)?;
Tlv::parse_single(data, tag)
}
/// Write metadata
#[cfg(feature = "untested")]
pub(crate) fn write(txn: &Transaction<'_>, tag: u8, data: &[u8]) -> Result<(), Error> {
if data.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => OBJ_ADMIN_DATA,
TAG_PROTECTED => OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
if data.is_empty() {
// Deleting metadata
return txn.save_object(obj_id, &[]);
}
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
let len = Tlv::write(&mut buf, tag, data)?;
txn.save_object(obj_id, &buf[..len])
}
/// 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
@@ -190,3 +221,27 @@ fn get_length_size(length: usize) -> usize {
3
}
}
mod private {
use super::*;
pub trait Sealed {
fn tag() -> u8;
fn obj_id() -> u32;
}
impl Sealed for Protected {
fn tag() -> u8 {
TAG_PROTECTED
}
fn obj_id() -> u32 {
OBJ_PRINTED
}
}
impl Sealed for Admin {
fn tag() -> u8 {
TAG_ADMIN
}
fn obj_id() -> u32 {
OBJ_ADMIN_DATA
}
}
}
+865 -221
View File
File diff suppressed because it is too large Load Diff
+60 -80
View File
@@ -1,7 +1,4 @@
//! MS Container Map Records
//!
//! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification:
//! <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
//! MS Container Map Records.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -33,54 +30,56 @@
// (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 crate::{error::Error, key::SlotId, serialization::*, yubikey::YubiKey, CB_OBJ_MAX};
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
use log::error;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
/// Container name length
const CONTAINER_NAME_LEN: usize = 40;
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27;
const OBJ_MSCMAP: u32 = 0x005f_ff10;
const TAG_MSCMAP: u8 = 0x81;
/// MS Container Map(?) Records
#[derive(Copy, Clone)]
pub struct Container {
/// Container name
pub name: [u16; CONTAINER_NAME_LEN],
/// MS Container Map records.
///
/// Defined in Microsoft's Smart Card Minidriver Specification:
/// <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
#[derive(Clone, Debug)]
pub struct MsContainer {
/// Container name.
pub name: [u16; Self::NAME_LEN],
/// Card slot
/// Card slot.
pub slot: SlotId,
/// Key spec
/// Key spec.
pub key_spec: u8,
/// Key size in bits
/// Key size in bits.
pub key_size_bits: u16,
/// Flags
/// Flags.
pub flags: u8,
/// PIN ID
/// PIN ID.
pub pin_id: u8,
/// Associated ECHD(?) container (typo of "ecdh" perhaps?)
/// Associated ECHD container.
pub associated_echd_container: u8,
/// Cert fingerprint
pub cert_fingerprint: [u8; 20],
/// Cert fingerprint.
pub cert_fingerprint: [u8; Self::CERT_FINGERPRINT_LEN],
}
impl Container {
/// Read MS Container Map records
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
impl MsContainer {
/// Container name length in UTF-16 chars.
const NAME_LEN: usize = 40;
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
const REC_LEN: usize = (2 * Self::NAME_LEN) + 27;
/// Length of a certificate fingerprint.
const CERT_FINGERPRINT_LEN: usize = 20;
/// Read MS Container Map records.
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![];
@@ -98,17 +97,17 @@ impl Container {
return Err(Error::InvalidObject);
}
for chunk in tlv.value.chunks_exact(CONTAINER_REC_LEN) {
containers.push(Container::new(chunk)?);
for chunk in tlv.value.chunks_exact(Self::REC_LEN) {
containers.push(MsContainer::new(chunk)?);
}
Ok(containers)
}
/// Write MS Container Map records.
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> {
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<()> {
let n_containers = containers.len();
let data_len = n_containers * CONTAINER_REC_LEN;
let data_len = n_containers * Self::REC_LEN;
let txn = yubikey.begin_transaction()?;
@@ -118,7 +117,7 @@ impl Container {
let mut buf = [0u8; CB_OBJ_MAX];
let offset = Tlv::write_as(&mut buf, TAG_MSCMAP, data_len, |buf| {
for (i, chunk) in buf.chunks_exact_mut(CONTAINER_REC_LEN).enumerate() {
for (i, chunk) in buf.chunks_exact_mut(Self::REC_LEN).enumerate() {
chunk.copy_from_slice(&containers[i].to_bytes());
}
})?;
@@ -126,36 +125,35 @@ impl Container {
txn.save_object(OBJ_MSCMAP, &buf[..offset])
}
/// Parse a container record from a byte slice
pub fn new(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != CONTAINER_REC_LEN {
/// Parse a container record from a byte slice.
pub fn new(bytes: &[u8]) -> Result<Self> {
if bytes.len() != Self::REC_LEN {
error!(
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
CONTAINER_REC_LEN,
Self::REC_LEN,
bytes.len()
);
return Err(Error::ParseError);
}
let mut name = [0u16; CONTAINER_NAME_LEN];
let name_bytes_len = CONTAINER_NAME_LEN * 2;
let mut name = [0u16; Self::NAME_LEN];
let name_bytes_len = Self::NAME_LEN * 2;
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
name[i] = u16::from_le_bytes([chunk[0], chunk[1]]);
}
let mut cert_fingerprint = [0u8; 20];
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
Ok(Container {
Ok(Self {
name,
slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
.try_into()
.unwrap(),
),
key_size_bits: u16::from_le_bytes([
bytes[name_bytes_len + 2],
bytes[name_bytes_len + 3],
]),
flags: bytes[name_bytes_len + 4],
pin_id: bytes[name_bytes_len + 5],
associated_echd_container: bytes[name_bytes_len + 6],
@@ -163,16 +161,17 @@ impl Container {
})
}
/// Parse the container name as a UTF-16 string
pub fn parse_name(&self) -> Result<String, Error> {
/// Parse the container name as a UTF-16 string.
pub fn parse_name(&self) -> Result<String> {
String::from_utf16(&self.name).map_err(|_| Error::ParseError)
}
/// Serialize a container record as a byte size
pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] {
let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN);
/// Serialize a container record as a byte size.
pub fn to_bytes(&self) -> [u8; Self::REC_LEN] {
// TODO(tarcieri): use array instead of `Vec`
let mut bytes = Vec::with_capacity(Self::REC_LEN);
for i in 0..CONTAINER_NAME_LEN {
for i in 0..Self::NAME_LEN {
bytes.extend_from_slice(&self.name[i].to_le_bytes());
}
@@ -183,36 +182,17 @@ impl Container {
bytes.push(self.pin_id);
bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint);
// TODO(tarcieri): use TryInto here when const generics are available
let mut result = [0u8; CONTAINER_REC_LEN];
result.copy_from_slice(&bytes);
result
bytes
.as_slice()
.try_into()
.expect("should be REC_LEN-sized")
}
}
impl Debug for Container {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PivContainer {{ name: {:?}, slot: {:?}, key_spec: {}, key_size_bits: {}, \
flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}",
&self.name[..],
self.slot,
self.key_spec,
self.key_size_bits,
self.flags,
self.pin_id,
self.associated_echd_container,
&self.cert_fingerprint[..]
)
}
}
impl<'a> TryFrom<&'a [u8]> for Container {
impl<'a> TryFrom<&'a [u8]> for MsContainer {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::new(bytes)
}
}
+22 -20
View File
@@ -1,11 +1,4 @@
//! `msroots`: PKCS#7 formatted certificate store for enterprise trusted roots.
//!
//! This `msroots` file contains a bag of certificates with empty content and
//! an empty signature, allowing an enterprise root certificate truststore to
//! be written to and read from a YubiKey.
//!
//! For more information, see:
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
//! PKCS#7 formatted certificate store for enterprise trusted roots.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -37,8 +30,11 @@
// (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 crate::{error::Error, serialization::*, yubikey::YubiKey};
use crate::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
serialization::*,
Error, Result, YubiKey,
};
use log::error;
const OBJ_MSROOTS1: u32 = 0x005f_ff11;
@@ -53,17 +49,24 @@ const OBJ_MSROOTS5: u32 = 0x005f_ff15;
const TAG_MSROOTS_END: u8 = 0x82;
const TAG_MSROOTS_MID: u8 = 0x83;
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
/// PKCS#7-formatted certificate store for enterprise trust roots.
///
/// The `msroots` file contains a bag of certificates with empty content and
/// an empty signature, allowing an enterprise root certificate truststore to
/// be written to and read from a YubiKey.
///
/// For more information, see:
/// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
pub struct MsRoots(Vec<u8>);
impl MsRoots {
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self, Error> {
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self> {
Ok(MsRoots(msroots.as_ref().into()))
}
/// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>, Error> {
pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
let txn = yubikey.begin_transaction()?;
// allocate first page
@@ -93,21 +96,20 @@ 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
pub fn write(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
pub fn write(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset: usize;
let mut data_offset: usize = 0;
let mut data_chunk: usize;
let data = &self.0;
let data_len = data.len();
let n_objs: usize;
let txn = yubikey.begin_transaction()?;
if data_len == 0 {
@@ -115,7 +117,7 @@ impl MsRoots {
}
// Calculate number of objects required to store blob
n_objs = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
let n_objs: usize = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 {
return Err(Error::SizeError);
+5
View File
@@ -0,0 +1,5 @@
/// YubiKey OTP Applet Name
pub(crate) const APPLET_NAME: &str = "YubiKey OTP";
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
+649 -185
View File
File diff suppressed because it is too large Load Diff
+39 -9
View File
@@ -1,11 +1,12 @@
//! Enums representing key policies.
use crate::{error::Error, serialization::Tlv};
use crate::{serialization::Tlv, Error, Result};
/// Specifies how often the PIN needs to be entered for access to the credential in a
/// given slot. This policy must be set upon key generation or importation, and cannot be
/// changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
/// given slot.
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PinPolicy {
/// Use the default PIN policy for the slot. See the slot's documentation for details.
Default,
@@ -34,10 +35,24 @@ impl From<PinPolicy> for u8 {
}
}
impl TryFrom<u8> for PinPolicy {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(PinPolicy::Default),
1 => Ok(PinPolicy::Never),
2 => Ok(PinPolicy::Once),
3 => Ok(PinPolicy::Always),
_ => Err(Error::GenericError),
}
}
}
impl PinPolicy {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize> {
match self {
PinPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xaa, &[self.into()]),
@@ -46,9 +61,10 @@ impl PinPolicy {
}
/// Specifies under what conditions a physical touch on the metal contact is required, in
/// addition to the [`PinPolicy`]. This policy must be set upon key generation or
/// importation, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
/// addition to the [`PinPolicy`].
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TouchPolicy {
/// Use the default touch policy for the slot.
Default,
@@ -81,10 +97,24 @@ impl From<TouchPolicy> for u8 {
impl TouchPolicy {
/// Writes the `TouchPolicy` in the format the YubiKey expects during key generation
/// or importation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize> {
match self {
TouchPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xab, &[self.into()]),
}
}
}
impl TryFrom<u8> for TouchPolicy {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(TouchPolicy::Default),
1 => Ok(TouchPolicy::Never),
2 => Ok(TouchPolicy::Always),
3 => Ok(TouchPolicy::Cached),
_ => Err(Error::GenericError),
}
}
}
+27 -18
View File
@@ -1,18 +1,18 @@
//! Support for enumerating available readers
//! Support for enumerating available PC/SC card readers.
use crate::{error::Error, yubikey::YubiKey};
use crate::{Error, Result, YubiKey};
use std::{
borrow::Cow,
convert::TryInto,
ffi::CStr,
fmt,
sync::{Arc, Mutex},
};
/// Iterator over connected readers
pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>;
/// Enumeration support for available readers
pub struct Readers {
/// PC/SC reader context: used to enumerate available PC/SC [`Reader`]s.
pub struct Context {
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
@@ -20,10 +20,16 @@ pub struct Readers {
reader_names: Vec<u8>,
}
impl Readers {
impl fmt::Debug for Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Context").finish_non_exhaustive()
}
}
impl Context {
/// Open a PC/SC context, which can be used to enumerate available PC/SC
/// readers (which can be used to connect to YubiKeys).
pub fn open() -> Result<Self, Error> {
pub fn open() -> Result<Self> {
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
let reader_names = vec![0u8; ctx.list_readers_len()?];
Ok(Self {
@@ -32,12 +38,13 @@ impl Readers {
})
}
/// Iterate over the available readers
pub fn iter(&mut self) -> Result<Iter<'_>, Error> {
/// Iterate over the available readers.
pub fn iter(&mut self) -> Result<Iter<'_>> {
let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = {
let c = ctx.lock().unwrap();
// TODO(tarcieri): better error?
let c = ctx.lock().map_err(|_| Error::GenericError)?;
// ensure PC/SC context is valid
c.is_valid()?;
@@ -45,6 +52,7 @@ impl Readers {
c.list_readers(reader_names)?.collect()
};
#[allow(clippy::needless_collect)]
let readers: Vec<_> = reader_cstrs
.iter()
.map(|name| Reader::new(name, Arc::clone(ctx)))
@@ -54,7 +62,7 @@ impl Readers {
}
}
/// An individual connected reader
/// An individual connected PC/SC card reader.
pub struct Reader<'ctx> {
/// Name of this reader
name: &'ctx CStr,
@@ -64,26 +72,27 @@ pub struct Reader<'ctx> {
}
impl<'ctx> Reader<'ctx> {
/// Create a new reader from its name and context
/// Create a new reader from its name and context.
fn new(name: &'ctx CStr, ctx: Arc<Mutex<pcsc::Context>>) -> Self {
// TODO(tarcieri): open devices, determine they're YubiKeys, get serial?
Self { name, ctx }
}
/// Get this reader's name
/// Get this reader's name.
pub fn name(&self) -> Cow<'_, str> {
// TODO(tarcieri): is lossy ok here? try to avoid lossiness?
self.name.to_string_lossy()
}
/// Open a connection to this reader, returning a `YubiKey` if successful
pub fn open(&self) -> Result<YubiKey, Error> {
/// Open a connection to this reader, returning a `YubiKey` if successful.
pub fn open(&self) -> Result<YubiKey> {
self.try_into()
}
/// Connect to this reader, returning its `pcsc::Card`
pub(crate) fn connect(&self) -> Result<pcsc::Card, Error> {
let ctx = self.ctx.lock().unwrap();
/// Connect to this reader, returning its `pcsc::Card`.
pub(crate) fn connect(&self) -> Result<pcsc::Card> {
// TODO(tarcieri): better error?
let ctx = self.ctx.lock().map_err(|_| Error::GenericError)?;
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
}
}
+12 -9
View File
@@ -30,7 +30,7 @@
// (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 crate::{error::Error, Buffer, ObjectId, CB_OBJ_TAG_MIN};
use crate::{consts::CB_OBJ_TAG_MIN, Buffer, Error, ObjectId, Result};
pub const OBJ_DISCOVERY: u32 = 0x7e;
@@ -44,24 +44,27 @@ pub(crate) struct Tlv<'a> {
impl<'a> Tlv<'a> {
/// Parses a `Tlv` from a buffer, returning the remainder of the buffer.
pub(crate) fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Self), Error> {
pub(crate) fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Self)> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
let tag = buffer[0];
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
let buffer = buffer.get(offset..).ok_or(Error::SizeError)?;
let (value, buffer) = buffer[offset..].split_at(len);
if buffer.len() >= len {
let (value, buffer) = buffer.split_at(len);
Ok((buffer, Tlv { tag, value }))
} else {
Err(Error::SizeError)
}
}
/// Takes a [`Buffer`] containing a single `Tlv` with the given tag, and returns a
/// `Buffer` containing only the value part of the `Tlv`.
pub(crate) fn parse_single(mut buffer: Buffer, tag: u8) -> Result<Buffer, Error> {
pub(crate) fn parse_single(mut buffer: Buffer, tag: u8) -> Result<Buffer> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
@@ -79,7 +82,7 @@ impl<'a> Tlv<'a> {
}
/// Writes a TLV to the given buffer.
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize, Error> {
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize> {
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
@@ -103,7 +106,7 @@ impl<'a> Tlv<'a> {
tag: u8,
length: usize,
value: Gen,
) -> Result<usize, Error>
) -> Result<usize>
where
Gen: FnOnce(&mut [u8]),
{
@@ -124,7 +127,7 @@ impl<'a> Tlv<'a> {
}
/// Set length
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize, Error> {
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize> {
if length < 0x80 {
if buffer.is_empty() {
Err(Error::SizeError)
+127
View File
@@ -0,0 +1,127 @@
//! Configuration setting values parsed from the environment and config file:
//! `/etc/yubico/yubikeypiv.conf`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// Default location of the YubiKey PIV configuration file
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
use std::{
env,
fs::File,
io::{BufRead, BufReader},
};
/// Source of how a setting was configured.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SettingSource {
/// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
User,
/// Admin-specified setting: sourced via the `/etc/yubico/yubikeypiv.conf`
/// configuration file.
Admin,
/// Default setting.
#[default]
Default,
}
/// Setting booleans: configuration values sourced from a file or the environment.
///
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
/// variables.
#[derive(Copy, Clone, Debug, Default)]
pub struct Setting {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user, admin, or default)
pub source: SettingSource,
}
impl Setting {
/// Get a setting by name.
pub fn get(key: &str, default: bool) -> Self {
Self::from_file(key)
.or_else(|| Self::from_env(key))
.unwrap_or(Self {
value: default,
source: SettingSource::Default,
})
}
/// Get a boolean config value from the provided config file
fn from_file(key: &str) -> Option<Self> {
if let Ok(file) = File::open(DEFAULT_CONFIG_FILE) {
for line in BufReader::new(file).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(2, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
return Some(Setting {
source: SettingSource::Admin,
value: value == "1" || value == "true",
});
}
}
}
None
}
/// Get a setting boolean from an environment variable
fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{key}"))
.ok()
.map(|value| Setting {
source: SettingSource::User,
value: value == "1" || value == "true",
})
}
}
-138
View File
@@ -1,138 +0,0 @@
//! Configuration setting values parsed from the environment and config file:
//! `/etc/yubico/yubikeypiv.conf`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// Default location of the YubiKey PIV configuration file
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
use std::{
env,
fs::File,
io::{BufRead, BufReader},
};
/// Source of how a setting was configured
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Source {
/// User-specified setting
User,
/// Admin-specified setting
Admin,
/// Default setting
Default,
}
/// Setting booleans
#[derive(Copy, Clone, Debug)]
pub struct BoolValue {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user/admin/default)
pub source: Source,
}
impl BoolValue {
/// Get a [`BoolValue`] value
pub fn get(key: &str, def: bool) -> Self {
let mut setting = get_setting_from_file(key);
if setting.source == Source::Default {
setting = get_setting_from_env(key);
}
if setting.source == Source::Default {
setting.value = def;
}
setting
}
}
/// Get a boolean config value
fn get_setting_from_file(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
let file = match File::open(DEFAULT_CONFIG_FILE) {
Ok(f) => f,
Err(_) => return setting,
};
for line in BufReader::new(file).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(1, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
setting.source = Source::Admin;
setting.value = value == "1" || value == "true";
break;
}
}
setting
}
/// Get a setting boolean from an environment variable
fn get_setting_from_env(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
if let Ok(value) = env::var(format!("YUBIKEY_PIV_{}", key)) {
setting.source = Source::User;
setting.value = value == "1" || value == "true";
}
setting
}
+209 -119
View File
@@ -2,19 +2,21 @@
use crate::{
apdu::Response,
apdu::{Ins, StatusWords, APDU},
error::Error,
key::{AlgorithmId, SlotId},
apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result},
mgm::MgmKey,
otp,
piv::{self, AlgorithmId, SlotId},
serialization::*,
yubikey::*,
Buffer, ObjectId, CB_BUF_MAX, CB_OBJ_MAX, PIV_AID, YK_AID,
Buffer, ObjectId,
};
use log::{error, trace};
use std::convert::TryInto;
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;
@@ -32,7 +34,7 @@ pub(crate) struct Transaction<'tx> {
impl<'tx> Transaction<'tx> {
/// Create a new transaction with the given card.
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self, Error> {
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self> {
Ok(Transaction {
inner: card.transaction()?,
})
@@ -45,7 +47,7 @@ impl<'tx> Transaction<'tx> {
/// single APDU messages at a time. For larger messages that need to be
/// split into multiple APDUs, use the [`Transaction::transfer_data`]
/// method instead.
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>, Error> {
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>> {
trace!(">>> {:?}", send_buffer);
let mut recv_buffer = vec![0u8; recv_len];
@@ -59,110 +61,128 @@ 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<(), Error> {
let response = APDU::new(Ins::SelectApplication)
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_AID)
.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()
);
return Err(Error::GenericError);
error!("{}: {:04x}", error, response.status_words().code());
return Err(match response.status_words() {
StatusWords::NotFoundError => Error::AppletNotFound { applet_name },
_ => Error::GenericError,
});
}
Ok(())
}
/// Get the version of the PIV application installed on the YubiKey.
pub fn get_version(&self) -> Result<Version, Error> {
pub fn get_version(&self) -> Result<Version> {
// 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);
}
if response.data().len() < 3 {
return Err(Error::SizeError);
}
Ok(Version::new(response.data()[..3].try_into().unwrap()))
Ok(response.data()[..3].try_into().map(Version::new)?)
}
/// Get YubiKey device serial number.
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> {
let response = if version.major < 5 {
// YK4 requires switching to the yk applet to retrieve the serial
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&YK_AID)
.transmit(self, 0xFF)?
.status_words();
pub fn get_serial(&self, version: Version) -> Result<Serial> {
match version.major {
// YK4 requires switching to the YK applet to retrieve the serial
4 => {
self.select_application(
otp::APPLET_ID,
otp::APPLET_NAME,
"failed selecting yk application",
)?;
if !sw.is_success() {
error!("failed selecting yk application: {:04x}", sw.code());
return Err(Error::GenericError);
}
let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
let resp = APDU::new(0x01).p1(0x10).transmit(self, 0xFF)?;
if !resp.is_success() {
if !response.is_success() {
// TODO(tarcieri): still reselect the PIV applet in this case?
error!(
"failed retrieving serial number: {:04x}",
resp.status_words().code()
response.status_words().code()
);
return Err(Error::GenericError);
}
// reselect the PIV applet
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&PIV_AID)
.transmit(self, 0xFF)?
.status_words();
self.select_application(
piv::APPLET_ID,
piv::APPLET_NAME,
"failed selecting application",
)?;
if !sw.is_success() {
error!("failed selecting application: {:04x}", sw.code());
return Err(Error::GenericError);
response.data().try_into()
}
resp
} else {
// YK5 implements getting the serial as a PIV applet command (0xf8)
let resp = APDU::new(Ins::GetSerial).transmit(self, 0xFF)?;
5 => {
let response = Apdu::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() {
if !response.is_success() {
error!(
"failed retrieving serial number: {:04x}",
resp.status_words().code()
response.status_words().code()
);
return Err(Error::GenericError);
}
resp
};
response.data().try_into()
}
response.data()[..4]
.try_into()
.map(|serial| Serial::from(u32::from_be_bytes(serial)))
.map_err(|_| Error::SizeError)
// Other versions unsupported
_ => Err(Error::NotSupported),
}
}
/// 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<(), Error> {
pub fn verify_pin(&self, pin: &[u8]) -> Result<()> {
if pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
let mut query = APDU::new(Ins::Verify);
let mut query = Apdu::new(Ins::Verify);
query.params(0x00, 0x80);
// Empty pin means we are querying the number of retries. We set no data in this
@@ -171,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)?;
@@ -191,7 +211,7 @@ impl<'tx> Transaction<'tx> {
action: ChangeRefAction,
current_pin: &[u8],
new_pin: &[u8],
) -> Result<(), Error> {
) -> Result<()> {
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
@@ -228,25 +248,18 @@ impl<'tx> Transaction<'tx> {
}
/// Set the management key (MGM).
#[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> {
let p2 = match touch.unwrap_or_default() {
0 => 0xff,
1 => 0xfe,
_ => {
return Err(Error::GenericError);
}
};
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)
let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2)
.data(&data)
.data(data)
.transmit(self, 261)?
.status_words();
@@ -269,20 +282,29 @@ impl<'tx> Transaction<'tx> {
algorithm: AlgorithmId,
key: SlotId,
decipher: bool,
) -> Result<Buffer, Error> {
) -> Result<Buffer> {
let in_len = sign_in.len();
let mut indata = [0u8; 1024];
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);
}
}
@@ -298,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 {
@@ -314,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
@@ -326,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());
@@ -364,12 +398,7 @@ impl<'tx> Transaction<'tx> {
/// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via
/// [`Transaction::transmit`].
pub fn transfer_data(
&self,
templ: &[u8],
in_data: &[u8],
max_out: usize,
) -> Result<Response, Error> {
pub fn transfer_data(&self, templ: &[u8], in_data: &[u8], max_out: usize) -> Result<Response> {
let mut in_offset = 0;
let mut out_data = vec![];
let mut sw;
@@ -386,17 +415,18 @@ impl<'tx> Transaction<'tx> {
trace!("going to send {} bytes in this go", this_size);
let response = APDU::new(templ[1])
let response = Apdu::new(templ[1])
.cla(cla)
.params(templ[2], templ[3])
.data(&in_data[in_offset..(in_offset + this_size)])
.transmit(self, 261)?;
sw = response.status_words().code();
sw = response.status_words();
if !response.is_success() && (sw >> 8 != 0x61) {
match sw {
StatusWords::Success | StatusWords::BytesRemaining { .. } => (),
// TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data));
_ => return Ok(Response::new(sw, out_data)),
}
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
@@ -417,17 +447,15 @@ impl<'tx> Transaction<'tx> {
}
}
while sw >> 8 == 0x61 {
trace!(
"The card indicates there is {} bytes more data for us",
sw & 0xff
);
while let StatusWords::BytesRemaining { len } = sw {
trace!("The card indicates there is {} bytes more data for us", len);
let response = APDU::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code();
let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
return Ok(Response::new(sw.into(), vec![]));
match sw {
StatusWords::Success | StatusWords::BytesRemaining { .. } => (),
_ => return Ok(Response::new(sw, vec![])),
}
if out_data.len() + response.data().len() > max_out {
@@ -443,11 +471,11 @@ impl<'tx> Transaction<'tx> {
out_data.extend_from_slice(&response.data()[..response.data().len()]);
}
Ok(Response::new(sw.into(), out_data))
Ok(Response::new(sw, out_data))
}
/// Fetch an object.
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer> {
let mut indata = [0u8; 5];
let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
@@ -481,7 +509,7 @@ impl<'tx> Transaction<'tx> {
}
/// Save an object.
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<()> {
let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
// TODO(tarcieri): replace with vector
@@ -508,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)
}
}
+324 -176
View File
@@ -31,38 +31,44 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
apdu::{Ins, APDU},
cccid::CCC,
chuid::CHUID,
apdu::{Apdu, Ins},
cccid::CccId,
chuid::ChuId,
config::Config,
error::Error,
error::{Error, Result},
mgm::MgmKey,
readers::{Reader, Readers},
piv,
reader::{Context, Reader},
transaction::Transaction,
};
use cipher::common::getrandom::SysRng;
use log::{error, info};
use pcsc::Card;
use rand_core::TryRng;
use std::{
convert::{TryFrom, TryInto},
cmp::{Ord, Ordering},
fmt::{self, Display},
str::FromStr,
};
#[cfg(feature = "untested")]
use crate::{
apdu::StatusWords, metadata, transaction::ChangeRefAction, Buffer, ObjectId, CB_BUF_MAX,
CB_OBJ_MAX, MGMT_AID, TAG_ADMIN, TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP,
use {
crate::{
apdu::StatusWords,
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP},
metadata::AdminData,
mgm,
transaction::ChangeRefAction,
Buffer, ObjectId,
},
std::time::{SystemTime, UNIX_EPOCH},
};
use getrandom::getrandom;
#[cfg(feature = "untested")]
use secrecy::ExposeSecret;
#[cfg(feature = "untested")]
use std::time::{SystemTime, UNIX_EPOCH};
/// Flag for PUK blocked
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
@@ -70,11 +76,12 @@ pub(crate) const KEY_CARDMGM: u8 = 0x9b;
const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN
pub type CachedPin = secrecy::SecretVec<u8>;
/// Cached YubiKey PIN.
// 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)]
/// YubiKey serial number.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct Serial(pub u32);
impl From<u32> for Serial {
@@ -89,11 +96,25 @@ impl From<Serial> for u32 {
}
}
impl TryFrom<&[u8]> for Serial {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() > 4 {
return Err(Error::SizeError);
}
let mut arr = [0u8; 4];
arr[(4 - bytes.len())..].copy_from_slice(bytes);
Ok(Self(u32::from_be_bytes(arr)))
}
}
impl FromStr for Serial {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
u32::from_str(s).map(Serial).map_err(|_| Error::ParseError)
fn from_str(s: &str) -> Result<Self> {
s.parse().map(Serial).map_err(|_| Error::ParseError)
}
}
@@ -103,7 +124,7 @@ impl Display for Serial {
}
}
/// YubiKey Version
/// YubiKey version.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Version {
/// Major version component
@@ -125,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 {
@@ -133,8 +170,40 @@ impl Display for Version {
}
}
/// YubiKey Device: this is the primary API for opening a session and
/// performing various operations.
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
/// with a YubiKey which is represented by this type.
@@ -148,63 +217,102 @@ pub struct YubiKey {
pub(crate) serial: Serial,
}
impl fmt::Debug for YubiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("YubiKey")
.field("name", &self.name)
.field("version", &self.version)
.field("serial", &self.serial)
.finish_non_exhaustive()
}
}
impl YubiKey {
/// Open a connection to a YubiKey.
///
/// Returns an error if there is more than one YubiKey detected.
/// Returns an error if more than one YubiKey is detected (or none at all).
///
/// NOTE: If multiple YubiKeys are connected, but we are only able to
/// open one of them (e.g. because the other one is in use, and the
/// connection doesn't allow sharing), the YubiKey that we were able to
/// open is returned.
///
/// If you need to operate in environments with more than one YubiKey
/// attached to the same system, use [`YubiKey::open_by_serial`] or
///[`yubikey_piv::Readers`] to select from the available PC/SC readers.
pub fn open() -> Result<Self, Error> {
let mut readers = Readers::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
let mut reader_iter = readers.iter()?;
/// [`yubikey::reader::Context`][`Context`] to select from the available
/// PC/SC readers.
pub fn open() -> Result<Self> {
let mut yubikey: Option<Self> = None;
let mut readers = Context::open()?;
for reader in readers.iter()? {
if let Ok(yk_found) = reader.open() {
if let Some(yk_stored) = yubikey {
// We found two YubiKeys, so we won't use either.
// Don't reset them.
let _ = yk_stored.disconnect(pcsc::Disposition::LeaveCard);
let _ = yk_found.disconnect(pcsc::Disposition::LeaveCard);
if let Some(reader) = reader_iter.next() {
if reader_iter.next().is_some() {
error!("multiple YubiKeys detected!");
return Err(Error::PcscError { inner: None });
} else {
yubikey = Some(yk_found);
}
}
}
return reader.open();
}
if let Some(yubikey) = yubikey {
// We found exactly one YubiKey that we could open, so we return it.
Ok(yubikey)
} else {
error!("no YubiKey detected!");
Err(Error::NotFound)
}
}
/// Open a YubiKey with a specific serial number.
pub fn open_by_serial(serial: Serial) -> Result<Self, Error> {
let mut readers = Readers::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
pub fn open_by_serial(serial: Serial) -> Result<Self> {
let mut readers = Context::open()?;
let mut open_error = None;
for reader in readers.iter()? {
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
Err(e) => {
// Save the first error we see that indicates we might have been able
// to find a matching YubiKey.
if open_error.is_none() {
if let Error::PcscError {
inner: Some(pcsc::Error::SharingViolation),
} = e
{
open_error = Some(e);
}
}
continue;
}
};
if serial == yubikey.serial() {
return Ok(yubikey);
} else {
// We didn't want this YubiKey; don't reset it.
let _ = yubikey.disconnect(pcsc::Disposition::LeaveCard);
}
}
Err(if let Some(e) = open_error {
e
} else {
error!("no YubiKey detected with serial: {}", serial);
Err(Error::NotFound)
Error::NotFound
})
}
/// Reconnect to a YubiKey
/// Reconnect to a YubiKey.
#[cfg(feature = "untested")]
pub fn reconnect(&mut self) -> Result<(), Error> {
pub fn reconnect(&mut self) -> Result<()> {
info!("trying to reconnect to current reader");
self.card.reconnect(
@@ -213,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)?;
@@ -228,13 +333,48 @@ impl YubiKey {
Ok(())
}
/// Begin a transaction.
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> {
// TODO(tarcieri): reconnect support
Ok(Transaction::new(&mut self.card)?)
/// Disconnect from the YubiKey.
///
/// In case of error, ownership of the YubiKey is returned to the caller.
///
/// # Note
///
/// `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: pcsc::Disposition,
) -> core::result::Result<(), (Self, Error)> {
let Self {
card,
name,
pin,
version,
serial,
} = self;
card.disconnect(disposition).map_err(|(card, e)| {
(
Self {
card,
name,
pin,
version,
serial,
},
e.into(),
)
})
}
/// Get the name of the associated PC/SC card reader
/// Begin a transaction.
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>> {
// TODO(tarcieri): reconnect support
Transaction::new(&mut self.card)
}
/// Get the name of the associated PC/SC card reader.
pub fn name(&self) -> &str {
&self.name
}
@@ -254,57 +394,62 @@ impl YubiKey {
}
/// Get device configuration.
pub fn config(&mut self) -> Result<Config, Error> {
pub fn config(&mut self) -> Result<Config> {
Config::get(self)
}
/// Get CHUID
pub fn chuid(&mut self) -> Result<CHUID, Error> {
CHUID::get(self)
/// Get Cardholder Unique Identifier (CHUID).
pub fn chuid(&mut self) -> Result<ChuId> {
ChuId::get(self)
}
/// Get CCCID
pub fn cccid(&mut self) -> Result<CCC, Error> {
CCC::get(self)
/// Get Cardholder Capability Container (CCC) Identifier.
pub fn cccid(&mut self) -> Result<CccId> {
CccId::get(self)
}
/// Authenticate to the card using the provided management key (MGM).
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
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)
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
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().unwrap());
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;
// 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)?;
if getrandom(&mut data[14..22]).is_err() {
error!("failed getting randomness for authentication");
return Err(Error::RandomnessError);
}
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 challenge = [0u8; 8];
challenge.copy_from_slice(&data[14..22]);
let mut host_challenge = vec![0u8; challenge_len];
SysRng
.try_fill_bytes(&mut host_challenge)
.map_err(|_| Error::GenericError)?;
let authentication = APDU::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
data.extend_from_slice(&host_challenge);
let authentication = Apdu::new(Ins::Authenticate)
.params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
.data(data)
.transmit(&txn, 261)?;
if !authentication.is_success() {
@@ -312,24 +457,22 @@ 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);
mgm_key.check_challenge(&host_challenge, &authentication.data()[4..])
}
Ok(())
/// Get the PIV keys contained in this YubiKey.
pub fn piv_keys(&mut self) -> Result<Vec<piv::Key>> {
piv::Key::list(self)
}
/// Deauthenticate
/// Deauthenticate.
#[cfg(feature = "untested")]
pub fn deauthenticate(&mut self) -> Result<(), Error> {
pub fn deauthenticate(&mut self) -> Result<()> {
let txn = self.begin_transaction()?;
let status_words = APDU::new(Ins::SelectApplication)
let status_words = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(MGMT_AID)
.data(mgm::APPLET_ID)
.transmit(&txn, 255)?
.status_words();
@@ -338,34 +481,39 @@ impl YubiKey {
"Failed selecting mgmt application: {:04x}",
status_words.code()
);
return Err(Error::GenericError);
return Err(match status_words {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: mgm::APPLET_NAME,
},
_ => Error::GenericError,
});
}
Ok(())
}
/// Verify device PIN.
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<()> {
{
let txn = self.begin_transaction()?;
txn.verify_pin(pin)?;
}
if !pin.is_empty() {
self.pin = Some(CachedPin::new(pin.into()))
self.pin = Some(pin.into())
}
Ok(())
}
/// Get the number of PIN retries
pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
/// Get the number of PIN retries.
pub fn get_pin_retries(&mut self) -> Result<u8> {
let txn = self.begin_transaction()?;
// 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(&[]) {
@@ -375,9 +523,9 @@ impl YubiKey {
}
}
/// Set the number of PIN retries
/// Set the number of PIN retries.
#[cfg(feature = "untested")]
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<(), Error> {
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<()> {
// Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 {
return Ok(());
@@ -399,54 +547,41 @@ impl YubiKey {
/// Change the Personal Identification Number (PIN).
///
/// The default PIN code is 123456
/// The default PIN code is `123456`.
#[cfg(feature = "untested")]
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> {
{
let txn = self.begin_transaction()?;
txn.change_ref(ChangeRefAction::ChangePin, current_pin, new_pin)?;
}
if !new_pin.is_empty() {
self.pin = Some(CachedPin::new(new_pin.into()));
self.pin = Some(new_pin.into());
}
Ok(())
}
/// Set PIN last changed
/// Set PIN last changed.
#[cfg(feature = "untested")]
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = [0u8; CB_BUF_MAX];
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<()> {
let txn = yubikey.begin_transaction()?;
let buffer = metadata::read(&txn, TAG_ADMIN)?;
let mut cb_data = buffer.len();
data[..cb_data].copy_from_slice(&buffer);
let mut admin_data = AdminData::read(&txn)?;
// TODO(tarcieri): double check this is little endian
let tnow = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.duration_since(UNIX_EPOCH)?
.as_secs()
.to_le_bytes();
metadata::set_item(
&mut data,
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_TIMESTAMP,
&tnow,
)
.map_err(|e| {
error!("could not set pin timestamp, err = {}", e);
e
})?;
admin_data
.set_item(TAG_ADMIN_TIMESTAMP, &tnow)
.inspect_err(|e| error!("could not set pin timestamp, err = {}", e))?;
metadata::write(&txn, TAG_ADMIN, &data).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(())
}
@@ -457,21 +592,21 @@ impl YubiKey {
///
/// The PUK is part of the PIV standard that the YubiKey follows.
///
/// The default PUK code is 12345678.
/// The default PUK code is `12345678`.
#[cfg(feature = "untested")]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> {
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.change_ref(ChangeRefAction::ChangePuk, current_puk, new_puk)
}
/// Block PUK: permanently prevent the PIN from becoming unblocked
/// Block PUK: permanently prevent the PIN from becoming unblocked.
#[cfg(feature = "untested")]
pub fn block_puk(yubikey: &mut YubiKey) -> Result<(), Error> {
pub fn block_puk(&mut self) -> Result<()> {
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
let mut tries_remaining: i32 = -1;
let mut flags = [0];
let txn = yubikey.begin_transaction()?;
let txn = self.begin_transaction()?;
while tries_remaining != 0 {
// 2 -> change puk
@@ -494,8 +629,10 @@ impl YubiKey {
}
}
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
// Attempt to set the "PUK blocked" flag in admin data.
let mut admin_data = AdminData::read(&txn)
.inspect(|data| {
if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags.len() {
flags.copy_from_slice(item)
} else {
@@ -506,22 +643,13 @@ impl YubiKey {
);
}
}
}
})
.unwrap_or_default();
flags[0] |= ADMIN_FLAGS_1_PUK_BLOCKED;
let mut data = [0u8; CB_BUF_MAX];
let mut cb_data: usize = data.len();
if metadata::set_item(
&mut data,
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags,
)
.is_ok()
{
if metadata::write(&txn, TAG_ADMIN, &data[..cb_data]).is_err() {
if admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags).is_ok() {
if admin_data.write(&txn).is_err() {
error!("could not write admin metadata");
}
} else {
@@ -534,45 +662,49 @@ impl YubiKey {
/// Unblock a Personal Identification Number (PIN) using a previously
/// configured PIN Unblocking Key (PUK).
#[cfg(feature = "untested")]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> {
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin)
}
/// Fetch an object from the YubiKey
/// Fetch an object from the YubiKey.
#[cfg(feature = "untested")]
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer, Error> {
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer> {
let txn = self.begin_transaction()?;
txn.fetch_object(object_id)
}
/// Save an object
/// Save an object.
#[cfg(feature = "untested")]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<(), Error> {
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.save_object(object_id, indata)
}
/// Get an auth challenge
/// Get an auth challenge.
#[cfg(feature = "untested")]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> {
let txn = self.begin_transaction()?;
let response = APDU::new(Ins::Authenticate)
let response = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.data([0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?;
if !response.is_success() {
return Err(Error::AuthenticationError);
}
Ok(response.data()[4..12].try_into().unwrap())
Ok(response
.data()
.get(4..12)
.ok_or(Error::SizeError)?
.try_into()?)
}
/// Verify an auth response
/// Verify an auth response.
#[cfg(feature = "untested")]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> {
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> {
let mut data = [0u8; 12];
data[0] = 0x7c;
data[1] = 0x0a;
@@ -583,9 +715,9 @@ impl YubiKey {
let txn = self.begin_transaction()?;
// send the response to the card and a challenge of our own.
let status_words = APDU::new(Ins::Authenticate)
let status_words = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.data(data)
.transmit(&txn, 261)?
.status_words();
@@ -602,7 +734,7 @@ impl YubiKey {
///
/// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")]
pub fn reset_device(&mut self) -> Result<(), Error> {
pub fn reset_device(&mut self) -> Result<()> {
let templ = [0, Ins::Reset.code(), 0, 0];
let txn = self.begin_transaction()?;
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();
@@ -618,23 +750,37 @@ impl YubiKey {
impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
type Error = Error;
fn try_from(reader: &'a Reader<'_>) -> Result<Self, Error> {
let mut card = reader.connect().map_err(|e| {
error!("error connecting to reader '{}': {}", reader.name(), e);
e
})?;
fn try_from(reader: &'a Reader<'_>) -> Result<Self> {
let mut card = reader
.connect()
.inspect_err(|e| error!("error connecting to reader '{}': {}", reader.name(), e))?;
info!("connected to reader: {}", reader.name());
let (version, serial) = {
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)?;
(v, s)
Ok((v, s))
};
match app_version_serial() {
Err(e) => {
error!("Could not use reader: {}", e);
// We were unable to use the card, so we've effectively only connected as
// a side-effect of determining this. Avoid disrupting its internal state
// any further (e.g. preserve the PIN cache of whatever applet is selected
// currently).
if let Err((_, e)) = card.disconnect(pcsc::Disposition::LeaveCard) {
error!("Failed to disconnect gracefully from card: {}", e);
}
Err(e)
}
Ok((version, serial)) => {
let yubikey = YubiKey {
card,
name: String::from(reader.name()),
@@ -646,3 +792,5 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
Ok(yubikey)
}
}
}
}
+85
View File
@@ -0,0 +1,85 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
d4:29:8f:df:8a:af:7b:c0:d7:bf:19:9d:90:d5:ef:ca
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Ferdinand Linnenberg CA
Validity
Not Before: Feb 10 12:25:37 2022 GMT
Not After : May 15 12:25:37 2024 GMT
Subject: CN=Bob
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d5:27:9b:99:1b:3a:36:64:36:c8:e5:78:64:b6:
9d:70:9d:29:6c:0e:85:91:4b:78:3b:dc:16:c3:09:
8c:d3:74:20:8c:6f:ed:c3:90:c9:1b:4d:80:d5:46:
da:52:7f:d2:2f:bc:b2:f7:40:8d:ad:dd:24:b9:5c:
dc:a2:21:2f:48:ec:06:93:8b:89:f0:cd:63:ff:a1:
fd:ce:36:d5:07:7a:1e:0e:cf:68:a8:c1:b3:7f:62:
84:b7:e1:cf:25:7b:3f:a8:3c:ac:07:1a:fd:c2:e1:
e0:9e:26:24:c1:0d:6d:9d:c6:57:6a:b4:39:28:3d:
88:3e:c9:6a:89:90:72:4a:7b:75:c5:5e:1b:5e:5c:
32:54:a3:ff:eb:01:68:7f:89:b4:4c:01:3f:08:8e:
6c:61:49:60:26:0b:26:58:81:d7:1a:57:ee:52:5c:
05:47:de:da:eb:b5:92:9d:5b:ce:26:18:44:59:3e:
27:d0:61:86:e2:f4:c6:d9:c7:2b:1f:cb:ea:78:f0:
a1:a9:57:d7:98:4c:c1:2f:ae:6a:38:b4:34:53:2e:
5a:9e:f8:58:c7:51:e7:fd:b8:27:cd:87:72:26:c1:
7d:14:c7:cd:fb:f2:04:8a:c4:8f:61:cf:a8:78:bd:
21:be:28:cb:e8:a8:65:29:28:82:46:2f:18:e6:ff:
6f:53
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
B5:A5:F0:37:25:97:AD:BE:F1:43:52:45:4D:8B:A0:5E:E9:78:21:B8
X509v3 Authority Key Identifier:
keyid:26:4E:EB:B0:A5:1B:08:A8:90:2A:85:04:73:84:B5:A5:2C:61:D6:91
DirName:/CN=Ferdinand Linnenberg CA
serial:8C:E0:40:D9:D8:60:E5:77
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Key Usage:
Digital Signature
Signature Algorithm: sha256WithRSAEncryption
19:f3:eb:c1:95:e6:d5:a9:33:d7:2e:02:d8:3a:91:84:81:14:
93:fc:03:4d:b1:4b:9d:0b:9b:94:93:9f:1a:0d:87:31:a1:fa:
a6:c7:3a:6b:18:24:12:ab:28:fb:c8:e3:09:a2:5d:50:49:00:
d9:18:e6:4a:09:18:e0:1c:da:d3:19:96:3d:74:72:fe:e0:8f:
ee:59:54:66:2e:57:72:b8:91:55:06:13:e5:9e:89:a2:3a:13:
3b:45:30:d3:cd:15:0e:81:eb:4f:b0:6a:a4:6d:00:7d:5b:c0:
4a:7f:97:d0:27:27:31:ae:3e:72:f1:74:fe:86:8e:29:a9:42:
23:26:22:db:08:8b:df:e9:d3:83:8d:81:10:36:d7:33:68:5e:
cb:93:cb:1e:12:c8:cb:be:5e:5c:8e:58:b0:1d:06:5e:c9:98:
b7:f1:49:fe:c4:03:de:b4:2b:da:9d:2c:7d:98:37:1c:6c:a8:
95:21:6f:23:e3:2e:09:bc:6c:e5:ed:e2:50:d8:f7:da:45:39:
d8:34:8a:57:0c:4f:d0:0d:80:06:d6:34:63:72:27:d1:50:d1:
d2:21:2c:97:57:17:98:02:95:3a:96:ed:75:9f:cc:f3:b8:f1:
3a:85:f9:58:08:9b:a0:75:fd:9b:fd:31:dd:08:dc:14:3d:f4:
68:aa:d4:30
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgIRANQpj9+Kr3vA178ZnZDV78owDQYJKoZIhvcNAQELBQAw
IjEgMB4GA1UEAwwXRmVyZGluYW5kIExpbm5lbmJlcmcgQ0EwHhcNMjIwMjEwMTIy
NTM3WhcNMjQwNTE1MTIyNTM3WjAOMQwwCgYDVQQDDANCb2IwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDVJ5uZGzo2ZDbI5Xhktp1wnSlsDoWRS3g73BbD
CYzTdCCMb+3DkMkbTYDVRtpSf9IvvLL3QI2t3SS5XNyiIS9I7AaTi4nwzWP/of3O
NtUHeh4Oz2iowbN/YoS34c8lez+oPKwHGv3C4eCeJiTBDW2dxldqtDkoPYg+yWqJ
kHJKe3XFXhteXDJUo//rAWh/ibRMAT8IjmxhSWAmCyZYgdcaV+5SXAVH3trrtZKd
W84mGERZPifQYYbi9MbZxysfy+p48KGpV9eYTMEvrmo4tDRTLlqe+FjHUef9uCfN
h3ImwX0Ux8378gSKxI9hz6h4vSG+KMvoqGUpKIJGLxjm/29TAgMBAAGjgaMwgaAw
CQYDVR0TBAIwADAdBgNVHQ4EFgQUtaXwNyWXrb7xQ1JFTYugXul4IbgwUgYDVR0j
BEswSYAUJk7rsKUbCKiQKoUEc4S1pSxh1pGhJqQkMCIxIDAeBgNVBAMMF0ZlcmRp
bmFuZCBMaW5uZW5iZXJnIENBggkAjOBA2dhg5XcwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBAQAZ8+vBlebVqTPXLgLY
OpGEgRST/ANNsUudC5uUk58aDYcxofqmxzprGCQSqyj7yOMJol1QSQDZGOZKCRjg
HNrTGZY9dHL+4I/uWVRmLldyuJFVBhPlnomiOhM7RTDTzRUOgetPsGqkbQB9W8BK
f5fQJycxrj5y8XT+ho4pqUIjJiLbCIvf6dODjYEQNtczaF7Lk8seEsjLvl5cjliw
HQZeyZi38Un+xAPetCvanSx9mDccbKiVIW8j4y4JvGzl7eJQ2PfaRTnYNIpXDE/Q
DYAG1jRjcifRUNHSISyXVxeYApU6lu11n8zzuPE6hflYCJugdf2b/THdCNwUPfRo
qtQw
-----END CERTIFICATE-----
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIi6DixMpf5PQCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECLgvCsIAjjXRBIIEyDAss0V4NrI5
W7KXPRgRJ1tqvQrWZQTIFu4Crzs9Inb4TtSv5mATI9ZU2RMFF6MYXlhIxJng861P
5IWcU6VeOjRFej8wcB3uTvD2z7NB2cyA5BZSojrZfX5OIEKL9sBzn0vinqmm5N1z
oXhLMgf0FZssA3+zjIf04vtvmk5pxTCE6dq6vlsEIJyQ0xGc39bStIwk2a4E9wvi
XayKNJnRFSrTahuI3DvQJPd9TdmM6sBKnrcJrDa6LvH51SrGzW8bBEjDAmC7yJdi
mBckFTjZ6rGrSxOI6HdnF9RP+y9OiLE4ou5OJ9MbBtngq9OrTImAMZ5ftWowaqX/
Do1UTqixi4ecWrr5fr1+A2Vch0I1drds2e/mmLR+5GEQXQXZPZKjtPMwxM/AYnTZ
w/M3T9KtwSj0s5G6Saz4WpzaUL7wATb/UNqMr8Ifl8mHEVoFZhvoRpMWA7Yj1oPb
cHz8lsfoSrTnK+zLR8ZK3HRu4MtpdCNVwQIJ67T6Feb8YLwYSccTNHBSqUmWRD95
wOOqY33xcfplaQ2Y+/8+mHScGSEPNmC4f7EggDeUnG3ow0f4n95DENO+aYqGLjSF
+XdCjhD+NTNdlV0z50B6P2XWUoBZOOnPfgFf4nAgn7xQbkZZOk5bQDKo2Zu9jW9/
uJyTHqI58tcopI3cjd5iQXOJUrM4OWpPIu3p+VWaIAA8JzJIfDN6fyQ3qsMr305L
30JcjrH6if/6J+2g+DpMAK928JY2hfE8VNWH7096ZnArp42/hLYsNuYnSrL1eS+g
F/4mvyZyLLTFyB0Frnic4I1QTuNkNmwSrm/B5wIWLqkS7XAyyDDXAcaTHdZCN6nM
O2OuF7DfBsFcNMM4VagG5adPS9CYkvz0EEh6ho5XiP2yL3tZfsHyuB4njAsV3aFi
D0Yq7QiCf5iA2d4KsYO6yr1wPfVhlsmPi3++mrHulBwwCQWHTlPgRZnjj1xgmPcQ
00KsUVh+CMWlf20O5sKhzjvkzbwUj1K+ZfMDuuq1RbzFRSx+Gx8vIaThFg9kVyoP
zuvzsT6qc3BGNHmaGZ3d5Re25AuGRTF4cTpDfjW0UL7Wnvnis7iMrUasDhyF7CFn
/KG7eKzxqS08o6D4AM5S/fzZEtszoEgAga6DS2R75FVskDweWuEIsar9UGg3UlmW
q3+rRPRf1CzrLtyYenkkLg/ajr8JOnFGqZVaLmMnegZQH6rF3aEzlQLNgbNepcuA
ObSmAO6MR3MlQgdsH/lNzOPdj1gKcE25hOjGfmwgbOXSJv9Cz0bcBLFEyLZSNpRk
HhNejj6BEz/Cmqg1wm7SOBHsXJGcOTnLO1Y3FBt0I7heWvWmj9rOLG9tvvx9dtrP
pQyDbIcWonuXLrXYSPyOjmeWoQSzdH3NsCswBV4G+iOiLCJDkElR+mrwKbhtS88T
3YeZnsCTsmH+jZpxGgPTObjIG91U4UE4Pnkwc2df355VuOKrf0rB/NK0A9hZGqsV
nxtodjn92P6UFzmfEdt95pMcmurK9wkm1kRkP7cIyAs2lCOIdbgGsszz6Mk16Xqy
49RdhLxJrJ4gkYZIAbY+KNGVc7uPhm/T9xGrIstbEsoUM7jy7nHMOCCDgdbwX/4X
caMeSMZcZ+RvraDDBEbSbg==
-----END ENCRYPTED PRIVATE KEY-----
+316 -65
View File
@@ -3,39 +3,38 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use getrandom::getrandom;
use lazy_static::lazy_static;
use cipher::common::{getrandom::SysRng, Generate};
use log::trace;
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
use once_cell::sync::Lazy;
use rsa::{pkcs1v15, RsaPublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryInto;
use std::{env, sync::Mutex};
use x509::RelativeDistinguishedName;
use yubikey_piv::{
certificate::{Certificate, PublicKeyInfo},
key::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
policy::{PinPolicy, TouchPolicy},
Error, MgmKey, YubiKey,
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::{yubikey_signer, Certificate},
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
};
lazy_static! {
/// Provide thread-safe access to a YubiKey
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
}
/// One-time test initialization and setup
fn init_yubikey() -> Mutex<YubiKey> {
static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
let yubikey = YubiKey::open().unwrap();
let yubikey = if let Ok(serial) = env::var("YUBIKEY_SERIAL") {
let serial = Serial::from_str(&serial).unwrap();
YubiKey::open_by_serial(serial).unwrap()
} else {
YubiKey::open().unwrap()
};
trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version());
Mutex::new(yubikey)
}
});
//
// CCCID support
@@ -44,12 +43,15 @@ fn init_yubikey() -> Mutex<YubiKey> {
#[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:?}"),
}
}
@@ -60,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:?}"),
}
}
@@ -76,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());
@@ -89,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());
@@ -102,46 +113,96 @@ 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());
}
//
// Management key support
//
#[cfg(feature = "untested")]
#[test]
#[ignore]
fn test_set_mgmkey() {
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(&default_key).is_ok());
// Set a protected management key.
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(&default_key).is_err());
assert!(yubikey.authenticate(&protected).is_ok());
// Set a manual management key.
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(&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(&default_key).is_ok());
}
//
// Certificate support
//
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
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);
// Generate a new key in the selected slot.
let generated = key::generate(
let generated = piv::generate(
&mut yubikey,
slot,
algorithm,
KT::ALGORITHM,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let mut serial = [0u8; 20];
getrandom(&mut serial).unwrap();
// 0x80 0x00 ... (20bytes) is invalid because of high MSB (serial will keep the sign)
// we'll limit ourselves to 19 bytes 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();
// Generate a self-signed certificate for the new key.
let extensions: &[x509::Extension<'_, &[u64]>] = &[];
let cert_result = Certificate::generate_self_signed(
let cert_result = Certificate::generate_self_signed::<_, KT>(
&mut yubikey,
slot,
serial,
None,
&[RelativeDistinguishedName::common_name("testSubject")],
validity,
Name::from_str("CN=testSubject").expect("parse name"),
generated,
extensions,
|_builder| Ok(()),
);
assert!(cert_result.is_ok());
@@ -153,57 +214,247 @@ fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
#[test]
#[ignore]
fn generate_self_signed_rsa_cert() {
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024);
let cert = generate_self_signed_cert::<yubikey_signer::YubiRsa<yubikey_signer::Rsa1024>>();
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
_ => unreachable!(),
};
let pubkey = RsaPublicKey::try_from(cert.subject_pki()).expect("valid rsa key");
let pubkey = pkcs1v15::VerifyingKey::<Sha256>::new(pubkey);
let data = cert.as_ref();
let data = cert.cert.to_der().expect("serialize certificate");
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
let msg = &data[4..8 + tbs_cert_len];
let sig = &data[data.len() - 128..];
let sig = pkcs1v15::Signature::try_from(&data[data.len() - 128..]).unwrap();
let hash = Sha256::digest(msg);
assert!(pubkey
.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(SHA2_256)
},
&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]
#[ignore]
fn generate_self_signed_ec_cert() {
let cert = generate_self_signed_cert(AlgorithmId::EccP256);
let cert = generate_self_signed_cert::<p256::NistP256>();
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::EcP256(pubkey) => pubkey,
_ => unreachable!(),
};
let vk = p256::ecdsa::VerifyingKey::try_from(cert.subject_pki()).expect("ecdsa key expected");
let data = cert.as_ref();
let data = cert.cert.to_der().expect("serialize certificate");
let tbs_cert_len = data[6] as usize;
let sig_algo_len = data[7 + tbs_cert_len + 1] as usize;
let sig_start = 7 + tbs_cert_len + 2 + sig_algo_len + 3;
let msg = &data[4..7 + tbs_cert_len];
let sig = &data[sig_start..];
let sig = p256::ecdsa::Signature::from_der(&data[sig_start..]).unwrap();
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1};
let ring_pk = UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, pubkey.as_bytes());
assert!(ring_pk.verify(msg, sig).is_ok());
use p256::ecdsa::signature::Verifier;
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() {
assert_eq!(format!("{}", SlotId::Authentication), "Authentication");
assert_eq!(format!("{}", SlotId::Signature), "Signature");
assert_eq!(format!("{}", SlotId::KeyManagement), "KeyManagement");
assert_eq!(
format!("{}", SlotId::CardAuthentication),
"CardAuthentication"
);
assert_eq!(format!("{}", SlotId::Attestation), "Attestation");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R1)), "R1");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R2)), "R2");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R3)), "R3");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R4)), "R4");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R5)), "R5");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R6)), "R6");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R7)), "R7");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R8)), "R8");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R9)), "R9");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R10)), "R10");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R11)), "R11");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R12)), "R12");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R13)), "R13");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R14)), "R14");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R15)), "R15");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R16)), "R16");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R17)), "R17");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R18)), "R18");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R19)), "R19");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R20)), "R20");
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Pin)),
"Pin"
);
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Puk)),
"Puk"
);
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Management)),
"Management"
);
}
//
// Metadata
//
#[test]
#[ignore]
fn test_read_metadata() {
let mut yubikey = 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(&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::EccP256,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
match piv::metadata(&mut yubikey, slot) {
Ok(metadata) => assert_eq!(metadata.public, Some(generated)),
Err(Error::NotSupported) => {
// Some YubiKeys don't support metadata
eprintln!("metadata not supported by this YubiKey");
}
Err(err) => panic!("{}", err),
}
}
#[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::from_bytes(bob_der).expect("Failed to parse valid certificate");
assert_eq!(
cert.subject(),
"CN=Bob",
"Subject is {} should be CN=Bob",
cert.subject()
);
assert_eq!(
cert.issuer(),
"CN=Ferdinand Linnenberg CA",
"Issuer is {} should be {}",
cert.issuer(),
"CN=Ferdinand Linnenberg CA"
);
}