Compare commits

..

14 Commits

Author SHA1 Message Date
Tony Arcieri (iqlusion) ee3702a65e yubikey v0.8.0 (#518) 2023-08-15 19:29:22 -06:00
Tony Arcieri (iqlusion) 45915e5e5a Make RsaKeyData::new fallible (#517)
Replaces unwraps with `Error::AlgorithmError`
2023-08-15 18:33:30 -06:00
Tony Arcieri (iqlusion) 75ce24a3ea Handle metadata command not being supported in test (#516)
Some YubiKeys don't support this command. Instead of failing the test
when it happens, log a warning message instead.
2023-08-15 18:21:45 -06:00
Tony Arcieri (iqlusion) 78313360a1 Add clippy::unwrap_used lint (#515)
Lints for usages of `unwrap()` in the `yubikey` crate (not CLI yet).

Replaces them with `?` or `expect()` as the situation warrants.
2023-08-15 18:02:25 -06:00
Tony Arcieri (iqlusion) d226209ea4 Use doc_auto_cfg (#514)
Removes manual feature annotations for docs.rs
2023-08-15 16:39:29 -06:00
Tony Arcieri (iqlusion) de142256d0 Bump clippy to use Rust 1.71 (#513) 2023-08-15 16:31:13 -06:00
hko-s 485d49a6c8 Make YubiKey::open() more robust (#504)
On systems with a physical card-reader, the previous implementation falsely
reports "multiple YubiKeys detected!", even if only one YubiKey is connected.
This change attempts to actually open each reader as a YubiKey, and only
reports "multiple YubiKeys" if it can actually open more than one.

Additionally, this change avoids resetting the YubiKeys in case we find more
than one.
2023-08-15 16:20:04 -06:00
Tony Arcieri (iqlusion) 9932d05428 Remove chrono dependency (#512)
It's no longer used as of #495
2023-08-15 16:18:08 -06:00
Tony Arcieri (iqlusion) 363648bbc5 Cargo.lock: bump dependencies (#511)
Upgrades the following dependencies:

    $ cargo update
    Updating crates.io index
    Updating aho-corasick v0.7.20 -> v1.0.4
      Adding android-tzdata v0.1.1
      Adding anstream v0.3.2
      Adding anstyle v1.0.1
      Adding anstyle-parse v0.2.1
      Adding anstyle-query v1.0.0
      Adding anstyle-wincon v1.0.2
    Updating base64ct v1.5.3 -> v1.6.0
      Adding bitflags v2.4.0
    Updating block-buffer v0.10.3 -> v0.10.4
    Updating bumpalo v3.11.1 -> v3.13.0
    Updating cc v1.0.78 -> v1.0.82
    Updating chrono v0.4.23 -> v0.4.26
    Updating cipher v0.4.3 -> v0.4.4
    Updating clap v4.0.32 -> v4.3.21
      Adding clap_builder v4.3.21
    Updating clap_derive v4.0.21 -> v4.3.12
    Updating clap_lex v0.3.0 -> v0.5.0
    Removing codespan-reporting v0.11.1
      Adding colorchoice v1.0.0
    Updating const-oid v0.9.2 -> v0.9.5
    Updating core-foundation-sys v0.8.3 -> v0.8.4
    Updating cpufeatures v0.2.5 -> v0.2.9
    Updating crypto-bigint v0.5.1 -> v0.5.2
    Removing cxx v1.0.85
    Removing cxx-build v1.0.85
    Removing cxxbridge-flags v1.0.85
    Removing cxxbridge-macro v1.0.85
    Updating der v0.7.6 -> v0.7.8
    Updating der_derive v0.7.1 -> v0.7.2
    Updating digest v0.10.6 -> v0.10.7
    Updating ecdsa v0.16.7 -> v0.16.8
    Updating elliptic-curve v0.13.4 -> v0.13.5
    Updating errno v0.2.8 -> v0.3.2
    Updating generic-array v0.14.6 -> v0.14.7
    Updating getrandom v0.2.8 -> v0.2.10
    Updating heck v0.4.0 -> v0.4.1
    Updating hermit-abi v0.2.6 -> v0.3.2
    Updating iana-time-zone v0.1.53 -> v0.1.57
    Updating iana-time-zone-haiku v0.1.1 -> v0.1.2
    Removing io-lifetimes v1.0.3
    Updating is-terminal v0.4.2 -> v0.4.9
    Updating js-sys v0.3.60 -> v0.3.64
    Updating libc v0.2.139 -> v0.2.147
    Updating libm v0.2.6 -> v0.2.7
    Removing link-cplusplus v1.0.8
    Updating linux-raw-sys v0.1.4 -> v0.4.5
    Updating log v0.4.17 -> v0.4.20
    Updating nom v7.1.2 -> v7.1.3
    Updating num-bigint-dig v0.8.2 -> v0.8.4
    Updating num-traits v0.2.15 -> v0.2.16
    Updating once_cell v1.17.0 -> v1.18.0
    Removing os_str_bytes v6.4.1
    Updating p256 v0.13.0 -> v0.13.2
    Updating pbkdf2 v0.12.1 -> v0.12.2
    Updating pkg-config v0.3.26 -> v0.3.27
    Updating primeorder v0.13.0 -> v0.13.2
    Removing proc-macro-error v1.0.4
    Removing proc-macro-error-attr v1.0.4
    Updating proc-macro2 v1.0.56 -> v1.0.66
    Updating quote v1.0.26 -> v1.0.32
    Updating regex v1.7.0 -> v1.9.3
      Adding regex-automata v0.3.6
    Updating regex-syntax v0.6.28 -> v0.7.4
    Updating rustix v0.36.6 -> v0.38.8
    Removing scratch v1.0.3
    Updating sec1 v0.7.1 -> v0.7.3
    Updating serde v1.0.152 -> v1.0.183
    Updating sha2 v0.10.6 -> v0.10.7
    Updating smallvec v1.10.0 -> v1.11.0
    Updating subtle v2.4.1 -> v2.5.0
    Removing syn v1.0.107
    Removing syn v2.0.15
      Adding syn v2.0.28
    Updating termcolor v1.1.3 -> v1.2.0
    Updating unicode-ident v1.0.6 -> v1.0.11
    Removing unicode-width v0.1.10
      Adding utf8parse v0.2.1
    Updating uuid v1.2.2 -> v1.4.1
    Updating wasm-bindgen v0.2.83 -> v0.2.87
    Updating wasm-bindgen-backend v0.2.83 -> v0.2.87
    Updating wasm-bindgen-macro v0.2.83 -> v0.2.87
    Updating wasm-bindgen-macro-support v0.2.83 -> v0.2.87
    Updating wasm-bindgen-shared v0.2.83 -> v0.2.87
      Adding windows v0.48.0
    Updating windows-sys v0.42.0 -> v0.48.0
      Adding windows-targets v0.48.2
    Updating windows_aarch64_gnullvm v0.42.0 -> v0.48.2
    Updating windows_aarch64_msvc v0.42.0 -> v0.48.2
    Updating windows_i686_gnu v0.42.0 -> v0.48.2
    Updating windows_i686_msvc v0.42.0 -> v0.48.2
    Updating windows_x86_64_gnu v0.42.0 -> v0.48.2
    Updating windows_x86_64_gnullvm v0.42.0 -> v0.48.2
    Updating windows_x86_64_msvc v0.42.0 -> v0.48.2
    Updating x509-cert v0.2.3 -> v0.2.4
2023-08-14 19:07:13 -06:00
Arthur Gautier 6a1e1603ef Use x509-cert certificate builder (#495)
Co-authored-by: Carl Wallace <carl@redhoundsoftware.com>
2023-08-14 18:31:39 -06:00
Arthur Gautier 8cf18d2986 Bump rsa to 0.9.0 (#502) 2023-05-03 06:22:45 -06:00
dependabot[bot] 07281440c0 Bump rsa from 0.9.0-pre.1 to 0.9.0-pre.2 (#500)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.0-pre.1 to 0.9.0-pre.2.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.0-pre.1...v0.9.0-pre.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 08:54:23 -06:00
dependabot[bot] cd76a55318 Bump rsa from 0.9.0-pre.0 to 0.9.0-pre.1 (#497)
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.9.0-pre.0 to 0.9.0-pre.1.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.0-pre.0...v0.9.0-pre.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-10 10:10:55 -06:00
dependabot[bot] 23bbf1b783 Bump zeroize from 1.5.7 to 1.6.0 (#496)
Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.5.7 to 1.6.0.
- [Release notes](https://github.com/RustCrypto/utils/releases)
- [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.5.7...zeroize-v1.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-03 11:33:52 -06:00
21 changed files with 738 additions and 1344 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.65.0 toolchain: 1.71.0
components: clippy components: clippy
override: true override: true
- run: sudo apt-get install libpcsclite-dev - run: sudo apt-get install libpcsclite-dev
+28 -4
View File
@@ -4,11 +4,11 @@ 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).
## Unreleased ## 0.8.0 (2023-08-15)
### Added ### Added
- `YubiKey::disconnect` - `impl Debug for {Context, YubiKey}` ([#457])
- `impl Debug for {Context, YubiKey}` - `YubiKey::disconnect` ([#462])
- `Error::AppletNotFound` - `Error::AppletNotFound` ([#476])
### Changed ### Changed
- `Reader::open` now returns `Error::AppletNotFound` instead of `Error::Generic` - `Reader::open` now returns `Error::AppletNotFound` instead of `Error::Generic`
@@ -18,11 +18,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Reader::open` now avoids resetting the card if an error occurs (equivalent to - `Reader::open` now avoids resetting the card if an error occurs (equivalent to
calling `YubiKey::disconnect(pcsc::Disposition::LeaveCard)` if `Reader::open` calling `YubiKey::disconnect(pcsc::Disposition::LeaveCard)` if `Reader::open`
succeeds). succeeds).
- Raise minimum `pcsc` version to 2.3.1 and remove workaround ([#478])
- Bump asymmetric crypto dependencies; MSRV 1.65 ([#490])
- `elliptic-curve` v0.13
- `k256` v0.13
- `p256` v0.13
- `p384` v0.13
- `pbkdf2` v0.12
- `rsa` v0.9 ([#502])
- `signature` v2
- Use `x509-cert` certificate builder ([#495])
- Make `RsaKeyData::new` fallible ([#517])
### Fixed ### Fixed
- `StatusWords::code` now returns the correct code (including embedded `tries` - `StatusWords::code` now returns the correct code (including embedded `tries`
count) for `StatusWords::VerifyFailError`. Previously the returned code lost count) for `StatusWords::VerifyFailError`. Previously the returned code lost
information and was not round-trip compatible with `StatusWords::from(u16)`. information and was not round-trip compatible with `StatusWords::from(u16)`.
- Parsing of serial numbers ([#466])
- Make `YubiKey::open()` more robust ([#504])
[#457]: https://github.com/iqlusioninc/yubikey.rs/pull/457
[#462]: https://github.com/iqlusioninc/yubikey.rs/pull/462
[#466]: https://github.com/iqlusioninc/yubikey.rs/pull/466
[#476]: https://github.com/iqlusioninc/yubikey.rs/pull/476
[#478]: https://github.com/iqlusioninc/yubikey.rs/pull/478
[#490]: https://github.com/iqlusioninc/yubikey.rs/pull/490
[#495]: https://github.com/iqlusioninc/yubikey.rs/pull/495
[#502]: https://github.com/iqlusioninc/yubikey.rs/pull/502
[#504]: https://github.com/iqlusioninc/yubikey.rs/pull/504
[#517]: https://github.com/iqlusioninc/yubikey.rs/pull/517
## 0.7.0 (2022-11-14) ## 0.7.0 (2022-11-14)
### Added ### Added
Generated
+238 -582
View File
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "yubikey" name = "yubikey"
version = "0.8.0-pre.0" version = "0.8.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
@@ -19,10 +19,11 @@ rust-version = "1.65"
[workspace] [workspace]
members = [".", "cli"] members = [".", "cli"]
[workspace.dependencies]
x509-cert = { version = "0.2.3", features = [ "builder", "hazmat" ] }
[dependencies] [dependencies]
chrono = "0.4.23" der = "0.7.1"
cookie-factory = "0.3"
der-parser = "8"
des = "0.8" des = "0.8"
elliptic-curve = "0.13" elliptic-curve = "0.13"
hex = { package = "base16ct", version = "0.2", features = ["alloc"] } hex = { package = "base16ct", version = "0.2", features = ["alloc"] }
@@ -32,19 +33,20 @@ nom = "7"
num-bigint-dig = { version = "0.8", 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"
ecdsa = { version = "0.16.7", features = ["digest", "pem"] }
p256 = "0.13" p256 = "0.13"
p384 = "0.13" p384 = "0.13"
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] } pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
pcsc = "2.3.1" pcsc = "2.3.1"
rand_core = { version = "0.6", features = ["std"] } rand_core = { version = "0.6", features = ["std"] }
rsa = "=0.9.0-pre.0" rsa = { version = "0.9.2", features = ["sha2"] }
secrecy = "0.8" secrecy = "0.8"
sha1 = { version = "0.10", features = ["oid"] } sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] } sha2 = { version = "0.10", features = ["oid"] }
signature = "2"
subtle = "2" subtle = "2"
uuid = { version = "1.2", features = ["v4"] } uuid = { version = "1.2", features = ["v4"] }
x509 = "0.2" x509-cert.workspace = true
x509-parser = "0.14"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "yubikey-cli" name = "yubikey-cli"
version = "0.7.0" version = "0.8.0-pre"
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.
@@ -22,5 +22,5 @@ log = "0.4"
once_cell = "1" once_cell = "1"
sha2 = "0.10" sha2 = "0.10"
termcolor = "1" termcolor = "1"
x509-parser = "0.14" x509-cert.workspace = true
yubikey = { version = "0.8.0-pre.0", path = ".." } yubikey = { version = "0.8", path = ".." }
+23 -45
View File
@@ -9,7 +9,7 @@ use std::{
sync::Mutex, sync::Mutex,
}; };
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_certificate; use x509_cert::der::Encode;
use yubikey::{certificate::Certificate, piv::*, YubiKey}; use yubikey::{certificate::Certificate, piv::*, YubiKey};
/// Print a success status message (in green if colors are enabled) /// Print a success status message (in green if colors are enabled)
@@ -180,52 +180,30 @@ pub fn print_cert_info(
return Ok(()); return Ok(());
} }
}; };
let buf = cert.into_buffer(); let cert = &cert.cert;
if !buf.is_empty() { let fingerprint = Sha256::digest(cert.to_der().unwrap());
let fingerprint = Sha256::digest(&buf); let slot_id: u8 = slot.into();
let slot_id: u8 = slot.into(); print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?; print_cert_attr(
match parse_x509_certificate(&buf) { stream,
Ok((_rem, cert)) => { "Algorithm",
print_cert_attr( cert.tbs_certificate.subject_public_key_info.algorithm.oid,
stream, )?;
"Algorithm",
cert.tbs_certificate.subject_pki.algorithm.algorithm,
)?;
print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?; print_cert_attr(stream, "Subject", &cert.tbs_certificate.subject)?;
print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?; print_cert_attr(stream, "Issuer", &cert.tbs_certificate.issuer)?;
print_cert_attr( print_cert_attr(
stream, stream,
"Fingerprint", "Fingerprint",
&hex::upper::encode_string(&fingerprint), &hex::upper::encode_string(&fingerprint),
)?; )?;
print_cert_attr( print_cert_attr(
stream, stream,
"Not Before", "Not Before",
cert.tbs_certificate cert.tbs_certificate.validity.not_before,
.validity )?;
.not_before print_cert_attr(stream, "Not After", cert.tbs_certificate.validity.not_after)?;
.to_rfc2822()
.unwrap(),
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate
.validity
.not_after
.to_rfc2822()
.unwrap(),
)?;
}
_ => {
println!("Failed to parse certificate");
return Ok(());
}
};
}
Ok(()) Ok(())
} }
+3 -8
View File
@@ -30,7 +30,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{Error, Result, YubiKey}; use crate::{Result, YubiKey};
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use std::fmt::{self, Debug, Display}; use std::fmt::{self, Debug, Display};
@@ -48,6 +48,7 @@ const OBJ_CAPABILITY: u32 = 0x005f_c107;
/// - 0xff == Manufacturer ID (dummy) /// - 0xff == Manufacturer ID (dummy)
/// - 0x02 == Card type (javaCard) /// - 0x02 == Card type (javaCard)
/// - next 14 bytes: card ID /// - next 14 bytes: card ID
#[allow(dead_code)]
const CCC_TMPL: &[u8] = &[ const CCC_TMPL: &[u8] = &[
0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4,
@@ -90,17 +91,11 @@ impl CccId {
pub fn get(yubikey: &mut YubiKey) -> Result<Self> { pub fn get(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_CAPABILITY)?; let response = txn.fetch_object(OBJ_CAPABILITY)?;
Ok(response[..Self::BYTE_SIZE].try_into().map(Self)?)
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
}
Ok(Self(response[..Self::BYTE_SIZE].try_into().unwrap()))
} }
/// Set Cardholder Capability Container (CCC) ID /// Set Cardholder Capability Container (CCC) ID
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> { pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CCC_TMPL.to_vec(); let mut buf = CCC_TMPL.to_vec();
buf[0..self.0.len()].copy_from_slice(&self.0); buf[0..self.0.len()].copy_from_slice(&self.0);
+269 -512
View File
@@ -33,90 +33,27 @@
use crate::{ use crate::{
consts::CB_OBJ_MAX, consts::CB_OBJ_MAX,
error::{Error, Result}, error::{Error, Result},
piv::{sign_data, AlgorithmId, SlotId}, piv::SlotId,
serialization::*, serialization::*,
transaction::Transaction, transaction::Transaction,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, Buffer,
}; };
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error; use log::error;
use num_bigint_dig::BigUint; use x509_cert::{
use p256::NistP256; builder::{Builder, CertificateBuilder, Profile},
use p384::NistP384; der::{self, referenced::OwnedToRef, Decode, Encode},
use rsa::{PublicKeyParts, RsaPublicKey}; name::Name,
use sha2::{Digest, Sha256}; serial_number::SerialNumber,
use std::fmt::Display; spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef},
use std::{fmt, ops::DerefMut}; time::Validity,
use x509::{der::Oid, RelativeDistinguishedName}; };
use x509_parser::{parse_x509_certificate, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing; use zeroize::Zeroizing;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
const TAG_CERT: u8 = 0x70; const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71; const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE; const TAG_CERT_LRC: u8 = 0xFE;
/// A serial number for a [`Certificate`].
#[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
}
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
impl TryFrom<&[u8]> for Serial {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Serial> {
if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(bytes)))
} else {
Err(Error::ParseError)
}
}
}
impl Serial {
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be()
}
/// Returns itself formatted as x509 compatible hex string
pub fn as_x509_hex(&self) -> String {
let data = self.to_bytes();
let raw_hex_string = format!("{:02X?}", data);
raw_hex_string
.replace(", ", ":")
.replace([']', '['], "")
.to_lowercase()
}
/// Returns itself formatted as x509 compatible int string
pub fn as_x509_int(&self) -> String {
let Serial(buint) = self;
format!("{}", buint)
}
}
impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.as_x509_hex())
}
}
/// Information about how a [`Certificate`] is stored within a YubiKey. /// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CertInfo { pub enum CertInfo {
@@ -148,210 +85,11 @@ impl From<CertInfo> for u8 {
} }
} }
impl x509::AlgorithmIdentifier for AlgorithmId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// RSA encryption
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => &[1, 2, 840, 113_549, 1, 1, 1],
// EC Public Key
AlgorithmId::EccP256 | AlgorithmId::EccP384 => &[1, 2, 840, 10045, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
use x509::der::write::der_oid;
// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
// ```text
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
// -- implicitCurve NULL
// -- specifiedCurve SpecifiedECDomain
// }
// ```
match self {
AlgorithmId::EccP256 => der_oid(&[1, 2, 840, 10045, 3, 1, 7][..])(w),
AlgorithmId::EccP384 => der_oid(&[1, 3, 132, 0, 34][..])(w),
_ => Ok(w),
}
}
}
/// Information about a public key within a [`Certificate`].
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Public key
pubkey: RsaPublicKey,
},
/// EC P-256 keys
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKeyInfo({:?})", self.algorithm())
}
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(&subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() {
1024 => AlgorithmId::Rsa1024,
2048 => AlgorithmId::Rsa2048,
_ => return Err(Error::AlgorithmError),
},
pubkey,
})
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
let algorithm_parameters = subject_pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::InvalidObject)?;
match read_pki::ec_parameters(algorithm_parameters)? {
AlgorithmId::EccP256 => EcPublicKey::<NistP256>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256)
.map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::<NistP384>::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384)
.map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
_ => Err(Error::InvalidObject),
}
}
/// Returns the algorithm that this public key can be used with.
pub fn algorithm(&self) -> AlgorithmId {
match self {
PublicKeyInfo::Rsa { algorithm, .. } => *algorithm,
PublicKeyInfo::EcP256(_) => AlgorithmId::EccP256,
PublicKeyInfo::EcP384(_) => AlgorithmId::EccP384,
}
}
}
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates /// Certificates
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate { pub struct Certificate {
serial: Serial, /// Inner certificate
#[allow(dead_code)] pub cert: x509_cert::Certificate,
issuer: String,
subject: String,
subject_pki: PublicKeyInfo,
data: Buffer,
}
impl<'a> TryFrom<&'a [u8]> for Certificate {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::from_bytes(bytes.to_vec())
}
} }
impl Certificate { impl Certificate {
@@ -360,112 +98,35 @@ impl Certificate {
/// ///
/// `extensions` is optional; if empty, no extensions will be included. Due to the /// `extensions` is optional; if empty, no extensions will be included. Due to the
/// need for an `O: Oid` type parameter, users who do not have any extensions should /// need for an `O: Oid` type parameter, users who do not have any extensions should
/// use the workaround `let extensions: &[x509::Extension<'_, &[u64]>] = &[];`. /// use the workaround `let extensions: &[x509_cert::Extension<'_, &[u64]>] = &[];`.
pub fn generate_self_signed<O: Oid>( pub fn generate_self_signed<F, KT: yubikey_signer::KeyType>(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
key: SlotId, key: SlotId,
serial: impl Into<Serial>, serial: SerialNumber,
not_after: Option<DateTime<Utc>>, validity: Validity,
subject: &[RelativeDistinguishedName<'_>], subject: Name,
subject_pki: PublicKeyInfo, subject_pki: SubjectPublicKeyInfoOwned,
extensions: &[x509::Extension<'_, O>], extensions: F,
) -> Result<Self> { ) -> Result<Self>
let serial = serial.into(); where
F: FnOnce(&mut CertificateBuilder<'_, yubikey_signer::Signer<'_, KT>>) -> der::Result<()>,
let mut tbs_cert = Buffer::new(Vec::with_capacity(CB_OBJ_MAX)); {
let signer = yubikey_signer::Signer::new(yubikey, key, subject_pki.owned_to_ref())?;
let signature_algorithm = match subject_pki.algorithm() { let mut builder = CertificateBuilder::new(
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => SignatureId::Sha256WithRsaEncryption, Profile::Manual { issuer: None },
AlgorithmId::EccP256 | AlgorithmId::EccP384 => SignatureId::EcdsaWithSha256,
};
cookie_factory::gen(
x509::write::tbs_certificate(
&serial.to_bytes(),
&signature_algorithm,
// Issuer and subject are the same in self-signed certificates.
subject,
Utc::now(),
not_after,
subject,
&subject_pki,
extensions,
),
tbs_cert.deref_mut(),
)
.expect("can serialize to Vec");
let signature = match signature_algorithm {
SignatureId::Sha256WithRsaEncryption => {
use cookie_factory::{combinator::slice, sequence::tuple};
use x509::{
der::write::{der_octet_string, der_sequence},
write::algorithm_identifier,
};
let em_len = if let AlgorithmId::Rsa1024 = subject_pki.algorithm() {
128
} else {
256
};
let h = Sha256::digest(&tbs_cert);
let t = cookie_factory::gen_simple(
der_sequence((
algorithm_identifier(&DigestId::Sha256),
der_octet_string(&h),
)),
vec![],
)
.expect("can serialize into Vec");
let em = cookie_factory::gen_simple(
tuple((
slice(&[0x00, 0x01]),
slice(&vec![0xff; em_len - t.len() - 3]),
slice(&[0x00]),
slice(t),
)),
vec![],
)
.expect("can serialize to Vec");
sign_data(yubikey, &em, subject_pki.algorithm(), key)
}
SignatureId::EcdsaWithSha256 => sign_data(
yubikey,
&Sha256::digest(&tbs_cert),
subject_pki.algorithm(),
key,
),
}?;
let mut data = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
cookie_factory::gen(
x509::write::certificate(&tbs_cert, &signature_algorithm, &signature),
data.deref_mut(),
)
.expect("can serialize to Vec");
let (issuer, subject) = parse_x509_certificate(&data)
.map(|(_, cert)| {
(
cert.tbs_certificate.issuer.to_string(),
cert.tbs_certificate.subject.to_string(),
)
})
.expect("We just serialized this correctly");
let cert = Certificate {
serial, serial,
issuer, validity,
subject, subject,
subject_pki, subject_pki,
data, &signer,
}; )
.map_err(|_| Error::KeyError)?;
// Add custom extensions
extensions(&mut builder)?;
let cert = builder.build().map_err(|_| Error::KeyError)?;
let cert = Self { cert };
cert.write(yubikey, key, CertInfo::Uncompressed)?; cert.write(yubikey, key, CertInfo::Uncompressed)?;
Ok(cert) Ok(cert)
@@ -480,18 +141,18 @@ impl Certificate {
return Err(Error::InvalidObject); return Err(Error::InvalidObject);
} }
Certificate::from_bytes(buf) Self::from_bytes(buf)
} }
/// Write this certificate into the YubiKey in the given slot /// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: CertInfo) -> Result<()> { pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: CertInfo) -> Result<()> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.data), certinfo) let data = self.cert.to_der().map_err(|_| Error::InvalidObject)?;
write_certificate(&txn, slot, Some(&data), certinfo)
} }
/// Delete a certificate located at the given slot of the given YubiKey /// Delete a certificate located at the given slot of the given YubiKey
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> { pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<()> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, CertInfo::Uncompressed) write_certificate(&txn, slot, None, CertInfo::Uncompressed)
@@ -506,55 +167,27 @@ impl Certificate {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let parsed_cert = match parse_x509_certificate(&cert) { x509_cert::Certificate::from_der(&cert)
Ok((_, cert)) => cert, .map(|cert| Self { cert })
_ => return Err(Error::InvalidObject), .map_err(|_| Error::InvalidObject)
};
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
serial,
issuer,
subject,
subject_pki,
data: cert,
})
}
/// Returns the serial number of the certificate.
pub fn serial(&self) -> &Serial {
&self.serial
} }
/// Returns the Issuer field of the certificate. /// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str { pub fn issuer(&self) -> String {
&self.issuer self.cert.tbs_certificate.issuer.to_string()
} }
/// Returns the SubjectName field of the certificate. /// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str { pub fn subject(&self) -> String {
&self.subject self.cert.tbs_certificate.subject.to_string()
} }
/// Returns the SubjectPublicKeyInfo field of the certificate. /// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> &PublicKeyInfo { pub fn subject_pki(&self) -> SubjectPublicKeyInfoRef<'_> {
&self.subject_pki self.cert
} .tbs_certificate
.subject_public_key_info
/// Extract the inner buffer .owned_to_ref()
pub fn into_buffer(self) -> Buffer {
self.data
}
}
impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
self.data.as_ref()
} }
} }
@@ -590,113 +223,237 @@ pub(crate) fn write_certificate(
) -> Result<()> { ) -> Result<()> {
let object_id = slot.object_id(); let object_id = slot.object_id();
if data.is_none() { if let Some(data) = data {
return txn.save_object(object_id, &[]); let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
// write compression info and LRC trailer
offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
} else {
txn.save_object(object_id, &[])
} }
let data = data.unwrap();
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
// write compression info and LRC trailer
offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
} }
mod read_pki { pub mod yubikey_signer {
use der_parser::{ //! Signer implementation for yubikey
asn1_rs::Any,
ber::BerObjectContent, use crate::{
der::{parse_der_integer, parse_der_sequence_defined_g, DerObject}, error::{Error, Result},
error::BerError, piv::AlgorithmId,
piv::{sign_data, SlotId},
YubiKey,
};
use der::{
asn1::{Any, OctetString},
oid::db::rfc5912,
Encode, Sequence,
};
use sha2::{Digest, Sha256, Sha384};
use signature::Keypair;
use std::{cell::RefCell, fmt, io::Write, marker::PhantomData};
use x509_cert::spki::{
self, AlgorithmIdentifierOwned, DynSignatureAlgorithmIdentifier, EncodePublicKey,
SignatureBitStringEncoding, SubjectPublicKeyInfoRef,
}; };
use nom::{combinator, sequence::pair, IResult};
use rsa::{BigUint, RsaPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384}; type SigResult<T> = core::result::Result<T, signature::Error>;
use crate::{piv::AlgorithmId, Error, Result};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1): /// Key to be used to sign certificates
/// ```text pub trait KeyType {
/// RSAPublicKey ::= SEQUENCE { /// Error returned when working with signature
/// modulus INTEGER, -- n type Error: Into<signature::Error> + fmt::Debug;
/// publicExponent INTEGER -- e /// The signature type returned by the signer
/// } type Signature: SignatureBitStringEncoding
/// ``` + for<'s> TryFrom<&'s [u8], Error = Self::Error>
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RsaPublicKey> { + fmt::Debug;
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], (DerObject<'_>, DerObject<'_>), BerError> { /// The public key used to verify signature
parse_der_sequence_defined_g(|i, _| pair(parse_der_integer, parse_der_integer)(i))(i) type VerifyingKey: EncodePublicKey
} + DynSignatureAlgorithmIdentifier
+ Clone
+ From<Self::PublicKey>;
/// Public key type used to load the SPKI formatted key
type PublicKey: for<'a> TryFrom<SubjectPublicKeyInfoRef<'a>, Error = spki::Error>;
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> { /// The algorithm used when talking with the yubikey
combinator::map(parse_rsa_pubkey, |(modulus, public_exponent)| { const ALGORITHM: AlgorithmId;
let n = match modulus.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
let e = match public_exponent.content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
(n, e) /// Prepare buffer before submitting it for signature
})(i) fn prepare(input: &[u8]) -> SigResult<Vec<u8>>;
} /// Read back the signature from the device
fn read_signature(input: &[u8]) -> SigResult<Self::Signature>;
let (n, e) = match rsa_pubkey_parts(encoded) {
Ok((_, res)) => res,
_ => return Err(Error::InvalidObject),
};
RsaPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
} }
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1): impl KeyType for p256::NistP256 {
/// ```text const ALGORITHM: AlgorithmId = AlgorithmId::EccP256;
/// ECParameters ::= CHOICE { type Error = ecdsa::Error;
/// namedCurve OBJECT IDENTIFIER type Signature = p256::ecdsa::DerSignature;
/// -- implicitCurve NULL type VerifyingKey = p256::ecdsa::VerifyingKey;
/// -- specifiedCurve SpecifiedECDomain type PublicKey = p256::ecdsa::VerifyingKey;
/// }
/// ```
pub(super) fn ec_parameters(parameters: &Any<'_>) -> Result<AlgorithmId> {
let curve_oid = parameters.as_oid().map_err(|_| Error::InvalidObject)?;
match curve_oid.to_string().as_str() { fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
OID_NIST_P256 => Ok(AlgorithmId::EccP256), Ok(Sha256::digest(input).to_vec())
OID_NIST_P384 => Ok(AlgorithmId::EccP384), }
_ => Err(Error::AlgorithmError),
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::from_bytes(input)
}
}
impl KeyType for p384::NistP384 {
const ALGORITHM: AlgorithmId = AlgorithmId::EccP384;
type Error = ecdsa::Error;
type Signature = p384::ecdsa::DerSignature;
type VerifyingKey = p384::ecdsa::VerifyingKey;
type PublicKey = p384::ecdsa::VerifyingKey;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
Ok(Sha384::digest(input).to_vec())
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::from_bytes(input)
}
}
/// Trait used to handle subtypes of RSA keys
pub trait RsaLength {
/// The length of the RSA key in bits
const BIT_LENGTH: usize;
/// The algorithm to use when talking with the Yubikey.
const ALGORITHM: AlgorithmId;
}
/// RSA 1024 bits key
pub struct Rsa1024;
impl RsaLength for Rsa1024 {
const BIT_LENGTH: usize = 1024;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa1024;
}
/// RSA 2048 bits key
pub struct Rsa2048;
impl RsaLength for Rsa2048 {
const BIT_LENGTH: usize = 2048;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa2048;
}
/// RSA keys used to sign certificates
pub struct YubiRsa<N: RsaLength> {
_len: PhantomData<N>,
}
impl<N: RsaLength> KeyType for YubiRsa<N> {
type Error = signature::Error;
type Signature = rsa::pkcs1v15::Signature;
type VerifyingKey = rsa::pkcs1v15::VerifyingKey<Sha256>;
type PublicKey = rsa::RsaPublicKey;
const ALGORITHM: AlgorithmId = N::ALGORITHM;
fn prepare(input: &[u8]) -> SigResult<Vec<u8>> {
let hashed = Sha256::digest(input).to_vec();
OctetString::new(hashed)
.map_err(|e| e.into())
.and_then(Self::emsa_pkcs1_1_5)
.map_err(signature::Error::from_source)
}
fn read_signature(input: &[u8]) -> SigResult<Self::Signature> {
Self::Signature::try_from(input)
}
}
impl<N: RsaLength> YubiRsa<N> {
/// https://www.rfc-editor.org/rfc/rfc8017#section-9.2
fn emsa_pkcs1_1_5(digest: OctetString) -> Result<Vec<u8>> {
/// https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4
#[derive(Debug, Sequence)]
struct DigestInfo {
digest_algorithm: AlgorithmIdentifierOwned,
digest: OctetString,
}
let em_len = N::BIT_LENGTH / 8;
let null = Any::null();
let t = DigestInfo {
digest_algorithm: AlgorithmIdentifierOwned {
oid: rfc5912::ID_SHA_256,
parameters: Some(null),
},
digest,
};
let t = t.to_der()?;
let ps = vec![0xff; em_len - t.len() - 3];
assert!(ps.len() >= 8, "spec violation");
let mut out = Vec::with_capacity(em_len);
out.write(&[0x00, 0x01]).map_err(|_| Error::MemoryError)?;
out.write(&ps).map_err(|_| Error::MemoryError)?;
out.write(&[0x00]).map_err(|_| Error::MemoryError)?;
out.write(&t).map_err(|_| Error::MemoryError)?;
Ok(out)
}
}
/// The entrypoint to sign data with the yubikey.
pub struct Signer<'y, KT: KeyType> {
yubikey: RefCell<&'y mut YubiKey>,
key: SlotId,
public_key: KT::VerifyingKey,
}
impl<'y, KT: KeyType> Signer<'y, KT> {
/// Create new Signer
pub fn new(
yubikey: &'y mut YubiKey,
key: SlotId,
subject_pki: SubjectPublicKeyInfoRef<'_>,
) -> Result<Self> {
let public_key = KT::PublicKey::try_from(subject_pki).map_err(|_| Error::ParseError)?;
let public_key = public_key.into();
Ok(Self {
yubikey: RefCell::new(yubikey),
key,
public_key,
})
}
}
impl<'y, KT: KeyType> Keypair for Signer<'y, KT> {
type VerifyingKey = KT::VerifyingKey;
fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey {
self.public_key.clone()
}
}
impl<'y, KT: KeyType> DynSignatureAlgorithmIdentifier for Signer<'y, KT> {
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
self.verifying_key().signature_algorithm_identifier()
}
}
impl<'y, KT: KeyType> signature::Signer<KT::Signature> for Signer<'y, KT> {
fn try_sign(&self, msg: &[u8]) -> SigResult<KT::Signature> {
let data = KT::prepare(msg)?;
let out = sign_data(
&mut self.yubikey.borrow_mut(),
&data,
KT::ALGORITHM,
self.key,
)
.map_err(signature::Error::from_source)?;
let out = KT::read_signature(&out)?;
Ok(out)
} }
} }
} }
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
use std::io::Write;
use x509::der::write::{der_integer, der_sequence};
/// Encodes a usize as an ASN.1 integer using DER.
fn der_integer_biguint<'a, W: Write + 'a>(num: &'a BigUint) -> impl SerializeFn<W> + 'a {
move |w: WriteContext<W>| der_integer(&num.to_bytes_be())(w)
}
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RsaPublicKey,
) -> impl SerializeFn<W> + 'a {
der_sequence((
der_integer_biguint(pubkey.n()),
der_integer_biguint(pubkey.e()),
))
}
}
+7 -11
View File
@@ -30,7 +30,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{Error, Result, YubiKey}; use crate::{Result, YubiKey};
use std::fmt::{self, Debug, Display}; use std::fmt::{self, Debug, Display};
use uuid::Uuid; use uuid::Uuid;
@@ -61,6 +61,7 @@ const OBJ_CHUID: u32 = 0x005f_c102;
/// - 0x35: Exp. Date (hard-coded) /// - 0x35: Exp. Date (hard-coded)
/// - 0x3e: Signature (hard-coded, empty) /// - 0x3e: Signature (hard-coded, empty)
/// - 0xfe: Error Detection Code (hard-coded) /// - 0xfe: Error Detection Code (hard-coded)
#[allow(dead_code)]
const CHUID_TMPL: &[u8] = &[ const CHUID_TMPL: &[u8] = &[
0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58,
0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00,
@@ -86,12 +87,13 @@ impl ChuId {
pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] { pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] {
self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)] self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)]
.try_into() .try_into()
.unwrap() .expect("should be FASCN_SIZE")
} }
/// Return Card UUID/GUID component of CHUID /// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Uuid { pub fn uuid(&self) -> Uuid {
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap() Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)])
.expect("should be UUID-sized")
} }
/// Return expiration date component of CHUID /// Return expiration date component of CHUID
@@ -99,24 +101,18 @@ impl ChuId {
pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] { pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)] self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
.try_into() .try_into()
.unwrap() .expect("should be EXPIRATION_SIZE")
} }
/// Get Cardholder Unique Identifier (CHUID) /// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> { pub fn get(yubikey: &mut YubiKey) -> Result<ChuId> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_CHUID)?; let response = txn.fetch_object(OBJ_CHUID)?;
Ok(response[..Self::BYTE_SIZE].try_into().map(Self)?)
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
} }
/// Set Cardholder Unique Identifier (CHUID) /// Set Cardholder Unique Identifier (CHUID)
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> { pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
let mut buf = CHUID_TMPL.to_vec(); let mut buf = CHUID_TMPL.to_vec();
buf[..Self::BYTE_SIZE].copy_from_slice(&self.0); buf[..Self::BYTE_SIZE].copy_from_slice(&self.0);
+1 -1
View File
@@ -111,7 +111,7 @@ impl Config {
error!("pin timestamp in admin metadata is an invalid size"); error!("pin timestamp in admin metadata is an invalid size");
} else { } else {
// TODO(tarcieri): double-check endianness is correct // TODO(tarcieri): double-check endianness is correct
let pin_last_changed = u32::from_le_bytes(item.try_into().unwrap()); let pin_last_changed = u32::from_le_bytes([item[0], item[1], item[2], item[3]]);
if pin_last_changed != 0 { if pin_last_changed != 0 {
config.pin_last_changed = config.pin_last_changed =
+18
View File
@@ -164,6 +164,18 @@ impl Display for Error {
} }
} }
impl From<std::array::TryFromSliceError> for Error {
fn from(_: std::array::TryFromSliceError) -> Error {
Error::SizeError
}
}
impl From<std::time::SystemTimeError> for Error {
fn from(_: std::time::SystemTimeError) -> Error {
Error::GenericError
}
}
impl From<pcsc::Error> for Error { impl From<pcsc::Error> for Error {
fn from(err: pcsc::Error) -> Error { fn from(err: pcsc::Error) -> Error {
Error::PcscError { inner: Some(err) } Error::PcscError { inner: Some(err) }
@@ -179,3 +191,9 @@ impl std::error::Error for Error {
} }
} }
} }
impl From<x509_cert::der::Error> for Error {
fn from(_err: x509_cert::der::Error) -> Error {
Error::ParseError
}
}
+9 -2
View File
@@ -2,9 +2,16 @@
#![doc( #![doc(
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png" html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo-sq.png"
)] )]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] #![warn(
clippy::mod_module_files,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
// Adapted from yubico-piv-tool: // Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/> // <https://github.com/Yubico/yubico-piv-tool/>
+2 -5
View File
@@ -71,7 +71,7 @@ impl<T: MetadataType> Default for Metadata<T> {
fn default() -> Self { fn default() -> Self {
Metadata { Metadata {
inner: Zeroizing::new(vec![]), inner: Zeroizing::new(vec![]),
_marker: PhantomData::default(), _marker: PhantomData,
} }
} }
} }
@@ -82,13 +82,12 @@ impl<T: MetadataType> Metadata<T> {
let data = txn.fetch_object(T::obj_id())?; let data = txn.fetch_object(T::obj_id())?;
Ok(Metadata { Ok(Metadata {
inner: Tlv::parse_single(data, T::tag())?, inner: Tlv::parse_single(data, T::tag())?,
_marker: PhantomData::default(), _marker: PhantomData,
}) })
} }
/// Write metadata /// Write metadata
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> { pub(crate) fn write(&self, txn: &Transaction<'_>) -> Result<()> {
if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX { if self.inner.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError); return Err(Error::GenericError);
@@ -106,7 +105,6 @@ impl<T: MetadataType> Metadata<T> {
/// Delete metadata /// Delete metadata
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> { pub(crate) fn delete(txn: &Transaction<'_>) -> Result<()> {
txn.save_object(T::obj_id(), &[]) txn.save_object(T::obj_id(), &[])
} }
@@ -130,7 +128,6 @@ impl<T: MetadataType> Metadata<T> {
/// Set metadata item /// Set metadata item
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> { pub(crate) fn set_item(&mut self, tag: u8, item: &[u8]) -> Result<()> {
let mut cb_temp: usize = 0; let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0; let mut tag_temp: u8 = 0;
-5
View File
@@ -128,7 +128,6 @@ impl MgmKey {
/// Get derived management key (MGM) /// Get derived management key (MGM)
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> { pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
@@ -153,7 +152,6 @@ impl MgmKey {
/// Get protected management key (MGM) /// Get protected management key (MGM)
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> { pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
@@ -184,7 +182,6 @@ impl MgmKey {
/// ///
/// This will wipe any metadata related to derived and PIN-protected management keys. /// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_default(yubikey: &mut YubiKey) -> Result<()> { pub fn set_default(yubikey: &mut YubiKey) -> Result<()> {
MgmKey::default().set_manual(yubikey, false) MgmKey::default().set_manual(yubikey, false)
} }
@@ -196,7 +193,6 @@ impl MgmKey {
/// ///
/// This will wipe any metadata related to derived and PIN-protected management keys. /// This will wipe any metadata related to derived and PIN-protected management keys.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
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()?;
@@ -256,7 +252,6 @@ impl MgmKey {
/// ///
/// This enables key management operations to be performed with access to the PIN. /// This enables key management operations to be performed with access to the PIN.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> { pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
+9 -8
View File
@@ -41,7 +41,6 @@ const TAG_MSCMAP: u8 = 0x81;
/// ///
/// Defined in Microsoft's Smart Card Minidriver Specification: /// Defined in Microsoft's Smart Card Minidriver Specification:
/// <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)> /// <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MsContainer { pub struct MsContainer {
/// Container name. /// Container name.
@@ -141,7 +140,7 @@ impl MsContainer {
let name_bytes_len = Self::NAME_LEN * 2; let name_bytes_len = Self::NAME_LEN * 2;
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() { for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
name[i] = u16::from_le_bytes(chunk.try_into().unwrap()); name[i] = u16::from_le_bytes([chunk[0], chunk[1]]);
} }
let mut cert_fingerprint = [0u8; 20]; let mut cert_fingerprint = [0u8; 20];
@@ -151,11 +150,10 @@ impl MsContainer {
name, name,
slot: bytes[name_bytes_len].try_into()?, slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1], key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes( key_size_bits: u16::from_le_bytes([
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)] bytes[name_bytes_len + 2],
.try_into() bytes[name_bytes_len + 3],
.unwrap(), ]),
),
flags: bytes[name_bytes_len + 4], flags: bytes[name_bytes_len + 4],
pin_id: bytes[name_bytes_len + 5], pin_id: bytes[name_bytes_len + 5],
associated_echd_container: bytes[name_bytes_len + 6], associated_echd_container: bytes[name_bytes_len + 6],
@@ -184,7 +182,10 @@ impl MsContainer {
bytes.push(self.pin_id); bytes.push(self.pin_id);
bytes.push(self.associated_echd_container); bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint); bytes.extend_from_slice(&self.cert_fingerprint);
bytes.as_slice().try_into().unwrap() bytes
.as_slice()
.try_into()
.expect("should be REC_LEN-sized")
} }
} }
-1
View File
@@ -57,7 +57,6 @@ const TAG_MSROOTS_MID: u8 = 0x83;
/// ///
/// For more information, see: /// For more information, see:
/// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots> /// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub struct MsRoots(Vec<u8>); pub struct MsRoots(Vec<u8>);
impl MsRoots { impl MsRoots {
+45 -40
View File
@@ -44,7 +44,7 @@
use crate::{ use crate::{
apdu::{Ins, StatusWords}, apdu::{Ins, StatusWords},
certificate::{self, Certificate, PublicKeyInfo}, certificate::{self, Certificate},
consts::CB_OBJ_MAX, consts::CB_OBJ_MAX,
error::{Error, Result}, error::{Error, Result},
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
@@ -53,15 +53,16 @@ use crate::{
yubikey::YubiKey, yubikey::YubiKey,
Buffer, ObjectId, Buffer, ObjectId,
}; };
use elliptic_curve::sec1::EncodedPoint as EcPublicKey; use elliptic_curve::{sec1::EncodedPoint as EcPublicKey, PublicKey};
use log::{debug, error, warn}; use log::{debug, error, warn};
use p256::NistP256; use p256::NistP256;
use p384::NistP384; use p384::NistP384;
use rsa::{BigUint, RsaPublicKey}; use rsa::{pkcs8::EncodePublicKey, BigUint, RsaPublicKey};
use std::{ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
str::FromStr, str::FromStr,
}; };
use x509_cert::{der::Decode, spki::SubjectPublicKeyInfoOwned};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use { use {
@@ -441,10 +442,11 @@ impl ManagementSlotId {
} }
/// Personal Identity Verification (PIV) key slots /// Personal Identity Verification (PIV) key slots
pub const SLOTS: [SlotId; 27] = [ pub const SLOTS: [SlotId; 28] = [
SlotId::Authentication, SlotId::Authentication,
SlotId::Signature, SlotId::Signature,
SlotId::KeyManagement, SlotId::KeyManagement,
SlotId::Attestation,
SlotId::Retired(RetiredSlotId::R1), SlotId::Retired(RetiredSlotId::R1),
SlotId::Retired(RetiredSlotId::R2), SlotId::Retired(RetiredSlotId::R2),
SlotId::Retired(RetiredSlotId::R3), SlotId::Retired(RetiredSlotId::R3),
@@ -519,7 +521,6 @@ impl AlgorithmId {
} }
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn get_elem_len(self) -> usize { fn get_elem_len(self) -> usize {
match self { match self {
AlgorithmId::Rsa1024 => 64, AlgorithmId::Rsa1024 => 64,
@@ -530,7 +531,6 @@ impl AlgorithmId {
} }
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn get_param_tag(self) -> u8 { fn get_param_tag(self) -> u8 {
match self { match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01, AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
@@ -591,7 +591,7 @@ pub fn generate(
algorithm: AlgorithmId, algorithm: AlgorithmId,
pin_policy: PinPolicy, pin_policy: PinPolicy,
touch_policy: TouchPolicy, touch_policy: TouchPolicy,
) -> Result<PublicKeyInfo> { ) -> Result<SubjectPublicKeyInfoOwned> {
// Keygen messages // Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type? // TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA"; const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
@@ -701,7 +701,6 @@ pub fn generate(
} }
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
fn write_key( fn write_key(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
slot: SlotId, slot: SlotId,
@@ -750,7 +749,6 @@ fn write_key(
/// The key data that makes up an RSA key. /// The key data that makes up an RSA key.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub struct RsaKeyData { pub struct RsaKeyData {
/// The secret prime `p`. /// The secret prime `p`.
p: Buffer, p: Buffer,
@@ -766,10 +764,12 @@ pub struct RsaKeyData {
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
impl RsaKeyData { impl RsaKeyData {
/// Generates a new RSA key data set from two randomly generated, secret, primes. /// Generates a new RSA key data set from two (randomly generated) secret primes.
/// ///
/// Panics if `secret_p` or `secret_q` are invalid primes. /// # Returns
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Self { /// - `Ok(key_data)` if `secret_p` and `secret_q` are valid primes.
/// - `Err(Error::AlgorithmError)` if `secret_p`/`secret_q` are invalid primes.
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Result<Self> {
let p = BigUint::from_bytes_be(secret_p); let p = BigUint::from_bytes_be(secret_p);
let q = BigUint::from_bytes_be(secret_q); let q = BigUint::from_bytes_be(secret_q);
@@ -780,10 +780,10 @@ impl RsaKeyData {
p_t.lcm(&q_t) p_t.lcm(&q_t)
}; };
let exp = BigUint::from_u64(KEYDATA_RSA_EXP).unwrap(); let exp = BigUint::from_u64(KEYDATA_RSA_EXP).ok_or(Error::AlgorithmError)?;
let d = exp.mod_inverse(&totient).unwrap(); let d = exp.mod_inverse(&totient).ok_or(Error::AlgorithmError)?;
let d = d.to_biguint().unwrap(); let d = d.to_biguint().ok_or(Error::AlgorithmError)?;
// We calculate the optimization values ahead of time, instead of making the user // We calculate the optimization values ahead of time, instead of making the user
// do so. // do so.
@@ -791,16 +791,16 @@ impl RsaKeyData {
let dp = &d % (&p - BigUint::one()); let dp = &d % (&p - BigUint::one());
let dq = &d % (&q - BigUint::one()); let dq = &d % (&q - BigUint::one());
let qinv = q.clone().mod_inverse(&p).unwrap(); let qinv = q.clone().mod_inverse(&p).ok_or(Error::AlgorithmError)?;
let (_, qinv) = qinv.to_bytes_be(); let (_, qinv) = qinv.to_bytes_be();
RsaKeyData { Ok(RsaKeyData {
p: Zeroizing::new(p.to_bytes_be()), p: Zeroizing::new(p.to_bytes_be()),
q: Zeroizing::new(q.to_bytes_be()), q: Zeroizing::new(q.to_bytes_be()),
dp: Zeroizing::new(dp.to_bytes_be()), dp: Zeroizing::new(dp.to_bytes_be()),
dq: Zeroizing::new(dq.to_bytes_be()), dq: Zeroizing::new(dq.to_bytes_be()),
qinv: Zeroizing::new(qinv), qinv: Zeroizing::new(qinv),
} })
} }
fn total_len(&self) -> usize { fn total_len(&self) -> usize {
@@ -812,7 +812,6 @@ impl RsaKeyData {
/// ///
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`. /// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn import_rsa_key( pub fn import_rsa_key(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
slot: SlotId, slot: SlotId,
@@ -847,7 +846,6 @@ pub fn import_rsa_key(
/// ///
/// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`. /// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn import_ecc_key( pub fn import_ecc_key(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
slot: SlotId, slot: SlotId,
@@ -876,7 +874,6 @@ pub fn import_ecc_key(
/// ///
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html> /// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer> { pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer> {
let templ = [0, Ins::Attest.code(), key.into(), 0]; let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
@@ -912,7 +909,6 @@ pub fn sign_data(
/// Decrypt data using a PIV key. /// Decrypt data using a PIV key.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn decrypt_data( pub fn decrypt_data(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
input: &[u8], input: &[u8],
@@ -955,7 +951,7 @@ pub struct SlotMetadata {
/// Imported or generated key /// Imported or generated key
pub origin: Option<Origin>, pub origin: Option<Origin>,
/// Pub key of the key /// Pub key of the key
pub public: Option<PublicKeyInfo>, pub public: Option<SubjectPublicKeyInfoOwned>,
/// Whether PIN PUK and management key are default /// Whether PIN PUK and management key are default
pub default: Option<bool>, pub default: Option<bool>,
/// Number of retries left /// Number of retries left
@@ -1110,7 +1106,7 @@ fn read_public_key(
algorithm: AlgorithmId, algorithm: AlgorithmId,
input: &[u8], input: &[u8],
skip_asn1_tag: bool, skip_asn1_tag: bool,
) -> Result<PublicKeyInfo> { ) -> Result<SubjectPublicKeyInfoOwned> {
// TODO(str4d): Response is wrapped in an ASN.1 TLV: // TODO(str4d): Response is wrapped in an ASN.1 TLV:
// //
// 0x7f 0x49 -> Application | Constructed | 0x49 // 0x7f 0x49 -> Application | Constructed | 0x49
@@ -1159,14 +1155,17 @@ fn read_public_key(
} }
let exp = exp_tlv.value.to_vec(); let exp = exp_tlv.value.to_vec();
Ok(PublicKeyInfo::Rsa { let pubkey = RsaPublicKey::new(
algorithm, BigUint::from_bytes_be(&modulus),
pubkey: RsaPublicKey::new( BigUint::from_bytes_be(&exp),
BigUint::from_bytes_be(&modulus), )
BigUint::from_bytes_be(&exp), .map_err(|_| Error::InvalidObject)?;
) Ok(SubjectPublicKeyInfoOwned::from_der(
.map_err(|_| Error::InvalidObject)?, pubkey
}) .to_public_key_der()
.map_err(|_| Error::ParseError)?
.as_bytes(),
)?)
} }
AlgorithmId::EccP256 | AlgorithmId::EccP384 => { AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths // 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
@@ -1194,16 +1193,22 @@ fn read_public_key(
let point = tlv.value.to_vec(); let point = tlv.value.to_vec();
match algorithm { let pubkey = match algorithm {
AlgorithmId::EccP256 => { AlgorithmId::EccP256 => PublicKey::<NistP256>::try_from(
EcPublicKey::<NistP256>::from_bytes(point).map(PublicKeyInfo::EcP256) EcPublicKey::<NistP256>::from_bytes(point).map_err(|_| Error::InvalidObject)?,
} )
AlgorithmId::EccP384 => { .map_err(|_| Error::InvalidObject)?
EcPublicKey::<NistP384>::from_bytes(point).map(PublicKeyInfo::EcP384) .to_public_key_der(),
} AlgorithmId::EccP384 => PublicKey::<NistP384>::try_from(
EcPublicKey::<NistP384>::from_bytes(point).map_err(|_| Error::InvalidObject)?,
)
.map_err(|_| Error::InvalidObject)?
.to_public_key_der(),
_ => return Err(Error::AlgorithmError), _ => return Err(Error::AlgorithmError),
} }
.map_err(|_| Error::InvalidObject) .map_err(|_| Error::InvalidObject)?;
Ok(SubjectPublicKeyInfoOwned::from_der(pubkey.as_bytes())?)
} }
} }
} }
+5 -3
View File
@@ -1,6 +1,6 @@
//! Support for enumerating available PC/SC card readers. //! Support for enumerating available PC/SC card readers.
use crate::{Result, YubiKey}; use crate::{Error, Result, YubiKey};
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::CStr, ffi::CStr,
@@ -43,7 +43,8 @@ impl Context {
let Self { ctx, reader_names } = self; let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = { let reader_cstrs: Vec<_> = {
let c = ctx.lock().unwrap(); // TODO(tarcieri): better error?
let c = ctx.lock().map_err(|_| Error::GenericError)?;
// ensure PC/SC context is valid // ensure PC/SC context is valid
c.is_valid()?; c.is_valid()?;
@@ -90,7 +91,8 @@ impl<'ctx> Reader<'ctx> {
/// Connect to this reader, returning its `pcsc::Card`. /// Connect to this reader, returning its `pcsc::Card`.
pub(crate) fn connect(&self) -> Result<pcsc::Card> { pub(crate) fn connect(&self) -> Result<pcsc::Card> {
let ctx = self.ctx.lock().unwrap(); // TODO(tarcieri): better error?
let ctx = self.ctx.lock().map_err(|_| Error::GenericError)?;
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?) Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
} }
} }
+1 -5
View File
@@ -96,11 +96,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
if response.data().len() < 3 { Ok(response.data()[..3].try_into().map(Version::new)?)
return Err(Error::SizeError);
}
Ok(Version::new(response.data()[..3].try_into().unwrap()))
} }
/// Get YubiKey device serial number. /// Get YubiKey device serial number.
+34 -27
View File
@@ -178,27 +178,44 @@ impl fmt::Debug for YubiKey {
impl YubiKey { impl YubiKey {
/// Open a connection to a YubiKey. /// Open a connection to a YubiKey.
/// ///
/// Returns an error if there is more than one YubiKey detected. /// Returns an error if more than one YubiKey is detected (or none at all).
///
/// NOTE: If multiple YubiKeys are connected, but we are only able to
/// open one of them (e.g. because the other one is in use, and the
/// connection doesn't allow sharing), the YubiKey that we were able to
/// open is returned.
/// ///
/// If you need to operate in environments with more than one YubiKey /// If you need to operate in environments with more than one YubiKey
/// attached to the same system, use [`YubiKey::open_by_serial`] or /// attached to the same system, use [`YubiKey::open_by_serial`] or
/// [`yubikey::reader::Context`][`Context`] to select from the available /// [`yubikey::reader::Context`][`Context`] to select from the available
/// PC/SC readers. /// PC/SC readers.
pub fn open() -> Result<Self> { pub fn open() -> Result<Self> {
let mut yubikey: Option<Self> = None;
let mut readers = Context::open()?; let mut readers = Context::open()?;
let mut reader_iter = readers.iter()?; for reader in readers.iter()? {
if let Ok(yk_found) = reader.open() {
if let Some(yk_stored) = yubikey {
// We found two YubiKeys, so we won't use either.
// Don't reset them.
let _ = yk_stored.disconnect(pcsc::Disposition::LeaveCard);
let _ = yk_found.disconnect(pcsc::Disposition::LeaveCard);
if let Some(reader) = reader_iter.next() { error!("multiple YubiKeys detected!");
if reader_iter.next().is_some() { return Err(Error::PcscError { inner: None });
error!("multiple YubiKeys detected!"); } else {
return Err(Error::PcscError { inner: None }); yubikey = Some(yk_found);
}
} }
return reader.open();
} }
error!("no YubiKey detected!"); if let Some(yubikey) = yubikey {
Err(Error::NotFound) // We found exactly one YubiKey that we could open, so we return it.
Ok(yubikey)
} else {
error!("no YubiKey detected!");
Err(Error::NotFound)
}
} }
/// Open a YubiKey with a specific serial number. /// Open a YubiKey with a specific serial number.
@@ -243,7 +260,6 @@ impl YubiKey {
/// Reconnect to a YubiKey. /// Reconnect to a YubiKey.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn reconnect(&mut self) -> Result<()> { pub fn reconnect(&mut self) -> Result<()> {
info!("trying to reconnect to current reader"); info!("trying to reconnect to current reader");
@@ -355,7 +371,7 @@ impl YubiKey {
} }
// send a response to the cards challenge and a challenge of our own. // send a response to the cards challenge and a challenge of our own.
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap()); let response = mgm_key.decrypt(challenge.data()[4..12].try_into()?);
let mut data = [0u8; 22]; let mut data = [0u8; 22];
data[0] = TAG_DYN_AUTH; data[0] = TAG_DYN_AUTH;
@@ -397,7 +413,6 @@ impl YubiKey {
/// Deauthenticate. /// Deauthenticate.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn deauthenticate(&mut self) -> Result<()> { pub fn deauthenticate(&mut self) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -456,7 +471,6 @@ impl YubiKey {
/// Set the number of PIN retries. /// Set the number of PIN retries.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<()> { pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<()> {
// Special case: if either retry count is 0, it's a successful no-op // Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 { if pin_tries == 0 || puk_tries == 0 {
@@ -481,7 +495,6 @@ impl YubiKey {
/// ///
/// The default PIN code is `123456`. /// The default PIN code is `123456`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> { pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> {
{ {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -497,7 +510,6 @@ impl YubiKey {
/// Set PIN last changed. /// Set PIN last changed.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<()> { pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<()> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
@@ -505,8 +517,7 @@ impl YubiKey {
// TODO(tarcieri): double check this is little endian // TODO(tarcieri): double check this is little endian
let tnow = SystemTime::now() let tnow = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)?
.unwrap()
.as_secs() .as_secs()
.to_le_bytes(); .to_le_bytes();
@@ -533,7 +544,6 @@ impl YubiKey {
/// ///
/// The default PUK code is `12345678`. /// The default PUK code is `12345678`.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> { pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.change_ref(ChangeRefAction::ChangePuk, current_puk, new_puk) txn.change_ref(ChangeRefAction::ChangePuk, current_puk, new_puk)
@@ -541,7 +551,6 @@ 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")))]
pub fn block_puk(&mut self) -> 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;
@@ -605,7 +614,6 @@ impl YubiKey {
/// Unblock a Personal Identification Number (PIN) using a previously /// Unblock a Personal Identification Number (PIN) using a previously
/// configured PIN Unblocking Key (PUK). /// configured PIN Unblocking Key (PUK).
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> { pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin) txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin)
@@ -613,7 +621,6 @@ impl YubiKey {
/// Fetch an object from the YubiKey. /// Fetch an object from the YubiKey.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer> { pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.fetch_object(object_id) txn.fetch_object(object_id)
@@ -621,7 +628,6 @@ impl YubiKey {
/// Save an object. /// Save an object.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> { pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.save_object(object_id, indata) txn.save_object(object_id, indata)
@@ -629,7 +635,6 @@ impl YubiKey {
/// Get an auth challenge. /// Get an auth challenge.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> { pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -642,12 +647,15 @@ impl YubiKey {
return Err(Error::AuthenticationError); return Err(Error::AuthenticationError);
} }
Ok(response.data()[4..12].try_into().unwrap()) Ok(response
.data()
.get(4..12)
.ok_or(Error::SizeError)?
.try_into()?)
} }
/// Verify an auth response. /// Verify an auth response.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> { pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> {
let mut data = [0u8; 12]; let mut data = [0u8; 12];
data[0] = 0x7c; data[0] = 0x7c;
@@ -678,7 +686,6 @@ impl YubiKey {
/// ///
/// The reset function is only available when both pins are blocked. /// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
pub fn reset_device(&mut self) -> Result<()> { pub fn reset_device(&mut self) -> Result<()> {
let templ = [0, Ins::Reset.code(), 0, 0]; let templ = [0, Ins::Reset.code(), 0, 0];
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
+33 -74
View File
@@ -6,14 +6,15 @@
use log::trace; use log::trace;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use rsa::pkcs1v15; use rsa::{pkcs1v15, RsaPublicKey};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use signature::hazmat::PrehashVerifier; use signature::hazmat::PrehashVerifier;
use std::{env, str::FromStr, sync::Mutex}; use std::{env, str::FromStr, sync::Mutex, time::Duration};
use x509::RelativeDistinguishedName; use x509_cert::{der::Encode, name::Name, serial_number::SerialNumber, time::Validity};
use yubikey::{ use yubikey::{
certificate, certificate,
certificate::{Certificate, PublicKeyInfo}, certificate::yubikey_signer,
certificate::Certificate,
piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId}, piv::{self, AlgorithmId, Key, ManagementSlotId, RetiredSlotId, SlotId},
Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey, Error, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
}; };
@@ -147,7 +148,7 @@ fn test_set_mgmkey() {
// Certificate support // Certificate support
// //
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate { fn generate_self_signed_cert<KT: yubikey_signer::KeyType>() -> Certificate {
let mut yubikey = YUBIKEY.lock().unwrap(); let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
@@ -159,25 +160,28 @@ fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
let generated = piv::generate( let generated = piv::generate(
&mut yubikey, &mut yubikey,
slot, slot,
algorithm, KT::ALGORITHM,
PinPolicy::Default, PinPolicy::Default,
TouchPolicy::Default, TouchPolicy::Default,
) )
.unwrap(); .unwrap();
let mut serial = [0u8; 20]; // 0x80 0x00 ... (20bytes) is invalid because of high MSB (serial will keep the sign)
// we'll limit ourselves to 19 bytes serial.
let mut serial = [0u8; 19];
OsRng.fill_bytes(&mut serial); OsRng.fill_bytes(&mut serial);
let serial = SerialNumber::new(&serial[..]).expect("serial can't be more than 20 bytes long");
let validity = Validity::from_now(Duration::new(500000, 0)).unwrap();
// Generate a self-signed certificate for the new key. // Generate a self-signed certificate for the new key.
let extensions: &[x509::Extension<'_, &[u64]>] = &[]; let cert_result = Certificate::generate_self_signed::<_, KT>(
let cert_result = Certificate::generate_self_signed(
&mut yubikey, &mut yubikey,
slot, slot,
serial, serial,
None, validity,
&[RelativeDistinguishedName::common_name("testSubject")], Name::from_str("CN=testSubject").expect("parse name"),
generated, generated,
extensions, |_builder| Ok(()),
); );
assert!(cert_result.is_ok()); assert!(cert_result.is_ok());
@@ -189,18 +193,16 @@ fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
#[test] #[test]
#[ignore] #[ignore]
fn generate_self_signed_rsa_cert() { fn generate_self_signed_rsa_cert() {
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024); let cert = generate_self_signed_cert::<yubikey_signer::YubiRsa<yubikey_signer::Rsa1024>>();
// //
// Verify that the certificate is signed correctly // Verify that the certificate is signed correctly
// //
let pubkey = match cert.subject_pki() { let pubkey = RsaPublicKey::try_from(cert.subject_pki()).expect("valid rsa key");
PublicKeyInfo::Rsa { pubkey, .. } => pkcs1v15::VerifyingKey::<Sha256>::from(pubkey.clone()), let pubkey = pkcs1v15::VerifyingKey::<Sha256>::new(pubkey);
_ => unreachable!(),
};
let data = cert.as_ref(); let data = cert.cert.to_der().expect("serialize certificate");
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 = pkcs1v15::Signature::try_from(&data[data.len() - 128..]).unwrap(); let sig = pkcs1v15::Signature::try_from(&data[data.len() - 128..]).unwrap();
@@ -212,24 +214,20 @@ fn generate_self_signed_rsa_cert() {
#[test] #[test]
#[ignore] #[ignore]
fn generate_self_signed_ec_cert() { fn generate_self_signed_ec_cert() {
let cert = generate_self_signed_cert(AlgorithmId::EccP256); let cert = generate_self_signed_cert::<p256::NistP256>();
// //
// Verify that the certificate is signed correctly // Verify that the certificate is signed correctly
// //
let pubkey = match cert.subject_pki() { let vk = p256::ecdsa::VerifyingKey::try_from(cert.subject_pki()).expect("ecdsa key expected");
PublicKeyInfo::EcP256(pubkey) => pubkey,
_ => unreachable!(),
};
let data = cert.as_ref(); let data = cert.cert.to_der().expect("serialize certificate");
let tbs_cert_len = data[6] as usize; let tbs_cert_len = data[6] as usize;
let sig_algo_len = data[7 + tbs_cert_len + 1] as usize; let sig_algo_len = data[7 + tbs_cert_len + 1] as usize;
let sig_start = 7 + tbs_cert_len + 2 + sig_algo_len + 3; let sig_start = 7 + tbs_cert_len + 2 + sig_algo_len + 3;
let msg = &data[4..7 + tbs_cert_len]; let msg = &data[4..7 + tbs_cert_len];
let sig = p256::ecdsa::Signature::from_der(&data[sig_start..]).unwrap(); let sig = p256::ecdsa::Signature::from_der(&data[sig_start..]).unwrap();
let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey.as_bytes()).unwrap();
use p256::ecdsa::signature::Verifier; use p256::ecdsa::signature::Verifier;
assert!(vk.verify(msg, &sig).is_ok()); assert!(vk.verify(msg, &sig).is_ok());
@@ -269,11 +267,11 @@ fn test_slot_id_display() {
assert_eq!( assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Pin)), format!("{}", SlotId::Management(ManagementSlotId::Pin)),
"PIN" "Pin"
); );
assert_eq!( assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Puk)), format!("{}", SlotId::Management(ManagementSlotId::Puk)),
"PUK" "Puk"
); );
assert_eq!( assert_eq!(
format!("{}", SlotId::Management(ManagementSlotId::Management)), format!("{}", SlotId::Management(ManagementSlotId::Management)),
@@ -305,53 +303,14 @@ fn test_read_metadata() {
) )
.unwrap(); .unwrap();
let metadata = piv::metadata(&mut yubikey, slot).unwrap(); match piv::metadata(&mut yubikey, slot) {
Ok(metadata) => assert_eq!(metadata.public, Some(generated)),
assert_eq!(metadata.public, Some(generated)); Err(Error::NotSupported) => {
} // Some YubiKeys don't support metadata
eprintln!("metadata not supported by this YubiKey");
#[test] }
#[ignore] Err(err) => panic!("{}", err),
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] #[test]