in tls/s2n_x509_validator.c [843:984]
S2N_RESULT s2n_x509_validator_validate_cert_stapled_ocsp_response(struct s2n_x509_validator *validator,
struct s2n_connection *conn, const uint8_t *ocsp_response_raw, uint32_t ocsp_response_length)
{
if (validator->skip_cert_validation || !validator->check_stapled_ocsp) {
validator->state = OCSP_VALIDATED;
return S2N_RESULT_OK;
}
RESULT_ENSURE(validator->state == VALIDATED, S2N_ERR_INVALID_CERT_STATE);
#if !S2N_OCSP_STAPLING_SUPPORTED
/* Default to safety */
RESULT_BAIL(S2N_ERR_CERT_UNTRUSTED);
#else
RESULT_ENSURE_REF(ocsp_response_raw);
DEFER_CLEANUP(OCSP_RESPONSE *ocsp_response = d2i_OCSP_RESPONSE(NULL, &ocsp_response_raw, ocsp_response_length),
OCSP_RESPONSE_free_pointer);
RESULT_ENSURE(ocsp_response != NULL, S2N_ERR_INVALID_OCSP_RESPONSE);
int ocsp_status = OCSP_response_status(ocsp_response);
RESULT_ENSURE(ocsp_status == OCSP_RESPONSE_STATUS_SUCCESSFUL, S2N_ERR_CERT_UNTRUSTED);
DEFER_CLEANUP(OCSP_BASICRESP *basic_response = OCSP_response_get1_basic(ocsp_response), OCSP_BASICRESP_free_pointer);
RESULT_ENSURE(basic_response != NULL, S2N_ERR_INVALID_OCSP_RESPONSE);
/* X509_STORE_CTX_get0_chain() is better because it doesn't return a copy. But it's not available for Openssl 1.0.2.
* Therefore, we call this variant and clean it up at the end of the function.
* See the comments here:
* https://www.openssl.org/docs/man1.0.2/man3/X509_STORE_CTX_get1_chain.html
*/
DEFER_CLEANUP(STACK_OF(X509) *cert_chain = X509_STORE_CTX_get1_chain(validator->store_ctx),
s2n_openssl_x509_stack_pop_free);
RESULT_ENSURE_REF(cert_chain);
const int certs_in_chain = sk_X509_num(cert_chain);
RESULT_ENSURE(certs_in_chain > 0, S2N_ERR_NO_CERT_FOUND);
/* leaf is the top: not the bottom. */
X509 *subject = sk_X509_value(cert_chain, 0);
X509 *issuer = NULL;
/* find the issuer in the chain. If it's not there. Fail everything. */
for (int i = 0; i < certs_in_chain; ++i) {
X509 *issuer_candidate = sk_X509_value(cert_chain, i);
const int issuer_value = X509_check_issued(issuer_candidate, subject);
if (issuer_value == X509_V_OK) {
issuer = issuer_candidate;
break;
}
}
RESULT_ENSURE(issuer != NULL, S2N_ERR_CERT_UNTRUSTED);
/* Important: this checks that the stapled ocsp response CAN be verified, not that it has been verified. */
const int ocsp_verify_res = OCSP_basic_verify(basic_response, cert_chain, validator->trust_store->trust_store, 0);
RESULT_GUARD_OSSL(ocsp_verify_res, S2N_ERR_CERT_UNTRUSTED);
/* do the crypto checks on the response.*/
int status = 0;
int reason = 0;
/* SHA-1 is the only supported hash algorithm for the CertID due to its established use in
* OCSP responders.
*/
OCSP_CERTID *cert_id = OCSP_cert_to_id(EVP_sha1(), subject, issuer);
RESULT_ENSURE_REF(cert_id);
/**
*= https://www.rfc-editor.org/rfc/rfc6960#section-2.4
*#
*# thisUpdate The most recent time at which the status being
*# indicated is known by the responder to have been
*# correct.
*#
*# nextUpdate The time at or before which newer information will be
*# available about the status of the certificate.
**/
ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL;
/* Actual verification of the response */
const int ocsp_resp_find_status_res = OCSP_resp_find_status(basic_response, cert_id, &status, &reason, &revtime, &thisupd, &nextupd);
OCSP_CERTID_free(cert_id);
RESULT_GUARD_OSSL(ocsp_resp_find_status_res, S2N_ERR_CERT_UNTRUSTED);
uint64_t current_sys_time_nanoseconds = 0;
RESULT_GUARD(s2n_config_wall_clock(conn->config, ¤t_sys_time_nanoseconds));
if (sizeof(time_t) == 4) {
/* cast value to uint64_t to prevent overflow errors */
RESULT_ENSURE_LTE(current_sys_time_nanoseconds, (uint64_t) MAX_32_TIMESTAMP_NANOS);
}
/* convert the current_sys_time (which is in nanoseconds) to seconds */
time_t current_sys_time_seconds = (time_t) (current_sys_time_nanoseconds / ONE_SEC_IN_NANOS);
DEFER_CLEANUP(ASN1_GENERALIZEDTIME *current_sys_time = ASN1_GENERALIZEDTIME_set(NULL, current_sys_time_seconds), s2n_openssl_asn1_time_free_pointer);
RESULT_ENSURE_REF(current_sys_time);
/**
* It is fine to use ASN1_TIME functions with ASN1_GENERALIZEDTIME structures
* From openssl documentation:
* It is recommended that functions starting with ASN1_TIME be used instead
* of those starting with ASN1_UTCTIME or ASN1_GENERALIZEDTIME. The
* functions starting with ASN1_UTCTIME and ASN1_GENERALIZEDTIME act only on
* that specific time format. The functions starting with ASN1_TIME will
* operate on either format.
* https://www.openssl.org/docs/man1.1.1/man3/ASN1_TIME_to_generalizedtime.html
*
* ASN1_TIME_compare has a much nicer API, but is not available in Openssl
* 1.0.1, so we use ASN1_TIME_diff.
*/
int pday = 0;
int psec = 0;
RESULT_GUARD_OSSL(ASN1_TIME_diff(&pday, &psec, thisupd, current_sys_time), S2N_ERR_CERT_UNTRUSTED);
/* ensure that current_time is after or the same as "this update" */
RESULT_ENSURE(pday >= 0 && psec >= 0, S2N_ERR_CERT_INVALID);
/* ensure that current_time is before or the same as "next update" */
if (nextupd) {
RESULT_GUARD_OSSL(ASN1_TIME_diff(&pday, &psec, current_sys_time, nextupd), S2N_ERR_CERT_UNTRUSTED);
RESULT_ENSURE(pday >= 0 && psec >= 0, S2N_ERR_CERT_EXPIRED);
} else {
/**
* if nextupd isn't present, assume that nextupd is
* DEFAULT_OCSP_NEXT_UPDATE_PERIOD after thisupd. This means that if the
* current time is more than DEFAULT_OCSP_NEXT_UPDATE_PERIOD
* seconds ahead of thisupd, we consider it invalid. We already compared
* current_sys_time to thisupd, so reuse those values
*/
uint64_t seconds_after_thisupd = pday * (3600 * 24) + psec;
RESULT_ENSURE(seconds_after_thisupd < DEFAULT_OCSP_NEXT_UPDATE_PERIOD, S2N_ERR_CERT_EXPIRED);
}
switch (status) {
case V_OCSP_CERTSTATUS_GOOD:
validator->state = OCSP_VALIDATED;
return S2N_RESULT_OK;
case V_OCSP_CERTSTATUS_REVOKED:
RESULT_BAIL(S2N_ERR_CERT_REVOKED);
default:
RESULT_BAIL(S2N_ERR_CERT_UNTRUSTED);
}
#endif /* S2N_OCSP_STAPLING_SUPPORTED */
}