Skip to content

Commit

Permalink
support OCSP_response_create and OCSP_basic_add1_status
Browse files Browse the repository at this point in the history
  • Loading branch information
samuel40791765 committed Aug 13, 2024
1 parent d6caa99 commit b709724
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 44 deletions.
29 changes: 2 additions & 27 deletions crypto/ocsp/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,6 @@
extern "C" {
#endif

// OCSP reason codes identify the reason for the certificate revocation.
//
// CRLReason ::= ENUMERATED {
// unspecified (0),
// keyCompromise (1),
// cACompromise (2),
// affiliationChanged (3),
// superseded (4),
// cessationOfOperation (5),
// -- value 7 is not used
// certificateHold (6),
// removeFromCRL (8),
// privilegeWithdrawn (9),
// aACompromise (10) }
//
// Reason Code RFC: https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1
#define OCSP_REVOKED_STATUS_UNSPECIFIED 0
#define OCSP_REVOKED_STATUS_KEYCOMPROMISE 1
#define OCSP_REVOKED_STATUS_CACOMPROMISE 2
#define OCSP_REVOKED_STATUS_AFFILIATIONCHANGED 3
#define OCSP_REVOKED_STATUS_SUPERSEDED 4
#define OCSP_REVOKED_STATUS_CESSATIONOFOPERATION 5
#define OCSP_REVOKED_STATUS_CERTIFICATEHOLD 6
#define OCSP_REVOKED_STATUS_REMOVEFROMCRL 8
#define OCSP_REVOKED_STATUS_PRIVILEGEWITHDRAWN 9
#define OCSP_REVOKED_STATUS_AACOMPROMISE 10

// OCSP Request ASN.1 specification:
// https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1
//
Expand Down Expand Up @@ -254,6 +227,8 @@ DECLARE_ASN1_FUNCTIONS(OCSP_ONEREQ)
DECLARE_ASN1_FUNCTIONS(OCSP_RESPDATA)
DECLARE_ASN1_FUNCTIONS(OCSP_REQINFO)
DECLARE_ASN1_FUNCTIONS(OCSP_SIGNATURE)
DECLARE_ASN1_FUNCTIONS(OCSP_RESPBYTES)
DECLARE_ASN1_FUNCTIONS(OCSP_REVOKEDINFO)

// Try exchanging request and response via HTTP on (non-)blocking BIO in rctx.
OPENSSL_EXPORT int OCSP_REQ_CTX_nbio(OCSP_REQ_CTX *rctx);
Expand Down
2 changes: 2 additions & 0 deletions crypto/ocsp/ocsp_asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ IMPLEMENT_ASN1_FUNCTIONS(OCSP_ONEREQ)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_REQINFO)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_REQUEST)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPONSE)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPBYTES)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPDATA)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_REVOKEDINFO)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_BASICRESP)
IMPLEMENT_ASN1_DUP_FUNCTION(OCSP_CERTID)
IMPLEMENT_ASN1_FUNCTIONS(OCSP_SINGLERESP)
Expand Down
145 changes: 145 additions & 0 deletions crypto/ocsp/ocsp_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,148 @@ int OCSP_basic_add1_cert(OCSP_BASICRESP *resp, X509 *cert) {
X509_up_ref(cert);
return 1;
}

OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *resp, OCSP_CERTID *cid,
int status, int revoked_reason,
ASN1_TIME *revoked_time,
ASN1_TIME *this_update,
ASN1_TIME *next_update) {
GUARD_PTR(resp);
GUARD_PTR(cid);
GUARD_PTR(this_update);
// Ambiguous status values are not allowed.
if (status < V_OCSP_CERTSTATUS_GOOD || status > V_OCSP_CERTSTATUS_UNKNOWN) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNKNOWN_FIELD_VALUE);
return NULL;
}

OCSP_SINGLERESP *single = OCSP_SINGLERESP_new();
if (single == NULL) {
goto err;
}

// Init |resp->tbsResponseData->responses| if NULL.
if (resp->tbsResponseData->responses == NULL) {
resp->tbsResponseData->responses = sk_OCSP_SINGLERESP_new_null();
if (resp->tbsResponseData->responses == NULL) {
goto err;
}
}

if (!ASN1_TIME_to_generalizedtime(this_update, &single->thisUpdate)) {
goto err;
}
if (next_update != NULL) {
// |next_update| is allowed to be NULL. Only set |single->nextUpdate| if
// |next_update| is non-NULL.
if (!ASN1_TIME_to_generalizedtime(next_update, &single->nextUpdate)) {
goto err;
}
}

// Reset |single->certId|.
OCSP_CERTID_free(single->certId);
single->certId = OCSP_CERTID_dup(cid);
if (single->certId == NULL) {
goto err;
}

single->certStatus->type = status;
switch (single->certStatus->type) {
case V_OCSP_CERTSTATUS_REVOKED:
if (revoked_time == NULL) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_REVOKED_TIME);
goto err;
}

