Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee3702a65e | |||
| 45915e5e5a | |||
| 75ce24a3ea | |||
| 78313360a1 | |||
| d226209ea4 | |||
| de142256d0 | |||
| 485d49a6c8 | |||
| 9932d05428 | |||
| 363648bbc5 | |||
| 6a1e1603ef | |||
| 8cf18d2986 | |||
| 07281440c0 | |||
| cd76a55318 | |||
| 23bbf1b783 |
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 =
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+48
-46
@@ -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,
|
||||||
@@ -565,12 +565,9 @@ impl Key {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !buf.is_empty() {
|
if !buf.is_empty() {
|
||||||
match Certificate::from_bytes(buf) {
|
let cert = Certificate::from_bytes(buf)?;
|
||||||
Ok(cert) => keys.push(Key { slot, cert }),
|
keys.push(Key { slot, cert });
|
||||||
Err(Error::InvalidObject) => {} // skip slots we can't parse (e.g. Ed25519)
|
}
|
||||||
Err(other) => return Err(other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
@@ -594,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";
|
||||||
@@ -704,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,
|
||||||
@@ -753,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,
|
||||||
@@ -769,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);
|
||||||
|
|
||||||
@@ -783,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.
|
||||||
@@ -794,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 {
|
||||||
@@ -815,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,
|
||||||
@@ -850,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,
|
||||||
@@ -879,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()?;
|
||||||
@@ -915,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],
|
||||||
@@ -958,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
|
||||||
@@ -1113,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
|
||||||
@@ -1162,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
|
||||||
@@ -1197,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
@@ -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
@@ -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
@@ -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
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user