Compare commits

..

291 Commits

Author SHA1 Message Date
Tony Arcieri (iqlusion) d880faaefa yubikey v0.7.0 (#444) 2022-11-14 15:53:00 -08:00
Tony Arcieri (iqlusion) cc00a10c2f img: add logo-sq.png (#445)
Square logo for use with rustdoc
2022-11-14 15:15:19 -08:00
Tony Arcieri (iqlusion) 0a2e798894 Switch from subtle-encoding to base16ct (#443) 2022-11-14 14:26:07 -08:00
Tony Arcieri (iqlusion) 5c4259023f Switch from lazy_static to once_cell (#442)
The latter will hopefully eventually be upstreamed into `std`.
2022-11-14 12:52:27 -08:00
Tony Arcieri (iqlusion) 57bb088c7d yubikey-cli: bump x509-parser to v0.14 (#441) 2022-11-14 12:30:55 -08:00
Tony Arcieri (iqlusion) ccf19a3668 Bump rsa to v0.7.1 (#440) 2022-11-14 11:08:05 -08:00
dependabot[bot] db13fce53b Bump clap from 3.2.23 to 4.0.23 (#438)
Bumps [clap](https://github.com/clap-rs/clap) from 3.2.23 to 4.0.23.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v3.2.23...v4.0.23)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update pbkdf dependency to 0.9

* Update x509-parser dependency to 0.11

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

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

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

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

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

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

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

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

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

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

This commit renames the project to yubikey.rs

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

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

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

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

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

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

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

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

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

* Transaction::set_mgm_key: Take touch requirement as bool

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

* Replace MgmKey::set with MgmKey::set_manual

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

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

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

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

* Bump der-parser, nom, x509-parser

* clippy

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-03 13:51:52 +00:00
Jack Grigg 76c093e68e Minor cleanups 2019-12-03 03:24:10 +00:00
Jack Grigg ada3454d26 Fix bug in MgmKey::decrypt 2019-12-03 03:24:09 +00:00
Jack Grigg 370a90f800 Correctly return StatusWords from transfer_data 2019-12-03 03:24:07 +00:00
Jack Grigg 7bcd8664a4 AlgorithmId::write helper to match policy helpers 2019-12-03 03:24:06 +00:00
Jack Grigg 3a4515d902 Convert PIN and touch policies into enums 2019-12-03 03:23:59 +00:00
Tony Arcieri 7b70ea0f91 Merge pull request #56 from iqlusioninc/cli/fix-readme-badge
cli: fix build badge
2019-12-02 12:31:30 -08:00
Tony Arcieri 9bc28f4f75 cli: fix build badge 2019-12-02 12:18:44 -08:00
Tony Arcieri 140016bbd7 Merge pull request #54 from iqlusioninc/yubikey-cli/v0.0.1
yubikey-cli v0.0.1
2019-12-02 12:08:13 -08:00
Carl Wallace a9e0363d09 remove spurious blank lines flagged by fmt 2019-12-01 18:23:32 -05:00
Carl Wallace bfd728d1ac remove sha2, which was rendered OBE as print cert info was moved to CLI 2019-12-01 18:22:18 -05:00
Carl Wallace a110289910 move print cert info to CLI 2019-12-01 18:20:18 -05:00
Carl Wallace b9d6057d4e address fmt issues 2019-12-01 15:12:05 -05:00
Carl Wallace 2087e53109 add print cert info method in support of status action a la yubico-piv-tool 2019-12-01 14:59:21 -05:00
Carl Wallace 5f5844ccb4 Merge remote-tracking branch 'upstream/develop' into develop 2019-12-01 14:49:41 -05:00
Carl Wallace c8e5c96398 change cccid handling to target entire ccc object (a la yubico-piv-tool status action) 2019-11-30 15:11:10 -05:00
46 changed files with 5383 additions and 3306 deletions
+5
View File
@@ -0,0 +1,5 @@
[advisories]
ignore = [
"RUSTSEC-2020-0071", # time
"RUSTSEC-2020-0159", # chrono
]
+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
time: '13:00'
open-pull-requests-limit: 10
+89
View File
@@ -0,0 +1,89 @@
name: CI
on:
pull_request: {}
push:
branches: main
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo check
test:
strategy:
matrix:
include:
- platform: ubuntu-latest
toolchain: stable
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: stable
deps: true
- platform: macos-latest
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.60.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.60.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.60.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.65.0
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo clippy --all --all-features -- -D warnings
-175
View File
@@ -1,175 +0,0 @@
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
on:
pull_request: {}
push:
branches: develop
name: Rust
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
# Need to install `libpscslite-dev` on Linux
linux:
name: Test Suite
strategy:
matrix:
toolchain:
- 1.39.0
- stable
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
test:
name: Test Suite
strategy:
matrix:
platform:
- macos-latest
- windows-latest
toolchain:
- 1.39.0
- stable
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install rustfmt
run: rustup component add rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Install clippy
run: rustup component add clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all --all-features -- -D warnings
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings
+45
View File
@@ -0,0 +1,45 @@
name: Security Audit
on:
pull_request:
paths: Cargo.lock
push:
branches: main
paths: Cargo.lock
schedule:
- cron: '0 0 * * *'
jobs:
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }}
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny warnings --ignore RUSTSEC-2019-0031 # spin
+213 -27
View File
@@ -4,7 +4,195 @@ 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.3] (2019-12-02) ## 0.7.0 (2022-11-14)
### Added
- Display inner PC/SC errors ([#420])
- Support for metadata command ([#371])
- Better `certificate::Serial` inspection ([#437])
### Changed
- MSRV 1.60.0 ([#423])
- Bump `rsa` to v0.7.1 ([#440])
- Switch from `lazy_static` to `once_cell` ([#442])
- Switch from `subtle-encoding` to `base16ct` ([#443])
### Fixed
- Use `chrono` v0.4.23 or newer ([#436])
- `Certificate::issuer` was returning the subject instead ([#437])
[#371]: https://github.com/iqlusioninc/yubikey.rs/pull/371
[#420]: https://github.com/iqlusioninc/yubikey.rs/pull/420
[#423]: https://github.com/iqlusioninc/yubikey.rs/pull/423
[#436]: https://github.com/iqlusioninc/yubikey.rs/pull/436
[#437]: https://github.com/iqlusioninc/yubikey.rs/pull/437
[#440]: https://github.com/iqlusioninc/yubikey.rs/pull/440
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
## 0.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade ([#343])
- RustCrypto crate upgrades; MSRV 1.57 ([#378])
- `des` v0.8
- `elliptic-curve` v0.12
- `hmac` v0.12
- `num-bigint-dig` v0.8
- `pbkdf2` v0.11
- `p256` v0.11
- `p384` v0.11
- `rsa` v0.6
- `sha1` v0.10 (replacing `sha-1`)
- `sha2` v0.10
- Bump `uuid` to v1.0 ([#376])
- Bump `der-parser` to v8.0 ([#402])
- Bump `x509-parser` to v0.14 ([#402])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#376]: https://github.com/iqlusioninc/yubikey.rs/pull/376
[#378]: https://github.com/iqlusioninc/yubikey.rs/pull/378
[#402]: https://github.com/iqlusioninc/yubikey.rs/pull/402
## 0.5.0 (2021-11-21)
### Changed
- Update `rsa` dependency to 0.5 ([#315])
- Update `pbkdf2` dependency to 0.9 ([#315])
- Update `x509-parser` dependency to 0.12 ([#315], [#322])
- Update `nom` to v7.0 ([#322])
[#315]: https://github.com/iqlusioninc/yubikey.rs/pull/315
[#322]: https://github.com/iqlusioninc/yubikey.rs/pull/322
## 0.4.2 (2021-07-13)
### Added
- Make `yubikey::Buffer` a pub type ([#290])
### Changed
- Have `YubiKey::block_puk` take `&mut self` as argument ([#289])
[#289]: https://github.com/iqlusioninc/yubikey.rs/pull/289
[#290]: https://github.com/iqlusioninc/yubikey.rs/pull/290
## 0.4.1 (2021-07-12)
### Changed
- Rename `SettingValue` to `Setting` ([#286])
- Rename `Ccc` to `CccId` ([#287])
[#286]: https://github.com/iqlusioninc/yubikey.rs/pull/286
[#287]: https://github.com/iqlusioninc/yubikey.rs/pull/287
## 0.4.0 (2021-07-12) [YANKED]
### Added
- `Result` alias ([#271])
### Changed
- Renamed crate from `yubikey-piv` => `yubikey` ([#267])
- Renamed the following:
- `APDU` => `Apdu` ([#269])
- `CCC` => `Ccc` ([#269])
- `CHUID` => `ChuId` ([#269])
- `Ccc::cccid` => `Ccc::card_id` ([#270])
- `key` => `piv` ([#277])
- `readers` => `reader` ([#278])
- `readers::Readers` => `reader::Context` ([#278])
- Bumped the following dependencies:
- `rsa` => v0.4 ([#246])
- `des` => v0.7 ([#251])
- `elliptic-curve` => v0.10 ([#268])
- `hmac` => v0.11 ([#251])
- `pbkdf2` => v0.8 ([#251])
- `p256` => v0.9 ([#268])
- `p384` => v0.8 ([#268])
- MSRV 1.51+ ([#268])
- Flatten API ([#274])
- Replace `getrandom` with `rand_core` ([#276])
### Fixed
- Potential local DoS in TLV parser ([#279])
[#246]: https://github.com/iqlusioninc/yubikey.rs/pull/246
[#251]: https://github.com/iqlusioninc/yubikey.rs/pull/251
[#267]: https://github.com/iqlusioninc/yubikey.rs/pull/267
[#268]: https://github.com/iqlusioninc/yubikey.rs/pull/268
[#269]: https://github.com/iqlusioninc/yubikey.rs/pull/269
[#270]: https://github.com/iqlusioninc/yubikey.rs/pull/270
[#271]: https://github.com/iqlusioninc/yubikey.rs/pull/271
[#274]: https://github.com/iqlusioninc/yubikey.rs/pull/274
[#276]: https://github.com/iqlusioninc/yubikey.rs/pull/276
[#277]: https://github.com/iqlusioninc/yubikey.rs/pull/277
[#278]: https://github.com/iqlusioninc/yubikey.rs/pull/278
[#279]: https://github.com/iqlusioninc/yubikey.rs/pull/279
## yubikey-piv 0.3.0 (2021-03-22)
### Added
- Typed structs for PIN-protected and admin metadata ([#223])
- `MgmKey::set_default`/`MgmKey::set_manual` methods ([#224])
### Changed
- Have `Transaction::set_mgm_key` take touch requirement as bool ([#224])
### Removed
- `MgmKey::set` method ([#224])
[#223]: https://github.com/iqlusioninc/yubikey.rs/pull/223
[#224]: https://github.com/iqlusioninc/yubikey.rs/pull/224
## yubikey-piv 0.2.0 (2021-01-30)
### Changed
- Bump `der-parser` to v5.0 ([#194])
- Improve self-signed certificates ([#207])
- 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 ### Added
- Initial `Readers` enumerator for detecting YubiKeys ([#51]) - Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45]) - Certificate parsing ([#45])
@@ -20,20 +208,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Ins` (APDU instruction codes) enum ([#33]) - `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#32]) - Factor `Response` into `apdu` module; improved debugging ([#32])
[0.0.3]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/53 [#51]: https://github.com/iqlusioninc/yubikey.rs/pull/51
[#51]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/51 [#45]: https://github.com/iqlusioninc/yubikey.rs/pull/45
[#45]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/45 [#44]: https://github.com/iqlusioninc/yubikey.rs/pull/44
[#44]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/44 [#43]: https://github.com/iqlusioninc/yubikey.rs/pull/43
[#43]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/43 [#42]: https://github.com/iqlusioninc/yubikey.rs/pull/42
[#42]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/42 [#39]: https://github.com/iqlusioninc/yubikey.rs/pull/39
[#39]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/39 [#37]: https://github.com/iqlusioninc/yubikey.rs/pull/37
[#37]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/37 [#36]: https://github.com/iqlusioninc/yubikey.rs/pull/36
[#36]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/36 [#34]: https://github.com/iqlusioninc/yubikey.rs/pull/34
[#34]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/34 [#33]: https://github.com/iqlusioninc/yubikey.rs/pull/33
[#33]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/33 [#32]: https://github.com/iqlusioninc/yubikey.rs/pull/32
[#32]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/32
## [0.0.2] (2019-11-25) ## 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])
@@ -48,17 +235,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/iqlusioninc/yubikey-piv.rs/pull/31 [#30]: https://github.com/iqlusioninc/yubikey.rs/pull/30
[#30]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/30 [#19]: https://github.com/iqlusioninc/yubikey.rs/pull/19
[#19]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/19 [#17]: https://github.com/iqlusioninc/yubikey.rs/pull/17
[#17]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/17 [#15]: https://github.com/iqlusioninc/yubikey.rs/pull/15
[#15]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/15 [#13]: https://github.com/iqlusioninc/yubikey.rs/pull/13
[#13]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/13 [#10]: https://github.com/iqlusioninc/yubikey.rs/pull/10
[#10]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/10 [#9]: https://github.com/iqlusioninc/yubikey.rs/pull/9
[#9]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/9 [#8]: https://github.com/iqlusioninc/yubikey.rs/pull/8
[#8]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/8 [#7]: https://github.com/iqlusioninc/yubikey.rs/pull/7
[#7]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/7 [#6]: https://github.com/iqlusioninc/yubikey.rs/pull/6
[#6]: https://github.com/iqlusioninc/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
+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
+892 -578
View File
File diff suppressed because it is too large Load Diff
+41 -28
View File
@@ -1,48 +1,61 @@
[package] [package]
name = "yubikey-piv" name = "yubikey"
version = "0.0.3" # Also update html_root_url in lib.rs when bumping this version = "0.7.0"
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" license = "BSD-2-Clause"
license = "BSD-2-Clause" repository = "https://github.com/iqlusioninc/yubikey.rs"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs" readme = "README.md"
readme = "README.md" categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
categories = ["api-bindings", "cryptography", "hardware-support"] keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"] edition = "2021"
rust-version = "1.60"
[workspace] [workspace]
members = [".", "cli"] members = [".", "cli"]
[badges]
maintenance = { status = "experimental" }
[dependencies] [dependencies]
der-parser = "3" chrono = "0.4.23"
des = "0.3" cookie-factory = "0.3"
ecdsa = "0.1" der-parser = "8"
getrandom = "0.1" des = "0.8"
hmac = "0.7" elliptic-curve = "0.12"
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
hmac = "0.12"
log = "0.4" log = "0.4"
nom = "5" nom = "7"
pbkdf2 = "0.3" num-bigint-dig = { version = "0.8", features = ["rand"] }
num-traits = "0.2"
num-integer = "0.1"
pbkdf2 = { version = "0.11", default-features = false }
p256 = "0.11"
p384 = "0.11"
pcsc = "2" pcsc = "2"
rsa = "0.1.4" rand_core = { version = "0.6", features = ["std"] }
secrecy = "0.5" rsa = "0.7"
sha-1 = "0.8" secrecy = "0.8"
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
subtle = "2" subtle = "2"
x509-parser = "0.6" uuid = { version = "1.2", features = ["v4"] }
x509 = "0.2"
x509-parser = "0.14"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.7" env_logger = "0.9"
once_cell = "1"
rsa = { version = "0.7.1", features = ["hazmat"] }
signature = { version = "1.6.4", features = ["hazmat-preview"] }
[features] [features]
untested = [] untested = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+108 -82
View File
@@ -1,18 +1,21 @@
<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]
![Rust Version][rustc-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] [![Safety Dance][safety-image]][safety-link]
[![Dependency Status][deps-image]][deps-link]
[![2-Clause BSD Licensed][license-image]][license-link]
![MSRV][msrv-image]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] driver 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.
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
access provided by the [`pcsc` crate].
[Documentation][docs-link] [Documentation][docs-link]
@@ -26,25 +29,58 @@ encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information 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.
If you've been wanting to use Rust to sign and/or encrypt stuff using a If you've been wanting to use Rust to sign and/or encrypt data using a
private key generated and stored on a Yubikey (with option PIN-based access), private key generated and stored on a YubiKey (with option PIN-based access),
this is the crate you've been after! this is the crate you've been after!
Note that while this project started as a fork of a [Yubico] project, Note that while this project started as a fork of a [Yubico] project,
this fork is **NOT** an official Yubico project and is in no way supported or this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico. endorsed by Yubico.
## Features
### Personal Identity Verification (PIV)
[PIV] is a [NIST] standard for both *signing* and *encryption*
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
PIV-related functionality can be found in the [`piv`] module.
This library natively implements the protocol used to manage and
utilize PIV encryption and signing keys which can be generated, imported,
and stored on YubiKey devices.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- 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)
NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
## Minimum Supported Rust Version ## Minimum Supported Rust Version
- Rust **1.39+** Rust **1.60** 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
@@ -55,37 +91,14 @@ USE AT YOUR OWN RISK!
## Status ## Status
This project is a largely incomplete work-in-progress. So far the only Functionality which has been successfully tested is available by default.
functionality which has actually been tested is connecting to Yubikeys.
If you're interested helping test functionality, the table below documents Any functionality which is gated on the `untested` feature has not been
the current status of the project and relevant GitHub issues for various properly tested and is not known to function correctly.
functions of the YubiKey:
| | Module | Issue | Description | Please see the [`untested` functionality tracking issue] for current status.
|----|---------------|-------|-------------| We would appreciate any help testing this functionality and removing the
| 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest | `untested` gating as well as writing more automated tests.
| ⚠️ | `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
@@ -96,7 +109,7 @@ Tests which run live against a YubiKey device are marked as `#[ignore]` by
default in order to pass when running in a CI environment. To run these default in order to pass when running in a CI environment. To run these
tests locally, invoke the following command: tests locally, invoke the following command:
``` ```shell
cargo test -- --ignored cargo test -- --ignored
``` ```
@@ -105,26 +118,26 @@ information about what is happening. If you'd like to print this logging
information while running the tests, set the `RUST_LOG` environment variable information while running the tests, set the `RUST_LOG` environment variable
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`): to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
``` ```shell
RUST_LOG=info cargo test -- --ignored RUST_LOG=info cargo test -- --ignored
``` ```
To trace every message sent to/from the card i.e. the raw To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level: Application Protocol Data Unit (APDU) messages, use the `trace` log level:
``` ```text
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::apdu] >>> APDU { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [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] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8] [TRACE yubikey::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] } [TRACE yubikey::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] } [TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0] [TRACE yubikey::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [5, 1, 2] } [TRACE yubikey::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] } [TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0] [TRACE yubikey::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] } [TRACE yubikey::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok test connect ... ok
``` ```
@@ -138,6 +151,14 @@ 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
refactored into safer, more idiomatic Rust. refactored into safer, more idiomatic Rust.
For more information on [yubico-piv-tool] and background information on how
the YubiKey implementation of PIV works in general, see the
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
## ⚠️ Security Warning
No security audits of this crate have ever been performed.
## Code of Conduct ## 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.
@@ -146,12 +167,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-2022 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
@@ -187,43 +208,48 @@ or conditions.
[//]: # (badges) [//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-piv.svg [crate-image]: https://buildstats.info/crate/yubikey
[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
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg [msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/ [safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/Rust/badge.svg?branch=develop&event=push [build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions [build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg [deps-image]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs/status.svg
[gitter-link]: https://gitter.im/iqlusioninc/community [deps-link]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs
[//]: # (general links) [//]: # (general links)
[PIV]: https://piv.idmanagement.gov/ [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/ [Yubico]: https://www.yubico.com/
[PIV]: https://piv.idmanagement.gov/
[NIST]: https://www.nist.gov/
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey 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/
[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/iqlusioninc/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md [cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause [BSDL]: https://opensource.org/licenses/BSD-2-Clause
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
[//]: # (github issues) [//]: # (github issues)
[#18]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/18 [#18]: https://github.com/iqlusioninc/yubikey.rs/issues/18
[#20]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/20 [#20]: https://github.com/iqlusioninc/yubikey.rs/issues/20
[#21]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/21 [#21]: https://github.com/iqlusioninc/yubikey.rs/issues/21
[#22]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/22 [#22]: https://github.com/iqlusioninc/yubikey.rs/issues/22
[#23]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/23 [#23]: https://github.com/iqlusioninc/yubikey.rs/issues/23
[#24]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/24 [#24]: https://github.com/iqlusioninc/yubikey.rs/issues/24
[#25]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/25 [#25]: https://github.com/iqlusioninc/yubikey.rs/issues/25
[#26]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/26 [#26]: https://github.com/iqlusioninc/yubikey.rs/issues/26
[#27]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/27 [#27]: https://github.com/iqlusioninc/yubikey.rs/issues/27
[#28]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/28 [#28]: https://github.com/iqlusioninc/yubikey.rs/issues/28
+54
View File
@@ -4,5 +4,59 @@ 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.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade; MSRV 1.57 ([#343])
- Migrate from `gumdrop` to `clap` v3 ([#379])
- Bump `yubikey` dependency to v0.6 ([#403])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#379]: https://github.com/iqlusioninc/yubikey.rs/pull/379
[#403]: https://github.com/iqlusioninc/yubikey.rs/pull/403
## 0.5.0 (2021-11-21)
### Changed
- Bump `yubikey` dependency to v0.5 ([#327])
[#327]: https://github.com/iqlusioninc/yubikey.rs/pull/327
## 0.4.0 (2021-07-12)
### Changed
- Switch to renamed `yubikey` crate ([#283])
- Bump MSRV to 1.51+ ([#283])
[#283]: https://github.com/iqlusioninc/yubikey.rs/pull/283
## 0.3.0 (2021-03-22)
### Changed
- Bump `yubikey-piv` dependency to v0.3 ([#240])
[#240]: https://github.com/iqlusioninc/yubikey.rs/pull/240
## 0.2.0 (2021-01-30)
### Changed
- Bump MSRV to 1.46+ ([#208])
- Bump `yubikey-piv` dependency to v0.2 ([#220])
[#208]: https://github.com/iqlusioninc/yubikey.rs/pull/208
[#220]: https://github.com/iqlusioninc/yubikey.rs/pull/220
## 0.1.0 (2020-10-19)
### Added
- `status` command ([#72], [#74])
### Changed
- Bump `yubikey-piv` to v0.1 ([#180])
- Bump `x509-parser` to v0.8 ([#181])
- Bump `sha2` to v0.9 ([#182])
- Rename `list` command to `readers`; improve usage ([#71])
[#182]: https://github.com/iqlusioninc/yubikey.rs/pull/182
[#181]: https://github.com/iqlusioninc/yubikey.rs/pull/181
[#180]: https://github.com/iqlusioninc/yubikey.rs/pull/180
[#74]: https://github.com/iqlusioninc/yubikey.rs/pull/74
[#72]: https://github.com/iqlusioninc/yubikey.rs/pull/72
[#71]: https://github.com/iqlusioninc/yubikey.rs/pull/71
## 0.0.1 (2019-12-02) ## 0.0.1 (2019-12-02)
- Initial release - Initial release
+17 -12
View File
@@ -1,21 +1,26 @@
[package] [package]
name = "yubikey-cli" name = "yubikey-cli"
version = "0.0.1" version = "0.6.0"
description = """ description = """
Command-line interface for performing encryption and signing using RSA and/or Command-line interface for performing encryption and signing using RSA/ECC keys
ECC keys stored on YubiKey devices. stored on YubiKey devices.
""" """
authors = ["Tony Arcieri <bascule@gmail.com>"] authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018"
license = "BSD-2-Clause" license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs" repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md" readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"] categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"] keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
edition = "2021"
rust-version = "1.56"
[dependencies] [dependencies]
gumdrop = "0.7" clap = { version = "4", features = ["derive"] }
env_logger = "0.7" env_logger = "0.9"
lazy_static = "1" hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
log = "0.4"
once_cell = "1"
sha2 = "0.10"
termcolor = "1" termcolor = "1"
yubikey-piv = { version = "0.0.3", path = ".." } x509-parser = "0.14"
yubikey = { version = "0.7", path = ".." }
+9 -13
View File
@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/img/logo.png" width="150" height="110"> <img src="https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png" width="150" height="110">
# yubikey-cli.rs # yubikey-cli.rs
@@ -18,15 +18,15 @@ utility with general-purpose public-key encryption and signing support.
## Minimum Supported Rust Version ## Minimum Supported Rust Version
- Rust **1.39+** Rust **1.60** 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]).
## Security Warning ## Security Warning
@@ -35,10 +35,6 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK! USE AT YOUR OWN RISK!
## Status
WIP. Check back later.
## 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.
@@ -47,7 +43,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License ## License
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri Copyright (c) 2014-2022 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
@@ -88,12 +84,12 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-cli/badge.svg [docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/ [docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg [license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg [maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/ [safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey-cli.rs/workflows/Rust/badge.svg?branch=develop&event=push [build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-cli.rs/actions [build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg [gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community [gitter-link]: https://gitter.im/iqlusioninc/community
@@ -108,5 +104,5 @@ 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/iqlusioninc/yubikey-cli.rs/blob/develop/CODE_OF_CONDUCT.md [cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause [BSDL]: https://opensource.org/licenses/BSD-2-Clause
+3 -3
View File
@@ -8,9 +8,9 @@
unused_qualifications unused_qualifications
)] )]
use gumdrop::Options; use clap::Parser;
use yubikey_cli::commands::YubikeyCli; use yubikey_cli::commands::YubiKeyCli;
fn main() { fn main() {
YubikeyCli::parse_args_default_or_exit().run(); YubiKeyCli::parse().run()
} }
+43 -51
View File
@@ -1,93 +1,85 @@
//! Commands of the CLI application //! Commands of the CLI application
pub mod list; pub mod readers;
pub mod status;
use self::list::ListCmd; use self::{readers::ReadersCmd, status::StatusCmd};
use crate::status; use crate::terminal;
use gumdrop::Options; use clap::Parser;
use std::env; use std::{env, process::exit};
use std::process::exit;
use termcolor::ColorChoice; use termcolor::ColorChoice;
use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility /// The `yubikey` CLI utility
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct YubikeyCli { pub struct YubiKeyCli {
/// Obtain help about the current command /// Serial number of the YubiKey to connect to
#[options(short = "h", help = "print help message")] #[clap(short = 's', long = "serial")]
pub help: bool, pub serial: Option<Serial>,
/// Subcommand to execute. /// Subcommand to execute.
#[options(command)] #[clap(subcommand)]
pub command: Option<Commands>, pub command: Commands,
} }
impl YubikeyCli { impl YubiKeyCli {
/// Run the underlying command type or print usage info and exit /// Run the underlying command type or print usage info and exit
pub fn run(&self) { pub fn run(&self) {
// TODO(tarcieri): make this more configurable // TODO(tarcieri): make this more configurable
status::set_color_choice(ColorChoice::Auto); terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set // 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();
} }
match &self.command { self.command.run(self.yubikey_init())
Some(cmd) => cmd.run(), }
None => println!("{}", Commands::usage()),
/// 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 /// Subcommands of this application
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub enum Commands { pub enum Commands {
/// `help` subcommand
#[options(help = "show help for a command")]
Help(HelpOpts),
/// `version` subcommand /// `version` subcommand
#[options(help = "display version information")] #[clap(about = "display version information")]
Version(VersionOpts), Version(VersionOpts),
/// `list` subcommand /// `readers` subcommand
#[options(help = "list detected readers")] #[clap(about = "list detected readers")]
List(ListCmd), Readers(ReadersCmd),
/// `status` subcommand
#[clap(about = "show yubikey status")]
Status(StatusCmd),
} }
impl Commands { impl Commands {
/// Run the given command /// Run the given command
pub fn run(&self) { pub fn run(&self, yubikey: YubiKey) {
match self { match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(), Commands::Version(version) => version.run(),
Commands::List(list) => list.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 /// Version options
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct VersionOpts {} pub struct VersionOpts {}
impl VersionOpts { impl VersionOpts {
-40
View File
@@ -1,40 +0,0 @@
//! List detected readers
use gumdrop::Options;
use std::process::exit;
use yubikey_piv::readers::Readers;
/// The `list` subcommand
#[derive(Debug, Options)]
pub struct ListCmd {}
impl ListCmd {
/// Run the `list` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let mut yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
println!("{}: {} (serial: {})", i + 1, name, serial);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
//! List detected readers
use crate::terminal::STDOUT;
use clap::Parser;
use std::{
io::{self, Write},
process::exit,
};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{Context, Serial};
/// The `readers` subcommand
#[derive(Debug, Parser)]
pub struct ReadersCmd {}
impl ReadersCmd {
/// Run the `readers` subcommand
pub fn run(&self) {
let mut readers = Context::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
let mut s = STDOUT.lock();
s.reset().unwrap();
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
self.print_reader(&mut s, i + 1, &name, serial).unwrap();
}
}
/// Print a reader
fn print_reader(
&self,
stream: &mut StandardStreamLock<'_>,
index: usize,
name: &str,
serial: Serial,
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>3}:", index)?;
stream.reset()?;
writeln!(stream, " {} (serial: {})", name, serial)?;
stream.flush()?;
Ok(())
}
}
+60
View File
@@ -0,0 +1,60 @@
//! Print device status
use crate::terminal::{print_cert_info, STDOUT};
use clap::Parser;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{piv::*, YubiKey};
// String to use for `None`
const NONE_STR: &str = "<none>";
/// The `status` subcommand
#[derive(Debug, Parser)]
pub struct StatusCmd {}
impl StatusCmd {
/// Run the `status` subcommand
pub fn run(&self, mut yk: YubiKey) {
let mut s = STDOUT.lock();
s.reset().unwrap();
self.attr(&mut s, "name", yk.name()).unwrap();
self.attr(&mut s, "version", yk.version()).unwrap();
self.attr(&mut s, "serial", yk.serial()).unwrap();
if let Ok(chuid) = yk.chuid() {
self.attr(&mut s, "CHUID", chuid).unwrap();
} else {
self.attr(&mut s, "CHUID", NONE_STR).unwrap();
}
if let Ok(chuid) = yk.cccid() {
self.attr(&mut s, "CCC", chuid).unwrap();
} else {
self.attr(&mut s, "CCC", NONE_STR).unwrap();
}
self.attr(&mut s, "PIN retries", yk.get_pin_retries().unwrap())
.unwrap();
for slot in SLOTS.iter().cloned() {
print_cert_info(&mut yk, slot, &mut s).unwrap();
}
}
/// Print a status attribute
fn attr(
&self,
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> io::Result<()> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
Ok(())
}
}
+8 -9
View File
@@ -1,14 +1,13 @@
//! `yubikey` command-line utility //! `yubikey` command-line utility.
//!
//! The goal of this tool is to provide functionality similar to `yubico-piv-tool`
//! but implemented in pure Rust.
//!
//! It also serves as a demonstration/example of how to use the `yubikey` crate.
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn( #![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#[macro_use] #[macro_use]
pub mod status; pub mod terminal;
pub mod commands; pub mod commands;
+100 -20
View File
@@ -1,15 +1,22 @@
//! Status messages //! Status messages
use lazy_static::lazy_static; use log::debug;
use std::io::{self, Write}; use once_cell::sync::Lazy;
use std::sync::Mutex; use sha2::{Digest, Sha256};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use std::{
io::{self, Write},
str,
sync::Mutex,
};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Print a success status message (in green if colors are enabled) /// Print a success status message (in green if colors are enabled)
#[macro_export] #[macro_export]
macro_rules! status_ok { macro_rules! status_ok {
($status:expr, $msg:expr) => { ($status:expr, $msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.justified() .justified()
.bold() .bold()
.color(termcolor::Color::Green) .color(termcolor::Color::Green)
@@ -25,7 +32,7 @@ macro_rules! status_ok {
#[macro_export] #[macro_export]
macro_rules! status_warn { macro_rules! status_warn {
($msg:expr) => { ($msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.bold() .bold()
.color(termcolor::Color::Yellow) .color(termcolor::Color::Yellow)
.status("warning:") .status("warning:")
@@ -40,7 +47,7 @@ macro_rules! status_warn {
#[macro_export] #[macro_export]
macro_rules! status_err { macro_rules! status_err {
($msg:expr) => { ($msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.bold() .bold()
.color(termcolor::Color::Red) .color(termcolor::Color::Red)
.status("error:") .status("error:")
@@ -51,16 +58,14 @@ macro_rules! status_err {
}; };
} }
lazy_static! { /// Color configuration
/// Color configuration static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
/// Standard output /// Standard output
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice()); pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
/// Standard error /// Standard error
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice()); pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
}
/// Obtain the color configuration. /// Obtain the color configuration.
/// ///
@@ -132,18 +137,16 @@ impl Status {
/// Print the given message to stdout /// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) { pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg) self.print(&STDOUT, msg).expect("error printing to stdout!")
.expect("error printing to stdout!")
} }
/// Print the given message to stderr /// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) { pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg) self.print(&STDERR, msg).expect("error printing to stderr!")
.expect("error printing to stderr!")
} }
/// Print the given message /// Print the given message
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> Result<(), io::Error> { fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> io::Result<()> {
let mut s = stream.lock(); let mut s = stream.lock();
s.reset()?; s.reset()?;
s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?; s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
@@ -163,3 +166,80 @@ impl Status {
Ok(()) Ok(())
} }
} }
/// Write information about certificate found in slot a la yubico-piv-tool output.
pub fn print_cert_info(
yubikey: &mut YubiKey,
slot: SlotId,
stream: &mut StandardStreamLock<'_>,
) -> io::Result<()> {
let cert = match Certificate::read(yubikey, slot) {
Ok(c) => c,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
return Ok(());
}
};
let buf = cert.into_buffer();
if !buf.is_empty() {
let fingerprint = Sha256::digest(&buf);
let slot_id: u8 = slot.into();
print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
match parse_x509_certificate(&buf) {
Ok((_rem, cert)) => {
print_cert_attr(
stream,
"Algorithm",
cert.tbs_certificate.subject_pki.algorithm.algorithm,
)?;
print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?;
print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?;
print_cert_attr(
stream,
"Fingerprint",
&hex::upper::encode_string(&fingerprint),
)?;
print_cert_attr(
stream,
"Not Before",
cert.tbs_certificate
.validity
.not_before
.to_rfc2822()
.unwrap(),
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate
.validity
.not_after
.to_rfc2822()
.unwrap(),
)?;
}
_ => {
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(())
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+11 -10
View File
@@ -30,7 +30,7 @@
// (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, transaction::Transaction, Buffer}; use crate::{transaction::Transaction, Buffer, Result};
use log::trace; use log::trace;
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
@@ -41,7 +41,7 @@ const APDU_DATA_MAX: usize = 0xFF;
/// ///
/// These messages are packets used to communicate with the YubiKey. /// These messages are packets used to communicate with the YubiKey.
#[derive(Clone, Debug, Eq, PartialEq)] #[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,
@@ -58,7 +58,7 @@ 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: impl Into<Ins>) -> Self { pub fn new(ins: impl Into<Ins>) -> Self {
Self { Self {
@@ -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,7 +109,7 @@ 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> {
trace!(">>> {:?}", self); trace!(">>> {:?}", self);
let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?); let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response); trace!("<<< {:?}", &response);
@@ -131,13 +129,13 @@ impl APDU {
} }
} }
impl Drop for APDU { 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) {
// Only `data` may contain secrets // Only `data` may contain secrets
self.data.zeroize(); self.data.zeroize();
@@ -197,6 +195,9 @@ pub enum Ins {
/// Get device serial /// Get device serial
GetSerial, GetSerial,
/// Get slot metadata
GetMetadata,
/// Other/unrecognized instruction codes /// Other/unrecognized instruction codes
Other(u8), Other(u8),
} }
@@ -221,6 +222,7 @@ impl Ins {
Ins::SetPinRetries => 0xfa, Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9, Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8, Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
Ins::Other(code) => code, Ins::Other(code) => code,
} }
} }
@@ -245,6 +247,7 @@ impl From<u8> for Ins {
0xfa => Ins::SetPinRetries, 0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest, 0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial, 0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code), code => Ins::Other(code),
} }
} }
@@ -268,7 +271,6 @@ pub(crate) struct Response {
impl Response { impl Response {
/// Create a new response from the given status words and buffer /// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response { pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data } Response { status_words, data }
} }
@@ -279,7 +281,6 @@ impl Response {
} }
/// Get the raw [`StatusWords`] code for this response. /// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u16 { pub fn code(&self) -> u16 {
self.status_words.code() self.status_words.code()
} }
+58 -20
View File
@@ -30,8 +30,15 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey}; use crate::{Error, Result, YubiKey};
use getrandom::getrandom; use rand_core::{OsRng, RngCore};
use std::fmt::{self, Debug, Display};
/// 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 +55,69 @@ 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(pub [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 CccId(pub [u8; Self::BYTE_SIZE]);
impl CccId {
/// CCC size in bytes
pub const BYTE_SIZE: usize = 51;
/// Return CardId component of CCC
pub fn card_id(&self) -> Result<CardId> {
let mut cccid = [0u8; CardId::BYTE_SIZE];
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CardId::BYTE_SIZE)]);
Ok(CardId(cccid))
} }
/// Get Cardholder Capability Container (CCC) ID /// 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 AsRef<[u8]> for CccId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for CccId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::upper::encode_string(self.as_ref()))
} }
} }
+440 -140
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,22 +31,26 @@
// 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::{AlgorithmId, SlotId}, piv::{sign_data, AlgorithmId, SlotId},
serialization::*, serialization::*,
transaction::Transaction, transaction::Transaction,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, Buffer,
}; };
use ecdsa::{ use chrono::{DateTime, Utc};
curve::{CompressedCurvePoint, NistP256, NistP384, UncompressedCurvePoint}, use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
generic_array::GenericArray,
};
use log::error; use log::error;
use rsa::{PublicKey, RSAPublicKey}; use num_bigint_dig::BigUint;
use std::fmt; use p256::NistP256;
use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo}; use p384::NistP384;
use rsa::{PublicKeyParts, RsaPublicKey};
use sha2::{Digest, Sha256};
use std::fmt::Display;
use std::{fmt, 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. // TODO: Make these der_parser::oid::Oid constants when it has const fn support.
@@ -55,24 +59,127 @@ 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_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34"; const OID_NIST_P384: &str = "1.3.132.0.34";
/// An encoded point on the Nist P-256 curve. const TAG_CERT: u8 = 0x70;
#[derive(Clone, Eq, PartialEq)] const TAG_CERT_COMPRESS: u8 = 0x71;
pub enum EcP256Point { const TAG_CERT_LRC: u8 = 0xFE;
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP256>),
/// Uncompressed encoding of a point on the curve. /// A serial number for a [`Certificate`].
Uncompressed(UncompressedCurvePoint<NistP256>), #[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
} }
/// An encoded point on the Nist P-384 curve. impl From<[u8; 20]> for Serial {
#[derive(Clone, Eq, PartialEq)] fn from(bytes: [u8; 20]) -> Serial {
pub enum EcP384Point { Serial(BigUint::from_bytes_be(&bytes))
/// Compressed encoding of a point on the curve. }
Compressed(CompressedCurvePoint<NistP384>), }
/// Uncompressed encoding of a point on the curve. impl TryFrom<&[u8]> for Serial {
Uncompressed(UncompressedCurvePoint<NistP384>), 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()
}
/// Returns itself formatted as x509 compatible hex string
pub fn as_x509_hex(&self) -> String {
let data = self.to_bytes();
let raw_hex_string = format!("{:02X?}", data);
raw_hex_string
.replace(", ", ":")
.replace([']', '['], "")
.to_lowercase()
}
/// Returns itself formatted as x509 compatible int string
pub fn as_x509_int(&self) -> String {
let Serial(buint) = self;
format!("{}", buint)
}
}
impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.as_x509_hex())
}
}
/// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, Eq, 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`]. /// Information about a public key within a [`Certificate`].
@@ -84,14 +191,14 @@ pub enum PublicKeyInfo {
algorithm: AlgorithmId, algorithm: AlgorithmId,
/// Public key /// Public key
pubkey: RSAPublicKey, pubkey: RsaPublicKey,
}, },
/// EC P-256 keys /// EC P-256 keys
EcP256(EcP256Point), EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys /// EC P-384 keys
EcP384(EcP384Point), EcP384(EcPublicKey<NistP384>),
} }
impl fmt::Debug for PublicKeyInfo { impl fmt::Debug for PublicKeyInfo {
@@ -101,10 +208,10 @@ impl fmt::Debug for PublicKeyInfo {
} }
impl PublicKeyInfo { impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self, Error> { fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() { match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => { OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?; let pubkey = read_pki::rsa_pubkey(&subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa { Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() { algorithm: match pubkey.n().bits() {
@@ -117,33 +224,19 @@ impl PublicKeyInfo {
} }
OID_EC_PUBLIC_KEY => { OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data; let key_bytes = &subject_pki.subject_public_key.data;
match read_pki::ec_parameters(&subject_pki.algorithm.parameters)? { let algorithm_parameters = subject_pki
AlgorithmId::EccP256 => match key_bytes.len() { .algorithm
33 => CompressedCurvePoint::<NistP256>::from_bytes( .parameters
GenericArray::clone_from_slice(key_bytes), .as_ref()
) .ok_or(Error::InvalidObject)?;
.map(EcP256Point::Compressed),
65 => UncompressedCurvePoint::<NistP256>::from_bytes( match read_pki::ec_parameters(algorithm_parameters)? {
GenericArray::clone_from_slice(key_bytes), AlgorithmId::EccP256 => EcPublicKey::<NistP256>::from_bytes(key_bytes)
) .map(PublicKeyInfo::EcP256)
.map(EcP256Point::Uncompressed), .map_err(|_| Error::InvalidObject),
_ => None, AlgorithmId::EccP384 => EcPublicKey::<NistP384>::from_bytes(key_bytes)
} .map(PublicKeyInfo::EcP384)
.map(PublicKeyInfo::EcP256) .map_err(|_| Error::InvalidObject),
.ok_or(Error::InvalidObject),
AlgorithmId::EccP384 => match key_bytes.len() {
49 => CompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Compressed),
97 => UncompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Uncompressed),
_ => None,
}
.map(PublicKeyInfo::EcP384)
.ok_or(Error::InvalidObject),
_ => Err(Error::AlgorithmError), _ => Err(Error::AlgorithmError),
} }
} }
@@ -161,17 +254,225 @@ impl PublicKeyInfo {
} }
} }
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates /// Certificates
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate { pub struct Certificate {
serial: Serial,
#[allow(dead_code)]
issuer: String,
subject: String, subject: String,
subject_pki: PublicKeyInfo, subject_pki: PublicKeyInfo,
data: Buffer, 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)?;
@@ -179,25 +480,25 @@ impl Certificate {
return Err(Error::InvalidObject); return Err(Error::InvalidObject);
} }
Certificate::new(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.data), 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() {
@@ -205,21 +506,36 @@ impl Certificate {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let parsed_cert = match parse_x509_der(&cert) { let parsed_cert = match parse_x509_certificate(&cert) {
Ok((_, cert)) => cert, Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject), _ => return Err(Error::InvalidObject),
}; };
let subject = format!("{}", parsed_cert.tbs_certificate.subject); let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?; let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate { Ok(Certificate {
serial,
issuer,
subject, subject,
subject_pki, subject_pki,
data: cert, 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.issuer
}
/// Returns the SubjectName field of the certificate. /// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str { pub fn subject(&self) -> &str {
&self.subject &self.subject
@@ -243,11 +559,10 @@ impl AsRef<[u8]> for Certificate {
} }
/// 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 = slot.object_id();
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?
@@ -255,24 +570,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 {
buf.copy_within(offset..offset + len, 0); Ok(buf)
buf.truncate(len);
} }
Ok(buf)
} }
/// Write certificate /// Write certificate
@@ -280,12 +586,8 @@ 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 mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let object_id = slot.object_id(); let object_id = slot.object_id();
if data.is_none() { if data.is_none() {
@@ -294,50 +596,28 @@ 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 { mod read_pki {
use der_parser::{ use der_parser::{
asn1_rs::Any,
ber::BerObjectContent, ber::BerObjectContent,
der::{parse_der_integer, DerObject}, der::{parse_der_integer, parse_der_sequence_defined_g, DerObject},
error::BerError, error::BerError,
*,
}; };
use nom::{combinator, IResult}; use nom::{combinator, sequence::pair, IResult};
use rsa::{BigUint, RSAPublicKey}; use rsa::{BigUint, RsaPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384}; use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{error::Error, key::AlgorithmId}; use crate::{piv::AlgorithmId, Error, Result};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1): /// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text /// ```text
@@ -346,21 +626,18 @@ mod read_pki {
/// publicExponent INTEGER -- e /// publicExponent INTEGER -- e
/// } /// }
/// ``` /// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey, Error> { pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> { fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> {
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer) parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i)
} }
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> { fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |object| { combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| {
let seq = object.as_sequence().expect("is DER sequence"); let n = match modulus.content {
assert_eq!(seq.len(), 2);
let n = match seq[0].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s), BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"), _ => panic!("expected DER integer"),
}; };
let e = match seq[1].content { let e = match public_exponent.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s), BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"), _ => panic!("expected DER integer"),
}; };
@@ -374,7 +651,7 @@ mod read_pki {
_ => return Err(Error::InvalidObject), _ => return Err(Error::InvalidObject),
}; };
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject) RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
} }
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1): /// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
@@ -385,13 +662,8 @@ mod read_pki {
/// -- specifiedCurve SpecifiedECDomain /// -- specifiedCurve SpecifiedECDomain
/// } /// }
/// ``` /// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId, Error> { pub(super) fn ec_parameters(parameters: &Any<'_>) -> Result<AlgorithmId> {
let curve_oid = match parameters.as_context_specific() { let curve_oid = parameters.as_oid().map_err(|_| Error::InvalidObject)?;
Ok((_, Some(named_curve))) => {
named_curve.as_oid_val().map_err(|_| Error::InvalidObject)
}
_ => Err(Error::InvalidObject),
}?;
match curve_oid.to_string().as_str() { match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256), OID_NIST_P256 => Ok(AlgorithmId::EccP256),
@@ -400,3 +672,31 @@ mod read_pki {
} }
} }
} }
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
use std::io::Write;
use x509::der::write::{der_integer, der_sequence};
/// Encodes a usize as an ASN.1 integer using DER.
fn der_integer_biguint<'a, W: Write + 'a>(num: &'a BigUint) -> impl SerializeFn<W> + 'a {
move |w: WriteContext<W>| der_integer(&num.to_bytes_be())(w)
}
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RsaPublicKey,
) -> impl SerializeFn<W> + 'a {
der_sequence((
der_integer_biguint(pubkey.n()),
der_integer_biguint(pubkey.e()),
))
}
}
+57 -38
View File
@@ -30,8 +30,21 @@
// (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::fmt::{self, Debug, Display};
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,66 +68,72 @@ 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) Card UUID/GUID value /// Cardholder Unique Identifier (CHUID).
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct ChuidUuid(pub [u8; YKPIV_CARDID_SIZE]); pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
/// Cardholder Unique Identifier (CHUID) impl ChuId {
#[derive(Copy, Clone)] /// CHUID size in bytes
pub struct CHUID(pub [u8; YKPIV_CHUID_SIZE]); pub const BYTE_SIZE: usize = 59;
/// FASC-N component size
pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
impl CHUID {
/// Return FASC-N component of CHUID /// Return FASC-N component of CHUID
pub fn fascn(&self) -> Result<[u8; YKPIV_FASCN_SIZE], Error> { pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] {
let mut fascn = [0u8; YKPIV_FASCN_SIZE]; self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)]
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + YKPIV_FASCN_SIZE)]); .try_into()
Ok(fascn) .unwrap()
} }
/// Return Card UUID/GUID component of CHUID /// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Result<[u8; YKPIV_CARDID_SIZE], Error> { pub fn uuid(&self) -> Uuid {
let mut uuid = [0u8; YKPIV_CARDID_SIZE]; Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap()
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
Ok(uuid)
} }
/// Return expiration date component of CHUID /// Return expiration date component of CHUID
pub fn expiration(&self) -> Result<[u8; YKPIV_EXPIRATION_SIZE], Error> { // TODO(tarcieri): parse expiration?
let mut expiration = [0u8; YKPIV_EXPIRATION_SIZE]; pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
expiration.copy_from_slice( self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + YKPIV_EXPIRATION_SIZE)], .try_into()
); .unwrap()
Ok(expiration)
}
/// Generate a random Cardholder Unique Identifier (CHUID)
pub fn generate() -> Result<ChuidUuid, Error> {
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(ChuidUuid(id))
} }
/// Get Cardholder Unique Identifier (CHUID) /// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<CHUID, Error> { pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> {
let txn = yubikey.begin_transaction()?; let 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 chuid = [0u8; YKPIV_CHUID_SIZE]; Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
chuid.copy_from_slice(&response[0..YKPIV_CHUID_SIZE]);
let retval = CHUID { 0: chuid };
Ok(retval)
} }
/// 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[0..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 AsRef<[u8]> for ChuId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for ChuId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::upper::encode_string(self.as_ref()))
} }
} }
+47 -27
View File
@@ -30,44 +30,61 @@
// (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::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 +98,7 @@ impl Config {
} }
} }
if metadata::get_item(&data, TAG_ADMIN_SALT).is_ok() { if admin_data.get_item(TAG_ADMIN_SALT).is_ok() {
if config.mgm_type != MgmType::Manual { 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 +106,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 +132,9 @@ impl Config {
} }
} }
if metadata::get_item(&data, TAG_PROTECTED_MGM).is_ok() { if protected_data.get_item(TAG_PROTECTED_MGM).is_ok() {
if config.mgm_type != MgmType::Protected { 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 -167
View File
@@ -1,172 +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_FASCN_OFFS: usize = 2;
pub const CHUID_GUID_OFFS: usize = 29;
pub const CHUID_EXPIRATION_OFFS: usize = 47;
pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
pub const CONTAINER_NAME_LEN: usize = 40;
pub const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; // 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
pub const DES_TYPE_3DES: u8 = 1;
pub const DES_LEN_DES: usize = 8;
pub const DES_LEN_3DES: usize = DES_LEN_DES * 3;
// device types
pub const DEVTYPE_UNKNOWN: u32 = 0x0000_0000;
pub const DEVTYPE_NEO: u32 = 0x4E45_0000; //"NE"
pub const DEVTYPE_YK: u32 = 0x594B_0000; //"YK"
pub const DEVTYPE_NEOr3: u32 = (DEVTYPE_NEO | 0x0000_7233); //"r3"
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
pub const DEVYTPE_YK5: u32 = (DEVTYPE_YK | 0x0000_0035); // "5"
pub const ITER_MGM_PBKDF2: usize = 10000;
pub const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
// sw is status words, see NIST special publication 800-73-4, section 5.6
pub const SW_SUCCESS: i32 = 0x9000;
pub const SW_ERR_SECURITY_STATUS: i32 = 0x6982;
pub const SW_ERR_AUTH_BLOCKED: i32 = 0x6983;
pub const SW_ERR_INCORRECT_PARAM: i32 = 0x6a80;
// this is a custom sw for yubikey
pub const SW_ERR_INCORRECT_SLOT: i32 = 0x6b00;
pub const SW_ERR_NOT_SUPPORTED: i32 = 0x6d00;
pub const TAG_CERT: u8 = 0x70;
pub const TAG_CERT_COMPRESS: u8 = 0x71;
pub const TAG_CERT_LRC: u8 = 0xFE;
pub const TAG_ADMIN: u8 = 0x80;
pub const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub const TAG_ADMIN_SALT: u8 = 0x82;
pub const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub const TAG_PROTECTED: u8 = 0x88;
pub const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub const TAG_PROTECTED_MGM: u8 = 0x89;
pub const TAG_MSCMAP: u8 = 0x81;
pub const TAG_MSROOTS_END: u8 = 0x82;
pub const TAG_MSROOTS_MID: u8 = 0x83;
pub const TAG_RSA_MODULUS: u8 = 0x81;
pub const TAG_RSA_EXP: u8 = 0x82;
pub const TAG_ECC_POINT: u8 = 0x86;
pub const YKPIV_ALGO_TAG: u8 = 0x80;
pub const YKPIV_ALGO_3DES: u8 = 0x03;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
pub const YKPIV_CHUID_SIZE: usize = 59;
pub const YKPIV_CARDID_SIZE: usize = 16;
pub const YKPIV_FASCN_SIZE: usize = 25;
pub const YKPIV_EXPIRATION_SIZE: usize = 8;
pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103;
pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106;
pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108;
pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109;
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
// Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10;
pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11;
pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
pub const YKPIV_PINPOLICY_ONCE: u8 = 2;
pub const YKPIV_PINPOLICY_ALWAYS: u8 = 3;
pub const YKPIV_TOUCHPOLICY_TAG: u8 = 0xab;
pub const YKPIV_TOUCHPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_TOUCHPOLICY_NEVER: u8 = 1;
pub const YKPIV_TOUCHPOLICY_ALWAYS: u8 = 2;
pub const YKPIV_TOUCHPOLICY_CACHED: u8 = 3;
+83 -75
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>,
}, },
/// PIN locked
PinLocked,
/// Range error
RangeError,
/// Size error /// Size error
SizeError, SizeError,
/// Applet error
AppletError,
/// Authentication error
AuthenticationError,
/// Randomness error
RandomnessError,
/// Generic error
GenericError,
/// Key error
KeyError,
/// Parse error
ParseError,
/// Wrong PIN /// Wrong PIN
WrongPin { WrongPin {
/// Number of tries remaining /// Number of tries remaining
tries: u8, tries: u8,
}, },
/// Invalid object
InvalidObject,
/// Algorithm error
AlgorithmError,
/// PIN locked
PinLocked,
/// Argument error
ArgumentError,
/// Range error
RangeError,
/// Not supported
NotSupported,
} }
impl Error { impl Error {
@@ -95,53 +99,59 @@ 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, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Error::MemoryError => "memory error", Error::AlgorithmError => f.write_str("algorithm error"),
Error::PcscError { .. } => "PCSC error", Error::AppletError => f.write_str("applet error"),
Error::SizeError => "size error", Error::ArgumentError => f.write_str("argument error"),
Error::AppletError => "applet error", Error::AuthenticationError => f.write_str("authentication error"),
Error::AuthenticationError => "authentication error", Error::GenericError => f.write_str("generic error"),
Error::RandomnessError => "randomness error", Error::InvalidObject => f.write_str("invalid object"),
Error::GenericError => "generic error", Error::KeyError => f.write_str("key error"),
Error::KeyError => "key error", Error::MemoryError => f.write_str("memory error"),
Error::ParseError => "parse error", Error::NotSupported => f.write_str("not supported"),
Error::WrongPin { .. } => "wrong pin", Error::NotFound => f.write_str("not found"),
Error::InvalidObject => "invalid object", Error::ParseError => f.write_str("parse error"),
Error::AlgorithmError => "algorithm error",
Error::PinLocked => "PIN locked", Error::PcscError {
Error::ArgumentError => "argument error", inner: Some(pcsc_error),
Error::RangeError => "range error", } => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)),
Error::NotSupported => "not supported",
Error::PcscError { .. } => f.write_str("PC/SC error"),
Error::PinLocked => f.write_str("PIN locked"),
Error::RangeError => f.write_str("range error"),
Error::SizeError => f.write_str("size error"),
Error::WrongPin { .. } => f.write_str("wrong pin"),
} }
} }
} }
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg()) self.msg(f)
} }
} }
@@ -154,10 +164,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,
} }
} }
-581
View File
@@ -1,581 +0,0 @@
//! PIV cryptographic keys stored in a YubiKey.
//!
//! Supported algorithms:
//!
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate},
consts::*,
error::Error,
serialization::*,
settings,
yubikey::YubiKey,
Buffer, ObjectId,
};
use log::{debug, error, warn};
use std::convert::TryFrom;
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SlotId {
/// This certificate and its associated private key is used to authenticate the card
/// and the cardholder. This slot is used for things like system login. The end user
/// PIN is required to perform any private key operations. Once the PIN has been
/// provided successfully, multiple private key operations may be performed without
/// additional cardholder consent.
Authentication,
/// This certificate and its associated private key is used for digital signatures for
/// the purpose of document signing, or signing files and executables. The end user
/// PIN is required to perform any private key operations. The PIN must be submitted
/// every time immediately before a sign operation, to ensure cardholder participation
/// for every digital signature generated.
Signature,
/// This certificate and its associated private key is used for encryption for the
/// purpose of confidentiality. This slot is used for things like encrypting e-mails
/// or files. The end user PIN is required to perform any private key operations. Once
/// the PIN has been provided successfully, multiple private key operations may be
/// performed without additional cardholder consent.
KeyManagement,
/// This certificate and its associated private key is used to support additional
/// physical access applications, such as providing physical access to buildings via
/// PIV-enabled door locks. The end user PIN is NOT required to perform private key
/// operations for this slot.
CardAuthentication,
/// These slots are only available on the YubiKey 4 & 5. They are meant for previously
/// used Key Management keys to be able to decrypt earlier encrypted documents or
/// emails. In the YubiKey 4 & 5 all 20 of them are fully available for use.
Retired(RetiredSlotId),
/// This slot is only available on YubiKey version 4.3 and newer. It is only used for
/// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten.
Attestation,
}
impl TryFrom<u8> for SlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x9a => Ok(SlotId::Authentication),
0x9c => Ok(SlotId::Signature),
0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication),
0xf9 => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(value).map(SlotId::Retired),
}
}
}
impl From<SlotId> for u8 {
fn from(slot: SlotId) -> u8 {
match slot {
SlotId::Authentication => 0x9a,
SlotId::Signature => 0x9c,
SlotId::KeyManagement => 0x9d,
SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9,
}
}
}
impl SlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`SlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
SlotId::Authentication => 0x005f_c105,
SlotId::Signature => 0x005f_c10a,
SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(),
SlotId::Attestation => 0x005f_ff01,
}
}
}
/// Retired slot IDs.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RetiredSlotId {
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
R16,
R17,
R18,
R19,
R20,
}
impl TryFrom<u8> for RetiredSlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x82 => Ok(RetiredSlotId::R1),
0x83 => Ok(RetiredSlotId::R2),
0x84 => Ok(RetiredSlotId::R3),
0x85 => Ok(RetiredSlotId::R4),
0x86 => Ok(RetiredSlotId::R5),
0x87 => Ok(RetiredSlotId::R6),
0x88 => Ok(RetiredSlotId::R7),
0x89 => Ok(RetiredSlotId::R8),
0x8a => Ok(RetiredSlotId::R9),
0x8b => Ok(RetiredSlotId::R10),
0x8c => Ok(RetiredSlotId::R11),
0x8d => Ok(RetiredSlotId::R12),
0x8e => Ok(RetiredSlotId::R13),
0x8f => Ok(RetiredSlotId::R14),
0x90 => Ok(RetiredSlotId::R15),
0x91 => Ok(RetiredSlotId::R16),
0x92 => Ok(RetiredSlotId::R17),
0x93 => Ok(RetiredSlotId::R18),
0x94 => Ok(RetiredSlotId::R19),
0x95 => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl From<RetiredSlotId> for u8 {
fn from(slot: RetiredSlotId) -> u8 {
match slot {
RetiredSlotId::R1 => 0x82,
RetiredSlotId::R2 => 0x83,
RetiredSlotId::R3 => 0x84,
RetiredSlotId::R4 => 0x85,
RetiredSlotId::R5 => 0x86,
RetiredSlotId::R6 => 0x87,
RetiredSlotId::R7 => 0x88,
RetiredSlotId::R8 => 0x89,
RetiredSlotId::R9 => 0x8a,
RetiredSlotId::R10 => 0x8b,
RetiredSlotId::R11 => 0x8c,
RetiredSlotId::R12 => 0x8d,
RetiredSlotId::R13 => 0x8e,
RetiredSlotId::R14 => 0x8f,
RetiredSlotId::R15 => 0x90,
RetiredSlotId::R16 => 0x91,
RetiredSlotId::R17 => 0x92,
RetiredSlotId::R18 => 0x93,
RetiredSlotId::R19 => 0x94,
RetiredSlotId::R20 => 0x95,
}
}
}
impl RetiredSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
RetiredSlotId::R1 => 0x005f_c10d,
RetiredSlotId::R2 => 0x005f_c10e,
RetiredSlotId::R3 => 0x005f_c10f,
RetiredSlotId::R4 => 0x005f_c110,
RetiredSlotId::R5 => 0x005f_c111,
RetiredSlotId::R6 => 0x005f_c112,
RetiredSlotId::R7 => 0x005f_c113,
RetiredSlotId::R8 => 0x005f_c114,
RetiredSlotId::R9 => 0x005f_c115,
RetiredSlotId::R10 => 0x005f_c116,
RetiredSlotId::R11 => 0x005f_c117,
RetiredSlotId::R12 => 0x005f_c118,
RetiredSlotId::R13 => 0x005f_c119,
RetiredSlotId::R14 => 0x005f_c11a,
RetiredSlotId::R15 => 0x005f_c11b,
RetiredSlotId::R16 => 0x005f_c11c,
RetiredSlotId::R17 => 0x005f_c11d,
RetiredSlotId::R18 => 0x005f_c11e,
RetiredSlotId::R19 => 0x005f_c11f,
RetiredSlotId::R20 => 0x005f_c120,
}
}
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [SlotId; 24] = [
SlotId::Authentication,
SlotId::Signature,
SlotId::KeyManagement,
SlotId::Retired(RetiredSlotId::R1),
SlotId::Retired(RetiredSlotId::R2),
SlotId::Retired(RetiredSlotId::R3),
SlotId::Retired(RetiredSlotId::R4),
SlotId::Retired(RetiredSlotId::R5),
SlotId::Retired(RetiredSlotId::R6),
SlotId::Retired(RetiredSlotId::R7),
SlotId::Retired(RetiredSlotId::R8),
SlotId::Retired(RetiredSlotId::R9),
SlotId::Retired(RetiredSlotId::R10),
SlotId::Retired(RetiredSlotId::R11),
SlotId::Retired(RetiredSlotId::R12),
SlotId::Retired(RetiredSlotId::R13),
SlotId::Retired(RetiredSlotId::R14),
SlotId::Retired(RetiredSlotId::R15),
SlotId::Retired(RetiredSlotId::R16),
SlotId::Retired(RetiredSlotId::R17),
SlotId::Retired(RetiredSlotId::R18),
SlotId::Retired(RetiredSlotId::R19),
SlotId::Retired(RetiredSlotId::R20),
SlotId::CardAuthentication,
];
/// Algorithm identifiers
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlgorithmId {
/// 1024-bit RSA.
Rsa1024,
/// 2048-bit RSA.
Rsa2048,
/// ECDSA with the NIST P256 curve.
EccP256,
/// ECDSA with the NIST P384 curve.
EccP384,
}
impl TryFrom<u8> for AlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
impl From<AlgorithmId> for u8 {
fn from(id: AlgorithmId) -> u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
}
}
}
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
/// Card slot
slot: SlotId,
/// Cert
cert: Certificate,
}
impl Key {
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut keys = vec![];
let txn = yubikey.begin_transaction()?;
for slot in SLOTS.iter().cloned() {
let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
continue;
}
};
if !buf.is_empty() {
let cert = Certificate::new(buf)?;
keys.push(Key { slot, cert });
}
}
Ok(keys)
}
/// Get the slot ID for this key
pub fn slot(&self) -> SlotId {
self.slot
}
/// Get the certificate for this key
pub fn certificate(&self) -> &Certificate {
&self.cert
}
}
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
/// Information about a generated key
// TODO(tarcieri): this could use some more work
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GeneratedKey {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Modulus
modulus: Vec<u8>,
/// Exponent
exp: Vec<u8>,
},
/// ECC keys
Ecc {
/// ECC algorithm
algorithm: AlgorithmId,
/// Public curve point (i.e. public key)
point: Vec<u8>,
},
}
impl GeneratedKey {
/// Get the algorithm
pub fn algorithm(&self) -> AlgorithmId {
*match self {
GeneratedKey::Rsa { algorithm, .. } => algorithm,
GeneratedKey::Ecc { algorithm, .. } => algorithm,
}
}
}
/// Generate key
#[allow(clippy::cognitive_complexity)]
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: u8,
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
let setting_roca: settings::BoolValue;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
if yubikey.device_model() == DEVTYPE_YK4
&& yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
setting_roca = settings::BoolValue::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
settings::Source::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
settings::Source::Admin => {
if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN
} else {
SZ_ROCA_BLOCK_ADMIN
}
}
_ => SZ_ROCA_DEFAULT,
};
warn!(
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
(ROCA) and should be replaced. On-chip key generation {} See \
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
for additional information on device replacement and mitigation assistance",
yubikey.serial, psz_msg
);
if !setting_roca.value {
return Err(Error::NotSupported);
}
}
}
_ => (),
}
let txn = yubikey.begin_transaction()?;
templ[3] = slot.into();
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm.into(),
]);
if in_data[4] == 0 {
error!("unexpected algorithm");
return Err(Error::AlgorithmError);
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]);
}
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
if !response.is_success() {
let err_msg = "failed to generate new key";
match response.status_words() {
StatusWords::IncorrectSlotError => {
error!("{} (incorrect slot)", err_msg);
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
error!("{} (pin policy not supported?)", err_msg);
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
error!("{} (touch policy not supported?)", err_msg);
} else {
error!("{} (algorithm not supported?)", err_msg);
}
return Err(Error::AlgorithmError);
}
StatusWords::SecurityStatusError => {
error!("{} (not authenticated)", err_msg);
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:x})", err_msg, other.code());
return Err(Error::GenericError);
}
}
}
let data = Buffer::new(response.data().into());
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let mut offset = 5;
let mut len = 0;
if data[offset] != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let modulus = data[offset..(offset + len)].to_vec();
offset += len;
if data[offset] != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let exp = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Rsa {
algorithm,
modulus,
exp,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let mut offset = 3;
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
if data[offset] != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
offset += 1;
// the curve point should always be determined by the curve
let len_byte = data[offset];
offset += 1;
if len_byte as usize != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point })
}
}
}
+38 -131
View File
@@ -1,95 +1,10 @@
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for #![doc = include_str!("../README.md")]
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC]) #![doc(
//! interface as provided by the [`pcsc` crate]. html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
//! )]
//! **PIV** is a [NIST] standard for both *signing* and *encryption* #![cfg_attr(docsrs, feature(doc_cfg))]
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys. #![forbid(unsafe_code)]
//! #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
//! This library natively implements the protocol used to manage and
//! utilize PIV encryption and signing keys which can be generated, imported,
//! and stored on YubiKey devices.
//!
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
//! on which devices support PIV and the available functionality.
//!
//! ## Minimum Supported Rust Version
//!
//! Rust 1.39+
//!
//! ## Supported YubiKeys
//!
//! - [YubiKey NEO] series
//! - [YubiKey 4] series
//! - [YubiKey 5] series
//!
//! NOTE: Nano and USB-C variants of the above are also supported
//!
//! ## Supported Algorithms
//!
//! - **Authentication**: `3DES`
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
//!
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
//!
//! ## Status
//!
//! This is a work-in-progress effort, and while much of the library-level
//! code from upstream [yubico-piv-tool] has been translated into Rust
//! presenting a safe interface, much of it is still untested.
//!
//! Please see the [project's README.md for a complete status][status].
//!
//! ## History
//!
//! This library is a Rust translation of the [yubico-piv-tool] utility by
//! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode], and then subsequently heavily
//! refactored into safer, more idiomatic Rust.
//!
//! For more information on [yubico-piv-tool] and background information on how
//! the YubiKey implementation of PIV works in general, see the
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide].
//!
//! ## Security Warning
//!
//! No security audits of this crate have ever been performed. Presently it is in
//! an experimental stage and may still contain high-severity issues.
//!
//! USE AT YOUR OWN RISK!
//!
//! ## Code of Conduct
//!
//! We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
//!
//! For more information, please see [CODE_OF_CONDUCT.md][cc-md].
//!
//! ## License
//!
//! **yubikey-piv.rs** is a fork of and originally a mechanical translation from
//! Yubico's [yubico-piv-tool], a C library/CLI program. The original library
//! was licensed under a [2-Clause BSD License][BSDL], which this library inherits
//! as a derived work.
//!
//! [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
//! [PIV]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
//! [Yubico]: https://www.yubico.com/
//! [PC/SC]: https://en.wikipedia.org/wiki/PC/SC
//! [`pcsc` crate]: https://github.com/bluetech/pcsc-rust
//! [NIST]: https://www.nist.gov/
//! [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
//! [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
//! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
//! [status]: https://github.com/tarcieri/yubikey-piv.rs#status
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
//! [cc-web]: https://contributor-covenant.org/
//! [cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
//! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/> // <https://github.com/Yubico/yubico-piv-tool/>
@@ -121,56 +36,48 @@
// (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.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.3"
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
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;
pub mod readers;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
mod msroots;
pub mod piv;
mod policy;
pub mod reader;
mod serialization; mod serialization;
#[cfg(feature = "untested")] mod setting;
pub mod settings;
mod transaction; mod transaction;
pub mod yubikey; mod yubikey;
pub use self::{readers::Readers, yubikey::YubiKey}; pub use crate::{
cccid::{CardId, CccId},
certificate::Certificate,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
setting::{Setting, SettingSource},
yubikey::{CachedPin, Serial, Version, YubiKey},
};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey}; pub use crate::{mscmap::MsContainer, msroots::MsRoots};
/// Object identifiers pub use uuid::Uuid;
/// Object identifiers: handles to particular objects stored on a YubiKey.
pub type ObjectId = u32; pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector) /// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>; pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
+184 -165
View File
@@ -30,199 +30,194 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (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 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 cb_temp: usize = 0;
let mut offset = 0;
while offset < data.len() { #[cfg(feature = "untested")]
let tag_temp = data[offset]; use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
offset += 1;
if !has_valid_length(&data[offset..], data.len() - 1) { #[cfg(feature = "untested")]
return Err(Error::SizeError); use std::iter;
const TAG_ADMIN: u8 = 0x80;
const TAG_PROTECTED: u8 = 0x88;
pub const OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const OBJ_PRINTED: u32 = 0x005f_c109;
pub(crate) trait MetadataType: private::Sealed {}
/// A type variable corresponding to PIN-protected metadata.
pub(crate) enum Protected {}
impl MetadataType for Protected {}
/// A type variable corresponding to administrative metadata.
pub(crate) enum Admin {}
impl MetadataType for Admin {}
/// Metadata stored in a YubiKey.
pub(crate) struct Metadata<T: MetadataType> {
inner: Buffer,
_marker: PhantomData<T>,
}
/// PIN-protected metadata stored in a YubiKey.
pub(crate) type ProtectedData = Metadata<Protected>;
/// Administrative metadata stored in a YubiKey.
pub(crate) type AdminData = Metadata<Admin>;
impl<T: MetadataType> Default for Metadata<T> {
fn default() -> Self {
Metadata {
inner: Zeroizing::new(vec![]),
_marker: PhantomData::default(),
} }
offset += get_length(&data[offset..], &mut cb_temp);
if tag_temp == tag {
// found tag
break;
}
offset += cb_temp;
}
if offset < data.len() {
Ok(&data[offset..offset + cb_temp])
} else {
Err(Error::GenericError)
} }
} }
/// Set metadata item impl<T: MetadataType> Metadata<T> {
pub(crate) fn set_item( /// Read metadata
data: &mut [u8], pub(crate) fn read(txn: &Transaction<'_>) -> Result<Self> {
pcb_data: &mut usize, let data = txn.fetch_object(T::obj_id())?;
cb_data_max: usize, Ok(Metadata {
tag: u8, inner: Tlv::parse_single(data, T::tag())?,
p_item: &[u8], _marker: PhantomData::default(),
) -> Result<(), Error> { })
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let cb_item = p_item.len();
let mut offset = 0;
while offset < *pcb_data {
tag_temp = data[offset];
offset += 1;
cb_len = get_length(&data[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
break;
}
offset += cb_temp;
} }
if tag_temp != tag { /// Write metadata
if cb_item == 0 { #[cfg(feature = "untested")]
// We've been asked to delete an existing item that isn't in the blob #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
return Ok(()); pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
} if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
// We did not find an existing tag, append
offset = *pcb_data;
cb_len = get_length_size(cb_item);
// If length would cause buffer overflow, return error
if (*pcb_data + cb_len + cb_item) > cb_data_max {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
data[offset] = tag; if self.inner.is_empty() {
offset += 1; return Self::delete(txn);
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
*pcb_data += 1 + cb_len + cb_item;
return Ok(());
}
// Found tag
// Check length, if it matches, overwrite
if cb_temp == cb_item {
data[offset..offset + cb_item].copy_from_slice(p_item);
return Ok(());
}
// Length doesn't match, expand/shrink to fit
let next_offset = offset + cb_temp;
// Must be signed to have negative offsets
let cb_moved: isize = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 {
get_length_size(cb_item) as isize
} else {
// For tag, if deleting
-1
} }
// Accounts for different length encoding
- cb_len as isize;
// If length would cause buffer overflow, return error let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if (*pcb_data as isize + cb_moved) as usize > cb_data_max { let len = Tlv::write(&mut buf, T::tag(), &self.inner)?;
return Err(Error::GenericError);
txn.save_object(T::obj_id(), &buf[..len])
} }
// Move remaining data /// Delete metadata
data.copy_within( #[cfg(feature = "untested")]
next_offset..*pcb_data, #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
(next_offset as isize + cb_moved) as usize, pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
); txn.save_object(T::obj_id(), &[])
*pcb_data = (*pcb_data as isize + cb_moved) as usize;
// Re-encode item and insert
if cb_item != 0 {
offset -= cb_len;
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
} }
Ok(()) /// Get metadata item
} pub(crate) fn get_item(&self, tag: u8) -> Result<&[u8]> {
let mut data = &self.inner[..];
/// Read metadata while !data.is_empty() {
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> { let (remaining, tlv) = Tlv::parse(data)?;
let obj_id = match tag { data = remaining;
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
let mut data = txn.fetch_object(obj_id)?; if tlv.tag == tag {
// found tag
return Ok(tlv.value);
}
}
if data.len() < CB_OBJ_TAG_MIN { Err(Error::GenericError)
return Err(Error::GenericError);
} }
if tag != data[0] { /// Set metadata item
return Err(Error::GenericError); #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let mut offset = 0;
while offset < self.inner.len() {
tag_temp = self.inner[offset];
offset += 1;
cb_len = get_length(&self.inner[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
break;
}
offset += cb_temp;
}
if tag_temp != tag {
if item.is_empty() {
// We've been asked to delete an existing item that isn't in the blob
return Ok(());
}
// We did not find an existing tag, append
assert_eq!(offset, self.inner.len());
self.inner
.extend(iter::repeat(0).take(1 + get_length_size(item.len()) + item.len()));
Tlv::write(&mut self.inner[offset..], tag, item)?;
return Ok(());
}
// Found tag
// Check length, if it matches, overwrite
if cb_temp == item.len() {
self.inner[offset..offset + item.len()].copy_from_slice(item);
return Ok(());
}
// Length doesn't match, expand/shrink to fit
let next_offset = offset + cb_temp;
// Must be signed to have negative offsets
let cb_moved: isize = (item.len() as isize - cb_temp as isize)
+ if item.is_empty() {
// For tag, if deleting
-1
} else {
get_length_size(item.len()) as isize
}
// Accounts for different length encoding
- cb_len as isize;
// If length would cause buffer overflow, return error
if (self.inner.len() as isize + cb_moved) as usize > CB_OBJ_MAX {
return Err(Error::GenericError);
}
// Move remaining data
let orig_len = self.inner.len();
if cb_moved > 0 {
self.inner.extend(iter::repeat(0).take(cb_moved as usize));
}
self.inner.copy_within(
next_offset..orig_len,
(next_offset as isize + cb_moved) as usize,
);
self.inner
.resize((orig_len as isize + cb_moved) as usize, 0);
// Re-encode item and insert
if !item.is_empty() {
offset -= cb_len;
offset += set_length(&mut self.inner[offset..], item.len())?;
self.inner[offset..offset + item.len()].copy_from_slice(item);
}
Ok(())
} }
let mut pcb_data = 0;
let offset = 1 + get_length(&data[1..], &mut pcb_data);
if pcb_data > data.len() - offset {
return Err(Error::GenericError);
}
data.copy_within(offset..offset + pcb_data, 0);
data.truncate(pcb_data);
Ok(data)
}
/// Write metadata
pub(crate) fn write(
txn: &Transaction<'_>,
tag: u8,
data: &[u8],
max_size: usize,
) -> Result<(), Error> {
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if data.len() > max_size - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
if data.is_empty() {
// Deleting metadata
return txn.save_object(obj_id, &[]);
}
buf[0] = tag;
let mut offset = set_length(&mut buf[1..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(data);
offset += data.len();
txn.save_object(obj_id, &buf[..offset])
} }
/// Get the size of a length tag for the given length /// 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
@@ -232,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
}
}
}
+145 -84
View File
@@ -30,27 +30,41 @@
// (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 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, KeyInit},
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 +87,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 +113,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!(
@@ -125,21 +137,22 @@ 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 +170,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 +260,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 +294,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 +315,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 +323,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 +337,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 +355,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)?)
} }
} }
@@ -343,7 +404,7 @@ fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
c = (c & 0x0F) + ((c >> 4) & 0x0F); c = (c & 0x0F) + ((c >> 4) & 0x0F);
// if count is even, set low key bit to 1, otherwise 0 // if count is even, set low key bit to 1, otherwise 0
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 }); tmp[i] = (key[i] & 0xFE) | u8::from(c & 0x01 != 0x01);
} }
// check odd parity key against table by DES key block // check odd parity key against table by DES key block
+73 -103
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,115 @@
// (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::{
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,7 +147,7 @@ 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].try_into()?, slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1], key_spec: bytes[name_bytes_len + 1],
@@ -172,16 +163,17 @@ 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());
} }
@@ -192,36 +184,14 @@ impl Container {
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)
} }
} }
+55 -51
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,38 +30,59 @@
// (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;
/// `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<Option<Self>, Error> { pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
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 data = Vec::with_capacity(cb_data); let mut data = Vec::with_capacity(CB_OBJ_MAX);
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 { for object_id in OBJ_MSROOTS1..OBJ_MSROOTS5 {
let buf = txn.fetch_object(object_id)?; let buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
if cb_buf < CB_OBJ_TAG_MIN { let (_, tlv) = match Tlv::parse(&buf) {
return Ok(None); Ok(res) => res,
} Err(_) => return Ok(None),
};
let tag = buf[0]; if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tlv.tag)
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
@@ -76,17 +90,9 @@ impl MsRoots {
return Ok(None); return Ok(None);
} }
let mut len: usize = 0; data.extend_from_slice(tlv.value);
let offset = 1 + get_length(&buf[1..], &mut len);
// check that decoded length represents object contents if tlv.tag == TAG_MSROOTS_END {
if len > cb_buf - offset {
return Ok(None);
}
data.extend_from_slice(&buf[offset..offset + len]);
if tag == TAG_MSROOTS_END {
break; break;
} }
} }
@@ -98,23 +104,22 @@ impl MsRoots {
} }
/// 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;
let mut data_chunk: usize; let mut data_chunk: usize;
let data = &self.0; let data = &self.0;
let data_len = data.len(); let data_len = data.len();
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; let n_objs: usize = (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);
@@ -123,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;
} }
+1236
View File
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
//! Enums representing key policies.
use crate::{serialization::Tlv, Error, Result};
/// Specifies how often the PIN needs to be entered for access to the credential in a
/// given slot.
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PinPolicy {
/// Use the default PIN policy for the slot. See the slot's documentation for details.
Default,
/// 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 TryFrom<u8> for PinPolicy {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(PinPolicy::Default),
1 => Ok(PinPolicy::Never),
2 => Ok(PinPolicy::Once),
3 => Ok(PinPolicy::Always),
_ => Err(Error::GenericError),
}
}
}
impl PinPolicy {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize> {
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, Eq, 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()]),
}
}
}
impl TryFrom<u8> for TouchPolicy {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(TouchPolicy::Default),
1 => Ok(TouchPolicy::Never),
2 => Ok(TouchPolicy::Always),
3 => Ok(TouchPolicy::Cached),
_ => Err(Error::GenericError),
}
}
}
+16 -16
View File
@@ -1,9 +1,8 @@
//! Support for enumerating available readers //! Support for enumerating available PC/SC card readers.
use crate::{error::Error, yubikey::YubiKey}; use crate::{Result, YubiKey};
use std::{ use std::{
borrow::Cow, borrow::Cow,
convert::TryInto,
ffi::CStr, ffi::CStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@@ -11,8 +10,8 @@ use std::{
/// Iterator over connected readers /// Iterator over connected readers
pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>; pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>;
/// Enumeration support for available readers /// PC/SC reader context: used to enumerate available PC/SC [`Reader`]s.
pub struct Readers { pub struct Context {
/// PC/SC context /// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>, ctx: Arc<Mutex<pcsc::Context>>,
@@ -20,10 +19,10 @@ pub struct Readers {
reader_names: Vec<u8>, reader_names: Vec<u8>,
} }
impl Readers { impl Context {
/// Open a PC/SC context, which can be used to enumerate available PC/SC /// Open a PC/SC context, which can be used to enumerate available PC/SC
/// readers (which can be used to connect to YubiKeys). /// readers (which can be used to connect to YubiKeys).
pub fn open() -> Result<Self, Error> { pub fn open() -> Result<Self> {
let ctx = pcsc::Context::establish(pcsc::Scope::System)?; let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
let reader_names = vec![0u8; ctx.list_readers_len()?]; let reader_names = vec![0u8; ctx.list_readers_len()?];
Ok(Self { Ok(Self {
@@ -32,8 +31,8 @@ impl Readers {
}) })
} }
/// Iterate over the available readers /// Iterate over the available readers.
pub fn iter(&mut self) -> Result<Iter<'_>, Error> { pub fn iter(&mut self) -> Result<Iter<'_>> {
let Self { ctx, reader_names } = self; let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = { let reader_cstrs: Vec<_> = {
@@ -45,6 +44,7 @@ impl Readers {
c.list_readers(reader_names)?.collect() c.list_readers(reader_names)?.collect()
}; };
#[allow(clippy::needless_collect)]
let readers: Vec<_> = reader_cstrs let readers: Vec<_> = reader_cstrs
.iter() .iter()
.map(|name| Reader::new(name, Arc::clone(ctx))) .map(|name| Reader::new(name, Arc::clone(ctx)))
@@ -54,7 +54,7 @@ impl Readers {
} }
} }
/// An individual connected reader /// An individual connected PC/SC card reader.
pub struct Reader<'ctx> { pub struct Reader<'ctx> {
/// Name of this reader /// Name of this reader
name: &'ctx CStr, name: &'ctx CStr,
@@ -64,25 +64,25 @@ pub struct Reader<'ctx> {
} }
impl<'ctx> Reader<'ctx> { impl<'ctx> Reader<'ctx> {
/// Create a new reader from its name and context /// Create a new reader from its name and context.
fn new(name: &'ctx CStr, ctx: Arc<Mutex<pcsc::Context>>) -> Self { fn new(name: &'ctx CStr, ctx: Arc<Mutex<pcsc::Context>>) -> Self {
// TODO(tarcieri): open devices, determine they're YubiKeys, get serial? // TODO(tarcieri): open devices, determine they're YubiKeys, get serial?
Self { name, ctx } Self { name, ctx }
} }
/// Get this reader's name /// Get this reader's name.
pub fn name(&self) -> Cow<'_, str> { pub fn name(&self) -> Cow<'_, str> {
// TODO(tarcieri): is lossy ok here? try to avoid lossiness? // TODO(tarcieri): is lossy ok here? try to avoid lossiness?
self.name.to_string_lossy() self.name.to_string_lossy()
} }
/// Open a connection to this reader, returning a `YubiKey` if successful /// Open a connection to this reader, returning a `YubiKey` if successful.
pub fn open(&self) -> Result<YubiKey, Error> { pub fn open(&self) -> Result<YubiKey> {
self.try_into() self.try_into()
} }
/// Connect to this reader, returning its `pcsc::Card` /// Connect to this reader, returning its `pcsc::Card`.
pub(crate) fn connect(&self) -> Result<pcsc::Card, Error> { pub(crate) fn connect(&self) -> Result<pcsc::Card> {
let ctx = self.ctx.lock().unwrap(); let ctx = self.ctx.lock().unwrap();
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?) Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
} }
+114 -10
View File
@@ -30,30 +30,134 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (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;
+132
View File
@@ -0,0 +1,132 @@
//! Configuration setting values parsed from the environment and config file:
//! `/etc/yubico/yubikeypiv.conf`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// Default location of the YubiKey PIV configuration file
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
use std::{
env,
fs::File,
io::{BufRead, BufReader},
};
/// Source of how a setting was configured.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SettingSource {
/// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
User,
/// Admin-specified setting: sourced via the `/etc/yubico/yubikeypiv.conf`
/// configuration file.
Admin,
/// Default setting.
Default,
}
impl Default for SettingSource {
fn default() -> Self {
Self::Default
}
}
/// Setting booleans: configuration values sourced from a file or the environment.
///
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
/// variables.
#[derive(Copy, Clone, Debug, Default)]
pub struct Setting {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user, admin, or default)
pub source: SettingSource,
}
impl Setting {
/// Get a setting by name.
pub fn get(key: &str, default: bool) -> Self {
Self::from_file(key)
.or_else(|| Self::from_env(key))
.unwrap_or(Self {
value: default,
source: SettingSource::Default,
})
}
/// Get a boolean config value from the provided config file
fn from_file(key: &str) -> Option<Self> {
if let Ok(file) = File::open(DEFAULT_CONFIG_FILE) {
for line in BufReader::new(file).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(2, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
return Some(Setting {
source: SettingSource::Admin,
value: value == "1" || value == "true",
});
}
}
}
None
}
/// Get a setting boolean from an environment variable
fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{}", key))
.ok()
.map(|value| Setting {
source: SettingSource::User,
value: value == "1" || value == "true",
})
}
}
-138
View File
@@ -1,138 +0,0 @@
//! Configuration setting values parsed from the environment and config file:
//! `/etc/yubico/yubikeypiv.conf`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// Default location of the YubiKey PIV configuration file
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
use std::{
env,
fs::File,
io::{BufRead, BufReader},
};
/// Source of how a setting was configured
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Source {
/// User-specified setting
User,
/// Admin-specified setting
Admin,
/// Default setting
Default,
}
/// Setting booleans
#[derive(Copy, Clone, Debug)]
pub struct BoolValue {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user/admin/default)
pub source: Source,
}
impl BoolValue {
/// Get a [`BoolValue`] value
pub fn get(key: &str, def: bool) -> Self {
let mut setting = get_setting_from_file(key);
if setting.source == Source::Default {
setting = get_setting_from_env(key);
}
if setting.source == Source::Default {
setting.value = def;
}
setting
}
}
/// Get a boolean config value
fn get_setting_from_file(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
let file = match File::open(DEFAULT_CONFIG_FILE) {
Ok(f) => f,
Err(_) => return setting,
};
for line in BufReader::new(file).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(1, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
setting.source = Source::Admin;
setting.value = value == "1" || value == "true";
break;
}
}
setting
}
/// Get a setting boolean from an environment variable
fn get_setting_from_env(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
if let Ok(value) = env::var(format!("YUBIKEY_PIV_{}", key)) {
setting.source = Source::User;
setting.value = value == "1" || value == "true";
}
setting
}
+109 -134
View File
@@ -1,46 +1,49 @@
//! YubiKey PC/SC transactions //! YubiKey PC/SC transactions
use crate::{ use crate::{
apdu::{Ins, APDU}, apdu::Response,
error::Error, apdu::{Apdu, Ins, StatusWords},
yubikey::*, consts::{CB_BUF_MAX, CB_OBJ_MAX},
}; error::{Error, Result},
#[cfg(feature = "untested")] piv::{AlgorithmId, SlotId},
use crate::{
apdu::{Response, StatusWords},
consts::*,
key::{AlgorithmId, SlotId},
mgm::MgmKey,
serialization::*, serialization::*,
yubikey::*,
Buffer, ObjectId, Buffer, ObjectId,
}; };
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto;
#[cfg(feature = "untested")]
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.
/// ///
@@ -48,7 +51,7 @@ 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<Vec<u8>, Error> { pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>> {
trace!(">>> {:?}", send_buffer); trace!(">>> {:?}", send_buffer);
let mut recv_buffer = vec![0u8; recv_len]; let mut recv_buffer = vec![0u8; recv_len];
@@ -63,10 +66,10 @@ impl<'tx> Transaction<'tx> {
} }
/// Select application. /// Select application.
pub fn select_application(&self) -> Result<(), Error> { pub fn select_application(&self) -> Result<()> {
let response = APDU::new(Ins::SelectApplication) 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);
@@ -84,10 +87,10 @@ 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(Ins::GetVersion).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);
@@ -100,15 +103,13 @@ impl<'tx> Transaction<'tx> {
Ok(Version::new(response.data()[..3].try_into().unwrap())) Ok(Version::new(response.data()[..3].try_into().unwrap()))
} }
/// 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(Ins::SelectApplication) 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();
@@ -117,7 +118,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!(
@@ -128,9 +129,9 @@ impl<'tx> Transaction<'tx> {
} }
// reselect the PIV applet // reselect the PIV applet
let sw = APDU::new(Ins::SelectApplication) 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();
@@ -141,8 +142,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(Ins::GetSerial).transmit(self, 0xFF)?; let resp = Apdu::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() { if !resp.is_success() {
error!( error!(
@@ -162,13 +163,12 @@ impl<'tx> Transaction<'tx> {
} }
/// 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 { if pin.len() > CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let mut query = APDU::new(Ins::Verify); let mut query = Apdu::new(Ins::Verify);
query.params(0x00, 0x80); query.params(0x00, 0x80);
// Empty pin means we are querying the number of retries. We set no data in this // Empty pin means we are querying the number of retries. We set no data in this
@@ -190,20 +190,26 @@ impl<'tx> Transaction<'tx> {
} }
} }
/// 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, Ins::ChangeReference.code(), 0, 0x80]; &self,
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] = Ins::ResetRetry.code(); const PUK: u8 = 0x81;
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81; let templ = match action {
} ChangeRefAction::ChangePin => [0, Ins::ChangeReference.code(), 0, PIN],
ChangeRefAction::ChangePuk => [0, Ins::ChangeReference.code(), 0, PUK],
ChangeRefAction::UnblockPin => [0, Ins::ResetRetry.code(), 0, PIN],
};
let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]); let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
indata[0..current_pin.len()].copy_from_slice(current_pin); indata[0..current_pin.len()].copy_from_slice(current_pin);
@@ -229,24 +235,18 @@ 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(Ins::SetMgmKey) let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2) .params(0xff, p2)
.data(&data) .data(data)
.transmit(self, 261)? .transmit(self, 261)?
.status_words(); .status_words();
@@ -262,7 +262,6 @@ 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,
@@ -270,11 +269,10 @@ impl<'tx> Transaction<'tx> {
algorithm: AlgorithmId, algorithm: AlgorithmId,
key: SlotId, key: SlotId,
decipher: bool, decipher: bool,
) -> Result<Buffer, 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, Ins::Authenticate.code(), algorithm.into(), key.into()]; let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
let mut len: usize = 0;
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
@@ -310,19 +308,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] = match (algorithm, decipher) { &mut buf[2..],
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85, match (algorithm, decipher) {
_ => 0x81, (AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
}; _ => 0x81,
},
offset += 3; sign_in
offset += set_length(&mut indata[offset..], in_len); )
indata[offset..(offset + in_len)].copy_from_slice(sign_in); .expect("large enough"),
offset += in_len; 1 + bytes + in_len
);
})?;
let response = self let response = self
.transfer_data(&templ, &indata[..offset], 1024) .transfer_data(&templ, &indata[..offset], 1024)
@@ -341,41 +341,33 @@ impl<'tx> Transaction<'tx> {
} }
} }
let data = response.data(); 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);
Ok(Buffer::new(data[offset..(offset + len)].into()))
} }
/// 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 = 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;
@@ -389,19 +381,19 @@ 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.is_empty() && (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 {}",
@@ -426,7 +418,7 @@ impl<'tx> Transaction<'tx> {
sw & 0xff sw & 0xff
); );
let response = APDU::new(Ins::GetResponseApdu).transmit(self, 261)?; let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code(); sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) { if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
@@ -449,9 +441,8 @@ impl<'tx> Transaction<'tx> {
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, Ins::GetData.code(), 0x3f, 0xff]; let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
@@ -462,40 +453,30 @@ 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 = Buffer::new(response.data().into()); 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, Ins::PutData.code(), 0x3f, 0xff];
// TODO(tarcieri): replace with vector // TODO(tarcieri): replace with vector
@@ -508,14 +489,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
+211 -369
View File
@@ -30,53 +30,59 @@
// (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.
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
#[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::{Ins, StatusWords, APDU}, apdu::{Apdu, Ins},
key::{AlgorithmId, SlotId}, cccid::CccId,
metadata, chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::MgmKey, mgm::MgmKey,
serialization::*, piv,
Buffer, ObjectId, reader::{Context, Reader},
};
use crate::{
consts::*,
error::Error,
readers::{Reader, Readers},
transaction::Transaction, transaction::Transaction,
}; };
#[cfg(feature = "untested")] use log::{error, info};
use getrandom::getrandom;
use log::{error, info, warn};
use pcsc::Card; use pcsc::Card;
#[cfg(feature = "untested")] use rand_core::{OsRng, RngCore};
use secrecy::ExposeSecret;
use std::{ use std::{
convert::TryFrom,
fmt::{self, Display}, fmt::{self, Display},
str::FromStr,
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use std::{ use {
convert::TryInto, crate::{
time::{SystemTime, UNIX_EPOCH}, apdu::StatusWords,
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP},
metadata::AdminData,
transaction::ChangeRefAction,
Buffer, ObjectId,
},
secrecy::ExposeSecret,
std::time::{SystemTime, UNIX_EPOCH},
}; };
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
/// PIV Application ID /// Flag for PUK blocked
pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08]; pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// MGMT Application ID. /// 3DES authentication
pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key
pub(crate) const KEY_CARDMGM: u8 = 0x9b;
/// MGMT Applet ID.
///
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html> /// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17]; #[cfg(feature = "untested")]
const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// Cached YubiKey PIN const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN.
pub type CachedPin = secrecy::SecretVec<u8>; pub type CachedPin = secrecy::SecretVec<u8>;
/// YubiKey Serial Number /// YubiKey serial number.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Serial(pub u32); pub struct Serial(pub u32);
@@ -92,13 +98,21 @@ impl From<Serial> for u32 {
} }
} }
impl FromStr for Serial {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
s.parse().map(Serial).map_err(|_| Error::ParseError)
}
}
impl Display for Serial { impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
/// YubiKey Version /// YubiKey version.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Version { pub struct Version {
/// Major version component /// Major version component
@@ -122,8 +136,13 @@ impl Version {
} }
} }
/// YubiKey Device: this is the primary API for opening a session and impl Display for Version {
/// performing various operations. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
/// YubiKey device: primary API for opening a session and performing various operations.
/// ///
/// Almost all functionality in this library will require an open session /// Almost all functionality in this library will require an open session
/// with a YubiKey which is represented by this type. /// with a YubiKey which is represented by this type.
@@ -131,8 +150,8 @@ impl Version {
#[cfg_attr(not(feature = "untested"), allow(dead_code))] #[cfg_attr(not(feature = "untested"), allow(dead_code))]
pub struct YubiKey { pub struct YubiKey {
pub(crate) card: Card, pub(crate) card: Card,
pub(crate) name: String,
pub(crate) pin: Option<CachedPin>, pub(crate) pin: Option<CachedPin>,
pub(crate) is_neo: bool,
pub(crate) version: Version, pub(crate) version: Version,
pub(crate) serial: Serial, pub(crate) serial: Serial,
} }
@@ -143,10 +162,16 @@ impl YubiKey {
/// Returns an error if there is more than one YubiKey detected. /// Returns an error if there is more than one YubiKey detected.
/// ///
/// If you need to operate in environments with more than one YubiKey /// If you need to operate in environments with more than one YubiKey
/// attached to the same system, use [`yubikey_piv::Readers`] to select /// attached to the same system, use [`YubiKey::open_by_serial`] or
/// from the available PC/SC readers connected. /// [`yubikey::reader::Context`][`Context`] to select from the available
pub fn open() -> Result<Self, Error> { /// PC/SC readers.
let mut readers = Readers::open()?; pub fn open() -> Result<Self> {
let mut readers = Context::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
let mut reader_iter = readers.iter()?; let mut reader_iter = readers.iter()?;
if let Some(reader) = reader_iter.next() { if let Some(reader) = reader_iter.next() {
@@ -159,12 +184,37 @@ impl YubiKey {
} }
error!("no YubiKey detected!"); error!("no YubiKey detected!");
Err(Error::GenericError) Err(Error::NotFound)
} }
/// Reconnect to a YubiKey /// Open a YubiKey with a specific serial number.
pub fn open_by_serial(serial: Serial) -> Result<Self> {
let mut readers = Context::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
for reader in readers.iter()? {
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
if serial == yubikey.serial() {
return Ok(yubikey);
}
}
error!("no YubiKey detected with serial: {}", serial);
Err(Error::NotFound)
}
/// Reconnect to a YubiKey.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn reconnect(&mut self) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn reconnect(&mut self) -> Result<()> {
info!("trying to reconnect to current reader"); info!("trying to reconnect to current reader");
self.card.reconnect( self.card.reconnect(
@@ -189,47 +239,53 @@ impl YubiKey {
} }
/// Begin a transaction. /// Begin a transaction.
#[cfg(feature = "untested")] pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>> {
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> {
// TODO(tarcieri): reconnect support // TODO(tarcieri): reconnect support
Ok(Transaction::new(&mut self.card)?) Transaction::new(&mut self.card)
}
/// Get the name of the associated PC/SC card reader.
pub fn name(&self) -> &str {
&self.name
} }
/// Get the YubiKey's PIV application version. /// Get the YubiKey's PIV application version.
/// ///
/// This always uses the cached version queried when the key is initialized. /// This always uses the cached version queried when the key is initialized.
pub fn version(&mut self) -> Version { pub fn version(&self) -> Version {
self.version self.version
} }
/// Get YubiKey device serial number. /// Get YubiKey device serial number.
/// ///
/// This always uses the cached version queried when the key is initialized. /// This always uses the cached version queried when the key is initialized.
pub fn serial(&mut self) -> Serial { pub fn serial(&self) -> Serial {
self.serial self.serial
} }
/// Get YubiKey device model /// Get device configuration.
// TODO(tarcieri): use an emum for this pub fn config(&mut self) -> Result<Config> {
#[cfg(feature = "untested")] Config::get(self)
pub fn device_model(&self) -> u32 { }
if self.is_neo {
DEVTYPE_NEOr3 /// Get Cardholder Unique Identifier (CHUID).
} else { pub fn chuid(&mut self) -> Result<ChuId> {
// TODO(tarcieri): YK5? ChuId::get(self)
DEVTYPE_YK4 }
}
/// Get Cardholder Capability Container (CCC) Identifier.
pub fn cccid(&mut self) -> Result<CccId> {
CccId::get(self)
} }
/// Authenticate to the card using the provided management key (MGM). /// Authenticate to the card using the provided management key (MGM).
#[cfg(feature = "untested")] pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<()> {
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// get a challenge from the card // get a challenge from the card
let challenge = APDU::new(Ins::Authenticate) let challenge = Apdu::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x80, 0x00]) .data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !challenge.is_success() || challenge.data().len() < 12 { if !challenge.is_success() || challenge.data().len() < 12 {
@@ -240,25 +296,21 @@ impl YubiKey {
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap()); let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
let mut data = [0u8; 22]; let mut data = [0u8; 22];
data[0] = 0x7c; data[0] = TAG_DYN_AUTH;
data[1] = 20; // 2 + 8 + 2 +8 data[1] = 20; // 2 + 8 + 2 +8
data[2] = 0x80; data[2] = 0x80;
data[3] = 8; data[3] = 8;
data[4..12].copy_from_slice(&response); data[4..12].copy_from_slice(&response);
data[12] = 0x81; data[12] = 0x81;
data[13] = 8; data[13] = 8;
OsRng.fill_bytes(&mut data[14..22]);
if getrandom(&mut data[14..22]).is_err() {
error!("failed getting randomness for authentication");
return Err(Error::RandomnessError);
}
let mut challenge = [0u8; 8]; let mut challenge = [0u8; 8];
challenge.copy_from_slice(&data[14..22]); challenge.copy_from_slice(&data[14..22]);
let authentication = APDU::new(Ins::Authenticate) let authentication = Apdu::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&data) .data(data)
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !authentication.is_success() { if !authentication.is_success() {
@@ -276,12 +328,18 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Deauthenticate /// Get the PIV keys contained in this YubiKey.
pub fn piv_keys(&mut self) -> Result<Vec<piv::Key>> {
piv::Key::list(self)
}
/// Deauthenticate.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn deauthenticate(&mut self) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn deauthenticate(&mut self) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let status_words = APDU::new(Ins::SelectApplication) let status_words = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(MGMT_AID) .data(MGMT_AID)
.transmit(&txn, 255)? .transmit(&txn, 255)?
@@ -298,37 +356,8 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Sign data using a PIV key
#[cfg(feature = "untested")]
pub fn sign_data(
&mut self,
raw_in: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(raw_in, algorithm, key, false)
}
/// Decrypt data using a PIV key
#[cfg(feature = "untested")]
pub fn decrypt_data(
&mut self,
input: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true)
}
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")] pub fn verify_pin(&mut self, pin: &[u8]) -> Result<()> {
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
{ {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.verify_pin(pin)?; txn.verify_pin(pin)?;
@@ -341,9 +370,8 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Get the number of PIN retries /// Get the number of PIN retries.
#[cfg(feature = "untested")] pub fn get_pin_retries(&mut self) -> Result<u8> {
pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// Force a re-select to unverify, because once verified the spec dictates that // Force a re-select to unverify, because once verified the spec dictates that
@@ -359,9 +387,10 @@ impl YubiKey {
} }
} }
/// Set the number of PIN retries /// Set the number of PIN retries.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<()> {
// Special case: if either retry count is 0, it's a successful no-op // Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 { if pin_tries == 0 || puk_tries == 0 {
return Ok(()); return Ok(());
@@ -383,12 +412,13 @@ impl YubiKey {
/// Change the Personal Identification Number (PIN). /// Change the Personal Identification Number (PIN).
/// ///
/// The default PIN code is 123456 /// The default PIN code is `123456`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> {
{ {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PIN, current_pin, new_pin)?; txn.change_ref(ChangeRefAction::ChangePin, current_pin, new_pin)?;
} }
if !new_pin.is_empty() { if !new_pin.is_empty() {
@@ -398,16 +428,13 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Set PIN last changed /// Set PIN last changed.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
let mut data = [0u8; CB_BUF_MAX]; pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<()> {
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
let buffer = metadata::read(&txn, TAG_ADMIN)?; let mut admin_data = AdminData::read(&txn)?;
let mut cb_data = buffer.len();
data[..cb_data].copy_from_slice(&buffer);
// TODO(tarcieri): double check this is little endian // TODO(tarcieri): double check this is little endian
let tnow = SystemTime::now() let tnow = SystemTime::now()
@@ -416,19 +443,14 @@ impl YubiKey {
.as_secs() .as_secs()
.to_le_bytes(); .to_le_bytes();
metadata::set_item( admin_data
&mut data, .set_item(TAG_ADMIN_TIMESTAMP, &tnow)
&mut cb_data, .map_err(|e| {
CB_OBJ_MAX, error!("could not set pin timestamp, err = {}", e);
TAG_ADMIN_TIMESTAMP, e
&tnow, })?;
)
.map_err(|e| {
error!("could not set pin timestamp, err = {}", e);
e
})?;
metadata::write(&txn, TAG_ADMIN, &data, max_size).map_err(|e| { admin_data.write(&txn).map_err(|e| {
error!("could not write admin data, err = {}", e); error!("could not write admin data, err = {}", e);
e e
})?; })?;
@@ -442,26 +464,27 @@ impl YubiKey {
/// ///
/// The PUK is part of the PIV standard that the YubiKey follows. /// The PUK is part of the PIV standard that the YubiKey follows.
/// ///
/// The default PUK code is 12345678. /// The default PUK code is `12345678`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk) txn.change_ref(ChangeRefAction::ChangePuk, current_puk, new_puk)
} }
/// Block PUK: permanently prevent the PIN from becoming unblocked /// Block PUK: permanently prevent the PIN from becoming unblocked.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn block_puk(yubikey: &mut YubiKey) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn block_puk(&mut self) -> Result<()> {
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
let mut tries_remaining: i32 = -1; let mut tries_remaining: i32 = -1;
let mut flags = [0]; let mut flags = [0];
let max_size = yubikey.obj_size_max(); let txn = self.begin_transaction()?;
let txn = yubikey.begin_transaction()?;
while tries_remaining != 0 { while tries_remaining != 0 {
// 2 -> change puk // 2 -> change puk
let res = txn.change_pin(CHREF_ACT_CHANGE_PUK, &puk, &puk); let res = txn.change_ref(ChangeRefAction::ChangePuk, &puk, &puk);
match res { match res {
Ok(()) => puk[0] += 1, Ok(()) => puk[0] += 1,
@@ -480,34 +503,29 @@ impl YubiKey {
} }
} }
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) { // Attempt to set the "PUK blocked" flag in admin data.
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) { let mut admin_data = AdminData::read(&txn)
if item.len() == flags.len() { .map(|data| {
flags.copy_from_slice(item) if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
} else { if item.len() == flags.len() {
error!( flags.copy_from_slice(item)
"admin flags exist, but are incorrect size: {} (expected {})", } else {
item.len(), error!(
flags.len() "admin flags exist, but are incorrect size: {} (expected {})",
); item.len(),
flags.len()
);
}
} }
}
} data
})
.unwrap_or_default();
flags[0] |= ADMIN_FLAGS_1_PUK_BLOCKED; flags[0] |= ADMIN_FLAGS_1_PUK_BLOCKED;
let mut data = [0u8; CB_BUF_MAX];
let mut cb_data: usize = data.len();
if metadata::set_item( if admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags).is_ok() {
&mut data, if admin_data.write(&txn).is_err() {
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags,
)
.is_ok()
{
if metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size).is_err() {
error!("could not write admin metadata"); error!("could not write admin metadata");
} }
} else { } else {
@@ -520,181 +538,37 @@ impl YubiKey {
/// Unblock a Personal Identification Number (PIN) using a previously /// Unblock a Personal Identification Number (PIN) using a previously
/// configured PIN Unblocking Key (PUK). /// configured PIN Unblocking Key (PUK).
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin) txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin)
} }
/// Fetch an object from the YubiKey /// Fetch an object from the YubiKey.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer, Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.fetch_object(object_id) txn.fetch_object(object_id)
} }
/// Save an object /// Save an object.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.save_object(object_id, indata) txn.save_object(object_id, indata)
} }
/// Import a private encryption or signing key into the YubiKey /// Get an auth challenge.
// TODO(tarcieri): refactor this into separate methods per key type
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn import_private_key( #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
&mut self, pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> {
key: SlotId,
algorithm: AlgorithmId,
p: Option<&[u8]>,
q: Option<&[u8]>,
dp: Option<&[u8]>,
dq: Option<&[u8]>,
qinv: Option<&[u8]>,
ec_data: Option<&[u8]>,
pin_policy: u8,
touch_policy: u8,
) -> Result<(), Error> {
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
if pin_policy != YKPIV_PINPOLICY_DEFAULT
&& pin_policy != YKPIV_PINPOLICY_NEVER
&& pin_policy != YKPIV_PINPOLICY_ONCE
&& pin_policy != YKPIV_PINPOLICY_ALWAYS
{
return Err(Error::GenericError);
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT
&& touch_policy != YKPIV_TOUCHPOLICY_NEVER
&& touch_policy != YKPIV_TOUCHPOLICY_ALWAYS
&& touch_policy != YKPIV_TOUCHPOLICY_CACHED
{
return Err(Error::GenericError);
}
let (elem_len, params, param_tag) = match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
(Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() {
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
_ => unreachable!(),
},
vec![p, q, dp, dq, qinv],
0x01,
)
}
_ => return Err(Error::GenericError),
},
AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data {
Some(ec_data) => {
if ec_data.len() >= key_data.len() {
// This can never be true, but check to be explicit.
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
_ => unreachable!(),
},
vec![ec_data],
0x06,
)
}
_ => return Err(Error::GenericError),
},
};
let mut offset = 0;
for (i, param) in params.into_iter().enumerate() {
key_data[offset] = param_tag + i as u8;
offset += 1;
offset += set_length(&mut key_data[offset..], elem_len);
let padding = elem_len - param.len();
let remaining = key_data.len() - offset;
if padding > remaining {
return Err(Error::AlgorithmError);
}
for b in &mut key_data[offset..offset + padding] {
*b = 0;
}
offset += padding;
key_data[offset..offset + param.len()].copy_from_slice(param);
offset += param.len();
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
key_data[offset] = YKPIV_PINPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = pin_policy;
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
key_data[offset] = YKPIV_TOUCHPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = touch_policy;
offset += 3;
}
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let status_words = txn let response = Apdu::new(Ins::Authenticate)
.transfer_data(&templ, &key_data[..offset], 256)? .params(ALGO_3DES, KEY_CARDMGM)
.status_words(); .data([0x7c, 0x02, 0x81, 0x00])
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
/// Generate an attestation certificate for a stored key.
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = self.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported);
} else {
return Err(Error::GenericError);
}
}
if response.data()[0] != 0x30 {
return Err(Error::GenericError);
}
Ok(Buffer::new(response.data().into()))
}
/// Get an auth challenge
#[cfg(feature = "untested")]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
let txn = self.begin_transaction()?;
let response = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !response.is_success() { if !response.is_success() {
@@ -704,9 +578,10 @@ impl YubiKey {
Ok(response.data()[4..12].try_into().unwrap()) Ok(response.data()[4..12].try_into().unwrap())
} }
/// Verify an auth response /// Verify an auth response.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> {
let mut data = [0u8; 12]; let mut data = [0u8; 12];
data[0] = 0x7c; data[0] = 0x7c;
data[1] = 0x0a; data[1] = 0x0a;
@@ -717,9 +592,9 @@ impl YubiKey {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// send the response to the card and a challenge of our own. // send the response to the card and a challenge of our own.
let status_words = APDU::new(Ins::Authenticate) let status_words = Apdu::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&data) .data(data)
.transmit(&txn, 261)? .transmit(&txn, 261)?
.status_words(); .status_words();
@@ -736,7 +611,8 @@ impl YubiKey {
/// ///
/// The reset function is only available when both pins are blocked. /// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn reset_device(&mut self) -> Result<(), Error> { #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn reset_device(&mut self) -> Result<()> {
let templ = [0, Ins::Reset.code(), 0, 0]; let templ = [0, Ins::Reset.code(), 0, 0];
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words(); let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();
@@ -747,22 +623,12 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Get max object size supported by this device
#[cfg(feature = "untested")]
pub(crate) fn obj_size_max(&self) -> usize {
if self.is_neo {
CB_OBJ_MAX_NEO
} else {
CB_OBJ_MAX
}
}
} }
impl<'a> TryFrom<&'a Reader<'_>> for YubiKey { impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
type Error = Error; type Error = Error;
fn try_from(reader: &'a Reader<'_>) -> Result<Self, Error> { fn try_from(reader: &'a Reader<'_>) -> Result<Self> {
let mut card = reader.connect().map_err(|e| { let mut card = reader.connect().map_err(|e| {
error!("error connecting to reader '{}': {}", reader.name(), e); error!("error connecting to reader '{}': {}", reader.name(), e);
e e
@@ -770,43 +636,19 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
info!("connected to reader: {}", reader.name()); info!("connected to reader: {}", reader.name());
let mut is_neo = false; let (version, serial) = {
let version: Version;
let serial: Serial;
{
let txn = Transaction::new(&mut card)?; let txn = Transaction::new(&mut card)?;
let mut atr_buf = [0; CB_ATR_MAX];
let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?;
if atr == YKPIV_ATR_NEO_R3 {
is_neo = true;
}
txn.select_application()?; txn.select_application()?;
// now that the PIV application is selected, retrieve the version let v = txn.get_version()?;
// and serial number. Previously the NEO/YK4 required switching let s = txn.get_serial(v)?;
// to the yk applet to retrieve the serial, YK5 implements this (v, s)
// as a PIV applet command. Unfortunately, this change requires };
// that we retrieve the version number first, so that get_serial
// can determine how to get the serial number, which for the NEO/Yk4
// will result in another selection of the PIV applet.
version = txn.get_version().map_err(|e| {
warn!("failed to retrieve version: '{}'", e);
e
})?;
serial = txn.get_serial(version).map_err(|e| {
warn!("failed to retrieve serial number: '{}'", e);
e
})?;
}
let yubikey = YubiKey { let yubikey = YubiKey {
card, card,
name: String::from(reader.name()),
pin: None, pin: None,
is_neo,
version, version,
serial, serial,
}; };
+85
View File
@@ -0,0 +1,85 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
d4:29:8f:df:8a:af:7b:c0:d7:bf:19:9d:90:d5:ef:ca
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Ferdinand Linnenberg CA
Validity
Not Before: Feb 10 12:25:37 2022 GMT
Not After : May 15 12:25:37 2024 GMT
Subject: CN=Bob
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d5:27:9b:99:1b:3a:36:64:36:c8:e5:78:64:b6:
9d:70:9d:29:6c:0e:85:91:4b:78:3b:dc:16:c3:09:
8c:d3:74:20:8c:6f:ed:c3:90:c9:1b:4d:80:d5:46:
da:52:7f:d2:2f:bc:b2:f7:40:8d:ad:dd:24:b9:5c:
dc:a2:21:2f:48:ec:06:93:8b:89:f0:cd:63:ff:a1:
fd:ce:36:d5:07:7a:1e:0e:cf:68:a8:c1:b3:7f:62:
84:b7:e1:cf:25:7b:3f:a8:3c:ac:07:1a:fd:c2:e1:
e0:9e:26:24:c1:0d:6d:9d:c6:57:6a:b4:39:28:3d:
88:3e:c9:6a:89:90:72:4a:7b:75:c5:5e:1b:5e:5c:
32:54:a3:ff:eb:01:68:7f:89:b4:4c:01:3f:08:8e:
6c:61:49:60:26:0b:26:58:81:d7:1a:57:ee:52:5c:
05:47:de:da:eb:b5:92:9d:5b:ce:26:18:44:59:3e:
27:d0:61:86:e2:f4:c6:d9:c7:2b:1f:cb:ea:78:f0:
a1:a9:57:d7:98:4c:c1:2f:ae:6a:38:b4:34:53:2e:
5a:9e:f8:58:c7:51:e7:fd:b8:27:cd:87:72:26:c1:
7d:14:c7:cd:fb:f2:04:8a:c4:8f:61:cf:a8:78:bd:
21:be:28:cb:e8:a8:65:29:28:82:46:2f:18:e6:ff:
6f:53
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
B5:A5:F0:37:25:97:AD:BE:F1:43:52:45:4D:8B:A0:5E:E9:78:21:B8
X509v3 Authority Key Identifier:
keyid:26:4E:EB:B0:A5:1B:08:A8:90:2A:85:04:73:84:B5:A5:2C:61:D6:91
DirName:/CN=Ferdinand Linnenberg CA
serial:8C:E0:40:D9:D8:60:E5:77
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Key Usage:
Digital Signature
Signature Algorithm: sha256WithRSAEncryption
19:f3:eb:c1:95:e6:d5:a9:33:d7:2e:02:d8:3a:91:84:81:14:
93:fc:03:4d:b1:4b:9d:0b:9b:94:93:9f:1a:0d:87:31:a1:fa:
a6:c7:3a:6b:18:24:12:ab:28:fb:c8:e3:09:a2:5d:50:49:00:
d9:18:e6:4a:09:18:e0:1c:da:d3:19:96:3d:74:72:fe:e0:8f:
ee:59:54:66:2e:57:72:b8:91:55:06:13:e5:9e:89:a2:3a:13:
3b:45:30:d3:cd:15:0e:81:eb:4f:b0:6a:a4:6d:00:7d:5b:c0:
4a:7f:97:d0:27:27:31:ae:3e:72:f1:74:fe:86:8e:29:a9:42:
23:26:22:db:08:8b:df:e9:d3:83:8d:81:10:36:d7:33:68:5e:
cb:93:cb:1e:12:c8:cb:be:5e:5c:8e:58:b0:1d:06:5e:c9:98:
b7:f1:49:fe:c4:03:de:b4:2b:da:9d:2c:7d:98:37:1c:6c:a8:
95:21:6f:23:e3:2e:09:bc:6c:e5:ed:e2:50:d8:f7:da:45:39:
d8:34:8a:57:0c:4f:d0:0d:80:06:d6:34:63:72:27:d1:50:d1:
d2:21:2c:97:57:17:98:02:95:3a:96:ed:75:9f:cc:f3:b8:f1:
3a:85:f9:58:08:9b:a0:75:fd:9b:fd:31:dd:08:dc:14:3d:f4:
68:aa:d4:30
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgIRANQpj9+Kr3vA178ZnZDV78owDQYJKoZIhvcNAQELBQAw
IjEgMB4GA1UEAwwXRmVyZGluYW5kIExpbm5lbmJlcmcgQ0EwHhcNMjIwMjEwMTIy
NTM3WhcNMjQwNTE1MTIyNTM3WjAOMQwwCgYDVQQDDANCb2IwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDVJ5uZGzo2ZDbI5Xhktp1wnSlsDoWRS3g73BbD
CYzTdCCMb+3DkMkbTYDVRtpSf9IvvLL3QI2t3SS5XNyiIS9I7AaTi4nwzWP/of3O
NtUHeh4Oz2iowbN/YoS34c8lez+oPKwHGv3C4eCeJiTBDW2dxldqtDkoPYg+yWqJ
kHJKe3XFXhteXDJUo//rAWh/ibRMAT8IjmxhSWAmCyZYgdcaV+5SXAVH3trrtZKd
W84mGERZPifQYYbi9MbZxysfy+p48KGpV9eYTMEvrmo4tDRTLlqe+FjHUef9uCfN
h3ImwX0Ux8378gSKxI9hz6h4vSG+KMvoqGUpKIJGLxjm/29TAgMBAAGjgaMwgaAw
CQYDVR0TBAIwADAdBgNVHQ4EFgQUtaXwNyWXrb7xQ1JFTYugXul4IbgwUgYDVR0j
BEswSYAUJk7rsKUbCKiQKoUEc4S1pSxh1pGhJqQkMCIxIDAeBgNVBAMMF0ZlcmRp
bmFuZCBMaW5uZW5iZXJnIENBggkAjOBA2dhg5XcwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBAQAZ8+vBlebVqTPXLgLY
OpGEgRST/ANNsUudC5uUk58aDYcxofqmxzprGCQSqyj7yOMJol1QSQDZGOZKCRjg
HNrTGZY9dHL+4I/uWVRmLldyuJFVBhPlnomiOhM7RTDTzRUOgetPsGqkbQB9W8BK
f5fQJycxrj5y8XT+ho4pqUIjJiLbCIvf6dODjYEQNtczaF7Lk8seEsjLvl5cjliw
HQZeyZi38Un+xAPetCvanSx9mDccbKiVIW8j4y4JvGzl7eJQ2PfaRTnYNIpXDE/Q
DYAG1jRjcifRUNHSISyXVxeYApU6lu11n8zzuPE6hflYCJugdf2b/THdCNwUPfRo
qtQw
-----END CERTIFICATE-----
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIi6DixMpf5PQCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECLgvCsIAjjXRBIIEyDAss0V4NrI5
W7KXPRgRJ1tqvQrWZQTIFu4Crzs9Inb4TtSv5mATI9ZU2RMFF6MYXlhIxJng861P
5IWcU6VeOjRFej8wcB3uTvD2z7NB2cyA5BZSojrZfX5OIEKL9sBzn0vinqmm5N1z
oXhLMgf0FZssA3+zjIf04vtvmk5pxTCE6dq6vlsEIJyQ0xGc39bStIwk2a4E9wvi
XayKNJnRFSrTahuI3DvQJPd9TdmM6sBKnrcJrDa6LvH51SrGzW8bBEjDAmC7yJdi
mBckFTjZ6rGrSxOI6HdnF9RP+y9OiLE4ou5OJ9MbBtngq9OrTImAMZ5ftWowaqX/
Do1UTqixi4ecWrr5fr1+A2Vch0I1drds2e/mmLR+5GEQXQXZPZKjtPMwxM/AYnTZ
w/M3T9KtwSj0s5G6Saz4WpzaUL7wATb/UNqMr8Ifl8mHEVoFZhvoRpMWA7Yj1oPb
cHz8lsfoSrTnK+zLR8ZK3HRu4MtpdCNVwQIJ67T6Feb8YLwYSccTNHBSqUmWRD95
wOOqY33xcfplaQ2Y+/8+mHScGSEPNmC4f7EggDeUnG3ow0f4n95DENO+aYqGLjSF
+XdCjhD+NTNdlV0z50B6P2XWUoBZOOnPfgFf4nAgn7xQbkZZOk5bQDKo2Zu9jW9/
uJyTHqI58tcopI3cjd5iQXOJUrM4OWpPIu3p+VWaIAA8JzJIfDN6fyQ3qsMr305L
30JcjrH6if/6J+2g+DpMAK928JY2hfE8VNWH7096ZnArp42/hLYsNuYnSrL1eS+g
F/4mvyZyLLTFyB0Frnic4I1QTuNkNmwSrm/B5wIWLqkS7XAyyDDXAcaTHdZCN6nM
O2OuF7DfBsFcNMM4VagG5adPS9CYkvz0EEh6ho5XiP2yL3tZfsHyuB4njAsV3aFi
D0Yq7QiCf5iA2d4KsYO6yr1wPfVhlsmPi3++mrHulBwwCQWHTlPgRZnjj1xgmPcQ
00KsUVh+CMWlf20O5sKhzjvkzbwUj1K+ZfMDuuq1RbzFRSx+Gx8vIaThFg9kVyoP
zuvzsT6qc3BGNHmaGZ3d5Re25AuGRTF4cTpDfjW0UL7Wnvnis7iMrUasDhyF7CFn
/KG7eKzxqS08o6D4AM5S/fzZEtszoEgAga6DS2R75FVskDweWuEIsar9UGg3UlmW
q3+rRPRf1CzrLtyYenkkLg/ajr8JOnFGqZVaLmMnegZQH6rF3aEzlQLNgbNepcuA
ObSmAO6MR3MlQgdsH/lNzOPdj1gKcE25hOjGfmwgbOXSJv9Cz0bcBLFEyLZSNpRk
HhNejj6BEz/Cmqg1wm7SOBHsXJGcOTnLO1Y3FBt0I7heWvWmj9rOLG9tvvx9dtrP
pQyDbIcWonuXLrXYSPyOjmeWoQSzdH3NsCswBV4G+iOiLCJDkElR+mrwKbhtS88T
3YeZnsCTsmH+jZpxGgPTObjIG91U4UE4Pnkwc2df355VuOKrf0rB/NK0A9hZGqsV
nxtodjn92P6UFzmfEdt95pMcmurK9wkm1kRkP7cIyAs2lCOIdbgGsszz6Mk16Xqy
49RdhLxJrJ4gkYZIAbY+KNGVc7uPhm/T9xGrIstbEsoUM7jy7nHMOCCDgdbwX/4X
caMeSMZcZ+RvraDDBEbSbg==
-----END ENCRYPTED PRIVATE KEY-----
+364 -8
View File
@@ -3,18 +3,374 @@
#![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 log::trace;
use yubikey_piv::YubiKey; use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore};
use rsa::pkcs1v15;
use sha2::{Digest, Sha256};
use signature::{hazmat::PrehashVerifier, Signature as _};
use std::{env, str::FromStr, sync::Mutex};
use x509::RelativeDistinguishedName;
use yubikey::{
certificate,
certificate::{Certificate, PublicKeyInfo},
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
};
#[test] static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
#[ignore]
fn connect() {
// 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().unwrap(); let yubikey = if let Ok(serial) = env::var("YUBIKEY_SERIAL") {
dbg!(&yubikey.version()); let serial = Serial::from_str(&serial).unwrap();
dbg!(&yubikey.serial()); YubiKey::open_by_serial(serial).unwrap()
} else {
YubiKey::open().unwrap()
};
trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version());
Mutex::new(yubikey)
});
//
// CCCID support
//
#[test]
#[ignore]
fn test_get_cccid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.cccid() {
Ok(cccid) => trace!("CCCID: {:?}", cccid),
Err(Error::NotFound) => trace!("CCCID not found"),
Err(err) => panic!("error getting CCCID: {:?}", err),
}
}
//
// CHUID support
//
#[test]
#[ignore]
fn test_get_chuid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.chuid() {
Ok(chuid) => trace!("CHUID: {:?}", chuid),
Err(Error::NotFound) => trace!("CHUID not found"),
Err(err) => panic!("error getting CHUID: {:?}", err),
}
}
//
// Device config support
//
#[test]
#[ignore]
fn test_get_config() {
let mut yubikey = YUBIKEY.lock().unwrap();
let config_result = yubikey.config();
assert!(config_result.is_ok());
trace!("config: {:?}", config_result.unwrap());
}
//
// Cryptographic key support
//
#[test]
#[ignore]
fn test_list_keys() {
let mut yubikey = YUBIKEY.lock().unwrap();
let keys_result = Key::list(&mut yubikey);
assert!(keys_result.is_ok());
trace!("keys: {:?}", keys_result.unwrap());
}
//
// PIN support
//
#[test]
#[ignore]
fn test_verify_pin() {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"000000").is_err());
assert!(yubikey.verify_pin(b"123456").is_ok());
}
//
// Management key support
//
#[cfg(feature = "untested")]
#[test]
#[ignore]
fn test_set_mgmkey() {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
// Set a protected management key.
assert!(MgmKey::generate().set_protected(&mut yubikey).is_ok());
let protected = MgmKey::get_protected(&mut yubikey).unwrap();
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_ok());
// Set a manual management key.
let manual = MgmKey::generate();
assert!(manual.set_manual(&mut yubikey, false).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_err());
assert!(yubikey.authenticate(protected.clone()).is_err());
assert!(yubikey.authenticate(manual.clone()).is_ok());
// Set back to the default management key.
assert!(MgmKey::set_default(&mut yubikey).is_ok());
assert!(MgmKey::get_protected(&mut yubikey).is_err());
assert!(yubikey.authenticate(protected).is_err());
assert!(yubikey.authenticate(manual).is_err());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
}
//
// Certificate support
//
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1);
// Generate a new key in the selected slot.
let generated = piv::generate(
&mut yubikey,
slot,
algorithm,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let mut serial = [0u8; 20];
OsRng.fill_bytes(&mut serial);
// Generate a self-signed certificate for the new key.
let extensions: &[x509::Extension<'_, &[u64]>] = &[];
let cert_result = Certificate::generate_self_signed(
&mut yubikey,
slot,
serial,
None,
&[RelativeDistinguishedName::common_name("testSubject")],
generated,
extensions,
);
assert!(cert_result.is_ok());
let cert = cert_result.unwrap();
trace!("cert: {:?}", cert);
cert
}
#[test]
#[ignore]
fn generate_self_signed_rsa_cert() {
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024);
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pkcs1v15::VerifyingKey::<Sha256>::from(pubkey.clone()),
_ => 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 = pkcs1v15::Signature::from_bytes(&data[data.len() - 128..]).unwrap();
let hash = Sha256::digest(msg);
assert!(pubkey.verify_prehash(&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());
}
#[test]
#[ignore]
fn test_slot_id_display() {
assert_eq!(format!("{}", SlotId::Authentication), "Authentication");
assert_eq!(format!("{}", SlotId::Signature), "Signature");
assert_eq!(format!("{}", SlotId::KeyManagement), "KeyManagement");
assert_eq!(
format!("{}", SlotId::CardAuthentication),
"CardAuthentication"
);
assert_eq!(format!("{}", SlotId::Attestation), "Attestation");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R1)), "R1");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R2)), "R2");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R3)), "R3");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R4)), "R4");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R5)), "R5");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R6)), "R6");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R7)), "R7");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R8)), "R8");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R9)), "R9");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R10)), "R10");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R11)), "R11");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R12)), "R12");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R13)), "R13");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R14)), "R14");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R15)), "R15");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R16)), "R16");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R17)), "R17");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R18)), "R18");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R19)), "R19");
assert_eq!(format!("{}", SlotId::Retired(RetiredSlotId::R20)), "R20");
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Pin)),
"PIN"
);
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Puk)),
"PUK"
);
assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Management)),
"Management"
);
}
//
// Metadata
//
#[test]
#[ignore]
fn test_read_metadata() {
let mut yubikey = 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,
AlgorithmId::EccP256,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let metadata = piv::metadata(&mut yubikey, slot).unwrap();
assert_eq!(metadata.public, Some(generated));
}
#[test]
#[ignore]
fn test_serial_string_conversions() {
//2^152+1
let serial: [u8; 20] = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01,
];
let s = certificate::Serial::from(serial);
assert_eq!(
s.as_x509_int(),
"5708990770823839524233143877797980545530986497"
);
assert_eq!(
s.as_x509_hex(),
"01:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01"
);
let serial2: [u8; 20] = [
0xA1, 0xF3, 0x02, 0x30, 0x76, 0x01, 0x32, 0x48, 0x09, 0x9C, 0x10, 0xAA, 0x3F, 0xA0, 0x54,
0x0D, 0xC0, 0xB7, 0x65, 0x01,
];
let s2 = certificate::Serial::from(serial2);
assert_eq!(
s2.as_x509_int(),
"924566785900861696177829411010986812227211191553"
);
assert_eq!(
s2.as_x509_hex(),
"a1:f3:02:30:76:01:32:48:09:9c:10:aa:3f:a0:54:0d:c0:b7:65:01"
);
let serial3: [u8; 20] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x3F, 0xA0, 0x54,
0x0D, 0xC0, 0xB7, 0x65, 0x01,
];
let s3 = certificate::Serial::from(serial3);
assert_eq!(s3.as_x509_int(), "3140531249369331492097");
assert_eq!(s3.as_x509_hex(), "aa:3f:a0:54:0d:c0:b7:65:01");
}
#[test]
#[ignore]
fn test_parse_cert_from_der() {
let bob_der = std::fs::read("tests/assets/Bob.der").expect(".der file not found");
let cert =
certificate::Certificate::from_bytes(bob_der).expect("Failed to parse valid certificate");
assert_eq!(
cert.subject(),
"CN=Bob",
"Subject is {} should be CN=Bob",
cert.subject()
);
assert_eq!(
cert.issuer(),
"CN=Ferdinand Linnenberg CA",
"Issuer is {} should be {}",
cert.issuer(),
"CN=Ferdinand Linnenberg CA"
);
} }