certificate_manager 0.2.2

A library for managing and spoofing X.509 certificates
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
use pkcs8::der::Decode;
use pkcs8::{
    pkcs5::pbes2::Parameters, DecodePrivateKey, LineEnding, PrivateKeyInfo, SecretDocument,
};
use rcgen::{
    date_time_ymd, BasicConstraints, Certificate, CertificateParams, DistinguishedName, DnType,
    IsCa, KeyPair, KeyUsagePurpose, SerialNumber, PKCS_RSA_SHA256,
};
use rustls_pki_types::CertificateDer;
use std::{fs::File, path::Path, sync::Arc};
use x509_parser::prelude::{oid_registry, GeneralName, ParsedExtension, X509Certificate};

use crate::error::Error;

/// A Certificate Authority for signing certificates.
#[derive(Clone)]
pub struct CertificateAuthority {
    pub cert: Arc<Certificate>,
    pub key_pair: Arc<KeyPair>,
}

impl CertificateAuthority {
    /// Generates a new self-signed certificate with a randomly generated serial number and a
    /// validity period of 1 year. The certificate will have a subject and issuer name that matches
    /// the provided `certificate_name`, or defaults to "localhost" if no value is provided.
    ///
    /// # Arguments
    ///
    /// * `certificate_name` - An optional string to set as the subject and issuer name in the
    ///   certificate. If not provided, the default value "localhost" is used.
    ///
    /// # Returns
    ///
    /// * `Result<CertifiedKey, Error>` - Returns a `CertifiedKey` containing the generated
    ///   certificate and private key if successful, or an `Error` if the operation fails.
    ///
    /// # Notes
    ///
    /// * The serial number is randomly generated.
    /// * The validity period is set to 1 year, but the certificate should be renewed before it
    ///   expires.
    /// * The certificate is self-signed, meaning it is signed by the same entity that created it.
    ///   This is not suitable for production use, as it is not trusted by default.
    pub fn new(certificate_name: Option<String>) -> Result<Self, Error> {
        // Generate an RSA private key
        let key = KeyPair::generate_for(&PKCS_RSA_SHA256)?;

        // Set the distinguished name (subject and issuer) and create certificate parameters
        let mut dn = DistinguishedName::new();
        let mut params = match certificate_name {
            Some(name) => {
                dn.push(DnType::CommonName, name.clone());
                CertificateParams::new(vec![name])?
            }
            None => {
                dn.push(DnType::CommonName, "localhost");
                CertificateParams::new(vec!["localhost".to_string()])?
            }
        };
        params.distinguished_name = dn;

        // Set key usage for a CA certificate
        params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];

        // Set the certificate as a CA
        params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);

        // Set arandom serial number
        params.serial_number = Some(SerialNumber::from_slice(
            &rand::random::<[u8; 16]>(),
        ));

        // Set the validity period
        params.not_before = date_time_ymd(2025, 1, 1);
        params.not_after = date_time_ymd(2045, 1, 1);

        let cert = params.self_signed(&key)?;

        Ok(Self {
            cert: Arc::new(cert),
            key_pair: Arc::new(key),
        })
    }

    /// Loads a `CertificateAuthority` from a certificate and private key stored in PEM files.
    ///
    /// # Arguments
    ///
    /// * `cert_file` - The path to the PEM file containing the CA's self-signed certificate.
    /// * `key_file` - The path to the PEM file containing the CA's private signing key.
    /// * `passphrase` - An optional string to decrypt the private key. If provided, the passphrase
    ///   is used to decrypt the private key.
    ///
    /// # Returns
    ///
    /// * `Result<Self, Error>` - Returns a `CertificateAuthority` instance if successful,
    ///   or an `Error` if the operation fails.
    pub fn load_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
        cert_file: P,
        key_file: Q,
        passphrase: Option<String>,
    ) -> Result<Self, Error> {
        let private_key = decrypt_private_key(key_file, passphrase)?;
        let key = KeyPair::from_pem(&private_key)?;

        let cert_pem = String::from_utf8(get_bytes_from_file(cert_file)?)?;
        let cert = CertificateParams::from_ca_cert_pem(&cert_pem)?;
        let cert = cert.self_signed(&key)?;

        Ok(Self {
            cert: Arc::new(cert),
            key_pair: Arc::new(key),
        })
    }

    /// Generates a signed X509 certificate for a given domain, with an optional list of subject
    /// alternative names (SANs).
    ///
    /// # Arguments
    ///
    /// * `domain` - The primary domain name for the certificate.
    /// * `san` - An optional vector of strings representing the SANs to include in the certificate.
    /// * `ca` - The `CertifiedKey` instance representing the Certificate Authority to sign the
    ///   certificate with.
    ///
    /// # Returns
    ///
    /// A `Result` containing the signed `Certificate` instance if successful, or an `Error` if
    /// the operation fails.
    ///
    /// # Notes
    ///
    /// * If no SANs are provided, the primary domain name is used as the default SAN.
    /// * The certificate is signed by the provided Certificate Authority.
    /// * The certificate is valid for 1 year.
    pub fn create_signed_certificate_for_domain(
        &self,
        domain: &str,
        san: Option<Vec<String>>,
    ) -> Result<Certificate, Error> {
        // Set the subject alternative names to the provided SANs
        let mut params = if let Some(san_list) = san {
            CertificateParams::new(san_list)?
        } else {
            // Default SAN to the primary domain if none is provided
            CertificateParams::new(vec![domain.to_string()])?
        };
        // Set the issuer name to the CA's name
        let mut dn = DistinguishedName::new();
        dn.push(DnType::CommonName, domain);
        params.distinguished_name = dn;

        // Set the validity period for the certificate
        params.not_before = date_time_ymd(1995, 1, 1);
        params.not_after = date_time_ymd(1995, 1, 1);

        // Sign the certificate using the CA's private key
        let cert = params.signed_by(&*self.key_pair, &self.cert, &self.key_pair)?;

        // Build and return the signed X509 certificate
        Ok(cert)
    }

    /// Spoofs a given X509 certificate by resigning it with the provided Certificate Authority.
    ///
    /// This function takes an X509 certificate and a Certificate Authority as input and returns a
    /// new X509 certificate that is signed by the provided CA. The returned certificate has the
    /// same fields and extensions as the original, but with a different signature. This is useful
    /// for testing and debugging purposes.
    ///
    /// # Arguments
    ///
    /// * `certificate` - The X509 certificate to spoof.
    /// * `ca` - The Certificate Authority to use for signing.
    ///
    /// # Returns
    ///
    /// A `Result` containing the spoofed X509 certificate if successful, or an `Error` if the
    /// operation fails.
    pub fn spoof_certificate(
        &self,
        certificate: CertificateDer<'static>,
    ) -> Result<CertificateDer<'static>, Error> {
        let certificate_params = CertificateParams::from_ca_cert_der(&certificate)?;

        // Sign the certificate using the CA's private key and SHA-256 digest
        let certificate =
            certificate_params.signed_by(&*self.key_pair, &self.cert, &self.key_pair)?;

        // Build and return the spoofed X509 certificate
        Ok(certificate.der().clone())
    }
}

