in tls/s2n_x509_validator.c [389:554]
s2n_cert_validation_code 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_CERT_OK;
}
S2N_ERROR_IF(validator->state != VALIDATED, S2N_ERR_INVALID_CERT_STATE);
#if !S2N_OCSP_STAPLING_SUPPORTED
/* Default to safety */
return S2N_CERT_ERR_UNTRUSTED;
#else
OCSP_RESPONSE *ocsp_response = NULL;
OCSP_BASICRESP *basic_response = NULL;
STACK_OF(X509) *cert_chain = NULL;
s2n_cert_validation_code ret_val = S2N_CERT_ERR_INVALID;
if (!ocsp_response_raw) {
return ret_val;
}
ocsp_response = d2i_OCSP_RESPONSE(NULL, &ocsp_response_raw, ocsp_response_length);
if (!ocsp_response) {
goto clean_up;
}
int ocsp_status = OCSP_response_status(ocsp_response);
if (ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
goto clean_up;
}
basic_response = OCSP_response_get1_basic(ocsp_response);
if (!basic_response) {
goto clean_up;
}
/* 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
*/
cert_chain = X509_STORE_CTX_get1_chain(validator->store_ctx);
if (!cert_chain) {
goto clean_up;
}
const int certs_in_chain = sk_X509_num(cert_chain);
if (!certs_in_chain) {
goto clean_up;
}
/* 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;
}
}
if (!issuer) {
goto clean_up;
}
/* 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);
/* OCSP_basic_verify() returns 1 on success, 0 on error, or -1 on fatal error such as malloc failure. */
if (ocsp_verify_res != _OSSL_SUCCESS) {
ret_val = ocsp_verify_res == 0 ? S2N_CERT_ERR_UNTRUSTED : S2N_CERT_ERR_INTERNAL_ERROR;
goto clean_up;
}
/* do the crypto checks on the response.*/
int status = 0;
int reason = 0;
/* sha1 is the only supported OCSP digest */
OCSP_CERTID *cert_id = OCSP_cert_to_id(EVP_sha1(), subject, issuer);
if (!cert_id) {
goto clean_up;
}
ASN1_GENERALIZEDTIME *revtime, *thisupd, *nextupd;
/* 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);
if (!ocsp_resp_find_status_res) {
ret_val = S2N_CERT_ERR_UNTRUSTED;
goto clean_up;
}
uint64_t this_update = 0;
s2n_result thisupd_result = s2n_asn1_time_to_nano_since_epoch_ticks((const char *) thisupd->data,
(uint32_t) thisupd->length, &this_update);
uint64_t next_update = 0;
s2n_result nextupd_result = S2N_RESULT_OK;
if (nextupd) {
nextupd_result = s2n_asn1_time_to_nano_since_epoch_ticks((const char *) nextupd->data,
(uint32_t) nextupd->length, &next_update);
} else {
next_update = this_update + DEFAULT_OCSP_NEXT_UPDATE_PERIOD;
}
uint64_t current_time = 0;
const int current_time_err = conn->config->wall_clock(conn->config->sys_clock_ctx, ¤t_time);
if (current_time_err) {
goto clean_up;
}
if (s2n_result_is_error(thisupd_result) || s2n_result_is_error(nextupd_result) || current_time_err) {
ret_val = S2N_CERT_ERR_UNTRUSTED;
goto clean_up;
}
if (current_time < this_update || current_time > next_update) {
ret_val = S2N_CERT_ERR_EXPIRED;
goto clean_up;
}
switch (status) {
case V_OCSP_CERTSTATUS_GOOD:
validator->state = OCSP_VALIDATED;
ret_val = S2N_CERT_OK;
break;
case V_OCSP_CERTSTATUS_REVOKED:
ret_val = S2N_CERT_ERR_REVOKED;
goto clean_up;
case V_OCSP_CERTSTATUS_UNKNOWN:
goto clean_up;
default:
goto clean_up;
}
clean_up:
if (basic_response) {
OCSP_BASICRESP_free(basic_response);
}
if (ocsp_response) {
OCSP_RESPONSE_free(ocsp_response);
}
if (cert_chain) {
wipe_cert_chain(cert_chain);
}
return ret_val;
#endif /* S2N_OCSP_STAPLING_SUPPORTED */
}