Search code examples
rusthashopensslx509

How to create a fingerprint in Rust for a certficate generated with the rcgen crate?


I'm trying to use the sha2 crate to create a SHA256 fingerprint of a certificate created with the rcgen crate. However, when I create the SHA256 fingerprint with openssl, the fingerprints differ. As far as I understood, the fingerprint is the SHA256-hash of the DER encoded cert. What am I doing wrong?

use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName, DnType};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Write;
use std::process::Command;

fn main() -> std::io::Result<()> {
    // Create a certificate with rcgen
    let mut params: CertificateParams = Default::default();
    params.not_before = date_time_ymd(2022, 6, 1);
    params.not_after = date_time_ymd(2023, 6, 1);
    params.distinguished_name = DistinguishedName::new();
    params.distinguished_name
        .push(DnType::CommonName, "my_common_name");
    let cert = Certificate::from_params(params).unwrap();
    let key_pem = cert.serialize_pem().unwrap();

    // Write the pem certificate to a file
    let mut f = File::create("/tmp/cert.crt")?;
    f.write_all(key_pem.as_bytes()).unwrap();

    // openssl x509 -in /tmp/cert.crt -noout -fingerprint -sha256
    let openssl_fingerprint = Command::new("openssl")
        .args(["x509", "-in", "/tmp/cert.crt", "-noout", "-fingerprint", "-sha256",
        ]).output()?;
    println!(
        "OpenSSL SHA256: {}",
        String::from_utf8(openssl_fingerprint.stdout).unwrap()
    );

    // Create fingerprint from der certificate:
    let cert_der = cert.serialize_der().unwrap();
    let mut hasher = Sha256::new();
    hasher.update(cert_der);
    let res = hasher.finalize();
    println!("SHA256: {:x?}", res);

    Ok(())
}

Cargo.toml with the following dependencies:

[package]
name = "cert-fingerprint-test"
version = "0.1.0"
edition = "2021"
[dependencies]
rcgen="0.9"
sha2="0.10.2"

Example output of the above code:

OpenSSL SHA256: SHA256 Fingerprint=D4:61:B6:2E:50:43:AB:B1:7C:43:EF:23:9A:48:BA:BF:CF:2E:99:5A:C4:F4:77:65:51:C4:7A:F5:A5:70:6C:9E

SHA256: [eb, ab, 31, 9, 9a, 47, bb, d8, 50, a5, 9c, 46, 90, ba, ba, 71, b9, 79, 91, 93, 1b, 83, 2f, d1, 18, 88, ef, 1b, 5f, 4b, e4, a4]

(The format differs of course, but I would expect the hex values to be the same)


Solution

  • It turns out that this is a bug/design limitation in rcgen. Every time serialize_der()/serialize_pem() is called, a new certificate is created, though this is not mentioned in the documentation. See https://github.com/est31/rcgen/issues/62.

    It is possible to use the pem-crate to parse the public key in pem format an convert it to der like so:

    let pem_serialized = cert.serialize_pem()?;
    let der_serialized = pem::parse(&pem_serialized).unwrap().contents;
    let hash = ring::digest::digest(&ring::digest::SHA256, &der_serialized);