Compare commits

..

255 Commits

Author SHA1 Message Date
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
Tony Arcieri (iqlusion) 37088bba56 yubikey-cli v0.2.0 (#222) 2021-01-30 12:23:09 -08:00
Tony Arcieri (iqlusion) 3580c45f71 yubikey-piv v0.2.0 (#220) 2021-01-30 07:47:39 -08:00
Tony Arcieri (iqlusion) 79c289ac00 Bump pbkdf2 dependency to v0.7 (#219) 2021-01-30 07:34:54 -08:00
Tony Arcieri (iqlusion) cbca858488 Cargo.lock: bump deps (#218) 2021-01-30 07:23:26 -08:00
Tony Arcieri (iqlusion) 8b896ab4de Rename default git branch from develop to main (#217) 2021-01-30 07:11:18 -08:00
str4d 24b035008c Improve self-signed certificates (#207)
Adds support for:
- A hierarchical SubjectName field.
- Certificate extensions.
2021-01-11 07:49:15 -08:00
str4d 90bc878b21 Dependency updates and MSRV 1.46 (#208)
- cargo update
- cli: Bump x509-parser to 0.9
- Bump elliptic-curve to 0.8. Also requires bumping p256 and p384.
- Bump MSRV to 1.46.0. Required to match the MSRV of elliptic-curve.
2021-01-10 07:14:02 -08:00
Shella Stephens 08185c5ec9 Bump der-parser, nom, x509-parser (#194)
* Bump der-parser from 4.1.0 to 5.0.0

Bumps [der-parser](https://github.com/rusticata/der-parser) from 4.1.0 to 5.0.0.
- [Release notes](https://github.com/rusticata/der-parser/releases)
- [Changelog](https://github.com/rusticata/der-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rusticata/der-parser/compare/der-parser-4.1.0...der-parser-5.0.0)

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

* Bump der-parser, nom, x509-parser

* clippy

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-07 07:20:58 -08:00
dependabot[bot] 7da2c7ba6a Bump termcolor from 1.1.0 to 1.1.2 (#191)
Bumps [termcolor](https://github.com/BurntSushi/termcolor) from 1.1.0 to 1.1.2.
- [Release notes](https://github.com/BurntSushi/termcolor/releases)
- [Commits](https://github.com/BurntSushi/termcolor/compare/1.1.0...1.1.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-30 10:52:35 -08:00
dependabot[bot] fecd786262 Bump ring from 0.16.15 to 0.16.18 (#192)
Bumps [ring](https://github.com/briansmith/ring) from 0.16.15 to 0.16.18.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-30 09:09:16 -08:00
dependabot[bot] 1a6d1d0a71 Bump num-integer from 0.1.43 to 0.1.44 (#185)
Bumps [num-integer](https://github.com/rust-num/num-integer) from 0.1.43 to 0.1.44.
- [Release notes](https://github.com/rust-num/num-integer/releases)
- [Changelog](https://github.com/rust-num/num-integer/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-integer/compare/num-integer-0.1.43...num-integer-0.1.44)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-09 06:10:32 -08:00
dependabot[bot] f43539088b Bump num-traits from 0.2.12 to 0.2.14 (#186)
Bumps [num-traits](https://github.com/rust-num/num-traits) from 0.2.12 to 0.2.14.
- [Release notes](https://github.com/rust-num/num-traits/releases)
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.12...num-traits-0.2.14)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-09 06:02:17 -08:00
dependabot[bot] 72f63131ac Bump sha-1 from 0.9.1 to 0.9.2 (#187)
Bumps [sha-1](https://github.com/RustCrypto/hashes) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/md5-v0.9.1...streebog-v0.9.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-09 05:34:54 -08:00
dependabot[bot] b59856d09e Bump sha2 from 0.9.1 to 0.9.2 (#188)
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.9.1...streebog-v0.9.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-09 05:27:54 -08:00
Tony Arcieri (iqlusion) 7628ebf605 yubikey-cli v0.1.0 (#183) 2020-10-19 09:28:45 -07:00
Tony Arcieri (iqlusion) 0688dbf30d cli: bump sha2 to v0.9 (#182) 2020-10-19 08:56:26 -07:00
Tony Arcieri (iqlusion) 7e3d0bc838 cli: bump x509-parser to v0.8 (#181) 2020-10-19 08:35:49 -07:00
Tony Arcieri (iqlusion) fc62fc286d yubikey-piv v0.1.0 (#180) 2020-10-19 08:26:05 -07:00
Tony Arcieri 199496ab01 Merge pull request #179 from iqlusioninc/x509-parser/v0.8
Bump x509-parser to v0.8
2020-10-19 08:06:36 -07:00
Tony Arcieri ab11e037cc Bump x509-parser to v0.8 2020-10-19 07:58:35 -07:00
Tony Arcieri 41d4d1b332 Merge pull request #178 from iqlusioninc/dependabot/cargo/env_logger-0.8.1
Bump env_logger from 0.7.1 to 0.8.1
2020-10-19 06:16:29 -07:00
dependabot[bot] b21c4bd307 Bump env_logger from 0.7.1 to 0.8.1
Bumps [env_logger](https://github.com/env-logger-rs/env_logger) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/env-logger-rs/env_logger/releases)
- [Changelog](https://github.com/env-logger-rs/env_logger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/env-logger-rs/env_logger/compare/v0.7.1...v0.8.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-19 13:01:11 +00:00
Tony Arcieri 64aea57a63 Merge pull request #177 from iqlusioninc/bump-rustcrypto-deps
Bump RustCrypto dependencies
2020-10-18 10:26:17 -07:00
Tony Arcieri 17ae87f741 Bump RustCrypto dependencies
Updates all RustCrypto crates (`crypto-mac`, `des`, `hmac`, `pbkdf2`)
to the latest versions.
2020-10-18 10:12:09 -07:00
Tony Arcieri 811a7d5373 Merge pull request #176 from iqlusioninc/ci/redo-config
CI: simplify configuration
2020-10-18 07:44:25 -07:00
Tony Arcieri 5b2e025aa4 CI: simplify configuration
Uses a trick to simplify and unify the CI configuration
2020-10-18 07:32:34 -07:00
Tony Arcieri 6313e2ec3f Merge pull request #175 from iqlusioninc/bump-p256-and-p384
Bump `p256` to v0.5; `p384` to v0.4
2020-10-17 14:26:03 -07:00
Tony Arcieri cbe60413cb Bump p256 to v0.5; p384 to v0.4; MSRV 1.44+ 2020-10-17 13:54:40 -07:00
Tony Arcieri 08220032db Merge pull request #174 from iqlusioninc/bump-deps
Cargo.lock: bump dependencies
2020-10-17 13:17:01 -07:00
Tony Arcieri 152a096a6b Cargo.lock: bump dependencies 2020-10-17 13:07:08 -07:00
dependabot[bot] ddb712cc48 Bump zeroize from 1.1.0 to 1.1.1 (#167)
Bumps [zeroize](https://github.com/iqlusioninc/crates) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/zeroize/v1.1.0...zeroize/v1.1.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-21 07:46:42 -07:00
Tony Arcieri (iqlusion) 32dcdfb67f Merge pull request #162 from str4d/update-deps
Update dependencies
2020-08-30 15:52:44 -07:00
Jack Grigg c396971496 cargo update 2020-08-30 18:19:39 +01:00
Jack Grigg f906e6a2d7 des 0.5 2020-08-30 18:18:29 +01:00
Jack Grigg 35fa940a37 secrecy 0.7 2020-08-30 18:16:45 +01:00
Jack Grigg 05a3b85934 hmac 0.9 and pbkdf2 0.5 2020-08-30 18:14:58 +01:00
Jack Grigg 23d0f96adc elliptic-curve 0.5
Requires p256 0.4 and p384 0.3
2020-08-30 18:13:17 +01:00
Shella Stephens 4435a54435 Update der-parser & x509-parser (#145)
* Update der-parser & x509-parser

* use rust v1.41 toolchain
2020-06-23 17:03:04 -07:00
dependabot[bot] 55b960501a Bump ring from 0.16.14 to 0.16.15 (#144)
Bumps [ring](https://github.com/briansmith/ring) from 0.16.14 to 0.16.15.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-23 14:47:23 -07:00
Shella Stephens 860c163eb9 Update rsa to v0.3 & other dependencies (#142)
* Update rsa to v0.3 & other dependencies
2020-06-15 16:40:33 -07:00
Tony Arcieri 15f9e265e6 Merge pull request #128 from BlackHoleFox/develop
Refactor key import function
2020-06-10 09:12:21 -07:00
BlackHoleFox 556b9cb671 Remove dependency on regular num-bigint 2020-06-09 18:42:56 -05:00
Shella Stephens 842198b566 Merge pull request #131 from iqlusioninc/dependabot/add-v2-config-file
Create Dependabot config file
2020-06-09 14:05:54 -07:00
dependabot-preview[bot] 812b4d588f Create Dependabot config file 2020-06-09 20:45:55 +00:00
BlackHoleFox 6e3560c10f Switch to buffer alias 2020-06-08 22:09:57 -05:00
BlackHoleFox 0f907ebd5c Implement RSA key precomputation 2020-06-08 21:48:25 -05:00
BlackHoleFox acc96e988f Refactor key import function 2020-06-01 23:07:18 -05:00
Tony Arcieri 4037dfe19d Merge pull request #125 from iqlusioninc/dependabot/cargo/sha2-0.8.2
Bump sha2 from 0.8.1 to 0.8.2
2020-05-25 07:04:19 -07:00
dependabot-preview[bot] a9e27eaadb Bump sha2 from 0.8.1 to 0.8.2
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.8.1 to 0.8.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.8.1...sha2-v0.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 13:36:25 +00:00
Tony Arcieri 9208cc3862 Merge pull request #118 from iqlusioninc/dependabot/cargo/x509-parser-0.7.0
Bump x509-parser from 0.6.5 to 0.7.0
2020-05-04 10:36:31 -07:00
dependabot-preview[bot] 2d4f2fa750 Bump x509-parser from 0.6.5 to 0.7.0
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.5 to 0.7.0.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 17:06:10 +00:00
Tony Arcieri 3a5411d989 Merge pull request #123 from iqlusioninc/dependabot/cargo/ring-0.16.13
Bump ring from 0.16.12 to 0.16.13
2020-05-04 10:04:25 -07:00
dependabot-preview[bot] ed66d399ca Bump ring from 0.16.12 to 0.16.13
Bumps [ring](https://github.com/briansmith/ring) from 0.16.12 to 0.16.13.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 16:14:43 +00:00
Tony Arcieri a018728f7a Merge pull request #122 from iqlusioninc/dependabot/cargo/p256-0.2.0
Bump p256 from 0.1.0 to 0.2.0
2020-05-04 09:13:09 -07:00
dependabot-preview[bot] 39d2b0982a Bump p256 from 0.1.0 to 0.2.0
Bumps [p256](https://github.com/RustCrypto/elliptic-curves) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/elliptic-curves/releases)
- [Commits](https://github.com/RustCrypto/elliptic-curves/compare/p256/v0.1.0...p256/v0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 16:03:26 +00:00
Tony Arcieri e501cce644 Merge pull request #124 from iqlusioninc/remove-unnecessary-parens
Remove unnecessary parens
2020-05-04 08:56:28 -07:00
Tony Arcieri 5e52f93f4a Remove unnecessary parens 2020-05-04 08:45:40 -07:00
Tony Arcieri c8cdff2bbf Merge pull request #121 from iqlusioninc/dependabot/cargo/gumdrop-0.8.0
Bump gumdrop from 0.7.0 to 0.8.0
2020-04-22 16:03:24 -07:00
Tony Arcieri 9d18a2cb08 Merge pull request #120 from iqlusioninc/dependabot/cargo/pcsc-2.4.0
Bump pcsc from 2.3.1 to 2.4.0
2020-04-22 16:01:53 -07:00
dependabot-preview[bot] 3aeaa7e1da Bump gumdrop from 0.7.0 to 0.8.0
Bumps [gumdrop](https://github.com/murarth/gumdrop) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/murarth/gumdrop/releases)
- [Commits](https://github.com/murarth/gumdrop/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 13:42:05 +00:00
dependabot-preview[bot] 38cce09a78 Bump pcsc from 2.3.1 to 2.4.0
Bumps [pcsc](https://github.com/bluetech/pcsc-rust) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/bluetech/pcsc-rust/releases)
- [Changelog](https://github.com/bluetech/pcsc-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bluetech/pcsc-rust/compare/v2.3.1...v2.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 13:41:41 +00:00
Tony Arcieri 6f3d65cfc0 Merge pull request #119 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-04-07 07:46:48 -07:00
Tony Arcieri 14cba00dcd Cargo.lock: update dependencies 2020-04-06 11:09:19 -07:00
Shella Stephens ce9b32a27e Merge pull request #116 from iqlusioninc/dependabot/cargo/x509-parser-0.6.5
Bump x509-parser from 0.6.4 to 0.6.5
2020-03-16 06:49:48 -07:00
dependabot-preview[bot] 9042454404 Bump x509-parser from 0.6.4 to 0.6.5
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 13:40:52 +00:00
Tony Arcieri bf376516c9 Merge pull request #115 from iqlusioninc/dependabot/cargo/chrono-0.4.11
Bump chrono from 0.4.10 to 0.4.11
2020-03-09 07:11:13 -07:00
dependabot-preview[bot] 46685da68a Bump chrono from 0.4.10 to 0.4.11
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/master/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.10...v0.4.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 13:33:44 +00:00
Tony Arcieri 3c70c77e95 Merge pull request #114 from iqlusioninc/docs/fix-logo-links
Fix logo links
2020-03-07 08:32:37 -08:00
Tony Arcieri 154d8bb97a Fix logo links 2020-03-07 08:20:16 -08:00
Tony Arcieri 31617bf9a8 Merge pull request #113 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-03-07 08:09:30 -08:00
Tony Arcieri 2144750f6f Cargo.lock: update dependencies 2020-03-07 07:59:42 -08:00
Tony Arcieri cbbbdd6d8c Merge pull request #112 from iqlusioninc/dependabot/cargo/x509-parser-0.6.4
Bump x509-parser from 0.6.2 to 0.6.4
2020-03-02 09:40:52 -08:00
dependabot-preview[bot] 9478cd392f Bump x509-parser from 0.6.2 to 0.6.4
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/compare/x509-parser-0.6.2...x509-parser-0.6.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-02 13:35:12 +00:00
Shella Stephens a70013d048 Merge pull request #110 from iqlusioninc/dependabot/cargo/x509-parser-0.6.2
Bump x509-parser from 0.6.0 to 0.6.2
2020-02-26 12:18:16 -08:00
Tony Arcieri e4418edb32 Merge pull request #111 from iqlusioninc/dependabot/cargo/nom-5.1.1
Bump nom from 5.1.0 to 5.1.1
2020-02-25 04:21:44 -10:00
dependabot-preview[bot] 89086bb1ac Bump nom from 5.1.0 to 5.1.1
Bumps [nom](https://github.com/Geal/nom) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/Geal/nom/releases)
- [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Geal/nom/compare/5.1.0...5.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-25 13:36:15 +00:00
dependabot-preview[bot] 3a0262ce90 Bump x509-parser from 0.6.0 to 0.6.2
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.0 to 0.6.2.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/compare/x509-parser-0.6.0...x509-parser-0.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 13:22:36 +00:00
Tony Arcieri f4b1e8b6e2 Merge pull request #108 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-02-15 07:13:59 -08:00
Tony Arcieri 926450b573 Cargo.lock: update dependencies 2020-02-15 07:03:21 -08:00
Tony Arcieri fec43589af Merge pull request #106 from iqlusioninc/dependabot/cargo/subtle-encoding-0.5.1
Bump subtle-encoding from 0.5.0 to 0.5.1
2020-02-10 06:16:36 -08:00
Tony Arcieri 4617ce053d Merge pull request #105 from iqlusioninc/dependabot/cargo/ring-0.16.11
Bump ring from 0.16.10 to 0.16.11
2020-02-10 06:16:18 -08:00
Tony Arcieri 8bc7b471f6 Merge pull request #107 from iqlusioninc/dependabot/cargo/cookie-factory-0.3.1
Bump cookie-factory from 0.3.0 to 0.3.1
2020-02-10 06:15:58 -08:00
dependabot-preview[bot] a1ef807dea Bump cookie-factory from 0.3.0 to 0.3.1
Bumps [cookie-factory](https://github.com/rust-bakery/cookie-factory) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/rust-bakery/cookie-factory/releases)
- [Commits](https://github.com/rust-bakery/cookie-factory/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 13:25:09 +00:00
dependabot-preview[bot] d67ae3b7e3 Bump subtle-encoding from 0.5.0 to 0.5.1
Bumps [subtle-encoding](https://github.com/iqlusioninc/crates) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/subtle-encoding/v0.5.0...subtle-encoding/v0.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 13:24:52 +00:00
dependabot-preview[bot] 268b3709fd Bump ring from 0.16.10 to 0.16.11
Bumps [ring](https://github.com/briansmith/ring) from 0.16.10 to 0.16.11.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 13:37:06 +00:00
Tony Arcieri b8eee17284 Merge pull request #104 from iqlusioninc/dependabot/cargo/ring-0.16.10
Bump ring from 0.16.9 to 0.16.10
2020-02-03 07:13:56 -08:00
dependabot-preview[bot] 9c566c9130 Bump ring from 0.16.9 to 0.16.10
Bumps [ring](https://github.com/briansmith/ring) from 0.16.9 to 0.16.10.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 13:25:33 +00:00
Tony Arcieri 3c07ee55f7 Merge pull request #103 from iqlusioninc/dependabot/cargo/der-parser-3.0.4
Bump der-parser from 3.0.3 to 3.0.4
2020-01-30 06:34:40 -08:00
dependabot-preview[bot] 264e6e0684 Bump der-parser from 3.0.3 to 3.0.4
Bumps [der-parser](https://github.com/rusticata/der-parser) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/rusticata/der-parser/releases)
- [Commits](https://github.com/rusticata/der-parser/compare/der-parser-3.0.3...der-parser-3.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 13:36:22 +00:00
Tony Arcieri fa5a87269b Merge pull request #101 from iqlusioninc/dependabot/cargo/num-bigint-0.2.6
Bump num-bigint from 0.2.5 to 0.2.6
2020-01-28 06:13:16 -08:00
dependabot-preview[bot] 33d6330186 Bump num-bigint from 0.2.5 to 0.2.6
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.5 to 0.2.6.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.5...num-bigint-0.2.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-28 13:37:27 +00:00
Tony Arcieri e0fbd57937 Merge pull request #100 from iqlusioninc/cargo-lock-v2
Cargo.lock: reformat as a ResolveVersion::V2 lockfile
2020-01-26 08:28:30 -08:00
Tony Arcieri fada2a5b78 Cargo.lock: reformat as a ResolveVersion::V2 lockfile 2020-01-26 08:17:48 -08:00
Tony Arcieri 2f4c1cfb11 Merge pull request #99 from iqlusioninc/update-deps
Cargo.lock: Update deps
2020-01-26 08:16:22 -08:00
Tony Arcieri 85deed78ec Cargo.lock: Update deps
We're using a yanked version of the `log` crate
2020-01-25 16:30:42 -08:00
Tony Arcieri ed5134e9a2 Merge pull request #98 from iqlusioninc/dependabot/cargo/termcolor-1.1.0
Bump termcolor from 1.0.5 to 1.1.0
2020-01-13 09:34:06 -05:00
dependabot-preview[bot] 4770505de0 Bump termcolor from 1.0.5 to 1.1.0
Bumps [termcolor](https://github.com/BurntSushi/termcolor) from 1.0.5 to 1.1.0.
- [Release notes](https://github.com/BurntSushi/termcolor/releases)
- [Commits](https://github.com/BurntSushi/termcolor/compare/1.0.5...1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 13:26:45 +00:00
Tony Arcieri fdb7b891ad Merge pull request #97 from iqlusioninc/dependabot/cargo/num-bigint-0.2.5
Bump num-bigint from 0.2.4 to 0.2.5
2020-01-10 09:05:44 -05:00
dependabot-preview[bot] 548d39d138 Bump num-bigint from 0.2.4 to 0.2.5
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.4 to 0.2.5.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.4...num-bigint-0.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 13:45:24 +00:00
Tony Arcieri 15aee107ad Merge pull request #96 from iqlusioninc/dependabot/cargo/nom-5.1.0
Bump nom from 5.0.1 to 5.1.0
2020-01-08 10:09:04 -05:00
dependabot-preview[bot] 8c65f37d97 Bump nom from 5.0.1 to 5.1.0
Bumps [nom](https://github.com/Geal/nom) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/Geal/nom/releases)
- [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Geal/nom/compare/5.0.1...5.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 13:41:21 +00:00
Tony Arcieri 8802ecb689 Merge pull request #95 from iqlusioninc/elliptic-curve/v0.3
Bump elliptic-curve from 0.2.0 to 0.3.0
2020-01-07 15:22:25 -05:00
Tony Arcieri 27504890d7 Bump elliptic-curve from 0.2.0 to 0.3.0 2020-01-07 15:11:27 -05:00
Tony Arcieri 4999139b9d Merge pull request #91 from iqlusioninc/dependabot/cargo/sha-1-0.8.2
Bump sha-1 from 0.8.1 to 0.8.2
2020-01-07 09:46:33 -05:00
Tony Arcieri b3490b4f39 Merge pull request #94 from iqlusioninc/dependabot/cargo/getrandom-0.1.14
Bump getrandom from 0.1.13 to 0.1.14
2020-01-07 09:46:03 -05:00
dependabot-preview[bot] b4a43f21d0 Bump getrandom from 0.1.13 to 0.1.14
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/rust-random/getrandom/releases)
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.1.13...v0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 13:42:44 +00:00
dependabot-preview[bot] ee84134f26 Bump sha-1 from 0.8.1 to 0.8.2
Bumps [sha-1](https://github.com/RustCrypto/hashes) from 0.8.1 to 0.8.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha1-v0.8.1...sha1-v0.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 02:53:06 +00:00
Tony Arcieri 3da9b3001b Merge pull request #93 from iqlusioninc/dependabot/cargo/sha2-0.8.1
Bump sha2 from 0.8.0 to 0.8.1
2020-01-06 21:51:50 -05:00
dependabot-preview[bot] 61054ae74a Bump sha2 from 0.8.0 to 0.8.1
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.8.0...sha2-v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 13:24:30 +00:00
Tony Arcieri 7cebce6156 Merge pull request #90 from iqlusioninc/dependabot/cargo/num-bigint-0.2.4
Bump num-bigint from 0.2.3 to 0.2.4
2020-01-02 11:29:55 -05:00
dependabot-preview[bot] 81e9c54a2c Bump num-bigint from 0.2.3 to 0.2.4
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.3...num-bigint-0.2.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 13:50:54 +00:00
Tony Arcieri ae20e6d950 Merge pull request #89 from iqlusioninc/dependabot/cargo/pcsc-2.3.1
Bump pcsc from 2.3.0 to 2.3.1
2019-12-20 06:30:14 -08:00
dependabot-preview[bot] 741ba528a4 Bump pcsc from 2.3.0 to 2.3.1
Bumps [pcsc](https://github.com/bluetech/pcsc-rust) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/bluetech/pcsc-rust/releases)
- [Changelog](https://github.com/bluetech/pcsc-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bluetech/pcsc-rust/compare/v2.3.0...v2.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-20 13:40:49 +00:00
Tony Arcieri aaaf3b142e Merge pull request #88 from str4d/open-usability
pcsc::Error::NoReadersAvailable -> Error::NotFound in YubiKey::open*
2019-12-18 09:12:50 -08:00
Jack Grigg b5e774cf2b pcsc::Error::NoReadersAvailable -> Error::NotFound in YubiKey::open*
This provides a consistent user experience between no readers being
connected, and readers being connected but not the one we are trying to
open.
2019-12-18 11:03:30 -06:00
Tony Arcieri 15a590dd93 Merge pull request #87 from iqlusioninc/ci/split-security-audit-and-ignore-spin
.github: split security_audit.yml; ignore spin advisory
2019-12-17 07:38:08 -08:00
Tony Arcieri e6d9003d09 .github: split security_audit.yml; ignore spin advisory
Splits the security audit into a separate file which only runs on
Cargo.toml changes or on a regular schedule.

Ignores the RUSTSEC-2019-0031 warning advisory.
2019-12-17 07:26:44 -08:00
Tony Arcieri 4dd0f7b31e Merge pull request #86 from iqlusioninc/dependabot/cargo/x509-0.1.2
Bump x509 from 0.1.1 to 0.1.2
2019-12-17 07:04:53 -08:00
dependabot-preview[bot] 2d57b8e2e1 Bump x509 from 0.1.1 to 0.1.2
Bumps [x509](https://github.com/str4d/x509.rs) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/str4d/x509.rs/releases)
- [Commits](https://github.com/str4d/x509.rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-17 13:42:12 +00:00
Tony Arcieri fe18a1d2e4 Merge pull request #85 from iqlusioninc/eliminate-rand-crate
tests: eliminate usage of `rand` crate
2019-12-16 07:41:54 -08:00
Tony Arcieri d4838f2652 tests: eliminate usage of rand crate
Otherwise dependabot will nag us until `num-bigint` updates.
2019-12-16 07:28:21 -08:00
Tony Arcieri 1dff6eca32 Merge pull request #84 from iqlusioninc/dependabot/cargo/log-0.4.10
Bump log from 0.4.8 to 0.4.10
2019-12-16 07:22:17 -08:00
dependabot-preview[bot] 90a44b38cb Bump log from 0.4.8 to 0.4.10
Bumps [log](https://github.com/rust-lang/log) from 0.4.8 to 0.4.10.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.8...0.4.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 15:12:50 +00:00
Tony Arcieri 4cc785fee9 Merge pull request #82 from str4d/chref-enum
Extract ChangeRefAction enum
2019-12-16 07:11:29 -08:00
Jack Grigg 422f89b3e9 Extract ChangeRefAction enum 2019-12-16 06:26:41 -06:00
Jack Grigg 976af45e22 Add missing entries to Cargo.lock 2019-12-16 06:25:48 -06:00
Tony Arcieri ae4e098a41 Merge pull request #80 from str4d/cert-gen
Certificate::generate_self_signed
2019-12-15 10:00:17 -08:00
Jack Grigg 985b1d272c Add a serial number wrapper struct with Into conversions 2019-12-15 17:50:25 +00:00
Jack Grigg 58acfe6330 Simplify issuer and subject stringification 2019-12-15 17:42:47 +00:00
Jack Grigg 02ade49288 tests/integration: Verify signature on generated EC certificate 2019-12-15 17:22:52 +00:00
Jack Grigg 1a95a5f921 Fix PKCS#1 v1.5 signature generation 2019-12-15 17:09:09 +00:00
Jack Grigg 620f2bcc74 tests/integration: Verify signature on generated RSA certificate 2019-12-15 17:09:09 +00:00
Tony Arcieri 0e14110e17 Merge pull request #79 from carl-wallace/develop
move print cert info into new CLI project
2019-12-15 08:25:37 -08:00
Jack Grigg 8ac78cafb8 Certificate::generate_self_signed 2019-12-15 10:59:50 +00:00
Jack Grigg 5e8a014be2 Expose certificate serial and issuer 2019-12-15 10:35:22 +00:00
Jack Grigg d44a32453c Write certificate TLVs into correct offsets 2019-12-15 10:33:01 +00:00
Carl Wallace 220c045dcb move print cert info into new CLI project 2019-12-14 13:27:54 -05:00
Tony Arcieri 6174b62a77 Merge pull request #78 from iqlusioninc/dependabot/cargo/secrecy-0.6.0
Bump secrecy from 0.5.1 to 0.6.0
2019-12-13 06:02:56 -08:00
dependabot-preview[bot] 36408ac658 Bump secrecy from 0.5.1 to 0.6.0
Bumps [secrecy](https://github.com/iqlusioninc/crates) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/secrecy/v0.5.1...secrecy/v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-13 13:45:51 +00:00
Tony Arcieri 16a9a1a2c6 Merge pull request #77 from iqlusioninc/dependabot/cargo/elliptic-curve-0.2.0
Bump elliptic-curve from 0.1.0 to 0.2.0
2019-12-12 06:25:10 -08:00
dependabot-preview[bot] cee7f1cef8 Bump elliptic-curve from 0.1.0 to 0.2.0
Bumps [elliptic-curve](https://github.com/RustCrypto/signatures) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/signatures/releases)
- [Commits](https://github.com/RustCrypto/signatures/compare/elliptic-curve/v0.1.0...elliptic-curve/v0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-12 13:49:12 +00:00
Tony Arcieri cb104f3df6 Merge pull request #76 from iqlusioninc/dependabot/cargo/rsa-0.2.0
Bump rsa from 0.1.4 to 0.2.0
2019-12-11 05:43:08 -08:00
dependabot-preview[bot] ac338cf17a Bump rsa from 0.1.4 to 0.2.0
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.1.4 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/release.toml)
- [Commits](https://github.com/RustCrypto/RSA/compare/0.1.4...0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 13:34:30 +00:00
Tony Arcieri a8ea3ec8b7 Merge pull request #75 from str4d/cert-gen-prep
Preparatory work for certificate generation
2019-12-10 18:57:25 -08:00
Jack Grigg d113c1f4b9 impl<'a> TryFrom<&'a [u8]> for Certificate 2019-12-11 02:44:40 +00:00
Jack Grigg 2eff313064 Fix bug in key::generate and document weirdness
Bug was introduced in #73 when starting offsets were overlooked. Digging
into why they were there led to uncovering the weird not-quite-ASN.1
format that the YubiKey returns generated pubkeys in.
2019-12-11 02:26:23 +00:00
Jack Grigg 41b10d1f23 Convert certificate info into an enum 2019-12-11 02:21:49 +00:00
Jack Grigg 4c2ecea721 Replace GeneratedKey with PublicKeyInfo 2019-12-11 00:31:31 +00:00
Jack Grigg e73607e662 Rename Certificate::new to Certificate::from_bytes 2019-12-11 00:30:39 +00:00
Tony Arcieri 17839da94f Merge pull request #74 from iqlusioninc/cli/reader-name
cli: print reader name as part of `status` command
2019-12-10 09:20:50 -08:00
Tony Arcieri 08897ec7c9 cli: print reader name as part of status command 2019-12-10 08:43:33 -08:00
Tony Arcieri 26c777b6ec Merge pull request #73 from str4d/tlv-extraction
TLV extraction
2019-12-10 08:21:42 -08:00
Jack Grigg 1bf3b13e52 Add missing untested feature gates 2019-12-10 13:31:48 +00:00
Jack Grigg 8385dda201 Check buffer length in set_length 2019-12-10 13:22:21 +00:00
Jack Grigg 363bdc4351 Extract TLV writing into serialization::Tlv 2019-12-10 13:17:01 +00:00
Jack Grigg da828abe3c Extract TLV parsing into serialization::Tlv 2019-12-10 13:14:39 +00:00
Tony Arcieri 339fb69e30 Merge pull request #72 from iqlusioninc/status-command
cli: add `status` command
2019-12-09 19:29:07 -08:00
Tony Arcieri 78d5f33695 cli: add status command
Provides equivalent functionality to `yubico-piv-tool`
2019-12-09 18:00:34 -08:00
Tony Arcieri 283e6fe363 Merge pull request #71 from iqlusioninc/cli/rename-list-to-readers-improve-usage
cli: rename 'list' command to 'readers'; improve usage
2019-12-09 09:51:34 -08:00
Tony Arcieri 55d077dd80 cli: rename 'list' command to 'readers'; improve usage
There are going to be several `list` commands (e.g. `yubikey keys list`)
so this is a confusing name.

If we need more than one `readers` subcommand we can change this to be
`readers list` eventually.

Separately (in what probably should've been its own commit, mea culpa)
this adds slightly better usage.
2019-12-09 09:39:24 -08:00
Tony Arcieri fd77ba6e74 Merge pull request #70 from carl-wallace/develop
add try_from String for SlotIds in support of CLI
2019-12-09 07:55:02 -08:00
Carl Wallace 855f2ecb24 add try_from String for SlotIds in support of CLI 2019-12-08 19:25:27 -05:00
Tony Arcieri 6436d9afcb Merge pull request #69 from iqlusioninc/open-by-serial
yubikey: add `open_by_serial` method
2019-12-08 12:58:40 -08:00
Tony Arcieri 4663cffb96 yubikey: add open_by_serial method
Support for opening a `YubiKey` with a specific serial number.
2019-12-08 12:12:03 -08:00
Tony Arcieri fb7e95e6d1 Merge pull request #68 from iqlusioninc/rename-container-module-to-mscmap
Rename `container` module to `mscmap`
2019-12-08 10:40:01 -08:00
Tony Arcieri 0a100acdd2 Rename container module to mscmap
Better reflects what it actually is.
2019-12-08 10:01:00 -08:00
Tony Arcieri 39a81fc300 Merge pull request #67 from iqlusioninc/eliminate-consts-module
Finish eliminating `consts` module
2019-12-08 09:43:11 -08:00
Tony Arcieri 31efd4e78c Finish eliminating consts module
Either moves constants into their relevant modules, or puts the
remaining ones into `lib.rs`
2019-12-08 09:32:57 -08:00
Tony Arcieri 86b8c6a6db Merge pull request #66 from iqlusioninc/tame-consts
consts: Whittle down to the essentials
2019-12-08 08:51:51 -08:00
Tony Arcieri 104020d518 consts: Whittle down to the essentials
This factors the junk drawer of constants into the relevant files.

There are still a few "global" ones left but they can be addressed in a
followup commit.
2019-12-08 08:39:21 -08:00
Tony Arcieri 4dfac56753 Merge pull request #65 from iqlusioninc/cccid-chuid-tests-and-cleanups
CCCID/CHUID tests and cleanups
2019-12-07 13:33:28 -08:00
Tony Arcieri 9482ae62ab CCCID/CHUID: add basic tests and do some cleanups
- Adds tests for CCCID/CHUID, allowing not found (is that ok?)
- Move constants under their respective modules and remove `YKPIV_`
2019-12-07 13:09:38 -08:00
Tony Arcieri 2587a4ac1e CCCID/CHUID refactoring
- Move generate methods to the appropriate static types
- Remove redundant name prefixes (Rust [RFC#356])

[RFC#356]: https://github.com/rust-lang/rfcs/pull/356
2019-12-07 12:39:52 -08:00
Tony Arcieri 3cf3c0867f Merge pull request #49 from carl-wallace/develop
change ccid handling to target entire CCC object
2019-12-07 12:10:44 -08:00
Tony Arcieri b2f11f5058 Merge pull request #64 from iqlusioninc/config-tests
Test `Config::get`
2019-12-07 12:10:24 -08:00
Tony Arcieri cdecfd92dd Test Config::get
Tests reading configuration from a live device:

    Config { protected_data_available: false, puk_blocked: false, puk_noblock_on_upgrade: false, pin_last_changed: 0, mgm_type: Manual }
2019-12-07 11:47:07 -08:00
Tony Arcieri 509c438330 Merge pull request #63 from iqlusioninc/drop-neo-support
Drop YubiKey NEO support (closes #18)
2019-12-07 11:32:10 -08:00
Tony Arcieri f6915ce5df Drop YubiKey NEO support (closes #18)
YubiKey NEOs are legacy YubiKey devices, most of which contain
unpatchable security vulnerabilities.

They have smaller buffer sizes than YK4 and YK5, which necessitates a
whole bunch of conditional gating and buffer size calculations.

Getting rid of them simplifies this logic and allows us to assume
consistent buffer sizes everywhere.

We never tested on NEOs anyway, and looking at the deleted code it seems
it may have been miscalculating the NEO's buffer size!

If someone *really* wants to support NEOs, it shouldn't be that hard to
restore, but the codebase is definitely cleaner without it.
2019-12-07 11:22:51 -08:00
Tony Arcieri 962089dbf8 Merge pull request #62 from iqlusioninc/keys/move-import-and-attest
Move `import` and `attest` to the `key` module
2019-12-07 10:47:44 -08:00
Tony Arcieri d6cd0130d3 Move sign/decrypt/import/attest to the key module
These are crypto key-related functions and are better factored under
this module.
2019-12-07 10:39:02 -08:00
Tony Arcieri 7d01dba11d Merge pull request #61 from iqlusioninc/test-listing-keys
Test `Key::list`
2019-12-07 10:19:43 -08:00
Tony Arcieri d1d384d304 Test Key::list
Adds a live-against-the-device test which ensures keys can be
successfully listed.
2019-12-07 10:09:56 -08:00
Tony Arcieri cb9d5221b2 Merge pull request #60 from iqlusioninc/test-verify-pin
Test YubiKey::verify_pin (--ignored)
2019-12-07 08:52:09 -08:00
Tony Arcieri c30cf5b83a Test YubiKey::verify_pin (--ignored)
Adds an off-by-default test that the `YubiKey::verify_pin` function
works, and removes it from `untested` gating.
2019-12-07 08:44:12 -08:00
Tony Arcieri 3c88f1be13 Merge pull request #59 from str4d/elliptic-curve
Switch to elliptic-curve crate
2019-12-07 08:03:07 -08:00
Jack Grigg 0551263286 Switch to elliptic-curve crate 2019-12-07 15:47:24 +00:00
Tony Arcieri 63fbc1dcf2 Merge pull request #50 from str4d/key-generation
Key generation prep
2019-12-04 08:03:39 -08:00
Carl Wallace 82c2d08aec Merge remote-tracking branch 'upstream/develop' into develop 2019-12-03 15:12:22 -05:00
Tony Arcieri f25e14c52c Merge pull request #58 from iqlusioninc/readme/fix-license-image
README.md: Fix license image
2019-12-03 11:24:42 -08:00
Tony Arcieri b1e8702059 README.md: Fix license image 2019-12-03 11:13:00 -08:00
Tony Arcieri f4f7041626 Merge pull request #57 from iqlusioninc/dependabot/cargo/zeroize-1.1.0
Bump zeroize from 1.0.0 to 1.1.0
2019-12-03 06:45:13 -08:00
dependabot-preview[bot] d6ad70f7d1 Bump zeroize from 1.0.0 to 1.1.0
Bumps [zeroize](https://github.com/iqlusioninc/crates) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/zeroize/v1.0.0...zeroize/v1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-03 13:51:52 +00:00
Jack Grigg 76c093e68e Minor cleanups 2019-12-03 03:24:10 +00:00
Jack Grigg ada3454d26 Fix bug in MgmKey::decrypt 2019-12-03 03:24:09 +00:00
Jack Grigg 370a90f800 Correctly return StatusWords from transfer_data 2019-12-03 03:24:07 +00:00
Jack Grigg 7bcd8664a4 AlgorithmId::write helper to match policy helpers 2019-12-03 03:24:06 +00:00
Jack Grigg 3a4515d902 Convert PIN and touch policies into enums 2019-12-03 03:23:59 +00:00
Tony Arcieri 7b70ea0f91 Merge pull request #56 from iqlusioninc/cli/fix-readme-badge
cli: fix build badge
2019-12-02 12:31:30 -08:00
Tony Arcieri 9bc28f4f75 cli: fix build badge 2019-12-02 12:18:44 -08:00
Tony Arcieri 140016bbd7 Merge pull request #54 from iqlusioninc/yubikey-cli/v0.0.1
yubikey-cli v0.0.1
2019-12-02 12:08:13 -08:00
Carl Wallace a9e0363d09 remove spurious blank lines flagged by fmt 2019-12-01 18:23:32 -05:00
Carl Wallace bfd728d1ac remove sha2, which was rendered OBE as print cert info was moved to CLI 2019-12-01 18:22:18 -05:00
Carl Wallace a110289910 move print cert info to CLI 2019-12-01 18:20:18 -05:00
Carl Wallace b9d6057d4e address fmt issues 2019-12-01 15:12:05 -05:00
Carl Wallace 2087e53109 add print cert info method in support of status action a la yubico-piv-tool 2019-12-01 14:59:21 -05:00
Carl Wallace 5f5844ccb4 Merge remote-tracking branch 'upstream/develop' into develop 2019-12-01 14:49:41 -05:00
Carl Wallace c8e5c96398 change cccid handling to target entire ccc object (a la yubico-piv-tool status action) 2019-11-30 15:11:10 -05:00
42 changed files with 4299 additions and 3165 deletions
+5
View File
@@ -0,0 +1,5 @@
[advisories]
ignore = [
"RUSTSEC-2020-0071", # time
"RUSTSEC-2020-0159", # chrono
]
+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
time: '13:00'
open-pull-requests-limit: 10
+89
View File
@@ -0,0 +1,89 @@
name: CI
on:
pull_request: {}
push:
branches: main
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo check
test:
strategy:
matrix:
include:
- platform: ubuntu-latest
toolchain: stable
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: stable
deps: true
- platform: macos-latest
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.51.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.51.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.51.0 # MSRV
deps: true
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
- run: ${{ matrix.deps }}
- run: cargo build --all --all-features --release
- run: cargo test --all --all-features --release
rustfmt:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.51.0 # MSRV
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo clippy --all --all-features -- -D warnings
-175
View File
@@ -1,175 +0,0 @@
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
on:
pull_request: {}
push:
branches: develop
name: Rust
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
# Need to install `libpscslite-dev` on Linux
linux:
name: Test Suite
strategy:
matrix:
toolchain:
- 1.39.0
- stable
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
test:
name: Test Suite
strategy:
matrix:
platform:
- macos-latest
- windows-latest
toolchain:
- 1.39.0
- stable
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install rustfmt
run: rustup component add rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Install clippy
run: rustup component add clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all --all-features -- -D warnings
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings
+45
View File
@@ -0,0 +1,45 @@
name: Security Audit
on:
pull_request:
paths: Cargo.lock
push:
branches: main
paths: Cargo.lock
schedule:
- cron: '0 0 * * *'
jobs:
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }}
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny warnings --ignore RUSTSEC-2019-0031 # spin
+165 -27
View File
@@ -4,7 +4,147 @@ 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.0.3] (2019-12-02)
## 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])
- Bump `x509-parser` to v0.9 ([#208])
- Bump elliptic-curve to 0.8. Also requires bumping p256 and p384 ([#208])
- Bump MSRV to 1.46+ ([#208])
- Bump `pbkdf2` dependency to v0.7 ([#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
## yubikey-piv 0.1.0 (2020-10-19)
### Added
- `Certificate::generate_self_signed` ([#80])
- `YubiKey::open_by_serial` ([#69])
- CCCID/CHUID tests and cleanups ([#65])
- Test `Config::get` ([#64])
- Test `Key::list` ([#61])
- Test `YubiKey::verify_pin` ([#60])
### Changed
- Bump `crypto-mac`, `des`, `hmac`, `pbkdf2` ([#177])
- Bump `p256` to v0.5; `p384` to v0.4; MSRV 1.44+ ([#175])
- Refactor key import function ([#128])
- Extract `ChangeRefAction` enum ([#82])
- TLV extraction ([#73])
- Rename `container` to `mscmap` ([#68])
- Finish eliminating `consts` module ([#67])
- Move `sign`/`decrypt`/`import`/`attest` to the `key` module ([#62])
### Fixed
- `pcsc::Error::NoReadersAvailable` -> `Error::NotFound` in `YubiKey::open*` ([#88])
### Removed
- YubiKey NEO support ([#63])
[#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
## yubikey-piv 0.0.3 (2019-12-02)
### Added
- Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45])
@@ -20,20 +160,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])
[0.0.3]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/53
[#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])
@@ -48,17 +187,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])
[0.0.2]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/31
[#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)
- It typechecks, ship it!
## 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
+547 -584
View File
File diff suppressed because it is too large Load Diff
+34 -24
View File
@@ -1,48 +1,58 @@
[package]
name = "yubikey-piv"
version = "0.0.3" # Also update html_root_url in lib.rs when bumping this
name = "yubikey"
version = "0.5.0" # Also update html_root_url in lib.rs when bumping this
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)
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
"""
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
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"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
[workspace]
members = [".", "cli"]
[badges]
maintenance = { status = "experimental" }
[dependencies]
der-parser = "3"
des = "0.3"
ecdsa = "0.1"
getrandom = "0.1"
hmac = "0.7"
chrono = "0.4"
cookie-factory = "0.3"
der-parser = "6"
des = "0.7"
elliptic-curve = "0.10"
hmac = "0.11"
log = "0.4"
nom = "5"
pbkdf2 = "0.3"
nom = "7"
num-bigint-dig = { version = "0.7", features = ["rand"] }
num-traits = "0.2"
num-integer = "0.1"
pbkdf2 = { version = "0.9", default-features = false }
p256 = "0.9"
p384 = "0.8"
pcsc = "2"
rsa = "0.1.4"
secrecy = "0.5"
sha-1 = "0.8"
rand_core = { version = "0.6", features = ["std"] }
rsa = "0.5"
secrecy = "0.8"
sha-1 = "0.9"
sha2 = "0.9"
subtle = "2"
x509-parser = "0.6"
subtle-encoding = "0.5"
uuid = { version = "0.8", features = ["v4"] }
x509 = "0.2"
x509-parser = "0.12"
zeroize = "1"
[dev-dependencies]
env_logger = "0.7"
env_logger = "0.9"
lazy_static = "1"
[features]
untested = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+51 -78
View File
@@ -1,18 +1,18 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/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]
![Apache2/MIT licensed][license-image]
[![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]
[![dependency status][deps-image]][deps-link]
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.
[Documentation][docs-link]
@@ -36,15 +36,21 @@ endorsed by Yubico.
## Minimum Supported Rust Version
- Rust **1.39+**
Rust **1.51** or newer.
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Supported Operating Systems
- Linux
- macOS
- Windows
## Security Warning
@@ -53,40 +59,6 @@ an experimental stage and may still contain high-severity issues.
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.
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:
| | 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 |
| ⚠️ | `container` | [#25] | MS Container Map Records |
| ⚠️ | `key` | [#26] | Crypto key management: list, generate, import |
| ⚠️ | `mgm` | [#26] | Management Key (MGM) support: set, get, derive
| ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots |
Legend:
| | Description |
|----|------------------------------------|
| 🚧 | 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.
## Testing
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
@@ -114,17 +86,17 @@ Application Protocol Data Unit (APDU) messages, use the `trace` log level:
```
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
```
@@ -146,12 +118,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-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
@@ -187,22 +159,23 @@ 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.svg
[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
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
[rustc-image]: https://img.shields.io/badge/rustc-1.51+-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/Rust/badge.svg?branch=develop&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/workflows/CI/badge.svg?branch=main&event=push
[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)
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/
@@ -212,18 +185,18 @@ or conditions.
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey-piv.rs/blob/develop/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
[//]: # (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
+38
View File
@@ -4,5 +4,43 @@ 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.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 ([#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 ([#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.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
+14 -10
View File
@@ -1,21 +1,25 @@
[package]
name = "yubikey-cli"
version = "0.0.1"
version = "0.5.0-pre"
description = """
Command-line interface for performing encryption and signing using RSA and/or
ECC keys stored on YubiKey devices.
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
"""
authors = ["Tony Arcieri <bascule@gmail.com>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
[dependencies]
gumdrop = "0.7"
env_logger = "0.7"
gumdrop = "0.8"
env_logger = "0.9"
lazy_static = "1"
log = "0.4"
sha2 = "0.9"
subtle-encoding = "0.5"
termcolor = "1"
yubikey-piv = { version = "0.0.3", path = ".." }
x509-parser = "0.12"
yubikey = { version = "0.5", path = ".." }
+8 -8
View File
@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/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,15 @@ utility with general-purpose public-key encryption and signing support.
## Minimum Supported Rust Version
- Rust **1.39+**
Rust **1.51** or newer.
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Security Warning
@@ -47,7 +47,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
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
@@ -92,8 +92,8 @@ or conditions.
[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-cli.rs/workflows/Rust/badge.svg?branch=develop&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-cli.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
@@ -108,5 +108,5 @@ or conditions.
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/develop/CODE_OF_CONDUCT.md
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
+2 -2
View File
@@ -9,8 +9,8 @@
)]
use gumdrop::Options;
use yubikey_cli::commands::YubikeyCli;
use yubikey_cli::commands::YubiKeyCli;
fn main() {
YubikeyCli::parse_args_default_or_exit().run();
YubiKeyCli::parse_args_default_or_exit().run();
}
+72 -15
View File
@@ -1,31 +1,69 @@
//! Commands of the CLI application
pub mod list;
pub mod readers;
pub mod status;
use self::list::ListCmd;
use crate::status;
use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal::{self, STDOUT};
use gumdrop::Options;
use std::env;
use std::process::exit;
use termcolor::ColorChoice;
use std::{
env,
io::{self, Write},
process::exit,
};
use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility
#[derive(Debug, Options)]
pub struct YubikeyCli {
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"
)]
pub serial: Option<Serial>,
/// Subcommand to execute.
#[options(command)]
pub command: Option<Commands>,
}
impl YubikeyCli {
impl YubiKeyCli {
/// Print usage information
pub fn print_usage() -> io::Result<()> {
let mut stdout = STDOUT.lock();
stdout.reset()?;
let mut bold = ColorSpec::new();
bold.set_bold(true);
stdout.set_color(&bold)?;
writeln!(
stdout,
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)?;
stdout.reset()?;
writeln!(stdout, "{}", env!("CARGO_PKG_AUTHORS"))?;
writeln!(stdout, "{}", env!("CARGO_PKG_DESCRIPTION").trim())?;
writeln!(stdout)?;
writeln!(stdout, "{}", Commands::usage())?;
Ok(())
}
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
status::set_color_choice(ColorChoice::Auto);
terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
@@ -33,8 +71,22 @@ impl YubikeyCli {
}
match &self.command {
Some(cmd) => cmd.run(),
None => println!("{}", Commands::usage()),
Some(cmd) => cmd.run(self.yubikey_init()),
None => Self::print_usage().unwrap(),
}
}
/// Initialize the YubiKey client driver
fn yubikey_init(&self) -> YubiKey {
match self.serial {
Some(serial) => YubiKey::open_by_serial(serial).unwrap_or_else(|e| {
status_err!("couldn't open YubiKey (serial #{}): {}", serial, e);
exit(1);
}),
None => YubiKey::open().unwrap_or_else(|e| {
status_err!("couldn't open default YubiKey: {}", e);
exit(1);
}),
}
}
}
@@ -50,18 +102,23 @@ pub enum Commands {
#[options(help = "display version information")]
Version(VersionOpts),
/// `list` subcommand
/// `readers` subcommand
#[options(help = "list detected readers")]
List(ListCmd),
Readers(ReadersCmd),
/// `status` subcommand
#[options(help = "show yubikey status")]
Status(StatusCmd),
}
impl Commands {
/// Run the given command
pub fn run(&self) {
pub fn run(&self, yubikey: YubiKey) {
match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(),
Commands::List(list) => list.run(),
Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
}
}
}
-40
View File
@@ -1,40 +0,0 @@
//! List detected readers
use gumdrop::Options;
use std::process::exit;
use yubikey_piv::readers::Readers;
/// The `list` subcommand
#[derive(Debug, Options)]
pub struct ListCmd {}
impl ListCmd {
/// Run the `list` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let mut yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
println!("{}: {} (serial: {})", i + 1, name, serial);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
//! List detected readers
use crate::terminal::STDOUT;
use gumdrop::Options;
use std::{
io::{self, Write},
process::exit,
};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{Context, Serial};
/// The `readers` subcommand
#[derive(Debug, Options)]
pub struct ReadersCmd {}
impl ReadersCmd {
/// Run the `readers` subcommand
pub fn run(&self) {
let mut readers = Context::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
let mut s = STDOUT.lock();
s.reset().unwrap();
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
self.print_reader(&mut s, i + 1, &name, serial).unwrap();
}
}
/// Print a reader
fn print_reader(
&self,
stream: &mut StandardStreamLock<'_>,
index: usize,
name: &str,
serial: Serial,
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>3}:", index)?;
stream.reset()?;
writeln!(stream, " {} (serial: {})", name, serial)?;
stream.flush()?;
Ok(())
}
}
+60
View File
@@ -0,0 +1,60 @@
//! Print device status
use crate::terminal::{print_cert_info, STDOUT};
use gumdrop::Options;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{piv::*, YubiKey};
// String to use for `None`
const NONE_STR: &str = "<none>";
/// The `status` subcommand
#[derive(Debug, Options)]
pub struct StatusCmd {}
impl StatusCmd {
/// Run the `status` subcommand
pub fn run(&self, mut yk: YubiKey) {
let mut s = STDOUT.lock();
s.reset().unwrap();
self.attr(&mut s, "name", yk.name()).unwrap();
self.attr(&mut s, "version", yk.version()).unwrap();
self.attr(&mut s, "serial", yk.serial()).unwrap();
if let Ok(chuid) = yk.chuid() {
self.attr(&mut s, "CHUID", chuid).unwrap();
} else {
self.attr(&mut s, "CHUID", NONE_STR).unwrap();
}
if let Ok(chuid) = yk.cccid() {
self.attr(&mut s, "CCC", chuid).unwrap();
} else {
self.attr(&mut s, "CCC", NONE_STR).unwrap();
}
self.attr(&mut s, "PIN retries", yk.get_pin_retries().unwrap())
.unwrap();
for slot in SLOTS.iter().cloned() {
print_cert_info(&mut yk, slot, &mut s).unwrap();
}
}
/// Print a status attribute
fn attr(
&self,
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(())
}
}
+8 -9
View File
@@ -1,14 +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 status;
pub mod terminal;
pub mod commands;
+84 -7
View File
@@ -1,15 +1,23 @@
//! 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 sha2::{Digest, Sha256};
use std::{
io::{self, Write},
str,
sync::Mutex,
};
use subtle_encoding::hex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Print a success status message (in green if colors are enabled)
#[macro_export]
macro_rules! status_ok {
($status:expr, $msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.justified()
.bold()
.color(termcolor::Color::Green)
@@ -25,7 +33,7 @@ macro_rules! status_ok {
#[macro_export]
macro_rules! status_warn {
($msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Yellow)
.status("warning:")
@@ -40,7 +48,7 @@ macro_rules! status_warn {
#[macro_export]
macro_rules! status_err {
($msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Red)
.status("error:")
@@ -143,7 +151,7 @@ impl Status {
}
/// 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 +171,72 @@ 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 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,
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
Ok(())
}
+6 -10
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 {
@@ -71,7 +71,6 @@ impl APDU {
}
/// Set this APDU's class
#[cfg(feature = "untested")]
pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value;
self
@@ -84,7 +83,6 @@ impl APDU {
}
/// Set both parameters for this APDU
#[cfg(feature = "untested")]
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1;
self.p2 = p2;
@@ -111,7 +109,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);
@@ -131,13 +129,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();
@@ -268,7 +266,6 @@ pub(crate) struct Response {
impl Response {
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
@@ -279,7 +276,6 @@ impl Response {
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u16 {
self.status_words.code()
}
+57 -20
View File
@@ -30,8 +30,20 @@
// (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::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Error, Result, YubiKey};
use rand_core::{OsRng, RngCore};
use std::{
convert::TryInto,
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
/// CCCID offset
const CCC_ID_OFFS: usize = 9;
/// CCC Object ID
const OBJ_CAPABILITY: u32 = 0x005f_c107;
/// Cardholder Capability Container (CCC) Template
///
@@ -48,38 +60,63 @@ const CCC_TMPL: &[u8] = &[
0x00, 0xfe, 0x00,
];
/// Cardholder Capability Container (CCC) Identifier
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CCCID(pub [u8; YKPIV_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 CCCID {
/// Generate a random CCCID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CCCID(id))
impl CardId {
/// CCCID size in bytes
pub const BYTE_SIZE: usize = 14;
/// Generate a random CCC Card ID
pub fn generate() -> Self {
let mut id = [0u8; Self::BYTE_SIZE];
OsRng.fill_bytes(&mut id);
Self(id)
}
}
/// 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;
/// Return CardId component of CCC
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(YKPIV_OBJ_CAPABILITY)?;
let response = txn.fetch_object(OBJ_CAPABILITY)?;
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
}
let mut cccid = [0u8; YKPIV_CCCID_SIZE];
cccid.copy_from_slice(&response[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]);
Ok(CCCID(cccid))
Ok(Self(response[..Self::BYTE_SIZE].try_into().unwrap()))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
/// Set Cardholder Capability Container (CCC) ID
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CCC_TMPL.to_vec();
buf[CCC_ID_OFFS..(CCC_ID_OFFS + self.0.len())].copy_from_slice(&self.0);
buf[0..self.0.len()].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf)
txn.save_object(OBJ_CAPABILITY, &buf)
}
}
impl Display for CccId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
}
}
+417 -138
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,22 +31,27 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
consts::*,
error::Error,
key::{AlgorithmId, SlotId},
consts::CB_OBJ_MAX,
error::{Error, Result},
piv::{sign_data, AlgorithmId, SlotId},
serialization::*,
transaction::Transaction,
yubikey::YubiKey,
Buffer,
};
use ecdsa::{
curve::{CompressedCurvePoint, NistP256, NistP384, UncompressedCurvePoint},
generic_array::GenericArray,
};
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error;
use rsa::{PublicKey, RSAPublicKey};
use num_bigint_dig::BigUint;
use p256::NistP256;
use p384::NistP384;
use rsa::{PublicKeyParts, RsaPublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
use std::fmt;
use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo};
use std::ops::DerefMut;
use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
@@ -55,24 +60,107 @@ 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";
/// An encoded point on the Nist P-256 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP256Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP256>),
const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE;
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP256>),
/// 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)
}
}
/// An encoded point on the Nist P-384 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP384Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP384>),
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP384>),
impl TryFrom<&[u8]> for Serial {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Serial> {
if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(&bytes)))
} else {
Err(Error::ParseError)
}
}
}
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)]
pub enum CertInfo {
/// The certificate is uncompressed.
Uncompressed,
/// The certificate is gzip-compressed.
Gzip,
}
impl TryFrom<u8> for CertInfo {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0x00 => Ok(CertInfo::Uncompressed),
0x01 => Ok(CertInfo::Gzip),
_ => Err(Error::InvalidObject),
}
}
}
impl From<CertInfo> for u8 {
fn from(certinfo: CertInfo) -> u8 {
match certinfo {
CertInfo::Uncompressed => 0x00,
CertInfo::Gzip => 0x01,
}
}
}
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`].
@@ -84,14 +172,14 @@ pub enum PublicKeyInfo {
algorithm: AlgorithmId,
/// Public key
pubkey: RSAPublicKey,
pubkey: RsaPublicKey,
},
/// EC P-256 keys
EcP256(EcP256Point),
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcP384Point),
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
@@ -101,7 +189,7 @@ impl fmt::Debug for PublicKeyInfo {
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self, Error> {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?;
@@ -117,33 +205,19 @@ impl PublicKeyInfo {
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
match read_pki::ec_parameters(&subject_pki.algorithm.parameters)? {
AlgorithmId::EccP256 => match key_bytes.len() {
33 => CompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Compressed),
65 => UncompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Uncompressed),
_ => None,
}
.map(PublicKeyInfo::EcP256)
.ok_or(Error::InvalidObject),
AlgorithmId::EccP384 => match key_bytes.len() {
49 => CompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Compressed),
97 => UncompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Uncompressed),
_ => None,
}
.map(PublicKeyInfo::EcP384)
.ok_or(Error::InvalidObject),
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),
}
}
@@ -161,17 +235,224 @@ impl PublicKeyInfo {
}
}
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> {
Self::from_bytes(bytes.to_vec())
}
}
impl Certificate {
/// Creates a new self-signed certificate for the given key. Writes the resulting
/// certificate to the slot before returning it.
///
/// `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>(
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> {
let serial = serial.into();
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,
};
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)?;
@@ -179,25 +460,25 @@ impl Certificate {
return Err(Error::InvalidObject);
}
Certificate::new(buf)
Certificate::from_bytes(buf)
}
/// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
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, max_size)
write_certificate(&txn, slot, Some(&self.data), certinfo)
}
/// Delete a certificate located at the given slot of the given YubiKey
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, 0, max_size)
write_certificate(&txn, slot, None, CertInfo::Uncompressed)
}
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(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() {
@@ -205,21 +486,36 @@ impl Certificate {
return Err(Error::SizeError);
}
let parsed_cert = match parse_x509_der(&cert) {
let parsed_cert = match parse_x509_certificate(&cert) {
Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject),
};
let subject = format!("{}", parsed_cert.tbs_certificate.subject);
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
}
/// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str {
&self.subject
}
/// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str {
&self.subject
@@ -243,11 +539,10 @@ impl AsRef<[u8]> for Certificate {
}
/// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
let mut len: usize = 0;
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let object_id = slot.object_id();
let mut buf = match txn.fetch_object(object_id) {
let buf = match txn.fetch_object(object_id) {
Ok(b) => b,
Err(_) => {
// TODO(tarcieri): is this really ok?
@@ -255,24 +550,15 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
}
};
if buf.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
// TODO(str4d): Check the rest of the buffer (TAG_CERT_COMPRESS and TAG_CERT_LRC)
if buf[0] == TAG_CERT {
let offset = 1 + get_length(&buf[1..], &mut len);
if len > buf.len() - offset {
Tlv::parse_single(buf, TAG_CERT).or_else(|_| {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
buf.copy_within(offset..offset + len, 0);
buf.truncate(len);
Ok(Zeroizing::new(vec![]))
})
} else {
Ok(buf)
}
Ok(buf)
}
/// Write certificate
@@ -280,12 +566,8 @@ pub(crate) fn write_certificate(
txn: &Transaction<'_>,
slot: SlotId,
data: Option<&[u8]>,
certinfo: u8,
max_size: usize,
) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
certinfo: CertInfo,
) -> Result<()> {
let object_id = slot.object_id();
if data.is_none() {
@@ -294,34 +576,12 @@ pub(crate) fn write_certificate(
let data = data.unwrap();
let mut req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */;
req_len += set_length(&mut buf, data.len());
req_len += data.len();
if req_len < data.len() || req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_CERT;
offset += 1;
offset += set_length(&mut buf[offset..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(&data);
offset += data.len();
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
// write compression info and LRC trailer
buf[offset] = TAG_CERT_COMPRESS;
buf[offset + 1] = 0x01;
buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP {
0x01
} else {
0x00
};
buf[offset + 3] = TAG_CERT_LRC;
buf[offset + 4] = 00;
offset += 5;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
}
@@ -329,15 +589,14 @@ pub(crate) fn write_certificate(
mod read_pki {
use der_parser::{
ber::BerObjectContent,
der::{parse_der_integer, DerObject},
der::{parse_der_integer, parse_der_sequence_defined_g, DerObject},
error::BerError,
*,
};
use nom::{combinator, IResult};
use rsa::{BigUint, RSAPublicKey};
use nom::{combinator, sequence::pair, IResult};
use rsa::{BigUint, RsaPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{error::Error, key::AlgorithmId};
use crate::{piv::AlgorithmId, Error, Result};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
@@ -346,21 +605,18 @@ mod read_pki {
/// 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)
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> {
parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i)
}
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
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 {
combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| {
let n = match modulus.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
let e = match seq[1].content {
let e = match public_exponent.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
@@ -374,7 +630,7 @@ mod read_pki {
_ => return Err(Error::InvalidObject),
};
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
}
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
@@ -385,13 +641,8 @@ mod read_pki {
/// -- specifiedCurve SpecifiedECDomain
/// }
/// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId, Error> {
let curve_oid = match parameters.as_context_specific() {
Ok((_, Some(named_curve))) => {
named_curve.as_oid_val().map_err(|_| Error::InvalidObject)
}
_ => Err(Error::InvalidObject),
}?;
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId> {
let curve_oid = parameters.as_oid_val().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
@@ -400,3 +651,31 @@ mod read_pki {
}
}
}
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)
}
/// 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()),
))
}
}
+56 -38
View File
@@ -30,8 +30,26 @@
// (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::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Error, Result, YubiKey};
use std::{
convert::TryInto,
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
use uuid::Uuid;
/// FASC-N offset
const CHUID_FASCN_OFFS: usize = 2;
/// GUID offset
const CHUID_GUID_OFFS: usize = 29;
/// Expiration offset
const CHUID_EXPIRATION_OFFS: usize = 47;
/// CHUID Object ID
const OBJ_CHUID: u32 = 0x005f_c102;
/// Cardholder Unique Identifier (CHUID) Template
///
@@ -55,66 +73,66 @@ 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 ChuidUuid(pub [u8; YKPIV_CARDID_SIZE]);
pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
/// Cardholder Unique Identifier (CHUID)
#[derive(Copy, Clone)]
pub struct CHUID(pub [u8; YKPIV_CHUID_SIZE]);
impl ChuId {
/// CHUID size in bytes
pub const BYTE_SIZE: usize = 59;
/// 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; YKPIV_FASCN_SIZE], Error> {
let mut fascn = [0u8; YKPIV_FASCN_SIZE];
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + YKPIV_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()
.unwrap()
}
/// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Result<[u8; YKPIV_CARDID_SIZE], Error> {
let mut uuid = [0u8; YKPIV_CARDID_SIZE];
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
Ok(uuid)
pub fn uuid(&self) -> Uuid {
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap()
}
/// Return expiration date component of CHUID
pub fn expiration(&self) -> Result<[u8; YKPIV_EXPIRATION_SIZE], Error> {
let mut expiration = [0u8; YKPIV_EXPIRATION_SIZE];
expiration.copy_from_slice(
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + YKPIV_EXPIRATION_SIZE)],
);
Ok(expiration)
}
/// Generate a random Cardholder Unique Identifier (CHUID)
pub fn generate() -> Result<ChuidUuid, Error> {
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(ChuidUuid(id))
// TODO(tarcieri): parse expiration?
pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
.try_into()
.unwrap()
}
/// 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(YKPIV_OBJ_CHUID)?;
let response = txn.fetch_object(OBJ_CHUID)?;
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
let mut chuid = [0u8; YKPIV_CHUID_SIZE];
chuid.copy_from_slice(&response[0..YKPIV_CHUID_SIZE]);
let retval = CHUID { 0: chuid };
Ok(retval)
Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
}
/// Set Cardholder Unique Identifier (CHUID)
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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(YKPIV_OBJ_CHUID, &buf)
txn.save_object(OBJ_CHUID, &buf)
}
}
impl Display for ChuId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
}
}
+50 -27
View File
@@ -30,44 +30,64 @@
// (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::{consts::*, error::Error, metadata, mgm::MgmType, yubikey::YubiKey};
use crate::{
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},
Result,
};
use log::error;
use std::convert::TryInto;
use std::{
convert::TryInto,
time::{Duration, SystemTime, UNIX_EPOCH},
};
/// Config
#[derive(Copy, Clone)]
const CB_ADMIN_TIMESTAMP: usize = 0x04;
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
/// 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: u32,
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: 0,
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 {
@@ -81,7 +101,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 {
@@ -89,20 +109,25 @@ 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 this is little endian
config.pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
// TODO(tarcieri): double-check endianness is correct
let pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
if pin_last_changed != 0 {
config.pin_last_changed =
Some(UNIX_EPOCH + Duration::from_secs(pin_last_changed as u64));
}
}
}
}
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 {
@@ -110,11 +135,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
+15 -167
View File
@@ -1,172 +1,20 @@
//! Constant values
// TODO(tarcieri): refactor these into enums!
//! Miscellaneous constant values
// 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.
/// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072;
// TODO(tarcieri): document these!
#![allow(missing_docs, non_upper_case_globals)]
/// YubiKey max object size
pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9;
pub const szLOG_SOURCE: &str = "yubikey-piv.rs";
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 const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
pub const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
// 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;
pub const CB_ADMIN_TIMESTAMP: usize = 0x04;
pub const CB_ADMIN_SALT: usize = 16;
pub const CB_ATR_MAX: usize = 33;
pub const CB_BUF_MAX_NEO: usize = 2048;
pub const CB_BUF_MAX_YK4: usize = 3072;
pub const CB_BUF_MAX: usize = CB_BUF_MAX_YK4;
pub const CB_ECC_POINTP256: usize = 65;
pub const CB_ECC_POINTP384: usize = 97;
pub const CB_OBJ_MAX_YK4: usize = CB_BUF_MAX_YK4 - 9;
pub const CB_OBJ_MAX: usize = CB_OBJ_MAX_YK4;
pub const CB_OBJ_MAX_NEO: usize = CB_BUF_MAX_NEO - 9;
pub const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
pub const CB_OBJ_TAG_MAX: usize = (CB_OBJ_TAG_MIN + 2); // 1 byte tag + 3 bytes len
pub const CB_PAGE: usize = 4096;
pub const CB_PIN_MAX: usize = 8;
pub const CCC_ID_OFFS: usize = 9;
pub const CHUID_FASCN_OFFS: usize = 2;
pub const CHUID_GUID_OFFS: usize = 29;
pub const CHUID_EXPIRATION_OFFS: usize = 47;
pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
pub const CONTAINER_NAME_LEN: usize = 40;
pub const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; // 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
pub const DES_TYPE_3DES: u8 = 1;
pub const DES_LEN_DES: usize = 8;
pub const DES_LEN_3DES: usize = DES_LEN_DES * 3;
// device types
pub const DEVTYPE_UNKNOWN: u32 = 0x0000_0000;
pub const DEVTYPE_NEO: u32 = 0x4E45_0000; //"NE"
pub const DEVTYPE_YK: u32 = 0x594B_0000; //"YK"
pub const DEVTYPE_NEOr3: u32 = (DEVTYPE_NEO | 0x0000_7233); //"r3"
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
pub const DEVYTPE_YK5: u32 = (DEVTYPE_YK | 0x0000_0035); // "5"
pub const ITER_MGM_PBKDF2: usize = 10000;
pub const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
// sw is status words, see NIST special publication 800-73-4, section 5.6
pub const SW_SUCCESS: i32 = 0x9000;
pub const SW_ERR_SECURITY_STATUS: i32 = 0x6982;
pub const SW_ERR_AUTH_BLOCKED: i32 = 0x6983;
pub const SW_ERR_INCORRECT_PARAM: i32 = 0x6a80;
// this is a custom sw for yubikey
pub const SW_ERR_INCORRECT_SLOT: i32 = 0x6b00;
pub const SW_ERR_NOT_SUPPORTED: i32 = 0x6d00;
pub const TAG_CERT: u8 = 0x70;
pub const TAG_CERT_COMPRESS: u8 = 0x71;
pub const TAG_CERT_LRC: u8 = 0xFE;
pub const TAG_ADMIN: u8 = 0x80;
pub const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub const TAG_ADMIN_SALT: u8 = 0x82;
pub const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub const TAG_PROTECTED: u8 = 0x88;
pub const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub const TAG_PROTECTED_MGM: u8 = 0x89;
pub const TAG_MSCMAP: u8 = 0x81;
pub const TAG_MSROOTS_END: u8 = 0x82;
pub const TAG_MSROOTS_MID: u8 = 0x83;
pub const TAG_RSA_MODULUS: u8 = 0x81;
pub const TAG_RSA_EXP: u8 = 0x82;
pub const TAG_ECC_POINT: u8 = 0x86;
pub const YKPIV_ALGO_TAG: u8 = 0x80;
pub const YKPIV_ALGO_3DES: u8 = 0x03;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
pub const YKPIV_CHUID_SIZE: usize = 59;
pub const YKPIV_CARDID_SIZE: usize = 16;
pub const YKPIV_FASCN_SIZE: usize = 25;
pub const YKPIV_EXPIRATION_SIZE: usize = 8;
pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103;
pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106;
pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108;
pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109;
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
// Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10;
pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11;
pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
pub const YKPIV_PINPOLICY_ONCE: u8 = 2;
pub const YKPIV_PINPOLICY_ALWAYS: u8 = 3;
pub const YKPIV_TOUCHPOLICY_TAG: u8 = 0xab;
pub const YKPIV_TOUCHPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_TOUCHPOLICY_NEVER: u8 = 1;
pub const YKPIV_TOUCHPOLICY_ALWAYS: u8 = 2;
pub const YKPIV_TOUCHPOLICY_CACHED: u8 = 3;
// Protected tags
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
+72 -70
View File
@@ -32,62 +32,66 @@
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,
/// Argument error
ArgumentError,
/// Authentication error
AuthenticationError,
/// 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,
}
impl Error {
@@ -95,46 +99,46 @@ 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::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 {
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::AppletError => "applet error",
Error::ArgumentError => "argument error",
Error::RangeError => "range error",
Error::AuthenticationError => "authentication error",
Error::GenericError => "generic error",
Error::InvalidObject => "invalid object",
Error::KeyError => "key error",
Error::MemoryError => "memory error",
Error::NotSupported => "not supported",
Error::NotFound => "not found",
Error::ParseError => "parse error",
Error::PcscError { .. } => "PC/SC error",
Error::PinLocked => "PIN locked",
Error::RangeError => "range error",
Error::SizeError => "size error",
Error::WrongPin { .. } => "wrong pin",
}
}
}
@@ -154,10 +158,8 @@ 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,
}
}
-581
View File
@@ -1,581 +0,0 @@
//! PIV cryptographic keys stored in a YubiKey.
//!
//! Supported algorithms:
//!
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
// 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.
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate},
consts::*,
error::Error,
serialization::*,
settings,
yubikey::YubiKey,
Buffer, ObjectId,
};
use log::{debug, error, warn};
use std::convert::TryFrom;
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SlotId {
/// This certificate and its associated private key is used to authenticate the card
/// and the cardholder. This slot is used for things like system login. The end user
/// PIN is required to perform any private key operations. Once the PIN has been
/// provided successfully, multiple private key operations may be performed without
/// additional cardholder consent.
Authentication,
/// This certificate and its associated private key is used for digital signatures for
/// the purpose of document signing, or signing files and executables. The end user
/// PIN is required to perform any private key operations. The PIN must be submitted
/// every time immediately before a sign operation, to ensure cardholder participation
/// for every digital signature generated.
Signature,
/// This certificate and its associated private key is used for encryption for the
/// purpose of confidentiality. This slot is used for things like encrypting e-mails
/// or files. The end user PIN is required to perform any private key operations. Once
/// the PIN has been provided successfully, multiple private key operations may be
/// performed without additional cardholder consent.
KeyManagement,
/// This certificate and its associated private key is used to support additional
/// physical access applications, such as providing physical access to buildings via
/// PIV-enabled door locks. The end user PIN is NOT required to perform private key
/// operations for this slot.
CardAuthentication,
/// These slots are only available on the YubiKey 4 & 5. They are meant for previously
/// used Key Management keys to be able to decrypt earlier encrypted documents or
/// emails. In the YubiKey 4 & 5 all 20 of them are fully available for use.
Retired(RetiredSlotId),
/// This slot is only available on YubiKey version 4.3 and newer. It is only used for
/// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten.
Attestation,
}
impl TryFrom<u8> for SlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x9a => Ok(SlotId::Authentication),
0x9c => Ok(SlotId::Signature),
0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication),
0xf9 => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(value).map(SlotId::Retired),
}
}
}
impl From<SlotId> for u8 {
fn from(slot: SlotId) -> u8 {
match slot {
SlotId::Authentication => 0x9a,
SlotId::Signature => 0x9c,
SlotId::KeyManagement => 0x9d,
SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9,
}
}
}
impl SlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`SlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
SlotId::Authentication => 0x005f_c105,
SlotId::Signature => 0x005f_c10a,
SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(),
SlotId::Attestation => 0x005f_ff01,
}
}
}
/// Retired slot IDs.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RetiredSlotId {
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
R16,
R17,
R18,
R19,
R20,
}
impl TryFrom<u8> for RetiredSlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x82 => Ok(RetiredSlotId::R1),
0x83 => Ok(RetiredSlotId::R2),
0x84 => Ok(RetiredSlotId::R3),
0x85 => Ok(RetiredSlotId::R4),
0x86 => Ok(RetiredSlotId::R5),
0x87 => Ok(RetiredSlotId::R6),
0x88 => Ok(RetiredSlotId::R7),
0x89 => Ok(RetiredSlotId::R8),
0x8a => Ok(RetiredSlotId::R9),
0x8b => Ok(RetiredSlotId::R10),
0x8c => Ok(RetiredSlotId::R11),
0x8d => Ok(RetiredSlotId::R12),
0x8e => Ok(RetiredSlotId::R13),
0x8f => Ok(RetiredSlotId::R14),
0x90 => Ok(RetiredSlotId::R15),
0x91 => Ok(RetiredSlotId::R16),
0x92 => Ok(RetiredSlotId::R17),
0x93 => Ok(RetiredSlotId::R18),
0x94 => Ok(RetiredSlotId::R19),
0x95 => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl From<RetiredSlotId> for u8 {
fn from(slot: RetiredSlotId) -> u8 {
match slot {
RetiredSlotId::R1 => 0x82,
RetiredSlotId::R2 => 0x83,
RetiredSlotId::R3 => 0x84,
RetiredSlotId::R4 => 0x85,
RetiredSlotId::R5 => 0x86,
RetiredSlotId::R6 => 0x87,
RetiredSlotId::R7 => 0x88,
RetiredSlotId::R8 => 0x89,
RetiredSlotId::R9 => 0x8a,
RetiredSlotId::R10 => 0x8b,
RetiredSlotId::R11 => 0x8c,
RetiredSlotId::R12 => 0x8d,
RetiredSlotId::R13 => 0x8e,
RetiredSlotId::R14 => 0x8f,
RetiredSlotId::R15 => 0x90,
RetiredSlotId::R16 => 0x91,
RetiredSlotId::R17 => 0x92,
RetiredSlotId::R18 => 0x93,
RetiredSlotId::R19 => 0x94,
RetiredSlotId::R20 => 0x95,
}
}
}
impl RetiredSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
RetiredSlotId::R1 => 0x005f_c10d,
RetiredSlotId::R2 => 0x005f_c10e,
RetiredSlotId::R3 => 0x005f_c10f,
RetiredSlotId::R4 => 0x005f_c110,
RetiredSlotId::R5 => 0x005f_c111,
RetiredSlotId::R6 => 0x005f_c112,
RetiredSlotId::R7 => 0x005f_c113,
RetiredSlotId::R8 => 0x005f_c114,
RetiredSlotId::R9 => 0x005f_c115,
RetiredSlotId::R10 => 0x005f_c116,
RetiredSlotId::R11 => 0x005f_c117,
RetiredSlotId::R12 => 0x005f_c118,
RetiredSlotId::R13 => 0x005f_c119,
RetiredSlotId::R14 => 0x005f_c11a,
RetiredSlotId::R15 => 0x005f_c11b,
RetiredSlotId::R16 => 0x005f_c11c,
RetiredSlotId::R17 => 0x005f_c11d,
RetiredSlotId::R18 => 0x005f_c11e,
RetiredSlotId::R19 => 0x005f_c11f,
RetiredSlotId::R20 => 0x005f_c120,
}
}
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [SlotId; 24] = [
SlotId::Authentication,
SlotId::Signature,
SlotId::KeyManagement,
SlotId::Retired(RetiredSlotId::R1),
SlotId::Retired(RetiredSlotId::R2),
SlotId::Retired(RetiredSlotId::R3),
SlotId::Retired(RetiredSlotId::R4),
SlotId::Retired(RetiredSlotId::R5),
SlotId::Retired(RetiredSlotId::R6),
SlotId::Retired(RetiredSlotId::R7),
SlotId::Retired(RetiredSlotId::R8),
SlotId::Retired(RetiredSlotId::R9),
SlotId::Retired(RetiredSlotId::R10),
SlotId::Retired(RetiredSlotId::R11),
SlotId::Retired(RetiredSlotId::R12),
SlotId::Retired(RetiredSlotId::R13),
SlotId::Retired(RetiredSlotId::R14),
SlotId::Retired(RetiredSlotId::R15),
SlotId::Retired(RetiredSlotId::R16),
SlotId::Retired(RetiredSlotId::R17),
SlotId::Retired(RetiredSlotId::R18),
SlotId::Retired(RetiredSlotId::R19),
SlotId::Retired(RetiredSlotId::R20),
SlotId::CardAuthentication,
];
/// Algorithm identifiers
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlgorithmId {
/// 1024-bit RSA.
Rsa1024,
/// 2048-bit RSA.
Rsa2048,
/// ECDSA with the NIST P256 curve.
EccP256,
/// ECDSA with the NIST P384 curve.
EccP384,
}
impl TryFrom<u8> for AlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
impl From<AlgorithmId> for u8 {
fn from(id: AlgorithmId) -> u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
}
}
}
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
/// Card slot
slot: SlotId,
/// Cert
cert: Certificate,
}
impl Key {
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut keys = vec![];
let txn = yubikey.begin_transaction()?;
for slot in SLOTS.iter().cloned() {
let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
continue;
}
};
if !buf.is_empty() {
let cert = Certificate::new(buf)?;
keys.push(Key { slot, cert });
}
}
Ok(keys)
}
/// Get the slot ID for this key
pub fn slot(&self) -> SlotId {
self.slot
}
/// Get the certificate for this key
pub fn certificate(&self) -> &Certificate {
&self.cert
}
}
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
/// Information about a generated key
// TODO(tarcieri): this could use some more work
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GeneratedKey {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Modulus
modulus: Vec<u8>,
/// Exponent
exp: Vec<u8>,
},
/// ECC keys
Ecc {
/// ECC algorithm
algorithm: AlgorithmId,
/// Public curve point (i.e. public key)
point: Vec<u8>,
},
}
impl GeneratedKey {
/// Get the algorithm
pub fn algorithm(&self) -> AlgorithmId {
*match self {
GeneratedKey::Rsa { algorithm, .. } => algorithm,
GeneratedKey::Ecc { algorithm, .. } => algorithm,
}
}
}
/// Generate key
#[allow(clippy::cognitive_complexity)]
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: u8,
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
let setting_roca: settings::BoolValue;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
if yubikey.device_model() == DEVTYPE_YK4
&& yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
setting_roca = settings::BoolValue::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
settings::Source::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
settings::Source::Admin => {
if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN
} else {
SZ_ROCA_BLOCK_ADMIN
}
}
_ => SZ_ROCA_DEFAULT,
};
warn!(
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
(ROCA) and should be replaced. On-chip key generation {} See \
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
for additional information on device replacement and mitigation assistance",
yubikey.serial, psz_msg
);
if !setting_roca.value {
return Err(Error::NotSupported);
}
}
}
_ => (),
}
let txn = yubikey.begin_transaction()?;
templ[3] = slot.into();
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm.into(),
]);
if in_data[4] == 0 {
error!("unexpected algorithm");
return Err(Error::AlgorithmError);
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]);
}
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
if !response.is_success() {
let err_msg = "failed to generate new key";
match response.status_words() {
StatusWords::IncorrectSlotError => {
error!("{} (incorrect slot)", err_msg);
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
error!("{} (pin policy not supported?)", err_msg);
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
error!("{} (touch policy not supported?)", err_msg);
} else {
error!("{} (algorithm not supported?)", err_msg);
}
return Err(Error::AlgorithmError);
}
StatusWords::SecurityStatusError => {
error!("{} (not authenticated)", err_msg);
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:x})", err_msg, other.code());
return Err(Error::GenericError);
}
}
}
let data = Buffer::new(response.data().into());
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let mut offset = 5;
let mut len = 0;
if data[offset] != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let modulus = data[offset..(offset + len)].to_vec();
offset += len;
if data[offset] != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let exp = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Rsa {
algorithm,
modulus,
exp,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let mut offset = 3;
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
if data[offset] != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
offset += 1;
// the curve point should always be determined by the curve
let len_byte = data[offset];
offset += 1;
if len_byte as usize != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point })
}
}
}
+73 -66
View File
@@ -1,10 +1,14 @@
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC])
//! **yubikey.rs**: pure Rust cross-platform host-side driver for [YubiKey]
//! devices from [Yubico] 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*
//! # 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.
@@ -12,38 +16,43 @@
//! 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
//! # Minimum Supported Rust Version
//! Rust **1.51** or newer.
//!
//! Rust 1.39+
//!
//! ## Supported YubiKeys
//!
//! - [YubiKey NEO] series
//! # Supported YubiKeys
//! - [YubiKey 4] series
//! - [YubiKey 5] series
//!
//! NOTE: Nano and USB-C variants of the above are also supported
//! NOTE: Nano and USB-C variants of the above are also supported.
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
//!
//! ## Supported Algorithms
//! # Supported Operating Systems
//! - Linux
//! - macOS
//! - Windows
//!
//! # Supported Algorithms
//! - **Authentication**: `3DES`
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//!
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
//!
//! ## Status
//! # Status
//! Functionality which has been successfully tested is available by default.
//!
//! 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.
//! Any functionality which is gated on the `untested` feature has not been
//! properly tested and is not known to function correctly.
//!
//! Please see the [project's README.md for a complete status][status].
//!
//! ## History
//! Please see the [`untested` functionality tracking issue] for current status.
//! We would appreciate any help testing this functionality and removing the
//! `untested` gating as well as writing more automated tests.
//!
//! # History
//! This library is a Rust translation of the [yubico-piv-tool] utility by
//! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode], and then subsequently heavily
@@ -53,25 +62,23 @@
//! the YubiKey implementation of PIV works in general, see the
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide].
//!
//! ## Security Warning
//!
//! # 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
//!
//! # 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
//! # License
//! **yubikey.rs** is a fork of and originally a mechanical translation from
//! Yubico's [yubico-piv-tool], a C library/CLI program.
//!
//! **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.
//! 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
@@ -83,12 +90,12 @@
//! [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/tarcieri/yubikey-piv.rs#status
//! [`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
//! [cc-web]: https://contributor-covenant.org/
//! [cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/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
// Adapted from yubico-piv-tool:
@@ -121,56 +128,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.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.3"
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png",
html_root_url = "https://docs.rs/yubikey/0.5.0"
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_qualifications
)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
mod apdu;
#[cfg(feature = "untested")]
pub mod cccid;
#[cfg(feature = "untested")]
mod cccid;
pub mod certificate;
#[cfg(feature = "untested")]
pub mod chuid;
#[cfg(feature = "untested")]
pub mod config;
pub mod consts;
#[cfg(feature = "untested")]
pub mod container;
pub mod error;
#[cfg(feature = "untested")]
pub mod key;
#[cfg(feature = "untested")]
mod chuid;
mod config;
mod consts;
mod error;
mod metadata;
mod mgm;
#[cfg(feature = "untested")]
pub mod mgm;
#[cfg(feature = "untested")]
pub mod msroots;
pub mod readers;
mod mscmap;
#[cfg(feature = "untested")]
mod msroots;
pub mod piv;
mod policy;
pub mod reader;
mod serialization;
#[cfg(feature = "untested")]
pub mod settings;
mod setting;
mod transaction;
pub mod yubikey;
mod yubikey;
pub use self::{readers::Readers, yubikey::YubiKey};
pub use crate::{
cccid::{CardId, CccId},
certificate::Certificate,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
setting::{Setting, SettingSource},
yubikey::{CachedPin, Serial, Version, YubiKey},
};
#[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey};
pub use crate::{mscmap::MsContainer, msroots::MsRoots};
/// Object identifiers
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>>;
pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
+184 -165
View File
@@ -30,199 +30,194 @@
// (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::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
use std::marker::PhantomData;
use zeroize::Zeroizing;
/// Get metadata item
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
let mut cb_temp: usize = 0;
let mut offset = 0;
use crate::{serialization::*, transaction::Transaction, Buffer, Error, Result};
while offset < data.len() {
let tag_temp = data[offset];
offset += 1;
#[cfg(feature = "untested")]
use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
if !has_valid_length(&data[offset..], data.len() - 1) {
return Err(Error::SizeError);
#[cfg(feature = "untested")]
use std::iter;
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::default(),
}
offset += get_length(&data[offset..], &mut cb_temp);
if tag_temp == tag {
// found tag
break;
}
offset += cb_temp;
}
if offset < data.len() {
Ok(&data[offset..offset + cb_temp])
} else {
Err(Error::GenericError)
}
}
/// Set metadata item
pub(crate) fn set_item(
data: &mut [u8],
pcb_data: &mut usize,
cb_data_max: usize,
tag: u8,
p_item: &[u8],
) -> Result<(), Error> {
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];
offset += 1;
cb_len = get_length(&data[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
break;
}
offset += cb_temp;
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::default(),
})
}
if tag_temp != tag {
if cb_item == 0 {
// 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
offset = *pcb_data;
cb_len = get_length_size(cb_item);
// If length would cause buffer overflow, return error
if (*pcb_data + cb_len + cb_item) > cb_data_max {
/// Write metadata
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
data[offset] = tag;
offset += 1;
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
*pcb_data += 1 + cb_len + cb_item;
return Ok(());
}
// Found tag
// Check length, if it matches, overwrite
if cb_temp == cb_item {
data[offset..offset + cb_item].copy_from_slice(p_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 {
// For tag, if deleting
-1
if self.inner.is_empty() {
return Self::delete(txn);
}
// 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 {
return Err(Error::GenericError);
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])
}
// Move remaining data
data.copy_within(
next_offset..*pcb_data,
(next_offset as isize + cb_moved) as usize,
);
*pcb_data = (*pcb_data as isize + cb_moved) as usize;
// Re-encode item and insert
if cb_item != 0 {
offset -= cb_len;
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
/// Delete metadata
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[])
}
Ok(())
}
/// Get metadata item
pub(crate) fn get_item(&self, tag: u8) -> Result<&[u8]> {
let mut data = &self.inner[..];
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
while !data.is_empty() {
let (remaining, tlv) = Tlv::parse(data)?;
data = remaining;
let mut data = txn.fetch_object(obj_id)?;
if tlv.tag == tag {
// found tag
return Ok(tlv.value);
}
}
if data.len() < CB_OBJ_TAG_MIN {
return Err(Error::GenericError);
Err(Error::GenericError)
}
if tag != data[0] {
return Err(Error::GenericError);
/// Set metadata item
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let mut offset = 0;
while offset < self.inner.len() {
tag_temp = self.inner[offset];
offset += 1;
cb_len = get_length(&self.inner[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
break;
}
offset += cb_temp;
}
if tag_temp != tag {
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
assert_eq!(offset, self.inner.len());
self.inner
.extend(iter::repeat(0).take(1 + get_length_size(item.len()) + item.len()));
Tlv::write(&mut self.inner[offset..], tag, item)?;
return Ok(());
}
// Found tag
// Check length, if it matches, overwrite
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 = (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 (self.inner.len() as isize + cb_moved) as usize > CB_OBJ_MAX {
return Err(Error::GenericError);
}
// Move remaining data
let orig_len = self.inner.len();
if cb_moved > 0 {
self.inner.extend(iter::repeat(0).take(cb_moved as usize));
}
self.inner.copy_within(
next_offset..orig_len,
(next_offset as isize + cb_moved) as usize,
);
self.inner
.resize((orig_len as isize + cb_moved) as usize, 0);
// Re-encode item and insert
if !item.is_empty() {
offset -= cb_len;
offset += set_length(&mut self.inner[offset..], item.len())?;
self.inner[offset..offset + item.len()].copy_from_slice(item);
}
Ok(())
}
let mut pcb_data = 0;
let offset = 1 + get_length(&data[1..], &mut pcb_data);
if pcb_data > data.len() - offset {
return Err(Error::GenericError);
}
data.copy_within(offset..offset + pcb_data, 0);
data.truncate(pcb_data);
Ok(data)
}
/// Write metadata
pub(crate) fn write(
txn: &Transaction<'_>,
tag: u8,
data: &[u8],
max_size: usize,
) -> Result<(), Error> {
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if data.len() > max_size - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
if data.is_empty() {
// Deleting metadata
return txn.save_object(obj_id, &[]);
}
buf[0] = tag;
let mut offset = set_length(&mut buf[1..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(data);
offset += data.len();
txn.save_object(obj_id, &buf[..offset])
}
/// 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
@@ -232,3 +227,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
}
}
}
+143 -81
View File
@@ -30,27 +30,42 @@
// (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::{consts::*, error::Error, metadata, yubikey::YubiKey};
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use getrandom::getrandom;
use hmac::Hmac;
use crate::{Error, Result};
use log::error;
use pbkdf2::pbkdf2;
use sha1::Sha1;
use rand_core::{OsRng, RngCore};
use std::convert::{TryFrom, TryInto};
use zeroize::{Zeroize, Zeroizing};
/// Default MGM key configured on all YubiKeys
const DEFAULT_MGM_KEY: [u8; DES_LEN_3DES] = [
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
];
#[cfg(feature = "untested")]
use crate::{
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_PROTECTED_MGM},
metadata::{AdminData, ProtectedData},
yubikey::YubiKey,
};
use des::{
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, NewBlockCipher},
TdesEde3,
};
#[cfg(feature = "untested")]
use {hmac::Hmac, pbkdf2::pbkdf2, sha1::Sha1};
/// Management Key (MGM) key types (manual/derived/protected)
pub(crate) const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
#[cfg(feature = "untested")]
const CB_ADMIN_SALT: usize = 16;
/// Size of a DES key
const DES_LEN_DES: usize = 8;
/// Size of a 3DES key
pub(crate) const DES_LEN_3DES: usize = DES_LEN_DES * 3;
/// Number of PBKDF2 iterations to use when deriving from a password
#[cfg(feature = "untested")]
const ITER_MGM_PBKDF2: u32 = 10000;
/// Management Key (MGM) key types (manual/derived/protected).
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(non_camel_case_types)]
pub enum MgmType {
/// Manual
Manual = 0,
@@ -73,27 +88,23 @@ pub struct MgmKey([u8; DES_LEN_3DES]);
impl MgmKey {
/// Generate a random MGM key
pub fn generate() -> Result<Self, Error> {
pub fn generate() -> Self {
let mut key_bytes = [0u8; DES_LEN_3DES];
if getrandom(&mut key_bytes).is_err() {
return Err(Error::RandomnessError);
}
MgmKey::new(key_bytes)
OsRng.fill_bytes(&mut key_bytes);
Self(key_bytes)
}
/// Create an MGM key from byte slice.
///
/// Returns an error if the slice is the wrong size or the key is weak.
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
bytes.as_ref().try_into()
}
/// Create an MGM key from the given byte array.
///
/// Returns an error if the key is weak.
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self, Error> {
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self> {
if is_weak_key(&key_bytes) {
error!(
"blacklisting key '{:?}' since it's weak (with odd parity)",
@@ -103,16 +114,18 @@ impl MgmKey {
return Err(Error::KeyError);
}
Ok(MgmKey(key_bytes))
Ok(Self(key_bytes))
}
/// Get derived management key (MGM)
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self, Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
// recover management key
let data = metadata::read(&txn, TAG_ADMIN)?;
let salt = metadata::get_item(&data, TAG_ADMIN_SALT)?;
let admin_data = AdminData::read(&txn)?;
let salt = admin_data.get_item(TAG_ADMIN_SALT)?;
if salt.len() != CB_ADMIN_SALT {
error!(
@@ -126,20 +139,21 @@ impl MgmKey {
let mut mgm = [0u8; DES_LEN_3DES];
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm);
MgmKey::from_bytes(mgm)
}
/// Get protected management key (MGM)
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self, Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let data = metadata::read(&txn, TAG_PROTECTED).map_err(|e| {
let protected_data = ProtectedData::read(&txn).map_err(|e| {
error!("could not read protected data (err: {:?})", e);
e
})?;
let item = metadata::get_item(&data, TAG_PROTECTED_MGM).map_err(|e| {
let item = protected_data.get_item(TAG_PROTECTED_MGM).map_err(|e| {
error!("could not read protected MGM from metadata (err: {:?})", e);
e
})?;
@@ -157,20 +171,87 @@ impl MgmKey {
MgmKey::from_bytes(item)
}
/// Set the management key (MGM)
pub fn set(&self, yubikey: &mut YubiKey, touch: Option<u8>) -> Result<(), Error> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(&self, touch)
/// Resets the management key for the given YubiKey to the default value.
///
/// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_default(yubikey: &mut YubiKey) -> Result<()> {
MgmKey::default().set_manual(yubikey, false)
}
/// Set protected management key (MGM)
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = Zeroizing::new(vec![0u8; CB_BUF_MAX]);
let max_size = yubikey.obj_size_max();
/// Configures the given YubiKey to use this management key.
///
/// The management key must be stored by the user, and provided when performing key
/// management operations.
///
/// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, None).map_err(|e| {
txn.set_mgm_key(&self, require_touch).map_err(|e| {
// Log a warning, since the device mgm key is corrupt or we're in a state
// where we can't set the mgm key.
error!("could not set new derived mgm key, err = {}", e);
e
})?;
// After this point, we've set the mgm key, so the function should succeed,
// regardless of being able to set the metadata.
if let Ok(mut admin_data) = AdminData::read(&txn) {
// Clear the protected mgm key bit.
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
let mut flags_1 = [0u8; 1];
if item.len() == flags_1.len() {
flags_1.copy_from_slice(item);
flags_1[0] &= !ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
error!("could not set admin flags item, err = {}", e);
}
} else {
error!(
"admin data flags are an incorrect size: {} (expected {})",
item.len(),
flags_1.len()
);
}
}
// Remove any existing salt for a derived mgm key.
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
error!("could not unset derived mgm salt (err = {})", e)
}
if let Err(e) = admin_data.write(&txn) {
error!("could not write admin data, err = {}", e);
}
}
// Clear any prior mgm key from protected data.
if let Ok(mut protected_data) = ProtectedData::read(&txn) {
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, &[]) {
error!("could not clear protected mgm item, err = {:?}", e);
} else if let Err(e) = protected_data.write(&txn) {
error!("could not write protected data, err = {:?}", e);
}
}
Ok(())
}
/// Configures the given YubiKey to use this as a PIN-protected management key.
///
/// This enables key management operations to be performed with access to the PIN.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, false).map_err(|e| {
// log a warning, since the device mgm key is corrupt or we're in
// a state where we can't set the mgm key
error!("could not set new derived mgm key, err = {}", e);
@@ -180,39 +261,25 @@ impl MgmKey {
// after this point, we've set the mgm key, so the function should
// succeed, regardless of being able to set the metadata
// set the new mgm key in protected data
let buffer = match metadata::read(&txn, TAG_PROTECTED) {
Ok(b) => b,
Err(_) => {
// set current metadata blob size to zero, we'll add to the blank blob
Zeroizing::new(vec![])
}
};
let mut cb_data = buffer.len();
data[..cb_data].copy_from_slice(&buffer);
// Fetch the current protected data, or start a blank metadata blob.
let mut protected_data = ProtectedData::read(&txn).unwrap_or_default();
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_PROTECTED_MGM,
self.as_ref(),
) {
// Set the new mgm key in protected data.
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, self.as_ref()) {
error!("could not set protected mgm item, err = {:?}", e);
} else {
metadata::write(&txn, TAG_PROTECTED, &data, max_size).map_err(|e| {
protected_data.write(&txn).map_err(|e| {
error!("could not write protected data, err = {:?}", e);
e
})?;
}
// set the protected mgm flag in admin data
cb_data = data.len();
let mut flags_1 = [0u8; 1];
if let Ok(buffer) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&buffer, TAG_ADMIN_FLAGS_1) {
let mut admin_data = if let Ok(mut admin_data) = AdminData::read(&txn) {
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags_1.len() {
flags_1.copy_from_slice(item);
} else {
@@ -228,26 +295,20 @@ impl MgmKey {
}
// remove any existing salt
if let Err(e) =
metadata::set_item(&mut data, &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, &[])
{
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
error!("could not unset derived mgm salt (err = {})", e)
}
admin_data
} else {
cb_data = 0;
}
AdminData::default()
};
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags_1,
) {
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
error!("could not set admin flags item, err = {}", e);
} else if let Err(e) = metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size) {
} else if let Err(e) = admin_data.write(&txn) {
error!("could not write admin data, err = {}", e);
}
@@ -255,7 +316,6 @@ impl MgmKey {
}
/// Encrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
@@ -264,11 +324,10 @@ impl MgmKey {
}
/// Decrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output));
.decrypt_block(GenericArray::from_mut_slice(&mut output));
output
}
}
@@ -279,9 +338,12 @@ impl AsRef<[u8; DES_LEN_3DES]> for MgmKey {
}
}
/// Default MGM key configured on all YubiKeys
impl Default for MgmKey {
fn default() -> Self {
MgmKey(DEFAULT_MGM_KEY)
MgmKey([
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
])
}
}
@@ -294,7 +356,7 @@ impl Drop for MgmKey {
impl<'a> TryFrom<&'a [u8]> for MgmKey {
type Error = Error;
fn try_from(key_bytes: &'a [u8]) -> Result<Self, Error> {
fn try_from(key_bytes: &'a [u8]) -> Result<Self> {
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
}
}
+74 -103
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,121 +30,116 @@
// (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::{consts::*, error::Error, key::SlotId, serialization::*, yubikey::YubiKey};
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
use log::error;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
use std::convert::{TryFrom, TryInto};
/// MS Container Map(?) Records
#[derive(Copy, Clone)]
pub struct Container {
/// Container name
pub name: [u16; CONTAINER_NAME_LEN],
const OBJ_MSCMAP: u32 = 0x005f_ff10;
/// Card slot
const TAG_MSCMAP: u8 = 0x81;
/// 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)>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
#[derive(Clone, Debug)]
pub struct MsContainer {
/// Container name.
pub name: [u16; Self::NAME_LEN],
/// 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(YKPIV_OBJ_MSCMAP)?;
let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![];
if response.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
let (_, tlv) = match Tlv::parse(&response) {
Ok(res) => res,
Err(_) => {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
};
if response[0] != TAG_MSCMAP {
if tlv.tag != TAG_MSCMAP {
// TODO(tarcieri): yubico-piv-tool returned success here? should we?
return Err(Error::InvalidObject);
}
let mut len = 0;
let offset = 1 + get_length(&response[1..], &mut len);
if len > response.len() - offset {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
for chunk in response[offset..(offset + len)].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> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
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 max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if n_containers == 0 {
return txn.save_object(YKPIV_OBJ_MSCMAP, &[]);
return txn.save_object(OBJ_MSCMAP, &[]);
}
let req_len = 1 + set_length(&mut buf, data_len) + data_len;
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(Self::REC_LEN).enumerate() {
chunk.copy_from_slice(&containers[i].to_bytes());
}
})?;
if req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_MSCMAP;
offset += 1;
offset += set_length(&mut buf[offset..], data_len);
for (i, chunk) in buf[..data_len]
.chunks_exact_mut(CONTAINER_REC_LEN)
.enumerate()
{
chunk.copy_from_slice(&containers[i].to_bytes());
}
offset += data_len;
txn.save_object(YKPIV_OBJ_MSCMAP, &buf[..offset])
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());
@@ -156,7 +148,7 @@ impl Container {
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],
@@ -172,16 +164,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());
}
@@ -192,36 +185,14 @@ 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().unwrap()
}
}
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)
}
}
+54 -50
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,38 +30,59 @@
// (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::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
serialization::*,
Error, Result, YubiKey,
};
use log::error;
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
const OBJ_MSROOTS1: u32 = 0x005f_ff11;
#[allow(dead_code)]
const OBJ_MSROOTS2: u32 = 0x005f_ff12;
#[allow(dead_code)]
const OBJ_MSROOTS3: u32 = 0x005f_ff13;
#[allow(dead_code)]
const OBJ_MSROOTS4: u32 = 0x005f_ff14;
const OBJ_MSROOTS5: u32 = 0x005f_ff15;
const TAG_MSROOTS_END: u8 = 0x82;
const TAG_MSROOTS_MID: u8 = 0x83;
/// 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>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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> {
let cb_data = yubikey.obj_size_max();
pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
let txn = yubikey.begin_transaction()?;
// allocate first page
let mut data = Vec::with_capacity(cb_data);
let mut data = Vec::with_capacity(CB_OBJ_MAX);
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
for object_id in OBJ_MSROOTS1..OBJ_MSROOTS5 {
let buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
if cb_buf < CB_OBJ_TAG_MIN {
return Ok(None);
}
let (_, tlv) = match Tlv::parse(&buf) {
Ok(res) => res,
Err(_) => return Ok(None),
};
let tag = buf[0];
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag)
if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tlv.tag)
{
// the current object doesn't contain a valid part of a msroots file
@@ -76,17 +90,9 @@ impl MsRoots {
return Ok(None);
}
let mut len: usize = 0;
let offset = 1 + get_length(&buf[1..], &mut len);
data.extend_from_slice(tlv.value);
// check that decoded length represents object contents
if len > cb_buf - offset {
return Ok(None);
}
data.extend_from_slice(&buf[offset..offset + len]);
if tag == TAG_MSROOTS_END {
if tlv.tag == TAG_MSROOTS_END {
break;
}
}
@@ -98,7 +104,7 @@ impl MsRoots {
}
/// 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;
@@ -106,15 +112,14 @@ impl MsRoots {
let data = &self.0;
let data_len = data.len();
let n_objs: usize;
let cb_obj_max = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if data_len == 0 {
return txn.save_object(YKPIV_OBJ_MSROOTS1, &[]);
return txn.save_object(OBJ_MSROOTS1, &[]);
}
// Calculate number of objects required to store blob
n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1;
n_objs = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 {
return Err(Error::SizeError);
@@ -123,24 +128,23 @@ impl MsRoots {
for i in 0..n_objs {
offset = 0;
data_chunk = if cb_obj_max - CB_OBJ_TAG_MAX < data_len - data_offset {
cb_obj_max - CB_OBJ_TAG_MAX
data_chunk = if CB_OBJ_MAX - CB_OBJ_TAG_MAX < data_len - data_offset {
CB_OBJ_MAX - CB_OBJ_TAG_MAX
} else {
data_len - data_offset
};
buf[offset] = if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
};
offset += Tlv::write(
&mut buf,
if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
},
&data[data_offset..(data_offset + data_chunk)],
)?;
offset += 1;
offset += set_length(&mut buf[offset..], data_chunk);
buf[offset..].copy_from_slice(&data[data_offset..(data_offset + data_chunk)]);
offset += data_chunk;
txn.save_object(YKPIV_OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
txn.save_object(OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
data_offset += data_chunk;
}
+893
View File
@@ -0,0 +1,893 @@
//! Personal Identity Verification (PIV) cryptographic keys stored in a YubiKey.
//!
//! Support for public-key cryptography using keys stored within the PIV
//! slots of a YubiKey.
//!
//! Supported algorithms:
//!
//! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
// 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.
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate, PublicKeyInfo},
error::{Error, Result},
policy::{PinPolicy, TouchPolicy},
serialization::*,
setting,
yubikey::YubiKey,
Buffer, ObjectId,
};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::{debug, error, warn};
use rsa::{BigUint, RsaPublicKey};
use std::{convert::TryFrom, str::FromStr};
#[cfg(feature = "untested")]
use {
crate::consts::CB_OBJ_MAX,
num_bigint_dig::traits::ModInverse,
num_integer::Integer,
num_traits::{FromPrimitive, One},
};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
const CB_ECC_POINTP256: usize = 65;
const CB_ECC_POINTP384: usize = 97;
const TAG_RSA_MODULUS: u8 = 0x81;
const TAG_RSA_EXP: u8 = 0x82;
const TAG_ECC_POINT: u8 = 0x86;
#[cfg(feature = "untested")]
const KEYDATA_LEN: usize = 1024;
#[cfg(feature = "untested")]
const KEYDATA_RSA_EXP: u64 = 65537;
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SlotId {
/// This certificate and its associated private key is used to authenticate the card
/// and the cardholder. This slot is used for things like system login. The end user
/// PIN is required to perform any private key operations. Once the PIN has been
/// provided successfully, multiple private key operations may be performed without
/// additional cardholder consent.
Authentication,
/// This certificate and its associated private key is used for digital signatures for
/// the purpose of document signing, or signing files and executables. The end user
/// PIN is required to perform any private key operations. The PIN must be submitted
/// every time immediately before a sign operation, to ensure cardholder participation
/// for every digital signature generated.
Signature,
/// This certificate and its associated private key is used for encryption for the
/// purpose of confidentiality. This slot is used for things like encrypting e-mails
/// or files. The end user PIN is required to perform any private key operations. Once
/// the PIN has been provided successfully, multiple private key operations may be
/// performed without additional cardholder consent.
KeyManagement,
/// This certificate and its associated private key is used to support additional
/// physical access applications, such as providing physical access to buildings via
/// PIV-enabled door locks. The end user PIN is NOT required to perform private key
/// operations for this slot.
CardAuthentication,
/// These slots are only available on the YubiKey 4 & 5. They are meant for previously
/// used Key Management keys to be able to decrypt earlier encrypted documents or
/// emails. In the YubiKey 4 & 5 all 20 of them are fully available for use.
Retired(RetiredSlotId),
/// This slot is only available on YubiKey version 4.3 and newer. It is only used for
/// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten.
Attestation,
}
impl TryFrom<u8> for SlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0x9a => Ok(SlotId::Authentication),
0x9c => Ok(SlotId::Signature),
0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication),
0xf9 => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(value).map(SlotId::Retired),
}
}
}
impl From<SlotId> for u8 {
fn from(slot: SlotId) -> u8 {
match slot {
SlotId::Authentication => 0x9a,
SlotId::Signature => 0x9c,
SlotId::KeyManagement => 0x9d,
SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9,
}
}
}
impl FromStr for SlotId {
type Err = Error;
fn from_str(s: &str) -> Result<SlotId> {
match s {
"9a" => Ok(SlotId::Authentication),
"9c" => Ok(SlotId::Signature),
"9d" => Ok(SlotId::KeyManagement),
"9e" => Ok(SlotId::CardAuthentication),
"f9" => Ok(SlotId::Attestation),
_ => s.parse().map(SlotId::Retired),
}
}
}
impl SlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`SlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
SlotId::Authentication => 0x005f_c105,
SlotId::Signature => 0x005f_c10a,
SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(),
SlotId::Attestation => 0x005f_ff01,
}
}
}
/// Retired slot IDs.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RetiredSlotId {
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
R16,
R17,
R18,
R19,
R20,
}
impl TryFrom<u8> for RetiredSlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0x82 => Ok(RetiredSlotId::R1),
0x83 => Ok(RetiredSlotId::R2),
0x84 => Ok(RetiredSlotId::R3),
0x85 => Ok(RetiredSlotId::R4),
0x86 => Ok(RetiredSlotId::R5),
0x87 => Ok(RetiredSlotId::R6),
0x88 => Ok(RetiredSlotId::R7),
0x89 => Ok(RetiredSlotId::R8),
0x8a => Ok(RetiredSlotId::R9),
0x8b => Ok(RetiredSlotId::R10),
0x8c => Ok(RetiredSlotId::R11),
0x8d => Ok(RetiredSlotId::R12),
0x8e => Ok(RetiredSlotId::R13),
0x8f => Ok(RetiredSlotId::R14),
0x90 => Ok(RetiredSlotId::R15),
0x91 => Ok(RetiredSlotId::R16),
0x92 => Ok(RetiredSlotId::R17),
0x93 => Ok(RetiredSlotId::R18),
0x94 => Ok(RetiredSlotId::R19),
0x95 => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl FromStr for RetiredSlotId {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
match value {
"82" => Ok(RetiredSlotId::R1),
"83" => Ok(RetiredSlotId::R2),
"84" => Ok(RetiredSlotId::R3),
"85" => Ok(RetiredSlotId::R4),
"86" => Ok(RetiredSlotId::R5),
"87" => Ok(RetiredSlotId::R6),
"88" => Ok(RetiredSlotId::R7),
"89" => Ok(RetiredSlotId::R8),
"8a" => Ok(RetiredSlotId::R9),
"8b" => Ok(RetiredSlotId::R10),
"8c" => Ok(RetiredSlotId::R11),
"8d" => Ok(RetiredSlotId::R12),
"8e" => Ok(RetiredSlotId::R13),
"8f" => Ok(RetiredSlotId::R14),
"90" => Ok(RetiredSlotId::R15),
"91" => Ok(RetiredSlotId::R16),
"92" => Ok(RetiredSlotId::R17),
"93" => Ok(RetiredSlotId::R18),
"94" => Ok(RetiredSlotId::R19),
"95" => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl From<RetiredSlotId> for u8 {
fn from(slot: RetiredSlotId) -> u8 {
match slot {
RetiredSlotId::R1 => 0x82,
RetiredSlotId::R2 => 0x83,
RetiredSlotId::R3 => 0x84,
RetiredSlotId::R4 => 0x85,
RetiredSlotId::R5 => 0x86,
RetiredSlotId::R6 => 0x87,
RetiredSlotId::R7 => 0x88,
RetiredSlotId::R8 => 0x89,
RetiredSlotId::R9 => 0x8a,
RetiredSlotId::R10 => 0x8b,
RetiredSlotId::R11 => 0x8c,
RetiredSlotId::R12 => 0x8d,
RetiredSlotId::R13 => 0x8e,
RetiredSlotId::R14 => 0x8f,
RetiredSlotId::R15 => 0x90,
RetiredSlotId::R16 => 0x91,
RetiredSlotId::R17 => 0x92,
RetiredSlotId::R18 => 0x93,
RetiredSlotId::R19 => 0x94,
RetiredSlotId::R20 => 0x95,
}
}
}
impl RetiredSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
RetiredSlotId::R1 => 0x005f_c10d,
RetiredSlotId::R2 => 0x005f_c10e,
RetiredSlotId::R3 => 0x005f_c10f,
RetiredSlotId::R4 => 0x005f_c110,
RetiredSlotId::R5 => 0x005f_c111,
RetiredSlotId::R6 => 0x005f_c112,
RetiredSlotId::R7 => 0x005f_c113,
RetiredSlotId::R8 => 0x005f_c114,
RetiredSlotId::R9 => 0x005f_c115,
RetiredSlotId::R10 => 0x005f_c116,
RetiredSlotId::R11 => 0x005f_c117,
RetiredSlotId::R12 => 0x005f_c118,
RetiredSlotId::R13 => 0x005f_c119,
RetiredSlotId::R14 => 0x005f_c11a,
RetiredSlotId::R15 => 0x005f_c11b,
RetiredSlotId::R16 => 0x005f_c11c,
RetiredSlotId::R17 => 0x005f_c11d,
RetiredSlotId::R18 => 0x005f_c11e,
RetiredSlotId::R19 => 0x005f_c11f,
RetiredSlotId::R20 => 0x005f_c120,
}
}
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [SlotId; 24] = [
SlotId::Authentication,
SlotId::Signature,
SlotId::KeyManagement,
SlotId::Retired(RetiredSlotId::R1),
SlotId::Retired(RetiredSlotId::R2),
SlotId::Retired(RetiredSlotId::R3),
SlotId::Retired(RetiredSlotId::R4),
SlotId::Retired(RetiredSlotId::R5),
SlotId::Retired(RetiredSlotId::R6),
SlotId::Retired(RetiredSlotId::R7),
SlotId::Retired(RetiredSlotId::R8),
SlotId::Retired(RetiredSlotId::R9),
SlotId::Retired(RetiredSlotId::R10),
SlotId::Retired(RetiredSlotId::R11),
SlotId::Retired(RetiredSlotId::R12),
SlotId::Retired(RetiredSlotId::R13),
SlotId::Retired(RetiredSlotId::R14),
SlotId::Retired(RetiredSlotId::R15),
SlotId::Retired(RetiredSlotId::R16),
SlotId::Retired(RetiredSlotId::R17),
SlotId::Retired(RetiredSlotId::R18),
SlotId::Retired(RetiredSlotId::R19),
SlotId::Retired(RetiredSlotId::R20),
SlotId::CardAuthentication,
];
/// Algorithm identifiers
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlgorithmId {
/// 1024-bit RSA.
Rsa1024,
/// 2048-bit RSA.
Rsa2048,
/// ECDSA with the NIST P256 curve.
EccP256,
/// ECDSA with the NIST P384 curve.
EccP384,
}
impl TryFrom<u8> for AlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
impl From<AlgorithmId> for u8 {
fn from(id: AlgorithmId) -> u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
}
}
}
impl AlgorithmId {
/// Writes the `AlgorithmId` in the format the YubiKey expects during key generation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize> {
Tlv::write(buf, 0x80, &[self.into()])
}
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn get_elem_len(self) -> usize {
match self {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
}
}
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn get_param_tag(self) -> u8 {
match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
}
}
}
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
/// Card slot
slot: SlotId,
/// Cert
cert: Certificate,
}
impl Key {
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>> {
let mut keys = vec![];
let txn = yubikey.begin_transaction()?;
for slot in SLOTS.iter().cloned() {
let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
continue;
}
};
if !buf.is_empty() {
let cert = Certificate::from_bytes(buf)?;
keys.push(Key { slot, cert });
}
}
Ok(keys)
}
/// Get the slot ID for this key
pub fn slot(&self) -> SlotId {
self.slot
}
/// Get the certificate for this key
pub fn certificate(&self) -> &Certificate {
&self.cert
}
}
/// Generate new key.
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: PinPolicy,
touch_policy: TouchPolicy,
) -> Result<PublicKeyInfo> {
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
let setting_roca: setting::Setting;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
if yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
setting_roca = setting::Setting::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
setting::SettingSource::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
setting::SettingSource::Admin => {
if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN
} else {
SZ_ROCA_BLOCK_ADMIN
}
}
_ => SZ_ROCA_DEFAULT,
};
warn!(
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
(ROCA) and should be replaced. On-chip key generation {} See \
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
for additional information on device replacement and mitigation assistance",
yubikey.serial, psz_msg
);
if !setting_roca.value {
return Err(Error::NotSupported);
}
}
}
_ => (),
}
let txn = yubikey.begin_transaction()?;
let templ = [0, Ins::GenerateAsymmetric.code(), 0, slot.into()];
let mut in_data = [0u8; 11];
let mut offset = Tlv::write_as(&mut in_data, 0xac, 3, |buf| {
assert_eq!(algorithm.write(buf).expect("large enough"), 3);
})?;
let pin_len = pin_policy.write(&mut in_data[offset..])?;
in_data[1] += pin_len as u8;
offset += pin_len;
let touch_len = touch_policy.write(&mut in_data[offset..])?;
in_data[1] += touch_len as u8;
offset += touch_len;
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
if !response.is_success() {
let err_msg = "failed to generate new key";
match response.status_words() {
StatusWords::IncorrectSlotError => {
error!("{} (incorrect slot)", err_msg);
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
match pin_policy {
PinPolicy::Default => match touch_policy {
TouchPolicy::Default => error!("{} (algorithm not supported?)", err_msg),
_ => error!("{} (touch policy not supported?)", err_msg),
},
_ => error!("{} (pin policy not supported?)", err_msg),
}
return Err(Error::AlgorithmError);
}
StatusWords::SecurityStatusError => {
error!("{} (not authenticated)", err_msg);
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:?})", err_msg, other);
return Err(Error::GenericError);
}
}
}
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
//
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
// - Use a full ASN.1 parser on the entire datastructure:
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
//
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
// parsing (which treats 128 as a single-byte definite length instead of an
// indefinite length):
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | |0x81|len128| modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | |0x81| len:256 | modulus |0x82|len3| exp |
//
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
// 3-byte length.
let data = &response.data()[5..];
let (data, modulus_tlv) = Tlv::parse(data)?;
if modulus_tlv.tag != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
let modulus = modulus_tlv.value.to_vec();
let (_, exp_tlv) = Tlv::parse(data)?;
if exp_tlv.tag != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
let exp = exp_tlv.value.to_vec();
Ok(PublicKeyInfo::Rsa {
algorithm,
pubkey: RsaPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
)
.map_err(|_| Error::InvalidObject)?,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
let data = &response.data()[3..];
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
let (_, tlv) = Tlv::parse(data)?;
if tlv.tag != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
// the curve point should always be determined by the curve
if tlv.value.len() != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = tlv.value.to_vec();
if let AlgorithmId::EccP256 = algorithm {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP256)
} else {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP384)
}
.map_err(|_| Error::InvalidObject)
}
}
}
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn write_key(
yubikey: &mut YubiKey,
slot: SlotId,
params: Vec<&[u8]>,
pin_policy: PinPolicy,
touch_policy: TouchPolicy,
algorithm: AlgorithmId,
) -> Result<()> {
let mut key_data = Buffer::new(vec![0u8; KEYDATA_LEN]);
let templ = [0, Ins::ImportKey.code(), algorithm.into(), slot.into()];
let mut offset = 0;
let elem_len = algorithm.get_elem_len();
let param_tag = algorithm.get_param_tag();
for (i, param) in params.into_iter().enumerate() {
offset += Tlv::write_as(
&mut key_data[offset..],
param_tag + (i as u8),
elem_len,
|buf| {
let padding = elem_len - param.len();
for b in &mut buf[..padding] {
*b = 0;
}
buf[padding..].copy_from_slice(param);
},
)?;
}
offset += pin_policy.write(&mut key_data[offset..])?;
offset += touch_policy.write(&mut key_data[offset..])?;
let txn = yubikey.begin_transaction()?;
let status_words = txn
.transfer_data(&templ, &key_data[..offset], 256)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
/// The key data that makes up an RSA key.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub struct RsaKeyData {
/// The secret prime `p`.
p: Buffer,
/// The secret prime, `q`.
q: Buffer,
/// D mod (P-1)
dp: Buffer,
/// D mod (Q-1)
dq: Buffer,
/// Q^-1 mod P
qinv: Buffer,
}
#[cfg(feature = "untested")]
impl RsaKeyData {
/// Generates a new RSA key data set from two randomly generated, secret, primes.
///
/// Panics if `secret_p` or `secret_q` are invalid primes.
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Self {
let p = BigUint::from_bytes_be(secret_p);
let q = BigUint::from_bytes_be(secret_q);
let totient = {
let p_t = &p - BigUint::one();
let q_t = &p - BigUint::one();
p_t.lcm(&q_t)
};
let exp = BigUint::from_u64(KEYDATA_RSA_EXP).unwrap();
let d = exp.mod_inverse(&totient).unwrap();
let d = d.to_biguint().unwrap();
// We calculate the optimization values ahead of time, instead of making the user
// do so.
let dp = &d % (&p - BigUint::one());
let dq = &d % (&q - BigUint::one());
let qinv = q.clone().mod_inverse(&p).unwrap();
let (_, qinv) = qinv.to_bytes_be();
RsaKeyData {
p: Zeroizing::new(p.to_bytes_be()),
q: Zeroizing::new(q.to_bytes_be()),
dp: Zeroizing::new(dp.to_bytes_be()),
dq: Zeroizing::new(dq.to_bytes_be()),
qinv: Zeroizing::new(qinv),
}
}
fn total_len(&self) -> usize {
self.p.len() + self.q.len() + self.dp.len() + self.qinv.len()
}
}
/// Imports a private RSA encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn import_rsa_key(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
key_data: RsaKeyData,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<()> {
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (),
_ => return Err(Error::AlgorithmError),
}
if key_data.total_len() > KEYDATA_LEN {
return Err(Error::SizeError);
}
let params = vec![
key_data.p.as_slice(),
key_data.q.as_slice(),
key_data.dp.as_slice(),
key_data.dq.as_slice(),
key_data.qinv.as_slice(),
];
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
Ok(())
}
/// Imports a private ECC encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn import_ecc_key(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
key_data: &[u8],
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<()> {
match algorithm {
AlgorithmId::EccP256 | AlgorithmId::EccP384 => (),
_ => return Err(Error::AlgorithmError),
}
if key_data.len() > KEYDATA_LEN {
return Err(Error::SizeError);
}
let params = vec![key_data];
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
Ok(())
}
/// Generate an attestation certificate for a stored key.
///
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer> {
let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = yubikey.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported);
} else {
return Err(Error::GenericError);
}
}
if response.data()[0] != 0x30 {
return Err(Error::GenericError);
}
Ok(Buffer::new(response.data().into()))
}
/// Sign data using a PIV key.
pub fn sign_data(
yubikey: &mut YubiKey,
raw_in: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer> {
let txn = yubikey.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(raw_in, algorithm, key, false)
}
/// Decrypt data using a PIV key.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn decrypt_data(
yubikey: &mut YubiKey,
input: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer> {
let txn = yubikey.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true)
}
+92
View File
@@ -0,0 +1,92 @@
//! Enums representing key policies.
use crate::{serialization::Tlv, Result};
/// Specifies how often the PIN needs to be entered for access to the credential in a
/// given slot.
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PinPolicy {
/// Use the default PIN policy for the slot. See the slot's documentation for details.
Default,
/// The end user PIN is **NOT** required to perform private key operations.
Never,
/// The end user PIN is required to perform any private key operations. Once the
/// correct PIN has been provided, multiple private key operations may be performed
/// without additional cardholder consent.
Once,
/// The end user PIN is required to perform any private key operations. The PIN must
/// be submitted immediately before each operation to ensure cardholder participation.
Always,
}
impl From<PinPolicy> for u8 {
fn from(policy: PinPolicy) -> u8 {
match policy {
PinPolicy::Default => 0,
PinPolicy::Never => 1,
PinPolicy::Once => 2,
PinPolicy::Always => 3,
}
}
}
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> {
match self {
PinPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xaa, &[self.into()]),
}
}
}
/// Specifies under what conditions a physical touch on the metal contact is required, in
/// addition to the [`PinPolicy`].
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TouchPolicy {
/// Use the default touch policy for the slot.
Default,
/// A physical touch is **NOT** required to perform private key operations.
Never,
/// A physical touch is required to perform any private key operations. The metal
/// contact must be touched during each operation to ensure cardholder participation.
Always,
/// A physical touch is required to perform any private key operations. Each touch
/// is cached for 15 seconds, during which time multiple private key operations may be
/// performed without additional cardholder interaction. After 15 seconds the cached
/// touch is cleared, and further operations require another physical touch.
Cached,
}
impl From<TouchPolicy> for u8 {
fn from(policy: TouchPolicy) -> u8 {
match policy {
TouchPolicy::Default => 0,
TouchPolicy::Never => 1,
TouchPolicy::Always => 2,
TouchPolicy::Cached => 3,
}
}
}
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> {
match self {
TouchPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xab, &[self.into()]),
}
}
}
+15 -15
View File
@@ -1,6 +1,6 @@
//! Support for enumerating available readers
//! Support for enumerating available PC/SC card readers.
use crate::{error::Error, yubikey::YubiKey};
use crate::{Result, YubiKey};
use std::{
borrow::Cow,
convert::TryInto,
@@ -11,8 +11,8 @@ use std::{
/// 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,10 @@ pub struct Readers {
reader_names: Vec<u8>,
}
impl Readers {
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,8 +32,8 @@ 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<_> = {
@@ -54,7 +54,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,25 +64,25 @@ 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> {
/// Connect to this reader, returning its `pcsc::Card`.
pub(crate) fn connect(&self) -> Result<pcsc::Card> {
let ctx = self.ctx.lock().unwrap();
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
}
+114 -10
View File
@@ -30,30 +30,134 @@
// (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::{consts::*, ObjectId};
use crate::{consts::CB_OBJ_TAG_MIN, Buffer, Error, ObjectId, Result};
pub const OBJ_DISCOVERY: u32 = 0x7e;
// TODO(tarcieri): refactor these into better serializers/message builders
/// A Type-Length-Value object that has been parsed from a buffer.
pub(crate) struct Tlv<'a> {
pub(crate) tag: u8,
pub(crate) value: &'a [u8],
}
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)> {
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)?;
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> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
if tag != buffer[0] {
return Err(Error::GenericError);
};
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
buffer.copy_within(offset..offset + len, 0);
buffer.truncate(len);
Ok(buffer)
}
/// Writes a TLV to the given buffer.
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize> {
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], value.len())?;
if buffer.len() < offset + value.len() {
return Err(Error::SizeError);
}
buffer[offset..offset + value.len()].copy_from_slice(value);
Ok(offset + value.len())
}
/// Writes a TLV to the given buffer.
///
/// `value` is guaranteed to be called with a mutable slice of length `length`.
pub(crate) fn write_as<Gen>(
buffer: &mut [u8],
tag: u8,
length: usize,
value: Gen,
) -> Result<usize>
where
Gen: FnOnce(&mut [u8]),
{
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], length)?;
if buffer.len() < offset + length {
return Err(Error::SizeError);
}
value(&mut buffer[offset..offset + length]);
Ok(offset + length)
}
}
/// Set length
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> usize {
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize> {
if length < 0x80 {
buffer[0] = length as u8;
1
if buffer.is_empty() {
Err(Error::SizeError)
} else {
buffer[0] = length as u8;
Ok(1)
}
} else if length < 0x100 {
buffer[0] = 0x81;
buffer[1] = length as u8;
2
if buffer.len() < 2 {
Err(Error::SizeError)
} else {
buffer[0] = 0x81;
buffer[1] = length as u8;
Ok(2)
}
} else if buffer.len() < 3 {
Err(Error::SizeError)
} else {
buffer[0] = 0x82;
buffer[1] = ((length >> 8) & 0xff) as u8;
buffer[2] = (length & 0xff) as u8;
3
Ok(3)
}
}
/// Parse length tag, returning the size of the length tag itself as the
/// returned value, and setting the len parameter to the parsed length.
pub(crate) fn get_length(buffer: &[u8], len: &mut usize) -> usize {
// This is not valid ASN.1 (0x80 is the indefinite length marker).
// See comment in key::generate for more context.
if buffer[0] < 0x81 {
*len = buffer[0] as usize;
1
@@ -83,9 +187,9 @@ pub(crate) fn has_valid_length(buffer: &[u8], len: usize) -> bool {
pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
buffer[0] = 0x5c;
if object_id == YKPIV_OBJ_DISCOVERY {
if object_id == OBJ_DISCOVERY {
buffer[1] = 1;
buffer[2] = YKPIV_OBJ_DISCOVERY as u8;
buffer[2] = OBJ_DISCOVERY as u8;
buffer = &mut buffer[3..];
} else if object_id > 0xffff && object_id <= 0x00ff_ffff {
buffer[1] = 3;
+141
View File
@@ -0,0 +1,141 @@
//! 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 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,
}
impl Default for SettingSource {
fn default() -> Self {
Self::Default
}
}
/// Setting booleans: configuration values sourced from a file or the environment.
///
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
/// variables.
#[derive(Copy, Clone, Debug)]
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(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 {
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",
})
}
}
impl Default for Setting {
fn default() -> Self {
Self {
value: false,
source: SettingSource::default(),
}
}
}
-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
}
+108 -132
View File
@@ -1,46 +1,50 @@
//! YubiKey PC/SC transactions
use crate::{
apdu::{Ins, APDU},
error::Error,
yubikey::*,
};
#[cfg(feature = "untested")]
use crate::{
apdu::{Response, StatusWords},
consts::*,
key::{AlgorithmId, SlotId},
mgm::MgmKey,
apdu::Response,
apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result},
piv::{AlgorithmId, SlotId},
serialization::*,
yubikey::*,
Buffer, ObjectId,
};
use log::{error, trace};
use std::convert::TryInto;
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES};
/// PIV Applet ID
const PIV_AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
const YK_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
const CB_PIN_MAX: usize = 8;
#[cfg(feature = "untested")]
pub(crate) enum ChangeRefAction {
ChangePin,
ChangePuk,
UnblockPin,
}
/// Exclusive transaction with the YubiKey's PC/SC card.
pub(crate) struct Transaction<'tx> {
inner: pcsc::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> {
/// Create a new transaction with the given card.
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self> {
Ok(Transaction {
inner: card.transaction()?,
})
}
/// Get an attribute of the card or card reader.
pub fn get_attribute<'buf>(
&self,
attribute: pcsc::Attribute,
buffer: &'buf mut [u8],
) -> Result<&'buf [u8], Error> {
Ok(self.inner.get_attribute(attribute, buffer)?)
}
/// Transmit a single serialized APDU to the card this transaction is open
/// with and receive a response.
///
@@ -48,7 +52,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];
@@ -63,10 +67,10 @@ impl<'tx> Transaction<'tx> {
}
/// Select application.
pub fn select_application(&self) -> Result<(), Error> {
let response = APDU::new(Ins::SelectApplication)
pub fn select_application(&self) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(&PIV_AID)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
@@ -84,10 +88,10 @@ impl<'tx> Transaction<'tx> {
Ok(())
}
/// Get the version of the PIV application installed on the YubiKey
pub fn get_version(&self) -> Result<Version, Error> {
/// Get the version of the PIV application installed on the YubiKey.
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() {
return Err(Error::GenericError);
@@ -100,15 +104,13 @@ impl<'tx> Transaction<'tx> {
Ok(Version::new(response.data()[..3].try_into().unwrap()))
}
/// Get YubiKey device serial number
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> {
let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
/// Get YubiKey device serial number.
pub fn get_serial(&self, version: Version) -> Result<Serial> {
let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet
let sw = APDU::new(Ins::SelectApplication)
// YK4 requires switching to the yk applet to retrieve the serial
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&yk_applet)
.data(&YK_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -117,7 +119,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError);
}
let resp = APDU::new(0x01).p1(0x10).transmit(self, 0xFF)?;
let resp = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
@@ -128,9 +130,9 @@ impl<'tx> Transaction<'tx> {
}
// reselect the PIV applet
let sw = APDU::new(Ins::SelectApplication)
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(&PIV_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -141,8 +143,8 @@ impl<'tx> Transaction<'tx> {
resp
} else {
// get serial from yk5 and later devices using the f8 command
let resp = APDU::new(Ins::GetSerial).transmit(self, 0xFF)?;
// YK5 implements getting the serial as a PIV applet command (0xf8)
let resp = Apdu::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
@@ -162,13 +164,12 @@ impl<'tx> Transaction<'tx> {
}
/// Verify device PIN.
#[cfg(feature = "untested")]
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
@@ -190,20 +191,26 @@ impl<'tx> Transaction<'tx> {
}
}
/// Change the PIN
/// Change the PIN.
#[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, Ins::ChangeReference.code(), 0, 0x80];
pub fn change_ref(
&self,
action: ChangeRefAction,
current_pin: &[u8],
new_pin: &[u8],
) -> Result<()> {
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
if action == CHREF_ACT_UNBLOCK_PIN {
templ[1] = Ins::ResetRetry.code();
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
const PIN: u8 = 0x80;
const PUK: u8 = 0x81;
let templ = match action {
ChangeRefAction::ChangePin => [0, Ins::ChangeReference.code(), 0, PIN],
ChangeRefAction::ChangePuk => [0, Ins::ChangeReference.code(), 0, PUK],
ChangeRefAction::UnblockPin => [0, Ins::ResetRetry.code(), 0, PIN],
};
let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
indata[0..current_pin.len()].copy_from_slice(current_pin);
@@ -229,22 +236,16 @@ 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] = YKPIV_ALGO_3DES;
data[1] = YKPIV_KEY_CARDMGM;
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 status_words = APDU::new(Ins::SetMgmKey)
let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2)
.data(&data)
.transmit(self, 261)?
@@ -262,7 +263,6 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing
/// operations.
// TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command(
&self,
@@ -270,11 +270,10 @@ 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()];
let mut len: usize = 0;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
@@ -310,19 +309,21 @@ impl<'tx> Transaction<'tx> {
3
};
indata[0] = 0x7c;
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3);
indata[offset] = 0x82;
indata[offset + 1] = 0x00;
indata[offset + 2] = match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
_ => 0x81,
};
offset += 3;
offset += set_length(&mut indata[offset..], in_len);
indata[offset..(offset + in_len)].copy_from_slice(sign_in);
offset += in_len;
let offset = Tlv::write_as(&mut indata, 0x7c, in_len + bytes + 3, |buf| {
assert_eq!(Tlv::write(buf, 0x82, &[]).expect("large enough"), 2);
assert_eq!(
Tlv::write(
&mut buf[2..],
match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
_ => 0x81,
},
sign_in
)
.expect("large enough"),
1 + bytes + in_len
);
})?;
let response = self
.transfer_data(&templ, &indata[..offset], 1024)
@@ -341,41 +342,33 @@ impl<'tx> Transaction<'tx> {
}
}
let data = response.data();
let (_, outer_tlv) = Tlv::parse(response.data())?;
// skip the first 7c tag
if data[0] != 0x7c {
if outer_tlv.tag != 0x7c {
error!("failed parsing signature reply (0x7c byte)");
return Err(Error::ParseError);
}
let mut offset = 1 + get_length(&data[1..], &mut len);
let (_, inner_tlv) = Tlv::parse(outer_tlv.value)?;
// skip the 82 tag
if data[offset] != 0x82 {
if inner_tlv.tag != 0x82 {
error!("failed parsing signature reply (0x82 byte)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
Ok(Buffer::new(data[offset..(offset + len)].into()))
Ok(Buffer::new(inner_tlv.value.into()))
}
/// Send/receive large amounts of data to/from the YubiKey, splitting long
/// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via
/// [`Transaction::transmit`].
#[cfg(feature = "untested")]
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 = 0;
let mut sw;
loop {
let mut this_size = 0xff;
@@ -389,19 +382,19 @@ 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)?;
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
sw = response.status_words().code();
if !response.is_success() && (sw >> 8 != 0x61) {
// TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data));
}
sw = response.status_words().code();
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
error!(
"output buffer too small: wanted to write {}, max was {}",
@@ -426,7 +419,7 @@ impl<'tx> Transaction<'tx> {
sw & 0xff
);
let response = APDU::new(Ins::GetResponseApdu).transmit(self, 261)?;
let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
@@ -449,9 +442,8 @@ impl<'tx> Transaction<'tx> {
Ok(Response::new(sw.into(), out_data))
}
/// Fetch an object
#[cfg(feature = "untested")]
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
/// Fetch an object.
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer> {
let mut indata = [0u8; 5];
let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
@@ -462,40 +454,30 @@ impl<'tx> Transaction<'tx> {
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
if !response.is_success() {
return Err(Error::GenericError);
if response.status_words() == StatusWords::NotFoundError {
return Err(Error::NotFound);
} else {
return Err(Error::GenericError);
}
}
let data = Buffer::new(response.data().into());
let mut outlen = 0;
let (remaining, tlv) = Tlv::parse(response.data())?;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) {
return Err(Error::SizeError);
}
let offs = get_length(&data[1..], &mut outlen);
if offs == 0 {
return Err(Error::SizeError);
}
if outlen + offs + 1 != data.len() {
if !remaining.is_empty() {
error!(
"invalid length indicated in object: total len is {} but indicated length is {}",
data.len(),
outlen
tlv.value.len() + remaining.len(),
tlv.value.len()
);
return Err(Error::SizeError);
}
Ok(Zeroizing::new(
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
))
Ok(Zeroizing::new(tlv.value.to_vec()))
}
/// Save an object
#[cfg(feature = "untested")]
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
/// Save an object.
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,14 +490,8 @@ impl<'tx> Transaction<'tx> {
let mut len = data.len();
let mut data_remaining = set_object(object_id, &mut data);
data_remaining[0] = 0x53;
data_remaining = &mut data_remaining[1..];
let offset = set_length(data_remaining, indata.len());
let offset = Tlv::write(data_remaining, 0x53, indata)?;
data_remaining = &mut data_remaining[offset..];
data_remaining[..indata.len()].copy_from_slice(indata);
data_remaining = &mut data_remaining[indata.len()..];
len -= data_remaining.len();
let status_words = self
+209 -366
View File
@@ -30,53 +30,60 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
#[cfg(feature = "untested")]
use crate::{
apdu::{Ins, StatusWords, APDU},
key::{AlgorithmId, SlotId},
metadata,
apdu::{Apdu, Ins},
cccid::CccId,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::MgmKey,
serialization::*,
Buffer, ObjectId,
};
use crate::{
consts::*,
error::Error,
readers::{Reader, Readers},
piv,
reader::{Context, Reader},
transaction::Transaction,
};
#[cfg(feature = "untested")]
use getrandom::getrandom;
use log::{error, info, warn};
use log::{error, info};
use pcsc::Card;
#[cfg(feature = "untested")]
use secrecy::ExposeSecret;
use rand_core::{OsRng, RngCore};
use std::{
convert::TryFrom,
convert::{TryFrom, TryInto},
fmt::{self, Display},
str::FromStr,
};
#[cfg(feature = "untested")]
use std::{
convert::TryInto,
time::{SystemTime, UNIX_EPOCH},
use {
crate::{
apdu::StatusWords,
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP},
metadata::AdminData,
transaction::ChangeRefAction,
Buffer, ObjectId,
},
secrecy::ExposeSecret,
std::time::{SystemTime, UNIX_EPOCH},
};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
/// PIV Application ID
pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// Flag for PUK blocked
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// MGMT Application ID.
/// 3DES authentication
pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key
pub(crate) const KEY_CARDMGM: u8 = 0x9b;
/// MGMT Applet ID.
///
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
#[cfg(feature = "untested")]
const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// Cached YubiKey PIN
const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN.
pub type CachedPin = secrecy::SecretVec<u8>;
/// YubiKey Serial Number
/// YubiKey serial number.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Serial(pub u32);
@@ -92,13 +99,21 @@ impl From<Serial> for u32 {
}
}
impl FromStr for Serial {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
s.parse().map(Serial).map_err(|_| Error::ParseError)
}
}
impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// YubiKey Version
/// YubiKey version.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Version {
/// Major version component
@@ -122,8 +137,13 @@ impl Version {
}
}
/// YubiKey Device: this is the primary API for opening a session and
/// performing various operations.
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
/// 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.
@@ -131,8 +151,8 @@ impl Version {
#[cfg_attr(not(feature = "untested"), allow(dead_code))]
pub struct YubiKey {
pub(crate) card: Card,
pub(crate) name: String,
pub(crate) pin: Option<CachedPin>,
pub(crate) is_neo: bool,
pub(crate) version: Version,
pub(crate) serial: Serial,
}
@@ -143,10 +163,16 @@ impl YubiKey {
/// Returns an error if there is more than one YubiKey detected.
///
/// If you need to operate in environments with more than one YubiKey
/// attached to the same system, use [`yubikey_piv::Readers`] to select
/// from the available PC/SC readers connected.
pub fn open() -> Result<Self, Error> {
let mut readers = Readers::open()?;
/// attached to the same system, use [`YubiKey::open_by_serial`] or
/// [`yubikey::reader::Context`][`Context`] to select from the available
/// PC/SC readers.
pub fn open() -> Result<Self> {
let mut readers = Context::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
let mut reader_iter = readers.iter()?;
if let Some(reader) = reader_iter.next() {
@@ -159,12 +185,37 @@ impl YubiKey {
}
error!("no YubiKey detected!");
Err(Error::GenericError)
Err(Error::NotFound)
}
/// Reconnect to a YubiKey
/// Open a YubiKey with a specific serial number.
pub fn open_by_serial(serial: Serial) -> Result<Self> {
let mut readers = Context::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
for reader in readers.iter()? {
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
if serial == yubikey.serial() {
return Ok(yubikey);
}
}
error!("no YubiKey detected with serial: {}", serial);
Err(Error::NotFound)
}
/// Reconnect to a YubiKey.
#[cfg(feature = "untested")]
pub fn reconnect(&mut self) -> Result<(), Error> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn reconnect(&mut self) -> Result<()> {
info!("trying to reconnect to current reader");
self.card.reconnect(
@@ -189,47 +240,53 @@ impl YubiKey {
}
/// Begin a transaction.
#[cfg(feature = "untested")]
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> {
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>> {
// TODO(tarcieri): reconnect support
Ok(Transaction::new(&mut self.card)?)
Transaction::new(&mut self.card)
}
/// Get the name of the associated PC/SC card reader.
pub fn name(&self) -> &str {
&self.name
}
/// Get the YubiKey's PIV application version.
///
/// This always uses the cached version queried when the key is initialized.
pub fn version(&mut self) -> Version {
pub fn version(&self) -> Version {
self.version
}
/// Get YubiKey device serial number.
///
/// This always uses the cached version queried when the key is initialized.
pub fn serial(&mut self) -> Serial {
pub fn serial(&self) -> Serial {
self.serial
}
/// Get YubiKey device model
// TODO(tarcieri): use an emum for this
#[cfg(feature = "untested")]
pub fn device_model(&self) -> u32 {
if self.is_neo {
DEVTYPE_NEOr3
} else {
// TODO(tarcieri): YK5?
DEVTYPE_YK4
}
/// Get device configuration.
pub fn config(&mut self) -> Result<Config> {
Config::get(self)
}
/// Get Cardholder Unique Identifier (CHUID).
pub fn chuid(&mut self) -> Result<ChuId> {
ChuId::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).
#[cfg(feature = "untested")]
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(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x80, 0x00])
let challenge = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?;
if !challenge.is_success() || challenge.data().len() < 12 {
@@ -240,24 +297,20 @@ impl YubiKey {
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
let mut data = [0u8; 22];
data[0] = 0x7c;
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 getrandom(&mut data[14..22]).is_err() {
error!("failed getting randomness for authentication");
return Err(Error::RandomnessError);
}
OsRng.fill_bytes(&mut data[14..22]);
let mut challenge = [0u8; 8];
challenge.copy_from_slice(&data[14..22]);
let authentication = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
let authentication = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?;
@@ -276,12 +329,18 @@ impl YubiKey {
Ok(())
}
/// Deauthenticate
/// Get the PIV keys contained in this YubiKey.
pub fn piv_keys(&mut self) -> Result<Vec<piv::Key>> {
piv::Key::list(self)
}
/// Deauthenticate.
#[cfg(feature = "untested")]
pub fn deauthenticate(&mut self) -> Result<(), Error> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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)
.transmit(&txn, 255)?
@@ -298,37 +357,8 @@ impl YubiKey {
Ok(())
}
/// Sign data using a PIV key
#[cfg(feature = "untested")]
pub fn sign_data(
&mut self,
raw_in: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(raw_in, algorithm, key, false)
}
/// Decrypt data using a PIV key
#[cfg(feature = "untested")]
pub fn decrypt_data(
&mut self,
input: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true)
}
/// Verify device PIN.
#[cfg(feature = "untested")]
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)?;
@@ -341,9 +371,8 @@ impl YubiKey {
Ok(())
}
/// Get the number of PIN retries
#[cfg(feature = "untested")]
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
@@ -359,9 +388,10 @@ 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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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(());
@@ -383,12 +413,13 @@ 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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> {
{
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PIN, current_pin, new_pin)?;
txn.change_ref(ChangeRefAction::ChangePin, current_pin, new_pin)?;
}
if !new_pin.is_empty() {
@@ -398,16 +429,13 @@ impl YubiKey {
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];
let max_size = yubikey.obj_size_max();
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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()
@@ -416,19 +444,14 @@ impl YubiKey {
.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)
.map_err(|e| {
error!("could not set pin timestamp, err = {}", e);
e
})?;
metadata::write(&txn, TAG_ADMIN, &data, max_size).map_err(|e| {
admin_data.write(&txn).map_err(|e| {
error!("could not write admin data, err = {}", e);
e
})?;
@@ -442,26 +465,27 @@ 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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk)
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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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 max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
let txn = self.begin_transaction()?;
while tries_remaining != 0 {
// 2 -> change puk
let res = txn.change_pin(CHREF_ACT_CHANGE_PUK, &puk, &puk);
let res = txn.change_ref(ChangeRefAction::ChangePuk, &puk, &puk);
match res {
Ok(()) => puk[0] += 1,
@@ -480,34 +504,29 @@ impl YubiKey {
}
}
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
if item.len() == flags.len() {
flags.copy_from_slice(item)
} else {
error!(
"admin flags exist, but are incorrect size: {} (expected {})",
item.len(),
flags.len()
);
// Attempt to set the "PUK blocked" flag in admin data.
let mut admin_data = AdminData::read(&txn)
.map(|data| {
if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags.len() {
flags.copy_from_slice(item)
} else {
error!(
"admin flags exist, but are incorrect size: {} (expected {})",
item.len(),
flags.len()
);
}
}
}
}
data
})
.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], max_size).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 {
@@ -520,180 +539,36 @@ 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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin)
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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> {
let txn = self.begin_transaction()?;
txn.save_object(object_id, indata)
}
/// Import a private encryption or signing key into the YubiKey
// TODO(tarcieri): refactor this into separate methods per key type
/// Get an auth challenge.
#[cfg(feature = "untested")]
pub fn import_private_key(
&mut self,
key: SlotId,
algorithm: AlgorithmId,
p: Option<&[u8]>,
q: Option<&[u8]>,
dp: Option<&[u8]>,
dq: Option<&[u8]>,
qinv: Option<&[u8]>,
ec_data: Option<&[u8]>,
pin_policy: u8,
touch_policy: u8,
) -> Result<(), Error> {
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
if pin_policy != YKPIV_PINPOLICY_DEFAULT
&& pin_policy != YKPIV_PINPOLICY_NEVER
&& pin_policy != YKPIV_PINPOLICY_ONCE
&& pin_policy != YKPIV_PINPOLICY_ALWAYS
{
return Err(Error::GenericError);
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT
&& touch_policy != YKPIV_TOUCHPOLICY_NEVER
&& touch_policy != YKPIV_TOUCHPOLICY_ALWAYS
&& touch_policy != YKPIV_TOUCHPOLICY_CACHED
{
return Err(Error::GenericError);
}
let (elem_len, params, param_tag) = match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
(Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() {
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
_ => unreachable!(),
},
vec![p, q, dp, dq, qinv],
0x01,
)
}
_ => return Err(Error::GenericError),
},
AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data {
Some(ec_data) => {
if ec_data.len() >= key_data.len() {
// This can never be true, but check to be explicit.
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
_ => unreachable!(),
},
vec![ec_data],
0x06,
)
}
_ => return Err(Error::GenericError),
},
};
let mut offset = 0;
for (i, param) in params.into_iter().enumerate() {
key_data[offset] = param_tag + i as u8;
offset += 1;
offset += set_length(&mut key_data[offset..], elem_len);
let padding = elem_len - param.len();
let remaining = key_data.len() - offset;
if padding > remaining {
return Err(Error::AlgorithmError);
}
for b in &mut key_data[offset..offset + padding] {
*b = 0;
}
offset += padding;
key_data[offset..offset + param.len()].copy_from_slice(param);
offset += param.len();
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
key_data[offset] = YKPIV_PINPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = pin_policy;
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
key_data[offset] = YKPIV_TOUCHPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = touch_policy;
offset += 3;
}
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> {
let txn = self.begin_transaction()?;
let status_words = txn
.transfer_data(&templ, &key_data[..offset], 256)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
/// Generate an attestation certificate for a stored key.
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = self.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported);
} else {
return Err(Error::GenericError);
}
}
if response.data()[0] != 0x30 {
return Err(Error::GenericError);
}
Ok(Buffer::new(response.data().into()))
}
/// Get an auth challenge
#[cfg(feature = "untested")]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
let txn = self.begin_transaction()?;
let response = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
let response = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?;
@@ -704,9 +579,10 @@ impl YubiKey {
Ok(response.data()[4..12].try_into().unwrap())
}
/// Verify an auth response
/// Verify an auth response.
#[cfg(feature = "untested")]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> {
let mut data = [0u8; 12];
data[0] = 0x7c;
data[1] = 0x0a;
@@ -717,8 +593,8 @@ 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)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
let status_words = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?
.status_words();
@@ -736,7 +612,8 @@ impl YubiKey {
///
/// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")]
pub fn reset_device(&mut self) -> Result<(), Error> {
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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();
@@ -747,22 +624,12 @@ impl YubiKey {
Ok(())
}
/// Get max object size supported by this device
#[cfg(feature = "untested")]
pub(crate) fn obj_size_max(&self) -> usize {
if self.is_neo {
CB_OBJ_MAX_NEO
} else {
CB_OBJ_MAX
}
}
}
impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
type Error = Error;
fn try_from(reader: &'a Reader<'_>) -> Result<Self, Error> {
fn try_from(reader: &'a Reader<'_>) -> Result<Self> {
let mut card = reader.connect().map_err(|e| {
error!("error connecting to reader '{}': {}", reader.name(), e);
e
@@ -770,43 +637,19 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
info!("connected to reader: {}", reader.name());
let mut is_neo = false;
let version: Version;
let serial: Serial;
{
let (version, serial) = {
let txn = Transaction::new(&mut card)?;
let mut atr_buf = [0; CB_ATR_MAX];
let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?;
if atr == YKPIV_ATR_NEO_R3 {
is_neo = true;
}
txn.select_application()?;
// now that the PIV application is selected, retrieve the version
// and serial number. Previously the NEO/YK4 required switching
// to the yk applet to retrieve the serial, YK5 implements this
// as a PIV applet command. Unfortunately, this change requires
// that we retrieve the version number first, so that get_serial
// can determine how to get the serial number, which for the NEO/Yk4
// will result in another selection of the PIV applet.
version = txn.get_version().map_err(|e| {
warn!("failed to retrieve version: '{}'", e);
e
})?;
serial = txn.get_serial(version).map_err(|e| {
warn!("failed to retrieve serial number: '{}'", e);
e
})?;
}
let v = txn.get_version()?;
let s = txn.get_serial(v)?;
(v, s)
};
let yubikey = YubiKey {
card,
name: String::from(reader.name()),
pin: None,
is_neo,
version,
serial,
};
+231 -8
View File
@@ -3,18 +3,241 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use std::env;
use yubikey_piv::YubiKey;
use lazy_static::lazy_static;
use log::trace;
use rand_core::{OsRng, RngCore};
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
use sha2::{Digest, Sha256};
use std::{convert::TryInto, env, sync::Mutex};
use x509::RelativeDistinguishedName;
use yubikey::{
certificate::{Certificate, PublicKeyInfo},
piv::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey,
};
#[test]
#[ignore]
fn connect() {
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> {
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
let mut yubikey = YubiKey::open().unwrap();
dbg!(&yubikey.version());
dbg!(&yubikey.serial());
let yubikey = YubiKey::open().unwrap();
trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version());
Mutex::new(yubikey)
}
//
// CCCID support
//
#[test]
#[ignore]
fn test_get_cccid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.cccid() {
Ok(cccid) => trace!("CCCID: {:?}", cccid),
Err(Error::NotFound) => trace!("CCCID not found"),
Err(err) => panic!("error getting CCCID: {:?}", err),
}
}
//
// CHUID support
//
#[test]
#[ignore]
fn test_get_chuid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.chuid() {
Ok(chuid) => trace!("CHUID: {:?}", chuid),
Err(Error::NotFound) => trace!("CHUID not found"),
Err(err) => panic!("error getting CHUID: {:?}", err),
}
}
//
// Device config support
//
#[test]
#[ignore]
fn test_get_config() {
let mut yubikey = YUBIKEY.lock().unwrap();
let config_result = yubikey.config();
assert!(config_result.is_ok());
trace!("config: {:?}", config_result.unwrap());
}
//
// Cryptographic key support
//
#[test]
#[ignore]
fn test_list_keys() {
let mut yubikey = YUBIKEY.lock().unwrap();
let keys_result = Key::list(&mut yubikey);
assert!(keys_result.is_ok());
trace!("keys: {:?}", keys_result.unwrap());
}
//
// PIN support
//
#[test]
#[ignore]
fn test_verify_pin() {
let mut yubikey = YUBIKEY.lock().unwrap();
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 yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
// Set a protected management key.
assert!(MgmKey::generate().set_protected(&mut yubikey).is_ok());
let protected = MgmKey::get_protected(&mut yubikey).unwrap();
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_ok());
// Set a manual management key.
let manual = MgmKey::generate();
assert!(manual.set_manual(&mut yubikey, false).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_err());
assert!(yubikey.authenticate(manual.clone()).is_ok());
// Set back to the default management key.
assert!(MgmKey::set_default(&mut yubikey).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(protected).is_err());
assert!(yubikey.authenticate(manual).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
}
//
// Certificate support
//
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1);
// Generate a new key in the selected slot.
let generated = piv::generate(
&mut yubikey,
slot,
algorithm,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let mut serial = [0u8; 20];
OsRng.fill_bytes(&mut serial);
// Generate a self-signed certificate for the new key.
let extensions: &[x509::Extension<'_, &[u64]>] = &[];
let cert_result = Certificate::generate_self_signed(
&mut yubikey,
slot,
serial,
None,
&[RelativeDistinguishedName::common_name("testSubject")],
generated,
extensions,
);
assert!(cert_result.is_ok());
let cert = cert_result.unwrap();
trace!("cert: {:?}", cert);
cert
}
#[test]
#[ignore]
fn generate_self_signed_rsa_cert() {
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024);
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
_ => unreachable!(),
};
let data = cert.as_ref();
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 hash = Sha256::digest(msg);
assert!(pubkey
.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(SHA2_256)
},
&hash,
sig
)
.is_ok());
}
#[test]
#[ignore]
fn generate_self_signed_ec_cert() {
let cert = generate_self_signed_cert(AlgorithmId::EccP256);
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::EcP256(pubkey) => pubkey,
_ => unreachable!(),
};
let data = cert.as_ref();
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 = p256::ecdsa::Signature::from_der(&data[sig_start..]).unwrap();
let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey.as_bytes()).unwrap();
use p256::ecdsa::signature::Verifier;
assert!(vk.verify(msg, &sig).is_ok());
}