Certificate::generate_self_signed

This commit is contained in:
Jack Grigg
2019-12-15 10:59:50 +00:00
parent 5e8a014be2
commit 8ac78cafb8
12 changed files with 323 additions and 56 deletions
Generated
+34
View File
@@ -127,6 +127,16 @@ name = "cfg-if"
version = "0.1.10" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@@ -135,6 +145,11 @@ dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "cookie-factory"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "crypto-mac" name = "crypto-mac"
version = "0.7.0" version = "0.7.0"
@@ -344,6 +359,7 @@ dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@@ -773,6 +789,15 @@ dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "x509"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie-factory 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "x509-parser" name = "x509-parser"
version = "0.6.0" version = "0.6.0"
@@ -801,6 +826,8 @@ dependencies = [
name = "yubikey-piv" name = "yubikey-piv"
version = "0.0.3" version = "0.0.3"
dependencies = [ dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie-factory 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"der-parser 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "der-parser 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"elliptic-curve 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "elliptic-curve 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -810,13 +837,17 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pcsc 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "pcsc 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"secrecy 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "secrecy 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle-encoding 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "subtle-encoding 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"x509 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@@ -858,7 +889,9 @@ dependencies = [
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cookie-factory 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2b81f8a9b8f5ffa2ed91a82c53392837eab151baf682acf8322c909b6d4fe9"
"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
"checksum der-parser 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "537a7d6becb8c3cae3bab31439c248a12d43267ccc0b1a0333362b7cefb61c3e" "checksum der-parser 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "537a7d6becb8c3cae3bab31439c248a12d43267ccc0b1a0333362b7cefb61c3e"
"checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af" "checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af"
@@ -939,6 +972,7 @@ dependencies = [
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" "checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9"
"checksum x509 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea9d91eac9f9a39e79cbe4eb4f899a15aec4886cc069dc665fd086362e9fa908"
"checksum x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b86b92815858495482b74dab17c0b2b2399f7582b6e7ca621b87aebf8fd00e9" "checksum x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b86b92815858495482b74dab17c0b2b2399f7582b6e7ca621b87aebf8fd00e9"
"checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" "checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8"
"checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" "checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
+6
View File
@@ -22,6 +22,8 @@ members = [".", "cli"]
maintenance = { status = "experimental" } maintenance = { status = "experimental" }
[dependencies] [dependencies]
chrono = "0.4"
cookie-factory = "0.3"
der-parser = "3" der-parser = "3"
des = "0.3" des = "0.3"
elliptic-curve = "0.2" elliptic-curve = "0.2"
@@ -29,18 +31,22 @@ getrandom = "0.1"
hmac = "0.7" hmac = "0.7"
log = "0.4" log = "0.4"
nom = "5" nom = "5"
num-bigint = { version = "0.2", features = ["rand"] }
pbkdf2 = "0.3" pbkdf2 = "0.3"
pcsc = "2" pcsc = "2"
rsa = "0.2.0" rsa = "0.2.0"
secrecy = "0.6" secrecy = "0.6"
sha-1 = "0.8" sha-1 = "0.8"
sha2 = "0.8"
subtle = "2" subtle = "2"
subtle-encoding = "0.5" subtle-encoding = "0.5"
x509 = "0.1"
x509-parser = "0.6" x509-parser = "0.6"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.7" env_logger = "0.7"
rand = "0.5"
lazy_static = "1" lazy_static = "1"
[features] [features]
-1
View File
@@ -276,7 +276,6 @@ impl Response {
} }
/// Get the raw [`StatusWords`] code for this response. /// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u16 { pub fn code(&self) -> u16 {
self.status_words.code() self.status_words.code()
} }
+221 -6
View File
@@ -32,24 +32,27 @@
use crate::{ use crate::{
error::Error, error::Error,
key::{AlgorithmId, SlotId}, key::{sign_data, AlgorithmId, SlotId},
serialization::*, serialization::*,
transaction::Transaction, transaction::Transaction,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, Buffer,
}; };
use chrono::{DateTime, Utc};
use elliptic_curve::weierstrass::{ use elliptic_curve::weierstrass::{
curve::{NistP256, NistP384}, curve::{NistP256, NistP384},
PublicKey as EcPublicKey, PublicKey as EcPublicKey,
}; };
use log::error; use log::error;
use num_bigint::BigUint;
use rsa::{PublicKey, RSAPublicKey}; use rsa::{PublicKey, RSAPublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::ops::DerefMut;
use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo}; use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::CB_OBJ_MAX; use crate::CB_OBJ_MAX;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support. // TODO: Make these der_parser::oid::Oid constants when it has const fn support.
@@ -59,9 +62,7 @@ const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34"; const OID_NIST_P384: &str = "1.3.132.0.34";
const TAG_CERT: u8 = 0x70; const TAG_CERT: u8 = 0x70;
#[cfg(feature = "untested")]
const TAG_CERT_COMPRESS: u8 = 0x71; const TAG_CERT_COMPRESS: u8 = 0x71;
#[cfg(feature = "untested")]
const TAG_CERT_LRC: u8 = 0xFE; const TAG_CERT_LRC: u8 = 0xFE;
/// Information about how a [`Certificate`] is stored within a YubiKey. /// Information about how a [`Certificate`] is stored within a YubiKey.
@@ -95,6 +96,40 @@ 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`]. /// Information about a public key within a [`Certificate`].
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo { pub enum PublicKeyInfo {
@@ -161,6 +196,59 @@ impl PublicKeyInfo {
} }
} }
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(),
}
}
}
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 {
@@ -180,6 +268,107 @@ impl<'a> TryFrom<&'a [u8]> for Certificate {
} }
impl Certificate { impl Certificate {
/// Creates a new self-signed certificate for the given key. Writes the resulting
/// certificate to the slot before returning it.
pub fn generate_self_signed(
yubikey: &mut YubiKey,
key: SlotId,
serial: BigUint,
not_after: Option<DateTime<Utc>>,
subject: String,
subject_pki: PublicKeyInfo,
) -> Result<Self, Error> {
// Issuer and subject are the same in self-signed certificates
let issuer = subject.clone();
let mut tbs_cert = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
let signature_algorithm = match subject_pki.algorithm() {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => SignatureId::Sha256WithRsaEncryption,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => SignatureId::EcdsaWithSha256,
};
cookie_factory::gen(
x509::write::tbs_certificate(
&serial.to_bytes_be(),
&signature_algorithm,
&issuer,
Utc::now(),
not_after,
&subject,
&subject_pki,
),
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(&signature_algorithm),
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 cert = Certificate {
serial,
issuer,
subject,
subject_pki,
data,
};
cert.write(yubikey, key, CertInfo::Uncompressed)?;
Ok(cert)
}
/// Read a certificate from the given slot in the YubiKey /// Read a certificate from the given slot in the YubiKey
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> { pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
@@ -193,7 +382,6 @@ impl Certificate {
} }
/// Write this certificate into the YubiKey in the given slot /// Write this certificate into the YubiKey in the given slot
#[cfg(feature = "untested")]
pub fn write( pub fn write(
&self, &self,
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
@@ -295,7 +483,6 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
} }
/// Write certificate /// Write certificate
#[cfg(feature = "untested")]
pub(crate) fn write_certificate( pub(crate) fn write_certificate(
txn: &Transaction<'_>, txn: &Transaction<'_>,
slot: SlotId, slot: SlotId,
@@ -394,3 +581,31 @@ mod read_pki {
} }
} }
} }
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKey, 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()),
))
}
}
+5 -14
View File
@@ -38,8 +38,11 @@
// 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::{ use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate}, certificate::{self, Certificate},
error::Error, error::Error,
serialization::*,
settings,
yubikey::YubiKey, yubikey::YubiKey,
ObjectId, ObjectId,
}; };
@@ -47,32 +50,23 @@ use log::debug;
use std::convert::TryFrom; use std::convert::TryFrom;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::CB_OBJ_MAX;
use crate::{ use crate::{
apdu::{Ins, StatusWords},
certificate::PublicKeyInfo, certificate::PublicKeyInfo,
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
serialization::*, Buffer,
settings, Buffer, CB_OBJ_MAX,
}; };
#[cfg(feature = "untested")]
use elliptic_curve::weierstrass::PublicKey as EcPublicKey; use elliptic_curve::weierstrass::PublicKey as EcPublicKey;
#[cfg(feature = "untested")]
use log::{error, warn}; use log::{error, warn};
#[cfg(feature = "untested")]
use rsa::{BigUint, RSAPublicKey}; use rsa::{BigUint, RSAPublicKey};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")]
const CB_ECC_POINTP256: usize = 65; const CB_ECC_POINTP256: usize = 65;
#[cfg(feature = "untested")]
const CB_ECC_POINTP384: usize = 97; const CB_ECC_POINTP384: usize = 97;
#[cfg(feature = "untested")]
const TAG_RSA_MODULUS: u8 = 0x81; const TAG_RSA_MODULUS: u8 = 0x81;
#[cfg(feature = "untested")]
const TAG_RSA_EXP: u8 = 0x82; const TAG_RSA_EXP: u8 = 0x82;
#[cfg(feature = "untested")]
const TAG_ECC_POINT: u8 = 0x86; const TAG_ECC_POINT: u8 = 0x86;
/// Slot identifiers. /// Slot identifiers.
@@ -381,7 +375,6 @@ impl From<AlgorithmId> for u8 {
} }
} }
#[cfg(feature = "untested")]
impl AlgorithmId { impl AlgorithmId {
/// Writes the `AlgorithmId` in the format the YubiKey expects during key generation. /// Writes the `AlgorithmId` in the format the YubiKey expects during key generation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> { pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
@@ -435,7 +428,6 @@ impl Key {
} }
/// Generate key /// Generate key
#[cfg(feature = "untested")]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn generate( pub fn generate(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
@@ -758,7 +750,6 @@ pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer, Error> {
} }
/// Sign data using a PIV key /// Sign data using a PIV key
#[cfg(feature = "untested")]
pub fn sign_data( pub fn sign_data(
yubikey: &mut YubiKey, yubikey: &mut YubiKey,
raw_in: &[u8], raw_in: &[u8],
-2
View File
@@ -151,7 +151,6 @@ pub mod msroots;
pub mod policy; pub mod policy;
pub mod readers; pub mod readers;
mod serialization; mod serialization;
#[cfg(feature = "untested")]
pub mod settings; pub mod settings;
mod transaction; mod transaction;
pub mod yubikey; pub mod yubikey;
@@ -174,7 +173,6 @@ pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
pub(crate) const CB_BUF_MAX: usize = 3072; pub(crate) const CB_BUF_MAX: usize = 3072;
/// YubiKey max object size /// YubiKey max object size
#[cfg(feature = "untested")]
pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9; pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9;
pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
-3
View File
@@ -41,7 +41,6 @@ use crate::{
metadata, yubikey::YubiKey, CB_BUF_MAX, CB_OBJ_MAX, TAG_ADMIN, TAG_ADMIN_FLAGS_1, metadata, yubikey::YubiKey, CB_BUF_MAX, CB_OBJ_MAX, TAG_ADMIN, TAG_ADMIN_FLAGS_1,
TAG_ADMIN_SALT, TAG_PROTECTED, TAG_PROTECTED_MGM, TAG_ADMIN_SALT, TAG_PROTECTED, TAG_PROTECTED_MGM,
}; };
#[cfg(feature = "untested")]
use des::{ use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher}, block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3, TdesEde3,
@@ -282,7 +281,6 @@ impl MgmKey {
} }
/// Encrypt with 3DES key /// Encrypt with 3DES key
#[cfg(feature = "untested")]
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] { pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned(); let mut output = input.to_owned();
@@ -292,7 +290,6 @@ impl MgmKey {
} }
/// Decrypt with 3DES key /// Decrypt with 3DES key
#[cfg(feature = "untested")]
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] { pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned(); let mut output = input.to_owned();
-3
View File
@@ -1,6 +1,5 @@
//! Enums representing key policies. //! Enums representing key policies.
#[cfg(feature = "untested")]
use crate::{error::Error, serialization::Tlv}; use crate::{error::Error, serialization::Tlv};
/// Specifies how often the PIN needs to be entered for access to the credential in a /// Specifies how often the PIN needs to be entered for access to the credential in a
@@ -38,7 +37,6 @@ impl From<PinPolicy> for u8 {
impl PinPolicy { impl PinPolicy {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or /// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation. /// importation.
#[cfg(feature = "untested")]
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> { pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
match self { match self {
PinPolicy::Default => Ok(0), PinPolicy::Default => Ok(0),
@@ -83,7 +81,6 @@ impl From<TouchPolicy> for u8 {
impl TouchPolicy { impl TouchPolicy {
/// Writes the `TouchPolicy` in the format the YubiKey expects during key generation /// Writes the `TouchPolicy` in the format the YubiKey expects during key generation
/// or importation. /// or importation.
#[cfg(feature = "untested")]
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> { pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
match self { match self {
TouchPolicy::Default => Ok(0), TouchPolicy::Default => Ok(0),
-3
View File
@@ -79,7 +79,6 @@ impl<'a> Tlv<'a> {
} }
/// Writes a TLV to the given buffer. /// Writes a TLV to the given buffer.
#[cfg(feature = "untested")]
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize, Error> { pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize, Error> {
if buffer.len() < CB_OBJ_TAG_MIN { if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError); return Err(Error::SizeError);
@@ -99,7 +98,6 @@ impl<'a> Tlv<'a> {
/// Writes a TLV to the given buffer. /// Writes a TLV to the given buffer.
/// ///
/// `value` is guaranteed to be called with a mutable slice of length `length`. /// `value` is guaranteed to be called with a mutable slice of length `length`.
#[cfg(feature = "untested")]
pub(crate) fn write_as<Gen>( pub(crate) fn write_as<Gen>(
buffer: &mut [u8], buffer: &mut [u8],
tag: u8, tag: u8,
@@ -126,7 +124,6 @@ impl<'a> Tlv<'a> {
} }
/// Set length /// Set length
#[cfg(feature = "untested")]
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize, Error> { pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize, Error> {
if length < 0x80 { if length < 0x80 {
if buffer.is_empty() { if buffer.is_empty() {
+3 -8
View File
@@ -4,20 +4,17 @@ use crate::{
apdu::Response, apdu::Response,
apdu::{Ins, StatusWords, APDU}, apdu::{Ins, StatusWords, APDU},
error::Error, error::Error,
key::{AlgorithmId, SlotId},
serialization::*, serialization::*,
yubikey::*, yubikey::*,
Buffer, ObjectId, CB_BUF_MAX, PIV_AID, YK_AID, Buffer, ObjectId, CB_BUF_MAX, CB_OBJ_MAX, PIV_AID, YK_AID,
}; };
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto; use std::convert::TryInto;
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::{ use crate::mgm::{MgmKey, DES_LEN_3DES};
key::{AlgorithmId, SlotId},
mgm::{MgmKey, DES_LEN_3DES},
CB_OBJ_MAX,
};
const CB_PIN_MAX: usize = 8; const CB_PIN_MAX: usize = 8;
@@ -252,7 +249,6 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing /// This is the common backend for all public key encryption and signing
/// operations. /// operations.
// TODO(tarcieri): refactor this to be less gross/coupled. // TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command( pub(crate) fn authenticated_command(
&self, &self,
@@ -472,7 +468,6 @@ impl<'tx> Transaction<'tx> {
} }
/// Save an object. /// Save an object.
#[cfg(feature = "untested")]
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> { pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, Ins::PutData.code(), 0x3f, 0xff]; let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
+6 -15
View File
@@ -31,48 +31,41 @@
// 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::{ use crate::{
apdu::{Ins, APDU},
cccid::CCC, cccid::CCC,
chuid::CHUID, chuid::CHUID,
config::Config, config::Config,
error::Error, error::Error,
mgm::MgmKey,
readers::{Reader, Readers}, readers::{Reader, Readers},
transaction::Transaction, transaction::Transaction,
}; };
use log::{error, info}; use log::{error, info};
use pcsc::Card; use pcsc::Card;
use std::{ use std::{
convert::TryFrom, convert::{TryFrom, TryInto},
fmt::{self, Display}, fmt::{self, Display},
str::FromStr, str::FromStr,
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::{Ins, StatusWords, APDU}, apdu::StatusWords, metadata, Buffer, ObjectId, CB_BUF_MAX, CB_OBJ_MAX, MGMT_AID, TAG_ADMIN,
metadata, TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP,
mgm::MgmKey,
Buffer, ObjectId, CB_BUF_MAX, CB_OBJ_MAX, MGMT_AID, TAG_ADMIN, TAG_ADMIN_FLAGS_1,
TAG_ADMIN_TIMESTAMP,
}; };
#[cfg(feature = "untested")]
use getrandom::getrandom; use getrandom::getrandom;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use std::{ use std::time::{SystemTime, UNIX_EPOCH};
convert::TryInto,
time::{SystemTime, UNIX_EPOCH},
};
/// Flag for PUK blocked /// Flag for PUK blocked
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01; pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// 3DES authentication /// 3DES authentication
#[cfg(feature = "untested")]
pub(crate) const ALGO_3DES: u8 = 0x03; pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key /// Card management key
#[cfg(feature = "untested")]
pub(crate) const KEY_CARDMGM: u8 = 0x9b; pub(crate) const KEY_CARDMGM: u8 = 0x9b;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -82,7 +75,6 @@ pub(crate) const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub(crate) const CHREF_ACT_CHANGE_PUK: i32 = 2; pub(crate) const CHREF_ACT_CHANGE_PUK: i32 = 2;
#[cfg(feature = "untested")]
const TAG_DYN_AUTH: u8 = 0x7c; const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN /// Cached YubiKey PIN
@@ -274,7 +266,6 @@ impl YubiKey {
} }
/// Authenticate to the card using the provided management key (MGM). /// Authenticate to the card using the provided management key (MGM).
#[cfg(feature = "untested")]
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> { pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
+48 -1
View File
@@ -5,8 +5,15 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::trace; use log::trace;
use num_bigint::RandBigInt;
use rand::rngs::OsRng;
use std::{env, sync::Mutex}; use std::{env, sync::Mutex};
use yubikey_piv::{key::Key, Error, YubiKey}; use yubikey_piv::{
certificate::Certificate,
key::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
policy::{PinPolicy, TouchPolicy},
Error, MgmKey, YubiKey,
};
lazy_static! { lazy_static! {
/// Provide thread-safe access to a YubiKey /// Provide thread-safe access to a YubiKey
@@ -96,3 +103,43 @@ fn test_verify_pin() {
assert!(yubikey.verify_pin(b"000000").is_err()); assert!(yubikey.verify_pin(b"000000").is_err());
assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(yubikey.verify_pin(b"123456").is_ok());
} }
//
// Certificate support
//
#[test]
#[ignore]
fn generate_self_signed_cert() {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1);
// Generate a new key in the selected slot.
let generated = key::generate(
&mut yubikey,
slot,
AlgorithmId::EccP256,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let mut rng = OsRng::new().unwrap();
// Generate a self-signed certificate for the new key.
let cert_result = Certificate::generate_self_signed(
&mut yubikey,
slot,
rng.gen_biguint(20 * 8),
None,
"testSubject".to_owned(),
generated,
);
assert!(cert_result.is_ok());
trace!("cert: {:?}", cert_result.unwrap());
}