diff --git a/hostapd/Android.mk b/hostapd/Android.mk index 867062420..cead73155 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -555,6 +555,7 @@ endif ifeq ($(CONFIG_TLS), openssl) ifdef TLS_FUNCS OBJS += src/crypto/tls_openssl.c +OBJS += src/crypto/tls_openssl_ocsp.c LIBS += -lssl endif OBJS += src/crypto/crypto_openssl.c diff --git a/hostapd/Makefile b/hostapd/Makefile index e5549f38e..fa5435d97 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -544,6 +544,7 @@ endif ifeq ($(CONFIG_TLS), openssl) ifdef TLS_FUNCS OBJS += ../src/crypto/tls_openssl.o +OBJS += ../src/crypto/tls_openssl_ocsp.o LIBS += -lssl endif OBJS += ../src/crypto/crypto_openssl.o diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index 030e1e2fb..471ae2b7c 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -30,16 +30,12 @@ #include #endif -#ifdef OPENSSL_IS_BORINGSSL -#include -#include -#endif /* OPENSSL_IS_BORINGSSL */ - #include "common.h" #include "crypto.h" #include "sha1.h" #include "sha256.h" #include "tls.h" +#include "tls_openssl.h" #if OPENSSL_VERSION_NUMBER < 0x10000000L /* ERR_remove_thread_state replaces ERR_remove_state and the latter is @@ -1654,819 +1650,6 @@ static void openssl_tls_cert_event(struct tls_connection *conn, } -#ifdef OPENSSL_IS_BORINGSSL - -/* - * CertID ::= SEQUENCE { - * hashAlgorithm AlgorithmIdentifier, - * issuerNameHash OCTET STRING, -- Hash of Issuer's DN - * issuerKeyHash OCTET STRING, -- Hash of Issuer's public key - * serialNumber CertificateSerialNumber } - */ -typedef struct { - X509_ALGOR *hashAlgorithm; - ASN1_OCTET_STRING *issuerNameHash; - ASN1_OCTET_STRING *issuerKeyHash; - ASN1_INTEGER *serialNumber; -} CertID; - -/* - * ResponseBytes ::= SEQUENCE { - * responseType OBJECT IDENTIFIER, - * response OCTET STRING } - */ -typedef struct { - ASN1_OBJECT *responseType; - ASN1_OCTET_STRING *response; -} ResponseBytes; - -/* - * OCSPResponse ::= SEQUENCE { - * responseStatus OCSPResponseStatus, - * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } - */ -typedef struct { - ASN1_ENUMERATED *responseStatus; - ResponseBytes *responseBytes; -} OCSPResponse; - -ASN1_SEQUENCE(ResponseBytes) = { - ASN1_SIMPLE(ResponseBytes, responseType, ASN1_OBJECT), - ASN1_SIMPLE(ResponseBytes, response, ASN1_OCTET_STRING) -} ASN1_SEQUENCE_END(ResponseBytes); - -ASN1_SEQUENCE(OCSPResponse) = { - ASN1_SIMPLE(OCSPResponse, responseStatus, ASN1_ENUMERATED), - ASN1_EXP_OPT(OCSPResponse, responseBytes, ResponseBytes, 0) -} ASN1_SEQUENCE_END(OCSPResponse); - -IMPLEMENT_ASN1_FUNCTIONS(OCSPResponse); - -/* - * ResponderID ::= CHOICE { - * byName [1] Name, - * byKey [2] KeyHash } - */ -typedef struct { - int type; - union { - X509_NAME *byName; - ASN1_OCTET_STRING *byKey; - } value; -} ResponderID; - -/* - * RevokedInfo ::= SEQUENCE { - * revocationTime GeneralizedTime, - * revocationReason [0] EXPLICIT CRLReason OPTIONAL } - */ -typedef struct { - ASN1_GENERALIZEDTIME *revocationTime; - ASN1_ENUMERATED *revocationReason; -} RevokedInfo; - -/* - * CertStatus ::= CHOICE { - * good [0] IMPLICIT NULL, - * revoked [1] IMPLICIT RevokedInfo, - * unknown [2] IMPLICIT UnknownInfo } - */ -typedef struct { - int type; - union { - ASN1_NULL *good; - RevokedInfo *revoked; - ASN1_NULL *unknown; - } value; -} CertStatus; - -/* - * SingleResponse ::= SEQUENCE { - * certID CertID, - * certStatus CertStatus, - * thisUpdate GeneralizedTime, - * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, - * singleExtensions [1] EXPLICIT Extensions OPTIONAL } - */ -typedef struct { - CertID *certID; - CertStatus *certStatus; - ASN1_GENERALIZEDTIME *thisUpdate; - ASN1_GENERALIZEDTIME *nextUpdate; - STACK_OF(X509_EXTENSION) *singleExtensions; -} SingleResponse; - -/* - * ResponseData ::= SEQUENCE { - * version [0] EXPLICIT Version DEFAULT v1, - * responderID ResponderID, - * producedAt GeneralizedTime, - * responses SEQUENCE OF SingleResponse, - * responseExtensions [1] EXPLICIT Extensions OPTIONAL } - */ -typedef struct { - ASN1_INTEGER *version; - ResponderID *responderID; - ASN1_GENERALIZEDTIME *producedAt; - STACK_OF(SingleResponse) *responses; - STACK_OF(X509_EXTENSION) *responseExtensions; -} ResponseData; - -/* - * BasicOCSPResponse ::= SEQUENCE { - * tbsResponseData ResponseData, - * signatureAlgorithm AlgorithmIdentifier, - * signature BIT STRING, - * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } - */ -typedef struct { - ResponseData *tbsResponseData; - X509_ALGOR *signatureAlgorithm; - ASN1_BIT_STRING *signature; - STACK_OF(X509) *certs; -} BasicOCSPResponse; - -ASN1_SEQUENCE(CertID) = { - ASN1_SIMPLE(CertID, hashAlgorithm, X509_ALGOR), - ASN1_SIMPLE(CertID, issuerNameHash, ASN1_OCTET_STRING), - ASN1_SIMPLE(CertID, issuerKeyHash, ASN1_OCTET_STRING), - ASN1_SIMPLE(CertID, serialNumber, ASN1_INTEGER) -} ASN1_SEQUENCE_END(CertID); - -ASN1_CHOICE(ResponderID) = { - ASN1_EXP(ResponderID, value.byName, X509_NAME, 1), - ASN1_EXP(ResponderID, value.byKey, ASN1_OCTET_STRING, 2) -} ASN1_CHOICE_END(ResponderID); - -ASN1_SEQUENCE(RevokedInfo) = { - ASN1_SIMPLE(RevokedInfo, revocationTime, ASN1_GENERALIZEDTIME), - ASN1_EXP_OPT(RevokedInfo, revocationReason, ASN1_ENUMERATED, 0) -} ASN1_SEQUENCE_END(RevokedInfo); - -ASN1_CHOICE(CertStatus) = { - ASN1_IMP(CertStatus, value.good, ASN1_NULL, 0), - ASN1_IMP(CertStatus, value.revoked, RevokedInfo, 1), - ASN1_IMP(CertStatus, value.unknown, ASN1_NULL, 2) -} ASN1_CHOICE_END(CertStatus); - -ASN1_SEQUENCE(SingleResponse) = { - ASN1_SIMPLE(SingleResponse, certID, CertID), - ASN1_SIMPLE(SingleResponse, certStatus, CertStatus), - ASN1_SIMPLE(SingleResponse, thisUpdate, ASN1_GENERALIZEDTIME), - ASN1_EXP_OPT(SingleResponse, nextUpdate, ASN1_GENERALIZEDTIME, 0), - ASN1_EXP_SEQUENCE_OF_OPT(SingleResponse, singleExtensions, - X509_EXTENSION, 1) -} ASN1_SEQUENCE_END(SingleResponse); - -ASN1_SEQUENCE(ResponseData) = { - ASN1_EXP_OPT(ResponseData, version, ASN1_INTEGER, 0), - ASN1_SIMPLE(ResponseData, responderID, ResponderID), - ASN1_SIMPLE(ResponseData, producedAt, ASN1_GENERALIZEDTIME), - ASN1_SEQUENCE_OF(ResponseData, responses, SingleResponse), - ASN1_EXP_SEQUENCE_OF_OPT(ResponseData, responseExtensions, - X509_EXTENSION, 1) -} ASN1_SEQUENCE_END(ResponseData); - -ASN1_SEQUENCE(BasicOCSPResponse) = { - ASN1_SIMPLE(BasicOCSPResponse, tbsResponseData, ResponseData), - ASN1_SIMPLE(BasicOCSPResponse, signatureAlgorithm, X509_ALGOR), - ASN1_SIMPLE(BasicOCSPResponse, signature, ASN1_BIT_STRING), - ASN1_EXP_SEQUENCE_OF_OPT(BasicOCSPResponse, certs, X509, 0) -} ASN1_SEQUENCE_END(BasicOCSPResponse); - -IMPLEMENT_ASN1_FUNCTIONS(BasicOCSPResponse); - -#define sk_SingleResponse_num(sk) \ -sk_num(CHECKED_CAST(_STACK *, STACK_OF(SingleResponse) *, sk)) - -#define sk_SingleResponse_value(sk, i) \ - ((SingleResponse *) \ - sk_value(CHECKED_CAST(_STACK *, STACK_OF(SingleResponse) *, sk), (i))) - - -static char * mem_bio_to_str(BIO *out) -{ - char *txt; - size_t rlen; - int res; - - rlen = BIO_ctrl_pending(out); - txt = os_malloc(rlen + 1); - if (!txt) { - BIO_free(out); - return NULL; - } - - res = BIO_read(out, txt, rlen); - BIO_free(out); - if (res < 0) { - os_free(txt); - return NULL; - } - - txt[res] = '\0'; - return txt; -} - - -static char * generalizedtime_str(ASN1_GENERALIZEDTIME *t) -{ - BIO *out; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - if (!ASN1_GENERALIZEDTIME_print(out, t)) { - BIO_free(out); - return NULL; - } - - return mem_bio_to_str(out); -} - - -static char * responderid_str(ResponderID *rid) -{ - BIO *out; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - switch (rid->type) { - case 0: - X509_NAME_print_ex(out, rid->value.byName, 0, XN_FLAG_ONELINE); - break; - case 1: - i2a_ASN1_STRING(out, rid->value.byKey, V_ASN1_OCTET_STRING); - break; - default: - BIO_free(out); - return NULL; - } - - return mem_bio_to_str(out); -} - - -static char * octet_string_str(ASN1_OCTET_STRING *o) -{ - BIO *out; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - i2a_ASN1_STRING(out, o, V_ASN1_OCTET_STRING); - return mem_bio_to_str(out); -} - - -static char * integer_str(ASN1_INTEGER *i) -{ - BIO *out; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - i2a_ASN1_INTEGER(out, i); - return mem_bio_to_str(out); -} - - -static char * algor_str(X509_ALGOR *alg) -{ - BIO *out; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - i2a_ASN1_OBJECT(out, alg->algorithm); - return mem_bio_to_str(out); -} - - -static char * extensions_str(const char *title, STACK_OF(X509_EXTENSION) *ext) -{ - BIO *out; - - if (!ext) - return NULL; - - out = BIO_new(BIO_s_mem()); - if (!out) - return NULL; - - if (!X509V3_extensions_print(out, title, ext, 0, 0)) { - BIO_free(out); - return NULL; - } - return mem_bio_to_str(out); -} - - -static int ocsp_resp_valid(ASN1_GENERALIZEDTIME *thisupd, - ASN1_GENERALIZEDTIME *nextupd) -{ - time_t now, tmp; - - if (!ASN1_GENERALIZEDTIME_check(thisupd)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Invalid OCSP response thisUpdate"); - return 0; - } - - time(&now); - tmp = now + 5 * 60; /* allow five minute clock difference */ - if (X509_cmp_time(thisupd, &tmp) > 0) { - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response not yet valid"); - return 0; - } - - if (!nextupd) - return 1; /* OK - no limit on response age */ - - if (!ASN1_GENERALIZEDTIME_check(nextupd)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Invalid OCSP response nextUpdate"); - return 0; - } - - tmp = now - 5 * 60; /* allow five minute clock difference */ - if (X509_cmp_time(nextupd, &tmp) < 0) { - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response expired"); - return 0; - } - - if (ASN1_STRING_cmp(nextupd, thisupd) < 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP response nextUpdate before thisUpdate"); - return 0; - } - - /* Both thisUpdate and nextUpdate are valid */ - return -1; -} - - -static int issuer_match(X509 *cert, X509 *issuer, CertID *certid) -{ - X509_NAME *iname; - ASN1_BIT_STRING *ikey; - const EVP_MD *dgst; - unsigned int len; - unsigned char md[EVP_MAX_MD_SIZE]; - ASN1_OCTET_STRING *hash; - char *txt; - - dgst = EVP_get_digestbyobj(certid->hashAlgorithm->algorithm); - if (!dgst) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not find matching hash algorithm for OCSP"); - return -1; - } - - iname = X509_get_issuer_name(cert); - if (!X509_NAME_digest(iname, dgst, md, &len)) - return -1; - hash = ASN1_OCTET_STRING_new(); - if (!hash) - return -1; - if (!ASN1_OCTET_STRING_set(hash, md, len)) { - ASN1_OCTET_STRING_free(hash); - return -1; - } - - txt = octet_string_str(hash); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: calculated issuerNameHash: %s", - txt); - os_free(txt); - } - - if (ASN1_OCTET_STRING_cmp(certid->issuerNameHash, hash)) { - ASN1_OCTET_STRING_free(hash); - return -1; - } - - ikey = X509_get0_pubkey_bitstr(issuer); - if (!EVP_Digest(ikey->data, ikey->length, md, &len, dgst, NULL) || - !ASN1_OCTET_STRING_set(hash, md, len)) { - ASN1_OCTET_STRING_free(hash); - return -1; - } - - txt = octet_string_str(hash); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: calculated issuerKeyHash: %s", - txt); - os_free(txt); - } - - if (ASN1_OCTET_STRING_cmp(certid->issuerKeyHash, hash)) { - ASN1_OCTET_STRING_free(hash); - return -1; - } - - ASN1_OCTET_STRING_free(hash); - return 0; -} - - -static X509 * ocsp_find_signer(STACK_OF(X509) *certs, ResponderID *rid) -{ - unsigned int i; - unsigned char hash[SHA_DIGEST_LENGTH]; - - if (rid->type == 0) { - /* byName */ - return X509_find_by_subject(certs, rid->value.byName); - } - - /* byKey */ - if (rid->value.byKey->length != SHA_DIGEST_LENGTH) - return NULL; - for (i = 0; i < sk_X509_num(certs); i++) { - X509 *x = sk_X509_value(certs, i); - - X509_pubkey_digest(x, EVP_sha1(), hash, NULL); - if (os_memcmp(rid->value.byKey->data, hash, - SHA_DIGEST_LENGTH) == 0) - return x; - } - - return NULL; -} - - -enum ocsp_result { - OCSP_GOOD, OCSP_REVOKED, OCSP_NO_RESPONSE, OCSP_INVALID -}; - -static enum ocsp_result check_ocsp_resp(struct tls_connection *conn, - X509 *cert, X509 *issuer) -{ - const uint8_t *resp_data; - size_t resp_len; - OCSPResponse *resp; - int status; - ResponseBytes *bytes; - const u8 *basic_data; - size_t basic_len; - BasicOCSPResponse *basic; - ResponseData *rd; - char *txt; - int i, num; - unsigned int j, num_resp; - SingleResponse *matching_resp = NULL, *cmp_sresp; - enum ocsp_result result = OCSP_INVALID; - X509_STORE *store; - STACK_OF(X509) *untrusted = NULL, *certs = NULL, *chain = NULL; - X509_STORE_CTX ctx; - X509 *signer, *tmp_cert; - int signer_trusted = 0; - EVP_PKEY *skey; - int ret; - char buf[256]; - - txt = integer_str(X509_get_serialNumber(cert)); - if (txt) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Searching OCSP response for peer certificate serialNumber: %s", txt); - os_free(txt); - } - - SSL_get0_ocsp_response(conn->ssl, &resp_data, &resp_len); - if (resp_data == NULL || resp_len == 0) { - wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); - return OCSP_NO_RESPONSE; - } - - wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", resp_data, resp_len); - - resp = d2i_OCSPResponse(NULL, &resp_data, resp_len); - if (!resp) { - wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSPResponse"); - return OCSP_INVALID; - } - - status = ASN1_ENUMERATED_get(resp->responseStatus); - if (status != 0) { - wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d", - status); - return OCSP_INVALID; - } - - bytes = resp->responseBytes; - - if (!bytes || - OBJ_obj2nid(bytes->responseType) != NID_id_pkix_OCSP_basic) { - wpa_printf(MSG_INFO, - "OpenSSL: Could not find BasicOCSPResponse"); - return OCSP_INVALID; - } - - basic_data = ASN1_STRING_data(bytes->response); - basic_len = ASN1_STRING_length(bytes->response); - wpa_hexdump(MSG_DEBUG, "OpenSSL: BasicOCSPResponse", - basic_data, basic_len); - - basic = d2i_BasicOCSPResponse(NULL, &basic_data, basic_len); - if (!basic) { - wpa_printf(MSG_INFO, - "OpenSSL: Could not parse BasicOCSPResponse"); - OCSPResponse_free(resp); - return OCSP_INVALID; - } - - rd = basic->tbsResponseData; - - if (basic->certs) { - untrusted = sk_X509_dup(basic->certs); - - num = sk_X509_num(basic->certs); - for (i = 0; i < num; i++) { - X509 *extra_cert; - - extra_cert = sk_X509_value(basic->certs, i); - X509_NAME_oneline(X509_get_subject_name(extra_cert), - buf, sizeof(buf)); - wpa_printf(MSG_DEBUG, - "OpenSSL: BasicOCSPResponse cert %s", buf); - - if (!sk_X509_push(untrusted, extra_cert)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not add certificate to the untrusted stack"); - } - } - } - - store = SSL_CTX_get_cert_store(conn->ssl_ctx); - if (conn->peer_issuer) { - if (X509_STORE_add_cert(store, conn->peer_issuer) != 1) { - tls_show_errors(MSG_INFO, __func__, - "OpenSSL: Could not add issuer to certificate store"); - } - certs = sk_X509_new_null(); - if (certs) { - tmp_cert = X509_dup(conn->peer_issuer); - if (tmp_cert && !sk_X509_push(certs, tmp_cert)) { - tls_show_errors( - MSG_INFO, __func__, - "OpenSSL: Could not add issuer to OCSP responder trust store"); - X509_free(tmp_cert); - sk_X509_free(certs); - certs = NULL; - } - if (certs && conn->peer_issuer_issuer) { - tmp_cert = X509_dup(conn->peer_issuer_issuer); - if (tmp_cert && - !sk_X509_push(certs, tmp_cert)) { - tls_show_errors( - MSG_INFO, __func__, - "OpenSSL: Could not add issuer's issuer to OCSP responder trust store"); - X509_free(tmp_cert); - } - } - } - } - - signer = ocsp_find_signer(certs, rd->responderID); - if (!signer) - signer = ocsp_find_signer(untrusted, rd->responderID); - else - signer_trusted = 1; - if (!signer) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not find OCSP signer certificate"); - goto fail; - } - - skey = X509_get_pubkey(signer); - if (!skey) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not get OCSP signer public key"); - goto fail; - } - if (ASN1_item_verify(ASN1_ITEM_rptr(ResponseData), - basic->signatureAlgorithm, basic->signature, - basic->tbsResponseData, skey) <= 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: BasicOCSPResponse signature is invalid"); - goto fail; - } - - X509_NAME_oneline(X509_get_subject_name(signer), buf, sizeof(buf)); - wpa_printf(MSG_DEBUG, - "OpenSSL: Found OCSP signer certificate %s and verified BasicOCSPResponse signature", - buf); - - if (!X509_STORE_CTX_init(&ctx, store, signer, untrusted)) - goto fail; - X509_STORE_CTX_set_purpose(&ctx, X509_PURPOSE_OCSP_HELPER); - ret = X509_verify_cert(&ctx); - chain = X509_STORE_CTX_get1_chain(&ctx); - X509_STORE_CTX_cleanup(&ctx); - if (ret <= 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not validate OCSP signer certificate"); - goto fail; - } - - if (!chain || sk_X509_num(chain) <= 0) { - wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP signer chain found"); - goto fail; - } - - if (!signer_trusted) { - X509_check_purpose(signer, -1, 0); - if ((signer->ex_flags & EXFLAG_XKUSAGE) && - (signer->ex_xkusage & XKU_OCSP_SIGN)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP signer certificate delegation OK"); - } else { - tmp_cert = sk_X509_value(chain, sk_X509_num(chain) - 1); - if (X509_check_trust(tmp_cert, NID_OCSP_sign, 0) != - X509_TRUST_TRUSTED) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP signer certificate not trusted"); - result = OCSP_NO_RESPONSE; - goto fail; - } - } - } - - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP version: %lu", - ASN1_INTEGER_get(rd->version)); - - txt = responderid_str(rd->responderID); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP responderID: %s", - txt); - os_free(txt); - } - - txt = generalizedtime_str(rd->producedAt); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP producedAt: %s", - txt); - os_free(txt); - } - - num_resp = sk_SingleResponse_num(rd->responses); - if (num_resp == 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: No OCSP SingleResponse within BasicOCSPResponse"); - result = OCSP_NO_RESPONSE; - goto fail; - } - cmp_sresp = sk_SingleResponse_value(rd->responses, 0); - for (j = 0; j < num_resp; j++) { - SingleResponse *sresp; - CertID *cid1, *cid2; - - sresp = sk_SingleResponse_value(rd->responses, j); - wpa_printf(MSG_DEBUG, "OpenSSL: OCSP SingleResponse %u/%u", - j + 1, num_resp); - - txt = algor_str(sresp->certID->hashAlgorithm); - if (txt) { - wpa_printf(MSG_DEBUG, - "OpenSSL: certID hashAlgorithm: %s", txt); - os_free(txt); - } - - txt = octet_string_str(sresp->certID->issuerNameHash); - if (txt) { - wpa_printf(MSG_DEBUG, - "OpenSSL: certID issuerNameHash: %s", txt); - os_free(txt); - } - - txt = octet_string_str(sresp->certID->issuerKeyHash); - if (txt) { - wpa_printf(MSG_DEBUG, - "OpenSSL: certID issuerKeyHash: %s", txt); - os_free(txt); - } - - txt = integer_str(sresp->certID->serialNumber); - if (txt) { - wpa_printf(MSG_DEBUG, - "OpenSSL: certID serialNumber: %s", txt); - os_free(txt); - } - - switch (sresp->certStatus->type) { - case 0: - wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: good"); - break; - case 1: - wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: revoked"); - break; - default: - wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: unknown"); - break; - } - - txt = generalizedtime_str(sresp->thisUpdate); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: thisUpdate: %s", txt); - os_free(txt); - } - - if (sresp->nextUpdate) { - txt = generalizedtime_str(sresp->nextUpdate); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: nextUpdate: %s", - txt); - os_free(txt); - } - } - - txt = extensions_str("singleExtensions", - sresp->singleExtensions); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: %s", txt); - os_free(txt); - } - - cid1 = cmp_sresp->certID; - cid2 = sresp->certID; - if (j > 0 && - (OBJ_cmp(cid1->hashAlgorithm->algorithm, - cid2->hashAlgorithm->algorithm) != 0 || - ASN1_OCTET_STRING_cmp(cid1->issuerNameHash, - cid2->issuerNameHash) != 0 || - ASN1_OCTET_STRING_cmp(cid1->issuerKeyHash, - cid2->issuerKeyHash) != 0)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Different OCSP response issuer information between SingleResponse values within BasicOCSPResponse"); - goto fail; - } - - if (!matching_resp && issuer && - ASN1_INTEGER_cmp(sresp->certID->serialNumber, - X509_get_serialNumber(cert)) == 0 && - issuer_match(cert, issuer, sresp->certID) == 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: This response matches peer certificate"); - matching_resp = sresp; - } - } - - txt = extensions_str("responseExtensions", rd->responseExtensions); - if (txt) { - wpa_printf(MSG_DEBUG, "OpenSSL: %s", txt); - os_free(txt); - } - - if (!matching_resp) { - wpa_printf(MSG_DEBUG, - "OpenSSL: Could not find OCSP response that matches the peer certificate"); - result = OCSP_NO_RESPONSE; - goto fail; - } - - if (!ocsp_resp_valid(matching_resp->thisUpdate, - matching_resp->nextUpdate)) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP response not valid at this time"); - goto fail; - } - - if (matching_resp->certStatus->type == 1) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP response indicated that the peer certificate has been revoked"); - result = OCSP_REVOKED; - goto fail; - } - - if (matching_resp->certStatus->type != 0) { - wpa_printf(MSG_DEBUG, - "OpenSSL: OCSP response did not indicate good status"); - result = OCSP_NO_RESPONSE; - goto fail; - } - - /* OCSP response indicated the certificate is good. */ - result = OCSP_GOOD; -fail: - sk_X509_pop_free(chain, X509_free); - sk_X509_free(untrusted); - sk_X509_pop_free(certs, X509_free); - BasicOCSPResponse_free(basic); - OCSPResponse_free(resp); - - return result; -} - -#endif /* OPENSSL_IS_BORINGSSL */ - - static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) { char buf[256]; @@ -2613,10 +1796,13 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) } #ifdef OPENSSL_IS_BORINGSSL - if (depth == 0 && (conn->flags & TLS_CONN_REQUEST_OCSP)) { + if (depth == 0 && (conn->flags & TLS_CONN_REQUEST_OCSP) && + preverify_ok) { enum ocsp_result res; - res = check_ocsp_resp(conn, err_cert, conn->peer_issuer); + res = check_ocsp_resp(conn->ssl_ctx, conn->ssl, err_cert, + conn->peer_issuer, + conn->peer_issuer_issuer); if (res == OCSP_REVOKED) { preverify_ok = 0; openssl_tls_fail_event(conn, err_cert, err, depth, buf, diff --git a/src/crypto/tls_openssl.h b/src/crypto/tls_openssl.h new file mode 100644 index 000000000..2a62d5c5d --- /dev/null +++ b/src/crypto/tls_openssl.h @@ -0,0 +1,19 @@ +/* + * SSL/TLS interface functions for OpenSSL + * Copyright (c) 2004-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef TLS_OPENSSL_H +#define TLS_OPENSSL_H + +enum ocsp_result { + OCSP_GOOD, OCSP_REVOKED, OCSP_NO_RESPONSE, OCSP_INVALID +}; + +enum ocsp_result check_ocsp_resp(SSL_CTX *ssl_ctx, SSL *ssl, X509 *cert, + X509 *issuer, X509 *issuer_issuer); + +#endif /* TLS_OPENSSL_H */ diff --git a/src/crypto/tls_openssl_ocsp.c b/src/crypto/tls_openssl_ocsp.c new file mode 100644 index 000000000..37c87f409 --- /dev/null +++ b/src/crypto/tls_openssl_ocsp.c @@ -0,0 +1,843 @@ +/* + * SSL/TLS interface functions for OpenSSL - BoringSSL OCSP + * Copyright (c) 2004-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include +#include +#include +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#endif /* OPENSSL_IS_BORINGSSL */ + +#include "common.h" +#include "tls_openssl.h" + + +#ifdef OPENSSL_IS_BORINGSSL + +static void tls_show_errors(int level, const char *func, const char *txt) +{ + unsigned long err; + + wpa_printf(level, "OpenSSL: %s - %s %s", + func, txt, ERR_error_string(ERR_get_error(), NULL)); + + while ((err = ERR_get_error())) { + wpa_printf(MSG_INFO, "OpenSSL: pending error: %s", + ERR_error_string(err, NULL)); + } +} + + +/* + * CertID ::= SEQUENCE { + * hashAlgorithm AlgorithmIdentifier, + * issuerNameHash OCTET STRING, -- Hash of Issuer's DN + * issuerKeyHash OCTET STRING, -- Hash of Issuer's public key + * serialNumber CertificateSerialNumber } + */ +typedef struct { + X509_ALGOR *hashAlgorithm; + ASN1_OCTET_STRING *issuerNameHash; + ASN1_OCTET_STRING *issuerKeyHash; + ASN1_INTEGER *serialNumber; +} CertID; + +/* + * ResponseBytes ::= SEQUENCE { + * responseType OBJECT IDENTIFIER, + * response OCTET STRING } + */ +typedef struct { + ASN1_OBJECT *responseType; + ASN1_OCTET_STRING *response; +} ResponseBytes; + +/* + * OCSPResponse ::= SEQUENCE { + * responseStatus OCSPResponseStatus, + * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + */ +typedef struct { + ASN1_ENUMERATED *responseStatus; + ResponseBytes *responseBytes; +} OCSPResponse; + +ASN1_SEQUENCE(ResponseBytes) = { + ASN1_SIMPLE(ResponseBytes, responseType, ASN1_OBJECT), + ASN1_SIMPLE(ResponseBytes, response, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(ResponseBytes); + +ASN1_SEQUENCE(OCSPResponse) = { + ASN1_SIMPLE(OCSPResponse, responseStatus, ASN1_ENUMERATED), + ASN1_EXP_OPT(OCSPResponse, responseBytes, ResponseBytes, 0) +} ASN1_SEQUENCE_END(OCSPResponse); + +IMPLEMENT_ASN1_FUNCTIONS(OCSPResponse); + +/* + * ResponderID ::= CHOICE { + * byName [1] Name, + * byKey [2] KeyHash } + */ +typedef struct { + int type; + union { + X509_NAME *byName; + ASN1_OCTET_STRING *byKey; + } value; +} ResponderID; + +/* + * RevokedInfo ::= SEQUENCE { + * revocationTime GeneralizedTime, + * revocationReason [0] EXPLICIT CRLReason OPTIONAL } + */ +typedef struct { + ASN1_GENERALIZEDTIME *revocationTime; + ASN1_ENUMERATED *revocationReason; +} RevokedInfo; + +/* + * CertStatus ::= CHOICE { + * good [0] IMPLICIT NULL, + * revoked [1] IMPLICIT RevokedInfo, + * unknown [2] IMPLICIT UnknownInfo } + */ +typedef struct { + int type; + union { + ASN1_NULL *good; + RevokedInfo *revoked; + ASN1_NULL *unknown; + } value; +} CertStatus; + +/* + * SingleResponse ::= SEQUENCE { + * certID CertID, + * certStatus CertStatus, + * thisUpdate GeneralizedTime, + * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, + * singleExtensions [1] EXPLICIT Extensions OPTIONAL } + */ +typedef struct { + CertID *certID; + CertStatus *certStatus; + ASN1_GENERALIZEDTIME *thisUpdate; + ASN1_GENERALIZEDTIME *nextUpdate; + STACK_OF(X509_EXTENSION) *singleExtensions; +} SingleResponse; + +/* + * ResponseData ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * responderID ResponderID, + * producedAt GeneralizedTime, + * responses SEQUENCE OF SingleResponse, + * responseExtensions [1] EXPLICIT Extensions OPTIONAL } + */ +typedef struct { + ASN1_INTEGER *version; + ResponderID *responderID; + ASN1_GENERALIZEDTIME *producedAt; + STACK_OF(SingleResponse) *responses; + STACK_OF(X509_EXTENSION) *responseExtensions; +} ResponseData; + +/* + * BasicOCSPResponse ::= SEQUENCE { + * tbsResponseData ResponseData, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + */ +typedef struct { + ResponseData *tbsResponseData; + X509_ALGOR *signatureAlgorithm; + ASN1_BIT_STRING *signature; + STACK_OF(X509) *certs; +} BasicOCSPResponse; + +ASN1_SEQUENCE(CertID) = { + ASN1_SIMPLE(CertID, hashAlgorithm, X509_ALGOR), + ASN1_SIMPLE(CertID, issuerNameHash, ASN1_OCTET_STRING), + ASN1_SIMPLE(CertID, issuerKeyHash, ASN1_OCTET_STRING), + ASN1_SIMPLE(CertID, serialNumber, ASN1_INTEGER) +} ASN1_SEQUENCE_END(CertID); + +ASN1_CHOICE(ResponderID) = { + ASN1_EXP(ResponderID, value.byName, X509_NAME, 1), + ASN1_EXP(ResponderID, value.byKey, ASN1_OCTET_STRING, 2) +} ASN1_CHOICE_END(ResponderID); + +ASN1_SEQUENCE(RevokedInfo) = { + ASN1_SIMPLE(RevokedInfo, revocationTime, ASN1_GENERALIZEDTIME), + ASN1_EXP_OPT(RevokedInfo, revocationReason, ASN1_ENUMERATED, 0) +} ASN1_SEQUENCE_END(RevokedInfo); + +ASN1_CHOICE(CertStatus) = { + ASN1_IMP(CertStatus, value.good, ASN1_NULL, 0), + ASN1_IMP(CertStatus, value.revoked, RevokedInfo, 1), + ASN1_IMP(CertStatus, value.unknown, ASN1_NULL, 2) +} ASN1_CHOICE_END(CertStatus); + +ASN1_SEQUENCE(SingleResponse) = { + ASN1_SIMPLE(SingleResponse, certID, CertID), + ASN1_SIMPLE(SingleResponse, certStatus, CertStatus), + ASN1_SIMPLE(SingleResponse, thisUpdate, ASN1_GENERALIZEDTIME), + ASN1_EXP_OPT(SingleResponse, nextUpdate, ASN1_GENERALIZEDTIME, 0), + ASN1_EXP_SEQUENCE_OF_OPT(SingleResponse, singleExtensions, + X509_EXTENSION, 1) +} ASN1_SEQUENCE_END(SingleResponse); + +ASN1_SEQUENCE(ResponseData) = { + ASN1_EXP_OPT(ResponseData, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ResponseData, responderID, ResponderID), + ASN1_SIMPLE(ResponseData, producedAt, ASN1_GENERALIZEDTIME), + ASN1_SEQUENCE_OF(ResponseData, responses, SingleResponse), + ASN1_EXP_SEQUENCE_OF_OPT(ResponseData, responseExtensions, + X509_EXTENSION, 1) +} ASN1_SEQUENCE_END(ResponseData); + +ASN1_SEQUENCE(BasicOCSPResponse) = { + ASN1_SIMPLE(BasicOCSPResponse, tbsResponseData, ResponseData), + ASN1_SIMPLE(BasicOCSPResponse, signatureAlgorithm, X509_ALGOR), + ASN1_SIMPLE(BasicOCSPResponse, signature, ASN1_BIT_STRING), + ASN1_EXP_SEQUENCE_OF_OPT(BasicOCSPResponse, certs, X509, 0) +} ASN1_SEQUENCE_END(BasicOCSPResponse); + +IMPLEMENT_ASN1_FUNCTIONS(BasicOCSPResponse); + +#define sk_SingleResponse_num(sk) \ +sk_num(CHECKED_CAST(_STACK *, STACK_OF(SingleResponse) *, sk)) + +#define sk_SingleResponse_value(sk, i) \ + ((SingleResponse *) \ + sk_value(CHECKED_CAST(_STACK *, STACK_OF(SingleResponse) *, sk), (i))) + + +static char * mem_bio_to_str(BIO *out) +{ + char *txt; + size_t rlen; + int res; + + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return NULL; + } + + res = BIO_read(out, txt, rlen); + BIO_free(out); + if (res < 0) { + os_free(txt); + return NULL; + } + + txt[res] = '\0'; + return txt; +} + + +static char * generalizedtime_str(ASN1_GENERALIZEDTIME *t) +{ + BIO *out; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + if (!ASN1_GENERALIZEDTIME_print(out, t)) { + BIO_free(out); + return NULL; + } + + return mem_bio_to_str(out); +} + + +static char * responderid_str(ResponderID *rid) +{ + BIO *out; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + switch (rid->type) { + case 0: + X509_NAME_print_ex(out, rid->value.byName, 0, XN_FLAG_ONELINE); + break; + case 1: + i2a_ASN1_STRING(out, rid->value.byKey, V_ASN1_OCTET_STRING); + break; + default: + BIO_free(out); + return NULL; + } + + return mem_bio_to_str(out); +} + + +static char * octet_string_str(ASN1_OCTET_STRING *o) +{ + BIO *out; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + i2a_ASN1_STRING(out, o, V_ASN1_OCTET_STRING); + return mem_bio_to_str(out); +} + + +static char * integer_str(ASN1_INTEGER *i) +{ + BIO *out; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + i2a_ASN1_INTEGER(out, i); + return mem_bio_to_str(out); +} + + +static char * algor_str(X509_ALGOR *alg) +{ + BIO *out; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + i2a_ASN1_OBJECT(out, alg->algorithm); + return mem_bio_to_str(out); +} + + +static char * extensions_str(const char *title, STACK_OF(X509_EXTENSION) *ext) +{ + BIO *out; + + if (!ext) + return NULL; + + out = BIO_new(BIO_s_mem()); + if (!out) + return NULL; + + if (!X509V3_extensions_print(out, title, ext, 0, 0)) { + BIO_free(out); + return NULL; + } + return mem_bio_to_str(out); +} + + +static int ocsp_resp_valid(ASN1_GENERALIZEDTIME *thisupd, + ASN1_GENERALIZEDTIME *nextupd) +{ + time_t now, tmp; + + if (!ASN1_GENERALIZEDTIME_check(thisupd)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Invalid OCSP response thisUpdate"); + return 0; + } + + time(&now); + tmp = now + 5 * 60; /* allow five minute clock difference */ + if (X509_cmp_time(thisupd, &tmp) > 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response not yet valid"); + return 0; + } + + if (!nextupd) + return 1; /* OK - no limit on response age */ + + if (!ASN1_GENERALIZEDTIME_check(nextupd)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Invalid OCSP response nextUpdate"); + return 0; + } + + tmp = now - 5 * 60; /* allow five minute clock difference */ + if (X509_cmp_time(nextupd, &tmp) < 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response expired"); + return 0; + } + + if (ASN1_STRING_cmp(nextupd, thisupd) < 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP response nextUpdate before thisUpdate"); + return 0; + } + + /* Both thisUpdate and nextUpdate are valid */ + return -1; +} + + +static int issuer_match(X509 *cert, X509 *issuer, CertID *certid) +{ + X509_NAME *iname; + ASN1_BIT_STRING *ikey; + const EVP_MD *dgst; + unsigned int len; + unsigned char md[EVP_MAX_MD_SIZE]; + ASN1_OCTET_STRING *hash; + char *txt; + + dgst = EVP_get_digestbyobj(certid->hashAlgorithm->algorithm); + if (!dgst) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not find matching hash algorithm for OCSP"); + return -1; + } + + iname = X509_get_issuer_name(cert); + if (!X509_NAME_digest(iname, dgst, md, &len)) + return -1; + hash = ASN1_OCTET_STRING_new(); + if (!hash) + return -1; + if (!ASN1_OCTET_STRING_set(hash, md, len)) { + ASN1_OCTET_STRING_free(hash); + return -1; + } + + txt = octet_string_str(hash); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: calculated issuerNameHash: %s", + txt); + os_free(txt); + } + + if (ASN1_OCTET_STRING_cmp(certid->issuerNameHash, hash)) { + ASN1_OCTET_STRING_free(hash); + return -1; + } + + ikey = X509_get0_pubkey_bitstr(issuer); + if (!EVP_Digest(ikey->data, ikey->length, md, &len, dgst, NULL) || + !ASN1_OCTET_STRING_set(hash, md, len)) { + ASN1_OCTET_STRING_free(hash); + return -1; + } + + txt = octet_string_str(hash); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: calculated issuerKeyHash: %s", + txt); + os_free(txt); + } + + if (ASN1_OCTET_STRING_cmp(certid->issuerKeyHash, hash)) { + ASN1_OCTET_STRING_free(hash); + return -1; + } + + ASN1_OCTET_STRING_free(hash); + return 0; +} + + +static X509 * ocsp_find_signer(STACK_OF(X509) *certs, ResponderID *rid) +{ + unsigned int i; + unsigned char hash[SHA_DIGEST_LENGTH]; + + if (rid->type == 0) { + /* byName */ + return X509_find_by_subject(certs, rid->value.byName); + } + + /* byKey */ + if (rid->value.byKey->length != SHA_DIGEST_LENGTH) + return NULL; + for (i = 0; i < sk_X509_num(certs); i++) { + X509 *x = sk_X509_value(certs, i); + + X509_pubkey_digest(x, EVP_sha1(), hash, NULL); + if (os_memcmp(rid->value.byKey->data, hash, + SHA_DIGEST_LENGTH) == 0) + return x; + } + + return NULL; +} + + +enum ocsp_result check_ocsp_resp(SSL_CTX *ssl_ctx, SSL *ssl, X509 *cert, + X509 *issuer, X509 *issuer_issuer) +{ + const uint8_t *resp_data; + size_t resp_len; + OCSPResponse *resp; + int status; + ResponseBytes *bytes; + const u8 *basic_data; + size_t basic_len; + BasicOCSPResponse *basic; + ResponseData *rd; + char *txt; + int i, num; + unsigned int j, num_resp; + SingleResponse *matching_resp = NULL, *cmp_sresp; + enum ocsp_result result = OCSP_INVALID; + X509_STORE *store; + STACK_OF(X509) *untrusted = NULL, *certs = NULL, *chain = NULL; + X509_STORE_CTX ctx; + X509 *signer, *tmp_cert; + int signer_trusted = 0; + EVP_PKEY *skey; + int ret; + char buf[256]; + + txt = integer_str(X509_get_serialNumber(cert)); + if (txt) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Searching OCSP response for peer certificate serialNumber: %s", txt); + os_free(txt); + } + + SSL_get0_ocsp_response(ssl, &resp_data, &resp_len); + if (resp_data == NULL || resp_len == 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); + return OCSP_NO_RESPONSE; + } + + wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", resp_data, resp_len); + + resp = d2i_OCSPResponse(NULL, &resp_data, resp_len); + if (!resp) { + wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSPResponse"); + return OCSP_INVALID; + } + + status = ASN1_ENUMERATED_get(resp->responseStatus); + if (status != 0) { + wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d", + status); + return OCSP_INVALID; + } + + bytes = resp->responseBytes; + + if (!bytes || + OBJ_obj2nid(bytes->responseType) != NID_id_pkix_OCSP_basic) { + wpa_printf(MSG_INFO, + "OpenSSL: Could not find BasicOCSPResponse"); + return OCSP_INVALID; + } + + basic_data = ASN1_STRING_data(bytes->response); + basic_len = ASN1_STRING_length(bytes->response); + wpa_hexdump(MSG_DEBUG, "OpenSSL: BasicOCSPResponse", + basic_data, basic_len); + + basic = d2i_BasicOCSPResponse(NULL, &basic_data, basic_len); + if (!basic) { + wpa_printf(MSG_INFO, + "OpenSSL: Could not parse BasicOCSPResponse"); + OCSPResponse_free(resp); + return OCSP_INVALID; + } + + rd = basic->tbsResponseData; + + if (basic->certs) { + untrusted = sk_X509_dup(basic->certs); + + num = sk_X509_num(basic->certs); + for (i = 0; i < num; i++) { + X509 *extra_cert; + + extra_cert = sk_X509_value(basic->certs, i); + X509_NAME_oneline(X509_get_subject_name(extra_cert), + buf, sizeof(buf)); + wpa_printf(MSG_DEBUG, + "OpenSSL: BasicOCSPResponse cert %s", buf); + + if (!sk_X509_push(untrusted, extra_cert)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not add certificate to the untrusted stack"); + } + } + } + + store = SSL_CTX_get_cert_store(ssl_ctx); + if (issuer) { + if (X509_STORE_add_cert(store, issuer) != 1) { + tls_show_errors(MSG_INFO, __func__, + "OpenSSL: Could not add issuer to certificate store"); + } + certs = sk_X509_new_null(); + if (certs) { + tmp_cert = X509_dup(issuer); + if (tmp_cert && !sk_X509_push(certs, tmp_cert)) { + tls_show_errors( + MSG_INFO, __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store"); + X509_free(tmp_cert); + sk_X509_free(certs); + certs = NULL; + } + if (certs && issuer_issuer) { + tmp_cert = X509_dup(issuer_issuer); + if (tmp_cert && + !sk_X509_push(certs, tmp_cert)) { + tls_show_errors( + MSG_INFO, __func__, + "OpenSSL: Could not add issuer's issuer to OCSP responder trust store"); + X509_free(tmp_cert); + } + } + } + } + + signer = ocsp_find_signer(certs, rd->responderID); + if (!signer) + signer = ocsp_find_signer(untrusted, rd->responderID); + else + signer_trusted = 1; + if (!signer) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not find OCSP signer certificate"); + goto fail; + } + + skey = X509_get_pubkey(signer); + if (!skey) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not get OCSP signer public key"); + goto fail; + } + if (ASN1_item_verify(ASN1_ITEM_rptr(ResponseData), + basic->signatureAlgorithm, basic->signature, + basic->tbsResponseData, skey) <= 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: BasicOCSPResponse signature is invalid"); + goto fail; + } + + X509_NAME_oneline(X509_get_subject_name(signer), buf, sizeof(buf)); + wpa_printf(MSG_DEBUG, + "OpenSSL: Found OCSP signer certificate %s and verified BasicOCSPResponse signature", + buf); + + if (!X509_STORE_CTX_init(&ctx, store, signer, untrusted)) + goto fail; + X509_STORE_CTX_set_purpose(&ctx, X509_PURPOSE_OCSP_HELPER); + ret = X509_verify_cert(&ctx); + chain = X509_STORE_CTX_get1_chain(&ctx); + X509_STORE_CTX_cleanup(&ctx); + if (ret <= 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not validate OCSP signer certificate"); + goto fail; + } + + if (!chain || sk_X509_num(chain) <= 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP signer chain found"); + goto fail; + } + + if (!signer_trusted) { + X509_check_purpose(signer, -1, 0); + if ((signer->ex_flags & EXFLAG_XKUSAGE) && + (signer->ex_xkusage & XKU_OCSP_SIGN)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP signer certificate delegation OK"); + } else { + tmp_cert = sk_X509_value(chain, sk_X509_num(chain) - 1); + if (X509_check_trust(tmp_cert, NID_OCSP_sign, 0) != + X509_TRUST_TRUSTED) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP signer certificate not trusted"); + result = OCSP_NO_RESPONSE; + goto fail; + } + } + } + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP version: %lu", + ASN1_INTEGER_get(rd->version)); + + txt = responderid_str(rd->responderID); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP responderID: %s", + txt); + os_free(txt); + } + + txt = generalizedtime_str(rd->producedAt); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP producedAt: %s", + txt); + os_free(txt); + } + + num_resp = sk_SingleResponse_num(rd->responses); + if (num_resp == 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: No OCSP SingleResponse within BasicOCSPResponse"); + result = OCSP_NO_RESPONSE; + goto fail; + } + cmp_sresp = sk_SingleResponse_value(rd->responses, 0); + for (j = 0; j < num_resp; j++) { + SingleResponse *sresp; + CertID *cid1, *cid2; + + sresp = sk_SingleResponse_value(rd->responses, j); + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP SingleResponse %u/%u", + j + 1, num_resp); + + txt = algor_str(sresp->certID->hashAlgorithm); + if (txt) { + wpa_printf(MSG_DEBUG, + "OpenSSL: certID hashAlgorithm: %s", txt); + os_free(txt); + } + + txt = octet_string_str(sresp->certID->issuerNameHash); + if (txt) { + wpa_printf(MSG_DEBUG, + "OpenSSL: certID issuerNameHash: %s", txt); + os_free(txt); + } + + txt = octet_string_str(sresp->certID->issuerKeyHash); + if (txt) { + wpa_printf(MSG_DEBUG, + "OpenSSL: certID issuerKeyHash: %s", txt); + os_free(txt); + } + + txt = integer_str(sresp->certID->serialNumber); + if (txt) { + wpa_printf(MSG_DEBUG, + "OpenSSL: certID serialNumber: %s", txt); + os_free(txt); + } + + switch (sresp->certStatus->type) { + case 0: + wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: good"); + break; + case 1: + wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: revoked"); + break; + default: + wpa_printf(MSG_DEBUG, "OpenSSL: certStatus: unknown"); + break; + } + + txt = generalizedtime_str(sresp->thisUpdate); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: thisUpdate: %s", txt); + os_free(txt); + } + + if (sresp->nextUpdate) { + txt = generalizedtime_str(sresp->nextUpdate); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: nextUpdate: %s", + txt); + os_free(txt); + } + } + + txt = extensions_str("singleExtensions", + sresp->singleExtensions); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: %s", txt); + os_free(txt); + } + + cid1 = cmp_sresp->certID; + cid2 = sresp->certID; + if (j > 0 && + (OBJ_cmp(cid1->hashAlgorithm->algorithm, + cid2->hashAlgorithm->algorithm) != 0 || + ASN1_OCTET_STRING_cmp(cid1->issuerNameHash, + cid2->issuerNameHash) != 0 || + ASN1_OCTET_STRING_cmp(cid1->issuerKeyHash, + cid2->issuerKeyHash) != 0)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Different OCSP response issuer information between SingleResponse values within BasicOCSPResponse"); + goto fail; + } + + if (!matching_resp && issuer && + ASN1_INTEGER_cmp(sresp->certID->serialNumber, + X509_get_serialNumber(cert)) == 0 && + issuer_match(cert, issuer, sresp->certID) == 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: This response matches peer certificate"); + matching_resp = sresp; + } + } + + txt = extensions_str("responseExtensions", rd->responseExtensions); + if (txt) { + wpa_printf(MSG_DEBUG, "OpenSSL: %s", txt); + os_free(txt); + } + + if (!matching_resp) { + wpa_printf(MSG_DEBUG, + "OpenSSL: Could not find OCSP response that matches the peer certificate"); + result = OCSP_NO_RESPONSE; + goto fail; + } + + if (!ocsp_resp_valid(matching_resp->thisUpdate, + matching_resp->nextUpdate)) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP response not valid at this time"); + goto fail; + } + + if (matching_resp->certStatus->type == 1) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP response indicated that the peer certificate has been revoked"); + result = OCSP_REVOKED; + goto fail; + } + + if (matching_resp->certStatus->type != 0) { + wpa_printf(MSG_DEBUG, + "OpenSSL: OCSP response did not indicate good status"); + result = OCSP_NO_RESPONSE; + goto fail; + } + + /* OCSP response indicated the certificate is good. */ + result = OCSP_GOOD; +fail: + sk_X509_pop_free(chain, X509_free); + sk_X509_free(untrusted); + sk_X509_pop_free(certs, X509_free); + BasicOCSPResponse_free(basic); + OCSPResponse_free(resp); + + return result; +} + +#endif /* OPENSSL_IS_BORINGSSL */ diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index b02e7345f..e949ce714 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -932,6 +932,7 @@ ifeq ($(CONFIG_TLS), openssl) ifdef TLS_FUNCS L_CFLAGS += -DEAP_TLS_OPENSSL OBJS += src/crypto/tls_openssl.c +OBJS += src/crypto/tls_openssl_ocsp.c LIBS += -lssl endif OBJS += src/crypto/crypto_openssl.c diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index c054246b9..e3d3acf16 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -959,6 +959,7 @@ ifeq ($(CONFIG_TLS), openssl) ifdef TLS_FUNCS CFLAGS += -DEAP_TLS_OPENSSL OBJS += ../src/crypto/tls_openssl.o +OBJS += ../src/crypto/tls_openssl_ocsp.o LIBS += -lssl endif OBJS += ../src/crypto/crypto_openssl.o