diff --git a/crypto/ocsp/internal.h b/crypto/ocsp/internal.h index cbc44bc1646..f749b97ea79 100644 --- a/crypto/ocsp/internal.h +++ b/crypto/ocsp/internal.h @@ -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 // @@ -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); diff --git a/crypto/ocsp/ocsp_asn.c b/crypto/ocsp/ocsp_asn.c index ebcbd30a725..839390a90d4 100644 --- a/crypto/ocsp/ocsp_asn.c +++ b/crypto/ocsp/ocsp_asn.c @@ -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) diff --git a/crypto/ocsp/ocsp_server.c b/crypto/ocsp/ocsp_server.c index 02c33ca4154..acc6e4a9427 100644 --- a/crypto/ocsp/ocsp_server.c +++ b/crypto/ocsp/ocsp_server.c @@ -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; +} diff --git a/crypto/ocsp/ocsp_test.cc b/crypto/ocsp/ocsp_test.cc index 0da4669acdf..a1e4324e6eb 100644 --- a/crypto/ocsp/ocsp_test.cc +++ b/crypto/ocsp/ocsp_test.cc @@ -128,6 +128,17 @@ static bssl::UniquePtr LoadOCSP_REQUEST( d2i_OCSP_REQUEST(nullptr, &ptr, der.size())); } +// Load a generic |OCSP_CERTID| for testing. +static OCSP_CERTID *LoadTestOCSP_CERTID() { + bssl::UniquePtr ca_cert(CertFromPEM( + GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str()) + .c_str())); + bssl::UniquePtr 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 der, const std::string ca_cert_file, const std::string server_cert_file, int expected_ocsp_verify_status, @@ -599,16 +610,8 @@ TEST_P(OCSPVerifyFlagTest, OCSPVerifyFlagTest) { } TEST(OCSPTest, GetInfo) { - bssl::UniquePtr issuer(CertFromPEM( - GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str()) - .c_str())); - bssl::UniquePtr subject(CertFromPEM( - GetTestData(std::string("crypto/ocsp/test/aws/server_cert.pem").c_str()) - .c_str())); - // Create a sample |OCSP_CERTID| structure. - bssl::UniquePtr cert_id( - OCSP_cert_to_id(EVP_sha256(), subject.get(), issuer.get())); + bssl::UniquePtr cert_id(LoadTestOCSP_CERTID()); ASSERT_TRUE(cert_id); ASN1_OCTET_STRING *nameHash = nullptr; @@ -644,6 +647,79 @@ TEST(OCSPTest, BasicAddCert) { cert.get()); } +TEST(OCSPTest, BasicAddStatus) { + bssl::UniquePtr basicResponse(OCSP_BASICRESP_new()); + ASSERT_TRUE(basicResponse); + bssl::UniquePtr certId(LoadTestOCSP_CERTID()); + ASSERT_TRUE(certId); + + bssl::UniquePtr 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)); + + // |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 ocsp_response_data(data.begin(), data.end()); + bssl::UniquePtr ocsp_response( + LoadOCSP_RESPONSE(ocsp_response_data)); + ASSERT_TRUE(ocsp_response); + bssl::UniquePtr 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_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_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 @@ -1004,14 +1080,7 @@ TEST(OCSPRequestTest, AddCert) { ASSERT_TRUE(ocspRequest); // Construct |OCSP_CERTID| from certs. - bssl::UniquePtr ca_cert(CertFromPEM( - GetTestData(std::string("crypto/ocsp/test/aws/ca_cert.pem").c_str()) - .c_str())); - bssl::UniquePtr 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); diff --git a/include/openssl/ocsp.h b/include/openssl/ocsp.h index af29b1d5664..69f7e783250 100644 --- a/include/openssl/ocsp.h +++ b/include/openssl/ocsp.h @@ -21,6 +21,33 @@ extern "C" { // Various OCSP flags and values +// The following constants are 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_NOCERTS is for |OCSP_request_sign| if no certificates are included // in the |OCSP_REQUEST|. Certificates are optional. @@ -369,6 +396,25 @@ OPENSSL_EXPORT int OCSP_id_get0_info(ASN1_OCTET_STRING **nameHash, // OCSP_basic_add1_cert adds |cert| to the |resp|. OPENSSL_EXPORT int OCSP_basic_add1_cert(OCSP_BASICRESP *resp, X509 *cert); +// OCSP_basic_add1_status creates and returns an |OCSP_SINGLERESP| with |cid|, +// |status|, |this_update| and |next_update|. The newly created +// |OCSP_SINGLERESP| is pushed onto the internal |OCSP_SINGLERESP| stack in +// |resp|. |status| should be a value defined by |V_OCSP_CERTSTATUS_*|. +// +// 1. If |status| has the value |V_OCSP_CERTSTATUS_REVOKED|, |revoked_reason| +// should be a valid |OCSP_REVOKED_STATUS_*| value and |revoked_time| cannot be +// empty. +// 2. If |status| has the value of either |V_OCSP_CERTSTATUS_GOOD| or +// |V_OCSP_CERTSTATUS_UNKNOWN|, |revoked_reason| and |revoked_time| are ignored. +OPENSSL_EXPORT 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); + +// OCSP_response_create creates an |OCSP_RESPONSE| and encodes an optional |bs| +// within it. +OPENSSL_EXPORT OCSP_RESPONSE *OCSP_response_create(int status, + OCSP_BASICRESP *bs); + // OCSP_SINGLERESP_get0_id returns the |OCSP_CERTID| within |x|. OPENSSL_EXPORT const OCSP_CERTID *OCSP_SINGLERESP_get0_id( const OCSP_SINGLERESP *x); @@ -477,6 +523,7 @@ BSSL_NAMESPACE_END #define OCSP_R_NOT_BASIC_RESPONSE 104 #define OCSP_R_NO_CERTIFICATES_IN_CHAIN 105 #define OCSP_R_NO_RESPONSE_DATA 108 +#define OCSP_R_NO_REVOKED_TIME 109 #define OCSP_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 110 #define OCSP_R_RESPONSE_CONTAINS_NO_REVOCATION_DATA 111 #define OCSP_R_ROOT_CA_NOT_TRUSTED 112 @@ -494,5 +541,6 @@ BSSL_NAMESPACE_END #define OCSP_R_STATUS_TOO_OLD 127 #define OCSP_R_NO_SIGNER_KEY 130 #define OCSP_R_OCSP_REQUEST_DUPLICATE_SIGNATURE 131 +#define OCSP_R_UNKNOWN_FIELD_VALUE 132 #endif // AWSLC_OCSP_H