single->certStatus->value.revoked = OCSP_REVOKEDINFO_new();
if (single->certStatus->value.revoked == NULL) {
goto err;
}

// Start assigning values to |info| once initialized successfully.
OCSP_REVOKEDINFO *info = single->certStatus->value.revoked;
if (!ASN1_TIME_to_generalizedtime(revoked_time, &info->revocationTime)) {
goto err;
}
// https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1 specifies the only
// valid reason codes are 0-10. Value 7 is not used.
if (revoked_reason < OCSP_REVOKED_STATUS_UNSPECIFIED ||
revoked_reason > OCSP_REVOKED_STATUS_AACOMPROMISE ||
revoked_reason == 7) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNKNOWN_FIELD_VALUE);
goto err;
}
info->revocationReason = ASN1_ENUMERATED_new();
if (info->revocationReason == NULL ||
!ASN1_ENUMERATED_set(info->revocationReason, revoked_reason)) {
goto err;
}

break;

case V_OCSP_CERTSTATUS_GOOD:
single->certStatus->value.good = ASN1_NULL_new();
if (single->certStatus->value.good == NULL) {
goto err;
}
break;

case V_OCSP_CERTSTATUS_UNKNOWN:
single->certStatus->value.unknown = ASN1_NULL_new();
if (single->certStatus->value.unknown == NULL) {
goto err;
}
break;

default:
goto err;
}

// Finally add the |OCSP_SINGLERESP| we were working with to |resp|.
if (!sk_OCSP_SINGLERESP_push(resp->tbsResponseData->responses, single)) {
goto err;
}
return single;

err:
OCSP_SINGLERESP_free(single);
return NULL;
}

OCSP_RESPONSE *OCSP_response_create(int status, OCSP_BASICRESP *bs) {
if (status < OCSP_RESPONSE_STATUS_SUCCESSFUL ||
status > OCSP_RESPONSE_STATUS_UNAUTHORIZED ||
// 4 is not a valid response status code.
status == 4) {
OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNKNOWN_FIELD_VALUE);
return NULL;
}

OCSP_RESPONSE *rsp = OCSP_RESPONSE_new();
if (rsp == NULL) {
goto err;
}
if (!ASN1_ENUMERATED_set(rsp->responseStatus, status)) {
goto err;
}
if (bs == NULL) {
// |bs| is allowed to be NULL.
return rsp;
}

rsp->responseBytes = OCSP_RESPBYTES_new();
if (rsp->responseBytes == NULL) {
goto err;
}
rsp->responseBytes->responseType = OBJ_nid2obj(NID_id_pkix_OCSP_basic);
if (!ASN1_item_pack(bs, ASN1_ITEM_rptr(OCSP_BASICRESP),
&rsp->responseBytes->response)) {
goto err;
}
return rsp;

err:
OCSP_RESPONSE_free(rsp);
return NULL;
}
107 changes: 90 additions & 17 deletions crypto/ocsp/ocsp_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ static bssl::UniquePtr<OCSP_REQUEST> LoadOCSP_REQUEST(
d2i_OCSP_REQUEST(nullptr, &ptr, der.size()));
}

// Load a generic |OCSP_CERTID| for testing.
static OCSP_CERTID *LoadTestOCSP_CERTID() {
bssl::UniquePtr<X509> ca_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
.c_str()));
bssl::UniquePtr<X509> server_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/server_cert.pem").c_str())
.c_str()));
return OCSP_cert_to_id(nullptr, ca_cert.get(), server_cert.get());
}

static void ExtractAndVerifyBasicOCSP(
bssl::Span<const uint8_t> der, const std::string ca_cert_file,
const std::string server_cert_file, int expected_ocsp_verify_status,
Expand Down Expand Up @@ -599,16 +610,8 @@ TEST_P(OCSPVerifyFlagTest, OCSPVerifyFlagTest) {
}

TEST(OCSPTest, GetInfo) {
bssl::UniquePtr<X509> issuer(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
.c_str()));
bssl::UniquePtr<X509> subject(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/server_cert.pem").c_str())
.c_str()));

// Create a sample |OCSP_CERTID| structure.
bssl::UniquePtr<OCSP_CERTID> cert_id(
OCSP_cert_to_id(EVP_sha256(), subject.get(), issuer.get()));
bssl::UniquePtr<OCSP_CERTID> cert_id(LoadTestOCSP_CERTID());
ASSERT_TRUE(cert_id);

ASN1_OCTET_STRING *nameHash = nullptr;
Expand Down Expand Up @@ -644,6 +647,83 @@ TEST(OCSPTest, BasicAddCert) {
cert.get());
}

