use {
crate::{
certificate::{AppleCertificate, OID_USER_ID},
cryptography::PrivateKey,
error::AppleCodesignError,
remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError},
},
bcder::Oid,
bytes::Bytes,
log::{error, warn},
security_framework::{
certificate::SecCertificate,
item::{ItemClass, ItemSearchOptions, Reference, SearchResult},
key::SecKey,
os::macos::{
item::ItemSearchOptionsExt,
keychain::{SecKeychain, SecPreferencesDomain},
},
},
security_framework_sys::key::Algorithm as KeychainAlgorithm,
signature::Signer,
std::ops::Deref,
x509_certificate::{
CapturedX509Certificate, KeyAlgorithm, KeyInfoSigner, Sign, Signature, SignatureAlgorithm,
X509CertificateError,
},
zeroize::Zeroizing,
};
const SYSTEM_ROOTS_KEYCHAIN: &str = "/System/Library/Keychains/SystemRootCertificates.keychain";
#[derive(Clone, Copy, Debug)]
pub enum KeychainDomain {
User,
System,
Common,
Dynamic,
}
impl From<KeychainDomain> for SecPreferencesDomain {
fn from(v: KeychainDomain) -> Self {
match v {
KeychainDomain::User => Self::User,
KeychainDomain::System => Self::System,
KeychainDomain::Common => Self::Common,
KeychainDomain::Dynamic => Self::Dynamic,
}
}
}
impl TryFrom<&str> for KeychainDomain {
type Error = String;
fn try_from(v: &str) -> Result<Self, Self::Error> {
match v {
"user" => Ok(Self::User),
"system" => Ok(Self::System),
"common" => Ok(Self::Common),
"dynamic" => Ok(Self::Dynamic),
_ => Err(format!(
"{} is not a valid keychain domain; use user, system, common, or dynamic",
v
)),
}
}
}
#[derive(Clone)]
pub struct KeychainCertificate {
sec_cert: SecCertificate,
sec_key: SecKey,
captured: CapturedX509Certificate,
}
impl Deref for KeychainCertificate {
type Target = CapturedX509Certificate;
fn deref(&self) -> &Self::Target {
&self.captured
}
}
impl Signer<Signature> for KeychainCertificate {
fn try_sign(&self, message: &[u8]) -> Result<Signature, signature::Error> {
let algorithm = self
.signature_algorithm()
.map_err(signature::Error::from_source)?;
let algorithm = match algorithm {
SignatureAlgorithm::RsaSha1 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA1,
SignatureAlgorithm::RsaSha256 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA256,
SignatureAlgorithm::RsaSha384 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA384,
SignatureAlgorithm::RsaSha512 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA512,
SignatureAlgorithm::EcdsaSha256 => KeychainAlgorithm::ECDSASignatureMessageX962SHA256,
SignatureAlgorithm::EcdsaSha384 => KeychainAlgorithm::ECDSASignatureMessageX962SHA384,
SignatureAlgorithm::Ed25519 => KeychainAlgorithm::ECDSASignatureMessageX962SHA512,
SignatureAlgorithm::NoSignature(_) => {
return Err(signature::Error::from_source("digest only signature"));
}
};
warn!(
"attempting to create signature using keychain item: {}",
self.sec_cert.subject_summary()
);
let signature = self
.sec_key
.create_signature(algorithm, message)
.map_err(|e| {
signature::Error::from_source(format!(
"when attempting to create signature from keychain item: {}",
e
))
})?;
Ok(Signature::from(signature))
}
}
impl Sign for KeychainCertificate {
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.captured.key_algorithm()
}
fn public_key_data(&self) -> Bytes {
self.captured.public_key_data()
}
fn signature_algorithm(&self) -> Result<SignatureAlgorithm, X509CertificateError> {
self.captured
.signature_algorithm()
.ok_or(X509CertificateError::UnknownSignatureAlgorithm(format!(
"{:?}",
self.captured.signature_algorithm_oid()
)))
}
fn private_key_data(&self) -> Option<Zeroizing<Vec<u8>>> {
None
}
fn rsa_primes(
&self,
) -> Result<Option<(Zeroizing<Vec<u8>>, Zeroizing<Vec<u8>>)>, X509CertificateError> {
Ok(None)
}
}
impl KeyInfoSigner for KeychainCertificate {}
impl PublicKeyPeerDecrypt for KeychainCertificate {
fn decrypt(&self, _ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
error!("missing feature along with workarounds tracked in https://github.com/indygreg/apple-platform-rs/issues/7");
Err(RemoteSignError::Crypto(
"decryption not yet implemented for keychain stored keys".into(),
))
}
}
impl PrivateKey for KeychainCertificate {
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 KeychainCertificate {
pub fn as_captured_x509_certificate(&self) -> CapturedX509Certificate {
self.captured.clone()
}
}
fn find_certificates(
keychains: &[SecKeychain],
) -> Result<Vec<KeychainCertificate>, AppleCodesignError> {
let mut search = ItemSearchOptions::default();
search.keychains(keychains);
search.class(ItemClass::identity());
search.limit(i32::MAX as i64);
let mut certs = vec![];
for item in search.search()? {
match item {
SearchResult::Ref(reference) => match reference {
Reference::Identity(identity) => {
let cert = identity.certificate()?;
let private_key = identity.private_key()?;
if let Ok(captured) = CapturedX509Certificate::from_der(cert.to_der()) {
certs.push(KeychainCertificate {
sec_cert: cert,
sec_key: private_key,
captured,
});
}
}
_ => {
return Err(AppleCodesignError::KeychainError(
"non-certificate reference from keychain search (this should not happen)"
.to_string(),
));
}
},
_ => {
return Err(AppleCodesignError::KeychainError(
"non-reference result from keychain search (this should not happen)"
.to_string(),
));
}
}
}
Ok(certs)
}
pub fn keychain_find_code_signing_certificates(
domain: KeychainDomain,
password: Option<&str>,
) -> Result<Vec<KeychainCertificate>, AppleCodesignError> {
let mut keychain = SecKeychain::default_for_domain(domain.into())?;
if password.is_some() {
keychain.unlock(password)?;
}
let certs = find_certificates(&[keychain])?;
Ok(certs
.into_iter()
.filter(|cert| !cert.captured.apple_code_signing_extensions().is_empty())
.collect::<Vec<_>>())
}
pub fn macos_keychain_find_certificate_chain(
domain: KeychainDomain,
password: Option<&str>,
user_id: &str,
) -> Result<Vec<CapturedX509Certificate>, AppleCodesignError> {
let mut keychain = SecKeychain::default_for_domain(domain.into())?;
if password.is_some() {
keychain.unlock(password)?;
}
let keychains = vec![SecKeychain::open(SYSTEM_ROOTS_KEYCHAIN)?, keychain];
let certs = find_certificates(&keychains)?;
let start_cert: &CapturedX509Certificate = certs
.iter()
.find_map(|cert| {
if let Ok(Some(value)) = cert
.captured
.subject_name()
.find_first_attribute_string(Oid(OID_USER_ID.as_ref().into()))
{
if value == user_id {
Some(&cert.captured)
} else {
None
}
} else {
None
}
})
.ok_or_else(|| AppleCodesignError::CertificateNotFound(format!("UID={}", user_id)))?;
let mut chain = vec![start_cert.clone()];
let mut last_issuer_name = start_cert.issuer_name();
loop {
let issuer = certs.iter().find_map(|cert| {
if cert.captured.subject_name() == last_issuer_name {
Some(&cert.captured)
} else {
None
}
});
if let Some(issuer) = issuer {
chain.push(issuer.clone());
if issuer.subject_name() == issuer.issuer_name() {
break;
} else {
last_issuer_name = issuer.issuer_name();
}
} else {
break;
}
}
Ok(chain)
}