From 902e68de060fbd75e8a8d93d30a3978abc6f975b Mon Sep 17 00:00:00 2001 From: Dmitry Pankratov <dmitry@pankratov.net> Date: Sun, 15 Sep 2024 21:15:37 +0200 Subject: [PATCH] Added CMS support --- security-framework-sys/src/certificate.rs | 18 + security-framework-sys/src/cms.rs | 263 +++++++ security-framework-sys/src/lib.rs | 1 + security-framework/src/cms.rs | 645 ++++++++++++++++++ security-framework/src/lib.rs | 2 + security-framework/test/cms/encrypted.p7m | Bin 0 -> 422 bytes security-framework/test/cms/keystore.p12 | Bin 0 -> 4109 bytes .../test/cms/signed-encrypted.p7m | Bin 0 -> 1802 bytes security-framework/test/cms/signed.p7m | Bin 0 -> 1402 bytes systest/build.rs | 2 + systest/src/main.rs | 1 + 11 files changed, 932 insertions(+) create mode 100644 security-framework-sys/src/cms.rs create mode 100644 security-framework/src/cms.rs create mode 100644 security-framework/test/cms/encrypted.p7m create mode 100644 security-framework/test/cms/keystore.p12 create mode 100644 security-framework/test/cms/signed-encrypted.p7m create mode 100644 security-framework/test/cms/signed.p7m diff --git a/security-framework-sys/src/certificate.rs b/security-framework-sys/src/certificate.rs index 13d60e78..4d38ca2a 100644 --- a/security-framework-sys/src/certificate.rs +++ b/security-framework-sys/src/certificate.rs @@ -36,6 +36,24 @@ extern "C" { #[cfg(target_os = "macos")] pub static kSecPropertyTypeDate: CFStringRef; + // certificate policies + pub static kSecPolicyAppleX509Basic: CFStringRef; + pub static kSecPolicyAppleSSL: CFStringRef; + pub static kSecPolicyAppleSMIME: CFStringRef; + pub static kSecPolicyAppleEAP: CFStringRef; + pub static kSecPolicyAppleIPsec: CFStringRef; + #[cfg(target_os = "macos")] + pub static kSecPolicyApplePKINITClient: CFStringRef; + #[cfg(target_os = "macos")] + pub static kSecPolicyApplePKINITServer: CFStringRef; + pub static kSecPolicyAppleCodeSigning: CFStringRef; + pub static kSecPolicyMacAppStoreReceipt: CFStringRef; + pub static kSecPolicyAppleIDValidation: CFStringRef; + pub static kSecPolicyAppleTimeStamping: CFStringRef; + pub static kSecPolicyAppleRevocation: CFStringRef; + pub static kSecPolicyApplePassbookSigning: CFStringRef; + pub static kSecPolicyApplePayIssuerEncryption: CFStringRef; + pub fn SecCertificateGetTypeID() -> CFTypeID; pub fn SecCertificateCreateWithData( allocator: CFAllocatorRef, diff --git a/security-framework-sys/src/cms.rs b/security-framework-sys/src/cms.rs new file mode 100644 index 00000000..ea398403 --- /dev/null +++ b/security-framework-sys/src/cms.rs @@ -0,0 +1,263 @@ +//! Cryptographic Message Syntax support + +use std::os::raw::c_void; + +use core_foundation_sys::array::CFArrayRef; +use core_foundation_sys::base::{Boolean, CFTypeID, CFTypeRef, OSStatus}; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::date::CFAbsoluteTime; +use core_foundation_sys::string::CFStringRef; + +use crate::base::SecCertificateRef; +use crate::trust::SecTrustRef; + +pub enum OpaqueCMSEncoderRef {} +pub type CMSEncoderRef = *mut OpaqueCMSEncoderRef; + +pub enum OpaqueCMSDecoderRef {} +pub type CMSDecoderRef = *mut OpaqueCMSEncoderRef; + +#[repr(i32)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum CMSSignerStatus { + kCMSSignerUnsigned = 0, + kCMSSignerValid = 1, + kCMSSignerNeedsDetachedContent = 2, + kCMSSignerInvalidSignature = 3, + kCMSSignerInvalidCert = 4, + kCMSSignerInvalidIndex = 5, +} + +pub type CMSSignedAttributes = u32; +pub const kCMSAttrNone: CMSSignedAttributes = 0x0000; +pub const kCMSAttrSmimeCapabilities: CMSSignedAttributes = 0x0001; +pub const kCMSAttrSmimeEncryptionKeyPrefs: CMSSignedAttributes = 0x0002; +pub const kCMSAttrSmimeMSEncryptionKeyPrefs: CMSSignedAttributes = 0x0004; +pub const kCMSAttrSigningTime: CMSSignedAttributes = 0x0008; +pub const kCMSAttrAppleCodesigningHashAgility: CMSSignedAttributes = 0x0010; +pub const kCMSAttrAppleCodesigningHashAgilityV2: CMSSignedAttributes = 0x0020; +pub const kCMSAttrAppleExpirationTime: CMSSignedAttributes = 0x0040; + +#[repr(i32)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum CMSCertificateChainMode { + kCMSCertificateNone = 0, + kCMSCertificateSignerOnly = 1, + kCMSCertificateChain = 2, + kCMSCertificateChainWithRoot = 3, + kCMSCertificateChainWithRootOrFail = 4, +} + +extern "C" { + + // CMS decoder + + pub fn CMSDecoderGetTypeID() -> CFTypeID; + + pub fn CMSDecoderCreate(output: *mut CMSDecoderRef) -> OSStatus; + + pub fn CMSDecoderUpdateMessage( + decoder: CMSDecoderRef, + msg_bytes: *const c_void, + msg_bytes_len: usize, + ) -> OSStatus; + + pub fn CMSDecoderFinalizeMessage(decoder: CMSDecoderRef) -> OSStatus; + + pub fn CMSDecoderSetDetachedContent( + decoder: CMSDecoderRef, + detached_content: CFDataRef, + ) -> OSStatus; + + pub fn CMSDecoderCopyDetachedContent( + decoder: CMSDecoderRef, + detached_content_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSDecoderGetNumSigners( + decoder: CMSDecoderRef, + num_signers_out: *mut usize, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerStatus( + decoder: CMSDecoderRef, + signer_index: usize, + policy_or_array: CFTypeRef, + evaluate_sec_trust: Boolean, + signer_status_out: *mut CMSSignerStatus, + sec_trust_out: *mut SecTrustRef, + cert_verify_result_code_out: *mut OSStatus, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerEmailAddress( + decoder: CMSDecoderRef, + signer_index: usize, + signer_email_address_out: *mut CFStringRef, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerCert( + decoder: CMSDecoderRef, + signer_index: usize, + signer_cert_out: *mut SecCertificateRef, + ) -> OSStatus; + + pub fn CMSDecoderIsContentEncrypted( + decoder: CMSDecoderRef, + is_encrypted_out: *mut Boolean, + ) -> OSStatus; + + pub fn CMSDecoderCopyEncapsulatedContentType( + decoder: CMSDecoderRef, + content_type_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSDecoderCopyAllCerts( + decoder: CMSDecoderRef, + certs_out: *mut CFArrayRef, + ) -> OSStatus; + + pub fn CMSDecoderCopyContent( + decoder: CMSDecoderRef, + content_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerSigningTime( + decoder: CMSDecoderRef, + signer_index: usize, + sign_time_out: *mut CFAbsoluteTime, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerTimestamp( + decoder: CMSDecoderRef, + signer_index: usize, + timestamp: *mut CFAbsoluteTime, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerTimestampWithPolicy( + decoder: CMSDecoderRef, + timestamp_policy: CFTypeRef, + signer_index: usize, + timestamp: *mut CFAbsoluteTime, + ) -> OSStatus; + + pub fn CMSDecoderCopySignerTimestampCertificates( + decoder: CMSDecoderRef, + signer_index: usize, + certificate_refs: *mut CFArrayRef, + ) -> OSStatus; + + + // CMS encoder + + pub static kCMSEncoderDigestAlgorithmSHA1: CFStringRef; + pub static kCMSEncoderDigestAlgorithmSHA256: CFStringRef; + + pub fn CMSEncoderGetTypeID() -> CFTypeID; + + pub fn CMSEncoderCreate(encoder_out: *mut CMSEncoderRef) -> OSStatus; + + pub fn CMSEncoderSetSignerAlgorithm( + encoder: CMSEncoderRef, + digest_alogrithm: CFStringRef, + ) -> OSStatus; + + pub fn CMSEncoderAddSigners( + encoder: CMSEncoderRef, + signer_or_array: CFTypeRef, + ) -> OSStatus; + + pub fn CMSEncoderCopySigners( + encoder: CMSEncoderRef, + signers_out: *mut CFArrayRef, + ) -> OSStatus; + + pub fn CMSEncoderAddRecipients( + encoder: CMSEncoderRef, + recipient_or_array: CFTypeRef, + ) -> OSStatus; + + pub fn CMSEncoderCopyRecipients( + encoder: CMSEncoderRef, + recipients_out: *mut CFArrayRef, + ) -> OSStatus; + + pub fn CMSEncoderSetHasDetachedContent( + encoder: CMSEncoderRef, + detached_content: Boolean, + ) -> OSStatus; + + pub fn CMSEncoderGetHasDetachedContent( + encoder: CMSEncoderRef, + detached_content_out: *mut Boolean, + ) -> OSStatus; + + pub fn CMSEncoderSetEncapsulatedContentTypeOID( + encoder: CMSEncoderRef, + content_type_oid: CFTypeRef, + ) -> OSStatus; + + pub fn CMSEncoderCopyEncapsulatedContentType( + encoder: CMSEncoderRef, + content_type_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSEncoderAddSupportingCerts( + encoder: CMSEncoderRef, + cert_or_array: CFTypeRef, + ) -> OSStatus; + + pub fn CMSEncoderCopySupportingCerts( + encoder: CMSEncoderRef, + certs_out: *mut CFArrayRef, + ) -> OSStatus; + + pub fn CMSEncoderAddSignedAttributes( + encoder: CMSEncoderRef, + signed_attributes: CMSSignedAttributes, + ) -> OSStatus; + + pub fn CMSEncoderSetCertificateChainMode( + encoder: CMSEncoderRef, + chain_mode: CMSCertificateChainMode, + ) -> OSStatus; + + pub fn CMSEncoderGetCertificateChainMode( + encoder: CMSEncoderRef, + chain_mode_out: *mut CMSCertificateChainMode, + ) -> OSStatus; + + pub fn CMSEncoderUpdateContent( + encoder: CMSEncoderRef, + content: *const c_void, + content_len: usize, + ) -> OSStatus; + + pub fn CMSEncoderCopyEncodedContent( + encoder: CMSEncoderRef, + encoded_content_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSEncodeContent( + signers: CFTypeRef, + recipients: CFTypeRef, + content_type_oid: CFTypeRef, + detached_content: Boolean, + signed_attributes: CMSSignedAttributes, + content: *const c_void, + content_len: usize, + encoded_content_out: *mut CFDataRef, + ) -> OSStatus; + + pub fn CMSEncoderCopySignerTimestamp( + encoder: CMSEncoderRef, + signer_index: usize, + timestamp: *mut CFAbsoluteTime, + ) -> OSStatus; + + pub fn CMSEncoderCopySignerTimestampWithPolicy( + encoder: CMSEncoderRef, + timestamp_policy: CFTypeRef, + signer_index: usize, + timestamp: *mut CFAbsoluteTime, + ) -> OSStatus; +} diff --git a/security-framework-sys/src/lib.rs b/security-framework-sys/src/lib.rs index 0cb6a611..8240c86c 100644 --- a/security-framework-sys/src/lib.rs +++ b/security-framework-sys/src/lib.rs @@ -17,6 +17,7 @@ pub mod certificate; #[cfg(target_os = "macos")] pub mod certificate_oids; pub mod cipher_suite; +pub mod cms; #[cfg(target_os = "macos")] pub mod code_signing; #[cfg(target_os = "macos")] diff --git a/security-framework/src/cms.rs b/security-framework/src/cms.rs new file mode 100644 index 00000000..1c4562d3 --- /dev/null +++ b/security-framework/src/cms.rs @@ -0,0 +1,645 @@ +//! Cryptographic Message Syntax support + +use std::{fmt, ptr}; + +use core_foundation::array::CFArray; +use core_foundation::data::CFData; +use core_foundation::string::CFString; +use core_foundation_sys::array::CFArrayRef; +use core_foundation_sys::base::{OSStatus, TCFTypeRef}; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::date::CFAbsoluteTime; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::cms::*; +use security_framework_sys::trust::SecTrustRef; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::core_foundation::base::TCFType; +use crate::cvt; +use crate::policy::SecPolicy; +use crate::trust::SecTrust; + +pub use decoder::CMSDecoder; + +pub use encoder::cms_encode_content; +pub use encoder::CMS_DIGEST_ALGORITHM_SHA1; +pub use encoder::CMS_DIGEST_ALGORITHM_SHA256; +pub use encoder::CMSEncoder; +pub use encoder::SignedAttributes; + +mod encoder { + use super::*; + use crate::identity::SecIdentity; + + /// SHA1 digest algorithm + pub const CMS_DIGEST_ALGORITHM_SHA1: &str = "sha1"; + /// SHA256 digest algorithm + pub const CMS_DIGEST_ALGORITHM_SHA256: &str = "sha256"; + + bitflags::bitflags! { + /// Optional attributes you can add to a signed message + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct SignedAttributes: CMSSignedAttributes { + /// Identify signature, encryption, and digest algorithms supported by the encoder + const SMIME_CAPABILITIES = kCMSAttrSmimeCapabilities; + /// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption + const SMIME_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeEncryptionKeyPrefs; + /// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption, + /// but using an attribute object identifier (OID) preferred by Microsoft + const SMIME_MS_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeMSEncryptionKeyPrefs; + /// Include the signing time + const SIGNING_TIME = kCMSAttrSigningTime; + /// Include Apple codesigning hash agility + const APPLE_CODESIGNING_HASH_AGILITY = kCMSAttrAppleCodesigningHashAgility; + /// Include Apple codesigning hash agility, version 2 + const APPLE_CODESIGNING_HASH_AGILITY_V2 = kCMSAttrAppleCodesigningHashAgilityV2; + /// Include the expiration time + const APPLE_EXPIRATION_TIME = kCMSAttrAppleExpirationTime; + } + } + + + declare_TCFType! { + /// A type representing CMS encoder + CMSEncoder, CMSEncoderRef + } + impl_TCFType!(CMSEncoder, CMSEncoderRef, CMSEncoderGetTypeID); + + unsafe impl Sync for CMSEncoder {} + unsafe impl Send for CMSEncoder {} + + impl fmt::Debug for CMSEncoder { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("CMSEncoder").finish() + } + } + + impl CMSEncoder { + /// Create a new instance of `CMSEncoder` + pub fn create() -> Result<Self> { + let mut inner: CMSEncoderRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCreate(&mut inner) })?; + Ok(Self(inner)) + } + + /// Sets the digest algorithm to use for the signer. + /// Can be one of the predefined types: + /// + /// * `kCMSEncoderDigestAlgorithmSHA1` + /// * `kCMSEncoderDigestAlgorithmSHA256` + pub fn set_signer_algorithm(&self, digest_algorithm: &str) -> Result<()> { + let alg = CFString::new(digest_algorithm); + + cvt(unsafe { CMSEncoderSetSignerAlgorithm(self.0, alg.as_concrete_TypeRef()) })?; + Ok(()) + } + + /// Specify signers of the CMS message; implies that the message will be signed + pub fn add_signers(&self, signers: &[SecIdentity]) -> Result<()> { + let signers = CFArray::from_CFTypes(signers); + cvt(unsafe { + CMSEncoderAddSigners( + self.0, + if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() }) + })?; + Ok(()) + } + + /// Obtains the array of signers specified with the `add_signers` function + pub fn get_signers(&self) -> Result<Vec<SecIdentity>> { + let mut out: CFArrayRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCopySigners(self.0, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + let array = unsafe { CFArray::<SecIdentity>::wrap_under_create_rule(out) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + } + + /// Specifies a message is to be encrypted and specifies the recipients of the message + pub fn add_recipients(&self, recipients: &[SecCertificate]) -> Result<()> { + let recipients = CFArray::from_CFTypes(recipients); + cvt(unsafe { + CMSEncoderAddRecipients( + self.0, + if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() }) + })?; + Ok(()) + } + + /// Obtains the array of recipients specified with the `add_recipients` function + pub fn get_recipients(&self) -> Result<Vec<SecCertificate>> { + let mut out: CFArrayRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCopyRecipients(self.0, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + } + + /// Specifies whether the signed data is to be separate from the message + pub fn set_has_detached_content(&self, has_detached_content: bool) -> Result<()> { + cvt(unsafe { CMSEncoderSetHasDetachedContent(self.0, has_detached_content.into()) })?; + Ok(()) + } + + /// Indicates whether the message is to have detached content + pub fn get_has_detached_content(&self) -> Result<bool> { + let mut has_detached_content = 0; + cvt(unsafe { CMSEncoderGetHasDetachedContent(self.0, &mut has_detached_content) })?; + Ok(has_detached_content != 0) + } + + /// Specifies an object identifier for the encapsulated data of a signed message + pub fn set_encapsulated_content_type_oid(&self, oid: &str) -> Result<()> { + let oid = CFString::new(oid); + cvt(unsafe { CMSEncoderSetEncapsulatedContentTypeOID(self.0, oid.as_CFTypeRef()) })?; + Ok(()) + } + + /// Obtains the object identifier for the encapsulated data of a signed message + pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> { + let mut out: CFDataRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCopyEncapsulatedContentType(self.0, &mut out) })?; + Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() }) + } + + /// Adds certificates to a message + pub fn add_supporting_certs(&self, certs: &[SecCertificate]) -> Result<()> { + let certs = CFArray::from_CFTypes(certs); + cvt(unsafe { + CMSEncoderAddSupportingCerts( + self.0, + if !certs.is_empty() { certs.as_CFTypeRef() } else { ptr::null() }) + })?; + Ok(()) + } + + /// Obtains the certificates added to a message with `add_supporting_certs` + pub fn get_supporting_certs(&self) -> Result<Vec<SecCertificate>> { + let mut out: CFArrayRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCopySupportingCerts(self.0, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + } + + /// Specifies attributes for a signed message + pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> { + cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?; + Ok(()) + } + + /// Specifies which certificates to include in a signed CMS message + pub fn set_certificate_chain_mode( + &self, + certificate_chain_mode: CMSCertificateChainMode) -> Result<()> + { + cvt(unsafe { CMSEncoderSetCertificateChainMode(self.0, certificate_chain_mode) })?; + Ok(()) + } + + /// Obtains a constant that indicates which certificates are to be included in a signed CMS message + pub fn get_certificate_chain_mode(&self) -> Result<CMSCertificateChainMode> { + let mut out = CMSCertificateChainMode::kCMSCertificateNone; + cvt(unsafe { CMSEncoderGetCertificateChainMode(self.0, &mut out) })?; + Ok(out) + } + + /// Feeds content bytes into the encoder + pub fn update_content(&self, content: &[u8]) -> Result<()> { + cvt(unsafe { CMSEncoderUpdateContent(self.0, content.as_ptr() as _, content.len()) })?; + Ok(()) + } + + /// Finishes encoding the message and obtains the encoded result + pub fn get_encoded_content(&self) -> Result<Vec<u8>> { + let mut out: CFDataRef = ptr::null_mut(); + cvt(unsafe { CMSEncoderCopyEncodedContent(self.0, &mut out) })?; + Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() }) + } + + /// Returns the timestamp of a signer of a CMS message, if present + pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> { + let mut out = CFAbsoluteTime::default(); + cvt(unsafe { CMSEncoderCopySignerTimestamp(self.0, signer_index, &mut out) })?; + Ok(out) + } + + /// Returns the timestamp of a signer of a CMS message using a particular policy, if present + pub fn get_signer_timestamp_with_policy( + &self, + timestamp_policy: Option<CFStringRef>, + signer_index: usize) -> Result<CFAbsoluteTime> + { + let mut out = CFAbsoluteTime::default(); + cvt(unsafe { + CMSEncoderCopySignerTimestampWithPolicy( + self.0, + timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()), + signer_index, + &mut out) + })?; + + Ok(out) + } + } + + /// Encodes a message and obtains the result in one high-level function call + pub fn cms_encode_content( + signers: &[SecIdentity], + recipients: &[SecCertificate], + content_type_oid: Option<&str>, + detached_content: bool, + signed_attributes: SignedAttributes, + content: &[u8], + ) -> Result<Vec<u8>> { + let mut out: CFDataRef = ptr::null_mut(); + let signers = CFArray::from_CFTypes(signers); + let recipients = CFArray::from_CFTypes(recipients); + let content_type_oid = content_type_oid.map(CFString::new); + + cvt(unsafe { + CMSEncodeContent( + if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() }, + if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() }, + content_type_oid.as_ref().map(|oid| oid.as_CFTypeRef()).unwrap_or(ptr::null()), + detached_content.into(), + signed_attributes.bits(), + content.as_ptr() as _, + content.len(), + &mut out, + ) + })?; + + Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() }) + } +} + +mod decoder { + use super::*; + + /// Holds a result of the `CMSDecoder::get_signer_status` function + pub struct SignerStatus { + /// Signature status + pub signer_status: CMSSignerStatus, + /// Trust instance that was used to verify the signer’s certificate + pub sec_trust: SecTrust, + /// Result of the certificate verification + pub cert_verify_result: Result<()>, + } + + declare_TCFType! { + /// A type representing CMS Decoder + CMSDecoder, CMSDecoderRef + } + impl_TCFType!(CMSDecoder, CMSDecoderRef, CMSDecoderGetTypeID); + + unsafe impl Sync for CMSDecoder {} + unsafe impl Send for CMSDecoder {} + + impl fmt::Debug for CMSDecoder { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("CMSDecoder").finish() + } + } + + impl CMSDecoder { + /// Create a new instance of `CMSDecoder` + pub fn create() -> Result<Self> { + let mut inner: CMSDecoderRef = ptr::null_mut(); + cvt(unsafe { CMSDecoderCreate(&mut inner) })?; + Ok(Self(inner)) + } + + /// Feeds raw bytes of the message to be decoded into the decoder + pub fn update_message( + &self, + message: &[u8], + ) -> Result<()> { + cvt(unsafe { CMSDecoderUpdateMessage(self.0, message.as_ptr() as _, message.len()) })?; + Ok(()) + } + + /// Indicates that there is no more data to decode + pub fn finalize_message(&self) -> Result<()> { + cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?; + Ok(()) + } + + /// Specifies the message’s detached content, if any + pub fn set_detached_content(&self, detached_content: &[u8]) -> Result<()> { + let data = CFData::from_buffer(detached_content); + cvt(unsafe { CMSDecoderSetDetachedContent(self.0, data.as_concrete_TypeRef()) })?; + Ok(()) + } + + /// Obtains the detached content specified with the `set_detached_content` function + pub fn get_detached_content(&self) -> Result<Vec<u8>> { + unsafe { + let mut out: CFDataRef = ptr::null_mut(); + cvt(CMSDecoderCopyDetachedContent(self.0, &mut out))?; + if out.is_null() { + Ok(Vec::new()) + } else { + Ok(CFData::wrap_under_create_rule(out).to_vec()) + } + } + } + + /// Obtains the number of signers of a message + pub fn get_num_signers(&self) -> Result<usize> { + let mut out = 0; + cvt(unsafe { CMSDecoderGetNumSigners(self.0, &mut out) })?; + Ok(out) + } + + /// Obtains the status of a CMS message’s signature + pub fn get_signer_status( + &self, + signer_index: usize, + policies: &[SecPolicy]) -> Result<SignerStatus> + { + let policies = CFArray::from_CFTypes(policies); + + let mut signer_status = CMSSignerStatus::kCMSSignerUnsigned; + let mut sec_trust: SecTrustRef = ptr::null_mut(); + let mut verify_result = OSStatus::default(); + + cvt(unsafe { + CMSDecoderCopySignerStatus( + self.0, + signer_index, + if policies.is_empty() { ptr::null() } else { policies.as_CFTypeRef() }, + true.into(), + &mut signer_status, + &mut sec_trust, + &mut verify_result, + ) + })?; + + Ok(SignerStatus { + signer_status, + sec_trust: unsafe { SecTrust::wrap_under_create_rule(sec_trust) }, + cert_verify_result: cvt(verify_result), + }) + } + + /// Obtains the email address of the specified signer of a CMS message + pub fn get_signer_email_address(&self, signer_index: usize) -> Result<String> { + let mut out: CFStringRef = ptr::null_mut(); + cvt(unsafe { CMSDecoderCopySignerEmailAddress(self.0, signer_index, &mut out) })?; + Ok(unsafe { CFString::wrap_under_create_rule(out).to_string() }) + } + + /// Determines whether a CMS message was encrypted + pub fn is_content_encrypted(&self) -> Result<bool> { + let mut out = 0; + cvt(unsafe { CMSDecoderIsContentEncrypted(self.0, &mut out) })?; + Ok(out != 0) + } + + /// Obtains the object identifier for the encapsulated data of a signed message + pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> { + let mut out: CFDataRef = ptr::null_mut(); + if out.is_null() { + Ok(Vec::new()) + } else { + cvt(unsafe { CMSDecoderCopyEncapsulatedContentType(self.0, &mut out) })?; + Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() }) + } + } + + /// Obtains an array of all of the certificates in a message + pub fn get_all_certs(&self) -> Result<Vec<SecCertificate>> { + let mut out: CFArrayRef = ptr::null_mut(); + cvt(unsafe { CMSDecoderCopyAllCerts(self.0, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + } + + /// Obtains the message content, if any + pub fn get_content(&self) -> Result<Vec<u8>> { + let mut out: CFDataRef = ptr::null_mut(); + + cvt(unsafe { CMSDecoderCopyContent(self.0, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() }) + } + } + + /// Obtains the signing time of a CMS message, if present + pub fn get_signer_signing_time(&self, signer_index: usize) -> Result<CFAbsoluteTime> { + let mut out = CFAbsoluteTime::default(); + cvt(unsafe { CMSDecoderCopySignerSigningTime(self.0, signer_index, &mut out) })?; + Ok(out) + } + + /// Returns the timestamp of a signer of a CMS message, if present + pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> { + let mut out = CFAbsoluteTime::default(); + cvt(unsafe { CMSDecoderCopySignerTimestamp(self.0, signer_index, &mut out) })?; + Ok(out) + } + + /// Returns the timestamp of a signer of a CMS message using a given policy, if present + pub fn get_signer_timestamp_with_policy( + &self, + timestamp_policy: Option<CFStringRef>, + signer_index: usize) -> Result<CFAbsoluteTime> + { + let mut out = CFAbsoluteTime::default(); + cvt(unsafe { + CMSDecoderCopySignerTimestampWithPolicy( + self.0, + timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()), + signer_index, + &mut out) + })?; + + Ok(out) + } + + /// Returns an array containing the certificates from a timestamp response + pub fn get_signer_timestamp_certificates( + &self, + signer_index: usize) -> Result<Vec<SecCertificate>> + { + let mut out: CFArrayRef = ptr::null_mut(); + cvt(unsafe { CMSDecoderCopySignerTimestampCertificates(self.0, signer_index, &mut out) })?; + + if out.is_null() { + Ok(Vec::new()) + } else { + let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + } + } +} + +#[cfg(test)] +mod tests { + use security_framework_sys::cms::CMSSignerStatus; + use crate::cms::{cms_encode_content, CMSDecoder, CMSEncoder, SignedAttributes}; + use crate::import_export::{ImportedIdentity, Pkcs12ImportOptions}; + use crate::policy::SecPolicy; + + const KEYSTORE: &[u8] = include_bytes!("../test/cms/keystore.p12"); + const ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/encrypted.p7m"); + const SIGNED_ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/signed-encrypted.p7m"); + + fn import_keystore() -> Vec<ImportedIdentity> { + let mut import_opts = Pkcs12ImportOptions::new(); + import_opts.passphrase("cms").import(KEYSTORE).unwrap() + } + + #[test] + fn test_decode_encrypted() { + let _identities = import_keystore(); + + let decoder = CMSDecoder::create().unwrap(); + decoder.update_message(ENCRYPTED_CMS).unwrap(); + decoder.finalize_message().unwrap(); + + assert!(decoder.is_content_encrypted().unwrap()); + assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n"); + assert_eq!(decoder.get_all_certs().unwrap().len(), 0); + assert_eq!(decoder.get_num_signers().unwrap(), 0); + } + + #[test] + fn test_decode_signed_and_encrypted() { + let _identities = import_keystore(); + + let decoder = CMSDecoder::create().unwrap(); + decoder.update_message(SIGNED_ENCRYPTED_CMS).unwrap(); + decoder.finalize_message().unwrap(); + + assert!(decoder.is_content_encrypted().unwrap()); + + let signed_content = decoder.get_content().unwrap(); + + let decoder2 = CMSDecoder::create().unwrap(); + decoder2.update_message(&signed_content).unwrap(); + decoder2.finalize_message().unwrap(); + assert_eq!(decoder2.get_content().unwrap(), b"encrypted message\n"); + assert_eq!(decoder2.get_num_signers().unwrap(), 1); + + let policies = vec![SecPolicy::create_x509()]; + let status = decoder2.get_signer_status(0, &policies).unwrap(); + assert!(status.cert_verify_result.is_err()); + assert_eq!(status.signer_status, CMSSignerStatus::kCMSSignerInvalidCert); + } + + #[test] + fn test_encode_encrypted() { + let identities = import_keystore(); + + let chain = identities + .iter() + .filter_map(|id| id.cert_chain.as_ref()) + .next() + .unwrap(); + + let message = cms_encode_content( + &[], + &chain[0..1], + None, + false, + SignedAttributes::empty(), + b"encrypted message\n", + ).unwrap(); + + let decoder = CMSDecoder::create().unwrap(); + decoder.update_message(&message).unwrap(); + decoder.finalize_message().unwrap(); + assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n"); + } + + #[test] + fn test_encode_signed_encrypted() { + let identities = import_keystore(); + + let chain = identities + .iter() + .filter_map(|id| id.cert_chain.as_ref()) + .next() + .unwrap(); + + let identity = identities + .iter() + .filter_map(|id| id.identity.as_ref()) + .next() + .unwrap(); + + let message = cms_encode_content( + &[identity.clone()], + &chain[0..1], + None, + false, + SignedAttributes::empty(), + b"encrypted message\n", + ).unwrap(); + + let decoder = CMSDecoder::create().unwrap(); + decoder.update_message(&message).unwrap(); + decoder.finalize_message().unwrap(); + assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n"); + assert_eq!(decoder.get_num_signers().unwrap(), 1); + } + + #[test] + fn test_encode_with_cms_encoder() { + let identities = import_keystore(); + + let chain = identities + .iter() + .filter_map(|id| id.cert_chain.as_ref()) + .next() + .unwrap(); + + let identity = identities + .iter() + .filter_map(|id| id.identity.as_ref()) + .next() + .unwrap(); + + let encoder = CMSEncoder::create().unwrap(); + + let message = cms_encode_content( + &[identity.clone()], + &chain[0..1], + None, + false, + SignedAttributes::empty(), + b"encrypted message\n", + ).unwrap(); + + let decoder = CMSDecoder::create().unwrap(); + decoder.update_message(&message).unwrap(); + decoder.finalize_message().unwrap(); + assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n"); + assert_eq!(decoder.get_num_signers().unwrap(), 1); + } +} diff --git a/security-framework/src/lib.rs b/security-framework/src/lib.rs index 81631afe..f6fdd054 100644 --- a/security-framework/src/lib.rs +++ b/security-framework/src/lib.rs @@ -57,6 +57,8 @@ pub mod secure_transport; pub mod trust; #[cfg(target_os = "macos")] pub mod trust_settings; +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] +pub mod cms; #[cfg(target_os = "macos")] trait Pkcs12ImportOptionsInternals { diff --git a/security-framework/test/cms/encrypted.p7m b/security-framework/test/cms/encrypted.p7m new file mode 100644 index 0000000000000000000000000000000000000000..f1bc0ba3c1072a9942a2b339c0d0fb5af6d17d1e GIT binary patch literal 422 zcmXqLVqC<=snzDu_MMlJoq0hM<79&-#(pM72E!&sD<IPh$TW~M5Hb`n;A3MBWnmU( zcTCPrRd9A>;?VmzSz)uK{u~2dxDkwutPCtoj0{TGAKl%kGRJz7Mx;WKN6D8hS>I0T z>x!S<^<ObmF^zZEwTe=&i;aDo41QdgAiXYYXWz6%Im<F1PUqZl=K7QGZ+jV@E$8vd zykhxf(#z7wWgWj?#HdVlesl2tnGmMV`C1DFmS)9^I!>|_?TneKa<OEmd$eNY+0Kw7 z|9sA?6n|}CJ(Tn~=|!aB)l3o9j{3*U-?tdOO*+qZ?UT!Lv#P1p@jP|K?;>|vUbUH~ zGVR5^d$L>p9f<MU!TVxqklL;_-8#J5D{WYxo8-7WZcR3gw060evQO70rTqZ6yEXUp zP8XdsH6g!iY^xNC-wPFXJE<;sZmH`0Wr@q82FZ55XWPCwzg4+t-~$g5c18nPHqL}L z55`nx7Dg==0kycQ4TXOv>~%3L-)Lx&y{19ooWsgwz9ma-&7<7}LfoXcUQLskvvl`k O`ySOhVSammy#N5=ZLd85 literal 0 HcmV?d00001 diff --git a/security-framework/test/cms/keystore.p12 b/security-framework/test/cms/keystore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..0031baf27100488825cd17e4f8acad00a5764051 GIT binary patch literal 4109 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 Bw(I}^ literal 0 HcmV?d00001 diff --git a/security-framework/test/cms/signed-encrypted.p7m b/security-framework/test/cms/signed-encrypted.p7m new file mode 100644 index 0000000000000000000000000000000000000000..895c7e3093151159d874b3e0d5a5058659657411 GIT binary patch literal 1802 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 literal 0 HcmV?d00001 diff --git a/security-framework/test/cms/signed.p7m b/security-framework/test/cms/signed.p7m new file mode 100644 index 0000000000000000000000000000000000000000..bff2ce69f63a2f83824bdae294b3789e4e67de53 GIT binary patch literal 1402 zcmXqLVl89i)N1o+`_9YA&a|M3HQk_zHJOQ#(U8}Gn~gJ}&4V$OnT3hbKoOygae)Yn zP-<RsQDs3%YKlT`YH@L5dMej~CZ-<-O-x@5ni%gaU}j=uV&c&II9Xw{rT!cPUbt>X zZdL{ZAwvNJJ~rl17G_~~$K>2p1!qSCIdNVi69Y>_GXrx&a|44YAlDL!Yrqe75m*mP za&EC9&>AIVa~W9~n41{+8GzzkOihf843oY}9?f8T^MB$Kaiec4(uST>RzF|&;EKd~ zhJ1CinMzZ7EuYxjiZ%P&@gU53d0wN<=88uL_nhBic>X1~0#n^*Y1{5^Wp6L<>~8H~ z(7fPO<YXXq^=Lga_mkV<Y=JMH{n^42Yq9w21B3b>vYR*S`F-rlU6R&+w!p7*-Vw9o zfx>d}>25RD{^a==u;=zS&0v*Z3%&MuM?^KsZsjlw{?hZVvix-mi*di<aV0*lA8u3b zb-ntoc3h&+PJ30yTiqj^r)8CE>}`3(s`h=5H!ks?Gre76&&5e;n_l|OGE91|xw~4| zHfND<%1$vp-K|Y~7uN(>g(pU}RVsYqtpAiNT;Ly=&&|Zl$iTQb(7@k-2N=|{!i<dn zS(pqM3}it(J{B<+kz>1hX5Fu36o37~K3Hwnf1B6IhZh249VpGu!otkNyu&~k#8GA8 zG2mk3&}L&~Wo2h(K@N0aWC8=7ks+PE?nSoKoNmJ}Y6_e;g`D3i&c9*8Ao<<WE+Aam zHN{M7U-z6i2g4`(-Uf-gJ+>1|`LggtKWm}xD~@p6a&`IF4mJngHXc$u5>Q(BGR-*8 z!qhnBVCBcE4-a(?>Ym{?lAqpF=$0GhcSfa6f7^RgE&d~Q->#OIx}~P;1Sj0&-oK&p z==3)Im5asq&E@bq{jc)MVbibP#_QGdxwxtpHdi`q_!yVfoyW6}Gsw}$@9tUl^`%<t z7&leVU-oaO=BhW%EK2WqB3^n%YD?~3Y8x!*KWXdH(4YQOC(F9++hugp>d%F(s}4Q5 zI@K<?A!?;tFJn{eDJOTuXZp8dZpXf_E&Ook{WrrVCOu%<*8*l7137HT8!0a!XR8H` zPYfjBd4rSL5GV=Djf@5|a1JM{AwMjC7@8QE7+FRc=p$rV43$_E&icB#2+jUFXWyo~ zp(~la0w!@dIkK@9O`YQ+`uLf*%R_@oga&@Y9D^*R49=*9%n?K8FoEJ8;wENxV6KI+ znV6Uu8h~j5!Dn<ZU}t01=3{1JM-|sVPBOrJ%F@Kh(7pI}d(&F;bIz<*47-e#GkRZG zKK}Fkr1BP{!t3nM#da#4`u}bI!xv#QI~|TjXB}OzC;#rA<=T8+tF~RqpD#aug4?VZ zpU|2%F`F_w?^c=Mu6=e_|MAP7+_lpuv|##`cT=k4e8Lu|&v^OUn!T>z@SzDW0=p6q znK7LcFMXiX8PsvUPA*GQ`MCC%`TLIuZ(Kg*`pQVom)c2eN;}T*TAVz8e$lQunVs_( z`4%z%-=^?=x}N&e&*Gl?_bj{SZ?OqjTr4&HAXlNoYDcFxQLXyRQeLUw))hE-R9EMn vCbw#}LWlXSopT-<I;p2}Yd<cDSf<k9bBVuG$bkLk>`T0MG3zI-&@lu68F>3k literal 0 HcmV?d00001 diff --git a/systest/build.rs b/systest/build.rs index 2cb63ba1..1d2b9116 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -32,6 +32,8 @@ fn main() { .header("Security/SecRandom.h") .header("Security/SecureTransport.h") .header("Security/SecTrust.h") + .header("Security/CMSEncoder.h") + .header("Security/CMSDecoder.h") .flag("-Wno-deprecated-declarations") .type_name(|name, _, _| name.to_string()) .skip_signededness(|s| s.ends_with("Ref") || s.ends_with("Func")) diff --git a/systest/src/main.rs b/systest/src/main.rs index 331dc973..55178724 100644 --- a/systest/src/main.rs +++ b/systest/src/main.rs @@ -39,5 +39,6 @@ use security_framework_sys::secure_transport::*; use security_framework_sys::transform::*; use security_framework_sys::trust::*; use security_framework_sys::trust_settings::*; +use security_framework_sys::cms::*; include!(concat!(env!("OUT_DIR"), "/all.rs"));