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.

Tuesday, January 06, 2009

Viewing extensions in X.509 certificates


In the last post we saw how to create certificates with custom extensions - as a second step let us see how we can access these extensions and make sense of them. The code below opens a certificate, counts the number of extensions in it and iterates over every extension and prints a representation of the extension understandable to, us, humans.
X509V3_add_standard_extensions();

inf = fopen("mycrt.crt", "r");
cert = (X509*) PEM_read_X509(inf, NULL, NULL)
count = X509_get_ext_count(cert);

for(i = 0; i < count; i++) {
        ext = X509_get_ext(cert, i);

        printf("%s\n", OBJ_nid2ln(OBJ_obj2nid(ext->object)));
        if(!X509V3_EXT_print_fp(stdout, ext, 0, 0)) {
                ERR_print_errors_fp(stderr);
        }
        printf("\n");
        X509_EXTENSION_free(ext);
}


It doesn't print the human representation of all the extensions found but only for the built in extensions, because the library doesn't yet know to represent the custom extensions that we have placed in the certificate.

More about parsing custom extensions and making sense of the values in it in a later post.

Creating X.509 certificates with custom extensions


Every time when I need to do something with OpenSSL, it involves searching lot of places including the library code itself to achieve my target - This time the requirement is to generate a X509 v3 certificate which contains non-standard/custom extensions. Luckily I found convincing documents early enough.

Here the task is to generate a CA certificate with standard extensions and then to create another certificate containing custom extensions and sign it with the newly created CA. The code below achieves both of the tasks.

1. To generate the ca certificate, run the script as ./genkey.sh myca ==> which generates myca.key and myca.crt
2. To generate the authentication certificate, run the script as ./genkey.sh myca mycrt ==> which generates mycrt.key and mycrt.crt and signs it with myca.key

The script is mostly self explanatory and contains an inline openssl config file - The main points to note are:
1. Extension are to be placed separately in a named section
2. Custom extension are of the form oid=DER:<DER-Encode-Hex-Values>
3. openssl command x509 should be given the extension file name and section name

#! /bin/sh
# Filename: genkey.sh

if [ $# -lt 1 ]; then
        echo "Usage: $0 ca-name [new-cert]"
        exit -1
fi


ca="$1"
new=${ca}

if [ $# -ge 2 ]; then
        new="$2"
fi

if [ -f ${new}.key ]; then
        echo "${new} already exists: Delete ${new}.key & ${new}.crt to proceed"
        exit -1
fi


#----[ inline config file ]--------------------------------------------

ca_extensions="ca_extensions"
cert_extensions="cert_extensions"

config=.${new}.config
cat > ${config} << EOF
[ req ]
default_bits           = 2048
default_keyfile        = ${new}.key
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no

[ req_distinguished_name ]
CN                     = ${new}
OU                     = ouTest
O                      = oTest
C                      = IN

[ req_attributes ]

[ ${ca_extensions} ]
basicConstraints=critical,CA:true
subjectKeyIdentifier=hash
keyUsage=keyCertSign,cRLSign
authorityInfoAccess=OCSP;URI:http://ocsp.test.com:8080/

[ ${cert_extensions} ]
subjectKeyIdentifier=hash
keyUsage=critical,digitalSignature,keyEncipherment
certificatePolicies=1.2.276.0.76.4.64
crlDistributionPoints=URI:ldap://ocsp.test.com:389/cn=test Komponenten Testreferenz CA01
authorityInfoAccess=OCSP;URI:http://ocsp.test.com:8080/CMOCSP/OCSP
authorityKeyIdentifier=keyid
1.3.36.8.3.3=DER:\
304DA421301F310B30090603550406130244453110300E060355\
040A130767656D6174696B302830263024302230150C13416E77\
656E64756E67736B6F6E6E656B746F72300906072A8214004C0477
extendedKeyUsage=clientAuth,serverAuth

EOF


#----[ Generate key and sign ]------------------------------------------


# 1. Generate Key & CSR
openssl req -new -nodes -config ${config} > ${new}.csr


# 2. Self sign (or) Sign with CA
if [ ${new} == ${ca} ]; then
        echo "Generating self signed CA: ${ca}.crt"
        openssl x509 -extfile ${config} -extensions ${ca_extensions} \
                -sha1 -req -signkey ${ca}.key < ${new}.csr > ${new}.crt
else
        echo "Signing ${new}.crt with ${ca}.crt"
        openssl x509 -extfile ${config} -extensions ${cert_extensions} \
                -sha1 -req -CAkey ${ca}.key -CA ${ca}.crt < ${new}.csr > ${new}.crt
fi

rm ${new}.csr
rm ${config}

Saturday, January 03, 2009

Resolutions for the year and after

1. To overcome my laziness.
2. To be more focussed and ignore distraction.

Top two things needed to shape myself in to a better person.
Hopefully I will be able to overpower laziness and distraction.