Modifying extension list in X509 certificate using OpenSSL in C

Viewed 277

I am tring to insert signed sct into a precertificate that has a poison extension. So I first remove the poison extension, then add the SCT. This is what I've done:

int main(int argc, char **argv) {
    size_t lenCert = 0, lenCert2 = 0;
    char *filePEM  = "testpem/precert3.cert.pem";
    char *strCertPem = loadFileContent(filePEM, &lenCert);
    const X509 *cert = parse_certificate(strCertPem);
    X509 *certRef = X509_dup(cert);

    if(!cert || cert==NULL){
        printf("Failed parsing\n");
        return -1;
    }

    int len_init = -1;
    unsigned char *buf_init = NULL;
    len_init = i2d_X509(certRef, &buf_init);

    if(len_init < 0){
        printf("INIT: failed conversion to DER\n");
        return -1;
    } else {
        printf("INIT: Successful conversion to DER[%d]\n", len_init);
    }


    printf("size certificate: %ld\n", lenCert);
    X509_EXTENSION *tmpext;
    const STACK_OF(X509_EXTENSION) *allExt = X509_get0_extensions(cert);
    const STACK_OF(X509_EXTENSION) *allExt2 = X509_get0_extensions(certRef);
    int my_idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
    int idx = my_idx;
    int cc = X509_get_ext_count(cert);
    printf("Extension count in cert BEFORE = %d\n", cc);

    printf((allExt==NULL) ? "Extensions extraction FAILED\n" : "Extensions extraction SUCCESS\n");

    int counter = X509v3_get_ext_count(allExt);
    printf("Extension[%d] count BEFORE = %d\n", idx, counter);

    do {
        tmpext = X509v3_get_ext(allExt, idx);
        X509v3_delete_ext(allExt, idx);
        X509_EXTENSION_free(tmpext);
        idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
        printf("pass\n");
    } while (idx != -1);

    counter = X509v3_get_ext_count(allExt);
    printf("Extension count AFTER = %d\n", counter);

    if(X509_cmp( cert, certRef)){
        printf("Certificate modified\n\n");
    } else {
        printf("FAILED!!! \n");
    }

    cc = X509_get_ext_count(cert);
    printf("Extension count in cert AFTER = %d\n", cc);

    int len_inter;
    unsigned char *buf_inter = NULL;
    len_inter = i2d_X509(cert, &buf_inter);
    if(len_inter < 0){
        printf("INTERMEDIATE: failed conversion to DER\n");
        return -1;
    } else {
        printf("INTERMEDIATE: Successful conversion to DER[%d]\n", len_inter);
    }

    unsigned char *dersct;
    size_t lenSCTList = 0;
    char *b64SCTList = "BIF6AHgAdgCwzIPlpfl9a698CcwoSQSHKsfoixMsY1C3xv0m4WxsdwAAAWZ7z/DQAAAEAwBHMEUCIQDKJPPQhWqje1rQq+T06x0iNlLT7rX71k23VPZkhm/QCwIgfhwNK7izeq0fHAlu7HuYRjmvym51RRdlNWhd50LQdu4=";
    int b64Res = Base64Decode(b64SCTList, &dersct, &lenSCTList); //Decodes a base64 encoded string
    printf("size final SCT List: %ld\n", lenSCTList);

    STACK_OF(SCT) * scts = d2i_SCT_LIST(NULL, (const unsigned char **) &dersct, lenSCTList);
    if(scts==NULL){
        printf("Could not convert SCT List!");
        return -1;
    } 
    printf("SCT List converted !\n");
    ASN1_OCTET_STRING   *val   = ASN1_OCTET_STRING_new();
    ASN1_OCTET_STRING_set(val, dersct, (int)lenSCTList);
    X509_EXTENSION* extSCT = X509_EXTENSION_create_by_NID(NULL, NID_ct_precert_scts, 0, val);

    if(extSCT){
        printf("created extension\n");
    } else {
        printf("Failed to create extension\n");
        return -1;
    }

    if( X509_add_ext(cert, extSCT, -1)) {
        printf("Extension added\n");
       //  X509_EXTENSION_free(extSCT);

    } else {
        printf("failed to add extension\n");
        return -1;
    }
    int len_final;
    unsigned char *buf_final = NULL;
    len_final = i2d_X509(cert, &buf_final);
    if(len_final < 0){
        printf("FINAL: failed conversion to DER\n");
        return -1;
    } else {
        printf("FINAL: Successful conversion to DER[%d]\n", len_final);
    }

    BIO *Cout = BIO_new(BIO_s_mem());
    PEM_write_bio_X509(Cout, cert);
    char* data;
    const long len = BIO_get_mem_data(Cout, &data);
    
    cc = X509_get_ext_count(cert);
    printf("Extension count in cert AFTER = %d\n", cc);
    printf("\ndata[%ld]: \n%s\n\n", len, data);
    BIO_free_all(Cout);



    int my_idx2 = X509_get_ext_by_NID(cert, NID_ct_precert_poison, -1);
    X509_EXTENSION* extPoison2 = X509_get_ext(cert, my_idx2);

    if(!extPoison2){
        printf("failed last extension[%d] extract \n ", my_idx2);
        return -1;
    } else {
        printf("Succeeded last extension extract[%d]\n ", my_idx2);
    }


    return 0;
}

