Skip to content

Commit

Permalink
add support for OCSP_request_verify
Browse files Browse the repository at this point in the history
  • Loading branch information
samuel40791765 committed Aug 19, 2024
1 parent 8080ce3 commit 30d3135
Show file tree
Hide file tree
Showing 9 changed files with 948 additions and 693 deletions.
149 changes: 124 additions & 25 deletions crypto/ocsp/ocsp_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -517,38 +517,39 @@ TEST(OCSPTest, TestGoodOCSP_SHA256) {
ASSERT_EQ(-1, X509_cmp_time(nextupd, &connection_time));
}

struct OCSPVerifyFlagTestVector {
struct OCSPResponseVerifyFlagTestVector {
const char *ocsp_response;
unsigned long ocsp_verify_flag;
bool include_expired_signer_cert;
int expected_ocsp_verify_status;
};

static const OCSPVerifyFlagTestVector OCSPVerifyFlagTestVectors[] = {
{"ocsp_response", OCSP_NOINTERN, false, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_response", OCSP_NOCHAIN, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_expired_signer", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_wrong_signer", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response", OCSP_NOEXPLICIT, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response", OCSP_TRUSTOTHER, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_expired_signer", OCSP_TRUSTOTHER, false,
OCSP_VERIFYSTATUS_ERROR},
// |OCSP_TRUSTOTHER| sets |OCSP_NOVERIFY| if the signer cert is included
// within the parameters.
{"ocsp_response_expired_signer", OCSP_TRUSTOTHER, true,
OCSP_VERIFYSTATUS_SUCCESS},
static const OCSPResponseVerifyFlagTestVector
OCSPResponseVerifyFlagTestVectors[] = {
{"ocsp_response", OCSP_NOINTERN, false, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_response", OCSP_NOCHAIN, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_expired_signer", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_wrong_signer", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response", OCSP_NOEXPLICIT, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response", OCSP_TRUSTOTHER, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_response_expired_signer", OCSP_TRUSTOTHER, false,
OCSP_VERIFYSTATUS_ERROR},
// |OCSP_TRUSTOTHER| sets |OCSP_NOVERIFY| if the signer cert is included
// within the parameters.
{"ocsp_response_expired_signer", OCSP_TRUSTOTHER, true,
OCSP_VERIFYSTATUS_SUCCESS},
};

class OCSPVerifyFlagTest
: public testing::TestWithParam<OCSPVerifyFlagTestVector> {};
: public testing::TestWithParam<OCSPResponseVerifyFlagTestVector> {};

INSTANTIATE_TEST_SUITE_P(All, OCSPVerifyFlagTest,
testing::ValuesIn(OCSPVerifyFlagTestVectors));
testing::ValuesIn(OCSPResponseVerifyFlagTestVectors));

TEST_P(OCSPVerifyFlagTest, OCSPVerifyFlagTest) {
const OCSPVerifyFlagTestVector &t = GetParam();
const OCSPResponseVerifyFlagTestVector &t = GetParam();

std::string respData =
GetTestData(std::string("crypto/ocsp/test/aws/" +
Expand Down Expand Up @@ -597,6 +598,98 @@ TEST_P(OCSPVerifyFlagTest, OCSPVerifyFlagTest) {
ASSERT_EQ(t.expected_ocsp_verify_status, ocsp_verify_status);
}

struct OCSPRequestVerifyFlagTestVector {
const char *ocsp_request;
unsigned long ocsp_verify_flag;
bool include_expired_signer_cert;
int expected_ocsp_verify_status;
};

static const OCSPRequestVerifyFlagTestVector
OCSPRequestVerifyFlagTestVectors[] = {
// Although signatures for OCSP requests are optional according to the
// RFC, OCSP requests with absent signatures can't be verified.
{"ocsp_request", 0, false, OCSP_VERIFYSTATUS_ERROR},
// ocsp_request_signed's certificate is embedded in the request.
{"ocsp_request_signed", 0, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_signed", OCSP_NOINTERN, false, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_signed", OCSP_NOCHAIN, false, OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_wrong_signer", 0, false, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_wrong_signer", OCSP_NOCHAIN, false,
OCSP_VERIFYSTATUS_ERROR},
// OCSP request verification will fail if the cert can't be found or
// when working with an expired cert.
{"ocsp_request_expired_signer", 0, true, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_expired_signer", 0, false, OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_expired_signer_no_certs", 0, true,
OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_expired_signer_no_certs", 0, false,
OCSP_VERIFYSTATUS_ERROR},
// |OCSP_NOVERIFY| skips certificate verification, but can't succeed if
// the signer cert isn't found.
{"ocsp_request_expired_signer", OCSP_NOVERIFY, true,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_expired_signer", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_expired_signer_no_certs", OCSP_NOVERIFY, true,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_expired_signer_no_certs", OCSP_NOVERIFY, false,
OCSP_VERIFYSTATUS_ERROR},
// |OCSP_TRUSTOTHER| sets |OCSP_NOVERIFY| if the signer cert is included
// within the parameters.
{"ocsp_request_expired_signer", OCSP_TRUSTOTHER, true,
OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_expired_signer", OCSP_TRUSTOTHER, false,
OCSP_VERIFYSTATUS_ERROR},
{"ocsp_request_expired_signer_no_certs", OCSP_TRUSTOTHER, true,
OCSP_VERIFYSTATUS_SUCCESS},
{"ocsp_request_expired_signer_no_certs", OCSP_TRUSTOTHER, false,
OCSP_VERIFYSTATUS_ERROR},
};

class OCSPRequestVerifyFlagTest
: public testing::TestWithParam<OCSPRequestVerifyFlagTestVector> {};

INSTANTIATE_TEST_SUITE_P(All, OCSPRequestVerifyFlagTest,
testing::ValuesIn(OCSPRequestVerifyFlagTestVectors));

TEST_P(OCSPRequestVerifyFlagTest, OCSPVerifyFlagTest) {
const OCSPRequestVerifyFlagTestVector &t = GetParam();

std::string respData =
GetTestData(std::string("crypto/ocsp/test/aws/" +
std::string(t.ocsp_request) + ".der")
.c_str());
std::vector<uint8_t> ocsp_request_data(respData.begin(), respData.end());
bssl::UniquePtr<OCSP_REQUEST> ocsp_request =
LoadOCSP_REQUEST(ocsp_request_data);
ASSERT_TRUE(ocsp_request);

// Set up trust store and certificate chain.
bssl::UniquePtr<X509> ca_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
.c_str()));

bssl::UniquePtr<X509_STORE> trust_store(X509_STORE_new());
X509_STORE_add_cert(trust_store.get(), ca_cert.get());
bssl::UniquePtr<STACK_OF(X509)> server_cert_chain(sk_X509_new_null());
ASSERT_TRUE(server_cert_chain);
if (t.include_expired_signer_cert) {
bssl::UniquePtr<X509> signing_cert(CertFromPEM(
GetTestData(
std::string("crypto/ocsp/test/aws/ocsp_expired_cert.pem").c_str())
.c_str()));
ASSERT_TRUE(sk_X509_push(server_cert_chain.get(), signing_cert.get()));
X509_up_ref(signing_cert.get());
}

// Does basic verification on OCSP request.
const int ocsp_verify_status =
OCSP_request_verify(ocsp_request.get(), server_cert_chain.get(),
trust_store.get(), t.ocsp_verify_flag);
ASSERT_EQ(t.expected_ocsp_verify_status, ocsp_verify_status);
}

TEST(OCSPTest, GetInfo) {
bssl::UniquePtr<X509> issuer(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
Expand Down Expand Up @@ -909,16 +1002,16 @@ TEST_P(OCSPRequestTest, OCSPRequestSign) {
bssl::UniquePtr<OCSP_REQUEST> ocspRequest =
LoadOCSP_REQUEST(ocsp_request_data);

bssl::UniquePtr<X509> server_cert(
bssl::UniquePtr<X509> signer_cert(
CertFromPEM(GetTestData(std::string("crypto/ocsp/test/aws/" +
std::string(t.signer_cert) + ".pem")
.c_str())
.c_str()));
ASSERT_TRUE(server_cert);
bssl::UniquePtr<STACK_OF(X509)> additional_cert(CertChainFromPEM(
ASSERT_TRUE(signer_cert);
bssl::UniquePtr<X509> ca_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
.c_str()));
ASSERT_TRUE(additional_cert);
ASSERT_TRUE(ca_cert);

if (t.expected_parse_status == OCSP_REQUEST_PARSE_SUCCESS) {
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
Expand All @@ -940,11 +1033,17 @@ TEST_P(OCSPRequestTest, OCSPRequestSign) {
ASSERT_TRUE(EVP_PKEY_set1_EC_KEY(pkey.get(), ecdsa.get()));
}

int ret = OCSP_request_sign(ocspRequest.get(), server_cert.get(),
pkey.get(), t.dgst, additional_cert.get(), 0);
int ret =
OCSP_request_sign(ocspRequest.get(), signer_cert.get(), pkey.get(),
t.dgst, CertsToStack({ca_cert.get()}).get(), 0);
if (t.expected_sign_status == OCSP_SIGN_SUCCESS) {
ASSERT_TRUE(ret);
EXPECT_TRUE(OCSP_request_is_signed(ocspRequest.get()));
bssl::UniquePtr<X509_STORE> trust_store(X509_STORE_new());
ASSERT_TRUE(X509_STORE_add_cert(trust_store.get(), ca_cert.get()));
EXPECT_TRUE(OCSP_request_verify(ocspRequest.get(),
CertsToStack({signer_cert.get()}).get(),
trust_store.get(), 0));
} else {
ASSERT_FALSE(ret);
EXPECT_FALSE(OCSP_request_is_signed(ocspRequest.get()));
Expand Down
119 changes: 114 additions & 5 deletions crypto/ocsp/ocsp_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <string.h>
#include "../internal.h"
#include "internal.h"

#define SIGNER_IN_CERTSTACK 2
#define SIGNER_IN_BASICRESP 1
#define SIGNER_IN_TRUSTED_CERTS 2
#define SIGNER_IN_OCSP_CERTS 1
#define SIGNER_NOT_FOUND 0

// Set up |X509_STORE_CTX| to verify signer and returns cert chain if verify is
Expand Down Expand Up @@ -57,15 +58,15 @@ static int ocsp_find_signer(X509 **psigner, OCSP_BASICRESP *bs,
signer = ocsp_find_signer_sk(certs, rid);
if (signer != NULL) {
*psigner = signer;
return SIGNER_IN_CERTSTACK;
return SIGNER_IN_TRUSTED_CERTS;
}

// look in certs stack the responder may have included in |OCSP_BASICRESP|,
// unless the flags contain |OCSP_NOINTERN|.
signer = ocsp_find_signer_sk(bs->certs, rid);
if (signer != NULL && !IS_OCSP_FLAG_SET(flags, OCSP_NOINTERN)) {
*psigner = signer;
return SIGNER_IN_BASICRESP;
return SIGNER_IN_OCSP_CERTS;
}
// Maybe lookup from store if by subject name.

Expand Down Expand Up @@ -340,8 +341,10 @@ int OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st,
OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND);
goto end;
}
if ((ret == SIGNER_IN_CERTSTACK) &&
if ((ret == SIGNER_IN_TRUSTED_CERTS) &&
IS_OCSP_FLAG_SET(flags, OCSP_TRUSTOTHER)) {
// We skip verification if the flag to trust |certs| is set and the signer
// is found within that stack.
flags |= OCSP_NOVERIFY;
}

Expand Down Expand Up @@ -388,3 +391,109 @@ int OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st,
sk_X509_free(untrusted);
return ret;
}

// ocsp_req_find_signer assigns |*psigner| to the signing certificate and
// returns |SIGNER_IN_OCSP_CERTS| if it was found within |req| or returns
// |SIGNER_IN_TRUSTED_CERTS| if it was found within |certs|. It returns
// |SIGNER_NOT_FOUND| if the signing certificate is not found.
static int ocsp_req_find_signer(X509 **psigner, OCSP_REQUEST *req,
X509_NAME *nm, STACK_OF(X509) *certs,
unsigned long flags) {
X509 *signer = NULL;
if (!IS_OCSP_FLAG_SET(flags, OCSP_NOINTERN)) {
signer = X509_find_by_subject(req->optionalSignature->certs, nm);
if (signer != NULL) {
*psigner = signer;
return SIGNER_IN_OCSP_CERTS;
}
}

signer = X509_find_by_subject(certs, nm);
if (signer != NULL) {
*psigner = signer;
return SIGNER_IN_TRUSTED_CERTS;
}
return SIGNER_NOT_FOUND;
}

int OCSP_request_verify(OCSP_REQUEST *req, STACK_OF(X509) *certs,
X509_STORE *store, unsigned long flags) {
GUARD_PTR(req);
GUARD_PTR(req->tbsRequest);
GUARD_PTR(store);

// Check if |req| has signature to check against.
if (req->optionalSignature == NULL) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_REQUEST_NOT_SIGNED);
return 0;
}

GENERAL_NAME *gen = req->tbsRequest->requestorName;
if (gen == NULL || gen->type != GEN_DIRNAME) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNSUPPORTED_REQUESTORNAME_TYPE);
return 0;
}

// Find |signer| from |certs| against criteria.
X509 *signer = NULL;
int signer_status =
ocsp_req_find_signer(&signer, req, gen->d.directoryName, certs, flags);
if (signer_status <= SIGNER_NOT_FOUND || signer == NULL) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND);
return 0;
}
if (signer_status == SIGNER_IN_TRUSTED_CERTS &&
IS_OCSP_FLAG_SET(flags, OCSP_TRUSTOTHER)) {
// We skip certificate verification if the flag to trust |certs| is set and
// the signer is found within that stack.
flags |= OCSP_NOVERIFY;
}

// Validate |req|'s signature.
EVP_PKEY *skey = X509_get0_pubkey(signer);
if (skey == NULL) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_SIGNER_KEY);
return 0;
}
if (ASN1_item_verify(ASN1_ITEM_rptr(OCSP_REQINFO),
req->optionalSignature->signatureAlgorithm,
req->optionalSignature->signature, req->tbsRequest,
skey) <= 0) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNATURE_FAILURE);
return 0;
}

