Compare commits

..

287 Commits

Author SHA1 Message Date
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
Tony Arcieri 3a41fdc3bc yubikey-cli v0.0.1 2019-12-02 11:54:41 -08:00
Tony Arcieri c377f226e2 Merge pull request #53 from iqlusioninc/yubikey-piv/v0.0.3
yubikey-piv v0.0.3
2019-12-02 11:40:43 -08:00
Tony Arcieri da897b99bb yubikey-piv v0.0.3 2019-12-02 11:17:10 -08:00
Tony Arcieri 9fa2d1c051 Merge pull request #52 from iqlusioninc/cli
cli: Initial `yubikey-cli` utility with `list` command
2019-12-02 11:00:09 -08:00
Tony Arcieri 07f70bccb5 cli: Initial yubikey-cli utility with list command
Adds a `yubikey-cli` crate to the workspace, with a `yubikey` binary,
which presently provides a `list` command for listing detected readers.

Dependencies:

- `env_logger`: logging
- `gumdrop`: argument parsing
- `termcolor`: colored terminal output

As this repo now contains a binary, it also checks in `Cargo.lock`.
2019-12-02 10:42:17 -08:00
Tony Arcieri 8e1469cff6 Merge pull request #51 from iqlusioninc/readers
readers: Initial `Readers` enumerator for detecting YubiKeys
2019-12-02 10:20:04 -08:00
Tony Arcieri 9ce2ffe938 readers: Use Reader to connect to YubiKey
Removes the legacy API inherited from `yubico-piv-tool` and uses
the `reader` module exclusively for selecting and opening the PC/SC
reader.
2019-12-02 10:11:58 -08:00
Tony Arcieri 589ca3de12 readers: Initial Readers enumerator for detecting YubiKeys
Adds a `yubikey_piv::Readers` type which opens a PC/SC context and can
enumerate detected PC/SC readers with a slightly more ergonomic API than
what's provided in the upstream crate.

Does not support actually instantiating a `YubiKey` from a `Reader<'_>`
yet, but ideally all connections to YubiKeys should go through this API.
2019-12-02 09:32:42 -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
Tony Arcieri ae071e706c Merge pull request #45 from str4d/certificate-parsing
Certificate parsing
2019-12-01 11:17:49 -08:00
Jack Grigg cd704c28d7 Extract OID strings as constants 2019-12-01 18:42:12 +00:00
Jack Grigg 3a283aca40 Use ecdsa crate for EC point representations 2019-12-01 18:23:57 +00:00
Jack Grigg e72ee5c60e Parse EC public keys within certificates 2019-12-01 16:54:22 +00:00
Jack Grigg 9ee1494c6f Parse RSA public keys within certificates 2019-12-01 16:09:59 +00:00
Jack Grigg d3e565ef55 Derive PartialEq for SlotId 2019-12-01 15:35:00 +00:00
Tony Arcieri 2bdeca0069 Merge pull request #44 from str4d/more-enums
Convert SlotId and AlgorithmId into enums
2019-11-30 14:28:36 -08:00
Jack Grigg bc95d8b7b9 Delete unnecessary commented-out code
We will handle the CardManagement slot separately.
2019-11-30 22:18:31 +00:00
Jack Grigg 11c93d6421 Inline SlotId constants 2019-11-30 22:01:22 +00:00
Jack Grigg afca0fec0a Convert AlgorithmId into an enum
3DES also has an algorithm ID, but it is completely disjoint from the
key algorithms, and can be handled separately later.
2019-11-30 20:47:37 +00:00
Tony Arcieri c8837d485f Merge pull request #43 from iqlusioninc/pin-secrecy
Use `secrecy` crate for storing `CachedPin`
2019-11-30 12:39:33 -08:00
Jack Grigg 12b5bd1e3c Convert SlotId into an enum 2019-11-30 20:15:16 +00:00
Jack Grigg c3698dcffb Key::list: Skip Certificate::new for empty buffers
This matches the C code behaviour.
2019-11-30 20:15:12 +00:00
Tony Arcieri 6a16c59567 Use secrecy crate for storing CachedPin
The `SecretVec` type automatically handles zeroing and may prevent
accidental exposure of the cached PIN via `Debug`.
2019-11-30 12:11:53 -08: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
Tony Arcieri 8e38cf6c4e Merge pull request #42 from carl-wallace/develop
Change CHUID struct to hold complete CHUID value. Add getters for sub components. Add additional consts to support this. Modified CCCID struct to be public (as prelude to similar treatment).
2019-11-30 11:44:51 -08:00
Tony Arcieri ac665f9ec9 Merge pull request #40 from str4d/pin-fixups
PIN fixups
2019-11-30 11:43:35 -08:00
Carl Wallace 77302af21e address formatting per cargo fmt 2019-11-30 14:22:33 -05:00
Carl Wallace 78288b4200 address formatting and documentation issues flagged by clippy 2019-11-30 14:13:34 -05:00
Jack Grigg a61a6fd94b Define more YubiKey-recognized status words
Recognized values sourced from https://github.com/Yubico/yubikey-manager
NotFoundError and NoSpaceError are specified in SP 800-73-4 Table 6.
2019-11-30 15:39:11 +00:00
Jack Grigg cfef291ad9 Use u16 for raw StatusWords 2019-11-30 15:39:10 +00:00
Jack Grigg 4b5cd8dd45 Make PIN verification failure a StatusWord case
Retry count is now u8, as  it cannot exceed 16 (being returned in the
lower half of SW2).
2019-11-30 15:39:09 +00:00
Jack Grigg 9fe363661e verify_pin: Don't set APDU data for empty PIN 2019-11-30 15:16:15 +00:00
Tony Arcieri 4af95edc74 Merge pull request #39 from iqlusioninc/safety-dance-and-other-readme-badge-fixups
Add #![forbid(unsafe_code)]; fix up README.md badges and links
2019-11-29 10:18:25 -08:00
Tony Arcieri 7f3d821df2 Add #![forbid(unsafe_code)]; fix up README.md badges and links
- Forbids unsafe code
- Adds a "Safety Dance" badge
- Fixes the GitHub Actions status badge
- Fixes up links that changed with the move to `iqlusioninc` org
2019-11-29 10:06:52 -08:00
Tony Arcieri 2f963a15d0 Merge pull request #37 from str4d/safety-rails
Safety rails
2019-11-29 09:56:54 -08:00
Carl Wallace 4210571da3 Change CHUID struct to hold complete CHUID value. Add getters for subcomponents. Add additional consts to support this. Modified CCCID struct to be public (as prelude to similar treatment). 2019-11-29 09:31:24 -05:00
Jack Grigg 1db929c10f Mark excluded nested match branches as unreachable 2019-11-29 00:09:08 +00:00
Jack Grigg 8240575bb4 Rewrite YubiKey::import_private_key without unsafe 2019-11-28 23:44:16 +00:00
Jack Grigg 1935216cf3 Rewrite MsRoots::read without unsafe 2019-11-28 23:43:02 +00:00
Jack Grigg 7c08674fac Use slice::copy_within in metadata::read 2019-11-28 23:43:01 +00:00
Jack Grigg 8b86a0f578 Rewrite metadata::get_item without unsafe 2019-11-28 23:42:55 +00:00
Jack Grigg bd5669d9ef Rewrite metadata::set_item without unsafe
Also re-introduces some comments that were lost during corrosion.
2019-11-28 23:06:09 +00:00
Jack Grigg afb6a9479e Use slice::copy_within in read_certificate 2019-11-28 23:03:11 +00:00
Jack Grigg 48d0a2ab04 Use slice::copy_from_slice in Transaction::change_pin 2019-11-28 23:02:33 +00:00
Tony Arcieri 82b4bbb35d Merge pull request #36 from carl-wallace/develop
Make anonymous CHUID struct public, remove spurious subtraction of two bytes in transfer_data
2019-11-27 12:37:06 -08:00
Carl Wallace 13b350f822 change length comparison to is_empty check per clippy 2019-11-27 15:26:13 -05:00
Carl Wallace 0f1ef2f519 Make anonymous field of CHUID struct public. Remove spurious -2 instances inside Transaction::transfer_data (the Response object is already eating the status words) 2019-11-27 15:09:53 -05:00
Tony Arcieri d799e9c35b Merge pull request #34 from tarcieri/have-encrypt-sign-return-buffer
Have `sign_data` and `decrypt_data` return a `Buffer`
2019-11-26 11:21:38 -08:00
Tony Arcieri 5bf27f5422 Have sign_data and decrypt_data return a Buffer 2019-11-26 11:06:11 -08:00
Tony Arcieri ecea0081b5 Merge pull request #33 from tarcieri/ins-enum
`Ins` (APDU instruction codes) enum
2019-11-26 10:05:35 -08:00
Tony Arcieri debde6e765 Ins (APDU instruction codes) enum
Converts a bag of constant values (`YKPIV_INS_*`) into an enum
representing APDU instruction codes (a.k.a. `ins`).

Among other things, this makes the `Debug` output for `APDU` more human
meaningful, since it can print a text label for the instruction rather
than a code number, which is helpful in trace debugging.
2019-11-26 09:52:19 -08:00
Tony Arcieri 3fa5555943 Merge pull request #32 from tarcieri/factor-responses-into-apdu-module
Factor `Response` into `apdu` module; improved debugging
2019-11-26 09:26:55 -08:00
Tony Arcieri d3af2f2d80 Factor Response into apdu module; improved debugging
This commit merges the `apdu` and `response` modules: the responses are
APDU responses, and so the two are related.