TEST(OCSPTest, BasicAddStatus) {
bssl::UniquePtr<OCSP_BASICRESP> basicResponse(OCSP_BASICRESP_new());
ASSERT_TRUE(basicResponse);
bssl::UniquePtr<OCSP_CERTID> certId(LoadTestOCSP_CERTID());
ASSERT_TRUE(certId);

bssl::UniquePtr<ASN1_TIME> revoked_time(ASN1_TIME_new()),
this_update(ASN1_TIME_new()), next_update(ASN1_TIME_new());
ASSERT_TRUE(
ASN1_TIME_set(revoked_time.get(), invalid_before_ocsp_update_time));
ASSERT_TRUE(ASN1_TIME_set(this_update.get(), valid_after_ocsp_update_time));
ASSERT_TRUE(ASN1_TIME_set(next_update.get(), valid_before_ocsp_expire_time));

EXPECT_TRUE(OCSP_basic_add1_status(basicResponse.get(), certId.get(),
V_OCSP_CERTSTATUS_GOOD, 0, nullptr,
this_update.get(), next_update.get()));

EXPECT_TRUE(OCSP_basic_add1_status(basicResponse.get(), certId.get(),
V_OCSP_CERTSTATUS_UNKNOWN, 0, nullptr,
this_update.get(), next_update.get()));

// Try setting a revoked response without an |ASN1_TIME|.
EXPECT_FALSE(OCSP_basic_add1_status(basicResponse.get(), certId.get(),
V_OCSP_CERTSTATUS_REVOKED, 0, nullptr,
this_update.get(), nullptr));

// Try setting a revoked response with an invalid revoked reason number.
EXPECT_FALSE(OCSP_basic_add1_status(
basicResponse.get(), certId.get(), V_OCSP_CERTSTATUS_REVOKED, 7,
revoked_time.get(), this_update.get(), nullptr));

EXPECT_TRUE(OCSP_basic_add1_status(
basicResponse.get(), certId.get(), V_OCSP_CERTSTATUS_REVOKED,
OCSP_REVOKED_STATUS_UNSPECIFIED, revoked_time.get(), this_update.get(),
nullptr));

// Try setting an invalid status to the response.
EXPECT_FALSE(OCSP_basic_add1_status(basicResponse.get(), certId.get(), 4, 0,
nullptr, this_update.get(), nullptr));

// |OCSP_basic_add1_status| has succeeded 3 times at this point, so the
// |basicResponse| should have 3 |OCSP_SINGLERESP|s in the internal stack.
EXPECT_EQ((int)sk_OCSP_SINGLERESP_num(
basicResponse.get()->tbsResponseData->responses),
3);
}

TEST(OCSPTest, OCSPResponseRecreate) {
std::string data = GetTestData(
std::string("crypto/ocsp/test/aws/ocsp_response.der").c_str());
std::vector<uint8_t> ocsp_response_data(data.begin(), data.end());
bssl::UniquePtr<OCSP_RESPONSE> ocsp_response(
LoadOCSP_RESPONSE(ocsp_response_data));
ASSERT_TRUE(ocsp_response);
bssl::UniquePtr<OCSP_BASICRESP> basic_response(
OCSP_response_get1_basic(ocsp_response.get()));
ASSERT_TRUE(basic_response);

// Recreate the same |OCSP_RESPONSE| with the same contents.
bssl::UniquePtr<OCSP_RESPONSE> ocsp_response_recreated(OCSP_response_create(
OCSP_response_status(ocsp_response.get()), basic_response.get()));
EXPECT_TRUE(ocsp_response_recreated);

// Write out the bytes from |ocsp_response_recreated| to compare with the
// original.
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
EXPECT_TRUE(i2d_OCSP_RESPONSE_bio(bio.get(), ocsp_response_recreated.get()));
const uint8_t *bio_data;
size_t bio_len;
ASSERT_TRUE(BIO_mem_contents(bio.get(), &bio_data, &bio_len));
EXPECT_EQ(Bytes(bio_data, bio_len),
Bytes(ocsp_response_data.data(), ocsp_response_data.size()));

// Disallow creation of an |OCSP_RESPONSE| with an invalid status number.
EXPECT_FALSE(OCSP_response_create(4, basic_response.get()));
}

// === Translation of OpenSSL's OCSP tests ===

// https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/test/recipes/80-test_ocsp.t
Expand Down Expand Up @@ -1004,14 +1084,7 @@ TEST(OCSPRequestTest, AddCert) {
ASSERT_TRUE(ocspRequest);

// Construct |OCSP_CERTID| from certs.
bssl::UniquePtr<X509> ca_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str())
.c_str()));
bssl::UniquePtr<X509> server_cert(CertFromPEM(
GetTestData(std::string("crypto/ocsp/test/aws/server_cert.pem").c_str())
.c_str()));
OCSP_CERTID *certId =
OCSP_cert_to_id(nullptr, ca_cert.get(), server_cert.get());
OCSP_CERTID *certId = LoadTestOCSP_CERTID();
ASSERT_TRUE(certId);

OCSP_ONEREQ *oneRequest = OCSP_request_add0_id(ocspRequest.get(), certId);
Expand Down
Loading

0 comments on commit b709724

Please sign in to comment.