cert_manager/
x509.rs

1use std::{
2    fs::{self, File},
3    io::{self, BufReader, Error, ErrorKind, Read, Write},
4    path::Path,
5};
6
7use rcgen::{
8    date_time_ymd, BasicConstraints, Certificate, CertificateParams, CertificateSigningRequest,
9    DistinguishedName, DnType, IsCa, KeyPair,
10};
11use rsa::{pkcs1::LineEnding, pkcs8::EncodePrivateKey, RsaPrivateKey};
12use rustls_pemfile::{read_one, Item};
13
14/// Represents a certificate authoriry.
15/// CA acts as a trusted third party.
16/// ref. <https://en.wikipedia.org/wiki/Certificate_authority>
17/// ref. <https://github.com/djc/sign-cert-remote/blob/main/src/main.rs>
18pub struct Ca {
19    pub cert: Certificate,
20}
21
22impl Ca {
23    pub fn new(common_name: &str) -> io::Result<Self> {
24        let cert_params = default_params(None, Some(common_name.to_string()), true)?;
25        let cert = generate(Some(cert_params))?;
26        Ok(Self { cert })
27    }
28
29    pub fn new_with_parameters(cert_params: Option<CertificateParams>) -> io::Result<Self> {
30        let cert = generate(cert_params)?;
31        Ok(Self { cert })
32    }
33
34    /// Saves the certificate in PEM format.
35    pub fn save(
36        &self,
37        overwrite: bool,
38        key_path: Option<&str>,
39        cert_path: Option<&str>,
40    ) -> io::Result<(String, String)> {
41        let key_path = if let Some(p) = key_path {
42            if !overwrite && Path::new(p).exists() {
43                return Err(Error::new(
44                    ErrorKind::Other,
45                    format!("key path '{p}' already exists"),
46                ));
47            }
48            p.to_string()
49        } else {
50            random_manager::tmp_path(10, Some(".key"))?
51        };
52
53        let cert_path = if let Some(p) = cert_path {
54            if !overwrite && Path::new(p).exists() {
55                return Err(Error::new(
56                    ErrorKind::Other,
57                    format!("cert path '{p}' already exists"),
58                ));
59            }
60            p.to_string()
61        } else {
62            random_manager::tmp_path(10, Some(".cert"))?
63        };
64
65        // ref. "crypto/tls.parsePrivateKey"
66        // ref. "crypto/x509.MarshalPKCS8PrivateKey"
67        let key_contents = self.cert.serialize_private_key_pem();
68        let mut key_file = File::create(&key_path)?;
69        key_file.write_all(key_contents.as_bytes())?;
70        log::info!("saved key '{key_path}' ({}-byte)", key_contents.len());
71
72        let cert_contents = self
73            .cert
74            .serialize_pem()
75            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize_pem {}", e)))?;
76        let mut cert_file = File::create(&cert_path)?;
77        cert_file.write_all(cert_contents.as_bytes())?;
78        log::info!("saved cert '{cert_path}' ({}-byte)", cert_contents.len());
79
80        Ok((key_path, cert_path))
81    }
82
83    /// Issues a certificate in PEM format.
84    /// And returns the issued certificate in PEM format.
85    pub fn issue_cert(&self, csr_pem: &str) -> io::Result<String> {
86        log::info!("issuing a cert for CSR");
87        let csr = CertificateSigningRequest::from_pem(csr_pem).map_err(|e| {
88            Error::new(
89                ErrorKind::Other,
90                format!("failed CertificateSigningRequest::from_pem {}", e),
91            )
92        })?;
93        csr.serialize_pem_with_signer(&self.cert).map_err(|e| {
94            Error::new(
95                ErrorKind::Other,
96                format!("failed serialize_pem_with_signer {}", e),
97            )
98        })
99    }
100
101    /// Issues and saves a certificate in PEM format.
102    /// And returns the issued cert in PEM format, and the saved cert file path.
103    pub fn issue_and_save_cert(
104        &self,
105        csr_pem: &str,
106        overwrite: bool,
107        cert_path: Option<&str>,
108    ) -> io::Result<(String, String)> {
109        let cert_path = if let Some(p) = cert_path {
110            if !overwrite && Path::new(p).exists() {
111                return Err(Error::new(
112                    ErrorKind::Other,
113                    format!("CSR path '{p}' already exists"),
114                ));
115            }
116            p.to_string()
117        } else {
118            random_manager::tmp_path(10, Some(".csr.pem"))?
119        };
120
121        log::info!("saving the issued certificate in '{cert_path}'");
122        let issued_cert = self.issue_cert(csr_pem)?;
123        let mut issued_cert_file = File::create(&cert_path)?;
124        issued_cert_file.write_all(issued_cert.as_bytes())?;
125        log::info!("saved cert '{cert_path}' ({}-byte)", issued_cert.len());
126
127        Ok((issued_cert, cert_path))
128    }
129}
130
131/// Represents a certificate signing request entity.
132/// ref. <https://en.wikipedia.org/wiki/Certificate_signing_request>
133/// ref. <https://github.com/djc/sign-cert-remote/blob/main/src/main.rs>
134pub struct CsrEntity {
135    pub cert: Certificate,
136    pub csr_pem: String,
137}
138
139impl CsrEntity {
140    pub fn new(common_name: &str) -> io::Result<Self> {
141        let cert_params = default_params(None, Some(common_name.to_string()), false)?;
142        let (cert, csr_pem) = generate_csr(cert_params)?;
143        Ok(Self { cert, csr_pem })
144    }
145
146    pub fn new_with_parameters(cert_params: CertificateParams) -> io::Result<Self> {
147        let (cert, csr_pem) = generate_csr(cert_params)?;
148        Ok(Self { cert, csr_pem })
149    }
150
151    /// Saves the CSR in PEM format, and returns the file path.
152    pub fn save_csr(&self, overwrite: bool, csr_path: Option<&str>) -> io::Result<String> {
153        let csr_path = if let Some(p) = csr_path {
154            if !overwrite && Path::new(p).exists() {
155                return Err(Error::new(
156                    ErrorKind::Other,
157                    format!("CSR path '{p}' already exists"),
158                ));
159            }
160            p.to_string()
161        } else {
162            random_manager::tmp_path(10, Some(".csr.pem"))?
163        };
164
165        let mut csr_file = File::create(&csr_path)?;
166        csr_file.write_all(self.csr_pem.as_bytes())?;
167        log::info!("saved CSR '{csr_path}' ({}-byte)", self.csr_pem.len());
168
169        Ok(csr_path)
170    }
171
172    /// Saves the key, cert, and CSR, in PEM format.
173    /// And returns the file paths.
174    pub fn save(
175        &self,
176        overwrite: bool,
177        csr_key_path: Option<&str>,
178        csr_cert_path: Option<&str>,
179        csr_path: Option<&str>,
180    ) -> io::Result<(String, String, String)> {
181        let csr_key_path = if let Some(p) = csr_key_path {
182            if !overwrite && Path::new(p).exists() {
183                return Err(Error::new(
184                    ErrorKind::Other,
185                    format!("CSR key path '{p}' already exists"),
186                ));
187            }
188            p.to_string()
189        } else {
190            random_manager::tmp_path(10, Some(".key"))?
191        };
192
193        let csr_cert_path = if let Some(p) = csr_cert_path {
194            if !overwrite && Path::new(p).exists() {
195                return Err(Error::new(
196                    ErrorKind::Other,
197                    format!("CSR cert path '{p}' already exists"),
198                ));
199            }
200            p.to_string()
201        } else {
202            random_manager::tmp_path(10, Some(".cert"))?
203        };
204
205        let csr_path = if let Some(p) = csr_path {
206            if !overwrite && Path::new(p).exists() {
207                return Err(Error::new(
208                    ErrorKind::Other,
209                    format!("CSR path '{p}' already exists"),
210                ));
211            }
212            p.to_string()
213        } else {
214            random_manager::tmp_path(10, Some(".csr.pem"))?
215        };
216
217        // ref. "crypto/tls.parsePrivateKey"
218        // ref. "crypto/x509.MarshalPKCS8PrivateKey"
219        let csr_key_contents = self.cert.serialize_private_key_pem();
220        let mut csr_key_file = File::create(&csr_key_path)?;
221        csr_key_file.write_all(csr_key_contents.as_bytes())?;
222        log::info!(
223            "saved key '{csr_key_path}' ({}-byte)",
224            csr_key_contents.len()
225        );
226
227        let csr_cert_contents = self
228            .cert
229            .serialize_pem()
230            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize_pem {}", e)))?;
231        let mut cert_file = File::create(&csr_cert_path)?;
232        cert_file.write_all(csr_cert_contents.as_bytes())?;
233        log::info!(
234            "saved cert '{csr_cert_path}' ({}-byte)",
235            csr_cert_contents.len()
236        );
237
238        // ref. "crypto/tls.parsePrivateKey"
239        // ref. "crypto/x509.MarshalPKCS8PrivateKey"
240        let mut csr_file = File::create(&csr_path)?;
241        csr_file.write_all(self.csr_pem.as_bytes())?;
242        log::info!("saved CSR '{csr_path}' ({}-byte)", self.csr_pem.len());
243
244        Ok((csr_key_path, csr_cert_path, csr_path))
245    }
246}
247
248/// RUST_LOG=debug cargo test --all-features --lib -- x509::test_csr --exact --show-output
249#[test]
250fn test_csr() {
251    use std::process::{Command, Stdio};
252
253    let _ = env_logger::builder()
254        .filter_level(log::LevelFilter::Info)
255        .is_test(true)
256        .try_init();
257
258    let ca = Ca::new("ca.hello.com").unwrap();
259    let (ca_key_path, ca_cert_path) = ca.save(true, None, None).unwrap();
260    let openssl_args = vec![
261        "x509".to_string(),
262        "-text".to_string(),
263        "-noout".to_string(),
264        "-in".to_string(),
265        ca_cert_path.to_string(),
266    ];
267    let openssl_cmd = Command::new("openssl")
268        .stderr(Stdio::piped())
269        .stdout(Stdio::piped())
270        .args(openssl_args)
271        .spawn()
272        .unwrap();
273    log::info!("ran openssl x509 with PID {}", openssl_cmd.id());
274    let res = openssl_cmd.wait_with_output();
275    match res {
276        Ok(output) => {
277            println!(
278                "openssl output {} bytes:\n{}\n",
279                output.stdout.len(),
280                String::from_utf8(output.stdout).unwrap()
281            )
282        }
283        Err(e) => {
284            log::warn!("failed to run openssl {}", e)
285        }
286    }
287
288    let csr_entity = CsrEntity::new("entity.hello.com").unwrap();
289    log::info!("csr_entity.csr:\n\n{}", csr_entity.csr_pem);
290    let (csr_key_path, csr_cert_path, csr_path) = csr_entity.save(true, None, None, None).unwrap();
291    log::info!("csr_key_path: {csr_key_path}");
292    log::info!("csr_cert_path: {csr_cert_path}");
293    log::info!("csr_path: {csr_path}");
294    let openssl_args = vec![
295        "x509".to_string(),
296        "-text".to_string(),
297        "-noout".to_string(),
298        "-in".to_string(),
299        csr_cert_path.to_string(),
300    ];
301    let openssl_cmd = Command::new("openssl")
302        .stderr(Stdio::piped())
303        .stdout(Stdio::piped())
304        .args(openssl_args)
305        .spawn()
306        .unwrap();
307    log::info!("ran openssl x509 with PID {}", openssl_cmd.id());
308    let res = openssl_cmd.wait_with_output();
309    match res {
310        Ok(output) => {
311            println!(
312                "openssl output {} bytes:\n{}\n",
313                output.stdout.len(),
314                String::from_utf8(output.stdout).unwrap()
315            )
316        }
317        Err(e) => {
318            log::warn!("failed to run openssl {}", e)
319        }
320    }
321
322    let issued_cert = ca.issue_cert(&csr_entity.csr_pem).unwrap();
323    log::info!("issued_cert:\n\n{issued_cert}");
324
325    let (issued_cert, issued_cert_path) = ca
326        .issue_and_save_cert(&csr_entity.csr_pem, true, None)
327        .unwrap();
328    log::info!("issued_cert:\n\n{issued_cert}");
329    log::info!("issued_cert issued_cert_path: {issued_cert_path}");
330    let openssl_args = vec![
331        "x509".to_string(),
332        "-text".to_string(),
333        "-noout".to_string(),
334        "-in".to_string(),
335        issued_cert_path.to_string(),
336    ];
337    let openssl_cmd = Command::new("openssl")
338        .stderr(Stdio::piped())
339        .stdout(Stdio::piped())
340        .args(openssl_args)
341        .spawn()
342        .unwrap();
343    log::info!("ran openssl x509 with PID {}", openssl_cmd.id());
344    let res = openssl_cmd.wait_with_output();
345    match res {
346        Ok(output) => {
347            println!(
348                "openssl output {} bytes:\n{}\n",
349                output.stdout.len(),
350                String::from_utf8(output.stdout).unwrap()
351            )
352        }
353        Err(e) => {
354            log::warn!("failed to run openssl {}", e)
355        }
356    }
357
358    fs::remove_file(&ca_key_path).unwrap();
359    fs::remove_file(&ca_cert_path).unwrap();
360
361    fs::remove_file(&csr_key_path).unwrap();
362    fs::remove_file(&csr_cert_path).unwrap();
363    fs::remove_file(&csr_path).unwrap();
364
365    fs::remove_file(&issued_cert_path).unwrap();
366}
367
368/// Generates a X509 certificate pair.
369/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/staking#NewCertAndKeyBytes>
370///
371/// See https://github.com/ava-labs/avalanche-types/blob/ad1730ed193cf1cd5056f23d130c3defc897cab5/avalanche-types/src/cert.rs
372/// to use "openssl" crate.
373pub fn generate(params: Option<CertificateParams>) -> io::Result<Certificate> {
374    let cert_params = if let Some(p) = params {
375        p
376    } else {
377        default_params(None, None, false)?
378    };
379    Certificate::from_params(cert_params).map_err(|e| {
380        Error::new(
381            ErrorKind::Other,
382            format!("failed to generate certificate {}", e),
383        )
384    })
385}
386
387/// Generates a certificate and returns the certificate and the CSR.
388/// ref. <https://github.com/djc/sign-cert-remote/blob/main/src/main.rs>
389pub fn generate_csr(params: CertificateParams) -> io::Result<(Certificate, String)> {
390    let cert = generate(Some(params))?;
391    let csr = cert.serialize_request_pem().map_err(|e| {
392        Error::new(
393            ErrorKind::Other,
394            format!("failed to serialize_request_pem {}", e),
395        )
396    })?;
397    Ok((cert, csr))
398}
399
400/// Generates a X509 certificate pair and writes them as PEM files.
401/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/staking#NewCertAndKeyBytes>
402///
403/// See https://github.com/ava-labs/avalanche-types/blob/ad1730ed193cf1cd5056f23d130c3defc897cab5/avalanche-types/src/cert.rs
404/// to use "openssl" crate.
405pub fn generate_and_write_pem(
406    params: Option<CertificateParams>,
407    key_path: &str,
408    cert_path: &str,
409) -> io::Result<()> {
410    log::info!("generating key '{key_path}', cert '{cert_path}' (PEM format)");
411    if Path::new(key_path).exists() {
412        return Err(Error::new(
413            ErrorKind::Other,
414            format!("key path '{key_path}' already exists"),
415        ));
416    }
417    if Path::new(cert_path).exists() {
418        return Err(Error::new(
419            ErrorKind::Other,
420            format!("cert path '{cert_path}' already exists"),
421        ));
422    }
423
424    let cert = generate(params)?;
425
426    // ref. "crypto/tls.parsePrivateKey"
427    // ref. "crypto/x509.MarshalPKCS8PrivateKey"
428    let key_contents = cert.serialize_private_key_pem();
429    let mut key_file = File::create(key_path)?;
430    key_file.write_all(key_contents.as_bytes())?;
431    log::info!("saved key '{key_path}' ({}-byte)", key_contents.len());
432
433    let cert_contents = cert
434        .serialize_pem()
435        .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize_pem {}", e)))?;
436
437    let mut cert_file = File::create(cert_path)?;
438    cert_file.write_all(cert_contents.as_bytes())?;
439    log::info!("saved cert '{cert_path}' ({}-byte)", cert_contents.len());
440
441    Ok(())
442}
443
444/// Loads the key and certificate from the PEM-encoded files.
445pub fn load_pem_to_vec(key_path: &str, cert_path: &str) -> io::Result<(Vec<u8>, Vec<u8>)> {
446    log::info!("loading PEM from key path '{key_path}' and cert '{cert_path}' (as PEM)");
447
448    if !Path::new(key_path).exists() {
449        return Err(Error::new(
450            ErrorKind::Other,
451            format!("key path '{key_path}' does not exist"),
452        ));
453    }
454    if !Path::new(cert_path).exists() {
455        return Err(Error::new(
456            ErrorKind::Other,
457            format!("cert path '{cert_path}' does not exist"),
458        ));
459    }
460
461    Ok((read_vec(key_path)?, read_vec(cert_path)?))
462}
463
464/// Use RSA for Apple M*.
465/// ref. <https://github.com/sfackler/rust-native-tls/issues/225>
466#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
467fn default_sig_algo() -> String {
468    "PKCS_RSA_SHA256".to_string()
469}
470
471#[cfg(not(all(target_arch = "aarch64", target_os = "macos")))]
472fn default_sig_algo() -> String {
473    "PKCS_ECDSA_P256_SHA256".to_string()
474}
475
476pub fn default_params(
477    sig_algo: Option<String>,
478    common_name: Option<String>,
479    is_ca: bool,
480) -> io::Result<CertificateParams> {
481    let mut cert_params = CertificateParams::default();
482
483    let sa = if let Some(sg) = &sig_algo {
484        sg.to_string()
485    } else {
486        default_sig_algo()
487    };
488    log::info!("generating parameter with signature algorithm '{sa}'");
489
490    let key_pair = match sa.as_str() {
491        "PKCS_RSA_SHA256" => {
492            cert_params.alg = &rcgen::PKCS_RSA_SHA256;
493            let mut rng = rand::thread_rng();
494            let private_key = RsaPrivateKey::new(&mut rng, 2048).map_err(|e| {
495                Error::new(ErrorKind::Other, format!("failed to generate key {}", e))
496            })?;
497            let key = private_key.to_pkcs8_pem(LineEnding::CRLF).map_err(|e| {
498                Error::new(ErrorKind::Other, format!("failed to convert key {}", e))
499            })?;
500            KeyPair::from_pem(&key).map_err(|e| {
501                Error::new(
502                    ErrorKind::Other,
503                    format!("failed to generate PKCS_RSA_SHA256 key pair {}", e),
504                )
505            })?
506        }
507
508        // this works on linux with avalanchego
509        "PKCS_ECDSA_P256_SHA256" => {
510            cert_params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
511            KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).map_err(|e| {
512                Error::new(
513                    ErrorKind::Other,
514                    format!("failed to generate PKCS_ECDSA_P256_SHA256 key pair {}", e),
515                )
516            })?
517        }
518
519        // this fails avalanchego peer IP verification (e.g., incorrect signature)
520        //
521        // currently, "avalanchego" only signs the IP with "crypto.SHA256"
522        // ref. "avalanchego/network/ip_signer.go.newIPSigner"
523        // ref. "avalanchego/network/peer/ip.go UnsignedIP.Sign" with "crypto.SHA256"
524        //
525        // TODO: support sha384/512 signatures in avalanchego node
526        "PKCS_ECDSA_P384_SHA384" => {
527            cert_params.alg = &rcgen::PKCS_ECDSA_P384_SHA384;
528            KeyPair::generate(&rcgen::PKCS_ECDSA_P384_SHA384).map_err(|e| {
529                Error::new(
530                    ErrorKind::Other,
531                    format!("failed to generate PKCS_ECDSA_P384_SHA384 key pair {}", e),
532                )
533            })?
534        }
535
536        _ => {
537            return Err(Error::new(
538                ErrorKind::InvalidInput,
539                format!("unknown signature algorithm {sa}"),
540            ))
541        }
542    };
543    cert_params.key_pair = Some(key_pair);
544
545    cert_params.not_before = date_time_ymd(2023, 5, 1);
546    cert_params.not_after = date_time_ymd(5000, 1, 1);
547
548    cert_params.distinguished_name = DistinguishedName::new();
549    cert_params
550        .distinguished_name
551        .push(DnType::CountryName, "US");
552    cert_params
553        .distinguished_name
554        .push(DnType::StateOrProvinceName, "NY");
555    cert_params
556        .distinguished_name
557        .push(DnType::OrganizationName, "Test Org");
558    if let Some(cm) = &common_name {
559        cert_params
560            .distinguished_name
561            .push(DnType::CommonName, cm.to_string());
562    } else {
563        cert_params
564            .distinguished_name
565            .push(DnType::CommonName, "test common name");
566    }
567
568    if is_ca {
569        cert_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
570    }
571
572    Ok(cert_params)
573}
574
575/// RUST_LOG=debug cargo test --all-features --lib -- x509::test_pem --exact --show-output
576#[test]
577fn test_pem() {
578    use std::process::{Command, Stdio};
579
580    let _ = env_logger::builder()
581        .filter_level(log::LevelFilter::Info)
582        .is_test(true)
583        .try_init();
584
585    let tmp_dir = tempfile::tempdir().unwrap();
586
587    let key_path = tmp_dir.path().join(random_manager::secure_string(20));
588    let key_path = key_path.as_os_str().to_str().unwrap();
589    let mut key_path = String::from(key_path);
590    key_path.push_str(".key");
591
592    let cert_path = tmp_dir.path().join(random_manager::secure_string(20));
593    let cert_path = cert_path.as_os_str().to_str().unwrap();
594    let mut cert_path = String::from(cert_path);
595    cert_path.push_str(".cert");
596    let cert_path = random_manager::tmp_path(10, Some(".pem")).unwrap();
597
598    generate_and_write_pem(None, &key_path, &cert_path).unwrap();
599    load_pem_to_vec(&key_path, &cert_path).unwrap();
600
601    let key_contents = fs::read(&key_path).unwrap();
602    let key_contents = String::from_utf8(key_contents.to_vec()).unwrap();
603    log::info!("key {}", key_contents);
604    log::info!("key: {} bytes", key_contents.len());
605
606    // openssl x509 -in [cert_path] -text -noout
607    let cert_contents = fs::read(&cert_path).unwrap();
608    let cert_contents = String::from_utf8(cert_contents.to_vec()).unwrap();
609    log::info!("cert {}", cert_contents);
610    log::info!("cert: {} bytes", cert_contents.len());
611
612    let openssl_args = vec![
613        "x509".to_string(),
614        "-in".to_string(),
615        cert_path.to_string(),
616        "-text".to_string(),
617        "-noout".to_string(),
618    ];
619    let openssl_cmd = Command::new("openssl")
620        .stderr(Stdio::piped())
621        .stdout(Stdio::piped())
622        .args(openssl_args)
623        .spawn()
624        .unwrap();
625    log::info!("ran openssl with PID {}", openssl_cmd.id());
626    let res = openssl_cmd.wait_with_output();
627    match res {
628        Ok(output) => {
629            log::info!(
630                "openssl output:\n{}\n",
631                String::from_utf8(output.stdout).unwrap()
632            )
633        }
634        Err(e) => {
635            log::warn!("failed to run openssl {}", e)
636        }
637    }
638
639    let (key, cert) = load_pem_key_cert_to_der(&key_path, &cert_path).unwrap();
640    log::info!("loaded key: {:?}", key);
641    log::info!("loaded cert: {:?}", cert);
642
643    let serial = load_pem_cert_serial(&cert_path).unwrap();
644    log::info!("serial: {:?}", serial);
645
646    fs::remove_file(&key_path).unwrap();
647    fs::remove_file(&cert_path).unwrap();
648}
649
650/// Loads the TLS key and certificate from the PEM-encoded files, as DER.
651pub fn load_pem_key_cert_to_der(
652    key_path: &str,
653    cert_path: &str,
654) -> io::Result<(rustls::PrivateKey, rustls::Certificate)> {
655    log::info!("loading PEM from key path '{key_path}' and cert '{cert_path}' (to DER)");
656    if !Path::new(key_path).exists() {
657        return Err(Error::new(
658            ErrorKind::NotFound,
659            format!("cert path {} does not exists", key_path),
660        ));
661    }
662    if !Path::new(cert_path).exists() {
663        return Err(Error::new(
664            ErrorKind::NotFound,
665            format!("cert path {} does not exists", cert_path),
666        ));
667    }
668
669    // ref. "tls.Certificate.Leaf.Raw" in Go
670    // ref. "tls.X509KeyPair"
671    // ref. "x509.ParseCertificate/parseCertificate"
672    // ref. "x509.Certificate.Leaf"
673    //
674    // use openssl::x509::X509;
675    // let pub_key_contents = fs::read(cert_file_path)?;
676    // let pub_key = X509::from_pem(&pub_key_contents.to_vec())?;
677    // let pub_key_der = pub_key.to_der()?;
678    //
679    // use pem;
680    // let pub_key_contents = fs::read(cert_file_path)?;
681    // let pub_key = pem::parse(&pub_key_contents.to_vec()).unwrap();
682    // let pub_key_der = pub_key.contents;
683
684    let key_file = File::open(key_path)?;
685    let mut reader = BufReader::new(key_file);
686    let pem_read = read_one(&mut reader)?;
687    let key = {
688        match pem_read.unwrap() {
689            Item::X509Certificate(_) => {
690                log::warn!("key path {} has unexpected certificate", key_path);
691                None
692            }
693            Item::RSAKey(key) => {
694                log::info!("loaded RSA key");
695                Some(key)
696            }
697            Item::PKCS8Key(key) => {
698                log::info!("loaded PKCS8 key");
699                Some(key)
700            }
701            Item::ECKey(key) => {
702                log::info!("loaded EC key");
703                Some(key)
704            }
705            _ => None,
706        }
707    };
708    if key.is_none() {
709        return Err(Error::new(
710            ErrorKind::NotFound,
711            format!("key path '{key_path}' found no key"),
712        ));
713    }
714    let key_der = key.unwrap();
715
716    let cert_file = File::open(cert_path)?;
717    let mut reader = BufReader::new(cert_file);
718    let pem_read = read_one(&mut reader)?;
719    let cert = {
720        match pem_read.unwrap() {
721            Item::X509Certificate(cert) => Some(cert),
722            Item::RSAKey(_) | Item::PKCS8Key(_) | Item::ECKey(_) => {
723                log::warn!("cert path '{cert_path}' has unexpected private key");
724                None
725            }
726            _ => None,
727        }
728    };
729    if cert.is_none() {
730        return Err(Error::new(
731            ErrorKind::NotFound,
732            format!("cert path '{cert_path}' found no cert"),
733        ));
734    }
735    let cert_der = cert.unwrap();
736
737    Ok((rustls::PrivateKey(key_der), rustls::Certificate(cert_der)))
738}
739
740/// Loads the serial number from the PEM-encoded certificate.
741pub fn load_pem_cert_serial(cert_path: &str) -> io::Result<Vec<u8>> {
742    log::info!("loading PEM cert '{cert_path}'");
743    if !Path::new(cert_path).exists() {
744        return Err(Error::new(
745            ErrorKind::NotFound,
746            format!("cert path '{cert_path}' does not exists"),
747        ));
748    }
749
750    let cert_raw = read_vec(cert_path)?;
751
752    let (_, parsed) = x509_parser::pem::parse_x509_pem(&cert_raw)
753        .map_err(|e| Error::new(ErrorKind::Other, format!("failed parse_x509_pem {}", e)))?;
754    let cert = parsed.parse_x509().map_err(|e| {
755        Error::new(
756            ErrorKind::Other,
757            format!("failed parse_x509_certificate {}", e),
758        )
759    })?;
760    let serial = cert.serial.clone();
761
762    Ok(serial.to_bytes_be())
763}
764
765/// Loads the PEM-encoded certificate as DER.
766pub fn load_pem_cert_to_der(cert_path: &str) -> io::Result<rustls::Certificate> {
767    log::info!("loading PEM cert '{cert_path}' (to DER)");
768    if !Path::new(cert_path).exists() {
769        return Err(Error::new(
770            ErrorKind::NotFound,
771            format!("cert path '{cert_path}' does not exists"),
772        ));
773    }
774
775    let cert_file = File::open(cert_path)?;
776    let mut reader = BufReader::new(cert_file);
777    let pem_read = read_one(&mut reader)?;
778    let cert = {
779        match pem_read.unwrap() {
780            Item::X509Certificate(cert) => Some(cert),
781            Item::RSAKey(_) | Item::PKCS8Key(_) | Item::ECKey(_) => {
782                log::warn!("cert path '{cert_path}' has unexpected private key");
783                None
784            }
785            _ => None,
786        }
787    };
788    if cert.is_none() {
789        return Err(Error::new(
790            ErrorKind::NotFound,
791            format!("cert path '{cert_path}' found no cert"),
792        ));
793    }
794    let cert_der = cert.unwrap();
795
796    Ok(rustls::Certificate(cert_der))
797}
798
799/// Generates a X509 certificate pair and returns them in DER format.
800/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/staking#NewCertAndKeyBytes>
801pub fn generate_der(
802    params: Option<CertificateParams>,
803) -> io::Result<(rustls::PrivateKey, rustls::Certificate)> {
804    log::info!("generating key and cert (DER format)");
805
806    let cert_params = if let Some(p) = params {
807        p
808    } else {
809        default_params(None, None, false)?
810    };
811    let cert = Certificate::from_params(cert_params).map_err(|e| {
812        Error::new(
813            ErrorKind::Other,
814            format!("failed to generate certificate {}", e),
815        )
816    })?;
817    let cert_der = cert
818        .serialize_der()
819        .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize_pem {}", e)))?;
820    // ref. "crypto/tls.parsePrivateKey"
821    // ref. "crypto/x509.MarshalPKCS8PrivateKey"
822    let key_der = cert.serialize_private_key_der();
823
824    Ok((rustls::PrivateKey(key_der), rustls::Certificate(cert_der)))
825}
826
827/// Loads the TLS key and certificate from the DER-encoded files.
828pub fn load_der_key_cert(
829    key_path: &str,
830    cert_path: &str,
831) -> io::Result<(rustls::PrivateKey, rustls::Certificate)> {
832    log::info!("loading DER from key path '{key_path}' and cert '{cert_path}'");
833    let (key, cert) = fs::read(key_path).and_then(|x| Ok((x, fs::read(cert_path)?)))?;
834    Ok((rustls::PrivateKey(key), rustls::Certificate(cert)))
835}
836
837/// RUST_LOG=debug cargo test --all-features --lib -- x509::test_generate_der --exact --show-output
838#[test]
839fn test_generate_der() {
840    let _ = env_logger::builder()
841        .filter_level(log::LevelFilter::Info)
842        .is_test(true)
843        .try_init();
844
845    let (key, cert) = generate_der(None).unwrap();
846    log::info!("key: {} bytes", key.0.len());
847    log::info!("cert: {} bytes", cert.0.len());
848}
849
850/// ref. <https://doc.rust-lang.org/std/fs/fn.read.html>
851fn read_vec(p: &str) -> io::Result<Vec<u8>> {
852    let mut f = File::open(p)?;
853    let metadata = fs::metadata(p)?;
854    let mut buffer = vec![0; metadata.len() as usize];
855    let _read_bytes = f.read(&mut buffer)?;
856    Ok(buffer)
857}