How to extract the curve_name from X509 ECC cert

Greetings Rustaceans, sending peace.

I am a beginner at Rust Programming. I am coming from C with a some crypto experience. Rust is a very impressive language, loving the journey so far. Here is my question.

**How do I extract the ECC curve-name from a X509 ECC certificate using the OpenSSL Crate? **

I have uploaded the example code (below) that I have written to pull out all that can from a PEM encoded cert. What I am after is a way to get the strings:

ASN1 OID: secp384r1
NIST CURVE: P-384

You can see these when you decode the cert using OpenSSL's x509 command: openssl x509 -in AffirmTrust_Premium_ECC.crt -inform PEM -noout -text

Based on the doc for the OpenSSL crate rust-openssl, there is indeed a function called curve_name within the EcGroupRef trait, but I am not sure how to make the connection. I was thinking that subject_key_id in the main.rs example (below) that I have uploaded is the OID that is needed, and all I have to do is convert it to a string? Anyhow, any help/pointers will be welcomed.

/Sincerely
Niah

use std::{
    include_bytes,
    io::Write
};

use openssl::{
    x509::X509,
    hash::MessageDigest
};

use hex::{self};

fn main() {
    // let file_data = std::fs::read(file)?;
    let file_data = include_bytes!("AffirmTrust_Premium_ECC.crt");
    let csv_data_cert_string = parse_cert_with_openssl(file_data).expect("Problem Parsing Cert");

    println!("\ncsv_data_cert_string:\n {}", csv_data_cert_string);

}

/*
This cert file used in this example is taken from
/usr/share/ca-certificates/mozilla/AffirmTrust_Premium_ECC.crt .  I
just copied in from that folder into this current dir.  So just in
case you want to run this example and do not have it, I have inlcude
it here
...
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
-----END CERTIFICATE-----
...

*/

fn parse_cert_with_openssl(cert_data: &[u8]) -> Result<String, std::io::Error> {

    let x509 = X509::from_pem(&cert_data)?;
    let version = x509.version().to_string();
    let subject = x509.subject_name();
    let subject_key_id = x509.subject_key_id().unwrap();
    let issuer = x509.issuer_name();
    let signature_algorithm = x509.signature_algorithm().object().to_string();
    let not_before = x509.not_before().to_string();
    let not_after =  x509.not_after().to_string();
    let pubkey = x509.public_key()?;
    let pubkey_len = pubkey.bits();
    let digest = x509.digest(MessageDigest::sha256())?;
    
    println!("Complete X509 decoded cert: {:?}", x509);
    println!("X509 version: {}", version);
    println!("X509 Public Key: {:?}", pubkey);
    println!("X509 Public Key Length: {}", pubkey_len);
    println!("X509 signature_algorithm: {:#?}", signature_algorithm);
    println!("X509 Subject Key ID: {:?}", subject_key_id.as_slice());
    println!("X509 Subject: {:?}", subject);
    println!("X509 Issuer: {:?}", issuer);
    println!("X509 validity: {} to {}", not_before, not_after);
    // How do I print bytes to hex without needing the hex crate?
    // let digest_string: String = format!("{:x}", digest.into());
    println!("X509 Digest: {:#?}", hex::encode(digest));
    let mut w = Vec::new();
    write!(&mut w, "{},", hex::encode(digest))?;
    write!(&mut w, "{},", version)?;
    write!(&mut w, "{},", pubkey_len)?;
    write!(&mut w, "{:?},", signature_algorithm)?;
    write!(&mut w, "{:?},", subject)?;
    write!(&mut w, "{:?},", issuer)?;
    write!(&mut w, "{} to {}", not_before, not_after)?;

    let csv_data_cert_string = String::from_utf8(w).unwrap();
    // println!("\nReturning from parse_cert() csv_data_cert_string:\n {}", csv_data_cert_string);

    Ok(csv_data_cert_string)
}


Try this:

let key = EcKey::<Public>::try_from(pubkey)?;
println!("{}", key.group().curve_name().unwrap().long_name()?);

The NIST curve name seems to be obtained from openssl's EC_curve_nid2nist() function but it doesn't look like rust-openssl is using it. You can call it directly using ffi:


use std::ffi::{c_int, c_char, CStr};
extern "C" {
    fn EC_curve_nid2nist(nid: c_int) -> *const c_char;
}

fn curve_nist_name(curve: Nid) -> Option<&'static str> {
    unsafe {
        let ptr = EC_curve_nid2nist(curve.as_raw());
        if !ptr.is_null() {
            Some(std::str::from_utf8_unchecked(CStr::from_ptr(ptr).to_bytes()))
        } else {
            None
        }
    }
}

//...
println!("{}", curve_nist_name(key.group().curve_name().unwrap()).unwrap());

Thanks for that, I will try as suggested and get back to you. I have been wondering how to call the OpenSSL APIs directly, so you have given much with this reply.

No problem. check out the FFI guide if you want to call more complex APIs.

Wow, It works, you are a genius at this! Thank you!

Code changes and output

    let pubkey = x509.public_key()?;
    let key = EcKey::<Public>::try_from(pubkey.clone())?;
    println!("curve_name: {}", curve_nist_name(key.group().curve_name().unwrap()).unwrap());
...
$ ./target/debug/parse-cert-example 
curve_name: P-384

If you have time to for these followup questions, I would love that.

  1. What is the difference between the 2 pubkey in the following. That is: is 2nd one merely a sort of type casting?:
    let pubkey = x509.public_key()?; // it seems this is more of key type than the actual key
    and
    let key = EcKey::<Public>::try_from(pubkey.clone())?;

  2. How do I also get the ASN1 OID: secp384r1 ? It this related to the subject_key_id?

  3. let digest_hex = format!("{:x}", digest)); // trying to convert byes to hex, but too many compiler errors, ended up using the hex crate instead.

  1. Check openssl's docs, PKey is a generic asymmetric key while EcKey is specifically an elliptic curve key
  2. That's the long_name() from my first example.
  3. {:x?} works but for proper hex formatting use a crate

Now it all starting to make sense, thanks again.

One Love!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.