Compare commits

..

54 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
35 changed files with 1783 additions and 864 deletions
+5
View File
@@ -0,0 +1,5 @@
[advisories]
ignore = [
"RUSTSEC-2020-0071", # time
"RUSTSEC-2020-0159", # chrono
]
+4 -4
View File
@@ -36,13 +36,13 @@ jobs:
toolchain: stable toolchain: stable
deps: true deps: true
- platform: ubuntu-latest - platform: ubuntu-latest
toolchain: 1.51.0 # MSRV toolchain: 1.60.0 # MSRV
deps: sudo apt-get install libpcsclite-dev deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest - platform: windows-latest
toolchain: 1.51.0 # MSRV toolchain: 1.60.0 # MSRV
deps: true deps: true
- platform: macos-latest - platform: macos-latest
toolchain: 1.51.0 # MSRV toolchain: 1.60.0 # MSRV
deps: true deps: true
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
@@ -82,7 +82,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.51.0 # MSRV toolchain: 1.65.0
components: clippy components: clippy
override: true override: true
- run: sudo apt-get install libpcsclite-dev - run: sudo apt-get install libpcsclite-dev
+77 -1
View File
@@ -4,7 +4,83 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.4.0 (2021-07-12) ## 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 ### Added
- `Result` alias ([#271]) - `Result` alias ([#271])
Generated
+617 -336
View File
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -1,54 +1,57 @@
[package] [package]
name = "yubikey" name = "yubikey"
version = "0.4.0" # Also update html_root_url in lib.rs when bumping this version = "0.7.0"
description = """ description = """
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
support for hardware-backed public-key decryption and digital signatures using support for hardware-backed public-key decryption and digital signatures using
the Personal Identity Verification (PIV) application. Supports RSA (1024/2048) the Personal Identity Verification (PIV) application. Supports RSA (1024/2048)
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
""" """
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"] authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
edition = "2018" license = "BSD-2-Clause"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs" repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md" readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"] categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"] keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021"
rust-version = "1.60"
[workspace] [workspace]
members = [".", "cli"] members = [".", "cli"]
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4.23"
cookie-factory = "0.3" cookie-factory = "0.3"
der-parser = "5" der-parser = "8"
des = "0.7" des = "0.8"
elliptic-curve = "0.10" elliptic-curve = "0.12"
hmac = "0.11" hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
hmac = "0.12"
log = "0.4" log = "0.4"
nom = "6" nom = "7"
num-bigint-dig = { version = "0.7", features = ["rand"] } num-bigint-dig = { version = "0.8", features = ["rand"] }
num-traits = "0.2" num-traits = "0.2"
num-integer = "0.1" num-integer = "0.1"
pbkdf2 = { version = "0.8", default-features = false } pbkdf2 = { version = "0.11", default-features = false }
p256 = "0.9" p256 = "0.11"
p384 = "0.8" p384 = "0.11"
pcsc = "2" pcsc = "2"
rand_core = { version = "0.6", features = ["std"] } rand_core = { version = "0.6", features = ["std"] }
rsa = "0.4" rsa = "0.7"
secrecy = "0.7" secrecy = "0.8"
sha-1 = "0.9" sha1 = { version = "0.10", features = ["oid"] }
sha2 = "0.9" sha2 = { version = "0.10", features = ["oid"] }
subtle = "2" subtle = "2"
subtle-encoding = "0.5" uuid = { version = "1.2", features = ["v4"] }
uuid = { version = "0.8", features = ["v4"] }
x509 = "0.2" x509 = "0.2"
x509-parser = "0.9" x509-parser = "0.14"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.8" env_logger = "0.9"
lazy_static = "1" once_cell = "1"
rsa = { version = "0.7.1", features = ["hazmat"] }
signature = { version = "1.6.4", features = ["hazmat-preview"] }
[features] [features]
untested = [] untested = []
+70 -14
View File
@@ -4,15 +4,19 @@
[![crate][crate-image]][crate-link] [![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link] [![Docs][docs-image]][docs-link]
[![2-Clause BSD Licensed][license-image]][license-link]
![Rust Version][rustc-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link] [![Build Status][build-image]][build-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 cross-platform host-side driver for [YubiKey] devices from [Yubico] Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
with support for public-key encryption and digital signatures using the with support for public-key encryption and digital signatures using the
[Personal Identity Verification (PIV)][PIV] application. [Personal Identity Verification (PIV)][PIV] application.
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
access provided by the [`pcsc` crate].
[Documentation][docs-link] [Documentation][docs-link]
## About ## About
@@ -25,17 +29,44 @@ encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality. on which devices support PIV and the available functionality.
If you've been wanting to use Rust to sign and/or encrypt stuff using a If you've been wanting to use Rust to sign and/or encrypt data using a
private key generated and stored on a Yubikey (with option PIN-based access), private key generated and stored on a YubiKey (with option PIN-based access),
this is the crate you've been after! this is the crate you've been after!
Note that while this project started as a fork of a [Yubico] project, Note that while this project started as a fork of a [Yubico] project,
this fork is **NOT** an official Yubico project and is in no way supported or this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico. endorsed by Yubico.
## Features
### Personal Identity Verification (PIV)
[PIV] is a [NIST] standard for both *signing* and *encryption*
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
PIV-related functionality can be found in the [`piv`] module.
This library natively implements the protocol used to manage and
utilize PIV encryption and signing keys which can be generated, imported,
and stored on YubiKey devices.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**:
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
- ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
## Minimum Supported Rust Version ## Minimum Supported Rust Version
Rust **1.51** or newer. Rust **1.60** or newer.
## Supported YubiKeys ## Supported YubiKeys
@@ -58,6 +89,17 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK! USE AT YOUR OWN RISK!
## Status
Functionality which has been successfully tested is available by default.
Any functionality which is gated on the `untested` feature has not been
properly tested and is not known to function correctly.
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 ## Testing
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
@@ -67,7 +109,7 @@ Tests which run live against a YubiKey device are marked as `#[ignore]` by
default in order to pass when running in a CI environment. To run these default in order to pass when running in a CI environment. To run these
tests locally, invoke the following command: tests locally, invoke the following command:
``` ```shell
cargo test -- --ignored cargo test -- --ignored
``` ```
@@ -76,14 +118,14 @@ information about what is happening. If you'd like to print this logging
information while running the tests, set the `RUST_LOG` environment variable information while running the tests, set the `RUST_LOG` environment variable
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`): to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
``` ```shell
RUST_LOG=info cargo test -- --ignored RUST_LOG=info cargo test -- --ignored
``` ```
To trace every message sent to/from the card i.e. the raw To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level: Application Protocol Data Unit (APDU) messages, use the `trace` log level:
``` ```text
running 1 test running 1 test
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID' [INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully [INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
@@ -109,6 +151,14 @@ Yubico, which was originally written in C. It was mechanically translated
from C into Rust using [Corrode], and then subsequently heavily from C into Rust using [Corrode], and then subsequently heavily
refactored into safer, more idiomatic Rust. refactored into safer, more idiomatic Rust.
For more information on [yubico-piv-tool] and background information on how
the YubiKey implementation of PIV works in general, see the
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
## ⚠️ Security Warning
No security audits of this crate have ever been performed.
## Code of Conduct ## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well. We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -122,7 +172,7 @@ Yubico's [yubico-piv-tool], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][BSDL], which this library inherits was licensed under a [2-Clause BSD License][BSDL], which this library inherits
as a derived work. as a derived work.
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -158,24 +208,29 @@ or conditions.
[//]: # (badges) [//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey.svg [crate-image]: https://buildstats.info/crate/yubikey
[crate-link]: https://crates.io/crates/yubikey [crate-link]: https://crates.io/crates/yubikey
[docs-image]: https://docs.rs/yubikey/badge.svg [docs-image]: https://docs.rs/yubikey/badge.svg
[docs-link]: https://docs.rs/yubikey/ [docs-link]: https://docs.rs/yubikey/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg [license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING [license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg [msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/ [safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push [build-image]: https://github.com/iqlusioninc/yubikey.rs/workflows/CI/badge.svg?branch=main&event=push
[build-link]: https://github.com/iqlusioninc/yubikey.rs/actions [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) [//]: # (general links)
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/ [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/ [Yubico]: https://www.yubico.com/
[PIV]: https://piv.idmanagement.gov/
[NIST]: https://www.nist.gov/
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4 [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/ [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
@@ -184,6 +239,7 @@ or conditions.
[cc-web]: https://contributor-covenant.org/ [cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md [cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause [BSDL]: https://opensource.org/licenses/BSD-2-Clause
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
[//]: # (github issues) [//]: # (github issues)
+31 -1
View File
@@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.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) ## 0.4.0 (2021-07-12)
### Changed ### Changed
- Switch to renamed `yubikey` crate ([#283]) - Switch to renamed `yubikey` crate ([#283])
@@ -30,7 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `status` command ([#72], [#74]) - `status` command ([#72], [#74])
### Changed ### Changed
- Bump `yubikey-piv` to v0.1.0 ([#180]) - Bump `yubikey-piv` to v0.1 ([#180])
- Bump `x509-parser` to v0.8 ([#181]) - Bump `x509-parser` to v0.8 ([#181])
- Bump `sha2` to v0.9 ([#182]) - Bump `sha2` to v0.9 ([#182])
- Rename `list` command to `readers`; improve usage ([#71]) - Rename `list` command to `readers`; improve usage ([#71])
+14 -13
View File
@@ -1,25 +1,26 @@
[package] [package]
name = "yubikey-cli" name = "yubikey-cli"
version = "0.4.0" version = "0.7.0"
description = """ description = """
Command-line interface for performing encryption and signing using RSA/ECC keys Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices. stored on YubiKey devices.
""" """
authors = ["Tony Arcieri <tony@iqlusion.io>"] authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018" license = "BSD-2-Clause"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs" repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md" readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"] categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"] keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
edition = "2021"
rust-version = "1.56"
[dependencies] [dependencies]
gumdrop = "0.8" clap = { version = "4", features = ["derive"] }
env_logger = "0.8" env_logger = "0.9"
lazy_static = "1" hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
log = "0.4" log = "0.4"
sha2 = "0.9" once_cell = "1"
subtle-encoding = "0.5" sha2 = "0.10"
termcolor = "1" termcolor = "1"
x509-parser = "0.9" x509-parser = "0.14"
yubikey = { version = "0.4", path = ".." } yubikey = { version = "0.7", path = ".." }
+3 -7
View File
@@ -18,7 +18,7 @@ utility with general-purpose public-key encryption and signing support.
## Minimum Supported Rust Version ## Minimum Supported Rust Version
Rust **1.51** or newer. Rust **1.60** or newer.
## Supported YubiKeys ## Supported YubiKeys
@@ -35,10 +35,6 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK! USE AT YOUR OWN RISK!
## Status
WIP. Check back later.
## Code of Conduct ## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well. We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -47,7 +43,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License ## License
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -88,7 +84,7 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-cli/badge.svg [docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/ [docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg [license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg [maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/ [safety-link]: https://github.com/rust-secure-code/safety-dance/
+2 -2
View File
@@ -8,9 +8,9 @@
unused_qualifications unused_qualifications
)] )]
use gumdrop::Options; use clap::Parser;
use yubikey_cli::commands::YubiKeyCli; use yubikey_cli::commands::YubiKeyCli;
fn main() { fn main() {
YubiKeyCli::parse_args_default_or_exit().run(); YubiKeyCli::parse().run()
} }
+15 -80
View File
@@ -4,62 +4,25 @@ pub mod readers;
pub mod status; pub mod status;
use self::{readers::ReadersCmd, status::StatusCmd}; use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal::{self, STDOUT}; use crate::terminal;
use gumdrop::Options; use clap::Parser;
use std::{ use std::{env, process::exit};
env, use termcolor::ColorChoice;
io::{self, Write},
process::exit,
};
use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey::{Serial, YubiKey}; use yubikey::{Serial, YubiKey};
/// The `yubikey` CLI utility /// The `yubikey` CLI utility
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct YubiKeyCli { pub struct YubiKeyCli {
/// Obtain help about the current command /// Serial number of the YubiKey to connect to
#[options(short = "h", help = "print help message")] #[clap(short = 's', long = "serial")]
pub help: bool,
/// Specify the serial number of the YubiKey to connect to
#[options(
short = "s",
long = "serial",
help = "serial number of the YubiKey to connect to"
)]
pub serial: Option<Serial>, pub serial: Option<Serial>,
/// Subcommand to execute. /// Subcommand to execute.
#[options(command)] #[clap(subcommand)]
pub command: Option<Commands>, pub command: Commands,
} }
impl YubiKeyCli { impl YubiKeyCli {
/// Print usage information
pub fn print_usage() -> io::Result<()> {
let mut stdout = STDOUT.lock();
stdout.reset()?;
let mut bold = ColorSpec::new();
bold.set_bold(true);
stdout.set_color(&bold)?;
writeln!(
stdout,
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)?;
stdout.reset()?;
writeln!(stdout, "{}", env!("CARGO_PKG_AUTHORS"))?;
writeln!(stdout, "{}", env!("CARGO_PKG_DESCRIPTION").trim())?;
writeln!(stdout)?;
writeln!(stdout, "{}", Commands::usage())?;
Ok(())
}
/// Run the underlying command type or print usage info and exit /// Run the underlying command type or print usage info and exit
pub fn run(&self) { pub fn run(&self) {
// TODO(tarcieri): make this more configurable // TODO(tarcieri): make this more configurable
@@ -70,10 +33,7 @@ impl YubiKeyCli {
env_logger::builder().format_timestamp(None).init(); env_logger::builder().format_timestamp(None).init();
} }
match &self.command { self.command.run(self.yubikey_init())
Some(cmd) => cmd.run(self.yubikey_init()),
None => Self::print_usage().unwrap(),
}
} }
/// Initialize the YubiKey client driver /// Initialize the YubiKey client driver
@@ -92,22 +52,18 @@ impl YubiKeyCli {
} }
/// Subcommands of this application /// Subcommands of this application
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub enum Commands { pub enum Commands {
/// `help` subcommand
#[options(help = "show help for a command")]
Help(HelpOpts),
/// `version` subcommand /// `version` subcommand
#[options(help = "display version information")] #[clap(about = "display version information")]
Version(VersionOpts), Version(VersionOpts),
/// `readers` subcommand /// `readers` subcommand
#[options(help = "list detected readers")] #[clap(about = "list detected readers")]
Readers(ReadersCmd), Readers(ReadersCmd),
/// `status` subcommand /// `status` subcommand
#[options(help = "show yubikey status")] #[clap(about = "show yubikey status")]
Status(StatusCmd), Status(StatusCmd),
} }
@@ -115,7 +71,6 @@ impl Commands {
/// Run the given command /// Run the given command
pub fn run(&self, yubikey: YubiKey) { pub fn run(&self, yubikey: YubiKey) {
match self { match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(), Commands::Version(version) => version.run(),
Commands::Readers(list) => list.run(), Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey), Commands::Status(status) => status.run(yubikey),
@@ -123,28 +78,8 @@ impl Commands {
} }
} }
/// Help options
#[derive(Debug, Options)]
pub struct HelpOpts {
#[options(free, help = "subcommand to get help for")]
free: Vec<String>,
}
impl HelpOpts {
fn run(&self) {
if let Some(command) = self.free.first() {
if let Some(usage) = Commands::command_usage(command) {
println!("{}", usage);
exit(1);
}
}
println!("{}", Commands::usage());
}
}
/// Version options /// Version options
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct VersionOpts {} pub struct VersionOpts {}
impl VersionOpts { impl VersionOpts {
+2 -2
View File
@@ -1,7 +1,7 @@
//! List detected readers //! List detected readers
use crate::terminal::STDOUT; use crate::terminal::STDOUT;
use gumdrop::Options; use clap::Parser;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
process::exit, process::exit,
@@ -10,7 +10,7 @@ use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{Context, Serial}; use yubikey::{Context, Serial};
/// The `readers` subcommand /// The `readers` subcommand
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct ReadersCmd {} pub struct ReadersCmd {}
impl ReadersCmd { impl ReadersCmd {
+2 -2
View File
@@ -1,7 +1,7 @@
//! Print device status //! Print device status
use crate::terminal::{print_cert_info, STDOUT}; use crate::terminal::{print_cert_info, STDOUT};
use gumdrop::Options; use clap::Parser;
use std::io::{self, Write}; use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor}; use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey::{piv::*, YubiKey}; use yubikey::{piv::*, YubiKey};
@@ -10,7 +10,7 @@ use yubikey::{piv::*, YubiKey};
const NONE_STR: &str = "<none>"; const NONE_STR: &str = "<none>";
/// The `status` subcommand /// The `status` subcommand
#[derive(Debug, Options)] #[derive(Debug, Parser)]
pub struct StatusCmd {} pub struct StatusCmd {}
impl StatusCmd { impl StatusCmd {
+20 -17
View File
@@ -1,14 +1,13 @@
//! Status messages //! Status messages
use lazy_static::lazy_static;
use log::debug; use log::debug;
use once_cell::sync::Lazy;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{ use std::{
io::{self, Write}, io::{self, Write},
str, str,
sync::Mutex, sync::Mutex,
}; };
use subtle_encoding::hex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate; use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey}; use yubikey::{certificate::Certificate, piv::*, YubiKey};
@@ -59,16 +58,14 @@ macro_rules! status_err {
}; };
} }
lazy_static! { /// Color configuration
/// Color configuration static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
/// Standard output /// Standard output
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice()); pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
/// Standard error /// Standard error
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice()); pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
}
/// Obtain the color configuration. /// Obtain the color configuration.
/// ///
@@ -140,14 +137,12 @@ impl Status {
/// Print the given message to stdout /// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) { pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg) self.print(&STDOUT, msg).expect("error printing to stdout!")
.expect("error printing to stdout!")
} }
/// Print the given message to stderr /// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) { pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg) self.print(&STDERR, msg).expect("error printing to stderr!")
.expect("error printing to stderr!")
} }
/// Print the given message /// Print the given message
@@ -204,17 +199,25 @@ pub fn print_cert_info(
print_cert_attr( print_cert_attr(
stream, stream,
"Fingerprint", "Fingerprint",
str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(), &hex::upper::encode_string(&fingerprint),
)?; )?;
print_cert_attr( print_cert_attr(
stream, stream,
"Not Before", "Not Before",
cert.tbs_certificate.validity.not_before.to_rfc2822(), cert.tbs_certificate
.validity
.not_before
.to_rfc2822()
.unwrap(),
)?; )?;
print_cert_attr( print_cert_attr(
stream, stream,
"Not After", "Not After",
cert.tbs_certificate.validity.not_after.to_rfc2822(), cert.tbs_certificate
.validity
.not_after
.to_rfc2822()
.unwrap(),
)?; )?;
} }
_ => { _ => {
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+5
View File
@@ -195,6 +195,9 @@ pub enum Ins {
/// Get device serial /// Get device serial
GetSerial, GetSerial,
/// Get slot metadata
GetMetadata,
/// Other/unrecognized instruction codes /// Other/unrecognized instruction codes
Other(u8), Other(u8),
} }
@@ -219,6 +222,7 @@ impl Ins {
Ins::SetPinRetries => 0xfa, Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9, Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8, Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
Ins::Other(code) => code, Ins::Other(code) => code,
} }
} }
@@ -243,6 +247,7 @@ impl From<u8> for Ins {
0xfa => Ins::SetPinRetries, 0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest, 0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial, 0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code), code => Ins::Other(code),
} }
} }
+12 -11
View File
@@ -32,12 +32,7 @@
use crate::{Error, Result, YubiKey}; use crate::{Error, Result, YubiKey};
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use std::{ use std::fmt::{self, Debug, Display};
convert::TryInto,
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
/// CCCID offset /// CCCID offset
const CCC_ID_OFFS: usize = 9; const CCC_ID_OFFS: usize = 9;
@@ -78,9 +73,9 @@ impl CardId {
/// Cardholder Capability Container (CCC) Identifier. /// Cardholder Capability Container (CCC) Identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Ccc(pub [u8; Self::BYTE_SIZE]); pub struct CccId(pub [u8; Self::BYTE_SIZE]);
impl Ccc { impl CccId {
/// CCC size in bytes /// CCC size in bytes
pub const BYTE_SIZE: usize = 51; pub const BYTE_SIZE: usize = 51;
@@ -115,8 +110,14 @@ impl Ccc {
} }
} }
impl Display for Ccc { impl AsRef<[u8]> for CccId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn as_ref(&self) -> &[u8] {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap()) &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()))
} }
} }
+49 -32
View File
@@ -45,11 +45,10 @@ use log::error;
use num_bigint_dig::BigUint; use num_bigint_dig::BigUint;
use p256::NistP256; use p256::NistP256;
use p384::NistP384; use p384::NistP384;
use rsa::{PublicKeyParts, RSAPublicKey}; use rsa::{PublicKeyParts, RsaPublicKey};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::convert::TryFrom; use std::fmt::Display;
use std::fmt; use std::{fmt, ops::DerefMut};
use std::ops::DerefMut;
use x509::{der::Oid, RelativeDistinguishedName}; use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo}; use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing; use zeroize::Zeroizing;
@@ -85,7 +84,7 @@ impl TryFrom<&[u8]> for Serial {
fn try_from(bytes: &[u8]) -> Result<Serial> { fn try_from(bytes: &[u8]) -> Result<Serial> {
if bytes.len() <= 20 { if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(&bytes))) Ok(Serial(BigUint::from_bytes_be(bytes)))
} else { } else {
Err(Error::ParseError) Err(Error::ParseError)
} }
@@ -96,10 +95,30 @@ impl Serial {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be() 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. /// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CertInfo { pub enum CertInfo {
/// The certificate is uncompressed. /// The certificate is uncompressed.
Uncompressed, Uncompressed,
@@ -172,7 +191,7 @@ pub enum PublicKeyInfo {
algorithm: AlgorithmId, algorithm: AlgorithmId,
/// Public key /// Public key
pubkey: RSAPublicKey, pubkey: RsaPublicKey,
}, },
/// EC P-256 keys /// EC P-256 keys
@@ -192,7 +211,7 @@ impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> { fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() { match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => { OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?; let pubkey = read_pki::rsa_pubkey(&subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa { Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() { algorithm: match pubkey.n().bits() {
@@ -212,10 +231,10 @@ impl PublicKeyInfo {
.ok_or(Error::InvalidObject)?; .ok_or(Error::InvalidObject)?;
match read_pki::ec_parameters(algorithm_parameters)? { match read_pki::ec_parameters(algorithm_parameters)? {
AlgorithmId::EccP256 => EcPublicKey::from_bytes(key_bytes) AlgorithmId::EccP256 => EcPublicKey::<NistP256>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256) .map(PublicKeyInfo::EcP256)
.map_err(|_| Error::InvalidObject), .map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::from_bytes(key_bytes) AlgorithmId::EccP384 => EcPublicKey::<NistP384>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384) .map(PublicKeyInfo::EcP384)
.map_err(|_| Error::InvalidObject), .map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError), _ => Err(Error::AlgorithmError),
@@ -320,6 +339,7 @@ impl x509::AlgorithmIdentifier for SignatureId {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate { pub struct Certificate {
serial: Serial, serial: Serial,
#[allow(dead_code)]
issuer: String, issuer: String,
subject: String, subject: String,
subject_pki: PublicKeyInfo, subject_pki: PublicKeyInfo,
@@ -364,12 +384,12 @@ impl Certificate {
&serial.to_bytes(), &serial.to_bytes(),
&signature_algorithm, &signature_algorithm,
// Issuer and subject are the same in self-signed certificates. // Issuer and subject are the same in self-signed certificates.
&subject, subject,
Utc::now(), Utc::now(),
not_after, not_after,
&subject, subject,
&subject_pki, &subject_pki,
&extensions, extensions,
), ),
tbs_cert.deref_mut(), tbs_cert.deref_mut(),
) )
@@ -513,7 +533,7 @@ impl Certificate {
/// Returns the Issuer field of the certificate. /// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str { pub fn issuer(&self) -> &str {
&self.subject &self.issuer
} }
/// Returns the SubjectName field of the certificate. /// Returns the SubjectName field of the certificate.
@@ -588,13 +608,13 @@ pub(crate) fn write_certificate(
mod read_pki { mod read_pki {
use der_parser::{ use der_parser::{
asn1_rs::Any,
ber::BerObjectContent, ber::BerObjectContent,
der::{parse_der_integer, DerObject}, der::{parse_der_integer, parse_der_sequence_defined_g, DerObject},
error::BerError, error::BerError,
*,
}; };
use nom::{combinator, IResult}; use nom::{combinator, sequence::pair, IResult};
use rsa::{BigUint, RSAPublicKey}; use rsa::{BigUint, RsaPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384}; use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{piv::AlgorithmId, Error, Result}; use crate::{piv::AlgorithmId, Error, Result};
@@ -606,21 +626,18 @@ mod read_pki {
/// publicExponent INTEGER -- e /// publicExponent INTEGER -- e
/// } /// }
/// ``` /// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey> { pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> { fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> {
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer) parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i)
} }
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> { fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |object| { combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| {
let seq = object.as_sequence().expect("is DER sequence"); let n = match modulus.content {
assert_eq!(seq.len(), 2);
let n = match seq[0].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s), BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"), _ => panic!("expected DER integer"),
}; };
let e = match seq[1].content { let e = match public_exponent.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s), BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"), _ => panic!("expected DER integer"),
}; };
@@ -634,7 +651,7 @@ mod read_pki {
_ => return Err(Error::InvalidObject), _ => return Err(Error::InvalidObject),
}; };
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject) RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
} }
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1): /// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
@@ -645,8 +662,8 @@ mod read_pki {
/// -- specifiedCurve SpecifiedECDomain /// -- specifiedCurve SpecifiedECDomain
/// } /// }
/// ``` /// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId> { pub(super) fn ec_parameters(parameters: &Any<'_>) -> Result<AlgorithmId> {
let curve_oid = parameters.as_oid_val().map_err(|_| Error::InvalidObject)?; let curve_oid = parameters.as_oid().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() { match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256), OID_NIST_P256 => Ok(AlgorithmId::EccP256),
@@ -658,7 +675,7 @@ mod read_pki {
mod write_pki { mod write_pki {
use cookie_factory::{SerializeFn, WriteContext}; use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RSAPublicKey}; use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
use std::io::Write; use std::io::Write;
use x509::der::write::{der_integer, der_sequence}; use x509::der::write::{der_integer, der_sequence};
@@ -675,7 +692,7 @@ mod write_pki {
/// } /// }
/// ``` /// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>( pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RSAPublicKey, pubkey: &'a RsaPublicKey,
) -> impl SerializeFn<W> + 'a { ) -> impl SerializeFn<W> + 'a {
der_sequence(( der_sequence((
der_integer_biguint(pubkey.n()), der_integer_biguint(pubkey.n()),
+10 -9
View File
@@ -31,12 +31,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{Error, Result, YubiKey}; use crate::{Error, Result, YubiKey};
use std::{ use std::fmt::{self, Debug, Display};
convert::TryInto,
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
use uuid::Uuid; use uuid::Uuid;
/// FASC-N offset /// FASC-N offset
@@ -131,8 +126,14 @@ impl ChuId {
} }
} }
impl Display for ChuId { impl AsRef<[u8]> for ChuId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn as_ref(&self) -> &[u8] {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap()) &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()))
} }
} }
+1 -4
View File
@@ -41,10 +41,7 @@ use crate::{
Result, Result,
}; };
use log::error; use log::error;
use std::{ use std::time::{Duration, SystemTime, UNIX_EPOCH};
convert::TryInto,
time::{Duration, SystemTime, UNIX_EPOCH},
};
const CB_ADMIN_TIMESTAMP: usize = 0x04; const CB_ADMIN_TIMESTAMP: usize = 0x04;
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01; const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
+24 -18
View File
@@ -121,31 +121,37 @@ impl Error {
} }
/// Error message /// Error message
pub fn msg(self) -> &'static str { pub fn msg(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Error::AlgorithmError => "algorithm error", Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => "applet error", Error::AppletError => f.write_str("applet error"),
Error::ArgumentError => "argument error", Error::ArgumentError => f.write_str("argument error"),
Error::AuthenticationError => "authentication error", Error::AuthenticationError => f.write_str("authentication error"),
Error::GenericError => "generic error", Error::GenericError => f.write_str("generic error"),
Error::InvalidObject => "invalid object", Error::InvalidObject => f.write_str("invalid object"),
Error::KeyError => "key error", Error::KeyError => f.write_str("key error"),
Error::MemoryError => "memory error", Error::MemoryError => f.write_str("memory error"),
Error::NotSupported => "not supported", Error::NotSupported => f.write_str("not supported"),
Error::NotFound => "not found", Error::NotFound => f.write_str("not found"),
Error::ParseError => "parse error", Error::ParseError => f.write_str("parse error"),
Error::PcscError { .. } => "PC/SC error",
Error::PinLocked => "PIN locked", Error::PcscError {
Error::RangeError => "range error", inner: Some(pcsc_error),
Error::SizeError => "size error", } => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)),
Error::WrongPin { .. } => "wrong pin",
Error::PcscError { .. } => f.write_str("PC/SC error"),
Error::PinLocked => f.write_str("PIN locked"),
Error::RangeError => f.write_str("range error"),
Error::SizeError => f.write_str("size error"),
Error::WrongPin { .. } => f.write_str("wrong pin"),
} }
} }
} }
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg()) self.msg(f)
} }
} }
+11 -111
View File
@@ -1,102 +1,10 @@
//! **yubikey.rs**: pure Rust cross-platform host-side driver for [YubiKey] #![doc = include_str!("../README.md")]
//! devices from [Yubico] using the Personal Computer/Smart Card ([PC/SC]) #![doc(
//! interface as provided by the [`pcsc` crate]. html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
//! )]
//! # Features #![cfg_attr(docsrs, feature(doc_cfg))]
//! ## Personal Identity Verification (PIV) #![forbid(unsafe_code)]
//! [PIV] is a [NIST] standard for both *signing* and *encryption* #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
//! 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.
//!
//! # Minimum Supported Rust Version
//! Rust **1.51** or newer.
//!
//! # Supported YubiKeys
//! - [YubiKey 4] series
//! - [YubiKey 5] series
//!
//! NOTE: Nano and USB-C variants of the above are also supported.
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
//!
//! # Supported Operating Systems
//! - Linux
//! - macOS
//! - Windows
//!
//! # 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)
//!
//! # Status
//! Functionality which has been successfully tested is available by default.
//!
//! Any functionality which is gated on the `untested` feature has not been
//! properly tested and is not known to function correctly.
//!
//! Please see the [`untested` functionality tracking issue] for current status.
//! We would appreciate any help testing this functionality and removing the
//! `untested` gating as well as writing more automated tests.
//!
//! # History
//! This library is a Rust translation of the [yubico-piv-tool] utility by
//! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode], and then subsequently heavily
//! 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.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/
//! [`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
//! [cc-web]: https://contributor-covenant.org/
//! [cc-md]: https://github.com/iqlusioninc/yubikey.rs/blob/main/CODE_OF_CONDUCT.md
//! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/> // <https://github.com/Yubico/yubico-piv-tool/>
@@ -128,14 +36,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png",
html_root_url = "https://docs.rs/yubikey/0.4.0"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
mod apdu; mod apdu;
mod cccid; mod cccid;
pub mod certificate; pub mod certificate;
@@ -153,12 +53,12 @@ pub mod piv;
mod policy; mod policy;
pub mod reader; pub mod reader;
mod serialization; mod serialization;
mod settings; mod setting;
mod transaction; mod transaction;
mod yubikey; mod yubikey;
pub use crate::{ pub use crate::{
cccid::{CardId, Ccc}, cccid::{CardId, CccId},
certificate::Certificate, certificate::Certificate,
chuid::ChuId, chuid::ChuId,
config::Config, config::Config,
@@ -167,7 +67,7 @@ pub use crate::{
piv::Key, piv::Key,
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
reader::Context, reader::Context,
settings::{SettingSource, SettingValue}, setting::{Setting, SettingSource},
yubikey::{CachedPin, Serial, Version, YubiKey}, yubikey::{CachedPin, Serial, Version, YubiKey},
}; };
@@ -180,4 +80,4 @@ pub use uuid::Uuid;
pub type ObjectId = u32; pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector) /// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>; pub type Buffer = zeroize::Zeroizing<Vec<u8>>;
+4 -5
View File
@@ -33,7 +33,6 @@
use crate::{Error, Result}; use crate::{Error, Result};
use log::error; use log::error;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use std::convert::{TryFrom, TryInto};
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -43,7 +42,7 @@ use crate::{
yubikey::YubiKey, yubikey::YubiKey,
}; };
use des::{ use des::{
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, NewBlockCipher}, cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit},
TdesEde3, TdesEde3,
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -138,7 +137,7 @@ impl MgmKey {
} }
let mut mgm = [0u8; DES_LEN_3DES]; let mut mgm = [0u8; DES_LEN_3DES];
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm); pbkdf2::<Hmac<Sha1>>(pin, salt, ITER_MGM_PBKDF2, &mut mgm);
MgmKey::from_bytes(mgm) MgmKey::from_bytes(mgm)
} }
@@ -191,7 +190,7 @@ impl MgmKey {
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> { pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(&self, require_touch).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 // Log a warning, since the device mgm key is corrupt or we're in a state
// where we can't set the mgm key. // where we can't set the mgm key.
error!("could not set new derived mgm key, err = {}", e); error!("could not set new derived mgm key, err = {}", e);
@@ -405,7 +404,7 @@ fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
c = (c & 0x0F) + ((c >> 4) & 0x0F); c = (c & 0x0F) + ((c >> 4) & 0x0F);
// if count is even, set low key bit to 1, otherwise 0 // if count is even, set low key bit to 1, otherwise 0
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 }); tmp[i] = (key[i] & 0xFE) | u8::from(c & 0x01 != 0x01);
} }
// check odd parity key against table by DES key block // check odd parity key against table by DES key block
-1
View File
@@ -32,7 +32,6 @@
use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey}; use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
use log::error; use log::error;
use std::convert::{TryFrom, TryInto};
const OBJ_MSCMAP: u32 = 0x005f_ff10; const OBJ_MSCMAP: u32 = 0x005f_ff10;
+2 -2
View File
@@ -111,7 +111,7 @@ impl MsRoots {
let mut data_chunk: usize; let mut data_chunk: usize;
let data = &self.0; let data = &self.0;
let data_len = data.len(); let data_len = data.len();
let n_objs: usize;
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
if data_len == 0 { if data_len == 0 {
@@ -119,7 +119,7 @@ impl MsRoots {
} }
// Calculate number of objects required to store blob // Calculate number of objects required to store blob
n_objs = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1; let n_objs: usize = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 { if n_objs > 5 {
return Err(Error::SizeError); return Err(Error::SizeError);
+447 -104
View File
@@ -45,21 +45,26 @@
use crate::{ use crate::{
apdu::{Ins, StatusWords}, apdu::{Ins, StatusWords},
certificate::{self, Certificate, PublicKeyInfo}, certificate::{self, Certificate, PublicKeyInfo},
consts::CB_OBJ_MAX,
error::{Error, Result}, error::{Error, Result},
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
serialization::*, serialization::*,
settings, setting,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, ObjectId, Buffer, ObjectId,
}; };
use elliptic_curve::sec1::EncodedPoint as EcPublicKey; use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::{debug, error, warn}; use log::{debug, error, warn};
use rsa::{BigUint, RSAPublicKey}; use p256::NistP256;
use std::{convert::TryFrom, str::FromStr}; use p384::NistP384;
use rsa::{BigUint, RsaPublicKey};
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use { use {
crate::consts::CB_OBJ_MAX,
num_bigint_dig::traits::ModInverse, num_bigint_dig::traits::ModInverse,
num_integer::Integer, num_integer::Integer,
num_traits::{FromPrimitive, One}, num_traits::{FromPrimitive, One},
@@ -83,7 +88,7 @@ const KEYDATA_RSA_EXP: u64 = 65537;
/// Slot identifiers. /// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html> /// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub enum SlotId { pub enum SlotId {
/// This certificate and its associated private key is used to authenticate the card /// This certificate and its associated private key is used to authenticate the card
/// and the cardholder. This slot is used for things like system login. The end user /// and the cardholder. This slot is used for things like system login. The end user
@@ -121,6 +126,9 @@ pub enum SlotId {
/// attestation of other keys generated on device with instruction `f9`. This slot is /// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten. /// not cleared on reset, but can be overwritten.
Attestation, Attestation,
/// Thse slots are used for management. PIN PUK and Management Key.
Management(ManagementSlotId),
} }
impl TryFrom<u8> for SlotId { impl TryFrom<u8> for SlotId {
@@ -133,7 +141,9 @@ impl TryFrom<u8> for SlotId {
0x9d => Ok(SlotId::KeyManagement), 0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication), 0x9e => Ok(SlotId::CardAuthentication),
0xf9 => Ok(SlotId::Attestation), 0xf9 => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(value).map(SlotId::Retired), _ => RetiredSlotId::try_from(value)
.map(SlotId::Retired)
.or_else(|_| ManagementSlotId::try_from(value).map(SlotId::Management)),
} }
} }
} }
@@ -147,6 +157,17 @@ impl From<SlotId> for u8 {
SlotId::CardAuthentication => 0x9e, SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(), SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9, SlotId::Attestation => 0xf9,
SlotId::Management(mgmt) => mgmt.into(),
}
}
}
impl Display for SlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SlotId::Management(r) => write!(f, "{:?}", r),
SlotId::Retired(r) => write!(f, "{:?}", r),
_ => write!(f, "{:?}", self),
} }
} }
} }
@@ -161,7 +182,10 @@ impl FromStr for SlotId {
"9d" => Ok(SlotId::KeyManagement), "9d" => Ok(SlotId::KeyManagement),
"9e" => Ok(SlotId::CardAuthentication), "9e" => Ok(SlotId::CardAuthentication),
"f9" => Ok(SlotId::Attestation), "f9" => Ok(SlotId::Attestation),
_ => s.parse().map(SlotId::Retired), _ => s
.parse()
.map(SlotId::Management)
.or_else(|_| s.parse().map(SlotId::Retired)),
} }
} }
} }
@@ -175,6 +199,7 @@ impl SlotId {
SlotId::KeyManagement => 0x005f_c10b, SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101, SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(), SlotId::Retired(retired) => retired.object_id(),
SlotId::Management(mgmt) => mgmt.object_id(),
SlotId::Attestation => 0x005f_ff01, SlotId::Attestation => 0x005f_ff01,
} }
} }
@@ -182,7 +207,7 @@ impl SlotId {
/// Retired slot IDs. /// Retired slot IDs.
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum RetiredSlotId { pub enum RetiredSlotId {
R1, R1,
R2, R2,
@@ -293,6 +318,12 @@ impl From<RetiredSlotId> for u8 {
} }
} }
impl Display for RetiredSlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl RetiredSlotId { impl RetiredSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`]. /// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
pub(crate) fn object_id(self) -> ObjectId { pub(crate) fn object_id(self) -> ObjectId {
@@ -321,8 +352,90 @@ impl RetiredSlotId {
} }
} }
/// Management slot IDs.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub enum ManagementSlotId {
/// Personal Identification Number (PIN).
Pin,
/// PIN Unblocking Key (PUK).
Puk,
/// Management Key.
Management,
}
impl TryFrom<u8> for ManagementSlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0x80 => Ok(ManagementSlotId::Pin),
0x81 => Ok(ManagementSlotId::Puk),
0x9b => Ok(ManagementSlotId::Management),
_ => Err(Error::InvalidObject),
}
}
}
impl FromStr for ManagementSlotId {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
match value {
"80" => Ok(ManagementSlotId::Pin),
"81" => Ok(ManagementSlotId::Puk),
"9b" => Ok(ManagementSlotId::Management),
_ => Err(Error::InvalidObject),
}
}
}
impl From<ManagementSlotId> for u8 {
fn from(slot: ManagementSlotId) -> u8 {
match slot {
ManagementSlotId::Pin => 0x80,
ManagementSlotId::Puk => 0x81,
ManagementSlotId::Management => 0x9b,
}
}
}
impl Display for ManagementSlotId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl ManagementSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`ManagementSlotId`].
///
/// These correspond to the "BER-TLV Tag" values from Table 3 of [NIST SP 800-73-4]:
/// "Object Identifiers of the PIV Data Objects for Interoperable Use".
///
/// [NIST SP 800-73-4]: https://csrc.nist.gov/publications/detail/sp/800-73/4/final
pub(crate) fn object_id(self) -> ObjectId {
match self {
// Data Object: "X.509 Certificate for Key Management"
// OID: 2.16.840.1.101.3.7.2.1.2
// BER-TLV Tag: '5FC10B'
ManagementSlotId::Pin => 0x005f_c10b,
// Data Object: "Key History Object"
// OID: 2.16.840.1.101.3.7.2.96.96
// BER-TLV Tag: '5FC10C'
ManagementSlotId::Puk => 0x005f_c10c,
// Data Object: "Retired X.509 Certificate for Key Management 3"
// OID: 2.16.840.1.101.3.7.2.16.3
// BER-TLV Tag: '5FC10F'
ManagementSlotId::Management => 0x005f_c10f,
}
}
}
/// Personal Identity Verification (PIV) key slots /// Personal Identity Verification (PIV) key slots
pub const SLOTS: [SlotId; 24] = [ pub const SLOTS: [SlotId; 27] = [
SlotId::Authentication, SlotId::Authentication,
SlotId::Signature, SlotId::Signature,
SlotId::KeyManagement, SlotId::KeyManagement,
@@ -347,6 +460,9 @@ pub const SLOTS: [SlotId; 24] = [
SlotId::Retired(RetiredSlotId::R19), SlotId::Retired(RetiredSlotId::R19),
SlotId::Retired(RetiredSlotId::R20), SlotId::Retired(RetiredSlotId::R20),
SlotId::CardAuthentication, SlotId::CardAuthentication,
SlotId::Management(ManagementSlotId::Pin),
SlotId::Management(ManagementSlotId::Puk),
SlotId::Management(ManagementSlotId::Management),
]; ];
/// Algorithm identifiers /// Algorithm identifiers
@@ -481,7 +597,7 @@ pub fn generate(
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator 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."; const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
let setting_roca: settings::SettingValue; let setting_roca: setting::Setting;
match algorithm { match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
@@ -489,17 +605,17 @@ pub fn generate(
&& (yubikey.version.minor < 3 && (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5)) || yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{ {
setting_roca = settings::SettingValue::get(SZ_SETTING_ROCA, true); setting_roca = setting::Setting::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source { let psz_msg = match setting_roca.source {
settings::SettingSource::User => { setting::SettingSource::User => {
if setting_roca.value { if setting_roca.value {
SZ_ROCA_ALLOW_USER SZ_ROCA_ALLOW_USER
} else { } else {
SZ_ROCA_BLOCK_USER SZ_ROCA_BLOCK_USER
} }
} }
settings::SettingSource::Admin => { setting::SettingSource::Admin => {
if setting_roca.value { if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN SZ_ROCA_ALLOW_ADMIN
} else { } else {
@@ -574,97 +690,8 @@ pub fn generate(
} }
} }
// TODO(str4d): Response is wrapped in an ASN.1 TLV: let value = response.data();
// read_public_key(algorithm, value, true)
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
// - Use a full ASN.1 parser on the entire datastructure:
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
//
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
// parsing (which treats 128 as a single-byte definite length instead of an
// indefinite length):
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | |0x81|len128| modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | |0x81| len:256 | modulus |0x82|len3| exp |
//
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
// 3-byte length.
let data = &response.data()[5..];
let (data, modulus_tlv) = Tlv::parse(data)?;
if modulus_tlv.tag != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
let modulus = modulus_tlv.value.to_vec();
let (_, exp_tlv) = Tlv::parse(data)?;
if exp_tlv.tag != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
let exp = exp_tlv.value.to_vec();
Ok(PublicKeyInfo::Rsa {
algorithm,
pubkey: RSAPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
)
.map_err(|_| Error::InvalidObject)?,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
let data = &response.data()[3..];
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
let (_, tlv) = Tlv::parse(data)?;
if tlv.tag != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
// the curve point should always be determined by the curve
if tlv.value.len() != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = tlv.value.to_vec();
if let AlgorithmId::EccP256 = algorithm {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP256)
} else {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP384)
}
.map_err(|_| Error::InvalidObject)
}
}
} }
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -891,3 +918,319 @@ pub fn decrypt_data(
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true) txn.authenticated_command(input, algorithm, key, true)
} }
/// Read metadata
pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result<SlotMetadata> {
let txn = yubikey.begin_transaction()?;
let templ = [0, Ins::GetMetadata.code(), 0, slot.into()];
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported); // Requires firmware 5.2.3
} else {
return Err(Error::GenericError);
}
}
let buf = Buffer::new(response.data().into());
SlotMetadata::try_from(buf)
}
/// Metadata from a slot
#[derive(Debug)]
pub struct SlotMetadata {
/// Algorithm / Type of key
pub algorithm: ManagementAlgorithmId,
/// PIN and touch policy
pub policy: Option<(PinPolicy, TouchPolicy)>,
/// Imported or generated key
pub origin: Option<Origin>,
/// Pub key of the key
pub public: Option<PublicKeyInfo>,
/// Whether PIN PUK and management key are default
pub default: Option<bool>,
/// Number of retries left
pub retries: Option<Retries>,
}
impl TryFrom<Buffer> for SlotMetadata {
type Error = Error;
fn try_from(buf: Buffer) -> Result<Self> {
use nom::{
combinator::{eof, map_res},
multi::fold_many1,
number::complete::u8,
};
let out = fold_many1(
|input| Tlv::parse(input).map_err(|_| nom::Err::Error(())),
|| {
Ok(SlotMetadata {
algorithm: ManagementAlgorithmId::PinPuk,
policy: None,
origin: None,
public: None,
default: None,
retries: None,
})
},
|acc: Result<SlotMetadata>, tlv| match acc {
Ok(mut metadata) => match tlv.tag {
1 => {
metadata.algorithm = ManagementAlgorithmId::try_from(tlv.value[0])?;
Ok(metadata)
}
2 => {
fn policy_parser(
i: &[u8],
) -> nom::IResult<&[u8], (PinPolicy, TouchPolicy)> {
let (i, pin) = map_res(u8, PinPolicy::try_from)(i)?;
let (i, touch) = map_res(u8, TouchPolicy::try_from)(i)?;
let (i, _) = eof(i)?;
Ok((i, (pin, touch)))
}
metadata.policy =
Some(policy_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(metadata)
}
3 => {
fn origin_parser(i: &[u8]) -> nom::IResult<&[u8], Origin> {
let (i, origin) = map_res(u8, Origin::try_from)(i)?;
let (i, _) = eof(i)?;
Ok((i, origin))
}
metadata.origin =
Some(origin_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(metadata)
}
4 => {
match metadata.algorithm {
ManagementAlgorithmId::Asymmetric(alg) => {
metadata.public = Some(read_public_key(alg, tlv.value, false)?);
}
_ => Err(Error::ParseError)?,
}
Ok(metadata)
}
5 => {
fn default_parser(i: &[u8]) -> nom::IResult<&[u8], bool> {
let (i, default) = u8(i)?;
let (i, _) = eof(i)?;
Ok((i, default == 1))
}
metadata.default =
Some(default_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(metadata)
}
6 => {
fn retries_parser(i: &[u8]) -> nom::IResult<&[u8], Retries> {
let (i, retry_count) = u8(i)?;
let (i, remaining_count) = u8(i)?;
let (i, _) = eof(i)?;
Ok((
i,
Retries {
retry_count,
remaining_count,
},
))
}
metadata.retries =
Some(retries_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(metadata)
}
_unsupported => {
// New unsupported tags
// https://docs.yubico.com/yesdk/users-manual/application-piv/apdu/metadata.html
Ok(metadata)
}
},
err => err,
},
)(buf.as_ref());
match out {
Ok((_, res)) => res,
_ => Err(Error::ParseError),
}
}
}
/// The number of retries used and remaining.
#[derive(Debug, PartialEq, Eq)]
pub struct Retries {
/// TODO
pub retry_count: u8,
/// Remaining attempts
pub remaining_count: u8,
}
/// Origin of a slot
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Origin {
/// The key has been imported
Imported,
/// The key has been generated on the YubiKey
Generated,
}
impl TryFrom<u8> for Origin {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
1 => Ok(Origin::Generated),
2 => Ok(Origin::Imported),
_ => Err(Error::GenericError),
}
}
}
fn read_public_key(
algorithm: AlgorithmId,
input: &[u8],
skip_asn1_tag: bool,
) -> Result<PublicKeyInfo> {
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
//
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
// - Use a full ASN.1 parser on the entire datastructure:
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
//
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
// parsing (which treats 128 as a single-byte definite length instead of an
// indefinite length):
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | |0x81|len128| modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | |0x81| len:256 | modulus |0x82|len3| exp |
//
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
// 3-byte length.
let data = if skip_asn1_tag { &input[5..] } else { input };
let (data, modulus_tlv) = Tlv::parse(data)?;
if modulus_tlv.tag != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
let modulus = modulus_tlv.value.to_vec();
let (_, exp_tlv) = Tlv::parse(data)?;
if exp_tlv.tag != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
let exp = exp_tlv.value.to_vec();
Ok(PublicKeyInfo::Rsa {
algorithm,
pubkey: RsaPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
)
.map_err(|_| Error::InvalidObject)?,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
let data = if skip_asn1_tag { &input[3..] } else { input };
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
let (_, tlv) = Tlv::parse(data)?;
if tlv.tag != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
// the curve point should always be determined by the curve
if tlv.value.len() != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = tlv.value.to_vec();
match algorithm {
AlgorithmId::EccP256 => {
EcPublicKey::<NistP256>::from_bytes(point).map(PublicKeyInfo::EcP256)
}
AlgorithmId::EccP384 => {
EcPublicKey::<NistP384>::from_bytes(point).map(PublicKeyInfo::EcP384)
}
_ => return Err(Error::AlgorithmError),
}
.map_err(|_| Error::InvalidObject)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// Algorithms as reported by the metadata command.
pub enum ManagementAlgorithmId {
/// Used on PIN and PUK slots.
PinPuk,
/// Used on the key management slot.
ThreeDes,
/// Used on all other slots.
Asymmetric(AlgorithmId),
}
impl TryFrom<u8> for ManagementAlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0xff => Ok(ManagementAlgorithmId::PinPuk),
0x03 => Ok(ManagementAlgorithmId::ThreeDes),
oth => AlgorithmId::try_from(oth).map(ManagementAlgorithmId::Asymmetric),
}
}
}
impl From<ManagementAlgorithmId> for u8 {
fn from(id: ManagementAlgorithmId) -> u8 {
match id {
ManagementAlgorithmId::PinPuk => 0xff,
ManagementAlgorithmId::ThreeDes => 0x03,
ManagementAlgorithmId::Asymmetric(oth) => oth.into(),
}
}
}
+31 -3
View File
@@ -1,12 +1,12 @@
//! Enums representing key policies. //! Enums representing key policies.
use crate::{serialization::Tlv, Result}; use crate::{serialization::Tlv, Error, Result};
/// Specifies how often the PIN needs to be entered for access to the credential in a /// Specifies how often the PIN needs to be entered for access to the credential in a
/// given slot. /// given slot.
/// ///
/// This policy must be set when keys are generated or imported, and cannot be changed later. /// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PinPolicy { pub enum PinPolicy {
/// Use the default PIN policy for the slot. See the slot's documentation for details. /// Use the default PIN policy for the slot. See the slot's documentation for details.
Default, Default,
@@ -35,6 +35,20 @@ impl From<PinPolicy> for u8 {
} }
} }
impl TryFrom<u8> for PinPolicy {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(PinPolicy::Default),
1 => Ok(PinPolicy::Never),
2 => Ok(PinPolicy::Once),
3 => Ok(PinPolicy::Always),
_ => Err(Error::GenericError),
}
}
}
impl PinPolicy { impl PinPolicy {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or /// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation. /// importation.
@@ -50,7 +64,7 @@ impl PinPolicy {
/// addition to the [`PinPolicy`]. /// addition to the [`PinPolicy`].
/// ///
/// This policy must be set when keys are generated or imported, and cannot be changed later. /// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TouchPolicy { pub enum TouchPolicy {
/// Use the default touch policy for the slot. /// Use the default touch policy for the slot.
Default, Default,
@@ -90,3 +104,17 @@ impl TouchPolicy {
} }
} }
} }
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),
}
}
}
+1 -1
View File
@@ -3,7 +3,6 @@
use crate::{Result, YubiKey}; use crate::{Result, YubiKey};
use std::{ use std::{
borrow::Cow, borrow::Cow,
convert::TryInto,
ffi::CStr, ffi::CStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@@ -45,6 +44,7 @@ impl Context {
c.list_readers(reader_names)?.collect() c.list_readers(reader_names)?.collect()
}; };
#[allow(clippy::needless_collect)]
let readers: Vec<_> = reader_cstrs let readers: Vec<_> = reader_cstrs
.iter() .iter()
.map(|name| Reader::new(name, Arc::clone(ctx))) .map(|name| Reader::new(name, Arc::clone(ctx)))
+7 -16
View File
@@ -65,8 +65,8 @@ impl Default for SettingSource {
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a /// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment /// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
/// variables. /// variables.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Default)]
pub struct SettingValue { pub struct Setting {
/// Boolean value /// Boolean value
pub value: bool, pub value: bool,
@@ -74,8 +74,8 @@ pub struct SettingValue {
pub source: SettingSource, pub source: SettingSource,
} }
impl SettingValue { impl Setting {
/// Get a [`SettingValue`] value by name. /// Get a setting by name.
pub fn get(key: &str, default: bool) -> Self { pub fn get(key: &str, default: bool) -> Self {
Self::from_file(key) Self::from_file(key)
.or_else(|| Self::from_env(key)) .or_else(|| Self::from_env(key))
@@ -99,7 +99,7 @@ impl SettingValue {
} }
let (name, value) = { let (name, value) = {
let mut parts = line.splitn(1, '='); let mut parts = line.splitn(2, '=');
let name = parts.next(); let name = parts.next();
let value = parts.next(); let value = parts.next();
match (name, value, parts.next()) { match (name, value, parts.next()) {
@@ -109,7 +109,7 @@ impl SettingValue {
}; };
if name == key { if name == key {
return Some(SettingValue { return Some(Setting {
source: SettingSource::Admin, source: SettingSource::Admin,
value: value == "1" || value == "true", value: value == "1" || value == "true",
}); });
@@ -124,18 +124,9 @@ impl SettingValue {
fn from_env(key: &str) -> Option<Self> { fn from_env(key: &str) -> Option<Self> {
env::var(format!("YUBIKEY_PIV_{}", key)) env::var(format!("YUBIKEY_PIV_{}", key))
.ok() .ok()
.map(|value| SettingValue { .map(|value| Setting {
source: SettingSource::User, source: SettingSource::User,
value: value == "1" || value == "true", value: value == "1" || value == "true",
}) })
} }
} }
impl Default for SettingValue {
fn default() -> Self {
Self {
value: false,
source: SettingSource::default(),
}
}
}
+4 -5
View File
@@ -11,7 +11,6 @@ use crate::{
Buffer, ObjectId, Buffer, ObjectId,
}; };
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto;
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -70,7 +69,7 @@ impl<'tx> Transaction<'tx> {
pub fn select_application(&self) -> Result<()> { pub fn select_application(&self) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication) let response = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&PIV_AID) .data(PIV_AID)
.transmit(self, 0xFF) .transmit(self, 0xFF)
.map_err(|e| { .map_err(|e| {
error!("failed communicating with card: '{}'", e); error!("failed communicating with card: '{}'", e);
@@ -110,7 +109,7 @@ impl<'tx> Transaction<'tx> {
// YK4 requires switching to the yk applet to retrieve the serial // YK4 requires switching to the yk applet to retrieve the serial
let sw = Apdu::new(Ins::SelectApplication) let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&YK_AID) .data(YK_AID)
.transmit(self, 0xFF)? .transmit(self, 0xFF)?
.status_words(); .status_words();
@@ -132,7 +131,7 @@ impl<'tx> Transaction<'tx> {
// reselect the PIV applet // reselect the PIV applet
let sw = Apdu::new(Ins::SelectApplication) let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(&PIV_AID) .data(PIV_AID)
.transmit(self, 0xFF)? .transmit(self, 0xFF)?
.status_words(); .status_words();
@@ -247,7 +246,7 @@ impl<'tx> Transaction<'tx> {
let status_words = Apdu::new(Ins::SetMgmKey) let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2) .params(0xff, p2)
.data(&data) .data(data)
.transmit(self, 261)? .transmit(self, 261)?
.status_words(); .status_words();
+9 -10
View File
@@ -32,7 +32,7 @@
use crate::{ use crate::{
apdu::{Apdu, Ins}, apdu::{Apdu, Ins},
cccid::Ccc, cccid::CccId,
chuid::ChuId, chuid::ChuId,
config::Config, config::Config,
error::{Error, Result}, error::{Error, Result},
@@ -45,7 +45,6 @@ use log::{error, info};
use pcsc::Card; use pcsc::Card;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use std::{ use std::{
convert::{TryFrom, TryInto},
fmt::{self, Display}, fmt::{self, Display},
str::FromStr, str::FromStr,
}; };
@@ -275,8 +274,8 @@ impl YubiKey {
} }
/// Get Cardholder Capability Container (CCC) Identifier. /// Get Cardholder Capability Container (CCC) Identifier.
pub fn cccid(&mut self) -> Result<Ccc> { pub fn cccid(&mut self) -> Result<CccId> {
Ccc::get(self) CccId::get(self)
} }
/// Authenticate to the card using the provided management key (MGM). /// Authenticate to the card using the provided management key (MGM).
@@ -286,7 +285,7 @@ impl YubiKey {
// get a challenge from the card // get a challenge from the card
let challenge = Apdu::new(Ins::Authenticate) let challenge = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00]) .data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !challenge.is_success() || challenge.data().len() < 12 { if !challenge.is_success() || challenge.data().len() < 12 {
@@ -311,7 +310,7 @@ impl YubiKey {
let authentication = Apdu::new(Ins::Authenticate) let authentication = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&data) .data(data)
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !authentication.is_success() { if !authentication.is_success() {
@@ -476,12 +475,12 @@ impl YubiKey {
/// Block PUK: permanently prevent the PIN from becoming unblocked. /// Block PUK: permanently prevent the PIN from becoming unblocked.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))] #[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn block_puk(yubikey: &mut YubiKey) -> Result<()> { pub fn block_puk(&mut self) -> Result<()> {
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
let mut tries_remaining: i32 = -1; let mut tries_remaining: i32 = -1;
let mut flags = [0]; let mut flags = [0];
let txn = yubikey.begin_transaction()?; let txn = self.begin_transaction()?;
while tries_remaining != 0 { while tries_remaining != 0 {
// 2 -> change puk // 2 -> change puk
@@ -569,7 +568,7 @@ impl YubiKey {
let response = Apdu::new(Ins::Authenticate) let response = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00]) .data([0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !response.is_success() { if !response.is_success() {
@@ -595,7 +594,7 @@ impl YubiKey {
// send the response to the card and a challenge of our own. // send the response to the card and a challenge of our own.
let status_words = Apdu::new(Ins::Authenticate) let status_words = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM) .params(ALGO_3DES, KEY_CARDMGM)
.data(&data) .data(data)
.transmit(&txn, 261)? .transmit(&txn, 261)?
.status_words(); .status_words();
+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-----
+159 -26
View File
@@ -3,37 +3,39 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use lazy_static::lazy_static;
use log::trace; use log::trace;
use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey}; use rsa::pkcs1v15;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{convert::TryInto, env, sync::Mutex}; use signature::{hazmat::PrehashVerifier, Signature as _};
use std::{env, str::FromStr, sync::Mutex};
use x509::RelativeDistinguishedName; use x509::RelativeDistinguishedName;
use yubikey::{ use yubikey::{
certificate,
certificate::{Certificate, PublicKeyInfo}, certificate::{Certificate, PublicKeyInfo},
piv::{self, AlgorithmId, Key, RetiredSlotId, SlotId}, piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey, Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
}; };
lazy_static! { static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
/// Provide thread-safe access to a YubiKey
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
}
/// One-time test initialization and setup
fn init_yubikey() -> Mutex<YubiKey> {
// Only show logs if `RUST_LOG` is set // Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() { if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init(); env_logger::builder().format_timestamp(None).init();
} }
let yubikey = YubiKey::open().unwrap(); let yubikey = if let Ok(serial) = env::var("YUBIKEY_SERIAL") {
let serial = Serial::from_str(&serial).unwrap();
YubiKey::open_by_serial(serial).unwrap()
} else {
YubiKey::open().unwrap()
};
trace!("serial: {}", yubikey.serial()); trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version()); trace!("version: {}", yubikey.version());
Mutex::new(yubikey) Mutex::new(yubikey)
} });
// //
// CCCID support // CCCID support
@@ -194,26 +196,17 @@ fn generate_self_signed_rsa_cert() {
// //
let pubkey = match cert.subject_pki() { let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pubkey, PublicKeyInfo::Rsa { pubkey, .. } => pkcs1v15::VerifyingKey::<Sha256>::from(pubkey.clone()),
_ => unreachable!(), _ => unreachable!(),
}; };
let data = cert.as_ref(); let data = cert.as_ref();
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize; 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 msg = &data[4..8 + tbs_cert_len];
let sig = &data[data.len() - 128..]; let sig = pkcs1v15::Signature::from_bytes(&data[data.len() - 128..]).unwrap();
let hash = Sha256::digest(msg); let hash = Sha256::digest(msg);
assert!(pubkey assert!(pubkey.verify_prehash(&hash, &sig).is_ok());
.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(SHA2_256)
},
&hash,
sig
)
.is_ok());
} }
#[test] #[test]
@@ -241,3 +234,143 @@ fn generate_self_signed_ec_cert() {
use p256::ecdsa::signature::Verifier; use p256::ecdsa::signature::Verifier;
assert!(vk.verify(msg, &sig).is_ok()); 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"
);
}