Compare commits

..

21 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
25 changed files with 1184 additions and 673 deletions
+4 -4
View File
@@ -36,13 +36,13 @@ jobs:
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.57.0 # MSRV
toolchain: 1.60.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.57.0 # MSRV
toolchain: 1.60.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.57.0 # MSRV
toolchain: 1.60.0 # MSRV
deps: true
runs-on: ${{ matrix.platform }}
steps:
@@ -82,7 +82,7 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.57.0 # MSRV
toolchain: 1.65.0
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
+25
View File
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.7.0 (2022-11-14)
### 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])
Generated
+246 -337
View File
File diff suppressed because it is too large Load Diff
+12 -10
View File
@@ -1,6 +1,6 @@
[package]
name = "yubikey"
version = "0.6.0"
version = "0.7.0"
description = """
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
support for hardware-backed public-key decryption and digital signatures using
@@ -11,20 +11,21 @@ authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey.rs"
readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"]
categories = ["api-bindings", "authentication", "cryptography", "hardware-support"]
keywords = ["ecdsa", "encryption", "rsa", "piv", "signature"]
edition = "2021"
rust-version = "1.57"
rust-version = "1.60"
[workspace]
members = [".", "cli"]
[dependencies]
chrono = "0.4"
chrono = "0.4.23"
cookie-factory = "0.3"
der-parser = "8"
des = "0.8"
elliptic-curve = "0.12"
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
hmac = "0.12"
log = "0.4"
nom = "7"
@@ -36,20 +37,21 @@ p256 = "0.11"
p384 = "0.11"
pcsc = "2"
rand_core = { version = "0.6", features = ["std"] }
rsa = "0.6"
rsa = "0.7"
secrecy = "0.8"
sha1 = "0.10"
sha2 = "0.10"
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
subtle = "2"
subtle-encoding = "0.5"
uuid = { version = "1.0", features = ["v4"] }
uuid = { version = "1.2", features = ["v4"] }
x509 = "0.2"
x509-parser = "0.14"
zeroize = "1"
[dev-dependencies]
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]
untested = []
+65 -12
View File
@@ -4,16 +4,19 @@
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-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]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link]
[![dependency status][deps-image]][deps-link]
Pure Rust cross-platform host-side driver for [YubiKey] devices from [Yubico]
with support for public-key encryption and digital signatures using the
[Personal Identity Verification (PIV)][PIV] application.
Uses the Personal Computer/Smart Card ([PC/SC]) interface with cross-platform
access provided by the [`pcsc` crate].
[Documentation][docs-link]
## About
@@ -34,9 +37,36 @@ Note that while this project started as a fork of a [Yubico] project,
this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico.
## Features
### Personal Identity Verification (PIV)
[PIV] is a [NIST] standard for both *signing* and *encryption*
using SmartCards and SmartCard-based hardware tokens like YubiKeys.
PIV-related functionality can be found in the [`piv`] module.
This library natively implements the protocol used to manage and
utilize PIV encryption and signing keys which can be generated, imported,
and stored on YubiKey devices.
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**:
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
- ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
## Minimum Supported Rust Version
Rust **1.57** or newer.
Rust **1.60** or newer.
## Supported YubiKeys
@@ -59,6 +89,17 @@ an experimental stage and may still contain high-severity issues.
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
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
@@ -68,7 +109,7 @@ Tests which run live against a YubiKey device are marked as `#[ignore]` by
default in order to pass when running in a CI environment. To run these
tests locally, invoke the following command:
```
```shell
cargo test -- --ignored
```
@@ -77,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
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
```
```shell
RUST_LOG=info cargo test -- --ignored
```
To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
```
```text
running 1 test
[INFO yubikey::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
@@ -110,6 +151,14 @@ Yubico, which was originally written in C. It was mechanically translated
from C into Rust using [Corrode], and then subsequently heavily
refactored into safer, more idiomatic Rust.
For more information on [yubico-piv-tool] and background information on how
the YubiKey implementation of PIV works in general, see the
[Yubico PIV Tool Command Line Guide][piv-tool-guide].
## ⚠️ Security Warning
No security audits of this crate have ever been performed.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -123,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
as a derived work.
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -159,13 +208,13 @@ or conditions.
[//]: # (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
[docs-image]: https://docs.rs/yubikey/badge.svg
[docs-link]: https://docs.rs/yubikey/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey.rs/blob/main/COPYING
[msrv-image]: https://img.shields.io/badge/rustc-1.57+-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-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
@@ -176,9 +225,12 @@ or conditions.
[//]: # (general links)
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/
[PIV]: https://piv.idmanagement.gov/
[NIST]: https://www.nist.gov/
[PC/SC]: https://en.wikipedia.org/wiki/PC/SC
[`pcsc` crate]: https://github.com/bluetech/pcsc-rust
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
@@ -187,6 +239,7 @@ or conditions.
[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
[`untested` functionality tracking issue]: https://github.com/iqlusioninc/yubikey.rs/issues/280
[//]: # (github issues)
+24
View File
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.7.0 (2022-11-14)
### Changed
- Bump `clap` to v4.0 ([#438])
- Bump `x509-parser` to v0.14 ([#441])
- Switch from `lazy_static` to `once_cell` ([#442])
- Switch from `subtle-encoding` to `base16ct` ([#443])
- Bump `yubikey` dependency to v0.7 ([#444])
[#438]: https://github.com/iqlusioninc/yubikey.rs/pull/438
[#441]: https://github.com/iqlusioninc/yubikey.rs/pull/441
[#442]: https://github.com/iqlusioninc/yubikey.rs/pull/442
[#443]: https://github.com/iqlusioninc/yubikey.rs/pull/443
[#444]: https://github.com/iqlusioninc/yubikey.rs/pull/444
## 0.6.0 (2022-08-10)
### Changed
- 2021 edition upgrade; MSRV 1.57 ([#343])
- Migrate from `gumdrop` to `clap` v3 ([#379])
- Bump `yubikey` dependency to v0.6 ([#403])
[#343]: https://github.com/iqlusioninc/yubikey.rs/pull/343
[#379]: https://github.com/iqlusioninc/yubikey.rs/pull/379
[#403]: https://github.com/iqlusioninc/yubikey.rs/pull/403
## 0.5.0 (2021-11-21)
### Changed
- Bump `yubikey` dependency to v0.5 ([#327])
+7 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "yubikey-cli"
version = "0.6.0-pre"
version = "0.7.0"
description = """
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
@@ -15,12 +15,12 @@ edition = "2021"
rust-version = "1.56"
[dependencies]
clap = { version = "3", features = ["derive"] }
clap = { version = "4", features = ["derive"] }
env_logger = "0.9"
lazy_static = "1"
hex = { package = "base16ct", version = "0.1", features = ["alloc"] }
log = "0.4"
sha2 = "0.9"
subtle-encoding = "0.5"
once_cell = "1"
sha2 = "0.10"
termcolor = "1"
x509-parser = "0.12"
yubikey = { version = "0.6", path = ".." }
x509-parser = "0.14"
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
Rust **1.51** or newer.
Rust **1.60** or newer.
## Supported YubiKeys
@@ -35,10 +35,6 @@ an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK!
## Status
WIP. Check back later.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
@@ -47,7 +43,7 @@ For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
Copyright (c) 2014-2021 Yubico AB, Tony Arcieri
Copyright (c) 2014-2022 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -88,7 +84,7 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
+17 -14
View File
@@ -1,14 +1,13 @@
//! Status messages
use lazy_static::lazy_static;
use log::debug;
use once_cell::sync::Lazy;
use sha2::{Digest, Sha256};
use std::{
io::{self, Write},
str,
sync::Mutex,
};
use subtle_encoding::hex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate;
use yubikey::{certificate::Certificate, piv::*, YubiKey};
@@ -59,16 +58,14 @@ macro_rules! status_err {
};
}
lazy_static! {
/// Color configuration
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
/// 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
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.
///
@@ -140,14 +137,12 @@ impl Status {
/// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg)
.expect("error printing to stdout!")
self.print(&STDOUT, msg).expect("error printing to stdout!")
}
/// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg)
.expect("error printing to stderr!")
self.print(&STDERR, msg).expect("error printing to stderr!")
}
/// Print the given message
@@ -204,17 +199,25 @@ pub fn print_cert_info(
print_cert_attr(
stream,
"Fingerprint",
str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(),
&hex::upper::encode_string(&fingerprint),
)?;
print_cert_attr(
stream,
"Not Before",
cert.tbs_certificate.validity.not_before.to_rfc2822(),
cert.tbs_certificate
.validity
.not_before
.to_rfc2822()
.unwrap(),
)?;
print_cert_attr(
stream,
"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
GetSerial,
/// Get slot metadata
GetMetadata,
/// Other/unrecognized instruction codes
Other(u8),
}
@@ -219,6 +222,7 @@ impl Ins {
Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
Ins::Other(code) => code,
}
}
@@ -243,6 +247,7 @@ impl From<u8> for Ins {
0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code),
}
}
+8 -6
View File
@@ -32,11 +32,7 @@
use crate::{Error, Result, YubiKey};
use rand_core::{OsRng, RngCore};
use std::{
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
use std::fmt::{self, Debug, Display};
/// CCCID offset
const CCC_ID_OFFS: usize = 9;
@@ -114,8 +110,14 @@ impl CccId {
}
}
impl AsRef<[u8]> for CccId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for CccId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
f.write_str(&hex::upper::encode_string(self.as_ref()))
}
}
+23 -2
View File
@@ -47,6 +47,7 @@ use p256::NistP256;
use p384::NistP384;
use rsa::{PublicKeyParts, RsaPublicKey};
use sha2::{Digest, Sha256};
use std::fmt::Display;
use std::{fmt, ops::DerefMut};
use x509::{der::Oid, RelativeDistinguishedName};
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
@@ -94,10 +95,30 @@ impl Serial {
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be()
}
/// Returns itself formatted as x509 compatible hex string
pub fn as_x509_hex(&self) -> String {
let data = self.to_bytes();
let raw_hex_string = format!("{:02X?}", data);
raw_hex_string
.replace(", ", ":")
.replace([']', '['], "")
.to_lowercase()
}
/// Returns itself formatted as x509 compatible int string
pub fn as_x509_int(&self) -> String {
let Serial(buint) = self;
format!("{}", buint)
}
}
impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.as_x509_hex())
}
}
/// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CertInfo {
/// The certificate is uncompressed.
Uncompressed,
@@ -512,7 +533,7 @@ impl Certificate {
/// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str {
&self.subject
&self.issuer
}
/// Returns the SubjectName field of the certificate.
+8 -6
View File
@@ -31,11 +31,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{Error, Result, YubiKey};
use std::{
fmt::{self, Debug, Display},
str,
};
use subtle_encoding::hex;
use std::fmt::{self, Debug, Display};
use uuid::Uuid;
/// FASC-N offset
@@ -130,8 +126,14 @@ impl ChuId {
}
}
impl AsRef<[u8]> for ChuId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for ChuId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(str::from_utf8(&hex::encode(&self.0[..])).unwrap())
f.write_str(&hex::upper::encode_string(self.as_ref()))
}
}
+24 -18
View File
@@ -121,31 +121,37 @@ impl Error {
}
/// Error message
pub fn msg(self) -> &'static str {
pub fn msg(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::AlgorithmError => "algorithm error",
Error::AppletError => "applet error",
Error::ArgumentError => "argument error",
Error::AuthenticationError => "authentication error",
Error::GenericError => "generic error",
Error::InvalidObject => "invalid object",
Error::KeyError => "key error",
Error::MemoryError => "memory error",
Error::NotSupported => "not supported",
Error::NotFound => "not found",
Error::ParseError => "parse error",
Error::PcscError { .. } => "PC/SC error",
Error::PinLocked => "PIN locked",
Error::RangeError => "range error",
Error::SizeError => "size error",
Error::WrongPin { .. } => "wrong pin",
Error::AlgorithmError => f.write_str("algorithm error"),
Error::AppletError => f.write_str("applet error"),
Error::ArgumentError => f.write_str("argument error"),
Error::AuthenticationError => f.write_str("authentication error"),
Error::GenericError => f.write_str("generic error"),
Error::InvalidObject => f.write_str("invalid object"),
Error::KeyError => f.write_str("key error"),
Error::MemoryError => f.write_str("memory error"),
Error::NotSupported => f.write_str("not supported"),
Error::NotFound => f.write_str("not found"),
Error::ParseError => f.write_str("parse error"),
Error::PcscError {
inner: Some(pcsc_error),
} => f.write_fmt(format_args!("PC/SC error: {}", pcsc_error)),
Error::PcscError { .. } => f.write_str("PC/SC error"),
Error::PinLocked => f.write_str("PIN locked"),
Error::RangeError => f.write_str("range error"),
Error::SizeError => f.write_str("size error"),
Error::WrongPin { .. } => f.write_str("wrong pin"),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg())
self.msg(f)
}
}
+7 -106
View File
@@ -1,102 +1,10 @@
//! **yubikey.rs**: pure Rust cross-platform host-side driver for [YubiKey]
//! devices from [Yubico] using the Personal Computer/Smart Card ([PC/SC])
//! interface as provided by the [`pcsc` crate].
//!
//! # 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.
//!
//! # Minimum Supported Rust Version
//! Rust **1.57** 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
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -128,13 +36,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
mod apdu;
mod cccid;
pub mod certificate;
+1 -1
View File
@@ -404,7 +404,7 @@ fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
c = (c & 0x0F) + ((c >> 4) & 0x0F);
// if count is even, set low key bit to 1, otherwise 0
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 });
tmp[i] = (key[i] & 0xFE) | u8::from(c & 0x01 != 0x01);
}
// check odd parity key against table by DES key block
+418 -99
View File
@@ -45,6 +45,7 @@
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate, PublicKeyInfo},
consts::CB_OBJ_MAX,
error::{Error, Result},
policy::{PinPolicy, TouchPolicy},
serialization::*,
@@ -64,7 +65,6 @@ use std::{
#[cfg(feature = "untested")]
use {
crate::consts::CB_OBJ_MAX,
num_bigint_dig::traits::ModInverse,
num_integer::Integer,
num_traits::{FromPrimitive, One},
@@ -126,6 +126,9 @@ pub enum SlotId {
/// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten.
Attestation,
/// Thse slots are used for management. PIN PUK and Management Key.
Management(ManagementSlotId),
}
impl TryFrom<u8> for SlotId {
@@ -138,7 +141,9 @@ impl TryFrom<u8> for SlotId {
0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication),
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)),
}
}
}
@@ -152,6 +157,7 @@ impl From<SlotId> for u8 {
SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9,
SlotId::Management(mgmt) => mgmt.into(),
}
}
}
@@ -159,6 +165,7 @@ impl From<SlotId> for u8 {
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),
}
@@ -175,7 +182,10 @@ impl FromStr for SlotId {
"9d" => Ok(SlotId::KeyManagement),
"9e" => Ok(SlotId::CardAuthentication),
"f9" => Ok(SlotId::Attestation),
_ => s.parse().map(SlotId::Retired),
_ => s
.parse()
.map(SlotId::Management)
.or_else(|_| s.parse().map(SlotId::Retired)),
}
}
}
@@ -189,6 +199,7 @@ impl SlotId {
SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(),
SlotId::Management(mgmt) => mgmt.object_id(),
SlotId::Attestation => 0x005f_ff01,
}
}
@@ -341,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
pub const SLOTS: [SlotId; 24] = [
pub const SLOTS: [SlotId; 27] = [
SlotId::Authentication,
SlotId::Signature,
SlotId::KeyManagement,
@@ -367,6 +460,9 @@ pub const SLOTS: [SlotId; 24] = [
SlotId::Retired(RetiredSlotId::R19),
SlotId::Retired(RetiredSlotId::R20),
SlotId::CardAuthentication,
SlotId::Management(ManagementSlotId::Pin),
SlotId::Management(ManagementSlotId::Puk),
SlotId::Management(ManagementSlotId::Management),
];
/// Algorithm identifiers
@@ -594,101 +690,8 @@ pub fn generate(
}
}
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
//
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
// - Use a full ASN.1 parser on the entire datastructure:
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
//
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
// parsing (which treats 128 as a single-byte definite length instead of an
// indefinite length):
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | |0x81|len128| modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | |0x81| len:256 | modulus |0x82|len3| exp |
//
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
// 3-byte length.
let data = &response.data()[5..];
let (data, modulus_tlv) = Tlv::parse(data)?;
if modulus_tlv.tag != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
let modulus = modulus_tlv.value.to_vec();
let (_, exp_tlv) = Tlv::parse(data)?;
if exp_tlv.tag != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
let exp = exp_tlv.value.to_vec();
Ok(PublicKeyInfo::Rsa {
algorithm,
pubkey: RsaPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
)
.map_err(|_| Error::InvalidObject)?,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
let data = &response.data()[3..];
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
let (_, tlv) = Tlv::parse(data)?;
if tlv.tag != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
// the curve point should always be determined by the curve
if tlv.value.len() != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = tlv.value.to_vec();
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)
}
}
let value = response.data();
read_public_key(algorithm, value, true)
}
#[cfg(feature = "untested")]
@@ -915,3 +918,319 @@ pub fn decrypt_data(
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
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.
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
/// given slot.
///
/// 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 {
/// Use the default PIN policy for the slot. See the slot's documentation for details.
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 {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation.
@@ -50,7 +64,7 @@ impl PinPolicy {
/// addition to the [`PinPolicy`].
///
/// This policy must be set when keys are generated or imported, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TouchPolicy {
/// Use the default touch policy for the slot.
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),
}
}
}
+4 -4
View File
@@ -69,7 +69,7 @@ impl<'tx> Transaction<'tx> {
pub fn select_application(&self) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&PIV_AID)
.data(PIV_AID)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
@@ -109,7 +109,7 @@ impl<'tx> Transaction<'tx> {
// YK4 requires switching to the yk applet to retrieve the serial
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&YK_AID)
.data(YK_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -131,7 +131,7 @@ impl<'tx> Transaction<'tx> {
// reselect the PIV applet
let sw = Apdu::new(Ins::SelectApplication)
.p1(0x04)
.data(&PIV_AID)
.data(PIV_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -246,7 +246,7 @@ impl<'tx> Transaction<'tx> {
let status_words = Apdu::new(Ins::SetMgmKey)
.params(0xff, p2)
.data(&data)
.data(data)
.transmit(self, 261)?
.status_words();
+4 -4
View File
@@ -285,7 +285,7 @@ impl YubiKey {
// get a challenge from the card
let challenge = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?;
if !challenge.is_success() || challenge.data().len() < 12 {
@@ -310,7 +310,7 @@ impl YubiKey {
let authentication = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.data(data)
.transmit(&txn, 261)?;
if !authentication.is_success() {
@@ -568,7 +568,7 @@ impl YubiKey {
let response = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.data([0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?;
if !response.is_success() {
@@ -594,7 +594,7 @@ impl YubiKey {
// send the response to the card and a challenge of our own.
let status_words = Apdu::new(Ins::Authenticate)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.data(data)
.transmit(&txn, 261)?
.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-----
+126 -26
View File
@@ -3,37 +3,39 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use lazy_static::lazy_static;
use log::trace;
use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore};
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
use rsa::pkcs1v15;
use sha2::{Digest, Sha256};
use std::{env, sync::Mutex};
use signature::{hazmat::PrehashVerifier, Signature as _};
use std::{env, str::FromStr, sync::Mutex};
use x509::RelativeDistinguishedName;
use yubikey::{
certificate,
certificate::{Certificate, PublicKeyInfo},
piv::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey,
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
};
lazy_static! {
/// Provide thread-safe access to a YubiKey
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
}
/// One-time test initialization and setup
fn init_yubikey() -> Mutex<YubiKey> {
static YUBIKEY: Lazy<Mutex<YubiKey>> = Lazy::new(|| {
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
let 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!("version: {}", yubikey.version());
Mutex::new(yubikey)
}
});
//
// CCCID support
@@ -194,26 +196,17 @@ fn generate_self_signed_rsa_cert() {
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
PublicKeyInfo::Rsa { pubkey, .. } => pkcs1v15::VerifyingKey::<Sha256>::from(pubkey.clone()),
_ => unreachable!(),
};
let data = cert.as_ref();
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
let msg = &data[4..8 + tbs_cert_len];
let sig = &data[data.len() - 128..];
let sig = pkcs1v15::Signature::from_bytes(&data[data.len() - 128..]).unwrap();
let hash = Sha256::digest(msg);
assert!(pubkey
.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(SHA2_256)
},
&hash,
sig
)
.is_ok());
assert!(pubkey.verify_prehash(&hash, &sig).is_ok());
}
#[test]
@@ -273,4 +266,111 @@ fn test_slot_id_display() {
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"
);
}