/* * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include #include #include #include #include #include #include #ifndef OPENSSL_NO_OCSP #include #endif #include "testutil.h" #ifndef OPENSSL_NO_OCSP /* * Fixtures for the OCSP stapled-response verification path * (check_cert_ocsp_resp() in x509_vfy.c), reached from X509_verify_cert() * when X509_V_FLAG_OCSP_RESP_CHECK is set and responses are attached with * X509_STORE_CTX_set_ocsp_resp(). * * Root CA (self-signed trust anchor) * \-- leaf (signed by the Root CA) * * The flat chain makes the root both the trust anchor and the authorized OCSP * responder for the leaf, and having the root key lets each test build its own * signed responses at run time. The certificates have a long validity because * OCSP_check_validity() compares against the wall clock and cannot be pinned * via X509_VERIFY_PARAM_set_time(). * * The arrays below are generated by the test-tools ocsptest command. */ static const char *kOcspTestRoot[] = { "-----BEGIN CERTIFICATE-----\n", "MIID+DCCAuCgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBmjELMAkGA1UEBhMCVVMx\n", "EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTAT\n", "BgNVBAoMDEV4YW1wbGUgQ29ycDEeMBwGA1UECwwVQ2VydGlmaWNhdGUgQXV0aG9y\n", "aXR5MScwJQYDVQQDDB5FeGFtcGxlIENvcnAgT0NTUCBUZXN0IFJvb3QgQ0EwIBcN\n", "MjYwMTAxMDAwMDAwWhgPMjEyNjAxMDEwMDAwMDBaMIGaMQswCQYDVQQGEwJVUzET\n", "MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEVMBMG\n", "A1UECgwMRXhhbXBsZSBDb3JwMR4wHAYDVQQLDBVDZXJ0aWZpY2F0ZSBBdXRob3Jp\n", "dHkxJzAlBgNVBAMMHkV4YW1wbGUgQ29ycCBPQ1NQIFRlc3QgUm9vdCBDQTCCASIw\n", "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKuZRsVUiwQoVlwfXY0nVpvMQiJN\n", "Su5YfrxzKJ9sMfPI2rEg9d8kW2qsfnCB0isj+SK+CkcfVu5aXcUVUnp08VuKpsI9\n", "idkeC7bhXkQgBR1p1mBwChc+UUD6qEindJPhFxYur+7gCmpamSHYr8iYmjVe78l8\n", "vRHVuw8KMsiGegvPsuxIYWdY4mgKSCu3o6DeK4XehXU4Ll5j0fGX5eJgbaI6g8qE\n", "j71J6G8Y4Ur/WqzTp/GZueTY0wzU8k45HS5uWarGYB2PH8DfM2SRQBw5hYd2xIvS\n", "jgFYzCXeIZFnHVCMeB//VaUyYGIaw+Dreh+Wb5zBPh9FyJkrOXYyMeFZUScCAwEA\n", "AaNFMEMwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n", "BBYEFGSeLzT7Ur6wrLpFYVDHmNi/TQp5MA0GCSqGSIb3DQEBCwUAA4IBAQAMMukk\n", "I0VRfxY1vZngu3LdnBk5If6TbQ6lmWatT4q3BrAID3L9zG/qev6DlyruvnVqoNdU\n", "WcWXnzFSDInOKM64wU2oxdosMPWuAKKLHgDdCicikJtF+N0qJgOcyD+Gv9t0kwDB\n", "O094nZUgrkCafJiy5y7KtZvqOjcvfEK+oD7fXX/VURv8NMOxpuassPHnHGvHqwKu\n", "7jBZMjkOoZH4hJSo5EzZxRZHz6ay7Q8e+6C6PG8CIYXpIqukjVNq9jBb2yog67mW\n", "lHlfnEENCyQDSmnEXAVNpJS67jmcItoD1utnDINVzu87DY9WiLZB06Y1xSp1ie7s\n", "FIfS7bZ1cxVkt5wS\n", "-----END CERTIFICATE-----\n", NULL }; static const char *kOcspTestRootKey[] = { "-----BEGIN RSA PRIVATE KEY-----\n", "MIIEowIBAAKCAQEAq5lGxVSLBChWXB9djSdWm8xCIk1K7lh+vHMon2wx88jasSD1\n", "3yRbaqx+cIHSKyP5Ir4KRx9W7lpdxRVSenTxW4qmwj2J2R4LtuFeRCAFHWnWYHAK\n", "Fz5RQPqoSKd0k+EXFi6v7uAKalqZIdivyJiaNV7vyXy9EdW7DwoyyIZ6C8+y7Ehh\n", "Z1jiaApIK7ejoN4rhd6FdTguXmPR8Zfl4mBtojqDyoSPvUnobxjhSv9arNOn8Zm5\n", "5NjTDNTyTjkdLm5ZqsZgHY8fwN8zZJFAHDmFh3bEi9KOAVjMJd4hkWcdUIx4H/9V\n", "pTJgYhrD4Ot6H5ZvnME+H0XImSs5djIx4VlRJwIDAQABAoIBAAj+6w/d47/JtuVI\n", "xlMSXzRMW/cyYsg7SWxAWT5/oeAW2n1zWJBkdopmv+YkAsw81uBfDWjhwraSHtz9\n", "xioh8U6MO+ZuQB4VY3soi2mPiCpyPvPP9nzLc9+v2ZydcrtsjxTxnkrWjJU7afsK\n", "l1nbw3x4HU1McG5RQbzYcFsaJFHJcVdxIYkDfEgnBok1ALaQuRUGfUVYZDVk6Ztg\n", "fMUh5nfNSHWu6y2i8YCphprIfu/zwadicaYLYljYqgP9J1MOPwuMcoCoWgotBUCZ\n", "+29qIPn1mK3ReBmYdE8IIEiSLHYJjJ7EVu8IUfSPotBGGC5PjC11HcCXx0xB9AfB\n", "I8VcDikCgYEA089WizVCWZMxWnCPLDG036vsug3/rrPr/O3W5VXTdpr00CBRzund\n", "7QnaxUyxw37SOAQUtzcKhC0CF3c7Dbryb8d51koROVlWC8rngoDMxiZkbZZe0kL1\n", "jHkOHEOoi83uY0tFoRhYtrcOYObNT9JDifdIDYCnxbLW4UQLo/9mFmUCgYEAz2ZH\n", "hxoxXJ9y3qmIcc2vZTRPdaB4r+qQi5krUgtYg1uKImrvDnnl/4xCnCG9G1s1M/wE\n", "cvuFG5FWGz6TtS0TsCGUXVWZ9s67JT53l02RIVTog/VRHbKsOnj+d9oXglGcxALT\n", "qG02lKocNHzrPUXEw/EIMETKR6WvATZCzhM9mpsCgYB9RoKfb27A4Cgun6husS+T\n", "o3IuUR1KzSvkux+BIRQjcF8fwh3gzb3u9wcn7satJBNeAjvmaW2U47H7AxAwfMPr\n", "jQXo0oIBc29LJkVrkJaNFCQOFQQcRHJLFUZdPT8xASngHKMgNvAxkW+1rIz+ixRb\n", "Q6CgK9oPOkmRjtd7thFBaQKBgQCMbt0QBhRWe0D0tCbHqFaTWJBVPYt60oF9hQFo\n", "VHZiu6EVHQMx8ihimT6hKdc6ps+nm4YHtXez6v07BWxOyW8DXDlx2XyfOexOk7W2\n", "pbcXsr6eW4XJbipgjX0A+pPgkhJsRt26tfi3QVhH0i4XFx7c7mB1Dp9JVE7jqzIh\n", "B7Y28QKBgCg93v7zP5GFmOa6zCZreIIfi1dy50otxfqYewcXy4Egt90cE67vY55u\n", "3B44ySrLkKDuzX2Inrwp5Vao8b9pDQ/AisRzt617eO1H5JtRF3lzZJukYC6j4urU\n", "24kB2IhZCB6/3pwGiS21ma9E6OF9tO5YaGGYo02ZqkoflN+XgoeZ\n", "-----END RSA PRIVATE KEY-----\n", NULL }; static const char *kOcspTestLeaf[] = { "-----BEGIN CERTIFICATE-----\n", "MIIEOzCCAyOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZoxCzAJBgNVBAYTAlVT\n", "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRUw\n", "EwYDVQQKDAxFeGFtcGxlIENvcnAxHjAcBgNVBAsMFUNlcnRpZmljYXRlIEF1dGhv\n", "cml0eTEnMCUGA1UEAwweRXhhbXBsZSBDb3JwIE9DU1AgVGVzdCBSb290IENBMCAX\n", "DTI2MDEwMTAwMDAwMFoYDzIxMjYwMTAxMDAwMDAwWjCBiDELMAkGA1UEBhMCVVMx\n", "EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTAT\n", "BgNVBAoMDEV4YW1wbGUgQ29ycDEVMBMGA1UECwwMV2ViIFNlcnZpY2VzMR4wHAYD\n", "VQQDDBVvY3NwLWxlYWYuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\n", "DwAwggEKAoIBAQCoQbsBGnLjATBzU8eWYMeyzu6vy4scpVDdhGnTWMaSIrqoXXge\n", "JXrMHavT13cv2wNTExA6BNqFZA6YLBXYpDJ5oXyOlY1SYjbapX4M0kry4tBZdGWu\n", "vnh04Q07SI8JvjtScB3lAIIhGr1WrQNLfEz+O80j4Pp8kLe1fWi0joB1CG4RWiuH\n", "/2Ls1rSEMTr6dABtQ+zwxRHcFlEAoR9ZEDzfN1hIQEByWJd7TMnv738usyp52wMi\n", "Gh/sruYHkpsO2tVoLCwm1OMSR1BLdGO1tJWSYN2+Tj2JVleQWcwwCgzDX4OlIdPF\n", "/CM/6aQA6BkDeMW//tO4Ec4X+530zPiFKcZLAgMBAAGjgZgwgZUwDAYDVR0TAQH/\n", "BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0O\n", "BBYEFBEZYONIpHIGBtlfPSlUTcDccGMiMB8GA1UdIwQYMBaAFGSeLzT7Ur6wrLpF\n", "YVDHmNi/TQp5MCAGA1UdEQQZMBeCFW9jc3AtbGVhZi5leGFtcGxlLmNvbTANBgkq\n", "hkiG9w0BAQsFAAOCAQEAaFYoQjmWdBBiD/hfSgV34Jf+PVKkecuu60VKDasPLNyV\n", "ZwZcW8dXF9NOYcebGIEx4rjQTC3xiHUknqfOzc2jmPdU/xBAyRiltQnUh3OFH1qn\n", "bX0YNUgfluLW+YSwNkriFLuzESBVadGhlX94mQwoqYyQJ+6/Ht2j4P4ZiIDRWfgc\n", "z9pN8YoCiXh/I/IxVIWunk7Dla+Gr6BDJ762iQMMzPQ5D3cRAe9BDRgGiN0lPeIa\n", "LMtqDeZZ+dR0KZAr3yZcfyci8SNXwCBbjsoVjmuUj6dlN12pRKxxtBSa23KT6/Dy\n", "ijXZ+PtyIJz8FVBxO0RoECLimwbUPHfNjAWjuJkugA==\n", "-----END CERTIFICATE-----\n", NULL }; /* Load the fixed PKI. Any of the out params may be NULL to skip it. */ static int load_pki(X509 **root, EVP_PKEY **root_key, X509 **leaf) { if (root != NULL && !TEST_ptr(*root = X509_from_strings(kOcspTestRoot))) return 0; if (root_key != NULL && !TEST_ptr(*root_key = PKEY_from_strings(kOcspTestRootKey))) return 0; if (leaf != NULL && !TEST_ptr(*leaf = X509_from_strings(kOcspTestLeaf))) return 0; return 1; } /* * Build a signed OCSP response for |cert| (issued by |issuer|). |resp_status| * is the outer response status, |cert_status| the single-response certificate * status. thisUpdate and nextUpdate are the current time offset by * |this_off_sec| and |next_off_sec| seconds, so callers can also produce * expired or not-yet-valid responses. The response is signed by * |signer|/|signer_key|. Returns a response the caller must free, or NULL. */ static OCSP_RESPONSE *make_ocsp_response(X509 *cert, X509 *issuer, int resp_status, int cert_status, int this_off_sec, int next_off_sec, X509 *signer, EVP_PKEY *signer_key) { OCSP_RESPONSE *resp = NULL; OCSP_BASICRESP *bs = NULL; OCSP_CERTID *cid = NULL; ASN1_TIME *thisupd = NULL, *nextupd = NULL, *revtime = NULL; if (cert_status == V_OCSP_CERTSTATUS_REVOKED && !TEST_ptr(revtime = X509_gmtime_adj(NULL, 0))) goto end; if (!TEST_ptr(bs = OCSP_BASICRESP_new()) || !TEST_ptr(thisupd = X509_time_adj_ex(NULL, 0, this_off_sec, NULL)) || !TEST_ptr(nextupd = X509_time_adj_ex(NULL, 0, next_off_sec, NULL)) || !TEST_ptr(cid = OCSP_cert_to_id(EVP_sha256(), cert, issuer)) || !TEST_ptr(OCSP_basic_add1_status(bs, cid, cert_status, 0, revtime, thisupd, nextupd)) || !TEST_true(OCSP_basic_sign(bs, signer, signer_key, EVP_sha256(), NULL, OCSP_NOCERTS))) goto end; resp = OCSP_response_create(resp_status, bs); end: ASN1_TIME_free(revtime); ASN1_TIME_free(thisupd); ASN1_TIME_free(nextupd); OCSP_CERTID_free(cid); OCSP_BASICRESP_free(bs); return resp; } /* Wrap |resp| into a stack, taking ownership on success. */ static STACK_OF(OCSP_RESPONSE) *make_ocsp_resp_stack(OCSP_RESPONSE *resp) { STACK_OF(OCSP_RESPONSE) *sk = sk_OCSP_RESPONSE_new_null(); if (!TEST_ptr(sk)) return NULL; if (!TEST_true(sk_OCSP_RESPONSE_push(sk, resp))) { sk_OCSP_RESPONSE_free(sk); return NULL; } return sk; } /* * Verify |leaf| against |root| with the stapled |resps| and |flags|, taking * ownership of |resps|. Returns X509_V_OK or an X509_V_ERR_xxx code. The * X509_verify_cert() call is wrapped for malloc-failure injection. */ static int verify_ocsp(X509 *leaf, X509 *root, STACK_OF(OCSP_RESPONSE) *resps, unsigned long flags) { X509_STORE *store = X509_STORE_new(); X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new(); int status = X509_V_ERR_UNSPECIFIED; if (!TEST_ptr(store) || !TEST_ptr(ctx) || !TEST_ptr(param)) goto end; if (!TEST_true(X509_STORE_add_cert(store, root)) || !TEST_true(X509_STORE_CTX_init(ctx, store, leaf, NULL))) goto end; X509_STORE_CTX_set_ocsp_resp(ctx, resps); X509_VERIFY_PARAM_set_depth(param, 16); if (flags != 0) X509_VERIFY_PARAM_set_flags(param, flags); X509_STORE_CTX_set0_param(ctx, param); param = NULL; ERR_clear_error(); MFAIL_start(); status = X509_verify_cert(ctx) == 1 ? X509_V_OK : X509_STORE_CTX_get_error(ctx); MFAIL_end(); end: X509_VERIFY_PARAM_free(param); X509_STORE_CTX_free(ctx); X509_STORE_free(store); sk_OCSP_RESPONSE_pop_free(resps, OCSP_RESPONSE_free); return status; } /* * Build a single response for the leaf and verify it, expecting |expected|. * Every failure inside check_cert_ocsp_resp() surfaces as * X509_V_ERR_OCSP_VERIFY_FAILED at the X509_verify_cert() level. */ static int run_ocsp_verify(int resp_status, int cert_status, int this_off_sec, int next_off_sec, int expected) { X509 *root = NULL, *leaf = NULL; EVP_PKEY *root_key = NULL; OCSP_RESPONSE *resp = NULL; STACK_OF(OCSP_RESPONSE) *resps = NULL; int testresult = 0; if (!load_pki(&root, &root_key, &leaf)) goto end; if (!TEST_ptr(resp = make_ocsp_response(leaf, root, resp_status, cert_status, this_off_sec, next_off_sec, root, root_key)) || !TEST_ptr(resps = make_ocsp_resp_stack(resp))) goto end; resp = NULL; /* owned by resps */ testresult = TEST_int_eq(verify_ocsp(leaf, root, resps, X509_V_FLAG_OCSP_RESP_CHECK), expected); resps = NULL; /* freed by verify_ocsp */ end: sk_OCSP_RESPONSE_pop_free(resps, OCSP_RESPONSE_free); OCSP_RESPONSE_free(resp); EVP_PKEY_free(root_key); X509_free(leaf); X509_free(root); return testresult; } /* * A good response that expired a moment ago is still accepted, because stapled * responses are honoured for up to five minutes past nextUpdate. */ static int test_ocsp_resp_good(void) { return run_ocsp_verify(OCSP_RESPONSE_STATUS_SUCCESSFUL, V_OCSP_CERTSTATUS_GOOD, -10 * 60, -2 * 60, X509_V_OK); } /* An outer response status other than successful is rejected. */ static int test_ocsp_resp_not_successful(void) { return run_ocsp_verify(OCSP_RESPONSE_STATUS_TRYLATER, V_OCSP_CERTSTATUS_GOOD, 0, 24 * 60 * 60, X509_V_ERR_OCSP_VERIFY_FAILED); } /* A response more than five minutes past nextUpdate is rejected as expired. */ static int test_ocsp_resp_expired(void) { return run_ocsp_verify(OCSP_RESPONSE_STATUS_SUCCESSFUL, V_OCSP_CERTSTATUS_GOOD, -20 * 60, -10 * 60, X509_V_ERR_OCSP_VERIFY_FAILED); } /* No response for the leaf's depth: sk_OCSP_RESPONSE_num() <= error_depth. */ static int test_ocsp_resp_none(void) { X509 *root = NULL, *leaf = NULL; STACK_OF(OCSP_RESPONSE) *resps = NULL; int testresult = 0; if (!load_pki(&root, NULL, &leaf) || !TEST_ptr(resps = sk_OCSP_RESPONSE_new_null())) goto end; testresult = TEST_int_eq(verify_ocsp(leaf, root, resps, X509_V_FLAG_OCSP_RESP_CHECK), X509_V_ERR_OCSP_VERIFY_FAILED); resps = NULL; /* freed by verify_ocsp */ end: sk_OCSP_RESPONSE_pop_free(resps, OCSP_RESPONSE_free); X509_free(leaf); X509_free(root); return testresult; } /* * A well-formed response carrying a single response for a different certificate: * no CertID matches the leaf, so no status is found for it. */ static int test_ocsp_resp_wrong_cert(void) { X509 *root = NULL, *leaf = NULL; EVP_PKEY *root_key = NULL; OCSP_RESPONSE *resp = NULL; STACK_OF(OCSP_RESPONSE) *resps = NULL; int testresult = 0; if (!load_pki(&root, &root_key, &leaf)) goto end; /* the response is about the root, not the leaf being verified */ if (!TEST_ptr(resp = make_ocsp_response(root, root, OCSP_RESPONSE_STATUS_SUCCESSFUL, V_OCSP_CERTSTATUS_GOOD, 0, 24 * 60 * 60, root, root_key)) || !TEST_ptr(resps = make_ocsp_resp_stack(resp))) goto end; resp = NULL; /* owned by resps */ testresult = TEST_int_eq(verify_ocsp(leaf, root, resps, X509_V_FLAG_OCSP_RESP_CHECK), X509_V_ERR_OCSP_VERIFY_FAILED); resps = NULL; /* freed by verify_ocsp */ end: sk_OCSP_RESPONSE_pop_free(resps, OCSP_RESPONSE_free); OCSP_RESPONSE_free(resp); EVP_PKEY_free(root_key); X509_free(leaf); X509_free(root); return testresult; } /* Exercise the success path under mfail. */ static int test_ocsp_resp_mfail(void) { X509 *root = NULL, *leaf = NULL; EVP_PKEY *root_key = NULL; OCSP_RESPONSE *resp = NULL; STACK_OF(OCSP_RESPONSE) *resps = NULL; int testresult = 0; if (!load_pki(&root, &root_key, &leaf)) goto end; if (!TEST_ptr(resp = make_ocsp_response(leaf, root, OCSP_RESPONSE_STATUS_SUCCESSFUL, V_OCSP_CERTSTATUS_GOOD, 0, 24 * 60 * 60, root, root_key)) || !TEST_ptr(resps = make_ocsp_resp_stack(resp))) goto end; resp = NULL; /* owned by resps */ testresult = verify_ocsp(leaf, root, resps, X509_V_FLAG_OCSP_RESP_CHECK) == X509_V_OK; resps = NULL; /* freed by verify_ocsp */ end: sk_OCSP_RESPONSE_pop_free(resps, OCSP_RESPONSE_free); OCSP_RESPONSE_free(resp); EVP_PKEY_free(root_key); X509_free(leaf); X509_free(root); return testresult; } #endif /* OPENSSL_NO_OCSP */ int setup_tests(void) { #ifndef OPENSSL_NO_OCSP ADD_TEST(test_ocsp_resp_good); ADD_TEST(test_ocsp_resp_not_successful); ADD_TEST(test_ocsp_resp_expired); ADD_TEST(test_ocsp_resp_none); ADD_TEST(test_ocsp_resp_wrong_cert); ADD_MFAIL_NO_CHECK_TEST(test_ocsp_resp_mfail); #endif return 1; }