/// Encrypts a private key using a passphrase.
///
/// This function takes a `KeyPair` and an `Option<String>` containing the passphrase.
/// If the passphrase is `Some`, it encrypts the private key using PBKDF2 with SHA-256 and AES-256-CBC,
/// and returns the encrypted key in PEM format.
/// If the passphrase is `None`, it returns the private key in unencrypted PEM format.
///
/// # Returns
///
/// * `Result<String, Error>` - Returns the encrypted private key in PEM format if successful,
///   or an `Error` if the operation fails.
///
/// # Notes
///
pub fn encrypt_private_key(
    key_pair: &KeyPair,
    passphrase: &Option<String>,
) -> Result<String, Error> {
    // Serialize the private key to PKCS#8 DER format
    let private_key_der = key_pair.serialize_der();

    let private_key = match passphrase {
        Some(passphrase) => {
            // Decode existing PKCS#8 private key
            let private_key_info = PrivateKeyInfo::from_der(&private_key_der)
                .map_err(|e| Error::Pkcs8Error(e.to_string()))?;

            // OpenSSL-compatible defaults
            let pbkdf2_iterations = 2048; // OpenSSL default iterations
            let pbkdf2_salt = rand::random::<[u8; 16]>(); // 16-byte salt
            let aes_iv = rand::random::<[u8; 16]>(); // AES-256-CBC IV (OpenSSL convention)

            // Create PBES2 parameters
            let pbes2_params =
                Parameters::pbkdf2_sha256_aes256cbc(pbkdf2_iterations, &pbkdf2_salt, &aes_iv)
                    .map_err(|e| Error::Pkcs8Error(e.to_string()))?;

            // Encrypt the private key
            let encrypted_key = private_key_info
                .encrypt_with_params(pbes2_params, passphrase)
                .map_err(|e| Error::Pkcs8Error(e.to_string()))?;

            // Convert the encrypted key to PEM format
            encrypted_key
                .to_pem("ENCRYPTED PRIVATE KEY", LineEnding::LF)
                .map_err(|e| Error::Pkcs8Error(e.to_string()))?
                .to_string()
        }
        None => {
            // If no passphrase, return the private key in unencrypted PEM format
            key_pair.serialize_pem()
        }
    };

    Ok(private_key)
}