// Set up |ctx| and start doing the actual verification.
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (ctx == NULL) {
return 0;
}

// Validate the signing certificate.
int ret = 0;
if (!IS_OCSP_FLAG_SET(flags, OCSP_NOVERIFY)) {
// Initialize and set purpose of |ctx| for verification.
if (!X509_STORE_CTX_init(ctx, store, signer, NULL) &&
!X509_STORE_CTX_set_purpose(ctx, X509_PURPOSE_OCSP_HELPER)) {
OPENSSL_PUT_ERROR(OCSP, ERR_R_X509_LIB);
goto end;
}
if (!IS_OCSP_FLAG_SET(flags, OCSP_NOCHAIN)) {
X509_STORE_CTX_set_chain(ctx, req->optionalSignature->certs);
}

// Do the verification.
if (X509_verify_cert(ctx) <= 0) {
int err = X509_STORE_CTX_get_error(ctx);
OPENSSL_PUT_ERROR(OCSP, OCSP_R_CERTIFICATE_VERIFY_ERROR);
ERR_add_error_data(2,
"Verify error:", X509_verify_cert_error_string(err));
goto end;
}
}
ret = 1;

end:
X509_STORE_CTX_free(ctx);
return ret;
}
12 changes: 12 additions & 0 deletions crypto/ocsp/test/aws/OCSP-TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ openssl ocsp -sha256 -issuer ca_cert.pem -cert server_cert.pem -signer ocsp_cert
```
openssl ocsp -issuer ca_cert.pem -cert server_cert.pem -signer ocsp_cert.pem -signkey ocsp_key.pem -sign_other ca_cert.pem -reqout ocsp_request_attached_cert.der
```
## Generating a OCSP request signed by the wrong signer
```
openssl ocsp -issuer ca_cert.pem -cert server_cert.pem -signer server_ecdsa_cert.pem -signkey server_ecdsa_key.pem -no_certs -reqout ocsp_request_wrong_signer.der
```

## Generating a OCSP request signed by an expired signer
```
openssl ocsp -issuer ca_cert.pem -cert server_cert.pem -signer ocsp_expired_cert.pem -signkey ocsp_key.pem -reqout ocsp_request_expired_signer.der
```
```
openssl ocsp -issuer ca_cert.pem -cert server_cert.pem -signer ocsp_expired_cert.pem -signkey ocsp_key.pem -no_certs -reqout ocsp_request_expired_signer_no_certs.der
```

## Generating a new OCSP response for the leaf cert
The current OCSP responses expire in 10 years. Our tests using these files only check if the timefield value has been
Expand Down
Binary file not shown.
Binary file not shown.
Binary file added crypto/ocsp/test/aws/ocsp_request_wrong_signer.der
Binary file not shown.
Loading

0 comments on commit 30d3135

Please sign in to comment.