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.
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.