/// Decrypts a private key stored in a PEM file.
///
/// If the passphrase is `Some`, it decrypts the private key using the provided passphrase.
/// If the passphrase is `None`, it assumes the private key is already decrypted and returns it as a PEM string.
///
/// # Arguments
///
/// * `key_file` - The path to the PEM file containing the private key.
/// * `passphrase` - An optional string to decrypt the private key. If provided, the passphrase
///   is used to decrypt the private key.
///
/// # Returns
///
/// * `Result<String, Error>` - Returns the decrypted private key in PEM format if successful,
///   or an `Error` if the operation fails.
fn decrypt_private_key<Q: AsRef<Path>>(
    key_file: Q,
    passphrase: Option<String>,
) -> Result<String, Error> {
    let private_key_pem = String::from_utf8(get_bytes_from_file(key_file)?)?;
    let private_key = match passphrase {
        Some(passphrase) => {
            // Decrypt the encrypted private key
            SecretDocument::from_pkcs8_encrypted_pem(&private_key_pem, passphrase.as_bytes())
                .map_err(|e| Error::Pkcs8Error(e.to_string()))?
                .to_pem("PRIVATE KEY", LineEnding::LF)
                .map_err(|e| Error::Pkcs8Error(e.to_string()))?
                .to_string()
        }
        None => SecretDocument::from_pkcs8_pem(&private_key_pem)
            .map_err(|e| Error::Pkcs8Error(e.to_string()))?
            .to_pem("PRIVATE KEY", LineEnding::LF)
            .map_err(|e| Error::Pkcs8Error(e.to_string()))?
            .to_string(),
    };
    Ok(private_key)
}
/// Reads the contents of a file into a byte vector.
///
/// # Arguments
///
/// * `path` - The path to the file to read.
///
/// # Returns
///
/// A `Result` containing the file contents as a byte vector or an `Error` if reading fails.
fn get_bytes_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
    let mut file = File::open(path)?;
    let mut bytes: Vec<u8> = vec![];
    std::io::copy(&mut file, &mut bytes)?;
    Ok(bytes)
}

/// Prints the contents of an X509 certificate to the console.
///
/// This function is intended for debugging and testing purposes. It prints the subject and
/// issuer names, subject alternative names (SANs), issuer alternative names (IANs), public
/// key, validity period, signature, and OCSP responders (if present) of the given X509
/// certificate.
pub fn print_certificate(certificate: &X509Certificate) {
    println!("New certificate");

    // Print the subject name
    println!("subject_name:");
    for entry in certificate.subject().iter_common_name() {
        println!(
            "CommonName: {}",
            entry.as_str().unwrap_or("<invalid UTF-8>")
        );
    }

    // Print the issuer name
    println!("issuer_name:");
    for entry in certificate.issuer().iter_common_name() {
        println!(
            "CommonName: {}",
            entry.as_str().unwrap_or("<invalid UTF-8>")
        );
    }

    // Print the subject alternative names (SANs)
    println!("subject_alt_names:");
    if let Some(san) = certificate.subject_alternative_name().unwrap() {
        for general_name in &san.value.general_names {
            print_general_name(general_name);
        }
    } else {
        println!("No subject alternative names found.");
    }

    // Print the issuer alternative names (IANs)
    println!("issuer_alt_names:");
    if let Some(ian_ext) = certificate
        .extensions()
        .iter()
        .find(|ext| ext.oid == oid_registry::OID_X509_EXT_ISSUER_ALT_NAME)
    {
        if let ParsedExtension::IssuerAlternativeName(ian) = &ian_ext.parsed_extension() {
            for general_name in &ian.general_names {
                print_general_name(general_name);
            }
        } else {
            println!("Issuer Alternative Name found, but format is unrecognized.");
        }
    } else {
        println!("No issuer alternative names found.");
    }

    // Print the public key of the certificate
    println!("public_key: {:?}", certificate.public_key());

    // Print the validity period of the certificate
    println!(
        "Validity period:\n  Not Before: {:?}\n  Not After: {:?}",
        certificate.validity.not_before.to_rfc2822(),
        certificate.validity.not_after.to_rfc2822()
    );

    // Print the signature of the certificate
    println!("Signature: {:x?}", certificate.signature_value.as_ref());

    // Print the signature algorithm used
    println!(
        "Signature algorithm: {}",
        certificate.signature_algorithm.algorithm
    );

    // Print the OCSP responders (AIA extension)
    println!("ocsp_responders:");
    if let Some(aia_extension) = certificate
        .extensions()
        .iter()
        .find(|ext| ext.oid == oid_registry::OID_PKIX_AUTHORITY_INFO_ACCESS)
    {
        if let ParsedExtension::AuthorityInfoAccess(access_info) = &aia_extension.parsed_extension()
        {
            for access in &access_info.accessdescs {
                println!("OCSP Responder: {}", access.access_location);
            }
        } else {
            println!("Error parsing AIA extension.");
        }
    } else {
        println!("No OCSP responders found.");
    }

    // Print the serial number of the certificate
    println!("Serial number : {}", certificate.serial);
}

/// Prints a human-readable representation of the given GeneralName to stdout.
///
/// This function matches on the given GeneralName and prints a string representation
/// of it. The printed string is intended to be human-readable, and as such, no
/// guarantees are made about the format of the output.
fn print_general_name(general_name: &GeneralName) {
    match general_name {
        GeneralName::DNSName(name) => println!("DNSName: {}", name),
        GeneralName::IPAddress(ip) => println!("IPAddress: {:?}", ip),
        GeneralName::URI(uri) => println!("URI: {}", uri),
        GeneralName::RFC822Name(email) => println!("Email: {}", email),
        _ => println!("Other GeneralName: {:?}", general_name),
    }
}