Compare commits

..

344 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update pbkdf dependency to 0.9

* Update x509-parser dependency to 0.11

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

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

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

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

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

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

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

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

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

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

This commit renames the project to yubikey.rs

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

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

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

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

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

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

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

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

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

* Transaction::set_mgm_key: Take touch requirement as bool

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

* Replace MgmKey::set with MgmKey::set_manual

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

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

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

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

* Bump der-parser, nom, x509-parser

* clippy

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-03 13:51:52 +00:00
Jack Grigg 76c093e68e Minor cleanups 2019-12-03 03:24:10 +00:00
Jack Grigg ada3454d26 Fix bug in MgmKey::decrypt 2019-12-03 03:24:09 +00:00
Jack Grigg 370a90f800 Correctly return StatusWords from transfer_data 2019-12-03 03:24:07 +00:00
Jack Grigg 7bcd8664a4 AlgorithmId::write helper to match policy helpers 2019-12-03 03:24:06 +00:00
Jack Grigg 3a4515d902 Convert PIN and touch policies into enums 2019-12-03 03:23:59 +00:00
Tony Arcieri 7b70ea0f91 Merge pull request #56 from iqlusioninc/cli/fix-readme-badge
cli: fix build badge
2019-12-02 12:31:30 -08:00
Tony Arcieri 9bc28f4f75 cli: fix build badge 2019-12-02 12:18:44 -08:00
Tony Arcieri 140016bbd7 Merge pull request #54 from iqlusioninc/yubikey-cli/v0.0.1
yubikey-cli v0.0.1
2019-12-02 12:08:13 -08:00
Tony Arcieri 3a41fdc3bc yubikey-cli v0.0.1 2019-12-02 11:54:41 -08:00
Tony Arcieri c377f226e2 Merge pull request #53 from iqlusioninc/yubikey-piv/v0.0.3
yubikey-piv v0.0.3
2019-12-02 11:40:43 -08:00
Tony Arcieri da897b99bb yubikey-piv v0.0.3 2019-12-02 11:17:10 -08:00
Tony Arcieri 9fa2d1c051 Merge pull request #52 from iqlusioninc/cli
cli: Initial `yubikey-cli` utility with `list` command
2019-12-02 11:00:09 -08:00
Tony Arcieri 07f70bccb5 cli: Initial yubikey-cli utility with list command
Adds a `yubikey-cli` crate to the workspace, with a `yubikey` binary,
which presently provides a `list` command for listing detected readers.

Dependencies:

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

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

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

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

