sfackler / rust-openssl

OpenSSL bindings for Rust
1.4k stars 749 forks source link

Parsing extensions as othername #2264

Open sethcall opened 3 months ago

sethcall commented 3 months ago

I'm using the rust-openssl crate to parse a X509 certificate. I'm trying to get at a Subject Alternate Name field on that cert.

For the example cert snippet (from openssl x509 -text)

...
 X509v3 Subject Alternative Name:
                othername: UPN::myuser@somedomain.com
...

Take the following example code:

use std::fmt::Pointer;
use openssl::x509::{GeneralName, X509};
fn main() {
    static SOME_PEM: &str = "spikes/x509_parser/cert.pem";
    let data = std::fs::read(SOME_PEM).expect("Could not read file");
    let cert = X509::from_pem(data.as_slice()).expect("Could not load cert");

    let sans = cert.subject_alt_names().unwrap();

    println("SAN count: {}", b.len());

    for entry in  &b {
        # all of the below (unsurprisingly) result in None
        # entry.ipaddress() 
        # entry.email() 
        # entry.dnsname() 
    }
 }

There is no entry.othername(); as I've dug into the code base, I've started to understand why. In openssl-sys/src/x509v3.rs, you can see:

#[repr(C)]
pub struct GENERAL_NAME {
    pub type_: c_int,
    // FIXME should be a union
    pub d: *mut c_void,
}

So I find the definition of GENERAL_NAME in C, and it's like this:

...
typedef struct otherName_st {
    ASN1_OBJECT *type_id;
    ASN1_TYPE *value;
} OTHERNAME;
...
typedef struct GENERAL_NAME_st {
# define GEN_OTHERNAME   0
# define GEN_EMAIL       1
# define GEN_DNS         2
# define GEN_X400        3
# define GEN_DIRNAME     4
# define GEN_EDIPARTY    5
# define GEN_URI         6
# define GEN_IPADD       7
# define GEN_RID         8
    int type;
    union {
        char *ptr;
        OTHERNAME *otherName;   /* otherName */
        ASN1_IA5STRING *rfc822Name;
        ASN1_IA5STRING *dNSName;
        ASN1_TYPE *x400Address;
        X509_NAME *directoryName;
        EDIPARTYNAME *ediPartyName;
        ASN1_IA5STRING *uniformResourceIdentifier;
        ASN1_OCTET_STRING *iPAddress;
        ASN1_OBJECT *registeredID;
        /* Old names */
        ASN1_OCTET_STRING *ip;  /* iPAddress */
        X509_NAME *dirn;        /* dirn */
        ASN1_IA5STRING *ia5;    /* rfc822Name, dNSName,
                                 * uniformResourceIdentifier */
        ASN1_OBJECT *rid;       /* registeredID */
        ASN1_TYPE *other;       /* x400Address */
    } d;
} GENERAL_NAME;

Right, so I see now why there are only helpers for dnsname, ipaddress, etc... these are easy to parse as a single object; they are not nested / custom objects. On the other hand, OTHERNAME is 'custom', so I don't think you can wrap this. Instead, I think we have to just provide a the ASNI1_OBJECT type_id (oid), and the ASN1_TYPE as a byte array ([u8]).

Anyway, I was interested in doing this, but I had some immediate questions, like, where does src\x509v3.rs come from... is this created by hand?

Ultimately, if this is done correctly,. I think someone can parse OTHERNAME like in this code example; i.e., give the user the raw data; they still have to parse on their own outside of this library. https://stackoverflow.com/a/25049371

Will5 commented 1 month ago

I am looking for a similar solution as well