This also moves the `trace` logging into the APDU type, which allows it
to display `Debug` output for APDUs and responses, which makes it easier
to understand what's going on (and will be even better once instructions
are converted into an enum so you can actually see what's happening).
2019-11-26 09:15:48 -08:00
Tony Arcieri 5fab09e54d Merge pull request #31 from tarcieri/v0.0.2
v0.0.2
2019-11-25 16:59:02 -08:00
42 changed files with 5762 additions and 2642 deletions
+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: master
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: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --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: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --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: -- -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
-1
View File
@@ -1,3 +1,2 @@
/target /target
**/*.rs.bk **/*.rs.bk
Cargo.lock
+153 -14
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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.2] (2019-11-25) ## 0.4.0 (2021-07-12)
### 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])
### Changed
- Use `Reader` to connect to `YubiKey` ([#51])
- Convert `SlotId` and `AlgorithmId` into enums ([#44])
- Use `secrecy` crate for storing `CachedPin` ([#43])
- Change `CHUID` struct to hold complete CHUID value ([#42])
- Eliminate all usages of `unsafe` ([#37], [#39])
- Make anonymous CHUID struct public ([#36])
- Have `sign_data` and `decrypt_data` return a `Buffer` ([#34])
- `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#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
## yubikey-piv 0.0.2 (2019-11-25)
### Added ### Added
- `untested` Cargo feature to mark untested functionality ([#30]) - `untested` Cargo feature to mark untested functionality ([#30])
- Initial connect test and docs ([#19]) - Initial connect test and docs ([#19])
@@ -19,17 +159,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `log` crate for logging ([#7]) - Use `log` crate for logging ([#7])
- Replace `ErrorKind::Ok` with `Result` ([#6]) - Replace `ErrorKind::Ok` with `Result` ([#6])
[0.0.2]: https://github.com/tarcieri/yubikey-piv.rs/pull/31 [#30]: https://github.com/iqlusioninc/yubikey.rs/pull/30
[#30]: https://github.com/tarcieri/yubikey-piv.rs/pull/30 [#19]: https://github.com/iqlusioninc/yubikey.rs/pull/19
[#19]: https://github.com/tarcieri/yubikey-piv.rs/pull/19 [#17]: https://github.com/iqlusioninc/yubikey.rs/pull/17
[#17]: https://github.com/tarcieri/yubikey-piv.rs/pull/17 [#15]: https://github.com/iqlusioninc/yubikey.rs/pull/15
[#15]: https://github.com/tarcieri/yubikey-piv.rs/pull/15 [#13]: https://github.com/iqlusioninc/yubikey.rs/pull/13
[#13]: https://github.com/tarcieri/yubikey-piv.rs/pull/13 [#10]: https://github.com/iqlusioninc/yubikey.rs/pull/10
[#10]: https://github.com/tarcieri/yubikey-piv.rs/pull/10 [#9]: https://github.com/iqlusioninc/yubikey.rs/pull/9
[#9]: https://github.com/tarcieri/yubikey-piv.rs/pull/9 [#8]: https://github.com/iqlusioninc/yubikey.rs/pull/8
[#8]: https://github.com/tarcieri/yubikey-piv.rs/pull/8 [#7]: https://github.com/iqlusioninc/yubikey.rs/pull/7
[#7]: https://github.com/tarcieri/yubikey-piv.rs/pull/7 [#6]: https://github.com/iqlusioninc/yubikey.rs/pull/6
[#6]: https://github.com/tarcieri/yubikey-piv.rs/pull/6
## 0.0.1 (2019-11-18) ## yubikey-piv 0.0.1 (2019-11-18)
- It typechecks, ship it! - Initial release
+8 -10
View File
@@ -6,8 +6,8 @@ In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience, size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and education, socio-economic status, nationality, personal appearance, race,
orientation. religion, or sexual identity and orientation.
## Our Standards ## Our Standards
@@ -23,7 +23,7 @@ include:
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or * The use of sexualized language or imagery and unwelcome sexual attention or
advances advances
* Trolling, insulting/derogatory comments, and personal or political attacks * Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or electronic * Publishing others' private information, such as a physical or electronic
@@ -55,8 +55,8 @@ further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [bascule@gmail.com]. All reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io).
complaints will be reviewed and investigated and will result in a response that All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately. Further details of specific enforcement policies may be posted separately.
@@ -65,12 +65,10 @@ Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other faith may face temporary or permanent repercussions as determined by other
members of the project's leadership. members of the project's leadership.
[bascule@gmail.com]: mailto:bascule@gmail.com
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version] available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+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. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
Generated
+1033
View File
File diff suppressed because it is too large Load Diff
+36 -17
View File
@@ -1,39 +1,58 @@
[package] [package]
name = "yubikey-piv" name = "yubikey"
version = "0.0.2" # Also update html_root_url in lib.rs when bumping this version = "0.4.0" # Also update html_root_url in lib.rs when bumping this
description = """ description = """
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV) Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
application providing general-purpose public-key signing and encryption support for hardware-backed public-key decryption and digital signatures using
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384) the Personal Identity Verification (PIV) application. Supports RSA (1024/2048)
algorithms (e.g, PKCS#1v1.5, ECDSA) 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" edition = "2018"
license = "BSD-2-Clause" license = "BSD-2-Clause"
repository = "https://github.com/tarcieri/yubikey-piv.rs" repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md" readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"] categories = ["api-bindings", "cryptography", "hardware-support"]
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"] keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
[badges] [workspace]
maintenance = { status = "experimental" } members = [".", "cli"]
[dependencies] [dependencies]
des = "0.3" chrono = "0.4"
getrandom = "0.1" cookie-factory = "0.3"
hmac = "0.7" der-parser = "5"
des = "0.7"
elliptic-curve = "0.10"
hmac = "0.11"
log = "0.4" log = "0.4"
pbkdf2 = "0.3" nom = "6"
num-bigint-dig = { version = "0.7", features = ["rand"] }
num-traits = "0.2"
num-integer = "0.1"
pbkdf2 = { version = "0.8", default-features = false }
p256 = "0.9"
p384 = "0.8"
pcsc = "2" pcsc = "2"
sha-1 = "0.8" rand_core = { version = "0.6", features = ["std"] }
rsa = "0.4"
secrecy = "0.7"
sha-1 = "0.9"
sha2 = "0.9"
subtle = "2" subtle = "2"
subtle-encoding = "0.5"
uuid = { version = "0.8", features = ["v4"] }
x509 = "0.2"
x509-parser = "0.9"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.7" env_logger = "0.8"
lazy_static = "1"
[features] [features]
untested = [] untested = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+53 -72
View File
@@ -1,17 +1,19 @@
<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] [![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link] [![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image] [![2-Clause BSD Licensed][license-image]][license-link]
![Rust Version][rustc-image] ![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image] ![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link] [![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link] [![Gitter Chat][gitter-image]][gitter-link]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] driver Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
with general-purpose public-key encryption and signing support. with support for public-key encryption and digital signatures using the
[Personal Identity Verification (PIV)][PIV] application.
[Documentation][docs-link] [Documentation][docs-link]
@@ -35,15 +37,21 @@ endorsed by Yubico.
## Minimum Supported Rust Version ## Minimum Supported Rust Version
- Rust **1.39+** Rust **1.51** or newer.
## Supported YubiKeys ## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series - [YubiKey 4] series
- [YubiKey 5] series - [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Supported Operating Systems
- Linux
- macOS
- Windows
## Security Warning ## Security Warning
@@ -52,40 +60,6 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK! 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 ## Testing
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
@@ -113,14 +87,17 @@ Application Protocol Data Unit (APDU) messages, use the `trace` log level:
``` ```
running 1 test running 1 test
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID' [INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully [INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8] [TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [160, 0, 0, 3, 8] }
[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0] [TRACE yubikey::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0] [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_piv::transaction] <<< [5, 1, 2, 144, 0] [TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0] [TRACE yubikey::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 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 test connect ... ok
``` ```
@@ -142,12 +119,12 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License ## 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 Yubico's [yubico-piv-tool], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][BSDL], which this library inherits was licensed under a [2-Clause BSD License][BSDL], which this library inherits
as a derived work. as a derived work.
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -183,20 +160,24 @@ or conditions.
[//]: # (badges) [//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-piv.svg [crate-image]: https://img.shields.io/crates/v/yubikey.svg
[crate-link]: https://crates.io/crates/yubikey-piv [crate-link]: https://crates.io/crates/yubikey
[docs-image]: https://docs.rs/yubikey-piv/badge.svg [docs-image]: https://docs.rs/yubikey/badge.svg
[docs-link]: https://docs.rs/yubikey-piv/ [docs-link]: https://docs.rs/yubikey/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg [license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-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
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg [maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[build-image]: https://github.com/tarcieri/yubikey-piv.rs/workflows/Rust/badge.svg [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[build-link]: https://github.com/tarcieri/yubikey-piv.rs/actions [safety-link]: https://github.com/rust-secure-code/safety-dance/
[gitter-image]: https://badges.gitter.im/yubihsm-piv-rs.svg [build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[gitter-link]: https://gitter.im/yubikey-piv-rs/community [build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
[//]: # (general links) [//]: # (general links)
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[PIV]: https://piv.idmanagement.gov/ [PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/ [Yubico]: https://www.yubico.com/
@@ -206,18 +187,18 @@ or conditions.
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/ [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode [Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/ [cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/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 [BSDL]: https://opensource.org/licenses/BSD-2-Clause
[//]: # (github issues) [//]: # (github issues)
[#18]: https://github.com/tarcieri/yubikey-piv.rs/issues/18 [#18]: https://github.com/iqlusioninc/yubikey.rs/issues/18
[#20]: https://github.com/tarcieri/yubikey-piv.rs/issues/20 [#20]: https://github.com/iqlusioninc/yubikey.rs/issues/20
[#21]: https://github.com/tarcieri/yubikey-piv.rs/issues/21 [#21]: https://github.com/iqlusioninc/yubikey.rs/issues/21
[#22]: https://github.com/tarcieri/yubikey-piv.rs/issues/22 [#22]: https://github.com/iqlusioninc/yubikey.rs/issues/22
[#23]: https://github.com/tarcieri/yubikey-piv.rs/issues/23 [#23]: https://github.com/iqlusioninc/yubikey.rs/issues/23
[#24]: https://github.com/tarcieri/yubikey-piv.rs/issues/24 [#24]: https://github.com/iqlusioninc/yubikey.rs/issues/24
[#25]: https://github.com/tarcieri/yubikey-piv.rs/issues/25 [#25]: https://github.com/iqlusioninc/yubikey.rs/issues/25
[#26]: https://github.com/tarcieri/yubikey-piv.rs/issues/26 [#26]: https://github.com/iqlusioninc/yubikey.rs/issues/26
[#27]: https://github.com/tarcieri/yubikey-piv.rs/issues/27 [#27]: https://github.com/iqlusioninc/yubikey.rs/issues/27
[#28]: https://github.com/tarcieri/yubikey-piv.rs/issues/28 [#28]: https://github.com/iqlusioninc/yubikey.rs/issues/28
+39
View File
@@ -0,0 +1,39 @@
# Changelog
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.3.0 (2021-03-22)
### Changed
- Bump `yubikey` 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` 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` to v0.1.0 ([#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
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "yubikey-cli"
version = "0.4.0-pre"
description = """
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
"""
authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
[dependencies]
gumdrop = "0.8"
env_logger = "0.8"
lazy_static = "1"
log = "0.4"
sha2 = "0.9"
subtle-encoding = "0.5"
termcolor = "1"
x509-parser = "0.9"
yubikey = { version = "0.4", path = ".." }
+112
View File
@@ -0,0 +1,112 @@
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png" width="150" height="110">
# yubikey-cli.rs
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![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]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] CLI
utility with general-purpose public-key encryption and signing support.
[Documentation][docs-link]
## Minimum Supported Rust Version
Rust **1.51** or newer.
## Supported YubiKeys
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## 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!
## Status
WIP. Check back later.
## 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
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
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.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you shall be licensed under the
[2-Clause BSD License][BSDL] as shown above, without any additional terms
or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-cli.svg
[crate-link]: https://crates.io/crates/yubikey-cli
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[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.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
[//]: # (general links)
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
+16
View File
@@ -0,0 +1,16 @@
//! `yubikey` command-line utility
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
use gumdrop::Options;
use yubikey_cli::commands::YubiKeyCli;
fn main() {
YubiKeyCli::parse_args_default_or_exit().run();
}
+155
View File
@@ -0,0 +1,155 @@
//! Commands of the CLI application
pub mod readers;
pub mod status;
use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal::{self, STDOUT};
use gumdrop::Options;
use std::{
env,
io::{self, Write},
process::exit,
};
use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility
#[derive(Debug, Options)]
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 {
/// 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
terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
match &self.command {
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);
}),
}
}
}
/// Subcommands of this application
#[derive(Debug, Options)]
pub enum Commands {
/// `help` subcommand
#[options(help = "show help for a command")]
Help(HelpOpts),
/// `version` subcommand
#[options(help = "display version information")]
Version(VersionOpts),
/// `readers` subcommand
#[options(help = "list detected readers")]
Readers(ReadersCmd),
/// `status` subcommand
#[options(help = "show yubikey status")]
Status(StatusCmd),
}
impl Commands {
/// Run the given command
pub fn run(&self, yubikey: YubiKey) {
match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(),
Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
}
}
}
/// Help options
#[derive(Debug, Options)]
pub struct HelpOpts {
#[options(free, help = "subcommand to get help for")]
free: Vec<String>,
}
impl HelpOpts {
fn run(&self) {
if let Some(command) = self.free.first() {
if let Some(usage) = Commands::command_usage(command) {
println!("{}", usage);
exit(1);
}
}
println!("{}", Commands::usage());
}
}
/// Version options
#[derive(Debug, Options)]
pub struct VersionOpts {}
impl VersionOpts {
/// Display version information
pub fn run(&self) {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}
}
+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(())
}
}
+62
View File
@@ -0,0 +1,62 @@
//! Print device status
use crate::terminal::STDOUT;
use gumdrop::Options;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{piv::*, YubiKey};
use crate::print_cert_info;
// 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(())
}
}
+92
View File
@@ -0,0 +1,92 @@
//! `yubikey` command-line utility.
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#[macro_use]
pub mod terminal;
pub mod commands;
use log::debug;
use sha2::{Digest, Sha256};
use std::io::{self, Write};
use std::str;
use subtle_encoding::hex;
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Write information about certificate found in slot a la yubico-piv-tool output.
pub fn print_cert_info(
yubikey: &mut YubiKey,
slot: SlotId,
stream: &mut StandardStreamLock<'_>,
) -> 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(())
}
+165
View File
@@ -0,0 +1,165 @@
//! Status messages
use lazy_static::lazy_static;
use std::io::{self, Write};
use std::sync::Mutex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
/// Print a success status message (in green if colors are enabled)
#[macro_export]
macro_rules! status_ok {
($status:expr, $msg:expr) => {
$crate::terminal::Status::new()
.justified()
.bold()
.color(termcolor::Color::Green)
.status($status)
.print_stdout($msg);
};
($status:expr, $fmt:expr, $($arg:tt)+) => {
$crate::status_ok!($status, format!($fmt, $($arg)+));
};
}
/// Print a warning status message (in yellow if colors are enabled)
#[macro_export]
macro_rules! status_warn {
($msg:expr) => {
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Yellow)
.status("warning:")
.print_stdout($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_warn!(format!($fmt, $($arg)+));
};
}
/// Print an error message (in red if colors are enabled)
#[macro_export]
macro_rules! status_err {
($msg:expr) => {
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Red)
.status("error:")
.print_stderr($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_err!(format!($fmt, $($arg)+));
};
}
lazy_static! {
/// Color configuration
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
/// Standard output
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice());
/// Standard error
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice());
}
/// Obtain the color configuration.
///
/// Panics if no configuration has been provided.
fn get_color_choice() -> ColorChoice {
let choice = COLOR_CHOICE.lock().unwrap();
*choice
.as_ref()
.expect("terminal stream accessed before initialized!")
}
/// Set the color configuration.
///
/// Panics if the terminal has already been configured.
pub(super) fn set_color_choice(color_choice: ColorChoice) {
let mut choice = COLOR_CHOICE.lock().unwrap();
assert!(choice.is_none(), "terminal colors already configured!");
*choice = Some(color_choice);
}
/// Status message builder
#[derive(Clone, Debug, Default)]
pub struct Status {
/// Should the status be justified?
justified: bool,
/// Should colors be bold?
bold: bool,
/// Color in which status should be displayed
color: Option<Color>,
/// Prefix of the status message (e.g. `Success`)
status: Option<String>,
}
impl Status {
/// Create a new status message with default settings
pub fn new() -> Self {
Self::default()
}
/// Justify status on display
pub fn justified(mut self) -> Self {
self.justified = true;
self
}
/// Make colors bold
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
/// Set the colors used to display this message
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
/// Set a status message to display
pub fn status<S>(mut self, msg: S) -> Self
where
S: ToString,
{
self.status = Some(msg.to_string());
self
}
/// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg)
.expect("error printing to stdout!")
}
/// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg)
.expect("error printing to stderr!")
}
/// Print the given message
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))?;
if let Some(status) = self.status {
if self.justified {
write!(s, "{:>12}", status)?;
} else {
write!(s, "{}", status)?;
}
}
s.reset()?;
writeln!(s, " {}", msg.as_ref())?;
s.flush()?;
Ok(())
}
}
+338 -36
View File
@@ -30,8 +30,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{error::Error, response::Response, transaction::Transaction, Buffer}; use crate::{transaction::Transaction, Buffer, Result};
use std::fmt::{self, Debug}; use log::trace;
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
/// Maximum amount of command data that can be included in an APDU /// Maximum amount of command data that can be included in an APDU
@@ -40,13 +40,13 @@ const APDU_DATA_MAX: usize = 0xFF;
/// Application Protocol Data Unit (APDU). /// Application Protocol Data Unit (APDU).
/// ///
/// These messages are packets used to communicate with the YubiKey. /// These messages are packets used to communicate with the YubiKey.
#[derive(Clone)] #[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) /// Instruction class: indicates the type of command (e.g. inter-industry or proprietary)
cla: u8, cla: u8,
/// Instruction code: indicates the specific command (e.g. "write data") /// Instruction code: indicates the specific command (e.g. "write data")
ins: u8, ins: Ins,
/// Instruction parameter 1 for the command (e.g. offset into file at which to write the data) /// Instruction parameter 1 for the command (e.g. offset into file at which to write the data)
p1: u8, p1: u8,
@@ -58,12 +58,12 @@ pub(crate) struct APDU {
data: Vec<u8>, data: Vec<u8>,
} }
impl APDU { impl Apdu {
/// Create a new APDU with the given instruction code /// Create a new APDU with the given instruction code
pub fn new(ins: u8) -> Self { pub fn new(ins: impl Into<Ins>) -> Self {
Self { Self {
cla: 0, cla: 0,
ins, ins: ins.into(),
p1: 0, p1: 0,
p2: 0, p2: 0,
data: vec![], data: vec![],
@@ -71,7 +71,6 @@ impl APDU {
} }
/// Set this APDU's class /// Set this APDU's class
#[cfg(feature = "untested")]
pub fn cla(&mut self, value: u8) -> &mut Self { pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value; self.cla = value;
self self
@@ -84,7 +83,6 @@ impl APDU {
} }
/// Set both parameters for this APDU /// Set both parameters for this APDU
#[cfg(feature = "untested")]
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self { pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1; self.p1 = p1;
self.p2 = p2; self.p2 = p2;
@@ -111,16 +109,18 @@ impl APDU {
} }
/// Transmit this APDU using the given card transaction /// 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> {
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?; trace!(">>> {:?}", self);
Ok(Response::from_bytes(response_bytes)) let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
Ok(response)
} }
/// Consume this APDU and return a self-zeroizing buffer /// Serialize this APDU as a self-zeroizing byte buffer
pub fn to_bytes(&self) -> Buffer { pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len()); let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla); bytes.push(self.cla);
bytes.push(self.ins); bytes.push(self.ins.code());
bytes.push(self.p1); bytes.push(self.p1);
bytes.push(self.p2); bytes.push(self.p2);
bytes.push(self.data.len() as u8); bytes.push(self.data.len() as u8);
@@ -129,33 +129,335 @@ impl APDU {
} }
} }
impl Debug for APDU { impl Drop for Apdu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"APDU {{ cla: {}, ins: {}, p1: {}, p2: {}, lc: {}, data: {:?} }}",
self.cla,
self.ins,
self.p1,
self.p2,
self.data.len(),
self.data.as_slice()
)
}
}
impl Drop for APDU {
fn drop(&mut self) { fn drop(&mut self) {
self.zeroize(); self.zeroize();
} }
} }
impl Zeroize for APDU { impl Zeroize for Apdu {
fn zeroize(&mut self) { fn zeroize(&mut self) {
self.cla.zeroize(); // Only `data` may contain secrets
self.ins.zeroize();
self.p1.zeroize();
self.p2.zeroize();
self.data.zeroize(); self.data.zeroize();
} }
} }
/// APDU instruction codes
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Ins {
/// Verify
Verify,
/// Change reference
ChangeReference,
/// Reset retry
ResetRetry,
/// Generate asymmetric
GenerateAsymmetric,
/// Authenticate
Authenticate,
/// Get data
GetData,
/// Put data
PutData,
/// Select application
SelectApplication,
/// Get response APDU
GetResponseApdu,
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
/// Set MGM key
SetMgmKey,
/// Import key
ImportKey,
/// Get version
GetVersion,
/// Reset device
Reset,
/// Set PIN retries
SetPinRetries,
/// Generate attestation certificate for asymmetric key
Attest,
/// Get device serial
GetSerial,
/// Other/unrecognized instruction codes
Other(u8),
}
impl Ins {
/// Get the code that corresponds to this instruction
pub fn code(self) -> u8 {
match self {
Ins::Verify => 0x20,
Ins::ChangeReference => 0x24,
Ins::ResetRetry => 0x2c,
Ins::GenerateAsymmetric => 0x47,
Ins::Authenticate => 0x87,
Ins::GetData => 0xcb,
Ins::PutData => 0xdb,
Ins::SelectApplication => 0xa4,
Ins::GetResponseApdu => 0xc0,
Ins::SetMgmKey => 0xff,
Ins::ImportKey => 0xfe,
Ins::GetVersion => 0xfd,
Ins::Reset => 0xfb,
Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::Other(code) => code,
}
}
}
impl From<u8> for Ins {
fn from(code: u8) -> Self {
match code {
0x20 => Ins::Verify,
0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry,
0x47 => Ins::GenerateAsymmetric,
0x87 => Ins::Authenticate,
0xcb => Ins::GetData,
0xdb => Ins::PutData,
0xa4 => Ins::SelectApplication,
0xc0 => Ins::GetResponseApdu,
0xff => Ins::SetMgmKey,
0xfe => Ins::ImportKey,
0xfd => Ins::GetVersion,
0xfb => Ins::Reset,
0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial,
code => Ins::Other(code),
}
}
}
impl From<Ins> for u8 {
fn from(ins: Ins) -> u8 {
ins.code()
}
}
/// APDU responses
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
data: Vec<u8>,
}
impl Response {
/// Create a new response from the given status words and buffer
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
pub fn code(&self) -> u16 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response data
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.data()
}
}
impl Drop for Response {
fn drop(&mut self) {
self.zeroize();
}
}
impl From<Vec<u8>> for Response {
fn from(mut bytes: Vec<u8>) -> Self {
if bytes.len() < 2 {
return Response {
status_words: StatusWords::None,
data: bytes,
};
}
let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16),
);
let len = bytes.len() - 2;
bytes.truncate(len);
Response {
status_words: sw,
data: bytes,
}
}
}
impl Zeroize for Response {
fn zeroize(&mut self) {
// Only `data` may contain secrets
self.data.zeroize();
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
NoInputDataError,
/// PIN verification failure
VerifyFailError {
/// Remaining verification attempts
tries: u8,
},
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L55
WrongLengthError,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L58
DataInvalidError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L59
ConditionsNotSatisfiedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L60
CommandNotAllowedError,
/// Incorrect parameter in command data field
IncorrectParamError,
/// Data object or application not found
NotFoundError,
/// Not enough memory
NoSpaceError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L65
CommandAbortedError,
/// Other/unrecognized status words
Other(u16),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u16 {
match self {
StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u16> for StatusWords {
fn from(sw: u16) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u16 {
sw.code()
}
}
+57 -20
View File
@@ -30,8 +30,20 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey}; use crate::{Error, Result, YubiKey};
use getrandom::getrandom; 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 /// Cardholder Capability Container (CCC) Template
/// ///
@@ -48,38 +60,63 @@ const CCC_TMPL: &[u8] = &[
0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00,
]; ];
/// Cardholder Capability Container (CCC) Identifier /// Cardholder Capability Container (CCC) Identifier Card ID.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct CCCID([u8; YKPIV_CCCID_SIZE]); pub struct CardId(pub [u8; Self::BYTE_SIZE]);
impl CCCID { impl CardId {
/// Generate a random CCCID /// CCCID size in bytes
pub fn generate() -> Result<Self, Error> { pub const BYTE_SIZE: usize = 14;
let mut id = [0u8; YKPIV_CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?; /// Generate a random CCC Card ID
Ok(CCCID(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 Ccc(pub [u8; Self::BYTE_SIZE]);
impl Ccc {
/// 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 /// 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 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() { if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
let mut cccid = [0u8; YKPIV_CCCID_SIZE]; Ok(Self(response[..Self::BYTE_SIZE].try_into().unwrap()))
cccid.copy_from_slice(&response[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]);
Ok(CCCID(cccid))
} }
/// Get Cardholder Capability Container (CCC) ID /// Set Cardholder Capability Container (CCC) ID
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 = CCC_TMPL.to_vec(); 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()?; let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf) txn.save_object(OBJ_CAPABILITY, &buf)
}
}
impl Display for Ccc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
} }
} }
+572 -73
View File
@@ -1,4 +1,4 @@
//! YubiKey Certificates //! X.509 certificate support.
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/> // <https://github.com/Yubico/yubico-piv-tool/>
@@ -31,25 +31,428 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{ use crate::{
consts::*, consts::CB_OBJ_MAX,
error::Error, error::{Error, Result},
key::{self, SlotId}, piv::{sign_data, AlgorithmId, SlotId},
serialization::*, serialization::*,
transaction::Transaction, transaction::Transaction,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, Buffer,
}; };
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error; use log::error;
use std::ptr; 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 std::ops::DerefMut;
use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing; use zeroize::Zeroizing;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE;
/// A serial number for a [`Certificate`].
#[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
}
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
impl TryFrom<&[u8]> for Serial {
type Error = 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`].
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Public key
pubkey: RSAPublicKey,
},
/// EC P-256 keys
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKeyInfo({:?})", self.algorithm())
}
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() {
1024 => AlgorithmId::Rsa1024,
2048 => AlgorithmId::Rsa2048,
_ => return Err(Error::AlgorithmError),
},
pubkey,
})
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
let algorithm_parameters = subject_pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::InvalidObject)?;
match read_pki::ec_parameters(algorithm_parameters)? {
AlgorithmId::EccP256 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256)
.map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384)
.map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
_ => Err(Error::InvalidObject),
}
}
/// Returns the algorithm that this public key can be used with.
pub fn algorithm(&self) -> AlgorithmId {
match self {
PublicKeyInfo::Rsa { algorithm, .. } => *algorithm,
PublicKeyInfo::EcP256(_) => AlgorithmId::EccP256,
PublicKeyInfo::EcP384(_) => AlgorithmId::EccP384,
}
}
}
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates /// Certificates
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate(Buffer); 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 { 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 /// 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 txn = yubikey.begin_transaction()?;
let buf = read_certificate(&txn, slot)?; let buf = read_certificate(&txn, slot)?;
@@ -57,25 +460,25 @@ impl Certificate {
return Err(Error::InvalidObject); return Err(Error::InvalidObject);
} }
Ok(Certificate(buf)) Certificate::from_bytes(buf)
} }
/// Write this certificate into the YubiKey in the given slot /// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> { pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: CertInfo) -> Result<()> {
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.0), certinfo, max_size) write_certificate(&txn, slot, Some(&self.data), certinfo)
} }
/// Delete a certificate located at the given slot of the given YubiKey /// Delete a certificate located at the given slot of the given YubiKey
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> { #[cfg(feature = "untested")]
let max_size = yubikey.obj_size_max(); #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> {
let txn = yubikey.begin_transaction()?; 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 /// 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(); let cert = cert.into();
if cert.is_empty() { if cert.is_empty() {
@@ -83,27 +486,63 @@ impl Certificate {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
Ok(Certificate(cert)) let parsed_cert = match parse_x509_certificate(&cert) {
Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject),
};
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
serial,
issuer,
subject,
subject_pki,
data: cert,
})
}
/// Returns the serial number of the certificate.
pub fn serial(&self) -> &Serial {
&self.serial
}
/// 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
}
/// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> &PublicKeyInfo {
&self.subject_pki
} }
/// Extract the inner buffer /// Extract the inner buffer
pub fn into_buffer(self) -> Buffer { pub fn into_buffer(self) -> Buffer {
self.0 self.data
} }
} }
impl AsRef<[u8]> for Certificate { impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
self.0.as_ref() self.data.as_ref()
} }
} }
/// Read certificate /// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> { pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let mut len: usize = 0; let object_id = slot.object_id();
let object_id = key::slot_object(slot)?;
let mut buf = match txn.fetch_object(object_id) { let buf = match txn.fetch_object(object_id) {
Ok(b) => b, Ok(b) => b,
Err(_) => { Err(_) => {
// TODO(tarcieri): is this really ok? // TODO(tarcieri): is this really ok?
@@ -111,27 +550,15 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
} }
}; };
if buf.len() < CB_OBJ_TAG_MIN { // TODO(str4d): Check the rest of the buffer (TAG_CERT_COMPRESS and TAG_CERT_LRC)
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
if buf[0] == TAG_CERT { if buf[0] == TAG_CERT {
let offset = 1 + get_length(&buf[1..], &mut len); Tlv::parse_single(buf, TAG_CERT).or_else(|_| {
if len > buf.len() - offset {
// TODO(tarcieri): is this really ok? // TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![])); Ok(Zeroizing::new(vec![]))
} })
} else {
unsafe { Ok(buf)
ptr::copy(buf.as_ptr().add(offset), buf.as_mut_ptr(), len);
}
buf.truncate(len);
} }
Ok(buf)
} }
/// Write certificate /// Write certificate
@@ -139,13 +566,9 @@ pub(crate) fn write_certificate(
txn: &Transaction<'_>, txn: &Transaction<'_>,
slot: SlotId, slot: SlotId,
data: Option<&[u8]>, data: Option<&[u8]>,
certinfo: u8, certinfo: CertInfo,
max_size: usize, ) -> Result<()> {
) -> Result<(), Error> { let object_id = slot.object_id();
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let object_id = key::slot_object(slot)?;
if data.is_none() { if data.is_none() {
return txn.save_object(object_id, &[]); return txn.save_object(object_id, &[]);
@@ -153,34 +576,110 @@ pub(crate) fn write_certificate(
let data = data.unwrap(); let data = data.unwrap();
let mut req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */; let mut buf = [0u8; CB_OBJ_MAX];
req_len += set_length(&mut buf, data.len()); let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
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();
// write compression info and LRC trailer // write compression info and LRC trailer
buf[offset] = TAG_CERT_COMPRESS; offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
buf[offset + 1] = 0x01; offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP {
0x01
} else {
0x00
};
buf[offset + 3] = TAG_CERT_LRC;
buf[offset + 4] = 00;
offset += 5;
txn.save_object(object_id, &buf[..offset]) txn.save_object(object_id, &buf[..offset])
} }
mod read_pki {
use der_parser::{
ber::BerObjectContent,
der::{parse_der_integer, DerObject},
error::BerError,
*,
};
use nom::{combinator, IResult};
use rsa::{BigUint, RSAPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{piv::AlgorithmId, Error, Result};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> {
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer)
}
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |object| {
let seq = object.as_sequence().expect("is DER sequence");
assert_eq!(seq.len(), 2);
let n = match seq[0].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
let e = match seq[1].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
(n, e)
})(i)
}
let (n, e) = match rsa_pubkey_parts(encoded) {
Ok((_, res)) => res,
_ => return Err(Error::InvalidObject),
};
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
}
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
/// ```text
/// ECParameters ::= CHOICE {
/// namedCurve OBJECT IDENTIFIER
/// -- implicitCurve NULL
/// -- specifiedCurve SpecifiedECDomain
/// }
/// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId> {
let curve_oid = parameters.as_oid_val().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
OID_NIST_P384 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
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()),
))
}
}
+64 -18
View File
@@ -30,8 +30,26 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey}; use crate::{Error, Result, YubiKey};
use getrandom::getrandom; 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 /// Cardholder Unique Identifier (CHUID) Template
/// ///
@@ -55,38 +73,66 @@ const CHUID_TMPL: &[u8] = &[
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
]; ];
/// Cardholder Unique Identifier (CHUID) /// Cardholder Unique Identifier (CHUID).
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct CHUID([u8; YKPIV_CARDID_SIZE]); pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
impl CHUID { impl ChuId {
/// Generate a random Cardholder Unique Identifier (CHUID) /// CHUID size in bytes
pub fn generate() -> Result<Self, Error> { pub const BYTE_SIZE: usize = 59;
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?; /// FASC-N component size
Ok(CHUID(id)) pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
/// Return FASC-N component of CHUID
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) -> Uuid {
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap()
}
/// Return expiration date component of CHUID
// 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) /// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> { pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> {
let txn = yubikey.begin_transaction()?; 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() { if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
let mut cardid = [0u8; YKPIV_CARDID_SIZE]; Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
cardid.copy_from_slice(&response[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
Ok(CHUID(cardid))
} }
/// Set Cardholder Unique Identifier (CHUID) /// 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(); let mut buf = CHUID_TMPL.to_vec();
buf[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + self.0.len())].copy_from_slice(&self.0); buf[..Self::BYTE_SIZE].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?; 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 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use 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 log::error;
use std::convert::TryInto; use std::{
convert::TryInto,
time::{Duration, SystemTime, UNIX_EPOCH},
};
/// Config const CB_ADMIN_TIMESTAMP: usize = 0x04;
#[derive(Copy, Clone)] const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
/// YubiKey configuration.
#[derive(Copy, Clone, Debug)]
pub struct Config { pub struct Config {
/// Protected data available /// Protected data available
protected_data_available: bool, pub protected_data_available: bool,
/// PUK blocked /// PUK blocked
puk_blocked: bool, pub puk_blocked: bool,
/// No block on upgrade /// No block on upgrade
puk_noblock_on_upgrade: bool, pub puk_noblock_on_upgrade: bool,
/// PIN last changed /// PIN last changed
pin_last_changed: u32, pub pin_last_changed: Option<SystemTime>,
/// MGM type /// MGM type
mgm_type: MgmType, pub mgm_type: MgmType,
} }
impl Config { impl Default for Config {
/// Get YubiKey config fn default() -> Config {
pub fn get(yubikey: &mut YubiKey) -> Result<Config, Error> { Config {
let mut config = Config {
protected_data_available: false, protected_data_available: false,
puk_blocked: false, puk_blocked: false,
puk_noblock_on_upgrade: false, puk_noblock_on_upgrade: false,
pin_last_changed: 0, pin_last_changed: None,
mgm_type: MgmType::Manual, 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()?; let txn = yubikey.begin_transaction()?;
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) { if let Ok(admin_data) = AdminData::read(&txn) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) { if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.is_empty() { if item.is_empty() {
error!("empty response for admin flags metadata item! ignoring"); error!("empty response for admin flags metadata item! ignoring");
} else { } 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 { if config.mgm_type != MgmType::Manual {
error!("conflicting types of MGM key administration configured"); error!("conflicting types of MGM key administration configured");
} else { } 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 { if item.len() != CB_ADMIN_TIMESTAMP {
error!("pin timestamp in admin metadata is an invalid size"); error!("pin timestamp in admin metadata is an invalid size");
} else { } else {
// TODO(tarcieri): double check this is little endian // TODO(tarcieri): double-check endianness is correct
config.pin_last_changed = u32::from_le_bytes(item.try_into().unwrap()); 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; 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() { if item.is_empty() {
error!("empty response for protected flags metadata item! ignoring"); error!("empty response for protected flags metadata item! ignoring");
} else if item[0] & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 { } 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 { if config.mgm_type != MgmType::Protected {
error!( error!("conflicting MGM key types: protected MGM exists");
"conflicting types of mgm key administration configured: protected MGM exists"
);
} }
// Always favor protected MGM // Always favor protected MGM
+15 -237
View File
@@ -1,242 +1,20 @@
//! Constant values //! Miscellaneous constant values
// TODO(tarcieri): refactor these into enums!
// Adapted from yubico-piv-tool: /// YubiKey max buffer size
// <https://github.com/Yubico/yubico-piv-tool/> pub(crate) const CB_BUF_MAX: usize = 3072;
//
// 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.
// TODO(tarcieri): document these! /// YubiKey max object size
#![allow(missing_docs, non_upper_case_globals)] 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; // Admin tags
pub const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02; 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; // Protected tags
pub const CB_ADMIN_SALT: usize = 16; pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
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_GUID_OFFS: usize = 29;
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_ALGO_RSA1024: u8 = 0x06;
pub const YKPIV_ALGO_RSA2048: u8 = 0x07;
pub const YKPIV_ALGO_ECCP256: u8 = 0x11;
pub const YKPIV_ALGO_ECCP384: u8 = 0x14;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
pub const YKPIV_CARDID_SIZE: usize = 16;
pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_INS_VERIFY: u8 = 0x20;
pub const YKPIV_INS_CHANGE_REFERENCE: u8 = 0x24;
pub const YKPIV_INS_RESET_RETRY: u8 = 0x2c;
pub const YKPIV_INS_GENERATE_ASYMMETRIC: u8 = 0x47;
pub const YKPIV_INS_AUTHENTICATE: u8 = 0x87;
pub const YKPIV_INS_GET_DATA: u8 = 0xcb;
pub const YKPIV_INS_PUT_DATA: u8 = 0xdb;
pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4;
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
pub const YKPIV_INS_RESET: u8 = 0xfb;
pub const YKPIV_INS_SET_PIN_RETRIES: u8 = 0xfa;
pub const YKPIV_INS_ATTEST: u8 = 0xf9;
pub const YKPIV_INS_GET_SERIAL: u8 = 0xf8;
pub const YKPIV_KEY_AUTHENTICATION: u8 = 0x9a;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_KEY_SIGNATURE: u8 = 0x9c;
pub const YKPIV_KEY_KEYMGM: u8 = 0x9d;
pub const YKPIV_KEY_CARDAUTH: u8 = 0x9e;
pub const YKPIV_KEY_RETIRED1: u8 = 0x82;
pub const YKPIV_KEY_RETIRED2: u8 = 0x83;
pub const YKPIV_KEY_RETIRED3: u8 = 0x84;
pub const YKPIV_KEY_RETIRED4: u8 = 0x85;
pub const YKPIV_KEY_RETIRED5: u8 = 0x86;
pub const YKPIV_KEY_RETIRED6: u8 = 0x87;
pub const YKPIV_KEY_RETIRED7: u8 = 0x88;
pub const YKPIV_KEY_RETIRED8: u8 = 0x89;
pub const YKPIV_KEY_RETIRED9: u8 = 0x8a;
pub const YKPIV_KEY_RETIRED10: u8 = 0x8b;
pub const YKPIV_KEY_RETIRED11: u8 = 0x8c;
pub const YKPIV_KEY_RETIRED12: u8 = 0x8d;
pub const YKPIV_KEY_RETIRED13: u8 = 0x8e;
pub const YKPIV_KEY_RETIRED14: u8 = 0x8f;
pub const YKPIV_KEY_RETIRED15: u8 = 0x90;
pub const YKPIV_KEY_RETIRED16: u8 = 0x91;
pub const YKPIV_KEY_RETIRED17: u8 = 0x92;
pub const YKPIV_KEY_RETIRED18: u8 = 0x93;
pub const YKPIV_KEY_RETIRED19: u8 = 0x94;
pub const YKPIV_KEY_RETIRED20: u8 = 0x95;
pub const YKPIV_KEY_ATTESTATION: u8 = 0xf9;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_AUTHENTICATION: u32 = 0x005f_c105; // cert for 9a key
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_SIGNATURE: u32 = 0x005f_c10a; // cert for 9c key
pub const YKPIV_OBJ_KEY_MANAGEMENT: u32 = 0x005f_c10b; // cert for 9d key
pub const YKPIV_OBJ_CARD_AUTH: u32 = 0x005f_c101; // cert for 9e key
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
pub const YKPIV_OBJ_RETIRED1: u32 = 0x005f_c10d;
pub const YKPIV_OBJ_RETIRED2: u32 = 0x005f_c10e;
pub const YKPIV_OBJ_RETIRED3: u32 = 0x005f_c10f;
pub const YKPIV_OBJ_RETIRED4: u32 = 0x005f_c110;
pub const YKPIV_OBJ_RETIRED5: u32 = 0x005f_c111;
pub const YKPIV_OBJ_RETIRED6: u32 = 0x005f_c112;
pub const YKPIV_OBJ_RETIRED7: u32 = 0x005f_c113;
pub const YKPIV_OBJ_RETIRED8: u32 = 0x005f_c114;
pub const YKPIV_OBJ_RETIRED9: u32 = 0x005f_c115;
pub const YKPIV_OBJ_RETIRED10: u32 = 0x005f_c116;
pub const YKPIV_OBJ_RETIRED11: u32 = 0x005f_c117;
pub const YKPIV_OBJ_RETIRED12: u32 = 0x005f_c118;
pub const YKPIV_OBJ_RETIRED13: u32 = 0x005f_c119;
pub const YKPIV_OBJ_RETIRED14: u32 = 0x005f_c11a;
pub const YKPIV_OBJ_RETIRED15: u32 = 0x005f_c11b;
pub const YKPIV_OBJ_RETIRED16: u32 = 0x005f_c11c;
pub const YKPIV_OBJ_RETIRED17: u32 = 0x005f_c11d;
pub const YKPIV_OBJ_RETIRED18: u32 = 0x005f_c11e;
pub const YKPIV_OBJ_RETIRED19: u32 = 0x005f_c11f;
pub const YKPIV_OBJ_RETIRED20: u32 = 0x005f_c120;
// Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_ATTESTATION: u32 = 0x005f_ff01;
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;
+74 -72
View File
@@ -32,62 +32,66 @@
use std::fmt::{self, Display}; 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)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error { 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 /// Memory error
MemoryError, MemoryError,
/// Not supported
NotSupported,
/// Not found
NotFound,
/// Parse error
ParseError,
/// PCSC error /// PCSC error
PcscError { PcscError {
/// Original PC/SC error /// Original PC/SC error
inner: Option<pcsc::Error>, inner: Option<pcsc::Error>,
}, },
/// 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: u32,
},
/// Invalid object
InvalidObject,
/// Algorithm error
AlgorithmError,
/// PIN locked /// PIN locked
PinLocked, PinLocked,
/// Argument error
ArgumentError,
/// Range error /// Range error
RangeError, RangeError,
/// Not supported /// Size error
NotSupported, SizeError,
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: u8,
},
} }
impl Error { impl Error {
@@ -95,46 +99,46 @@ impl Error {
/// ///
/// These names map to the legacy names from the Yubico C library, to /// These names map to the legacy names from the Yubico C library, to
/// assist in web searches for relevant information for these errors. /// assist in web searches for relevant information for these errors.
pub fn name(self) -> &'static str { pub fn name(self) -> Option<&'static str> {
match self { Some(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",
Error::AlgorithmError => "YKPIV_ALGORITHM_ERROR", Error::AlgorithmError => "YKPIV_ALGORITHM_ERROR",
Error::PinLocked => "YKPIV_PIN_LOCKED", Error::AppletError => "YKPIV_APPLET_ERROR",
Error::ArgumentError => "YKPIV_ARGUMENT_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::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 /// Error message
pub fn msg(self) -> &'static str { pub fn msg(self) -> &'static str {
match self { 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::AlgorithmError => "algorithm error",
Error::PinLocked => "PIN locked", Error::AppletError => "applet error",
Error::ArgumentError => "argument 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::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 { impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self { match self {
#[allow(trivial_casts)] // why doesn't this work without the cast??? #[allow(trivial_casts)]
Error::PcscError { inner } => inner Error::PcscError { inner } => inner.as_ref().map(|err| err as &_),
.as_ref()
.map(|err| err as &(dyn std::error::Error + 'static)),
_ => None, _ => None,
} }
} }
-375
View File
@@ -1,375 +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::{
certificate::{self, Certificate},
consts::*,
error::Error,
response::StatusWords,
serialization::*,
settings,
yubikey::YubiKey,
AlgorithmId, ObjectId,
};
use log::{debug, error, warn};
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
// TODO(tarcieri): replace these with enums
pub type SlotId = u8;
/// Get the [`ObjectId`] that corresponds to a given [`SlotId`]
// TODO(tarcieri): factor this into a slot ID enum
pub(crate) fn slot_object(slot: SlotId) -> Result<ObjectId, Error> {
let id = match slot {
YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION,
YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE,
YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT,
YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH,
YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION,
slot if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) => {
YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32
}
_ => return Err(Error::InvalidObject),
};
Ok(id)
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [u8; 24] = [
YKPIV_KEY_AUTHENTICATION,
YKPIV_KEY_SIGNATURE,
YKPIV_KEY_KEYMGM,
YKPIV_KEY_RETIRED1,
YKPIV_KEY_RETIRED2,
YKPIV_KEY_RETIRED3,
YKPIV_KEY_RETIRED4,
YKPIV_KEY_RETIRED5,
YKPIV_KEY_RETIRED6,
YKPIV_KEY_RETIRED7,
YKPIV_KEY_RETIRED8,
YKPIV_KEY_RETIRED9,
YKPIV_KEY_RETIRED10,
YKPIV_KEY_RETIRED11,
YKPIV_KEY_RETIRED12,
YKPIV_KEY_RETIRED13,
YKPIV_KEY_RETIRED14,
YKPIV_KEY_RETIRED15,
YKPIV_KEY_RETIRED16,
YKPIV_KEY_RETIRED17,
YKPIV_KEY_RETIRED18,
YKPIV_KEY_RETIRED19,
YKPIV_KEY_RETIRED20,
YKPIV_KEY_CARDAUTH,
];
/// 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;
}
};
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, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0];
let setting_roca: settings::BoolValue;
if yubikey.device_model() == DEVTYPE_YK4
&& (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)
&& 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);
}
}
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 | YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => (),
_ => {
error!("invalid algorithm specified");
return Err(Error::GenericError);
}
}
let txn = yubikey.begin_transaction()?;
templ[3] = slot;
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm,
]);
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 = response.into_buffer();
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_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,
})
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
let mut offset = 3;
let len = if algorithm == YKPIV_ALGO_ECCP256 {
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 })
}
_ => {
error!("wrong algorithm");
Err(Error::AlgorithmError)
}
}
}
+73 -68
View File
@@ -1,10 +1,14 @@
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for //! **yubikey.rs**: pure Rust cross-platform host-side driver for [YubiKey]
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC]) //! devices from [Yubico] using the Personal Computer/Smart Card ([PC/SC])
//! interface as provided by the [`pcsc` crate]. //! 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. //! 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 //! This library natively implements the protocol used to manage and
//! utilize PIV encryption and signing keys which can be generated, imported, //! utilize PIV encryption and signing keys which can be generated, imported,
//! and stored on YubiKey devices. //! and stored on YubiKey devices.
@@ -12,38 +16,43 @@
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information //! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
//! on which devices support PIV and the available functionality. //! on which devices support PIV and the available functionality.
//! //!
//! ## Minimum Supported Rust Version //! # Minimum Supported Rust Version
//! Rust **1.51** or newer.
//! //!
//! Rust 1.39+ //! # Supported YubiKeys
//!
//! ## Supported YubiKeys
//!
//! - [YubiKey NEO] series
//! - [YubiKey 4] series //! - [YubiKey 4] series
//! - [YubiKey 5] series //! - [YubiKey 5] series
//! //!
//! NOTE: Nano and USB-C variants of the above are also supported //! NOTE: Nano and USB-C variants of the above are also supported.
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
//! //!
//! ## Supported Algorithms //! # Supported Operating Systems
//! - Linux
//! - macOS
//! - Windows
//! //!
//! # Supported Algorithms
//! - **Authentication**: `3DES` //! - **Authentication**: `3DES`
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384` //! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**: //! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048` //! - 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) //! 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 //! Any functionality which is gated on the `untested` feature has not been
//! code from upstream [yubico-piv-tool] has been translated into Rust //! properly tested and is not known to function correctly.
//! presenting a safe interface, much of it is still untested.
//! //!
//! Please see the [project's README.md for a complete status][status]. //! Please see the [`untested` functionality tracking issue] for current status.
//! //! We would appreciate any help testing this functionality and removing the
//! ## History //! `untested` gating as well as writing more automated tests.
//! //!
//! # History
//! This library is a Rust translation of the [yubico-piv-tool] utility by //! This library is a Rust translation of the [yubico-piv-tool] utility by
//! Yubico, which was originally written in C. It was mechanically translated //! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode], and then subsequently heavily //! from C into Rust using [Corrode], and then subsequently heavily
@@ -53,25 +62,23 @@
//! the YubiKey implementation of PIV works in general, see the //! the YubiKey implementation of PIV works in general, see the
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide]. //! [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 //! No security audits of this crate have ever been performed. Presently it is in
//! an experimental stage and may still contain high-severity issues. //! an experimental stage and may still contain high-severity issues.
//! //!
//! USE AT YOUR OWN RISK! //! 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. //! 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]. //! 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 //! The original library was licensed under a [2-Clause BSD License][BSDL],
//! Yubico's [yubico-piv-tool], a C library/CLI program. The original library //! which this library inherits as a derived work.
//! 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/ //! [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
//! [PIV]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf //! [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 NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
//! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4 //! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/ //! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
//! [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/ //! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode //! [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 //! [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-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 //! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
@@ -121,57 +128,55 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc( #![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png", html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.2" html_root_url = "https://docs.rs/yubikey/0.4.0"
)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_qualifications
)] )]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
mod apdu; mod apdu;
#[cfg(feature = "untested")] mod cccid;
pub mod cccid;
#[cfg(feature = "untested")]
pub mod certificate; pub mod certificate;
#[cfg(feature = "untested")] mod chuid;
pub mod chuid; mod config;
#[cfg(feature = "untested")] mod consts;
pub mod config; mod error;
pub mod consts;
#[cfg(feature = "untested")]
pub mod container;
pub mod error;
#[cfg(feature = "untested")]
pub mod key;
#[cfg(feature = "untested")]
mod metadata; mod metadata;
mod mgm;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub mod mgm; mod mscmap;
#[cfg(feature = "untested")]
pub mod msroots;
mod response;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
mod msroots;
pub mod piv;
mod policy;
pub mod reader;
mod serialization; mod serialization;
#[cfg(feature = "untested")] mod settings;
pub mod settings;
mod transaction; mod transaction;
pub mod yubikey; mod yubikey;
pub use crate::{
cccid::{CardId, Ccc},
certificate::Certificate,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
settings::{SettingSource, SettingValue},
yubikey::{CachedPin, Serial, Version, YubiKey},
};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey}; pub use crate::{mscmap::MsContainer, msroots::MsRoots};
pub use yubikey::YubiKey;
/// Algorithm identifiers pub use uuid::Uuid;
// TODO(tarcieri): make this an enum
pub type AlgorithmId = u8;
/// Object identifiers /// Object identifiers: handles to particular objects stored on a YubiKey.
pub type ObjectId = u32; pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector) /// Buffer type (self-zeroizing byte vector)
+176 -194
View File
@@ -30,236 +30,194 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer}; use std::marker::PhantomData;
use std::{ptr, slice};
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// Get metadata item use crate::{serialization::*, transaction::Transaction, Buffer, Error, Result};
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
let mut p_temp: *const u8 = data.as_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8;
unsafe { #[cfg(feature = "untested")]
while p_temp < data.as_ptr().add(data.len()) { use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
tag_temp = *p_temp;
p_temp = p_temp.add(1);
let p_slice = slice::from_raw_parts( #[cfg(feature = "untested")]
p_temp, use std::iter;
data.as_ptr() as usize + data.len() - p_temp as usize,
);
if !has_valid_length( const TAG_ADMIN: u8 = 0x80;
p_slice, const TAG_PROTECTED: u8 = 0x88;
data.as_ptr().add(data.len()) as usize - p_temp as usize, pub const OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
) { pub const OBJ_PRINTED: u32 = 0x005f_c109;
return Err(Error::SizeError);
}
p_temp = p_temp.add(get_length(p_slice, &mut cb_temp)); pub(crate) trait MetadataType: private::Sealed {}
if tag_temp == tag { /// A type variable corresponding to PIN-protected metadata.
// found tag pub(crate) enum Protected {}
break; impl MetadataType for Protected {}
}
p_temp = p_temp.add(cb_temp); /// A type variable corresponding to administrative metadata.
} pub(crate) enum Admin {}
impl MetadataType for Admin {}
if p_temp < data.as_ptr().add(data.len()) { /// Metadata stored in a YubiKey.
return Ok(slice::from_raw_parts(p_temp, cb_temp)); pub(crate) struct Metadata<T: MetadataType> {
} inner: Buffer,
} _marker: PhantomData<T>,
Err(Error::GenericError)
} }
/// Set metadata item /// PIN-protected metadata stored in a YubiKey.
pub(crate) fn set_item( pub(crate) type ProtectedData = Metadata<Protected>;
data: &mut [u8], /// Administrative metadata stored in a YubiKey.
pcb_data: &mut usize, pub(crate) type AdminData = Metadata<Admin>;
cb_data_max: usize,
tag: u8,
p_item: &[u8],
) -> Result<(), Error> {
let mut p_temp: *mut u8 = data.as_mut_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let cb_item = p_item.len();
// Must be signed to have negative offsets
let cb_moved: isize;
let p_next: *mut u8;
while p_temp < data[*pcb_data..].as_mut_ptr() { impl<T: MetadataType> Default for Metadata<T> {
unsafe { fn default() -> Self {
tag_temp = *p_temp; Metadata {
p_temp = p_temp.add(1); inner: Zeroizing::new(vec![]),
_marker: PhantomData::default(),
}
}
}
cb_len = get_length( impl<T: MetadataType> Metadata<T> {
slice::from_raw_parts( /// Read metadata
p_temp, pub(crate) fn read(txn: &Transaction<'_>) -> Result<Self> {
data.as_mut_ptr() as usize + data.len() - p_temp as usize, let data = txn.fetch_object(T::obj_id())?;
), Ok(Metadata {
&mut cb_temp, inner: Tlv::parse_single(data, T::tag())?,
); _marker: PhantomData::default(),
p_temp = p_temp.add(cb_len); })
}
/// 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);
}
if self.inner.is_empty() {
return Self::delete(txn);
}
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
let len = Tlv::write(&mut buf, T::tag(), &self.inner)?;
txn.save_object(T::obj_id(), &buf[..len])
}
/// Delete metadata
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[])
}
/// Get metadata item
pub(crate) fn get_item(&self, tag: u8) -> Result<&[u8]> {
let mut data = &self.inner[..];
while !data.is_empty() {
let (remaining, tlv) = Tlv::parse(data)?;
data = remaining;
if tlv.tag == tag {
// found tag
return Ok(tlv.value);
}
}
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 { if tag_temp == tag {
break; break;
} }
p_temp = p_temp.add(cb_temp); offset += cb_temp;
} }
}
if tag_temp != tag { if tag_temp != tag {
if cb_item == 0 { if item.is_empty() {
// We've been asked to delete an existing item that isn't in the blob // 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(()); return Ok(());
} }
unsafe { // Found tag
p_temp = data.as_mut_ptr().add(*pcb_data);
cb_len = get_length_size(cb_item);
if (*pcb_data + cb_len + cb_item) > cb_data_max { // Check length, if it matches, overwrite
return Err(Error::GenericError); 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;
*p_temp = tag; // If length would cause buffer overflow, return error
p_temp = p_temp.add(1); if (self.inner.len() as isize + cb_moved) as usize > CB_OBJ_MAX {
p_temp = p_temp.add(set_length( return Err(Error::GenericError);
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
} }
*pcb_data += 1 + cb_len + cb_item; // Move remaining data
let orig_len = self.inner.len();
return Ok(()); if cb_moved > 0 {
} self.inner.extend(iter::repeat(0).take(cb_moved as usize));
if cb_temp == cb_item {
unsafe {
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
} }
self.inner.copy_within(
return Ok(()); next_offset..orig_len,
} (next_offset as isize + cb_moved) as usize,
p_next = unsafe { p_temp.add(cb_temp) };
cb_moved = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 {
get_length_size(cb_item) as isize
} else {
-1
}
- cb_len as isize;
if (*pcb_data + cb_moved as usize) > cb_data_max {
return Err(Error::GenericError);
}
unsafe {
ptr::copy(
p_next,
p_next.offset(cb_moved),
*pcb_data - p_next as usize - data.as_ptr() as usize,
); );
} self.inner
.resize((orig_len as isize + cb_moved) as usize, 0);
*pcb_data += cb_moved as usize; // Re-encode item and insert
if !item.is_empty() {
if cb_item != 0 { offset -= cb_len;
unsafe { offset += set_length(&mut self.inner[offset..], item.len())?;
p_temp = p_temp.offset(-(cb_len as isize)); self.inner[offset..offset + item.len()].copy_from_slice(item);
p_temp = p_temp.add(set_length(
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
} }
Ok(())
} }
Ok(())
}
/// 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),
};
let mut data = txn.fetch_object(obj_id)?;
if data.len() < CB_OBJ_TAG_MIN {
return Err(Error::GenericError);
}
if tag != data[0] {
return Err(Error::GenericError);
}
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);
}
unsafe {
ptr::copy(data.as_ptr().add(offset), data.as_mut_ptr(), pcb_data);
}
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 /// Get the size of a length tag for the given length
#[cfg(feature = "untested")]
fn get_length_size(length: usize) -> usize { fn get_length_size(length: usize) -> usize {
if length < 0x80 { if length < 0x80 {
1 1
@@ -269,3 +227,27 @@ fn get_length_size(length: usize) -> usize {
3 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 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, yubikey::YubiKey}; use crate::{Error, Result};
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use getrandom::getrandom;
use hmac::Hmac;
use log::error; use log::error;
use pbkdf2::pbkdf2; use rand_core::{OsRng, RngCore};
use sha1::Sha1;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
/// Default MGM key configured on all YubiKeys #[cfg(feature = "untested")]
const DEFAULT_MGM_KEY: [u8; DES_LEN_3DES] = [ use crate::{
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 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)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(non_camel_case_types)]
pub enum MgmType { pub enum MgmType {
/// Manual /// Manual
Manual = 0, Manual = 0,
@@ -73,27 +88,23 @@ pub struct MgmKey([u8; DES_LEN_3DES]);
impl MgmKey { impl MgmKey {
/// Generate a random MGM key /// Generate a random MGM key
pub fn generate() -> Result<Self, Error> { pub fn generate() -> Self {
let mut key_bytes = [0u8; DES_LEN_3DES]; let mut key_bytes = [0u8; DES_LEN_3DES];
OsRng.fill_bytes(&mut key_bytes);
if getrandom(&mut key_bytes).is_err() { Self(key_bytes)
return Err(Error::RandomnessError);
}
MgmKey::new(key_bytes)
} }
/// Create an MGM key from byte slice. /// Create an MGM key from byte slice.
/// ///
/// Returns an error if the slice is the wrong size or the key is weak. /// 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() bytes.as_ref().try_into()
} }
/// Create an MGM key from the given byte array. /// Create an MGM key from the given byte array.
/// ///
/// Returns an error if the key is weak. /// 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) { if is_weak_key(&key_bytes) {
error!( error!(
"blacklisting key '{:?}' since it's weak (with odd parity)", "blacklisting key '{:?}' since it's weak (with odd parity)",
@@ -103,16 +114,18 @@ impl MgmKey {
return Err(Error::KeyError); return Err(Error::KeyError);
} }
Ok(MgmKey(key_bytes)) Ok(Self(key_bytes))
} }
/// Get derived management key (MGM) /// 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()?; let txn = yubikey.begin_transaction()?;
// recover management key // recover management key
let data = metadata::read(&txn, TAG_ADMIN)?; let admin_data = AdminData::read(&txn)?;
let salt = metadata::get_item(&data, TAG_ADMIN_SALT)?; let salt = admin_data.get_item(TAG_ADMIN_SALT)?;
if salt.len() != CB_ADMIN_SALT { if salt.len() != CB_ADMIN_SALT {
error!( error!(
@@ -126,20 +139,21 @@ impl MgmKey {
let mut mgm = [0u8; DES_LEN_3DES]; let mut mgm = [0u8; DES_LEN_3DES];
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm); pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm);
MgmKey::from_bytes(mgm) MgmKey::from_bytes(mgm)
} }
/// Get protected management key (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 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); error!("could not read protected data (err: {:?})", e);
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); error!("could not read protected MGM from metadata (err: {:?})", e);
e e
})?; })?;
@@ -157,20 +171,87 @@ impl MgmKey {
MgmKey::from_bytes(item) MgmKey::from_bytes(item)
} }
/// Set the management key (MGM) /// Resets the management key for the given YubiKey to the default value.
pub fn set(&self, yubikey: &mut YubiKey, touch: Option<u8>) -> Result<(), Error> { ///
let txn = yubikey.begin_transaction()?; /// This will wipe any metadata related to derived and PIN-protected management keys.
txn.set_mgm_key(&self, touch) #[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) /// Configures the given YubiKey to use this management key.
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<(), Error> { ///
let mut data = Zeroizing::new(vec![0u8; CB_BUF_MAX]); /// The management key must be stored by the user, and provided when performing key
/// management operations.
let max_size = yubikey.obj_size_max(); ///
/// 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()?; 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 // log a warning, since the device mgm key is corrupt or we're in
// a state where we can't set the mgm key // a state where we can't set the mgm key
error!("could not set new derived mgm key, err = {}", e); error!("could not set new derived mgm key, err = {}", e);
@@ -180,39 +261,25 @@ impl MgmKey {
// after this point, we've set the mgm key, so the function should // after this point, we've set the mgm key, so the function should
// succeed, regardless of being able to set the metadata // succeed, regardless of being able to set the metadata
// set the new mgm key in protected data // Fetch the current protected data, or start a blank metadata blob.
let buffer = match metadata::read(&txn, TAG_PROTECTED) { let mut protected_data = ProtectedData::read(&txn).unwrap_or_default();
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);
if let Err(e) = metadata::set_item( // Set the new mgm key in protected data.
data.as_mut_slice(), if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, self.as_ref()) {
&mut cb_data,
CB_OBJ_MAX,
TAG_PROTECTED_MGM,
self.as_ref(),
) {
error!("could not set protected mgm item, err = {:?}", e); error!("could not set protected mgm item, err = {:?}", e);
} else { } 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); error!("could not write protected data, err = {:?}", e);
e e
})?; })?;
} }
// set the protected mgm flag in admin data // set the protected mgm flag in admin data
cb_data = data.len();
let mut flags_1 = [0u8; 1]; let mut flags_1 = [0u8; 1];
if let Ok(buffer) = metadata::read(&txn, TAG_ADMIN) { let mut admin_data = if let Ok(mut admin_data) = AdminData::read(&txn) {
if let Ok(item) = metadata::get_item(&buffer, TAG_ADMIN_FLAGS_1) { if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags_1.len() { if item.len() == flags_1.len() {
flags_1.copy_from_slice(item); flags_1.copy_from_slice(item);
} else { } else {
@@ -228,26 +295,20 @@ impl MgmKey {
} }
// remove any existing salt // remove any existing salt
if let Err(e) = if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
metadata::set_item(&mut data, &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, &[])
{
error!("could not unset derived mgm salt (err = {})", e) error!("could not unset derived mgm salt (err = {})", e)
} }
admin_data
} else { } else {
cb_data = 0; AdminData::default()
} };
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM; flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = metadata::set_item( if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags_1,
) {
error!("could not set admin flags item, err = {}", e); 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); error!("could not write admin data, err = {}", e);
} }
@@ -255,7 +316,6 @@ impl MgmKey {
} }
/// Encrypt with 3DES key /// 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] { pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned(); let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0)) TdesEde3::new(GenericArray::from_slice(&self.0))
@@ -264,11 +324,10 @@ impl MgmKey {
} }
/// Decrypt with 3DES key /// 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] { pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned(); let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0)) TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output)); .decrypt_block(GenericArray::from_mut_slice(&mut output));
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 { impl Default for MgmKey {
fn default() -> Self { 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 { impl<'a> TryFrom<&'a [u8]> for MgmKey {
type Error = Error; 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)?) Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
} }
} }
+76 -105
View File
@@ -1,7 +1,4 @@
//! MS Container Map Records //! 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)>
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/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 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use 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 log::error;
use std::{ use std::convert::{TryFrom, TryInto};
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
/// MS Container Map(?) Records const OBJ_MSCMAP: u32 = 0x005f_ff10;
#[derive(Copy, Clone)]
pub struct Container {
/// Container name
pub name: [u16; CONTAINER_NAME_LEN],
/// 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, pub slot: SlotId,
/// Key spec /// Key spec.
pub key_spec: u8, pub key_spec: u8,
/// Key size in bits /// Key size in bits.
pub key_size_bits: u16, pub key_size_bits: u16,
/// Flags /// Flags.
pub flags: u8, pub flags: u8,
/// PIN ID /// PIN ID.
pub pin_id: u8, pub pin_id: u8,
/// Associated ECHD(?) container (typo of "ecdh" perhaps?) /// Associated ECHD container.
pub associated_echd_container: u8, pub associated_echd_container: u8,
/// Cert fingerprint /// Cert fingerprint.
pub cert_fingerprint: [u8; 20], pub cert_fingerprint: [u8; Self::CERT_FINGERPRINT_LEN],
} }
impl Container { impl MsContainer {
/// Read MS Container Map records /// Container name length in UTF-16 chars.
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> { 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 txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_MSCMAP)?; let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![]; let mut containers = vec![];
if response.len() < CB_OBJ_TAG_MIN { let (_, tlv) = match Tlv::parse(&response) {
// TODO(tarcieri): is this really OK? Ok(res) => res,
return Ok(containers); 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? // TODO(tarcieri): yubico-piv-tool returned success here? should we?
return Err(Error::InvalidObject); return Err(Error::InvalidObject);
} }
let mut len = 0; for chunk in tlv.value.chunks_exact(Self::REC_LEN) {
let offset = 1 + get_length(&response[1..], &mut len); containers.push(MsContainer::new(chunk)?);
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)?);
} }
Ok(containers) Ok(containers)
} }
/// Write MS Container Map records. /// Write MS Container Map records.
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> { pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<()> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let n_containers = containers.len(); 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()?; let txn = yubikey.begin_transaction()?;
if n_containers == 0 { 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 { txn.save_object(OBJ_MSCMAP, &buf[..offset])
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])
} }
/// Parse a container record from a byte slice /// Parse a container record from a byte slice.
pub fn new(bytes: &[u8]) -> Result<Self, Error> { pub fn new(bytes: &[u8]) -> Result<Self> {
if bytes.len() != CONTAINER_REC_LEN { if bytes.len() != Self::REC_LEN {
error!( error!(
"couldn't parse PIV container: expected {}-bytes, got {}-bytes", "couldn't parse PIV container: expected {}-bytes, got {}-bytes",
CONTAINER_REC_LEN, Self::REC_LEN,
bytes.len() bytes.len()
); );
return Err(Error::ParseError); return Err(Error::ParseError);
} }
let mut name = [0u16; CONTAINER_NAME_LEN]; let mut name = [0u16; Self::NAME_LEN];
let name_bytes_len = CONTAINER_NAME_LEN * 2; let name_bytes_len = Self::NAME_LEN * 2;
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() { for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
name[i] = u16::from_le_bytes(chunk.try_into().unwrap()); name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
@@ -156,9 +148,9 @@ impl Container {
let mut cert_fingerprint = [0u8; 20]; let mut cert_fingerprint = [0u8; 20];
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]); cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
Ok(Container { Ok(Self {
name, name,
slot: bytes[name_bytes_len], slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1], key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes( key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)] bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
@@ -172,56 +164,35 @@ impl Container {
}) })
} }
/// Parse the container name as a UTF-16 string /// Parse the container name as a UTF-16 string.
pub fn parse_name(&self) -> Result<String, Error> { pub fn parse_name(&self) -> Result<String> {
String::from_utf16(&self.name).map_err(|_| Error::ParseError) String::from_utf16(&self.name).map_err(|_| Error::ParseError)
} }
/// Serialize a container record as a byte size /// Serialize a container record as a byte size.
pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] { pub fn to_bytes(&self) -> [u8; Self::REC_LEN] {
let mut bytes = Vec::with_capacity(CONTAINER_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()); bytes.extend_from_slice(&self.name[i].to_le_bytes());
} }
bytes.push(self.slot); bytes.push(self.slot.into());
bytes.push(self.key_spec); bytes.push(self.key_spec);
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes()); bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
bytes.push(self.flags); bytes.push(self.flags);
bytes.push(self.pin_id); bytes.push(self.pin_id);
bytes.push(self.associated_echd_container); bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint); bytes.extend_from_slice(&self.cert_fingerprint);
bytes.as_slice().try_into().unwrap()
// TODO(tarcieri): use TryInto here when const generics are available
let mut result = [0u8; CONTAINER_REC_LEN];
result.copy_from_slice(&bytes);
result
} }
} }
impl Debug for Container { impl<'a> TryFrom<&'a [u8]> for MsContainer {
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 {
type Error = Error; type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> { fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::new(bytes) Self::new(bytes)
} }
} }
+60 -78
View File
@@ -1,11 +1,4 @@
//! `msroots`: PKCS#7 formatted certificate store for enterprise trusted roots. //! 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>
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/> // <https://github.com/Yubico/yubico-piv-tool/>
@@ -37,90 +30,81 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey}; use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
serialization::*,
Error, Result, YubiKey,
};
use log::error; use log::error;
use std::{ptr, slice};
/// `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>); pub struct MsRoots(Vec<u8>);
impl MsRoots { impl MsRoots {
/// Initialize a local certificate struct from the given bytebuffer /// 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())) Ok(MsRoots(msroots.as_ref().into()))
} }
/// Read `msroots` file from YubiKey /// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> { pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
let mut len: usize = 0;
let mut ptr: *mut u8;
let mut tag: u8;
let mut offset: usize = 0;
let mut results = vec![];
let cb_data = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
// allocate first page // allocate first page
let mut p_data = vec![0u8; 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 mut buf = txn.fetch_object(object_id)?; let buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
ptr = buf.as_mut_ptr(); let (_, tlv) = match Tlv::parse(&buf) {
Ok(res) => res,
Err(_) => return Ok(None),
};
if cb_buf < CB_OBJ_TAG_MIN { if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
return Ok(results); && (TAG_MSROOTS_END != tlv.tag)
}
unsafe {
tag = *ptr;
ptr = ptr.add(1);
}
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag)
{ {
// the current object doesn't contain a valid part of a msroots file // the current object doesn't contain a valid part of a msroots file
// treat condition as object isn't found // treat condition as object isn't found
return Ok(results); return Ok(None);
} }
unsafe { data.extend_from_slice(tlv.value);
ptr = ptr.add(get_length(
slice::from_raw_parts(ptr, buf.as_ptr() as usize + buf.len() - ptr as usize),
&mut len,
));
}
// check that decoded length represents object contents if tlv.tag == TAG_MSROOTS_END {
if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize {
return Ok(results);
}
unsafe {
ptr::copy(ptr, p_data.as_mut_ptr().add(offset), len);
}
offset += len;
match MsRoots::new(&p_data[..offset]) {
Ok(msroots) => results.push(msroots),
Err(res) => error!("error parsing msroots: {:?}", res),
}
if tag == TAG_MSROOTS_END {
break; break;
} }
} }
Ok(results) MsRoots::new(&data).map(Some).map_err(|e| {
error!("error parsing msroots: {:?}", e);
e
})
} }
/// Write `msroots` file to YubiKey /// 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 buf = [0u8; CB_OBJ_MAX];
let mut offset: usize; let mut offset: usize;
let mut data_offset: usize = 0; let mut data_offset: usize = 0;
@@ -128,15 +112,14 @@ impl MsRoots {
let data = &self.0; let data = &self.0;
let data_len = data.len(); let data_len = data.len();
let n_objs: usize; let n_objs: usize;
let cb_obj_max = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
if data_len == 0 { 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 // 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 { if n_objs > 5 {
return Err(Error::SizeError); return Err(Error::SizeError);
@@ -145,24 +128,23 @@ impl MsRoots {
for i in 0..n_objs { for i in 0..n_objs {
offset = 0; offset = 0;
data_chunk = if cb_obj_max - CB_OBJ_TAG_MAX < data_len - data_offset { data_chunk = if CB_OBJ_MAX - CB_OBJ_TAG_MAX < data_len - data_offset {
cb_obj_max - CB_OBJ_TAG_MAX CB_OBJ_MAX - CB_OBJ_TAG_MAX
} else { } else {
data_len - data_offset data_len - data_offset
}; };
buf[offset] = if i == n_objs - 1 { offset += Tlv::write(
TAG_MSROOTS_END &mut buf,
} else { if i == n_objs - 1 {
TAG_MSROOTS_MID TAG_MSROOTS_END
}; } else {
TAG_MSROOTS_MID
},
&data[data_offset..(data_offset + data_chunk)],
)?;
offset += 1; txn.save_object(OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
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])?;
data_offset += data_chunk; 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::*,
settings,
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: settings::SettingValue;
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 = settings::SettingValue::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
settings::SettingSource::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
settings::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()]),
}
}
}
+89
View File
@@ -0,0 +1,89 @@
//! Support for enumerating available PC/SC card readers.
use crate::{Result, YubiKey};
use std::{
borrow::Cow,
convert::TryInto,
ffi::CStr,
sync::{Arc, Mutex},
};
/// Iterator over connected readers
pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>;
/// PC/SC reader context: used to enumerate available PC/SC [`Reader`]s.
pub struct Context {
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
/// Buffer for storing reader names
reader_names: Vec<u8>,
}
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> {
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
let reader_names = vec![0u8; ctx.list_readers_len()?];
Ok(Self {
ctx: Arc::new(Mutex::new(ctx)),
reader_names,
})
}
/// Iterate over the available readers.
pub fn iter(&mut self) -> Result<Iter<'_>> {
let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = {
let c = ctx.lock().unwrap();
// ensure PC/SC context is valid
c.is_valid()?;
c.list_readers(reader_names)?.collect()
};
let readers: Vec<_> = reader_cstrs
.iter()
.map(|name| Reader::new(name, Arc::clone(ctx)))
.collect();
Ok(readers.into_iter())
}
}
/// An individual connected PC/SC card reader.
pub struct Reader<'ctx> {
/// Name of this reader
name: &'ctx CStr,
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
}
impl<'ctx> Reader<'ctx> {
/// 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.
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> {
self.try_into()
}
/// 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)?)
}
}
-186
View File
@@ -1,186 +0,0 @@
//! Responses to issued commands
// 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::Buffer;
/// Parsed response to a command
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
buffer: Buffer,
}
impl Response {
/// Parse a response from the given buffer
pub fn from_bytes(mut buffer: Buffer) -> Self {
if buffer.len() >= 2 {
let sw = StatusWords::from(
(buffer[buffer.len() - 2] as u32) << 8 | (buffer[buffer.len() - 1] as u32),
);
let len = buffer.len() - 2;
buffer.truncate(len);
Response {
status_words: sw,
buffer,
}
} else {
Response {
status_words: StatusWords::None,
buffer,
}
}
}
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
Response {
status_words,
buffer,
}
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response buffer
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
/// Consume this response, returning its buffer
#[cfg(feature = "untested")]
pub fn into_buffer(self) -> Buffer {
self.buffer
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.buffer()
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// Incorrect parameter in command data field
IncorrectParamError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// Other/unrecognized status words
Other(u32),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
+114 -10
View File
@@ -30,30 +30,134 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use 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 // 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 /// 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 { if length < 0x80 {
buffer[0] = length as u8; if buffer.is_empty() {
1 Err(Error::SizeError)
} else {
buffer[0] = length as u8;
Ok(1)
}
} else if length < 0x100 { } else if length < 0x100 {
buffer[0] = 0x81; if buffer.len() < 2 {
buffer[1] = length as u8; Err(Error::SizeError)
2 } else {
buffer[0] = 0x81;
buffer[1] = length as u8;
Ok(2)
}
} else if buffer.len() < 3 {
Err(Error::SizeError)
} else { } else {
buffer[0] = 0x82; buffer[0] = 0x82;
buffer[1] = ((length >> 8) & 0xff) as u8; buffer[1] = ((length >> 8) & 0xff) as u8;
buffer[2] = (length & 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 /// Parse length tag, returning the size of the length tag itself as the
/// returned value, and setting the len parameter to the parsed length. /// returned value, and setting the len parameter to the parsed length.
pub(crate) fn get_length(buffer: &[u8], len: &mut usize) -> usize { 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 { if buffer[0] < 0x81 {
*len = buffer[0] as usize; *len = buffer[0] as usize;
1 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] { pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
buffer[0] = 0x5c; buffer[0] = 0x5c;
if object_id == YKPIV_OBJ_DISCOVERY { if object_id == OBJ_DISCOVERY {
buffer[1] = 1; buffer[1] = 1;
buffer[2] = YKPIV_OBJ_DISCOVERY as u8; buffer[2] = OBJ_DISCOVERY as u8;
buffer = &mut buffer[3..]; buffer = &mut buffer[3..];
} else if object_id > 0xffff && object_id <= 0x00ff_ffff { } else if object_id > 0xffff && object_id <= 0x00ff_ffff {
buffer[1] = 3; buffer[1] = 3;
+75 -72
View File
@@ -40,99 +40,102 @@ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
}; };
/// Source of how a setting was configured /// Source of how a setting was configured.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Source { pub enum SettingSource {
/// User-specified setting /// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
User, User,
/// Admin-specified setting /// Admin-specified setting: sourced via the `/etc/yubico/yubikeypiv.conf`
/// configuration file.
Admin, Admin,
/// Default setting /// Default setting.
Default, Default,
} }
/// Setting booleans 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)] #[derive(Copy, Clone, Debug)]
pub struct BoolValue { pub struct SettingValue {
/// Boolean value /// Boolean value
pub value: bool, pub value: bool,
/// Source of the configuration setting (user/admin/default) /// Source of the configuration setting (user, admin, or default)
pub source: Source, pub source: SettingSource,
} }
impl BoolValue { impl SettingValue {
/// Get a [`BoolValue`] value /// Get a [`SettingValue`] value by name.
pub fn get(key: &str, def: bool) -> Self { pub fn get(key: &str, default: bool) -> Self {
let mut setting = get_setting_from_file(key); Self::from_file(key)
.or_else(|| Self::from_env(key))
if setting.source == Source::Default { .unwrap_or(Self {
setting = get_setting_from_env(key); value: default,
} source: SettingSource::Default,
})
if setting.source == Source::Default {
setting.value = def;
}
setting
} }
}
/// Get a boolean config value /// Get a boolean config value from the provided config file
fn get_setting_from_file(key: &str) -> BoolValue { fn from_file(key: &str) -> Option<Self> {
let mut setting: BoolValue = BoolValue { if let Ok(file) = File::open(DEFAULT_CONFIG_FILE) {
value: false, for line in BufReader::new(file).lines() {
source: Source::Default, let line = match line {
}; Ok(line) => line,
_ => continue,
};
let file = match File::open(DEFAULT_CONFIG_FILE) { if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
Ok(f) => f, continue;
Err(_) => return setting, }
};
for line in BufReader::new(file).lines() { let (name, value) = {
let line = match line { let mut parts = line.splitn(1, '=');
Ok(line) => line, let name = parts.next();
_ => continue, let value = parts.next();
}; match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') { if name == key {
continue; return Some(SettingValue {
} source: SettingSource::Admin,
value: value == "1" || value == "true",
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 { None
setting.source = Source::Admin; }
setting.value = value == "1" || value == "true";
break; /// Get a setting boolean from an environment variable
fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{}", key))
.ok()
.map(|value| SettingValue {
source: SettingSource::User,
value: value == "1" || value == "true",
})
}
}
impl Default for SettingValue {
fn default() -> Self {
Self {
value: false,
source: SettingSource::default(),
} }
} }
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
} }
+147 -201
View File
@@ -1,41 +1,50 @@
//! YubiKey PC/SC transactions //! YubiKey PC/SC transactions
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*, Buffer};
#[cfg(feature = "untested")]
use crate::{ use crate::{
mgm::MgmKey, apdu::Response,
response::{Response, StatusWords}, apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result},
piv::{AlgorithmId, SlotId},
serialization::*, serialization::*,
ObjectId, yubikey::*,
Buffer, ObjectId,
}; };
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto; use std::convert::TryInto;
#[cfg(feature = "untested")]
use std::ptr;
use zeroize::Zeroizing; 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. /// Exclusive transaction with the YubiKey's PC/SC card.
pub(crate) struct Transaction<'tx> { pub(crate) struct Transaction<'tx> {
inner: pcsc::Transaction<'tx>, inner: pcsc::Transaction<'tx>,
} }
impl<'tx> Transaction<'tx> { impl<'tx> Transaction<'tx> {
/// Create a new transaction with the given card /// Create a new transaction with the given card.
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self, Error> { pub fn new(card: &'tx mut pcsc::Card) -> Result<Self> {
Ok(Transaction { Ok(Transaction {
inner: card.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 /// Transmit a single serialized APDU to the card this transaction is open
/// with and receive a response. /// with and receive a response.
/// ///
@@ -43,10 +52,10 @@ impl<'tx> Transaction<'tx> {
/// single APDU messages at a time. For larger messages that need to be /// single APDU messages at a time. For larger messages that need to be
/// split into multiple APDUs, use the [`Transaction::transfer_data`] /// split into multiple APDUs, use the [`Transaction::transfer_data`]
/// method instead. /// method instead.
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Buffer, Error> { pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>> {
trace!(">>> {:?}", send_buffer); trace!(">>> {:?}", send_buffer);
let mut recv_buffer = Zeroizing::new(vec![0u8; recv_len]); let mut recv_buffer = vec![0u8; recv_len];
let len = self let len = self
.inner .inner
@@ -54,17 +63,14 @@ impl<'tx> Transaction<'tx> {
.len(); .len();
recv_buffer.truncate(len); recv_buffer.truncate(len);
trace!("<<< {:?}", recv_buffer.as_slice());
Ok(recv_buffer) Ok(recv_buffer)
} }
/// Select application. /// Select application.
pub fn select_application(&self) -> Result<(), Error> { pub fn select_application(&self) -> Result<()> {
let response = APDU::new(YKPIV_INS_SELECT_APPLICATION) let response = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&AID) .data(&PIV_AID)
.transmit(self, 0xFF) .transmit(self, 0xFF)
.map_err(|e| { .map_err(|e| {
error!("failed communicating with card: '{}'", e); error!("failed communicating with card: '{}'", e);
@@ -82,35 +88,29 @@ impl<'tx> Transaction<'tx> {
Ok(()) Ok(())
} }
/// Get the version of the PIV application installed on the YubiKey /// Get the version of the PIV application installed on the YubiKey.
pub fn get_version(&self) -> Result<Version, Error> { pub fn get_version(&self) -> Result<Version> {
// get version from device // get version from device
let response = APDU::new(YKPIV_INS_GET_VERSION).transmit(self, 261)?; let response = Apdu::new(Ins::GetVersion).transmit(self, 261)?;
if !response.is_success() { if !response.is_success() {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
if response.buffer().len() < 3 { if response.data().len() < 3 {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
Ok(Version { Ok(Version::new(response.data()[..3].try_into().unwrap()))
major: response.buffer()[0],
minor: response.buffer()[1],
patch: response.buffer()[2],
})
} }
/// Get YubiKey device serial number /// Get YubiKey device serial number.
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> { pub fn get_serial(&self, version: Version) -> Result<Serial> {
let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
let response = if version.major < 5 { let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet // YK4 requires switching to the yk applet to retrieve the serial
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION) let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&yk_applet) .data(&YK_AID)
.transmit(self, 0xFF)? .transmit(self, 0xFF)?
.status_words(); .status_words();
@@ -119,7 +119,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError); 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() { if !resp.is_success() {
error!( error!(
@@ -130,9 +130,9 @@ impl<'tx> Transaction<'tx> {
} }
// reselect the PIV applet // reselect the PIV applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION) let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&AID) .data(&PIV_AID)
.transmit(self, 0xFF)? .transmit(self, 0xFF)?
.status_words(); .status_words();
@@ -143,8 +143,8 @@ impl<'tx> Transaction<'tx> {
resp resp
} else { } else {
// get serial from yk5 and later devices using the f8 command // YK5 implements getting the serial as a PIV applet command (0xf8)
let resp = APDU::new(YKPIV_INS_GET_SERIAL).transmit(self, 0xFF)?; let resp = Apdu::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() { if !resp.is_success() {
error!( error!(
@@ -157,74 +157,64 @@ impl<'tx> Transaction<'tx> {
resp resp
}; };
response.buffer()[..4] response.data()[..4]
.try_into() .try_into()
.map(|serial| Serial::from(u32::from_be_bytes(serial))) .map(|serial| Serial::from(u32::from_be_bytes(serial)))
.map_err(|_| Error::SizeError) .map_err(|_| Error::SizeError)
} }
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")] pub fn verify_pin(&self, pin: &[u8]) -> Result<()> {
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> { if pin.len() > CB_PIN_MAX {
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX?
if pin.len() != CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let response = APDU::new(YKPIV_INS_VERIFY) let mut query = Apdu::new(Ins::Verify);
.params(0x00, 0x80) query.params(0x00, 0x80);
.data(pin)
.transmit(self, 261)?; // Empty pin means we are querying the number of retries. We set no data in this
// case; if we instead sent [0xff; CB_PIN_MAX] it would count as an attempt and
// decrease the retry counter.
if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref());
}
let response = query.transmit(self, 261)?;
match response.status_words() { match response.status_words() {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }), StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => Err(Error::GenericError), _ => Err(Error::GenericError),
} }
} }
/// Change the PIN /// Change the PIN.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { pub fn change_ref(
let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80]; &self,
let mut indata = Zeroizing::new([0u8; 16]); action: ChangeRefAction,
current_pin: &[u8],
new_pin: &[u8],
) -> Result<()> {
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX { if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
if action == CHREF_ACT_UNBLOCK_PIN { const PIN: u8 = 0x80;
templ[1] = YKPIV_INS_RESET_RETRY; const PUK: u8 = 0x81;
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
unsafe { let templ = match action {
ptr::copy(current_pin.as_ptr(), indata.as_mut_ptr(), current_pin.len()); ChangeRefAction::ChangePin => [0, Ins::ChangeReference.code(), 0, PIN],
ChangeRefAction::ChangePuk => [0, Ins::ChangeReference.code(), 0, PUK],
ChangeRefAction::UnblockPin => [0, Ins::ResetRetry.code(), 0, PIN],
};
if current_pin.len() < CB_PIN_MAX { let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
ptr::write_bytes( indata[0..current_pin.len()].copy_from_slice(current_pin);
indata.as_mut_ptr().add(current_pin.len()), indata[CB_PIN_MAX..CB_PIN_MAX + new_pin.len()].copy_from_slice(new_pin);
0xff,
CB_PIN_MAX - current_pin.len(),
);
}
ptr::copy(
new_pin.as_ptr(),
indata.as_mut_ptr().offset(8),
new_pin.len(),
);
if new_pin.len() < CB_PIN_MAX {
ptr::write_bytes(
indata.as_mut_ptr().offset(8).add(new_pin.len()),
0xff,
CB_PIN_MAX - new_pin.len(),
);
}
}
let status_words = self let status_words = self
.transfer_data(&templ, indata.as_ref(), 0xFF)? .transfer_data(&templ, indata.as_ref(), 0xFF)?
@@ -233,7 +223,7 @@ impl<'tx> Transaction<'tx> {
match status_words { match status_words {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::PinLocked), StatusWords::AuthBlockedError => Err(Error::PinLocked),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => { _ => {
error!( error!(
"failed changing pin, token response code: {:x}.", "failed changing pin, token response code: {:x}.",
@@ -246,22 +236,16 @@ impl<'tx> Transaction<'tx> {
/// Set the management key (MGM). /// Set the management key (MGM).
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> { pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
let p2 = match touch.unwrap_or_default() { let p2 = if require_touch { 0xfe } else { 0xff };
0 => 0xff,
1 => 0xfe,
_ => {
return Err(Error::GenericError);
}
};
let mut data = [0u8; DES_LEN_3DES + 3]; let mut data = [0u8; DES_LEN_3DES + 3];
data[0] = YKPIV_ALGO_3DES; data[0] = ALGO_3DES;
data[1] = YKPIV_KEY_CARDMGM; data[1] = KEY_CARDMGM;
data[2] = DES_LEN_3DES as u8; data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref()); data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
let status_words = APDU::new(YKPIV_INS_SET_MGMKEY) let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2) .params(0xff, p2)
.data(&data) .data(&data)
.transmit(self, 261)? .transmit(self, 261)?
@@ -279,25 +263,21 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing /// This is the common backend for all public key encryption and signing
/// operations. /// operations.
// TODO(tarcieri): refactor this to be less gross/coupled. // TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command( pub(crate) fn authenticated_command(
&self, &self,
sign_in: &[u8], sign_in: &[u8],
out: &mut [u8], algorithm: AlgorithmId,
out_len: &mut usize, key: SlotId,
algorithm: u8,
key: u8,
decipher: bool, decipher: bool,
) -> Result<(), Error> { ) -> Result<Buffer> {
let in_len = sign_in.len(); let in_len = sign_in.len();
let mut indata = [0u8; 1024]; let mut indata = [0u8; 1024];
let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key]; let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
let mut len: usize = 0;
match algorithm { match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let key_len = if algorithm == YKPIV_ALGO_RSA1024 { let key_len = if let AlgorithmId::Rsa1024 = algorithm {
128 128
} else { } else {
256 256
@@ -307,8 +287,8 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let key_len = if algorithm == YKPIV_ALGO_ECCP256 { let key_len = if let AlgorithmId::EccP256 = algorithm {
32 32
} else { } else {
48 48
@@ -319,7 +299,6 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
_ => return Err(Error::AlgorithmError),
} }
let bytes = if in_len < 0x80 { let bytes = if in_len < 0x80 {
@@ -330,21 +309,21 @@ impl<'tx> Transaction<'tx> {
3 3
}; };
indata[0] = 0x7c; let offset = Tlv::write_as(&mut indata, 0x7c, in_len + bytes + 3, |buf| {
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3); assert_eq!(Tlv::write(buf, 0x82, &[]).expect("large enough"), 2);
indata[offset] = 0x82; assert_eq!(
indata[offset + 1] = 0x00; Tlv::write(
indata[offset + 2] = &mut buf[2..],
if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher { match (algorithm, decipher) {
0x85 (AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
} else { _ => 0x81,
0x81 },
}; sign_in
)
offset += 3; .expect("large enough"),
offset += set_length(&mut indata[offset..], in_len); 1 + bytes + in_len
indata[offset..(offset + in_len)].copy_from_slice(sign_in); );
offset += in_len; })?;
let response = self let response = self
.transfer_data(&templ, &indata[..offset], 1024) .transfer_data(&templ, &indata[..offset], 1024)
@@ -363,49 +342,33 @@ impl<'tx> Transaction<'tx> {
} }
} }
let data = response.buffer(); let (_, outer_tlv) = Tlv::parse(response.data())?;
// skip the first 7c tag // skip the first 7c tag
if data[0] != 0x7c { if outer_tlv.tag != 0x7c {
error!("failed parsing signature reply (0x7c byte)"); error!("failed parsing signature reply (0x7c byte)");
return Err(Error::ParseError); 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 // skip the 82 tag
if data[offset] != 0x82 { if inner_tlv.tag != 0x82 {
error!("failed parsing signature reply (0x82 byte)"); error!("failed parsing signature reply (0x82 byte)");
return Err(Error::ParseError); return Err(Error::ParseError);
} }
offset += 1; Ok(Buffer::new(inner_tlv.value.into()))
offset += get_length(&data[offset..], &mut len);
if len > *out_len {
error!("wrong size on output buffer");
return Err(Error::SizeError);
}
*out_len = len;
out[..len].copy_from_slice(&data[offset..(offset + len)]);
Ok(())
} }
/// Send/receive large amounts of data to/from the YubiKey, splitting long /// Send/receive large amounts of data to/from the YubiKey, splitting long
/// messages into smaller APDU-sized messages (using the provided APDU /// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via /// template to construct them), and then sending those via
/// [`Transaction::transmit`]. /// [`Transaction::transmit`].
#[cfg(feature = "untested")] pub fn transfer_data(&self, templ: &[u8], in_data: &[u8], max_out: usize) -> Result<Response> {
pub fn transfer_data(
&self,
templ: &[u8],
in_data: &[u8],
max_out: usize,
) -> Result<Response, Error> {
let mut in_offset = 0; let mut in_offset = 0;
let mut out_data = Zeroizing::new(vec![]); let mut out_data = vec![];
let mut sw = 0; let mut sw;
loop { loop {
let mut this_size = 0xff; let mut this_size = 0xff;
@@ -419,30 +382,30 @@ impl<'tx> Transaction<'tx> {
trace!("going to send {} bytes in this go", this_size); trace!("going to send {} bytes in this go", this_size);
let response = APDU::new(templ[1]) let response = Apdu::new(templ[1])
.cla(cla) .cla(cla)
.params(templ[2], templ[3]) .params(templ[2], templ[3])
.data(&in_data[in_offset..(in_offset + this_size)]) .data(&in_data[in_offset..(in_offset + this_size)])
.transmit(self, 261)?; .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? // TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data)); 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) {
if out_data.len() - response.buffer().len() - 2 > max_out {
error!( error!(
"output buffer too small: wanted to write {}, max was {}", "output buffer too small: wanted to write {}, max was {}",
out_data.len() - response.buffer().len() - 2, out_data.len() - response.data().len(),
max_out max_out
); );
return Err(Error::SizeError); return Err(Error::SizeError);
} }
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]); out_data.extend_from_slice(&response.data()[..response.data().len()]);
in_offset += this_size; in_offset += this_size;
if in_offset >= in_data.len() { if in_offset >= in_data.len() {
@@ -456,34 +419,33 @@ impl<'tx> Transaction<'tx> {
sw & 0xff sw & 0xff
); );
let response = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).transmit(self, 261)?; let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code(); sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) { if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
return Ok(Response::new(sw.into(), Zeroizing::new(vec![]))); return Ok(Response::new(sw.into(), vec![]));
} }
if out_data.len() + response.buffer().len() - 2 > max_out { if out_data.len() + response.data().len() > max_out {
error!( error!(
"output buffer too small: wanted to write {}, max was {}", "output buffer too small: wanted to write {}, max was {}",
out_data.len() + response.buffer().len() - 2, out_data.len() + response.data().len(),
max_out max_out
); );
return Err(Error::SizeError); return Err(Error::SizeError);
} }
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]); out_data.extend_from_slice(&response.data()[..response.data().len()]);
} }
Ok(Response::new(sw.into(), out_data)) Ok(Response::new(sw.into(), out_data))
} }
/// Fetch an object /// Fetch an object.
#[cfg(feature = "untested")] pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer> {
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
let mut indata = [0u8; 5]; let mut indata = [0u8; 5];
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff]; let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
let mut inlen = indata.len(); let mut inlen = indata.len();
let indata_remaining = set_object(object_id, &mut indata); let indata_remaining = set_object(object_id, &mut indata);
@@ -492,41 +454,31 @@ impl<'tx> Transaction<'tx> {
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?; let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
if !response.is_success() { 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 = response.into_buffer(); let (remaining, tlv) = Tlv::parse(response.data())?;
let mut outlen = 0;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) { if !remaining.is_empty() {
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() {
error!( error!(
"invalid length indicated in object: total len is {} but indicated length is {}", "invalid length indicated in object: total len is {} but indicated length is {}",
data.len(), tlv.value.len() + remaining.len(),
outlen tlv.value.len()
); );
return Err(Error::SizeError); return Err(Error::SizeError);
} }
Ok(Zeroizing::new( Ok(Zeroizing::new(tlv.value.to_vec()))
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
))
} }
/// Save an object /// Save an object.
#[cfg(feature = "untested")] pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<()> {
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> { let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
// TODO(tarcieri): replace with vector // TODO(tarcieri): replace with vector
let mut data = [0u8; CB_BUF_MAX]; let mut data = [0u8; CB_BUF_MAX];
@@ -538,14 +490,8 @@ impl<'tx> Transaction<'tx> {
let mut len = data.len(); let mut len = data.len();
let mut data_remaining = set_object(object_id, &mut data); let mut data_remaining = set_object(object_id, &mut data);
data_remaining[0] = 0x53; let offset = Tlv::write(data_remaining, 0x53, indata)?;
data_remaining = &mut data_remaining[1..];
let offset = set_length(data_remaining, indata.len());
data_remaining = &mut data_remaining[offset..]; 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(); len -= data_remaining.len();
let status_words = self let status_words = self
+267 -491
View File
File diff suppressed because it is too large Load Diff
+231 -8
View File
@@ -3,18 +3,241 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use std::env; use lazy_static::lazy_static;
use yubikey_piv::YubiKey; 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] lazy_static! {
#[ignore] /// Provide thread-safe access to a YubiKey
fn connect() { static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
}
/// One-time test initialization and setup
fn init_yubikey() -> Mutex<YubiKey> {
// Only show logs if `RUST_LOG` is set // Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() { if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init(); env_logger::builder().format_timestamp(None).init();
} }
let mut yubikey = YubiKey::open(None).unwrap(); let yubikey = YubiKey::open().unwrap();
dbg!(&yubikey.version()); trace!("serial: {}", yubikey.serial());
dbg!(&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());
} }