Thursday, January 08, 2009

Parsing and using custom extension in X.509 certificates


In the last two posts we saw how to create certificates with custom extensions and how to view extension in X.509 certificates, now it's time that we use them for some real purpose. The main purpose of placing custom extension is to express certain capabilities of the certificate holder. The receiving systems verifies the capabilities of the holder based on the presence of these extensions and the corresponding values in the extensions.

In the current example we will see the parsing of a non-standard extension called Admission with oid: 1.3.36.8.3.3 which is defined in the ISIS-MTT document

The ASN.1 description of the extension is given below
AdmissionSyntax ::= SEQUENCE {
    admissionAuthority GeneralName OPTIONAL,
    contentsOfAdmissions SEQUENCE OF Admissions }

Admissions ::= SEQUENCE {
    admissionAuthority [0] EXPLICIT GeneralName OPTIONAL,
    namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL,
    professionInfos SEQUENCE OF ProfessionInfo }

NamingAuthority ::= SEQUENCE {
    namingAuthorityId OBJECT IDENTIFIER OPTIONAL,
    namingAuthorityUrlQUENCE OF OBJECT IDENTIFIER OPTIONAL,
    registrationNumber PrintableString (SIZE(1..128)) OPTIONAL,
    addProfessionInfo OCTET STRING OPTIONAL }

ProfessionInfo ::= SEQUENCE {
    namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
    professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
    professionOIDS SEQUENCE OF OBJECT IDENTIFIER,
    registrationNumber PrintableString (SIZE(1..128)) OPTIONAL,
    addProfessionInfo OCTET STRING OPTIONAL }


If we build a certificate with this extension and try to display the contents using the method described in the previous post, we will not be able to see the particulars of this extension nor will we be able to query any of the fields from this extension. This is because OpenSSL doesn't  yet know to parse the contents of the extension. To help OpenSSL in parsing the data we have to define the structure of the extension. Which is done as follows.

First, the needed data structures are defined:
typedef struct NamingAuthority_st {
        ASN1_OBJECT* namingAuthorityId;
        ASN1_IA5STRING* namingAuthorityUrl;
        ASN1_STRING* namingAuthorityText;
} NAMING_AUTHORITY;
DECLARE_ASN1_ITEM(NAMING_AUTHORITY)

typedef struct ProfessionInfo_st {
        NAMING_AUTHORITY* namingAuthority;
        STACK_OF(DIRECTORYSTRING)* professionItems;
        STACK_OF(ASN1_OBJECT)* professionOIDs;
        ASN1_PRINTABLESTRING* registrationNumber;
        ASN1_OCTET_STRING* addProfessionInfo;
} PROFESSION_INFO;
DECLARE_ASN1_ITEM(PROFESSION_INFO)

typedef struct Admissions_st {
        GENERAL_NAME* admissionAuthority;
        NAMING_AUTHORITY* namingAuthority;
        STACK_OF(PROFESSION_INFO)* professionInfos;
} ADMISSIONS;
DECLARE_ASN1_ITEM(ADMISSIONS)

typedef struct AdmissionSyntax_st {
        GENERAL_NAME* admissionAuthority;
        STACK_OF(ADMISSIONS)* contentsOfAdmissions;
} ADMISSION_SYNTAX;
DECLARE_ASN1_ITEM(ADMISSION_SYNTAX)


and then the C translation of ASN.1 representation.
ASN1_SEQUENCE(NAMING_AUTHORITY) = {
        ASN1_OPT(NAMING_AUTHORITY, namingAuthorityId, ASN1_OBJECT),
        ASN1_OPT(NAMING_AUTHORITY, namingAuthorityUrl, ASN1_IA5STRING),
        ASN1_OPT(NAMING_AUTHORITY, namingAuthorityText, DIRECTORYSTRING),
} ASN1_SEQUENCE_END(NAMING_AUTHORITY)

ASN1_SEQUENCE(PROFESSION_INFO) = {
        ASN1_EXP_OPT(PROFESSION_INFO, namingAuthority, NAMING_AUTHORITY, 0),
        ASN1_SEQUENCE_OF(PROFESSION_INFO, professionItems, DIRECTORYSTRING),
        ASN1_SEQUENCE_OF(PROFESSION_INFO, professionOIDs, ASN1_OBJECT),
        ASN1_OPT(PROFESSION_INFO, registrationNumber, ASN1_PRINTABLESTRING),
        ASN1_OPT(PROFESSION_INFO, addProfessionInfo, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(PROFESSION_INFO)

ASN1_SEQUENCE(ADMISSIONS) = {
        ASN1_EXP_OPT(ADMISSIONS, admissionAuthority, GENERAL_NAME, 0),
        ASN1_EXP_OPT(ADMISSIONS, namingAuthority, NAMING_AUTHORITY, 1),
        ASN1_SEQUENCE_OF(ADMISSIONS, professionInfos, PROFESSION_INFO),
} ASN1_SEQUENCE_END(ADMISSIONS)

ASN1_SEQUENCE(ADMISSION_SYNTAX) = {
        ASN1_OPT(ADMISSION_SYNTAX, admissionAuthority, GENERAL_NAME),
        ASN1_SEQUENCE_OF(ADMISSION_SYNTAX, contentsOfAdmissions, ADMISSIONS),
} ASN1_SEQUENCE_END(ADMISSION_SYNTAX)


Once this is done we have to some how say about our new extension to openssl:
static X509V3_EXT_METHOD ext_admission = {
        .ext_nid = 0,
        .ext_flags = 0,
        .it = ASN1_ITEM_ref(ADMISSION_SYNTAX),
        .i2s = NULL,
        .s2i = NULL,
        .i2v = NULL,
        .v2i = NULL,
        .r2i = NULL,
        .i2r = i2r_AdmissionSyntax,
};

/*
 * Tell about our new extension to OpenSSL
 */
void x509_add_custom_extensions()
{
        ext_admission.ext_nid
                 = OBJ_create("1.3.36.8.3.3", "Admission", "Admission");
        X509V3_EXT_add(&ext_admission);
}


Now if we have an extension we can convert it to the corresponding data structure and then play with fields / verify them.
ADMISSION_SYNTAX* x = (ADMISSION_SYNTAX*) X509V3_EXT_d2i(ext);
sk_num(x->contentsOfAdmissions)
sk_value(x->contentsOfAdmissions, iAdmission);


i2r_AdmissionSyntax is a simple function (that you may write/or set to NULL) which converts the internal data structure to some human understandable form and is used when the function X509V3_EXT_print_fp is called.

2 comments:

Natanael said...

I guess you took this from the German health card specs, as you already mentioned that in an older post? I'd be interested in exchange regarding this topic (are you involved in the German health card project?), would be great, if you dropped me a line at nm [at] michael-wessel [dot] de!

Anonymous said...

Did not work. If i print out the ADMISSION_SYNTAX Object i got 0x0 which means to me that the data is not correctly parsed internal or i'm wrong? Or is the code you wrote outdated?

Could you provide our working example certificate?