use {
crate::{
cryptography::{rsa_oaep_post_decrypt_decode, PrivateKey},
remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError},
AppleCodesignError,
},
bcder::encode::Values,
bytes::Bytes,
log::{error, warn},
signature::Signer,
std::{
ops::DerefMut,
sync::{Arc, Mutex, MutexGuard},
},
x509::SubjectPublicKeyInfo,
x509_certificate::{
asn1time, rfc3280, rfc5280, CapturedX509Certificate, EcdsaCurve, KeyAlgorithm,
KeyInfoSigner, Sign, Signature, SignatureAlgorithm, X509CertificateError,
},
yubikey::{
certificate::{CertInfo, Certificate as YkCertificate},
piv::{import_ecc_key, import_rsa_key, AlgorithmId, SlotId},
Error as YkError, MgmKey, PinPolicy, TouchPolicy, YubiKey as RawYubiKey,
},
zeroize::Zeroizing,
};
pub trait PinCallback: Fn() -> Result<Vec<u8>, AppleCodesignError> + Send + Sync {}
impl<T: Fn() -> Result<Vec<u8>, AppleCodesignError> + Send + Sync> PinCallback for T {}
fn algorithm_from_certificate(
cert: &CapturedX509Certificate,
) -> Result<AlgorithmId, X509CertificateError> {
let key_algorithm = cert
.key_algorithm()
.ok_or(X509CertificateError::UnknownKeyAlgorithm(format!(
"{:?}",
cert.key_algorithm_oid()
)))?;
match key_algorithm {
KeyAlgorithm::Rsa => match cert.rsa_public_key_data()?.modulus.as_slice().len() {
129 => Ok(AlgorithmId::Rsa1024),
257 => Ok(AlgorithmId::Rsa2048),
_ => Err(X509CertificateError::Other(
"unable to determine RSA key algorithm".into(),
)),
},
KeyAlgorithm::Ed25519 => Err(X509CertificateError::UnknownKeyAlgorithm(
"unable to use ed25519 keys with smartcards".into(),
)),
KeyAlgorithm::Ecdsa(curve) => match curve {
EcdsaCurve::Secp256r1 => Ok(AlgorithmId::EccP256),
EcdsaCurve::Secp384r1 => Ok(AlgorithmId::EccP384),
},
}
}
pub enum RequiredAuthentication {
Pin,
ManagementKey,
ManagementKeyAndPin,
}
impl RequiredAuthentication {
pub fn requires_pin(&self) -> bool {
match self {
Self::Pin | Self::ManagementKeyAndPin => true,
Self::ManagementKey => false,
}
}
pub fn requires_management_key(&self) -> bool {
match self {
Self::ManagementKey | Self::ManagementKeyAndPin => true,
Self::Pin => false,
}
}
}
fn attempt_authenticated_operation<T>(
yk: &mut RawYubiKey,
op: impl Fn(&mut RawYubiKey) -> Result<T, AppleCodesignError>,
required_authentication: RequiredAuthentication,
get_device_pin: Option<&dyn PinCallback>,
) -> Result<T, AppleCodesignError> {
const MAX_ATTEMPTS: u8 = 3;
for attempt in 1..MAX_ATTEMPTS + 1 {
warn!("attempt {}/{}", attempt, MAX_ATTEMPTS);
match op(yk) {
Ok(x) => {
return Ok(x);
}
Err(AppleCodesignError::YubiKey(YkError::AuthenticationError)) => {
if attempt == MAX_ATTEMPTS {
return Err(AppleCodesignError::SmartcardFailedAuthentication);
}
warn!("device refused operation due to authentication error");
if required_authentication.requires_management_key() {
match yk.authenticate(MgmKey::default()) {
Ok(()) => {
warn!("management key authentication successful");
}
Err(e) => {
error!("management key authentication failure: {}", e);
continue;
}
}
}
if required_authentication.requires_pin() {
if let Some(pin_cb) = get_device_pin {
let pin = Zeroizing::new(pin_cb().map_err(|e| {
X509CertificateError::Other(format!(
"error retrieving device pin: {}",
e
))
})?);
match yk.verify_pin(&pin) {
Ok(()) => {
warn!("pin verification successful");
}
Err(e) => {
error!("pin verification failure: {}", e);
continue;
}
}
} else {
warn!(
"unable to retrieve device pin; future attempts will fail; giving up"
);
return Err(AppleCodesignError::SmartcardFailedAuthentication);
}
}
}
Err(e) => {
return Err(e);
}
}
}
Err(AppleCodesignError::SmartcardFailedAuthentication)
}
pub struct YubiKey {
yk: Arc<Mutex<RawYubiKey>>,
pin_callback: Option<Arc<dyn PinCallback>>,
}
impl From<RawYubiKey> for YubiKey {
fn from(yk: RawYubiKey) -> Self {
Self {
yk: Arc::new(Mutex::new(yk)),
pin_callback: None,
}
}
}
impl YubiKey {
pub fn new() -> Result<Self, AppleCodesignError> {
let yk = Arc::new(Mutex::new(RawYubiKey::open()?));
Ok(Self {
yk,
pin_callback: None,
})
}
pub fn set_pin_callback(&mut self, cb: impl PinCallback + 'static) {
self.pin_callback = Some(Arc::new(cb));
}
pub fn inner(&self) -> Result<MutexGuard<RawYubiKey>, AppleCodesignError> {
self.yk.lock().map_err(|_| AppleCodesignError::PoisonedLock)
}
pub fn find_certificates(
&mut self,
) -> Result<Vec<(SlotId, CapturedX509Certificate)>, AppleCodesignError> {
let mut guard = self.inner()?;
let yk = guard.deref_mut();
let slots = yk
.piv_keys()?
.into_iter()
.map(|key| key.slot())
.collect::<Vec<_>>();
let mut res = vec![];
for slot in slots {
let cert = YkCertificate::read(yk, slot)?;
let cert = CapturedX509Certificate::from_der(cert.into_buffer().to_vec())?;
res.push((slot, cert));
}
Ok(res)
}
pub fn get_certificate_signer(
&mut self,
slot_id: SlotId,
) -> Result<Option<CertificateSigner>, AppleCodesignError> {
Ok(self
.find_certificates()?
.into_iter()
.find_map(|(slot, cert)| {
if slot == slot_id {
Some(CertificateSigner {
yk: self.yk.clone(),
slot: slot_id,
cert,
pin_callback: self.pin_callback.clone(),
})
} else {
None
}
}))
}
fn import_rsa_key(
&mut self,
p: &[u8],
q: &[u8],
cert: &CapturedX509Certificate,
slot: SlotId,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), AppleCodesignError> {
let slot_pretty = hex::encode([u8::from(slot)]);
let public_key_data = cert.rsa_public_key_data()?;
let algorithm = match public_key_data.modulus.as_slice().len() {
129 => AlgorithmId::Rsa1024,
257 => AlgorithmId::Rsa2048,
_ => {
return Err(X509CertificateError::Other(
"unable to determine RSA key algorithm".into(),
)
.into());
}
};
warn!(
"attempting import of {:?} private key to slot {}",
algorithm, slot_pretty
);
let mut yk = self.inner()?;
attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
let rsa_key = ::yubikey::piv::RsaKeyData::new(p, q);
import_rsa_key(yk, slot, algorithm, rsa_key, touch_policy, pin_policy)?;
Ok(())
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
Ok(())
}
fn import_ecdsa_key(
&mut self,
private_key: &[u8],
cert: &CapturedX509Certificate,
slot: SlotId,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), AppleCodesignError> {
let slot_pretty = hex::encode([u8::from(slot)]);
let algorithm = algorithm_from_certificate(cert)?;
warn!(
"attempting import of ECDSA private key to slot {}",
slot_pretty
);
let mut yk = self.inner()?;
attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
import_ecc_key(yk, slot, algorithm, private_key, touch_policy, pin_policy)?;
Ok(())
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
Ok(())
}
pub fn import_key(
&mut self,
slot: SlotId,
key: &dyn KeyInfoSigner,
cert: &CapturedX509Certificate,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), AppleCodesignError> {
let slot_pretty = hex::encode([u8::from(slot)]);
match cert.key_algorithm() {
Some(KeyAlgorithm::Rsa) => {
let (p, q) = key.rsa_primes()?.ok_or_else(|| {
X509CertificateError::Other(
"could not locate RSA private key parameters".into(),
)
})?;
self.import_rsa_key(&p, &q, cert, slot, touch_policy, pin_policy)?;
}
Some(KeyAlgorithm::Ecdsa(_)) => {
let private_key = key.private_key_data().ok_or_else(|| {
X509CertificateError::Other("could not retrieve private key data".into())
})?;
self.import_ecdsa_key(&private_key, cert, slot, touch_policy, pin_policy)?;
}
Some(algorithm) => {
return Err(AppleCodesignError::CertificateUnsupportedKeyAlgorithm(
algorithm,
));
}
None => {
return Err(X509CertificateError::UnknownKeyAlgorithm("unknown".into()).into());
}
}
warn!(
"successfully wrote private key to slot {}; proceeding to write certificate",
slot_pretty
);
self.import_certificate(slot, cert)?;
warn!("successfully wrote certificate to slot {}", slot_pretty);
Ok(())
}
pub fn generate_key(
&mut self,
slot: SlotId,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), AppleCodesignError> {
let slot_pretty = hex::encode([u8::from(slot)]);
let mut yk = self.inner()?;
let algorithm = AlgorithmId::Rsa2048;
let key_algorithm = KeyAlgorithm::Rsa;
let signature_algorithm = SignatureAlgorithm::RsaSha256;
warn!(
"attempting to generate {:?} key in slot {}",
algorithm, slot_pretty,
);
attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
warn!("ensuring slot doesn't contain a certificate");
Ok(YkCertificate::delete(yk, slot)?)
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
let key_info = attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
warn!("generating new key on device...");
Ok(yubikey::piv::generate(
yk,
slot,
algorithm,
pin_policy,
touch_policy,
)?)
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
warn!("private key successfully generated");
let mut subject = rfc3280::Name::default();
subject
.append_common_name_utf8_string("unusable placeholder certificate")
.map_err(|e| AppleCodesignError::CertificateBuildError(format!("{:?}", e)))?;
let tbs_certificate = rfc5280::TbsCertificate {
version: Some(rfc5280::Version::V3),
serial_number: 1.into(),
signature: signature_algorithm.into(),
issuer: subject.clone(),
validity: rfc5280::Validity {
not_before: asn1time::Time::UtcTime(asn1time::UtcTime::now()),
not_after: asn1time::Time::UtcTime(asn1time::UtcTime::now()),
},
subject,
subject_public_key_info: rfc5280::SubjectPublicKeyInfo {
algorithm: key_algorithm.into(),
subject_public_key: bcder::BitString::new(0, key_info.public_key().into()),
},
issuer_unique_id: None,
subject_unique_id: None,
extensions: None,
raw_data: None,
};
let temp_cert = rfc5280::Certificate {
tbs_certificate,
signature_algorithm: signature_algorithm.into(),
signature: bcder::BitString::new(0, Bytes::new()),
};
let mut temp_cert_der = vec![];
temp_cert
.encode_ref()
.write_encoded(bcder::Mode::Der, &mut temp_cert_der)?;
let fake_cert = YkCertificate::from_bytes(temp_cert_der)?;
attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
warn!("writing temp cert");
Ok(fake_cert.write(yk, slot, CertInfo::Uncompressed)?)
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
Ok(())
}
pub fn import_certificate(
&mut self,
slot: SlotId,
cert: &CapturedX509Certificate,
) -> Result<(), AppleCodesignError> {
let slot_pretty = hex::encode([u8::from(slot)]);
let cert = YkCertificate::from_bytes(cert.encode_der()?)?;
let mut yk = self.inner()?;
attempt_authenticated_operation(
yk.deref_mut(),
|yk| {
warn!("writing certificate to slot {}", slot_pretty);
Ok(cert.write(yk, slot, CertInfo::Uncompressed)?)
},
RequiredAuthentication::ManagementKeyAndPin,
self.pin_callback.as_deref(),
)?;
warn!("certificate import successful");
Ok(())
}
}
#[derive(Clone)]
pub struct CertificateSigner {
yk: Arc<Mutex<RawYubiKey>>,
slot: SlotId,
cert: CapturedX509Certificate,
pin_callback: Option<Arc<dyn PinCallback>>,
}
impl Signer<Signature> for CertificateSigner {
fn try_sign(&self, message: &[u8]) -> Result<Signature, signature::Error> {
let algorithm_id =
algorithm_from_certificate(&self.cert).map_err(signature::Error::from_source)?;
let signature_algorithm = self
.cert
.signature_algorithm()
.ok_or(X509CertificateError::UnknownDigestAlgorithm(
"failed to resolve digest algorithm for certificate".into(),
))
.map_err(signature::Error::from_source)?;
let digest_algorithm = signature_algorithm
.digest_algorithm()
.ok_or(X509CertificateError::UnknownDigestAlgorithm(
"unable to resolve digest algorithm from signature algorithm".into(),
))
.map_err(signature::Error::from_source)?;
let digest = match algorithm_id {
AlgorithmId::Rsa1024 => digest_algorithm
.rsa_pkcs1_encode(message, 1024 / 8)
.map_err(signature::Error::from_source)?,
AlgorithmId::Rsa2048 => digest_algorithm
.rsa_pkcs1_encode(message, 2048 / 8)
.map_err(signature::Error::from_source)?,
AlgorithmId::EccP256 => digest_algorithm.digest_data(message),
AlgorithmId::EccP384 => digest_algorithm.digest_data(message),
};
let mut guard = self
.yk
.lock()
.map_err(|_| signature::Error::from_source("unable to acquire lock on YubiKey"))?;
let yk = guard.deref_mut();
warn!("initial signing attempt may fail if the certificate requires a pin to unlock");
attempt_authenticated_operation(
yk,
|yk| {
let signature = ::yubikey::piv::sign_data(yk, &digest, algorithm_id, self.slot)
.map_err(AppleCodesignError::YubiKey)?;
Ok(Signature::from(signature.to_vec()))
},
RequiredAuthentication::Pin,
self.pin_callback.as_deref(),
)
.map_err(signature::Error::from_source)
}
}
impl Sign for CertificateSigner {
fn sign(&self, message: &[u8]) -> Result<(Vec<u8>, SignatureAlgorithm), X509CertificateError> {
let algorithm = self.signature_algorithm()?;
Ok((self.try_sign(message)?.into(), algorithm))
}
fn key_algorithm(&self) -> Option<KeyAlgorithm> {
self.cert.key_algorithm()
}
fn public_key_data(&self) -> Bytes {
self.cert.public_key_data()
}
fn signature_algorithm(&self) -> Result<SignatureAlgorithm, X509CertificateError> {
self.cert
.signature_algorithm()
.ok_or(X509CertificateError::UnknownSignatureAlgorithm(format!(
"{:?}",
self.cert.signature_algorithm_oid()
)))
}
fn private_key_data(&self) -> Option<Vec<u8>> {
None
}
fn rsa_primes(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>, X509CertificateError> {
Ok(None)
}
}
impl KeyInfoSigner for CertificateSigner {}
impl PublicKeyPeerDecrypt for CertificateSigner {
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
let mut guard = self
.yk
.lock()
.map_err(|_| RemoteSignError::Crypto("unable to acquire lock on YubiKey".into()))?;
let yk = guard.deref_mut();
let algorithm_id = algorithm_from_certificate(&self.cert)?;
attempt_authenticated_operation(
yk,
|yk| {
let plaintext =
::yubikey::piv::decrypt_data(yk, ciphertext, algorithm_id, self.slot)?;
let rsa_modulus_length = match algorithm_id {
AlgorithmId::Rsa1024 => Some(1024 / 8),
AlgorithmId::Rsa2048 => Some(2048 / 8),
AlgorithmId::EccP256 | AlgorithmId::EccP384 => None,
};
let plaintext = match algorithm_id {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let mut digest = sha2::Sha256::default();
let mut mgf_digest = sha2::Sha256::default();
rsa_oaep_post_decrypt_decode(
rsa_modulus_length.unwrap(),
plaintext.to_vec(),
&mut digest,
&mut mgf_digest,
None,
)
.map_err(|e| {
RemoteSignError::Crypto(format!("error during OAEP decoding: {}", e))
})?
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => plaintext.to_vec(),
};
Ok(plaintext)
},
RequiredAuthentication::Pin,
self.pin_callback.as_deref(),
)
.map_err(|e| RemoteSignError::Crypto(format!("failed to decrypt using YubiKey: {}", e)))
}
}
impl PrivateKey for CertificateSigner {
fn as_key_info_signer(&self) -> &dyn KeyInfoSigner {
self
}
fn to_public_key_peer_decrypt(
&self,
) -> Result<Box<dyn PublicKeyPeerDecrypt>, AppleCodesignError> {
Ok(Box::new(self.clone()))
}
fn finish(&self) -> Result<(), AppleCodesignError> {
Ok(())
}
}
impl CertificateSigner {
pub fn slot(&self) -> SlotId {
self.slot
}
pub fn certificate(&self) -> &CapturedX509Certificate {
&self.cert
}
}