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)
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);