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;
pub const CMS_DIGEST_ALGORITHM_SHA1: &str = "sha1";
pub const CMS_DIGEST_ALGORITHM_SHA256: &str = "sha256";
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SignedAttributes: CMSSignedAttributes {
const SMIME_CAPABILITIES = kCMSAttrSmimeCapabilities;
const SMIME_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeEncryptionKeyPrefs;
const SMIME_MS_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeMSEncryptionKeyPrefs;
const SIGNING_TIME = kCMSAttrSigningTime;
const APPLE_CODESIGNING_HASH_AGILITY = kCMSAttrAppleCodesigningHashAgility;
const APPLE_CODESIGNING_HASH_AGILITY_V2 = kCMSAttrAppleCodesigningHashAgilityV2;
const APPLE_EXPIRATION_TIME = kCMSAttrAppleExpirationTime;
}
}
declare_TCFType! {
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 {
pub fn create() -> Result<Self> {
let mut inner: CMSEncoderRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCreate(&mut inner) })?;
Ok(Self(inner))
}
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(())
}
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(())
}
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())
}
}
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(())
}
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())
}
}
pub fn set_has_detached_content(&self, has_detached_content: bool) -> Result<()> {
cvt(unsafe { CMSEncoderSetHasDetachedContent(self.0, has_detached_content.into()) })?;
Ok(())
}
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)
}
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(())
}
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() })
}
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(())
}
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())
}
}
pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
Ok(())
}
pub fn set_certificate_chain_mode(
&self,
certificate_chain_mode: CMSCertificateChainMode) -> Result<()>
{
cvt(unsafe { CMSEncoderSetCertificateChainMode(self.0, certificate_chain_mode) })?;
Ok(())
}
pub fn get_certificate_chain_mode(&self) -> Result<CMSCertificateChainMode> {
let mut out = CMSCertificateChainMode::kCMSCertificateNone;
cvt(unsafe { CMSEncoderGetCertificateChainMode(self.0, &mut out) })?;
Ok(out)
}
pub fn update_content(&self, content: &[u8]) -> Result<()> {
cvt(unsafe { CMSEncoderUpdateContent(self.0, content.as_ptr() as _, content.len()) })?;
Ok(())
}
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() })
}
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)
}
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)
}
}
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::*;
pub struct SignerStatus {
pub signer_status: CMSSignerStatus,
pub sec_trust: SecTrust,
pub cert_verify_result: Result<()>,
}
declare_TCFType! {
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 {
pub fn create() -> Result<Self> {
let mut inner: CMSDecoderRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCreate(&mut inner) })?;
Ok(Self(inner))
}
pub fn update_message(
&self,
message: &[u8],
) -> Result<()> {
cvt(unsafe { CMSDecoderUpdateMessage(self.0, message.as_ptr() as _, message.len()) })?;
Ok(())
}
pub fn finalize_message(&self) -> Result<()> {
cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
Ok(())
}
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(())
}
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())
}
}
}
pub fn get_num_signers(&self) -> Result<usize> {
let mut out = 0;
cvt(unsafe { CMSDecoderGetNumSigners(self.0, &mut out) })?;
Ok(out)
}
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),
})
}
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() })
}
pub fn is_content_encrypted(&self) -> Result<bool> {
let mut out = 0;
cvt(unsafe { CMSDecoderIsContentEncrypted(self.0, &mut out) })?;
Ok(out != 0)
}
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() })
}
}
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())
}
}
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() })
}
}
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)
}
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)
}
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)
}
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, 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 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);
}
}