This code seems to work, all step are fine, problem is, the final certificate displayed, when I save it to a file and run the command:

openssl x509 -in cert.pem -noout -text

it is the same as the original precertificate, it contains the poison extension and no SCT. Even comparing both files, they are identical. Where did I go wrong?

1 Answers

Your main problem is that for X509 X509_CRL X509_REQ when created by parsing input i.e. not built up from scratch OpenSSL saves the tbs encoding and reuses it on output (and digesting and comparison, which is why your X509_cmp failed) even if you have changed some of the fields that go in that encoding, unless you sign the change(s) which you must to make the resulting object valid anyway. In short, you need to call X509_sign() or the extended form X509_sign_ctx() after making your changes.

After fixing that you have another problem: it does remove the poison ext and add an SCT ext -- which contains garbage, because your unnecessary call to d2i_SCT_LIST has changed the pointer you use. Removing that, plus your other unneeded cruft, produces the following code that works (with a precert and key of my own) to produce a correct-looking cert, though of course the SCTs you provided aren't valid for it:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/asn1.h>
#include <openssl/ct.h>
#include <openssl/pem.h>
#include <openssl/x509.h>

int main(int argc, char **argv) {
    // size_t lenCert = 0, lenCert2 = 0;
    // char *filePEM  = "testpem/precert3.cert.pem";
    // replace mystery routines by one PEM:
    FILE *infile = fopen(argv[1],"r"); if(!infile) exit(1);
    X509 *cert = PEM_read_X509 (infile, NULL, NULL, NULL);
    fclose(infile);
    X509 *certRef = X509_dup(cert);

    if(!cert || cert==NULL){ // redundant, and too late
        printf("Failed parsing\n");
        return -1;
    }

#if 0 // useless
    unsigned char *buf_init = NULL;
    int len_init = i2d_X509(certRef, &buf_init);
#endif

    //--printf("size certificate: %ld\n", lenCert);
    //--X509_EXTENSION *tmpext;
    const STACK_OF(X509_EXTENSION) *allExt = X509_get0_extensions(cert);
    const STACK_OF(X509_EXTENSION) *allExt2 = X509_get0_extensions(certRef);
    int my_idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
    int idx = my_idx;
#if 0
    int cc = X509_get_ext_count(cert);
    printf("Extension count in cert BEFORE = %d\n", cc);

    //--printf((allExt==NULL) ? "Extensions extraction FAILED\n" : "Extensions extraction SUCCESS\n");
#endif

#if 0 // useless 
    int counter = X509v3_get_ext_count(allExt);
    printf("Extension[%d] count BEFORE = %d\n", idx, counter);
#endif

#if 0
    do { 
        X509_EXTENSION * tmpext = X509v3_get_ext(allExt, idx);
#endif
        X509v3_delete_ext(allExt, idx);
#if 0
        X509_EXTENSION_free(tmpext);
        idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
        printf("pass\n");
    } while (idx != -1);
    printf("Extension count AFTER = %d\n", X509v3_get_ext_count(allExt));
#endif

#if 0
    if(X509_cmp( cert, certRef)){
        printf("Certificate modified\n\n");
    } else {
        printf("CENSORED \n");
    }
    printf("Extension count in cert AFTER = %d\n", X509_get_ext_count(cert));
#endif

#if 0 // useless
    unsigned char *buf_inter = NULL;
    int len_inter = i2d_X509(cert, &buf_inter);
#endif

    unsigned char *dersct;
    size_t lenSCTList = 0;
    char *b64SCTList = "BIF6AHgAdgCwzIPlpfl9a698CcwoSQSHKsfoixMsY1C3xv0m4WxsdwAAAWZ7z/DQAAAEAwBHMEUCIQDKJPPQhWqje1rQq+T06x0iNlLT7rX71k23VPZkhm/QCwIgfhwNK7izeq0fHAlu7HuYRjmvym51RRdlNWhd50LQdu4=";
    // replace mystery routine
    dersct = malloc(strlen(b64SCTList)); // more than needed but convenient
    lenSCTList = EVP_DecodeBlock(dersct, (unsigned char*)b64SCTList, strlen(b64SCTList));
    printf("size final SCT List: %ld\n", lenSCTList);

#if 0 // useless and harmful
    STACK_OF(SCT) * scts = d2i_SCT_LIST(NULL, (const unsigned char **) &dersct, lenSCTList);
    if(scts==NULL){
        printf("Could not convert SCT List!");
        return -1;
    } 
    printf("SCT List converted !\n");
#endif
    ASN1_OCTET_STRING   *val   = ASN1_OCTET_STRING_new();
    ASN1_OCTET_STRING_set(val, dersct, (int)lenSCTList);
    free(dersct); // added
    X509_EXTENSION* extSCT = X509_EXTENSION_create_by_NID(NULL, NID_ct_precert_scts, 0, val);

#if 0
    if(extSCT){
        printf("created extension\n");
    } else {
        printf("Failed to create extension\n");
        return -1;
    }
#endif

    if( X509_add_ext(cert, extSCT, -1)) {
        printf("Extension added\n");
       //  X509_EXTENSION_free(extSCT);

    } else {
        printf("failed to add extension\n");
        return -1;
    }
#if 0 // useless
    unsigned char *buf_final = NULL;
    int len_final = i2d_X509(cert, &buf_final);
#endif

    // added
    FILE * keyfile = fopen(argv[2],"r"); if(!keyfile) exit(2);
    EVP_PKEY * signkey = PEM_read_PrivateKey (keyfile, NULL, NULL, NULL);
    fclose(keyfile);
    if( X509_sign(cert,signkey,EVP_sha256())<=0 ) exit(9);

    BIO *Cout = BIO_new(BIO_s_mem());
    PEM_write_bio_X509(Cout, cert);
    char* data;
    const long len = BIO_get_mem_data(Cout, &data);
    
    printf("Extension count in cert AFTER = %d\n", X509_get_ext_count(cert));
    printf("\ndata[%ld]: \n%s\n\n", len, data);
    // added
    FILE *outfile = fopen(argv[3],"w"); if(!outfile) exit(3);
    fwrite(data,1,len,outfile); fclose(outfile);
    BIO_free_all(Cout);

#if 0 // useless
    int my_idx2 = X509_get_ext_by_NID(cert, NID_ct_precert_poison, -1);
    X509_EXTENSION* extPoison2 = X509_get_ext(cert, my_idx2);

    if(!extPoison2){
        printf("failed last extension[%d] extract \n ", my_idx2);
        return -1;
    } else {
        printf("Succeeded last extension extract[%d]\n ", my_idx2);
    }
#endif

    return 0;
}

However, modifying a value returned by get0 -- and 'discarding' the const on it -- is not good style, and might fail in some future implementation. It would be safer and also simpler to use X509_get_ext_by_NID and X509_delete_ext directly on cert.

Related