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(¬_before)?; 230 cert_builder.set_not_after(¬_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 }