simple-c2pa

This is a fork of https://gitlab.com/guardianproject/proofmode/simple-c2pa
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/simple-c2pa.git
Log | Files | Refs | README | LICENSE

certificates.rs (10929B)


      1 use std::result::Result;
      2 use std::sync::Arc;
      3 
      4 use openssl::asn1::Asn1Integer;
      5 use openssl::bn::BigNum;
      6 use openssl::ec::{EcGroup, EcKey};
      7 use openssl::hash::MessageDigest;
      8 use openssl::nid::Nid;
      9 use openssl::pkey::PKey;
     10 use openssl::x509::extension::{
     11     AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectKeyIdentifier,
     12 };
     13 use openssl::x509::{X509Name, X509NameBuilder, X509};
     14 use ring::rand::SecureRandom;
     15 
     16 use crate::common::{FileData, SimpleC2PAError};
     17 
     18 pub fn create_private_key() -> Result<Arc<FileData>, SimpleC2PAError> {
     19     let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
     20     let ec_key = EcKey::generate(&group)?;
     21     let key = PKey::from_ec_key(ec_key)?;
     22     let key_pem = key.private_key_to_pem_pkcs8()?;
     23     let file = FileData::new(None, Some(key_pem.clone()), None);
     24     Ok(file)
     25 }
     26 
     27 const DEFAULT_ORGANIZATION: &str = "SimpleC2PA";
     28 
     29 #[derive(Debug, Clone)]
     30 pub enum CertificateType<'a> {
     31     OnlineRoot {
     32         organization: Option<&'a str>,
     33         validity_days: Option<u32>,
     34     },
     35     OnlineIntermediate {
     36         organization: Option<&'a str>,
     37         validity_days: Option<u32>,
     38     },
     39     OfflineRoot {
     40         organization: Option<&'a str>,
     41         validity_days: Option<u32>,
     42     },
     43     OfflineIntermediate {
     44         organization: Option<&'a str>,
     45         validity_days: Option<u32>,
     46     },
     47     ContentCredentials {
     48         organization: Option<&'a str>,
     49         validity_days: Option<u32>,
     50     },
     51 }
     52 
     53 fn format_certificate_name(org: Option<&str>, name: &str) -> String {
     54     format!("{} {}", org.unwrap_or(DEFAULT_ORGANIZATION), name)
     55 }
     56 
     57 impl CertificateType<'_> {
     58     const fn is_ca(&self) -> bool {
     59         match self {
     60             CertificateType::OnlineRoot { .. } => true,
     61             CertificateType::OnlineIntermediate { .. } => true,
     62             CertificateType::OfflineRoot { .. } => true,
     63             CertificateType::OfflineIntermediate { .. } => true,
     64             CertificateType::ContentCredentials { .. } => false,
     65         }
     66     }
     67 
     68     fn validity_days(&self) -> u32 {
     69         match self {
     70             CertificateType::OnlineRoot { validity_days, .. } => validity_days.unwrap_or(365 * 20),
     71             CertificateType::OnlineIntermediate { validity_days, .. } => {
     72                 validity_days.unwrap_or(365 * 20)
     73             }
     74             CertificateType::OfflineRoot { validity_days, .. } => validity_days.unwrap_or(365 * 20),
     75             CertificateType::OfflineIntermediate { validity_days, .. } => {
     76                 validity_days.unwrap_or(365 * 20)
     77             }
     78             CertificateType::ContentCredentials { validity_days, .. } => {
     79                 validity_days.unwrap_or(365)
     80             }
     81         }
     82     }
     83 
     84     fn to_organization(&self) -> &str {
     85         match self {
     86             CertificateType::OnlineRoot { organization, .. } => {
     87                 organization.unwrap_or(DEFAULT_ORGANIZATION)
     88             }
     89             CertificateType::OnlineIntermediate { organization, .. } => {
     90                 organization.unwrap_or(DEFAULT_ORGANIZATION)
     91             }
     92             CertificateType::OfflineRoot { organization, .. } => {
     93                 organization.unwrap_or(DEFAULT_ORGANIZATION)
     94             }
     95             CertificateType::OfflineIntermediate { organization, .. } => {
     96                 organization.unwrap_or(DEFAULT_ORGANIZATION)
     97             }
     98             CertificateType::ContentCredentials { organization, .. } => {
     99                 organization.unwrap_or(DEFAULT_ORGANIZATION)
    100             }
    101         }
    102     }
    103 
    104     fn to_common_name(&self) -> String {
    105         match self {
    106             CertificateType::OnlineRoot { organization, .. } => {
    107                 format_certificate_name(*organization, "Root CA")
    108             }
    109             CertificateType::OnlineIntermediate { organization, .. } => {
    110                 format_certificate_name(*organization, "Intermediate CA")
    111             }
    112             CertificateType::OfflineRoot { organization, .. } => {
    113                 format_certificate_name(*organization, "Offline Root CA")
    114             }
    115             CertificateType::OfflineIntermediate { organization, .. } => {
    116                 format_certificate_name(*organization, "Offline Intermediate CA")
    117             }
    118             CertificateType::ContentCredentials { organization, .. } => {
    119                 format_certificate_name(*organization, "Content Credentials")
    120             }
    121         }
    122     }
    123 }
    124 
    125 #[derive(Clone, Debug)]
    126 pub struct CertificateOptions<'a> {
    127     key: Arc<FileData>,
    128     certificate_type: CertificateType<'a>,
    129     parent_certificate: Option<Arc<Certificate>>,
    130     email_address: Option<&'a str>,
    131     #[allow(dead_code)]
    132     pgp_fingerprint: Option<&'a str>,
    133 }
    134 
    135 impl CertificateOptions<'_> {
    136     pub fn new<'a>(
    137         key: Arc<FileData>,
    138         certificate_type: CertificateType<'a>,
    139         parent_certificate: Option<Arc<Certificate>>,
    140         email_address: Option<&'a str>,
    141         pgp_fingerprint: Option<&'a str>,
    142     ) -> Arc<CertificateOptions<'a>> {
    143         Arc::new(CertificateOptions {
    144             key,
    145             certificate_type,
    146             parent_certificate,
    147             email_address,
    148             pgp_fingerprint,
    149         })
    150     }
    151 }
    152 
    153 #[derive(Clone, Debug)]
    154 pub struct Certificate {
    155     certificate_data: Arc<FileData>,
    156     private_key_data: Arc<FileData>,
    157     #[allow(dead_code)]
    158     parent_certificate: Option<Arc<Certificate>>,
    159 }
    160 
    161 impl Certificate {
    162     pub fn new(
    163         certificate_data: Arc<FileData>,
    164         private_key_data: Arc<FileData>,
    165         parent_certificate: Option<Arc<Certificate>>,
    166     ) -> Arc<Self> {
    167         Arc::new(Certificate {
    168             certificate_data,
    169             private_key_data,
    170             parent_certificate,
    171         })
    172     }
    173 
    174     pub fn get_certificate_bytes(&self) -> Result<Vec<u8>, SimpleC2PAError> {
    175         self.certificate_data.get_bytes()
    176     }
    177 
    178     pub fn get_private_key_bytes(&self) -> Result<Vec<u8>, SimpleC2PAError> {
    179         self.private_key_data.get_bytes()
    180     }
    181 }
    182 fn generate_serial_number() -> Result<Asn1Integer, SimpleC2PAError> {
    183     let random = ring::rand::SystemRandom::new();
    184     let mut serial_number_bytes = [0u8; 20];
    185     let _ = random.fill(&mut serial_number_bytes);
    186     let serial_number_bignum = BigNum::from_slice(&serial_number_bytes)?;
    187     let serial_number = serial_number_bignum.to_asn1_integer()?;
    188 
    189     Ok(serial_number)
    190 }
    191 
    192 fn create_name(options: &Arc<CertificateOptions>) -> Result<X509Name, SimpleC2PAError> {
    193     let mut name_builder = X509NameBuilder::new()?;
    194     name_builder.append_entry_by_text("CN", &options.certificate_type.to_common_name())?;
    195     name_builder.append_entry_by_text("O", options.certificate_type.to_organization())?;
    196 
    197     if let Some(email_address) = options.email_address {
    198         name_builder.append_entry_by_text("emailAddress", email_address)?;
    199     }
    200 
    201     let name = name_builder.build();
    202 
    203     Ok(name)
    204 }
    205 
    206 pub fn create_certificate(
    207     options: Arc<CertificateOptions>,
    208 ) -> Result<Arc<Certificate>, SimpleC2PAError> {
    209     let serial_number = generate_serial_number()?;
    210     let private_key = PKey::private_key_from_pem(&options.key.get_bytes()?)?;
    211     let is_ca = options.certificate_type.is_ca();
    212     let name = create_name(&options)?;
    213 
    214     let mut cert_builder = X509::builder()?;
    215     cert_builder.set_version(2)?;
    216     cert_builder.set_subject_name(&name)?;
    217     if let Some(parent_certificate) = &options.parent_certificate {
    218         let parent_cert = X509::from_pem(&parent_certificate.certificate_data.get_bytes()?)?;
    219         cert_builder.set_issuer_name(parent_cert.subject_name())?;
    220     } else {
    221         cert_builder.set_issuer_name(&name)?;
    222     }
    223     cert_builder.set_pubkey(&private_key)?;
    224     cert_builder.set_serial_number(&serial_number)?;
    225 
    226     let not_before = openssl::asn1::Asn1Time::days_from_now(0)?;
    227     let not_after =
    228         openssl::asn1::Asn1Time::days_from_now(options.certificate_type.validity_days())?;
    229     cert_builder.set_not_before(&not_before)?;
    230     cert_builder.set_not_after(&not_after)?;
    231 
    232     let mut basic_constraints = BasicConstraints::new();
    233     if options.certificate_type.is_ca() {
    234         basic_constraints.critical().ca();
    235     }
    236     cert_builder.append_extension(basic_constraints.build()?)?;
    237 
    238     let mut key_usage = KeyUsage::new();
    239     if is_ca {
    240         key_usage.critical().key_cert_sign().crl_sign();
    241     } else {
    242         key_usage.digital_signature();
    243     }
    244     cert_builder.append_extension(key_usage.build()?)?;
    245 
    246     let subject_key_id =
    247         SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
    248     cert_builder.append_extension(subject_key_id)?;
    249 
    250     if !is_ca {
    251         let authority_key_id = AuthorityKeyIdentifier::new()
    252             .keyid(false)
    253             .build(&cert_builder.x509v3_context(None, None))?;
    254         cert_builder.append_extension(authority_key_id)?;
    255     }
    256 
    257     if !is_ca {
    258         let extended_key_usage = openssl::x509::extension::ExtendedKeyUsage::new()
    259             .email_protection()
    260             .build()?;
    261         cert_builder.append_extension(extended_key_usage)?;
    262     }
    263 
    264     let mut certificate_chain = vec![];
    265     if let Some(ref parent_certificate) = options.parent_certificate {
    266         let parent_private_key =
    267             PKey::private_key_from_pem(&parent_certificate.private_key_data.get_bytes()?)?;
    268         cert_builder.sign(&parent_private_key, MessageDigest::sha512())?;
    269         certificate_chain.push(parent_certificate);
    270     } else {
    271         cert_builder.sign(&private_key, MessageDigest::sha512())?;
    272     }
    273 
    274     let x509 = cert_builder.build();
    275     let file_data = FileData::new(None, Some(x509.to_pem()?), None);
    276     let certificate = Certificate::new(
    277         file_data.clone(),
    278         options.key.clone(),
    279         options.parent_certificate.clone(),
    280     );
    281 
    282     Ok(certificate)
    283 }
    284 
    285 pub fn create_root_certificate(
    286     organization: Option<&str>,
    287     validity_days: Option<u32>,
    288 ) -> Result<Arc<Certificate>, SimpleC2PAError> {
    289     let key = create_private_key().unwrap();
    290     let options = CertificateOptions::new(
    291         key.clone(),
    292         CertificateType::OfflineRoot {
    293             organization,
    294             validity_days,
    295         },
    296         None,
    297         None,
    298         None,
    299     );
    300     let certificate = create_certificate(options)?;
    301     Ok(certificate)
    302 }
    303 
    304 pub fn create_content_credentials_certificate(
    305     root_certificate: Option<Arc<Certificate>>,
    306     organization: Option<&str>,
    307     validity_days: Option<u32>,
    308 ) -> Result<Arc<Certificate>, SimpleC2PAError> {
    309     let key = create_private_key().unwrap();
    310     let options = CertificateOptions::new(
    311         key.clone(),
    312         CertificateType::ContentCredentials {
    313             organization,
    314             validity_days,
    315         },
    316         root_certificate,
    317         None,
    318         None,
    319     );
    320     let certificate = create_certificate(options)?;
    321     Ok(certificate)
    322 }
    323 
    324 pub fn request_signed_certificate(
    325     _options: Arc<CertificateOptions>,
    326 ) -> Result<String, SimpleC2PAError> {
    327     Ok("not yet implemented".to_string())
    328 }