Search code examples
opensslcertificateself-signed

Why OpenSSL says the certificate is trusted when it is issued by self-signed untrusted certificate?


For a test I have 3 certificates: one trusted self-signed root certificate, one untrusted self-signed certificate and one certificate issued by the untrusted self-signed certificate (so should not be trusted as well). However, if I validate the last certificate using OpenSSL (1.1.1d), it says it's valid. I was expecting that the validation will fail, since the root it is traced to is not trusted. What am I doing wrong?

Here is the code snippet (don't mind the leaks or missing checks, it is intended only for this demonstration):

const char* const rootPem =
"-----BEGIN CERTIFICATE-----\n"
"MIIB3jCCAYSgAwIBAgIJAPHf5UibUrNZMAoGCCqGSM49BAMCMEIxCzAJBgNVBAYT\n"
"AkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQxDTALBgNV\n"
"BAMMBFJPT1QwHhcNMjAwMjE5MTc1NDE5WhcNMjEwMjE4MTc1NDE5WjBCMQswCQYD\n"
"VQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDESMBAGA1UECgwJQWxpY2UgTHRkMQ0w\n"
"CwYDVQQDDARST09UMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErwct49VH1jQZ\n"
"kA8bZsENcn5kRjVabz9ZjewKXO1QmjE3Uqz9GjBaaH+H+OsZmVST+NQjX4XF6g/K\n"
"o1BmXoqs+qNjMGEwHQYDVR0OBBYEFO91oq/ncaT3AiZC3q0jHmptro3LMB8GA1Ud\n"
"IwQYMBaAFO91oq/ncaT3AiZC3q0jHmptro3LMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n"
"VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gAMEUCIDy3A4BP27GviTbXBtDrxvQD\n"
"0y0KwkjawXTb13euL9YGAiEA583y9AAUagppAejYTDIsurdFdRulUqVzPy6H5JCq\n"
"yb8=\n"
"-----END CERTIFICATE-----\n";

const char* const fakeRootPem =
"-----BEGIN CERTIFICATE-----\n"
"MIIB4zCCAYqgAwIBAgIJAI9ickj5XUpqMAoGCCqGSM49BAMCMEUxCzAJBgNVBAYT\n"
"AkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQxEDAOBgNV\n"
"BAMMB0ZBS0VfQ0EwHhcNMjAwMjI3MTAyNjQ2WhcNMjEwMjI2MTAyNjQ2WjBFMQsw\n"
"CQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDESMBAGA1UECgwJQWxpY2UgTHRk\n"
"MRAwDgYDVQQDDAdGQUtFX0NBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMD93\n"
"rwESh/AZLTrvWStRAujiHePnID2zuYnXSkOhh0KiIs+tOyQmQXv16gwYvRF1X0v7\n"
"Q6cWK4pOe1No5U5tlaNjMGEwHQYDVR0OBBYEFGfD4rQgCqDvO1U9Tvg/qVHrIaTn\n"
"MB8GA1UdIwQYMBaAFGfD4rQgCqDvO1U9Tvg/qVHrIaTnMA8GA1UdEwEB/wQFMAMB\n"
"Af8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0cAMEQCIE+cGjmuzPvPE2Kh\n"
"uyP7mH2k7CBu99nQ9P0X+ecd5uynAiAa2g1+uweZ/lLbrH32dfnvNlfa29uTqVjX\n"
"SEXodEo78Q==\n"
"-----END CERTIFICATE-----\n";

const char* const fakeLeafPem =
"-----BEGIN CERTIFICATE-----\n"
"MIIB6TCCAZCgAwIBAgIRAOzOwVz92cGF99cokc68Gy8wCgYIKoZIzj0EAwIwQjEL\n"
"MAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0\n"
"ZDENMAsGA1UEAwwEUk9PVDAeFw0yMDAyMjcxMDI5MDRaFw0yMTAzMDgxMDI5MDRa\n"
"MEYxCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlBbGlj\n"
"ZSBMdGQxETAPBgNVBAMMCEZBS0VfQ0ExMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\n"
"QgAEOAAD+GG/lNYAYcakWcp3CwT2gMoHH4UZyyzLsr1Ge9QAkEU8eGrdJCwMNA6O\n"
"PVc7+UYPyd8k6opxODOphNKwV6NjMGEwHQYDVR0OBBYEFF+aTSzJFHSFg1r1pNlE\n"
"+1Z6Edi1MB8GA1UdIwQYMBaAFO91oq/ncaT3AiZC3q0jHmptro3LMA8GA1UdEwEB\n"
"/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0cAMEQCIE7IyY+M\n"
"tXdik8VLsyVJpdgV02VcUpDWECtQ5zBFaqfKAiApHJoxdwGtfyN2gkhcV8uQcjjW\n"
"ccI6BV7gTn/3F8UIsg==\n"
"-----END CERTIFICATE-----\n";

// trusted
BIO* bio = BIO_new(BIO_s_mem());
BIO_write(bio, rootPem, strlen(rootPem));
X509* trusted_root = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
X509_STORE* store = X509_STORE_new();
X509_STORE_add_cert(store, trusted_root);

// untrusted root
bio = BIO_new(BIO_s_mem());
BIO_write(bio, fakeRootPem, strlen(fakeRootPem));
X509* untrusted_root = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
STACK_OF(X509)* untrusted_chain = sk_X509_new_null();
sk_X509_push(untrusted_chain, untrusted_root);

// validation of untrusted cert
bio = BIO_new(BIO_s_mem());
BIO_write(bio, fakeLeafPem, strlen(fakeLeafPem));
X509* untrusted_cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
X509_STORE_CTX* ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(ctx, store, untrusted_cert, untrusted_chain);
int verified = X509_verify_cert(ctx);

printf("Certificate is %s\n", (verified == 1) ? " VALID" : "NOT VALID");

I've tried also passing the untrusted chain using X509_STORE_CTX_set0_untrusted but got the same result.


Solution

  • It looks like you are not testing what you think you are testing.

    Adding the following to the end of your snippet to get some more information about your certificates

    #define PRINT_NAMES(cert) \
        printf("\n" #cert " subject name: "); \
        X509_NAME_print_ex_fp(stdout, X509_get_subject_name(cert), 0, 0); \
        printf("\n" #cert " issuer name: "); \
        X509_NAME_print_ex_fp(stdout, X509_get_issuer_name(cert), 0, 0); \
        printf("\n");
    
    PRINT_NAMES(trusted_root);
    PRINT_NAMES(untrusted_root);
    PRINT_NAMES(untrusted_cert);
    

    results in

    trusted_root subject name: C=GB, ST=England, O=Alice Ltd, CN=ROOT
    trusted_root issuer name: C=GB, ST=England, O=Alice Ltd, CN=ROOT
    
    untrusted_root subject name: C=GB, ST=England, O=Alice Ltd, CN=FAKE_CA
    untrusted_root issuer name: C=GB, ST=England, O=Alice Ltd, CN=FAKE_CA
    
    untrusted_cert subject name: C=GB, ST=England, O=Alice Ltd, CN=FAKE_CA1
    untrusted_cert issuer name: C=GB, ST=England, O=Alice Ltd, CN=ROOT
    

    So untrusted_cert was actually issued by trusted_root, which explains the result.

    To quickly test what you were intending to test, you could flip the names of the two string literals, so define the first one as fakeRootPem and the second one as rootPem. That will make ROOT untrusted and FAKE_CA trusted in your code, and consequently produce the desired/expected output.