This also moves the `trace` logging into the APDU type, which allows it
to display `Debug` output for APDUs and responses, which makes it easier
to understand what's going on (and will be even better once instructions
are converted into an enum so you can actually see what's happening).
2019-11-26 09:15:48 -08:00
Tony Arcieri 5fab09e54d Merge pull request #31 from tarcieri/v0.0.2
v0.0.2
2019-11-25 16:59:02 -08:00
48 changed files with 6835 additions and 2798 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: master
name: Rust
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
# Need to install `libpscslite-dev` on Linux
linux:
name: Test Suite
strategy:
matrix:
toolchain:
- 1.39.0
- stable
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
test:
name: Test Suite
strategy:
matrix:
platform:
- macos-latest
- windows-latest
toolchain:
- 1.39.0
- stable
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install rustfmt
run: rustup component add rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Install clippy
run: rustup component add clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings
+45
View File
@@ -0,0 +1,45 @@
name: Security Audit
on:
pull_request:
paths: Cargo.lock
push:
branches: main
paths: Cargo.lock
schedule:
- cron: '0 0 * * *'
jobs:
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }}
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny warnings --ignore RUSTSEC-2019-0031 # spin
-1
View File
@@ -1,3 +1,2 @@
/target
**/*.rs.bk
Cargo.lock
+229 -14
View File
@@ -4,7 +4,223 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.2] (2019-11-25)
## 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
- Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45])
### Changed
- Use `Reader` to connect to `YubiKey` ([#51])
- Convert `SlotId` and `AlgorithmId` into enums ([#44])
- Use `secrecy` crate for storing `CachedPin` ([#43])
- Change `CHUID` struct to hold complete CHUID value ([#42])
- Eliminate all usages of `unsafe` ([#37], [#39])
- Make anonymous CHUID struct public ([#36])
- Have `sign_data` and `decrypt_data` return a `Buffer` ([#34])
- `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#32])
[#51]: https://github.com/iqlusioninc/yubikey.rs/pull/51
[#45]: https://github.com/iqlusioninc/yubikey.rs/pull/45
[#44]: https://github.com/iqlusioninc/yubikey.rs/pull/44
[#43]: https://github.com/iqlusioninc/yubikey.rs/pull/43
[#42]: https://github.com/iqlusioninc/yubikey.rs/pull/42
[#39]: https://github.com/iqlusioninc/yubikey.rs/pull/39
[#37]: https://github.com/iqlusioninc/yubikey.rs/pull/37
[#36]: https://github.com/iqlusioninc/yubikey.rs/pull/36
[#34]: https://github.com/iqlusioninc/yubikey.rs/pull/34
[#33]: https://github.com/iqlusioninc/yubikey.rs/pull/33
[#32]: https://github.com/iqlusioninc/yubikey.rs/pull/32
## yubikey-piv 0.0.2 (2019-11-25)
### Added
- `untested` Cargo feature to mark untested functionality ([#30])
- Initial connect test and docs ([#19])
@@ -19,17 +235,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `log` crate for logging ([#7])
- Replace `ErrorKind::Ok` with `Result` ([#6])
[0.0.2]: https://github.com/tarcieri/yubikey-piv.rs/pull/31
[#30]: https://github.com/tarcieri/yubikey-piv.rs/pull/30
[#19]: https://github.com/tarcieri/yubikey-piv.rs/pull/19
[#17]: https://github.com/tarcieri/yubikey-piv.rs/pull/17
[#15]: https://github.com/tarcieri/yubikey-piv.rs/pull/15
[#13]: https://github.com/tarcieri/yubikey-piv.rs/pull/13
[#10]: https://github.com/tarcieri/yubikey-piv.rs/pull/10
[#9]: https://github.com/tarcieri/yubikey-piv.rs/pull/9
[#8]: https://github.com/tarcieri/yubikey-piv.rs/pull/8
[#7]: https://github.com/tarcieri/yubikey-piv.rs/pull/7
[#6]: https://github.com/tarcieri/yubikey-piv.rs/pull/6
[#30]: https://github.com/iqlusioninc/yubikey.rs/pull/30
[#19]: https://github.com/iqlusioninc/yubikey.rs/pull/19
[#17]: https://github.com/iqlusioninc/yubikey.rs/pull/17
[#15]: https://github.com/iqlusioninc/yubikey.rs/pull/15
[#13]: https://github.com/iqlusioninc/yubikey.rs/pull/13
[#10]: https://github.com/iqlusioninc/yubikey.rs/pull/10
[#9]: https://github.com/iqlusioninc/yubikey.rs/pull/9
[#8]: https://github.com/iqlusioninc/yubikey.rs/pull/8
[#7]: https://github.com/iqlusioninc/yubikey.rs/pull/7
[#6]: https://github.com/iqlusioninc/yubikey.rs/pull/6
## 0.0.1 (2019-11-18)
- It typechecks, ship it!
## yubikey-piv 0.0.1 (2019-11-18)
- Initial release
+8 -10
View File
@@ -6,8 +6,8 @@ In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
@@ -23,7 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -55,8 +55,8 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [bascule@gmail.com]. All
complaints will be reviewed and investigated and will result in a response that
reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io).
All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
@@ -65,12 +65,10 @@ Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
[bascule@gmail.com]: mailto:bascule@gmail.com
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
Generated
+1314
View File
File diff suppressed because it is too large Load Diff
+43 -21
View File
@@ -1,39 +1,61 @@
[package]
name = "yubikey-piv"
version = "0.0.2" # Also update html_root_url in lib.rs when bumping this
name = "yubikey"
version = "0.7.0"
description = """
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
application providing general-purpose public-key signing and encryption
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
algorithms (e.g, PKCS#1v1.5, ECDSA)
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
support for hardware-backed public-key decryption and digital signatures using
the Personal Identity Verification (PIV) application. Supports RSA (1024/2048)
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
"""
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/tarcieri/yubikey-piv.rs"
readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"]
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021"
rust-version = "1.60"
[badges]
maintenance = { status = "experimental" }
[workspace]
members = [".", "cli"]
[dependencies]
des = "0.3"
getrandom = "0.1"
hmac = "0.7"
chrono = "0.4.23"
cookie-factory = "0.3"
der-parser = "8"
des = "0.8"
elliptic-curve = "0.12"
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
hmac = "0.12"
log = "0.4"
pbkdf2 = "0.3"
nom = "7"
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"
sha-1 = "0.8"
rand_core = { version = "0.6", features = ["std"] }
rsa = "0.7"
secrecy = "0.8"
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
subtle = "2"
uuid = { version = "1.2", features = ["v4"] }
x509 = "0.2"
x509-parser = "0.14"
zeroize = "1"
[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]
untested = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+110 -78
View File
@@ -1,17 +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]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link]
[![Safety Dance][safety-image]][safety-link]
[![Dependency Status][deps-image]][deps-link]
[![2-Clause BSD Licensed][license-image]][license-link]
![MSRV][msrv-image]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] driver
with general-purpose public-key encryption and signing support.
Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
with support for public-key encryption and digital signatures using the
[Personal Identity Verification (PIV)][PIV] application.
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
access provided by the [`pcsc` crate].
[Documentation][docs-link]
@@ -25,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
on which devices support PIV and the available functionality.
If you've been wanting to use Rust to sign and/or encrypt stuff using a
private key generated and stored on a Yubikey (with option PIN-based access),
If you've been wanting to use Rust to sign and/or encrypt data using a
private key generated and stored on a YubiKey (with option PIN-based access),
this is the crate you've been after!
Note that while this project started as a fork of a [Yubico] project,
this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico.
## Features
### Personal Identity Verification (PIV)
[PIV] is a [NIST] standard for both *signing* and *encryption*
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
PIV-related functionality can be found in the [`piv`] module.
This library natively implements the protocol used to manage and
utilize PIV encryption and signing keys which can be generated, imported,
and stored on YubiKey devices.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- 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
- Rust **1.39+**
Rust **1.60** or newer.
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Supported Operating Systems
- Linux
- macOS
- Windows
## Security Warning
@@ -54,37 +91,14 @@ USE AT YOUR OWN RISK!
## Status
This project is a largely incomplete work-in-progress. So far the only
functionality which has actually been tested is connecting to Yubikeys.
Functionality which has been successfully tested is available by default.
If you're interested helping test functionality, the table below documents
the current status of the project and relevant GitHub issues for various
functions of the YubiKey:
Any functionality which is gated on the `untested` feature has not been
properly tested and is not known to function correctly.
| | Module | Issue | Description |
|----|---------------|-------|-------------|
| 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest |
| ⚠️ | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs |
| ⚠️ | `certificate` | [#22] | Certificates for stored keys |
| ⚠️ | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) |
| ⚠️ | `config` | [#24] | Support for reading on-key configuration |
| ⚠️ | `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.
Please see the [`untested` functionality tracking issue] for current status.
We would appreciate any help testing this functionality and removing the
`untested` gating as well as writing more automated tests.
## Testing
@@ -95,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
tests locally, invoke the following command:
```
```shell
cargo test -- --ignored
```
@@ -104,23 +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
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
```
```shell
RUST_LOG=info cargo test -- --ignored
```
To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
```
```text
running 1 test
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0]
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [5, 1, 2, 144, 0]
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 0]
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [160, 0, 0, 3, 8] }
[TRACE yubikey::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey::apdu] >>> Apdu { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] }
[TRACE yubikey::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok
```
@@ -134,6 +151,14 @@ Yubico, which was originally written in C. It was mechanically translated
from C into Rust using [Corrode], and then subsequently heavily
refactored into safer, more idiomatic Rust.
For more information on [yubico-piv-tool] and background information on how
the YubiKey implementation of PIV works in general, see the
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
## ⚠️ Security Warning
No security audits of this crate have ever been performed.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -142,12 +167,12 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
**yubikey-piv.rs** is a fork of and originally a mechanical translation from
**yubikey.rs** is a fork of and originally a mechanical translation from
Yubico's [yubico-piv-tool], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
as a derived work.
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -183,41 +208,48 @@ or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-piv.svg
[crate-link]: https://crates.io/crates/yubikey-piv
[docs-image]: https://docs.rs/yubikey-piv/badge.svg
[docs-link]: https://docs.rs/yubikey-piv/
[crate-image]: https://buildstats.info/crate/yubikey
[crate-link]: https://crates.io/crates/yubikey
[docs-image]: https://docs.rs/yubikey/badge.svg
[docs-link]: https://docs.rs/yubikey/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[build-image]: https://github.com/tarcieri/yubikey-piv.rs/workflows/Rust/badge.svg
[build-link]: https://github.com/tarcieri/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/yubihsm-piv-rs.svg
[gitter-link]: https://gitter.im/yubikey-piv-rs/community
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
[msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[deps-image]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs/status.svg
[deps-link]: https://deps.rs/repo/github/iqlusioninc/yubikey.rs
[//]: # (general links)
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[Yubico]: https://www.yubico.com/
[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 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
[cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
[//]: # (github issues)
[#18]: https://github.com/tarcieri/yubikey-piv.rs/issues/18
[#20]: https://github.com/tarcieri/yubikey-piv.rs/issues/20
[#21]: https://github.com/tarcieri/yubikey-piv.rs/issues/21
[#22]: https://github.com/tarcieri/yubikey-piv.rs/issues/22
[#23]: https://github.com/tarcieri/yubikey-piv.rs/issues/23
[#24]: https://github.com/tarcieri/yubikey-piv.rs/issues/24
[#25]: https://github.com/tarcieri/yubikey-piv.rs/issues/25
[#26]: https://github.com/tarcieri/yubikey-piv.rs/issues/26
[#27]: https://github.com/tarcieri/yubikey-piv.rs/issues/27
[#28]: https://github.com/tarcieri/yubikey-piv.rs/issues/28
[#18]: https://github.com/iqlusioninc/yubikey.rs/issues/18
[#20]: https://github.com/iqlusioninc/yubikey.rs/issues/20
[#21]: https://github.com/iqlusioninc/yubikey.rs/issues/21
[#22]: https://github.com/iqlusioninc/yubikey.rs/issues/22
[#23]: https://github.com/iqlusioninc/yubikey.rs/issues/23
[#24]: https://github.com/iqlusioninc/yubikey.rs/issues/24
[#25]: https://github.com/iqlusioninc/yubikey.rs/issues/25
[#26]: https://github.com/iqlusioninc/yubikey.rs/issues/26
[#27]: https://github.com/iqlusioninc/yubikey.rs/issues/27
[#28]: https://github.com/iqlusioninc/yubikey.rs/issues/28
+76
View File
@@ -0,0 +1,76 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.7.0 (2022-11-14)
### Changed
- Bump `clap` to v4.0 ([#438])
- Bump `x509-parser` to v0.14 ([#441])
- Switch from `lazy_static` to `once_cell` ([#442])
- Switch from `subtle-encoding` to `base16ct` ([#443])
- Bump `yubikey` dependency to v0.7 ([#444])
[#438]: https://github.com/iqlusioninc/yubikey.rs/pull/438
[#441]: https://github.com/iqlusioninc/yubikey.rs/pull/441
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
[#444]: https://github.com/iqlusioninc/yubikey.rs/pull/444
## 0.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade; MSRV 1.57 ([#343])
- Migrate from `gumdrop` to `clap` v3 ([#379])
- Bump `yubikey` dependency to v0.6 ([#403])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#379]: https://github.com/iqlusioninc/yubikey.rs/pull/379
[#403]: https://github.com/iqlusioninc/yubikey.rs/pull/403
## 0.5.0 (2021-11-21)
### Changed
- Bump `yubikey` dependency to v0.5 ([#327])
[#327]: https://github.com/iqlusioninc/yubikey.rs/pull/327
## 0.4.0 (2021-07-12)
### Changed
- Switch to renamed `yubikey` crate ([#283])
- Bump MSRV to 1.51+ ([#283])
[#283]: https://github.com/iqlusioninc/yubikey.rs/pull/283
## 0.3.0 (2021-03-22)
### Changed
- Bump `yubikey-piv` dependency to v0.3 ([#240])
[#240]: https://github.com/iqlusioninc/yubikey.rs/pull/240
## 0.2.0 (2021-01-30)
### Changed
- Bump MSRV to 1.46+ ([#208])
- Bump `yubikey-piv` dependency to v0.2 ([#220])
[#208]: https://github.com/iqlusioninc/yubikey.rs/pull/208
[#220]: https://github.com/iqlusioninc/yubikey.rs/pull/220
## 0.1.0 (2020-10-19)
### Added
- `status` command ([#72], [#74])
### Changed
- Bump `yubikey-piv` to v0.1 ([#180])
- Bump `x509-parser` to v0.8 ([#181])
- Bump `sha2` to v0.9 ([#182])
- Rename `list` command to `readers`; improve usage ([#71])
[#182]: https://github.com/iqlusioninc/yubikey.rs/pull/182
[#181]: https://github.com/iqlusioninc/yubikey.rs/pull/181
[#180]: https://github.com/iqlusioninc/yubikey.rs/pull/180
[#74]: https://github.com/iqlusioninc/yubikey.rs/pull/74
[#72]: https://github.com/iqlusioninc/yubikey.rs/pull/72
[#71]: https://github.com/iqlusioninc/yubikey.rs/pull/71
## 0.0.1 (2019-12-02)
- Initial release
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "yubikey-cli"
version = "0.7.0"
description = """
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
"""
authors = ["Tony Arcieri <tony@iqlusion.io>"]
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
edition = "2021"
rust-version = "1.56"
[dependencies]
clap = { version = "4", features = ["derive"] }
env_logger = "0.9"
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
log = "0.4"
once_cell = "1"
sha2 = "0.10"
termcolor = "1"
x509-parser = "0.14"
yubikey = { version = "0.7", path = ".." }
+108
View File
@@ -0,0 +1,108 @@
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png" width="150" height="110">
# yubikey-cli.rs
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] CLI
utility with general-purpose public-key encryption and signing support.
[Documentation][docs-link]
## Minimum Supported Rust Version
Rust **1.60** or newer.
## Supported YubiKeys
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## 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
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you shall be licensed under the
[2-Clause BSD License][BSDL] as shown above, without any additional terms
or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-cli.svg
[crate-link]: https://crates.io/crates/yubikey-cli
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
[//]: # (general links)
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
+16
View File
@@ -0,0 +1,16 @@
//! `yubikey` command-line utility
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
use clap::Parser;
use yubikey_cli::commands::YubiKeyCli;
fn main() {
YubiKeyCli::parse().run()
}
+90
View File
@@ -0,0 +1,90 @@
//! Commands of the CLI application
pub mod readers;
pub mod status;
use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal;
use clap::Parser;
use std::{env, process::exit};
use termcolor::ColorChoice;
use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility
#[derive(Debug, Parser)]
pub struct YubiKeyCli {
/// Serial number of the YubiKey to connect to
#[clap(short = 's', long = "serial")]
pub serial: Option<Serial>,
/// Subcommand to execute.
#[clap(subcommand)]
pub command: Commands,
}
impl YubiKeyCli {
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
self.command.run(self.yubikey_init())
}
/// Initialize the YubiKey client driver
fn yubikey_init(&self) -> YubiKey {
match self.serial {
Some(serial) => YubiKey::open_by_serial(serial).unwrap_or_else(|e| {
status_err!("couldn't open YubiKey (serial #{}): {}", serial, e);
exit(1);
}),
None => YubiKey::open().unwrap_or_else(|e| {
status_err!("couldn't open default YubiKey: {}", e);
exit(1);
}),
}
}
}
/// Subcommands of this application
#[derive(Debug, Parser)]
pub enum Commands {
/// `version` subcommand
#[clap(about = "display version information")]
Version(VersionOpts),
/// `readers` subcommand
#[clap(about = "list detected readers")]
Readers(ReadersCmd),
/// `status` subcommand
#[clap(about = "show yubikey status")]
Status(StatusCmd),
}
impl Commands {
/// Run the given command
pub fn run(&self, yubikey: YubiKey) {
match self {
Commands::Version(version) => version.run(),
Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
}
}
}
/// Version options
#[derive(Debug, Parser)]
pub struct VersionOpts {}
impl VersionOpts {
/// Display version information
pub fn run(&self) {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}
}
+64
View File
@@ -0,0 +1,64 @@
//! List detected readers
use crate::terminal::STDOUT;
use 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(())
}
}
+13
View File
@@ -0,0 +1,13 @@
//! `yubikey` command-line utility.
//!
//! The goal of this tool is to provide functionality similar to `yubico-piv-tool`
//! but implemented in pure Rust.
//!
//! It also serves as a demonstration/example of how to use the `yubikey` crate.
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
#[macro_use]
pub mod terminal;
pub mod commands;
+245
View File
@@ -0,0 +1,245 @@
//! Status messages
use log::debug;
use once_cell::sync::Lazy;
use sha2::{Digest, Sha256};
use std::{
io::{self, Write},
str,
sync::Mutex,
};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Print a success status message (in green if colors are enabled)
#[macro_export]
macro_rules! status_ok {
($status:expr, $msg:expr) => {
$crate::terminal::Status::new()
.justified()
.bold()
.color(termcolor::Color::Green)
.status($status)
.print_stdout($msg);
};
($status:expr, $fmt:expr, $($arg:tt)+) => {
$crate::status_ok!($status, format!($fmt, $($arg)+));
};
}
/// Print a warning status message (in yellow if colors are enabled)
#[macro_export]
macro_rules! status_warn {
($msg:expr) => {
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Yellow)
.status("warning:")
.print_stdout($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_warn!(format!($fmt, $($arg)+));
};
}
/// Print an error message (in red if colors are enabled)
#[macro_export]
macro_rules! status_err {
($msg:expr) => {
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Red)
.status("error:")
.print_stderr($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_err!(format!($fmt, $($arg)+));
};
}
/// Color configuration
static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
/// Standard output
pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
/// Standard error
pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
/// Obtain the color configuration.
///
/// Panics if no configuration has been provided.
fn get_color_choice() -> ColorChoice {
let choice = COLOR_CHOICE.lock().unwrap();
*choice
.as_ref()
.expect("terminal stream accessed before initialized!")
}
/// Set the color configuration.
///
/// Panics if the terminal has already been configured.
pub(super) fn set_color_choice(color_choice: ColorChoice) {
let mut choice = COLOR_CHOICE.lock().unwrap();
assert!(choice.is_none(), "terminal colors already configured!");
*choice = Some(color_choice);
}
/// Status message builder
#[derive(Clone, Debug, Default)]
pub struct Status {
/// Should the status be justified?
justified: bool,
/// Should colors be bold?
bold: bool,
/// Color in which status should be displayed
color: Option<Color>,
/// Prefix of the status message (e.g. `Success`)
status: Option<String>,
}
impl Status {
/// Create a new status message with default settings
pub fn new() -> Self {
Self::default()
}
/// Justify status on display
pub fn justified(mut self) -> Self {
self.justified = true;
self
}
/// Make colors bold
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
/// Set the colors used to display this message
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
/// Set a status message to display
pub fn status<S>(mut self, msg: S) -> Self
where
S: ToString,
{
self.status = Some(msg.to_string());
self
}
/// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&STDOUT, msg).expect("error printing to stdout!")
}
/// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&STDERR, msg).expect("error printing to stderr!")
}
/// Print the given message
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> io::Result<()> {
let mut s = stream.lock();
s.reset()?;
s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
if let Some(status) = self.status {
if self.justified {
write!(s, "{:>12}", status)?;
} else {
write!(s, "{}", status)?;
}
}
s.reset()?;
writeln!(s, " {}", msg.as_ref())?;
s.flush()?;
Ok(())
}
}
/// 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

+343 -36
View File
@@ -30,8 +30,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{error::Error, response::Response, transaction::Transaction, Buffer};
use std::fmt::{self, Debug};
use crate::{transaction::Transaction, Buffer, Result};
use log::trace;
use zeroize::{Zeroize, Zeroizing};
/// Maximum amount of command data that can be included in an APDU
@@ -40,13 +40,13 @@ const APDU_DATA_MAX: usize = 0xFF;
/// Application Protocol Data Unit (APDU).
///
/// These messages are packets used to communicate with the YubiKey.
#[derive(Clone)]
pub(crate) struct APDU {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Apdu {
/// Instruction class: indicates the type of command (e.g. inter-industry or proprietary)
cla: u8,
/// Instruction code: indicates the specific command (e.g. "write data")
ins: u8,
ins: Ins,
/// Instruction parameter 1 for the command (e.g. offset into file at which to write the data)
p1: u8,
@@ -58,12 +58,12 @@ pub(crate) struct APDU {
data: Vec<u8>,
}
impl APDU {
impl Apdu {
/// Create a new APDU with the given instruction code
pub fn new(ins: u8) -> Self {
pub fn new(ins: impl Into<Ins>) -> Self {
Self {
cla: 0,
ins,
ins: ins.into(),
p1: 0,
p2: 0,
data: vec![],
@@ -71,7 +71,6 @@ impl APDU {
}
/// Set this APDU's class
#[cfg(feature = "untested")]
pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value;
self
@@ -84,7 +83,6 @@ impl APDU {
}
/// Set both parameters for this APDU
#[cfg(feature = "untested")]
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1;
self.p2 = p2;
@@ -111,16 +109,18 @@ impl APDU {
}
/// Transmit this APDU using the given card transaction
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?;
Ok(Response::from_bytes(response_bytes))
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response> {
trace!(">>> {:?}", self);
let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
Ok(response)
}
/// Consume this APDU and return a self-zeroizing buffer
/// Serialize this APDU as a self-zeroizing byte buffer
pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla);
bytes.push(self.ins);
bytes.push(self.ins.code());
bytes.push(self.p1);
bytes.push(self.p2);
bytes.push(self.data.len() as u8);
@@ -129,33 +129,340 @@ impl APDU {
}
}
impl Debug for APDU {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"APDU {{ cla: {}, ins: {}, p1: {}, p2: {}, lc: {}, data: {:?} }}",
self.cla,
self.ins,
self.p1,
self.p2,
self.data.len(),
self.data.as_slice()
)
}
}
impl Drop for APDU {
impl Drop for Apdu {
fn drop(&mut self) {
self.zeroize();
}
}
impl Zeroize for APDU {
impl Zeroize for Apdu {
fn zeroize(&mut self) {
self.cla.zeroize();
self.ins.zeroize();
self.p1.zeroize();
self.p2.zeroize();
// Only `data` may contain secrets
self.data.zeroize();
}
}
/// APDU instruction codes
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Ins {
/// Verify
Verify,
/// Change reference
ChangeReference,
/// Reset retry
ResetRetry,
/// Generate asymmetric
GenerateAsymmetric,
/// Authenticate
Authenticate,
/// Get data
GetData,
/// Put data
PutData,
/// Select application
SelectApplication,
/// Get response APDU
GetResponseApdu,
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
/// Set MGM key
SetMgmKey,
/// Import key
ImportKey,
/// Get version
GetVersion,
/// Reset device
Reset,
/// Set PIN retries
SetPinRetries,
/// Generate attestation certificate for asymmetric key
Attest,
/// Get device serial
GetSerial,
/// Get slot metadata
GetMetadata,
/// Other/unrecognized instruction codes
Other(u8),
}
impl Ins {
/// Get the code that corresponds to this instruction
pub fn code(self) -> u8 {
match self {
Ins::Verify => 0x20,
Ins::ChangeReference => 0x24,
Ins::ResetRetry => 0x2c,
Ins::GenerateAsymmetric => 0x47,
Ins::Authenticate => 0x87,
Ins::GetData => 0xcb,
Ins::PutData => 0xdb,
Ins::SelectApplication => 0xa4,
Ins::GetResponseApdu => 0xc0,
Ins::SetMgmKey => 0xff,
Ins::ImportKey => 0xfe,
Ins::GetVersion => 0xfd,
Ins::Reset => 0xfb,
Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
Ins::Other(code) => code,
}
}
}
impl From<u8> for Ins {
fn from(code: u8) -> Self {
match code {
0x20 => Ins::Verify,
0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry,
0x47 => Ins::GenerateAsymmetric,
0x87 => Ins::Authenticate,
0xcb => Ins::GetData,
0xdb => Ins::PutData,
0xa4 => Ins::SelectApplication,
0xc0 => Ins::GetResponseApdu,
0xff => Ins::SetMgmKey,
0xfe => Ins::ImportKey,
0xfd => Ins::GetVersion,
0xfb => Ins::Reset,
0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code),
}
}
}
impl From<Ins> for u8 {
fn from(ins: Ins) -> u8 {
ins.code()
}
}
/// APDU responses
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
data: Vec<u8>,
}
impl Response {
/// Create a new response from the given status words and buffer
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
pub fn code(&self) -> u16 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response data
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.data()
}
}
impl Drop for Response {
fn drop(&mut self) {
self.zeroize();
}
}
impl From<Vec<u8>> for Response {
fn from(mut bytes: Vec<u8>) -> Self {
if bytes.len() < 2 {
return Response {
status_words: StatusWords::None,
data: bytes,
};
}
let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16),
);
let len = bytes.len() - 2;
bytes.truncate(len);
Response {
status_words: sw,
data: bytes,
}
}
}
impl Zeroize for Response {
fn zeroize(&mut self) {
// Only `data` may contain secrets
self.data.zeroize();
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
NoInputDataError,
/// PIN verification failure
VerifyFailError {
/// Remaining verification attempts
tries: u8,
},
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L55
WrongLengthError,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L58
DataInvalidError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L59
ConditionsNotSatisfiedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L60
CommandNotAllowedError,
/// Incorrect parameter in command data field
IncorrectParamError,
/// Data object or application not found
NotFoundError,
/// Not enough memory
NoSpaceError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L65
CommandAbortedError,
/// Other/unrecognized status words
Other(u16),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u16 {
match self {
StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u16> for StatusWords {
fn from(sw: u16) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u16 {
sw.code()
}
}
+58 -20
View File
@@ -30,8 +30,15 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Error, Result, YubiKey};
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
///
@@ -48,38 +55,69 @@ const CCC_TMPL: &[u8] = &[
0x00, 0xfe, 0x00,
];
/// Cardholder Capability Container (CCC) Identifier
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CCCID([u8; YKPIV_CCCID_SIZE]);
/// Cardholder Capability Container (CCC) Identifier Card ID.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct CardId(pub [u8; Self::BYTE_SIZE]);
impl CCCID {
/// Generate a random CCCID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CCCID(id))
impl CardId {
/// CCCID size in bytes
pub const BYTE_SIZE: usize = 14;
/// Generate a random CCC Card ID
pub fn generate() -> Self {
let mut id = [0u8; Self::BYTE_SIZE];
OsRng.fill_bytes(&mut id);
Self(id)
}
}
/// Cardholder Capability Container (CCC) Identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CccId(pub [u8; Self::BYTE_SIZE]);
impl CccId {
/// CCC size in bytes
pub const BYTE_SIZE: usize = 51;
/// Return CardId component of CCC
pub fn card_id(&self) -> Result<CardId> {
let mut cccid = [0u8; CardId::BYTE_SIZE];
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CardId::BYTE_SIZE)]);
Ok(CardId(cccid))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
pub fn get(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CAPABILITY)?;
let response = txn.fetch_object(OBJ_CAPABILITY)?;
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
}
let mut cccid = [0u8; YKPIV_CCCID_SIZE];
cccid.copy_from_slice(&response[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]);
Ok(CCCID(cccid))
Ok(Self(response[..Self::BYTE_SIZE].try_into().unwrap()))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
/// Set Cardholder Capability Container (CCC) ID
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CCC_TMPL.to_vec();
buf[CCC_ID_OFFS..(CCC_ID_OFFS + self.0.len())].copy_from_slice(&self.0);
buf[0..self.0.len()].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf)
txn.save_object(OBJ_CAPABILITY, &buf)
}
}
impl 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()))
}
}
+589 -73
View File
@@ -1,4 +1,4 @@
//! YubiKey Certificates
//! X.509 certificate support.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -31,25 +31,448 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
consts::*,
error::Error,
key::{self, SlotId},
consts::CB_OBJ_MAX,
error::{Error, Result},
piv::{sign_data, AlgorithmId, SlotId},
serialization::*,
transaction::Transaction,
yubikey::YubiKey,
Buffer,
};
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error;
use std::ptr;
use num_bigint_dig::BigUint;
use p256::NistP256;
use p384::NistP384;
use rsa::{PublicKeyParts, RsaPublicKey};
use sha2::{Digest, Sha256};
use std::fmt::Display;
use std::{fmt, ops::DerefMut};
use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE;
/// A serial number for a [`Certificate`].
#[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
}
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
impl TryFrom<&[u8]> for Serial {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Serial> {
if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(bytes)))
} else {
Err(Error::ParseError)
}
}
}
impl Serial {
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be()
}
/// 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`].
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Public key
pubkey: RsaPublicKey,
},
/// EC P-256 keys
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKeyInfo({:?})", self.algorithm())
}
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(&subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() {
1024 => AlgorithmId::Rsa1024,
2048 => AlgorithmId::Rsa2048,
_ => return Err(Error::AlgorithmError),
},
pubkey,
})
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
let algorithm_parameters = subject_pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::InvalidObject)?;
match read_pki::ec_parameters(algorithm_parameters)? {
AlgorithmId::EccP256 => EcPublicKey::<NistP256>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256)
.map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::<NistP384>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384)
.map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
_ => Err(Error::InvalidObject),
}
}
/// Returns the algorithm that this public key can be used with.
pub fn algorithm(&self) -> AlgorithmId {
match self {
PublicKeyInfo::Rsa { algorithm, .. } => *algorithm,
PublicKeyInfo::EcP256(_) => AlgorithmId::EccP256,
PublicKeyInfo::EcP384(_) => AlgorithmId::EccP384,
}
}
}
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates
#[derive(Clone, Debug)]
pub struct Certificate(Buffer);
pub struct Certificate {
serial: Serial,
#[allow(dead_code)]
issuer: String,
subject: String,
subject_pki: PublicKeyInfo,
data: Buffer,
}
impl<'a> TryFrom<&'a [u8]> for Certificate {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::from_bytes(bytes.to_vec())
}
}
impl Certificate {
/// Creates a new self-signed certificate for the given key. Writes the resulting
/// certificate to the slot before returning it.
///
/// `extensions` is optional; if empty, no extensions will be included. Due to the
/// need for an `O: Oid` type parameter, users who do not have any extensions should
/// use the workaround `let extensions: &[x509::Extension<'_, &[u64]>] = &[];`.
pub fn generate_self_signed<O: Oid>(
yubikey: &mut YubiKey,
key: SlotId,
serial: impl Into<Serial>,
not_after: Option<DateTime<Utc>>,
subject: &[RelativeDistinguishedName<'_>],
subject_pki: PublicKeyInfo,
extensions: &[x509::Extension<'_, O>],
) -> Result<Self> {
let serial = serial.into();
let mut tbs_cert = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
let signature_algorithm = match subject_pki.algorithm() {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => SignatureId::Sha256WithRsaEncryption,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => SignatureId::EcdsaWithSha256,
};
cookie_factory::gen(
x509::write::tbs_certificate(
&serial.to_bytes(),
&signature_algorithm,
// Issuer and subject are the same in self-signed certificates.
subject,
Utc::now(),
not_after,
subject,
&subject_pki,
extensions,
),
tbs_cert.deref_mut(),
)
.expect("can serialize to Vec");
let signature = match signature_algorithm {
SignatureId::Sha256WithRsaEncryption => {
use cookie_factory::{combinator::slice, sequence::tuple};
use x509::{
der::write::{der_octet_string, der_sequence},
write::algorithm_identifier,
};
let em_len = if let AlgorithmId::Rsa1024 = subject_pki.algorithm() {
128
} else {
256
};
let h = Sha256::digest(&tbs_cert);
let t = cookie_factory::gen_simple(
der_sequence((
algorithm_identifier(&DigestId::Sha256),
der_octet_string(&h),
)),
vec![],
)
.expect("can serialize into Vec");
let em = cookie_factory::gen_simple(
tuple((
slice(&[0x00, 0x01]),
slice(&vec![0xff; em_len - t.len() - 3]),
slice(&[0x00]),
slice(t),
)),
vec![],
)
.expect("can serialize to Vec");
sign_data(yubikey, &em, subject_pki.algorithm(), key)
}
SignatureId::EcdsaWithSha256 => sign_data(
yubikey,
&Sha256::digest(&tbs_cert),
subject_pki.algorithm(),
key,
),
}?;
let mut data = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
cookie_factory::gen(
x509::write::certificate(&tbs_cert, &signature_algorithm, &signature),
data.deref_mut(),
)
.expect("can serialize to Vec");
let (issuer, subject) = parse_x509_certificate(&data)
.map(|(_, cert)| {
(
cert.tbs_certificate.issuer.to_string(),
cert.tbs_certificate.subject.to_string(),
)
})
.expect("We just serialized this correctly");
let cert = Certificate {
serial,
issuer,
subject,
subject_pki,
data,
};
cert.write(yubikey, key, CertInfo::Uncompressed)?;
Ok(cert)
}
/// Read a certificate from the given slot in the YubiKey
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> {
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let buf = read_certificate(&txn, slot)?;
@@ -57,25 +480,25 @@ impl Certificate {
return Err(Error::InvalidObject);
}
Ok(Certificate(buf))
Certificate::from_bytes(buf)
}
/// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: CertInfo) -> Result<()> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.0), certinfo, max_size)
write_certificate(&txn, slot, Some(&self.data), certinfo)
}
/// Delete a certificate located at the given slot of the given YubiKey
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, 0, max_size)
write_certificate(&txn, slot, None, CertInfo::Uncompressed)
}
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(cert: impl Into<Buffer>) -> Result<Self, Error> {
pub fn from_bytes(cert: impl Into<Buffer>) -> Result<Self> {
let cert = cert.into();
if cert.is_empty() {
@@ -83,27 +506,63 @@ impl Certificate {
return Err(Error::SizeError);
}
Ok(Certificate(cert))
let parsed_cert = match parse_x509_certificate(&cert) {
Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject),
};
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
serial,
issuer,
subject,
subject_pki,
data: cert,
})
}
/// Returns the serial number of the certificate.
pub fn serial(&self) -> &Serial {
&self.serial
}
/// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str {
&self.issuer
}
/// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str {
&self.subject
}
/// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> &PublicKeyInfo {
&self.subject_pki
}
/// Extract the inner buffer
pub fn into_buffer(self) -> Buffer {
self.0
self.data
}
}
impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
self.data.as_ref()
}
}
/// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
let mut len: usize = 0;
let object_id = key::slot_object(slot)?;
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer> {
let object_id = slot.object_id();
let mut buf = match txn.fetch_object(object_id) {
let buf = match txn.fetch_object(object_id) {
Ok(b) => b,
Err(_) => {
// TODO(tarcieri): is this really ok?
@@ -111,27 +570,15 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
}
};
if buf.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
// TODO(str4d): Check the rest of the buffer (TAG_CERT_COMPRESS and TAG_CERT_LRC)
if buf[0] == TAG_CERT {
let offset = 1 + get_length(&buf[1..], &mut len);
if len > buf.len() - offset {
Tlv::parse_single(buf, TAG_CERT).or_else(|_| {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
unsafe {
ptr::copy(buf.as_ptr().add(offset), buf.as_mut_ptr(), len);
}
buf.truncate(len);
Ok(Zeroizing::new(vec![]))
})
} else {
Ok(buf)
}
Ok(buf)
}
/// Write certificate
@@ -139,13 +586,9 @@ pub(crate) fn write_certificate(
txn: &Transaction<'_>,
slot: SlotId,
data: Option<&[u8]>,
certinfo: u8,
max_size: usize,
) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let object_id = key::slot_object(slot)?;
certinfo: CertInfo,
) -> Result<()> {
let object_id = slot.object_id();
if data.is_none() {
return txn.save_object(object_id, &[]);
@@ -153,34 +596,107 @@ pub(crate) fn write_certificate(
let data = data.unwrap();
let mut req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */;
req_len += set_length(&mut buf, data.len());
req_len += data.len();
if req_len < data.len() || req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_CERT;
offset += 1;
offset += set_length(&mut buf[offset..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(&data);
offset += data.len();
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
// write compression info and LRC trailer
buf[offset] = TAG_CERT_COMPRESS;
buf[offset + 1] = 0x01;
buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP {
0x01
} else {
0x00
};
buf[offset + 3] = TAG_CERT_LRC;
buf[offset + 4] = 00;
offset += 5;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
}
mod read_pki {
use der_parser::{
asn1_rs::Any,
ber::BerObjectContent,
der::{parse_der_integer, parse_der_sequence_defined_g, DerObject},
error::BerError,
};
use nom::{combinator, sequence::pair, IResult};
use rsa::{BigUint, RsaPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{piv::AlgorithmId, Error, Result};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> {
parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i)
}
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| {
let n = match modulus.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
let e = match public_exponent.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
(n, e)
})(i)
}
let (n, e) = match rsa_pubkey_parts(encoded) {
Ok((_, res)) => res,
_ => return Err(Error::InvalidObject),
};
RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
}
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
/// ```text
/// ECParameters ::= CHOICE {
/// namedCurve OBJECT IDENTIFIER
/// -- implicitCurve NULL
/// -- specifiedCurve SpecifiedECDomain
/// }
/// ```
pub(super) fn ec_parameters(parameters: &Any<'_>) -> Result<AlgorithmId> {
let curve_oid = parameters.as_oid().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
OID_NIST_P384 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
use std::io::Write;
use x509::der::write::{der_integer, der_sequence};
/// Encodes a usize as an ASN.1 integer using DER.
fn der_integer_biguint<'a, W: Write + 'a>(num: &'a BigUint) -> impl SerializeFn<W> + 'a {
move |w: WriteContext<W>| der_integer(&num.to_bytes_be())(w)
}
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RsaPublicKey,
) -> impl SerializeFn<W> + 'a {
der_sequence((
der_integer_biguint(pubkey.n()),
der_integer_biguint(pubkey.e()),
))
}
}
+65 -18
View File
@@ -30,8 +30,21 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use crate::{Error, Result, YubiKey};
use std::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
///
@@ -55,38 +68,72 @@ const CHUID_TMPL: &[u8] = &[
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
];
/// Cardholder Unique Identifier (CHUID)
/// Cardholder Unique Identifier (CHUID).
#[derive(Copy, Clone, Debug)]
pub struct CHUID([u8; YKPIV_CARDID_SIZE]);
pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
impl CHUID {
/// Generate a random Cardholder Unique Identifier (CHUID)
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CHUID(id))
impl ChuId {
/// CHUID size in bytes
pub const BYTE_SIZE: usize = 59;
/// FASC-N component size
pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
/// Return FASC-N component of CHUID
pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] {
self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)]
.try_into()
.unwrap()
}
/// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Uuid {
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap()
}
/// Return expiration date component of CHUID
// TODO(tarcieri): parse expiration?
pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
.try_into()
.unwrap()
}
/// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CHUID)?;
let response = txn.fetch_object(OBJ_CHUID)?;
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
let mut cardid = [0u8; YKPIV_CARDID_SIZE];
cardid.copy_from_slice(&response[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
Ok(CHUID(cardid))
Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
}
/// Set Cardholder Unique Identifier (CHUID)
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CHUID_TMPL.to_vec();
buf[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + self.0.len())].copy_from_slice(&self.0);
buf[..Self::BYTE_SIZE].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
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
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, mgm::MgmType, yubikey::YubiKey};
use crate::{
consts::{
TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_ADMIN_TIMESTAMP, TAG_PROTECTED_FLAGS_1,
TAG_PROTECTED_MGM,
},
metadata::{AdminData, ProtectedData},
mgm::{MgmType, ADMIN_FLAGS_1_PROTECTED_MGM},
yubikey::{YubiKey, ADMIN_FLAGS_1_PUK_BLOCKED},
Result,
};
use log::error;
use std::convert::TryInto;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// Config
#[derive(Copy, Clone)]
const CB_ADMIN_TIMESTAMP: usize = 0x04;
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
/// YubiKey configuration.
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Protected data available
protected_data_available: bool,
pub protected_data_available: bool,
/// PUK blocked
puk_blocked: bool,
pub puk_blocked: bool,
/// No block on upgrade
puk_noblock_on_upgrade: bool,
pub puk_noblock_on_upgrade: bool,
/// PIN last changed
pin_last_changed: u32,
pub pin_last_changed: Option<SystemTime>,
/// MGM type
mgm_type: MgmType,
pub mgm_type: MgmType,
}
impl Config {
/// Get YubiKey config
pub fn get(yubikey: &mut YubiKey) -> Result<Config, Error> {
let mut config = Config {
impl Default for Config {
fn default() -> Config {
Config {
protected_data_available: false,
puk_blocked: false,
puk_noblock_on_upgrade: false,
pin_last_changed: 0,
pin_last_changed: None,
mgm_type: MgmType::Manual,
};
}
}
}
impl Config {
/// Get YubiKey config.
pub(crate) fn get(yubikey: &mut YubiKey) -> Result<Config> {
let mut config = Self::default();
let txn = yubikey.begin_transaction()?;
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
if let Ok(admin_data) = AdminData::read(&txn) {
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.is_empty() {
error!("empty response for admin flags metadata item! ignoring");
} else {
@@ -81,7 +98,7 @@ impl Config {
}
}
if metadata::get_item(&data, TAG_ADMIN_SALT).is_ok() {
if admin_data.get_item(TAG_ADMIN_SALT).is_ok() {
if config.mgm_type != MgmType::Manual {
error!("conflicting types of MGM key administration configured");
} else {
@@ -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 {
error!("pin timestamp in admin metadata is an invalid size");
} else {
// TODO(tarcieri): double check this is little endian
config.pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
// TODO(tarcieri): double-check endianness is correct
let pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
if pin_last_changed != 0 {
config.pin_last_changed =
Some(UNIX_EPOCH + Duration::from_secs(pin_last_changed as u64));
}
}
}
}
if let Ok(data) = metadata::read(&txn, TAG_PROTECTED) {
if let Ok(protected_data) = ProtectedData::read(&txn) {
config.protected_data_available = true;
if let Ok(item) = metadata::get_item(&data, TAG_PROTECTED_FLAGS_1) {
if let Ok(item) = protected_data.get_item(TAG_PROTECTED_FLAGS_1) {
if item.is_empty() {
error!("empty response for protected flags metadata item! ignoring");
} else if item[0] & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 {
@@ -110,11 +132,9 @@ impl Config {
}
}
if metadata::get_item(&data, TAG_PROTECTED_MGM).is_ok() {
if protected_data.get_item(TAG_PROTECTED_MGM).is_ok() {
if config.mgm_type != MgmType::Protected {
error!(
"conflicting types of mgm key administration configured: protected MGM exists"
);
error!("conflicting MGM key types: protected MGM exists");
}
// Always favor protected MGM
+15 -237
View File
@@ -1,242 +1,20 @@
//! Constant values
// TODO(tarcieri): refactor these into enums!
//! Miscellaneous constant values
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072;
// TODO(tarcieri): document these!
#![allow(missing_docs, non_upper_case_globals)]
/// YubiKey max object size
pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9;
pub const szLOG_SOURCE: &str = "yubikey-piv.rs";
pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
#[cfg(feature = "untested")]
pub(crate) const CB_OBJ_TAG_MAX: usize = CB_OBJ_TAG_MIN + 2; // 1 byte tag + 3 bytes len
pub const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
pub const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
// Admin tags
pub(crate) const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_ADMIN_SALT: u8 = 0x82;
pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub const CB_ADMIN_TIMESTAMP: usize = 0x04;
pub const CB_ADMIN_SALT: usize = 16;
pub const CB_ATR_MAX: usize = 33;
pub const CB_BUF_MAX_NEO: usize = 2048;
pub const CB_BUF_MAX_YK4: usize = 3072;
pub const CB_BUF_MAX: usize = CB_BUF_MAX_YK4;
pub const CB_ECC_POINTP256: usize = 65;
pub const CB_ECC_POINTP384: usize = 97;
pub const CB_OBJ_MAX_YK4: usize = CB_BUF_MAX_YK4 - 9;
pub const CB_OBJ_MAX: usize = CB_OBJ_MAX_YK4;
pub const CB_OBJ_MAX_NEO: usize = CB_BUF_MAX_NEO - 9;
pub const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
pub const CB_OBJ_TAG_MAX: usize = (CB_OBJ_TAG_MIN + 2); // 1 byte tag + 3 bytes len
pub const CB_PAGE: usize = 4096;
pub const CB_PIN_MAX: usize = 8;
pub const CCC_ID_OFFS: usize = 9;
pub const CHUID_GUID_OFFS: usize = 29;
pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
pub const CONTAINER_NAME_LEN: usize = 40;
pub const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; // 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
pub const DES_TYPE_3DES: u8 = 1;
pub const DES_LEN_DES: usize = 8;
pub const DES_LEN_3DES: usize = DES_LEN_DES * 3;
// device types
pub const DEVTYPE_UNKNOWN: u32 = 0x0000_0000;
pub const DEVTYPE_NEO: u32 = 0x4E45_0000; //"NE"
pub const DEVTYPE_YK: u32 = 0x594B_0000; //"YK"
pub const DEVTYPE_NEOr3: u32 = (DEVTYPE_NEO | 0x0000_7233); //"r3"
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
pub const DEVYTPE_YK5: u32 = (DEVTYPE_YK | 0x0000_0035); // "5"
pub const ITER_MGM_PBKDF2: usize = 10000;
pub const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
// sw is status words, see NIST special publication 800-73-4, section 5.6
pub const SW_SUCCESS: i32 = 0x9000;
pub const SW_ERR_SECURITY_STATUS: i32 = 0x6982;
pub const SW_ERR_AUTH_BLOCKED: i32 = 0x6983;
pub const SW_ERR_INCORRECT_PARAM: i32 = 0x6a80;
// this is a custom sw for yubikey
pub const SW_ERR_INCORRECT_SLOT: i32 = 0x6b00;
pub const SW_ERR_NOT_SUPPORTED: i32 = 0x6d00;
pub const TAG_CERT: u8 = 0x70;
pub const TAG_CERT_COMPRESS: u8 = 0x71;
pub const TAG_CERT_LRC: u8 = 0xFE;
pub const TAG_ADMIN: u8 = 0x80;
pub const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub const TAG_ADMIN_SALT: u8 = 0x82;
pub const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub const TAG_PROTECTED: u8 = 0x88;
pub const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub const TAG_PROTECTED_MGM: u8 = 0x89;
pub const TAG_MSCMAP: u8 = 0x81;
pub const TAG_MSROOTS_END: u8 = 0x82;
pub const TAG_MSROOTS_MID: u8 = 0x83;
pub const TAG_RSA_MODULUS: u8 = 0x81;
pub const TAG_RSA_EXP: u8 = 0x82;
pub const TAG_ECC_POINT: u8 = 0x86;
pub const YKPIV_ALGO_TAG: u8 = 0x80;
pub const YKPIV_ALGO_3DES: u8 = 0x03;
pub const YKPIV_ALGO_RSA1024: u8 = 0x06;
pub const YKPIV_ALGO_RSA2048: u8 = 0x07;
pub const YKPIV_ALGO_ECCP256: u8 = 0x11;
pub const YKPIV_ALGO_ECCP384: u8 = 0x14;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
pub const YKPIV_CARDID_SIZE: usize = 16;
pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_INS_VERIFY: u8 = 0x20;
pub const YKPIV_INS_CHANGE_REFERENCE: u8 = 0x24;
pub const YKPIV_INS_RESET_RETRY: u8 = 0x2c;
pub const YKPIV_INS_GENERATE_ASYMMETRIC: u8 = 0x47;
pub const YKPIV_INS_AUTHENTICATE: u8 = 0x87;
pub const YKPIV_INS_GET_DATA: u8 = 0xcb;
pub const YKPIV_INS_PUT_DATA: u8 = 0xdb;
pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4;
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
pub const YKPIV_INS_RESET: u8 = 0xfb;
pub const YKPIV_INS_SET_PIN_RETRIES: u8 = 0xfa;
pub const YKPIV_INS_ATTEST: u8 = 0xf9;
pub const YKPIV_INS_GET_SERIAL: u8 = 0xf8;
pub const YKPIV_KEY_AUTHENTICATION: u8 = 0x9a;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_KEY_SIGNATURE: u8 = 0x9c;
pub const YKPIV_KEY_KEYMGM: u8 = 0x9d;
pub const YKPIV_KEY_CARDAUTH: u8 = 0x9e;
pub const YKPIV_KEY_RETIRED1: u8 = 0x82;
pub const YKPIV_KEY_RETIRED2: u8 = 0x83;
pub const YKPIV_KEY_RETIRED3: u8 = 0x84;
pub const YKPIV_KEY_RETIRED4: u8 = 0x85;
pub const YKPIV_KEY_RETIRED5: u8 = 0x86;
pub const YKPIV_KEY_RETIRED6: u8 = 0x87;
pub const YKPIV_KEY_RETIRED7: u8 = 0x88;
pub const YKPIV_KEY_RETIRED8: u8 = 0x89;
pub const YKPIV_KEY_RETIRED9: u8 = 0x8a;
pub const YKPIV_KEY_RETIRED10: u8 = 0x8b;
pub const YKPIV_KEY_RETIRED11: u8 = 0x8c;
pub const YKPIV_KEY_RETIRED12: u8 = 0x8d;
pub const YKPIV_KEY_RETIRED13: u8 = 0x8e;
pub const YKPIV_KEY_RETIRED14: u8 = 0x8f;
pub const YKPIV_KEY_RETIRED15: u8 = 0x90;
pub const YKPIV_KEY_RETIRED16: u8 = 0x91;
pub const YKPIV_KEY_RETIRED17: u8 = 0x92;
pub const YKPIV_KEY_RETIRED18: u8 = 0x93;
pub const YKPIV_KEY_RETIRED19: u8 = 0x94;
pub const YKPIV_KEY_RETIRED20: u8 = 0x95;
pub const YKPIV_KEY_ATTESTATION: u8 = 0xf9;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_AUTHENTICATION: u32 = 0x005f_c105; // cert for 9a key
pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103;
pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106;
pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108;
pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109;
pub const YKPIV_OBJ_SIGNATURE: u32 = 0x005f_c10a; // cert for 9c key
pub const YKPIV_OBJ_KEY_MANAGEMENT: u32 = 0x005f_c10b; // cert for 9d key
pub const YKPIV_OBJ_CARD_AUTH: u32 = 0x005f_c101; // cert for 9e key
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
pub const YKPIV_OBJ_RETIRED1: u32 = 0x005f_c10d;
pub const YKPIV_OBJ_RETIRED2: u32 = 0x005f_c10e;
pub const YKPIV_OBJ_RETIRED3: u32 = 0x005f_c10f;
pub const YKPIV_OBJ_RETIRED4: u32 = 0x005f_c110;
pub const YKPIV_OBJ_RETIRED5: u32 = 0x005f_c111;
pub const YKPIV_OBJ_RETIRED6: u32 = 0x005f_c112;
pub const YKPIV_OBJ_RETIRED7: u32 = 0x005f_c113;
pub const YKPIV_OBJ_RETIRED8: u32 = 0x005f_c114;
pub const YKPIV_OBJ_RETIRED9: u32 = 0x005f_c115;
pub const YKPIV_OBJ_RETIRED10: u32 = 0x005f_c116;
pub const YKPIV_OBJ_RETIRED11: u32 = 0x005f_c117;
pub const YKPIV_OBJ_RETIRED12: u32 = 0x005f_c118;
pub const YKPIV_OBJ_RETIRED13: u32 = 0x005f_c119;
pub const YKPIV_OBJ_RETIRED14: u32 = 0x005f_c11a;
pub const YKPIV_OBJ_RETIRED15: u32 = 0x005f_c11b;
pub const YKPIV_OBJ_RETIRED16: u32 = 0x005f_c11c;
pub const YKPIV_OBJ_RETIRED17: u32 = 0x005f_c11d;
pub const YKPIV_OBJ_RETIRED18: u32 = 0x005f_c11e;
pub const YKPIV_OBJ_RETIRED19: u32 = 0x005f_c11f;
pub const YKPIV_OBJ_RETIRED20: u32 = 0x005f_c120;
// Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_ATTESTATION: u32 = 0x005f_ff01;
pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10;
pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11;
pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
pub const YKPIV_PINPOLICY_ONCE: u8 = 2;
pub const YKPIV_PINPOLICY_ALWAYS: u8 = 3;
pub const YKPIV_TOUCHPOLICY_TAG: u8 = 0xab;
pub const YKPIV_TOUCHPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_TOUCHPOLICY_NEVER: u8 = 1;
pub const YKPIV_TOUCHPOLICY_ALWAYS: u8 = 2;
pub const YKPIV_TOUCHPOLICY_CACHED: u8 = 3;
// Protected tags
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
+85 -77
View File
@@ -32,62 +32,66 @@
use std::fmt::{self, Display};
/// Kinds of errors
/// Result type with [`Error`].
pub type Result<T> = core::result::Result<T, Error>;
/// Kinds of errors.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Algorithm error
AlgorithmError,
/// Applet error
AppletError,
/// Argument error
ArgumentError,
/// Authentication error
AuthenticationError,
/// Generic error
GenericError,
/// Invalid object
InvalidObject,
/// Key error
KeyError,
/// Memory error
MemoryError,
/// Not supported
NotSupported,
/// Not found
NotFound,
/// Parse error
ParseError,
/// PCSC error
PcscError {
/// Original PC/SC error
inner: Option<pcsc::Error>,
},
/// Size error
SizeError,
/// Applet error
AppletError,
/// Authentication error
AuthenticationError,
/// Randomness error
RandomnessError,
/// Generic error
GenericError,
/// Key error
KeyError,
/// Parse error
ParseError,
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: u32,
},
/// Invalid object
InvalidObject,
/// Algorithm error
AlgorithmError,
/// PIN locked
PinLocked,
/// Argument error
ArgumentError,
/// Range error
RangeError,
/// Not supported
NotSupported,
/// Size error
SizeError,
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: u8,
},
}
impl Error {
@@ -95,53 +99,59 @@ impl Error {
///
/// These names map to the legacy names from the Yubico C library, to
/// assist in web searches for relevant information for these errors.
pub fn name(self) -> &'static str {
match self {
Error::MemoryError => "YKPIV_MEMORY_ERROR",
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
Error::SizeError => "YKPIV_SIZE_ERROR",
Error::AppletError => "YKPIV_APPLET_ERROR",
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
Error::RandomnessError => "YKPIV_RANDOMNESS_ERROR",
Error::GenericError => "YKPIV_GENERIC_ERROR",
Error::KeyError => "YKPIV_KEY_ERROR",
Error::ParseError => "YKPIV_PARSE_ERROR",
Error::WrongPin { .. } => "YKPIV_WRONG_PIN",
Error::InvalidObject => "YKPIV_INVALID_OBJECT",
pub fn name(self) -> Option<&'static str> {
Some(match self {
Error::AlgorithmError => "YKPIV_ALGORITHM_ERROR",
Error::PinLocked => "YKPIV_PIN_LOCKED",
Error::AppletError => "YKPIV_APPLET_ERROR",
Error::ArgumentError => "YKPIV_ARGUMENT_ERROR",
Error::RangeError => "YKPIV_RANGE_ERROR",
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
Error::GenericError => "YKPIV_GENERIC_ERROR",
Error::InvalidObject => "YKPIV_INVALID_OBJECT",
Error::KeyError => "YKPIV_KEY_ERROR",
Error::MemoryError => "YKPIV_MEMORY_ERROR",
Error::NotSupported => "YKPIV_NOT_SUPPORTED",
}
Error::ParseError => "YKPIV_PARSE_ERROR",
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
Error::PinLocked => "YKPIV_PIN_LOCKED",
Error::RangeError => "YKPIV_RANGE_ERROR",
Error::SizeError => "YKPIV_SIZE_ERROR",
Error::WrongPin { .. } => "YKPIV_WRONG_PIN",
_ => return None,
})
}
/// Error message
pub fn msg(self) -> &'static str {
pub fn msg(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MemoryError => "memory error",
Error::PcscError { .. } => "PCSC error",
Error::SizeError => "size error",
Error::AppletError => "applet error",
Error::AuthenticationError => "authentication error",
Error::RandomnessError => "randomness error",
Error::GenericError => "generic error",
Error::KeyError => "key error",
Error::ParseError => "parse error",
Error::WrongPin { .. } => "wrong pin",
Error::InvalidObject => "invalid object",
Error::AlgorithmError => "algorithm error",
Error::PinLocked => "PIN locked",
Error::ArgumentError => "argument error",
Error::RangeError => "range error",
Error::NotSupported => "not supported",
Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => f.write_str("applet error"),
Error::ArgumentError => f.write_str("argument error"),
Error::AuthenticationError => f.write_str("authentication error"),
Error::GenericError => f.write_str("generic error"),
Error::InvalidObject => f.write_str("invalid object"),
Error::KeyError => f.write_str("key error"),
Error::MemoryError => f.write_str("memory error"),
Error::NotSupported => f.write_str("not supported"),
Error::NotFound => f.write_str("not found"),
Error::ParseError => f.write_str("parse error"),
Error::PcscError {
inner: Some(pcsc_error),
} => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)),
Error::PcscError { .. } => f.write_str("PC/SC error"),
Error::PinLocked => f.write_str("PIN locked"),
Error::RangeError => f.write_str("range error"),
Error::SizeError => f.write_str("size error"),
Error::WrongPin { .. } => f.write_str("wrong pin"),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg())
self.msg(f)
}
}
@@ -154,10 +164,8 @@ impl From<pcsc::Error> for Error {
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[allow(trivial_casts)] // why doesn't this work without the cast???
Error::PcscError { inner } => inner
.as_ref()
.map(|err| err as &(dyn std::error::Error + 'static)),
#[allow(trivial_casts)]
Error::PcscError { inner } => inner.as_ref().map(|err| err as &_),
_ => None,
}
}
-375
View File
@@ -1,375 +0,0 @@
//! PIV cryptographic keys stored in a YubiKey.
//!
//! Supported algorithms:
//!
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
certificate::{self, Certificate},
consts::*,
error::Error,
response::StatusWords,
serialization::*,
settings,
yubikey::YubiKey,
AlgorithmId, ObjectId,
};
use log::{debug, error, warn};
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
// TODO(tarcieri): replace these with enums
pub type SlotId = u8;
/// Get the [`ObjectId`] that corresponds to a given [`SlotId`]
// TODO(tarcieri): factor this into a slot ID enum
pub(crate) fn slot_object(slot: SlotId) -> Result<ObjectId, Error> {
let id = match slot {
YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION,
YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE,
YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT,
YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH,
YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION,
slot if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) => {
YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32
}
_ => return Err(Error::InvalidObject),
};
Ok(id)
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [u8; 24] = [
YKPIV_KEY_AUTHENTICATION,
YKPIV_KEY_SIGNATURE,
YKPIV_KEY_KEYMGM,
YKPIV_KEY_RETIRED1,
YKPIV_KEY_RETIRED2,
YKPIV_KEY_RETIRED3,
YKPIV_KEY_RETIRED4,
YKPIV_KEY_RETIRED5,
YKPIV_KEY_RETIRED6,
YKPIV_KEY_RETIRED7,
YKPIV_KEY_RETIRED8,
YKPIV_KEY_RETIRED9,
YKPIV_KEY_RETIRED10,
YKPIV_KEY_RETIRED11,
YKPIV_KEY_RETIRED12,
YKPIV_KEY_RETIRED13,
YKPIV_KEY_RETIRED14,
YKPIV_KEY_RETIRED15,
YKPIV_KEY_RETIRED16,
YKPIV_KEY_RETIRED17,
YKPIV_KEY_RETIRED18,
YKPIV_KEY_RETIRED19,
YKPIV_KEY_RETIRED20,
YKPIV_KEY_CARDAUTH,
];
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
/// Card slot
slot: SlotId,
/// Cert
cert: Certificate,
}
impl Key {
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut keys = vec![];
let txn = yubikey.begin_transaction()?;
for slot in SLOTS.iter().cloned() {
let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b,
Err(e) => {
debug!("error reading certificate in slot {}: {}", slot, e);
continue;
}
};
let cert = Certificate::new(buf)?;
keys.push(Key { slot, cert });
}
Ok(keys)
}
/// Get the slot ID for this key
pub fn slot(&self) -> SlotId {
self.slot
}
/// Get the certificate for this key
pub fn certificate(&self) -> &Certificate {
&self.cert
}
}
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
/// Information about a generated key
// TODO(tarcieri): this could use some more work
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GeneratedKey {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Modulus
modulus: Vec<u8>,
/// Exponent
exp: Vec<u8>,
},
/// ECC keys
Ecc {
/// ECC algorithm
algorithm: AlgorithmId,
/// Public curve point (i.e. public key)
point: Vec<u8>,
},
}
impl GeneratedKey {
/// Get the algorithm
pub fn algorithm(&self) -> AlgorithmId {
*match self {
GeneratedKey::Rsa { algorithm, .. } => algorithm,
GeneratedKey::Ecc { algorithm, .. } => algorithm,
}
}
}
/// Generate key
#[allow(clippy::cognitive_complexity)]
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: u8,
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0];
let setting_roca: settings::BoolValue;
if yubikey.device_model() == DEVTYPE_YK4
&& (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)
&& yubikey.version.major == 4
&& (yubikey.version.minor < 3 || yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
setting_roca = settings::BoolValue::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
settings::Source::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
settings::Source::Admin => {
if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN
} else {
SZ_ROCA_BLOCK_ADMIN
}
}
_ => SZ_ROCA_DEFAULT,
};
warn!(
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
(ROCA) and should be replaced. On-chip key generation {} See \
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
for additional information on device replacement and mitigation assistance",
yubikey.serial, psz_msg
);
if !setting_roca.value {
return Err(Error::NotSupported);
}
}
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 | YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => (),
_ => {
error!("invalid algorithm specified");
return Err(Error::GenericError);
}
}
let txn = yubikey.begin_transaction()?;
templ[3] = slot;
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm,
]);
if in_data[4] == 0 {
error!("unexpected algorithm");
return Err(Error::AlgorithmError);
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]);
}
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
if !response.is_success() {
let err_msg = "failed to generate new key";
match response.status_words() {
StatusWords::IncorrectSlotError => {
error!("{} (incorrect slot)", err_msg);
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
error!("{} (pin policy not supported?)", err_msg);
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
error!("{} (touch policy not supported?)", err_msg);
} else {
error!("{} (algorithm not supported?)", err_msg);
}
return Err(Error::AlgorithmError);
}
StatusWords::SecurityStatusError => {
error!("{} (not authenticated)", err_msg);
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:x})", err_msg, other.code());
return Err(Error::GenericError);
}
}
}
let data = response.into_buffer();
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
let mut offset = 5;
let mut len = 0;
if data[offset] != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let modulus = data[offset..(offset + len)].to_vec();
offset += len;
if data[offset] != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let exp = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Rsa {
algorithm,
modulus,
exp,
})
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
let mut offset = 3;
let len = if algorithm == YKPIV_ALGO_ECCP256 {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
if data[offset] != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
offset += 1;
// the curve point should always be determined by the curve
let len_byte = data[offset];
offset += 1;
if len_byte as usize != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point })
}
_ => {
error!("wrong algorithm");
Err(Error::AlgorithmError)
}
}
}
+38 -133
View File
@@ -1,95 +1,10 @@
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC])
//! interface as provided by the [`pcsc` crate].
//!
//! **PIV** is a [NIST] standard for both *signing* and *encryption*
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
//!
//! This library natively implements the protocol used to manage and
//! utilize PIV encryption and signing keys which can be generated, imported,
//! and stored on YubiKey devices.
//!
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
//! on which devices support PIV and the available functionality.
//!
//! ## Minimum Supported Rust Version
//!
//! Rust 1.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
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -121,58 +36,48 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.2"
)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_qualifications
)]
mod apdu;
#[cfg(feature = "untested")]
pub mod cccid;
#[cfg(feature = "untested")]
mod cccid;
pub mod certificate;
#[cfg(feature = "untested")]
pub mod chuid;
#[cfg(feature = "untested")]
pub mod config;
pub mod consts;
#[cfg(feature = "untested")]
pub mod container;
pub mod error;
#[cfg(feature = "untested")]
pub mod key;
#[cfg(feature = "untested")]
mod chuid;
mod config;
mod consts;
mod error;
mod metadata;
mod mgm;
#[cfg(feature = "untested")]
pub mod mgm;
#[cfg(feature = "untested")]
pub mod msroots;
mod response;
mod mscmap;
#[cfg(feature = "untested")]
mod msroots;
pub mod piv;
mod policy;
pub mod reader;
mod serialization;
#[cfg(feature = "untested")]
pub mod settings;
mod setting;
mod transaction;
pub mod yubikey;
mod yubikey;
pub use crate::{
cccid::{CardId, CccId},
certificate::Certificate,
chuid::ChuId,
config::Config,
error::{Error, Result},
mgm::{MgmKey, MgmType},
piv::Key,
policy::{PinPolicy, TouchPolicy},
reader::Context,
setting::{Setting, SettingSource},
yubikey::{CachedPin, Serial, Version, YubiKey},
};
#[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey};
pub use yubikey::YubiKey;
pub use crate::{mscmap::MsContainer, msroots::MsRoots};
/// Algorithm identifiers
// TODO(tarcieri): make this an enum
pub type AlgorithmId = u8;
pub use uuid::Uuid;
/// Object identifiers
/// Object identifiers: handles to particular objects stored on a YubiKey.
pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
+176 -194
View File
@@ -30,236 +30,194 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
use std::{ptr, slice};
use std::marker::PhantomData;
use zeroize::Zeroizing;
/// Get metadata item
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
let mut p_temp: *const u8 = data.as_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8;
use crate::{serialization::*, transaction::Transaction, Buffer, Error, Result};
unsafe {
while p_temp < data.as_ptr().add(data.len()) {
tag_temp = *p_temp;
p_temp = p_temp.add(1);
#[cfg(feature = "untested")]
use crate::consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
let p_slice = slice::from_raw_parts(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
);
#[cfg(feature = "untested")]
use std::iter;
if !has_valid_length(
p_slice,
data.as_ptr().add(data.len()) as usize - p_temp as usize,
) {
return Err(Error::SizeError);
}
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;
p_temp = p_temp.add(get_length(p_slice, &mut cb_temp));
pub(crate) trait MetadataType: private::Sealed {}
if tag_temp == tag {
// found tag
break;
}
/// A type variable corresponding to PIN-protected metadata.
pub(crate) enum Protected {}
impl MetadataType for Protected {}
p_temp = p_temp.add(cb_temp);
}
/// A type variable corresponding to administrative metadata.
pub(crate) enum Admin {}
impl MetadataType for Admin {}
if p_temp < data.as_ptr().add(data.len()) {
return Ok(slice::from_raw_parts(p_temp, cb_temp));
}
}
Err(Error::GenericError)
/// Metadata stored in a YubiKey.
pub(crate) struct Metadata<T: MetadataType> {
inner: Buffer,
_marker: PhantomData<T>,
}
/// Set metadata item
pub(crate) fn set_item(
data: &mut [u8],
pcb_data: &mut usize,
cb_data_max: usize,
tag: u8,
p_item: &[u8],
) -> Result<(), Error> {
let mut p_temp: *mut u8 = data.as_mut_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let cb_item = p_item.len();
// Must be signed to have negative offsets
let cb_moved: isize;
let p_next: *mut u8;
/// 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>;
while p_temp < data[*pcb_data..].as_mut_ptr() {
unsafe {
tag_temp = *p_temp;
p_temp = p_temp.add(1);
impl<T: MetadataType> Default for Metadata<T> {
fn default() -> Self {
Metadata {
inner: Zeroizing::new(vec![]),
_marker: PhantomData::default(),
}
}
}
cb_len = get_length(
slice::from_raw_parts(
p_temp,
data.as_mut_ptr() as usize + data.len() - p_temp as usize,
),
&mut cb_temp,
);
p_temp = p_temp.add(cb_len);
impl<T: MetadataType> Metadata<T> {
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>) -> Result<Self> {
let data = txn.fetch_object(T::obj_id())?;
Ok(Metadata {
inner: Tlv::parse_single(data, T::tag())?,
_marker: PhantomData::default(),
})
}
/// Write metadata
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
if self.inner.is_empty() {
return Self::delete(txn);
}
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
let len = Tlv::write(&mut buf, T::tag(), &self.inner)?;
txn.save_object(T::obj_id(), &buf[..len])
}
/// Delete metadata
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[])
}
/// Get metadata item
pub(crate) fn get_item(&self, tag: u8) -> Result<&[u8]> {
let mut data = &self.inner[..];
while !data.is_empty() {
let (remaining, tlv) = Tlv::parse(data)?;
data = remaining;
if tlv.tag == tag {
// found tag
return Ok(tlv.value);
}
}
Err(Error::GenericError)
}
/// Set metadata item
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let mut offset = 0;
while offset < self.inner.len() {
tag_temp = self.inner[offset];
offset += 1;
cb_len = get_length(&self.inner[offset..], &mut cb_temp);
offset += cb_len;
if tag_temp == tag {
break;
}
p_temp = p_temp.add(cb_temp);
offset += cb_temp;
}
}
if tag_temp != tag {
if cb_item == 0 {
// We've been asked to delete an existing item that isn't in the blob
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(());
}
unsafe {
p_temp = data.as_mut_ptr().add(*pcb_data);
cb_len = get_length_size(cb_item);
// Found tag
if (*pcb_data + cb_len + cb_item) > cb_data_max {
return Err(Error::GenericError);
// 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;
*p_temp = tag;
p_temp = p_temp.add(1);
p_temp = p_temp.add(set_length(
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
// 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);
}
*pcb_data += 1 + cb_len + cb_item;
return Ok(());
}
if cb_temp == cb_item {
unsafe {
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
// Move remaining data
let orig_len = self.inner.len();
if cb_moved > 0 {
self.inner.extend(iter::repeat(0).take(cb_moved as usize));
}
return Ok(());
}
p_next = unsafe { p_temp.add(cb_temp) };
cb_moved = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 {
get_length_size(cb_item) as isize
} else {
-1
}
- cb_len as isize;
if (*pcb_data + cb_moved as usize) > cb_data_max {
return Err(Error::GenericError);
}
unsafe {
ptr::copy(
p_next,
p_next.offset(cb_moved),
*pcb_data - p_next as usize - data.as_ptr() as usize,
self.inner.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);
*pcb_data += cb_moved as usize;
if cb_item != 0 {
unsafe {
p_temp = p_temp.offset(-(cb_len as isize));
p_temp = p_temp.add(set_length(
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
// 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(())
}
Ok(())
}
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
let mut data = txn.fetch_object(obj_id)?;
if data.len() < CB_OBJ_TAG_MIN {
return Err(Error::GenericError);
}
if tag != data[0] {
return Err(Error::GenericError);
}
let mut pcb_data = 0;
let offset = 1 + get_length(&data[1..], &mut pcb_data);
if pcb_data > data.len() - offset {
return Err(Error::GenericError);
}
unsafe {
ptr::copy(data.as_ptr().add(offset), data.as_mut_ptr(), pcb_data);
}
data.truncate(pcb_data);
Ok(data)
}
/// Write metadata
pub(crate) fn write(
txn: &Transaction<'_>,
tag: u8,
data: &[u8],
max_size: usize,
) -> Result<(), Error> {
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if data.len() > max_size - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
if data.is_empty() {
// Deleting metadata
return txn.save_object(obj_id, &[]);
}
buf[0] = tag;
let mut offset = set_length(&mut buf[1..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(data);
offset += data.len();
txn.save_object(obj_id, &buf[..offset])
}
/// Get the size of a length tag for the given length
#[cfg(feature = "untested")]
fn get_length_size(length: usize) -> usize {
if length < 0x80 {
1
@@ -269,3 +227,27 @@ fn get_length_size(length: usize) -> usize {
3
}
}
mod private {
use super::*;
pub trait Sealed {
fn tag() -> u8;
fn obj_id() -> u32;
}
impl Sealed for Protected {
fn tag() -> u8 {
TAG_PROTECTED
}
fn obj_id() -> u32 {
OBJ_PRINTED
}
}
impl Sealed for Admin {
fn tag() -> u8 {
TAG_ADMIN
}
fn obj_id() -> u32 {
OBJ_ADMIN_DATA
}
}
}
+145 -84
View File
@@ -30,27 +30,41 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, yubikey::YubiKey};
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use getrandom::getrandom;
use hmac::Hmac;
use crate::{Error, Result};
use log::error;
use pbkdf2::pbkdf2;
use sha1::Sha1;
use std::convert::{TryFrom, TryInto};
use rand_core::{OsRng, RngCore};
use zeroize::{Zeroize, Zeroizing};
/// Default MGM key configured on all YubiKeys
const DEFAULT_MGM_KEY: [u8; DES_LEN_3DES] = [
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
];
#[cfg(feature = "untested")]
use crate::{
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_PROTECTED_MGM},
metadata::{AdminData, ProtectedData},
yubikey::YubiKey,
};
use des::{
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, 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)]
#[allow(non_camel_case_types)]
pub enum MgmType {
/// Manual
Manual = 0,
@@ -73,27 +87,23 @@ pub struct MgmKey([u8; DES_LEN_3DES]);
impl MgmKey {
/// Generate a random MGM key
pub fn generate() -> Result<Self, Error> {
pub fn generate() -> Self {
let mut key_bytes = [0u8; DES_LEN_3DES];
if getrandom(&mut key_bytes).is_err() {
return Err(Error::RandomnessError);
}
MgmKey::new(key_bytes)
OsRng.fill_bytes(&mut key_bytes);
Self(key_bytes)
}
/// Create an MGM key from byte slice.
///
/// Returns an error if the slice is the wrong size or the key is weak.
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
bytes.as_ref().try_into()
}
/// Create an MGM key from the given byte array.
///
/// Returns an error if the key is weak.
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self, Error> {
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self> {
if is_weak_key(&key_bytes) {
error!(
"blacklisting key '{:?}' since it's weak (with odd parity)",
@@ -103,16 +113,18 @@ impl MgmKey {
return Err(Error::KeyError);
}
Ok(MgmKey(key_bytes))
Ok(Self(key_bytes))
}
/// Get derived management key (MGM)
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self, Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
// recover management key
let data = metadata::read(&txn, TAG_ADMIN)?;
let salt = metadata::get_item(&data, TAG_ADMIN_SALT)?;
let admin_data = AdminData::read(&txn)?;
let salt = admin_data.get_item(TAG_ADMIN_SALT)?;
if salt.len() != CB_ADMIN_SALT {
error!(
@@ -125,21 +137,22 @@ impl MgmKey {
}
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)
}
/// Get protected management key (MGM)
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self, Error> {
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?;
let data = metadata::read(&txn, TAG_PROTECTED).map_err(|e| {
let protected_data = ProtectedData::read(&txn).map_err(|e| {
error!("could not read protected data (err: {:?})", e);
e
})?;
let item = metadata::get_item(&data, TAG_PROTECTED_MGM).map_err(|e| {
let item = protected_data.get_item(TAG_PROTECTED_MGM).map_err(|e| {
error!("could not read protected MGM from metadata (err: {:?})", e);
e
})?;
@@ -157,20 +170,87 @@ impl MgmKey {
MgmKey::from_bytes(item)
}
/// Set the management key (MGM)
pub fn set(&self, yubikey: &mut YubiKey, touch: Option<u8>) -> Result<(), Error> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(&self, touch)
/// Resets the management key for the given YubiKey to the default value.
///
/// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_default(yubikey: &mut YubiKey) -> Result<()> {
MgmKey::default().set_manual(yubikey, false)
}
/// Set protected management key (MGM)
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = Zeroizing::new(vec![0u8; CB_BUF_MAX]);
let max_size = yubikey.obj_size_max();
/// Configures the given YubiKey to use this management key.
///
/// The management key must be stored by the user, and provided when performing key
/// management operations.
///
/// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, None).map_err(|e| {
txn.set_mgm_key(self, require_touch).map_err(|e| {
// Log a warning, since the device mgm key is corrupt or we're in a state
// where we can't set the mgm key.
error!("could not set new derived mgm key, err = {}", e);
e
})?;
// After this point, we've set the mgm key, so the function should succeed,
// regardless of being able to set the metadata.
if let Ok(mut admin_data) = AdminData::read(&txn) {
// Clear the protected mgm key bit.
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
let mut flags_1 = [0u8; 1];
if item.len() == flags_1.len() {
flags_1.copy_from_slice(item);
flags_1[0] &= !ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
error!("could not set admin flags item, err = {}", e);
}
} else {
error!(
"admin data flags are an incorrect size: {} (expected {})",
item.len(),
flags_1.len()
);
}
}
// Remove any existing salt for a derived mgm key.
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
error!("could not unset derived mgm salt (err = {})", e)
}
if let Err(e) = admin_data.write(&txn) {
error!("could not write admin data, err = {}", e);
}
}
// Clear any prior mgm key from protected data.
if let Ok(mut protected_data) = ProtectedData::read(&txn) {
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, &[]) {
error!("could not clear protected mgm item, err = {:?}", e);
} else if let Err(e) = protected_data.write(&txn) {
error!("could not write protected data, err = {:?}", e);
}
}
Ok(())
}
/// Configures the given YubiKey to use this as a PIN-protected management key.
///
/// This enables key management operations to be performed with access to the PIN.
#[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, false).map_err(|e| {
// log a warning, since the device mgm key is corrupt or we're in
// a state where we can't set the mgm key
error!("could not set new derived mgm key, err = {}", e);
@@ -180,39 +260,25 @@ impl MgmKey {
// after this point, we've set the mgm key, so the function should
// succeed, regardless of being able to set the metadata
// set the new mgm key in protected data
let buffer = match metadata::read(&txn, TAG_PROTECTED) {
Ok(b) => b,
Err(_) => {
// set current metadata blob size to zero, we'll add to the blank blob
Zeroizing::new(vec![])
}
};
let mut cb_data = buffer.len();
data[..cb_data].copy_from_slice(&buffer);
// Fetch the current protected data, or start a blank metadata blob.
let mut protected_data = ProtectedData::read(&txn).unwrap_or_default();
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_PROTECTED_MGM,
self.as_ref(),
) {
// Set the new mgm key in protected data.
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, self.as_ref()) {
error!("could not set protected mgm item, err = {:?}", e);
} else {
metadata::write(&txn, TAG_PROTECTED, &data, max_size).map_err(|e| {
protected_data.write(&txn).map_err(|e| {
error!("could not write protected data, err = {:?}", e);
e
})?;
}
// set the protected mgm flag in admin data
cb_data = data.len();
let mut flags_1 = [0u8; 1];
if let Ok(buffer) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&buffer, TAG_ADMIN_FLAGS_1) {
let mut admin_data = if let Ok(mut admin_data) = AdminData::read(&txn) {
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
if item.len() == flags_1.len() {
flags_1.copy_from_slice(item);
} else {
@@ -228,26 +294,20 @@ impl MgmKey {
}
// remove any existing salt
if let Err(e) =
metadata::set_item(&mut data, &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, &[])
{
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
error!("could not unset derived mgm salt (err = {})", e)
}
admin_data
} else {
cb_data = 0;
}
AdminData::default()
};
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags_1,
) {
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
error!("could not set admin flags item, err = {}", e);
} else if let Err(e) = metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size) {
} else if let Err(e) = admin_data.write(&txn) {
error!("could not write admin data, err = {}", e);
}
@@ -255,7 +315,6 @@ impl MgmKey {
}
/// Encrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
@@ -264,11 +323,10 @@ impl MgmKey {
}
/// Decrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output));
.decrypt_block(GenericArray::from_mut_slice(&mut output));
output
}
}
@@ -279,9 +337,12 @@ impl AsRef<[u8; DES_LEN_3DES]> for MgmKey {
}
}
/// Default MGM key configured on all YubiKeys
impl Default for MgmKey {
fn default() -> Self {
MgmKey(DEFAULT_MGM_KEY)
MgmKey([
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
])
}
}
@@ -294,7 +355,7 @@ impl Drop for MgmKey {
impl<'a> TryFrom<&'a [u8]> for MgmKey {
type Error = Error;
fn try_from(key_bytes: &'a [u8]) -> Result<Self, Error> {
fn try_from(key_bytes: &'a [u8]) -> Result<Self> {
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
}
}
@@ -343,7 +404,7 @@ fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
c = (c & 0x0F) + ((c >> 4) & 0x0F);
// 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
+75 -105
View File
@@ -1,7 +1,4 @@
//! MS Container Map Records
//!
//! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification:
//! <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
//! MS Container Map Records.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -33,121 +30,115 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, key::SlotId, serialization::*, yubikey::YubiKey};
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
use log::error;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
/// MS Container Map(?) Records
#[derive(Copy, Clone)]
pub struct Container {
/// Container name
pub name: [u16; CONTAINER_NAME_LEN],
const OBJ_MSCMAP: u32 = 0x005f_ff10;
/// Card slot
const TAG_MSCMAP: u8 = 0x81;
/// MS Container Map records.
///
/// Defined in Microsoft's Smart Card Minidriver Specification:
/// <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
#[derive(Clone, Debug)]
pub struct MsContainer {
/// Container name.
pub name: [u16; Self::NAME_LEN],
/// Card slot.
pub slot: SlotId,
/// Key spec
/// Key spec.
pub key_spec: u8,
/// Key size in bits
/// Key size in bits.
pub key_size_bits: u16,
/// Flags
/// Flags.
pub flags: u8,
/// PIN ID
/// PIN ID.
pub pin_id: u8,
/// Associated ECHD(?) container (typo of "ecdh" perhaps?)
/// Associated ECHD container.
pub associated_echd_container: u8,
/// Cert fingerprint
pub cert_fingerprint: [u8; 20],
/// Cert fingerprint.
pub cert_fingerprint: [u8; Self::CERT_FINGERPRINT_LEN],
}
impl Container {
/// Read MS Container Map records
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
impl MsContainer {
/// Container name length in UTF-16 chars.
const NAME_LEN: usize = 40;
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
const REC_LEN: usize = (2 * Self::NAME_LEN) + 27;
/// Length of a certificate fingerprint.
const CERT_FINGERPRINT_LEN: usize = 20;
/// Read MS Container Map records.
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_MSCMAP)?;
let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![];
if response.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
let (_, tlv) = match Tlv::parse(&response) {
Ok(res) => res,
Err(_) => {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
};
if response[0] != TAG_MSCMAP {
if tlv.tag != TAG_MSCMAP {
// TODO(tarcieri): yubico-piv-tool returned success here? should we?
return Err(Error::InvalidObject);
}
let mut len = 0;
let offset = 1 + get_length(&response[1..], &mut len);
if len > response.len() - offset {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
for chunk in response[offset..(offset + len)].chunks_exact(CONTAINER_REC_LEN) {
containers.push(Container::new(chunk)?);
for chunk in tlv.value.chunks_exact(Self::REC_LEN) {
containers.push(MsContainer::new(chunk)?);
}
Ok(containers)
}
/// Write MS Container Map records.
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<()> {
let n_containers = containers.len();
let data_len = n_containers * CONTAINER_REC_LEN;
let data_len = n_containers * Self::REC_LEN;
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if n_containers == 0 {
return txn.save_object(YKPIV_OBJ_MSCMAP, &[]);
return txn.save_object(OBJ_MSCMAP, &[]);
}
let req_len = 1 + set_length(&mut buf, data_len) + data_len;
let mut buf = [0u8; CB_OBJ_MAX];
let offset = Tlv::write_as(&mut buf, TAG_MSCMAP, data_len, |buf| {
for (i, chunk) in buf.chunks_exact_mut(Self::REC_LEN).enumerate() {
chunk.copy_from_slice(&containers[i].to_bytes());
}
})?;
if req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_MSCMAP;
offset += 1;
offset += set_length(&mut buf[offset..], data_len);
for (i, chunk) in buf[..data_len]
.chunks_exact_mut(CONTAINER_REC_LEN)
.enumerate()
{
chunk.copy_from_slice(&containers[i].to_bytes());
}
offset += data_len;
txn.save_object(YKPIV_OBJ_MSCMAP, &buf[..offset])
txn.save_object(OBJ_MSCMAP, &buf[..offset])
}
/// Parse a container record from a byte slice
pub fn new(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != CONTAINER_REC_LEN {
/// Parse a container record from a byte slice.
pub fn new(bytes: &[u8]) -> Result<Self> {
if bytes.len() != Self::REC_LEN {
error!(
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
CONTAINER_REC_LEN,
Self::REC_LEN,
bytes.len()
);
return Err(Error::ParseError);
}
let mut name = [0u16; CONTAINER_NAME_LEN];
let name_bytes_len = CONTAINER_NAME_LEN * 2;
let mut name = [0u16; Self::NAME_LEN];
let name_bytes_len = Self::NAME_LEN * 2;
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
@@ -156,9 +147,9 @@ impl Container {
let mut cert_fingerprint = [0u8; 20];
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
Ok(Container {
Ok(Self {
name,
slot: bytes[name_bytes_len],
slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
@@ -172,56 +163,35 @@ impl Container {
})
}
/// Parse the container name as a UTF-16 string
pub fn parse_name(&self) -> Result<String, Error> {
/// Parse the container name as a UTF-16 string.
pub fn parse_name(&self) -> Result<String> {
String::from_utf16(&self.name).map_err(|_| Error::ParseError)
}
/// Serialize a container record as a byte size
pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] {
let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN);
/// Serialize a container record as a byte size.
pub fn to_bytes(&self) -> [u8; Self::REC_LEN] {
// TODO(tarcieri): use array instead of `Vec`
let mut bytes = Vec::with_capacity(Self::REC_LEN);
for i in 0..CONTAINER_NAME_LEN {
for i in 0..Self::NAME_LEN {
bytes.extend_from_slice(&self.name[i].to_le_bytes());
}
bytes.push(self.slot);
bytes.push(self.slot.into());
bytes.push(self.key_spec);
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
bytes.push(self.flags);
bytes.push(self.pin_id);
bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint);
// TODO(tarcieri): use TryInto here when const generics are available
let mut result = [0u8; CONTAINER_REC_LEN];
result.copy_from_slice(&bytes);
result
bytes.as_slice().try_into().unwrap()
}
}
impl Debug for Container {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PivContainer {{ name: {:?}, slot: {}, key_spec: {}, key_size_bits: {}, \
flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}",
&self.name[..],
self.slot,
self.key_spec,
self.key_size_bits,
self.flags,
self.pin_id,
self.associated_echd_container,
&self.cert_fingerprint[..]
)
}
}
impl<'a> TryFrom<&'a [u8]> for Container {
impl<'a> TryFrom<&'a [u8]> for MsContainer {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::new(bytes)
}
}
+61 -79
View File
@@ -1,11 +1,4 @@
//! `msroots`: PKCS#7 formatted certificate store for enterprise trusted roots.
//!
//! This `msroots` file contains a bag of certificates with empty content and
//! an empty signature, allowing an enterprise root certificate truststore to
//! be written to and read from a YubiKey.
//!
//! For more information, see:
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
//! PKCS#7 formatted certificate store for enterprise trusted roots.
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -37,106 +30,96 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
use crate::{
consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
serialization::*,
Error, Result, YubiKey,
};
use log::error;
use std::{ptr, slice};
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
const OBJ_MSROOTS1: u32 = 0x005f_ff11;
#[allow(dead_code)]
const OBJ_MSROOTS2: u32 = 0x005f_ff12;
#[allow(dead_code)]
const OBJ_MSROOTS3: u32 = 0x005f_ff13;
#[allow(dead_code)]
const OBJ_MSROOTS4: u32 = 0x005f_ff14;
const OBJ_MSROOTS5: u32 = 0x005f_ff15;
const TAG_MSROOTS_END: u8 = 0x82;
const TAG_MSROOTS_MID: u8 = 0x83;
/// PKCS#7-formatted certificate store for enterprise trust roots.
///
/// The `msroots` file contains a bag of certificates with empty content and
/// an empty signature, allowing an enterprise root certificate truststore to
/// be written to and read from a YubiKey.
///
/// For more information, see:
/// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub struct MsRoots(Vec<u8>);
impl MsRoots {
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self, Error> {
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self> {
Ok(MsRoots(msroots.as_ref().into()))
}
/// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut len: usize = 0;
let mut ptr: *mut u8;
let mut tag: u8;
let mut offset: usize = 0;
let mut results = vec![];
let cb_data = yubikey.obj_size_max();
pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
let txn = yubikey.begin_transaction()?;
// allocate first page
let mut p_data = vec![0u8; cb_data];
let mut data = Vec::with_capacity(CB_OBJ_MAX);
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
let mut buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
for object_id in OBJ_MSROOTS1..OBJ_MSROOTS5 {
let buf = txn.fetch_object(object_id)?;
ptr = buf.as_mut_ptr();
let (_, tlv) = match Tlv::parse(&buf) {
Ok(res) => res,
Err(_) => return Ok(None),
};
if cb_buf < CB_OBJ_TAG_MIN {
return Ok(results);
}
unsafe {
tag = *ptr;
ptr = ptr.add(1);
}
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag)
if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tlv.tag)
{
// the current object doesn't contain a valid part of a msroots file
// treat condition as object isn't found
return Ok(results);
return Ok(None);
}
unsafe {
ptr = ptr.add(get_length(
slice::from_raw_parts(ptr, buf.as_ptr() as usize + buf.len() - ptr as usize),
&mut len,
));
}
data.extend_from_slice(tlv.value);
// check that decoded length represents object contents
if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize {
return Ok(results);
}
unsafe {
ptr::copy(ptr, p_data.as_mut_ptr().add(offset), len);
}
offset += len;
match MsRoots::new(&p_data[..offset]) {
Ok(msroots) => results.push(msroots),
Err(res) => error!("error parsing msroots: {:?}", res),
}
if tag == TAG_MSROOTS_END {
if tlv.tag == TAG_MSROOTS_END {
break;
}
}
Ok(results)
MsRoots::new(&data).map(Some).map_err(|e| {
error!("error parsing msroots: {:?}", e);
e
})
}
/// Write `msroots` file to YubiKey
pub fn write(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
pub fn write(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset: usize;
let mut data_offset: usize = 0;
let mut data_chunk: usize;
let data = &self.0;
let data_len = data.len();
let n_objs: usize;
let cb_obj_max = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if data_len == 0 {
return txn.save_object(YKPIV_OBJ_MSROOTS1, &[]);
return txn.save_object(OBJ_MSROOTS1, &[]);
}
// Calculate number of objects required to store blob
n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1;
let n_objs: usize = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 {
return Err(Error::SizeError);
@@ -145,24 +128,23 @@ impl MsRoots {
for i in 0..n_objs {
offset = 0;
data_chunk = if cb_obj_max - CB_OBJ_TAG_MAX < data_len - data_offset {
cb_obj_max - CB_OBJ_TAG_MAX
data_chunk = if CB_OBJ_MAX - CB_OBJ_TAG_MAX < data_len - data_offset {
CB_OBJ_MAX - CB_OBJ_TAG_MAX
} else {
data_len - data_offset
};
buf[offset] = if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
};
offset += Tlv::write(
&mut buf,
if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
},
&data[data_offset..(data_offset + data_chunk)],
)?;
offset += 1;
offset += set_length(&mut buf[offset..], data_chunk);
buf[offset..].copy_from_slice(&data[data_offset..(data_offset + data_chunk)]);
offset += data_chunk;
txn.save_object(YKPIV_OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
txn.save_object(OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
data_offset += data_chunk;
}
+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),
}
}
}
+89
View File
@@ -0,0 +1,89 @@
//! Support for enumerating available PC/SC card readers.
use crate::{Result, YubiKey};
use std::{
borrow::Cow,
ffi::CStr,
sync::{Arc, Mutex},
};
/// Iterator over connected readers
pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>;
/// PC/SC reader context: used to enumerate available PC/SC [`Reader`]s.
pub struct Context {
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
/// Buffer for storing reader names
reader_names: Vec<u8>,
}
impl Context {
/// Open a PC/SC context, which can be used to enumerate available PC/SC
/// readers (which can be used to connect to YubiKeys).
pub fn open() -> Result<Self> {
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
let reader_names = vec![0u8; ctx.list_readers_len()?];
Ok(Self {
ctx: Arc::new(Mutex::new(ctx)),
reader_names,
})
}
/// Iterate over the available readers.
pub fn iter(&mut self) -> Result<Iter<'_>> {
let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = {
let c = ctx.lock().unwrap();
// ensure PC/SC context is valid
c.is_valid()?;
c.list_readers(reader_names)?.collect()
};
#[allow(clippy::needless_collect)]
let readers: Vec<_> = reader_cstrs
.iter()
.map(|name| Reader::new(name, Arc::clone(ctx)))
.collect();
Ok(readers.into_iter())
}
}
/// An individual connected PC/SC card reader.
pub struct Reader<'ctx> {
/// Name of this reader
name: &'ctx CStr,
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
}
impl<'ctx> Reader<'ctx> {
/// Create a new reader from its name and context.
fn new(name: &'ctx CStr, ctx: Arc<Mutex<pcsc::Context>>) -> Self {
// TODO(tarcieri): open devices, determine they're YubiKeys, get serial?
Self { name, ctx }
}
/// Get this reader's name.
pub fn name(&self) -> Cow<'_, str> {
// TODO(tarcieri): is lossy ok here? try to avoid lossiness?
self.name.to_string_lossy()
}
/// Open a connection to this reader, returning a `YubiKey` if successful.
pub fn open(&self) -> Result<YubiKey> {
self.try_into()
}
/// Connect to this reader, returning its `pcsc::Card`.
pub(crate) fn connect(&self) -> Result<pcsc::Card> {
let ctx = self.ctx.lock().unwrap();
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
}
}
-186
View File
@@ -1,186 +0,0 @@
//! Responses to issued commands
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::Buffer;
/// Parsed response to a command
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
buffer: Buffer,
}
impl Response {
/// Parse a response from the given buffer
pub fn from_bytes(mut buffer: Buffer) -> Self {
if buffer.len() >= 2 {
let sw = StatusWords::from(
(buffer[buffer.len() - 2] as u32) << 8 | (buffer[buffer.len() - 1] as u32),
);
let len = buffer.len() - 2;
buffer.truncate(len);
Response {
status_words: sw,
buffer,
}
} else {
Response {
status_words: StatusWords::None,
buffer,
}
}
}
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
Response {
status_words,
buffer,
}
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response buffer
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
/// Consume this response, returning its buffer
#[cfg(feature = "untested")]
pub fn into_buffer(self) -> Buffer {
self.buffer
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.buffer()
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// Incorrect parameter in command data field
IncorrectParamError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// Other/unrecognized status words
Other(u32),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
+114 -10
View File
@@ -30,30 +30,134 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, ObjectId};
use crate::{consts::CB_OBJ_TAG_MIN, Buffer, Error, ObjectId, Result};
pub const OBJ_DISCOVERY: u32 = 0x7e;
// TODO(tarcieri): refactor these into better serializers/message builders
/// A Type-Length-Value object that has been parsed from a buffer.
pub(crate) struct Tlv<'a> {
pub(crate) tag: u8,
pub(crate) value: &'a [u8],
}
impl<'a> Tlv<'a> {
/// Parses a `Tlv` from a buffer, returning the remainder of the buffer.
pub(crate) fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Self)> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
let tag = buffer[0];
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
let buffer = buffer.get(offset..).ok_or(Error::SizeError)?;
if buffer.len() >= len {
let (value, buffer) = buffer.split_at(len);
Ok((buffer, Tlv { tag, value }))
} else {
Err(Error::SizeError)
}
}
/// Takes a [`Buffer`] containing a single `Tlv` with the given tag, and returns a
/// `Buffer` containing only the value part of the `Tlv`.
pub(crate) fn parse_single(mut buffer: Buffer, tag: u8) -> Result<Buffer> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
if tag != buffer[0] {
return Err(Error::GenericError);
};
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
buffer.copy_within(offset..offset + len, 0);
buffer.truncate(len);
Ok(buffer)
}
/// Writes a TLV to the given buffer.
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize> {
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], value.len())?;
if buffer.len() < offset + value.len() {
return Err(Error::SizeError);
}
buffer[offset..offset + value.len()].copy_from_slice(value);
Ok(offset + value.len())
}
/// Writes a TLV to the given buffer.
///
/// `value` is guaranteed to be called with a mutable slice of length `length`.
pub(crate) fn write_as<Gen>(
buffer: &mut [u8],
tag: u8,
length: usize,
value: Gen,
) -> Result<usize>
where
Gen: FnOnce(&mut [u8]),
{
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], length)?;
if buffer.len() < offset + length {
return Err(Error::SizeError);
}
value(&mut buffer[offset..offset + length]);
Ok(offset + length)
}
}
/// Set length
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> usize {
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize> {
if length < 0x80 {
buffer[0] = length as u8;
1
if buffer.is_empty() {
Err(Error::SizeError)
} else {
buffer[0] = length as u8;
Ok(1)
}
} else if length < 0x100 {
buffer[0] = 0x81;
buffer[1] = length as u8;
2
if buffer.len() < 2 {
Err(Error::SizeError)
} else {
buffer[0] = 0x81;
buffer[1] = length as u8;
Ok(2)
}
} else if buffer.len() < 3 {
Err(Error::SizeError)
} else {
buffer[0] = 0x82;
buffer[1] = ((length >> 8) & 0xff) as u8;
buffer[2] = (length & 0xff) as u8;
3
Ok(3)
}
}
/// Parse length tag, returning the size of the length tag itself as the
/// returned value, and setting the len parameter to the parsed length.
pub(crate) fn get_length(buffer: &[u8], len: &mut usize) -> usize {
// This is not valid ASN.1 (0x80 is the indefinite length marker).
// See comment in key::generate for more context.
if buffer[0] < 0x81 {
*len = buffer[0] as usize;
1
@@ -83,9 +187,9 @@ pub(crate) fn has_valid_length(buffer: &[u8], len: usize) -> bool {
pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
buffer[0] = 0x5c;
if object_id == YKPIV_OBJ_DISCOVERY {
if object_id == OBJ_DISCOVERY {
buffer[1] = 1;
buffer[2] = YKPIV_OBJ_DISCOVERY as u8;
buffer[2] = OBJ_DISCOVERY as u8;
buffer = &mut buffer[3..];
} else if object_id > 0xffff && object_id <= 0x00ff_ffff {
buffer[1] = 3;
+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
}
+148 -203
View File
@@ -1,41 +1,49 @@
//! YubiKey PC/SC transactions
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*, Buffer};
#[cfg(feature = "untested")]
use crate::{
mgm::MgmKey,
response::{Response, StatusWords},
apdu::Response,
apdu::{Apdu, Ins, StatusWords},
consts::{CB_BUF_MAX, CB_OBJ_MAX},
error::{Error, Result},
piv::{AlgorithmId, SlotId},
serialization::*,
ObjectId,
yubikey::*,
Buffer, ObjectId,
};
use log::{error, trace};
use std::convert::TryInto;
#[cfg(feature = "untested")]
use std::ptr;
use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES};
/// PIV Applet ID
const PIV_AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
const YK_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
const CB_PIN_MAX: usize = 8;
#[cfg(feature = "untested")]
pub(crate) enum ChangeRefAction {
ChangePin,
ChangePuk,
UnblockPin,
}
/// Exclusive transaction with the YubiKey's PC/SC card.
pub(crate) struct Transaction<'tx> {
inner: pcsc::Transaction<'tx>,
}
impl<'tx> Transaction<'tx> {
/// Create a new transaction with the given card
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self, Error> {
/// Create a new transaction with the given card.
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self> {
Ok(Transaction {
inner: card.transaction()?,
})
}
/// Get an attribute of the card or card reader.
pub fn get_attribute<'buf>(
&self,
attribute: pcsc::Attribute,
buffer: &'buf mut [u8],
) -> Result<&'buf [u8], Error> {
Ok(self.inner.get_attribute(attribute, buffer)?)
}
/// Transmit a single serialized APDU to the card this transaction is open
/// with and receive a response.
///
@@ -43,10 +51,10 @@ impl<'tx> Transaction<'tx> {
/// single APDU messages at a time. For larger messages that need to be
/// split into multiple APDUs, use the [`Transaction::transfer_data`]
/// method instead.
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Buffer, Error> {
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>> {
trace!(">>> {:?}", send_buffer);
let mut recv_buffer = Zeroizing::new(vec![0u8; recv_len]);
let mut recv_buffer = vec![0u8; recv_len];
let len = self
.inner
@@ -54,17 +62,14 @@ impl<'tx> Transaction<'tx> {
.len();
recv_buffer.truncate(len);
trace!("<<< {:?}", recv_buffer.as_slice());
Ok(recv_buffer)
}
/// Select application.
pub fn select_application(&self) -> Result<(), Error> {
let response = APDU::new(YKPIV_INS_SELECT_APPLICATION)
pub fn select_application(&self) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(PIV_AID)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
@@ -82,35 +87,29 @@ impl<'tx> Transaction<'tx> {
Ok(())
}
/// Get the version of the PIV application installed on the YubiKey
pub fn get_version(&self) -> Result<Version, Error> {
/// Get the version of the PIV application installed on the YubiKey.
pub fn get_version(&self) -> Result<Version> {
// get version from device
let response = APDU::new(YKPIV_INS_GET_VERSION).transmit(self, 261)?;
let response = Apdu::new(Ins::GetVersion).transmit(self, 261)?;
if !response.is_success() {
return Err(Error::GenericError);
}
if response.buffer().len() < 3 {
if response.data().len() < 3 {
return Err(Error::SizeError);
}
Ok(Version {
major: response.buffer()[0],
minor: response.buffer()[1],
patch: response.buffer()[2],
})
Ok(Version::new(response.data()[..3].try_into().unwrap()))
}
/// Get YubiKey device serial number
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> {
let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
/// Get YubiKey device serial number.
pub fn get_serial(&self, version: Version) -> Result<Serial> {
let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
// YK4 requires switching to the yk applet to retrieve the serial
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&yk_applet)
.data(YK_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -119,7 +118,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError);
}
let resp = APDU::new(0x01).p1(0x10).transmit(self, 0xFF)?;
let resp = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
@@ -130,9 +129,9 @@ impl<'tx> Transaction<'tx> {
}
// reselect the PIV applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(PIV_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -143,8 +142,8 @@ impl<'tx> Transaction<'tx> {
resp
} else {
// get serial from yk5 and later devices using the f8 command
let resp = APDU::new(YKPIV_INS_GET_SERIAL).transmit(self, 0xFF)?;
// YK5 implements getting the serial as a PIV applet command (0xf8)
let resp = Apdu::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
@@ -157,74 +156,64 @@ impl<'tx> Transaction<'tx> {
resp
};
response.buffer()[..4]
response.data()[..4]
.try_into()
.map(|serial| Serial::from(u32::from_be_bytes(serial)))
.map_err(|_| Error::SizeError)
}
/// Verify device PIN.
#[cfg(feature = "untested")]
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX?
if pin.len() != CB_PIN_MAX {
pub fn verify_pin(&self, pin: &[u8]) -> Result<()> {
if pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
let response = APDU::new(YKPIV_INS_VERIFY)
.params(0x00, 0x80)
.data(pin)
.transmit(self, 261)?;
let mut query = Apdu::new(Ins::Verify);
query.params(0x00, 0x80);
// Empty pin means we are querying the number of retries. We set no data in this
// case; if we instead sent [0xff; CB_PIN_MAX] it would count as an attempt and
// decrease the retry counter.
if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref());
}
let response = query.transmit(self, 261)?;
match response.status_words() {
StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => Err(Error::GenericError),
}
}
/// Change the PIN
/// Change the PIN.
#[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80];
let mut indata = Zeroizing::new([0u8; 16]);
pub fn change_ref(
&self,
action: ChangeRefAction,
current_pin: &[u8],
new_pin: &[u8],
) -> Result<()> {
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
if action == CHREF_ACT_UNBLOCK_PIN {
templ[1] = YKPIV_INS_RESET_RETRY;
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
const PIN: u8 = 0x80;
const PUK: u8 = 0x81;
unsafe {
ptr::copy(current_pin.as_ptr(), indata.as_mut_ptr(), current_pin.len());
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],
};
if current_pin.len() < CB_PIN_MAX {
ptr::write_bytes(
indata.as_mut_ptr().add(current_pin.len()),
0xff,
CB_PIN_MAX - current_pin.len(),
);
}
ptr::copy(
new_pin.as_ptr(),
indata.as_mut_ptr().offset(8),
new_pin.len(),
);
if new_pin.len() < CB_PIN_MAX {
ptr::write_bytes(
indata.as_mut_ptr().offset(8).add(new_pin.len()),
0xff,
CB_PIN_MAX - new_pin.len(),
);
}
}
let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
indata[0..current_pin.len()].copy_from_slice(current_pin);
indata[CB_PIN_MAX..CB_PIN_MAX + new_pin.len()].copy_from_slice(new_pin);
let status_words = self
.transfer_data(&templ, indata.as_ref(), 0xFF)?
@@ -233,7 +222,7 @@ impl<'tx> Transaction<'tx> {
match status_words {
StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::PinLocked),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => {
error!(
"failed changing pin, token response code: {:x}.",
@@ -246,24 +235,18 @@ impl<'tx> Transaction<'tx> {
/// Set the management key (MGM).
#[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> {
let p2 = match touch.unwrap_or_default() {
0 => 0xff,
1 => 0xfe,
_ => {
return Err(Error::GenericError);
}
};
pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
let p2 = if require_touch { 0xfe } else { 0xff };
let mut data = [0u8; DES_LEN_3DES + 3];
data[0] = YKPIV_ALGO_3DES;
data[1] = YKPIV_KEY_CARDMGM;
data[0] = ALGO_3DES;
data[1] = KEY_CARDMGM;
data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
let status_words = APDU::new(YKPIV_INS_SET_MGMKEY)
let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2)
.data(&data)
.data(data)
.transmit(self, 261)?
.status_words();
@@ -279,25 +262,21 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing
/// operations.
// TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command(
&self,
sign_in: &[u8],
out: &mut [u8],
out_len: &mut usize,
algorithm: u8,
key: u8,
algorithm: AlgorithmId,
key: SlotId,
decipher: bool,
) -> Result<(), Error> {
) -> Result<Buffer> {
let in_len = sign_in.len();
let mut indata = [0u8; 1024];
let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key];
let mut len: usize = 0;
let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
let key_len = if algorithm == YKPIV_ALGO_RSA1024 {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let key_len = if let AlgorithmId::Rsa1024 = algorithm {
128
} else {
256
@@ -307,8 +286,8 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError);
}
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
let key_len = if algorithm == YKPIV_ALGO_ECCP256 {
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let key_len = if let AlgorithmId::EccP256 = algorithm {
32
} else {
48
@@ -319,7 +298,6 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError);
}
}
_ => return Err(Error::AlgorithmError),
}
let bytes = if in_len < 0x80 {
@@ -330,21 +308,21 @@ impl<'tx> Transaction<'tx> {
3
};
indata[0] = 0x7c;
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3);
indata[offset] = 0x82;
indata[offset + 1] = 0x00;
indata[offset + 2] =
if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher {
0x85
} else {
0x81
};
offset += 3;
offset += set_length(&mut indata[offset..], in_len);
indata[offset..(offset + in_len)].copy_from_slice(sign_in);
offset += in_len;
let offset = Tlv::write_as(&mut indata, 0x7c, in_len + bytes + 3, |buf| {
assert_eq!(Tlv::write(buf, 0x82, &[]).expect("large enough"), 2);
assert_eq!(
Tlv::write(
&mut buf[2..],
match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
_ => 0x81,
},
sign_in
)
.expect("large enough"),
1 + bytes + in_len
);
})?;
let response = self
.transfer_data(&templ, &indata[..offset], 1024)
@@ -363,49 +341,33 @@ impl<'tx> Transaction<'tx> {
}
}
let data = response.buffer();
let (_, outer_tlv) = Tlv::parse(response.data())?;
// skip the first 7c tag
if data[0] != 0x7c {
if outer_tlv.tag != 0x7c {
error!("failed parsing signature reply (0x7c byte)");
return Err(Error::ParseError);
}
let mut offset = 1 + get_length(&data[1..], &mut len);
let (_, inner_tlv) = Tlv::parse(outer_tlv.value)?;
// skip the 82 tag
if data[offset] != 0x82 {
if inner_tlv.tag != 0x82 {
error!("failed parsing signature reply (0x82 byte)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
if len > *out_len {
error!("wrong size on output buffer");
return Err(Error::SizeError);
}
*out_len = len;
out[..len].copy_from_slice(&data[offset..(offset + len)]);
Ok(())
Ok(Buffer::new(inner_tlv.value.into()))
}
/// Send/receive large amounts of data to/from the YubiKey, splitting long
/// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via
/// [`Transaction::transmit`].
#[cfg(feature = "untested")]
pub fn transfer_data(
&self,
templ: &[u8],
in_data: &[u8],
max_out: usize,
) -> Result<Response, Error> {
pub fn transfer_data(&self, templ: &[u8], in_data: &[u8], max_out: usize) -> Result<Response> {
let mut in_offset = 0;
let mut out_data = Zeroizing::new(vec![]);
let mut sw = 0;
let mut out_data = vec![];
let mut sw;
loop {
let mut this_size = 0xff;
@@ -419,30 +381,30 @@ impl<'tx> Transaction<'tx> {
trace!("going to send {} bytes in this go", this_size);
let response = APDU::new(templ[1])
let response = Apdu::new(templ[1])
.cla(cla)
.params(templ[2], templ[3])
.data(&in_data[in_offset..(in_offset + this_size)])
.transmit(self, 261)?;
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
sw = response.status_words().code();
if !response.is_success() && (sw >> 8 != 0x61) {
// TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data));
}
sw = response.status_words().code();
if out_data.len() - response.buffer().len() - 2 > max_out {
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
error!(
"output buffer too small: wanted to write {}, max was {}",
out_data.len() - response.buffer().len() - 2,
out_data.len() - response.data().len(),
max_out
);
return Err(Error::SizeError);
}
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
out_data.extend_from_slice(&response.data()[..response.data().len()]);
in_offset += this_size;
if in_offset >= in_data.len() {
@@ -456,34 +418,33 @@ impl<'tx> Transaction<'tx> {
sw & 0xff
);
let response = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).transmit(self, 261)?;
let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
return Ok(Response::new(sw.into(), Zeroizing::new(vec![])));
return Ok(Response::new(sw.into(), vec![]));
}
if out_data.len() + response.buffer().len() - 2 > max_out {
if out_data.len() + response.data().len() > max_out {
error!(
"output buffer too small: wanted to write {}, max was {}",
out_data.len() + response.buffer().len() - 2,
out_data.len() + response.data().len(),
max_out
);
return Err(Error::SizeError);
}
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
out_data.extend_from_slice(&response.data()[..response.data().len()]);
}
Ok(Response::new(sw.into(), out_data))
}
/// Fetch an object
#[cfg(feature = "untested")]
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
/// Fetch an object.
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer> {
let mut indata = [0u8; 5];
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff];
let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
let mut inlen = indata.len();
let indata_remaining = set_object(object_id, &mut indata);
@@ -492,41 +453,31 @@ impl<'tx> Transaction<'tx> {
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
if !response.is_success() {
return Err(Error::GenericError);
if response.status_words() == StatusWords::NotFoundError {
return Err(Error::NotFound);
} else {
return Err(Error::GenericError);
}
}
let data = response.into_buffer();
let mut outlen = 0;
let (remaining, tlv) = Tlv::parse(response.data())?;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) {
return Err(Error::SizeError);
}
let offs = get_length(&data[1..], &mut outlen);
if offs == 0 {
return Err(Error::SizeError);
}
if outlen + offs + 1 != data.len() {
if !remaining.is_empty() {
error!(
"invalid length indicated in object: total len is {} but indicated length is {}",
data.len(),
outlen
tlv.value.len() + remaining.len(),
tlv.value.len()
);
return Err(Error::SizeError);
}
Ok(Zeroizing::new(
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
))
Ok(Zeroizing::new(tlv.value.to_vec()))
}
/// Save an object
#[cfg(feature = "untested")]
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
/// Save an object.
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<()> {
let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
// TODO(tarcieri): replace with vector
let mut data = [0u8; CB_BUF_MAX];
@@ -538,14 +489,8 @@ impl<'tx> Transaction<'tx> {
let mut len = data.len();
let mut data_remaining = set_object(object_id, &mut data);
data_remaining[0] = 0x53;
data_remaining = &mut data_remaining[1..];
let offset = set_length(data_remaining, indata.len());
let offset = Tlv::write(data_remaining, 0x53, indata)?;
data_remaining = &mut data_remaining[offset..];
data_remaining[..indata.len()].copy_from_slice(indata);
data_remaining = &mut data_remaining[indata.len()..];
len -= data_remaining.len();
let status_words = self
+270 -495
View File
File diff suppressed because it is too large Load Diff
+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)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use std::env;
use yubikey_piv::YubiKey;
use log::trace;
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]
#[ignore]
fn connect() {
static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
let mut yubikey = YubiKey::open(None).unwrap();
dbg!(&yubikey.version());
dbg!(&yubikey.serial());
let yubikey = if let Ok(serial) = env::var("YUBIKEY_SERIAL") {
let serial = Serial::from_str(&serial).unwrap();
YubiKey::open_by_serial(serial).unwrap()
} else {
YubiKey::open().unwrap()
};
trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version());
Mutex::new(yubikey)
});
//
// CCCID support
//
#[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"
);
}