s2n_cert_validation_code s2n_x509_validator_validate_cert_stapled_ocsp_response()

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, &current_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 */
}