BoringSSL: Move OCSP implementation into a separate file
This makes it easier to share the OCSP implementation needed for BoringSSL outside tls_openssl.c. For now, this is mainly for http_curl.c. Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
parent
f846211e3e
commit
213e158ca8
7 changed files with 872 additions and 820 deletions
|
@ -555,6 +555,7 @@ endif
|
||||||
ifeq ($(CONFIG_TLS), openssl)
|
ifeq ($(CONFIG_TLS), openssl)
|
||||||
ifdef TLS_FUNCS
|
ifdef TLS_FUNCS
|
||||||
OBJS += src/crypto/tls_openssl.c
|
OBJS += src/crypto/tls_openssl.c
|
||||||
|
OBJS += src/crypto/tls_openssl_ocsp.c
|
||||||
LIBS += -lssl
|
LIBS += -lssl
|
||||||
endif
|
endif
|
||||||
OBJS += src/crypto/crypto_openssl.c
|
OBJS += src/crypto/crypto_openssl.c
|
||||||
|
|
|
@ -544,6 +544,7 @@ endif
|
||||||
ifeq ($(CONFIG_TLS), openssl)
|
ifeq ($(CONFIG_TLS), openssl)
|
||||||
ifdef TLS_FUNCS
|
ifdef TLS_FUNCS
|
||||||
OBJS += ../src/crypto/tls_openssl.o
|
OBJS += ../src/crypto/tls_openssl.o
|
||||||
|
OBJS += ../src/crypto/tls_openssl_ocsp.o
|
||||||
LIBS += -lssl
|
LIBS += -lssl
|
||||||
endif
|
endif
|
||||||
OBJS += ../src/crypto/crypto_openssl.o
|
OBJS += ../src/crypto/crypto_openssl.o
|
||||||
|
|
|
@ -30,16 +30,12 @@
|
||||||
#include <openssl/dh.h>
|
#include <openssl/dh.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef OPENSSL_IS_BORINGSSL
|
|
||||||
#include <openssl/asn1.h>
|
|
||||||
#include <openssl/asn1t.h>
|
|
||||||
#endif /* OPENSSL_IS_BORINGSSL */
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include "sha1.h"
|
#include "sha1.h"
|
||||||
#include "sha256.h"
|
#include "sha256.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
|
#include "tls_openssl.h"
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10000000L
|
#if OPENSSL_VERSION_NUMBER < 0x10000000L
|
||||||
/* ERR_remove_thread_state replaces ERR_remove_state and the latter is
|
/* 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)
|
static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
||||||
{
|
{
|
||||||
char buf[256];
|
char buf[256];
|
||||||
|
@ -2613,10 +1796,13 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef OPENSSL_IS_BORINGSSL
|
#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;
|
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) {
|
if (res == OCSP_REVOKED) {
|
||||||
preverify_ok = 0;
|
preverify_ok = 0;
|
||||||
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
|
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
|
||||||
|
|
19
src/crypto/tls_openssl.h
Normal file
19
src/crypto/tls_openssl.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* SSL/TLS interface functions for OpenSSL
|
||||||
|
* Copyright (c) 2004-2015, Jouni Malinen <j@w1.fi>
|
||||||
|
*
|
||||||
|
* 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 */
|
843
src/crypto/tls_openssl_ocsp.c
Normal file
843
src/crypto/tls_openssl_ocsp.c
Normal file
|
@ -0,0 +1,843 @@
|
||||||
|
/*
|
||||||
|
* SSL/TLS interface functions for OpenSSL - BoringSSL OCSP
|
||||||
|
* Copyright (c) 2004-2015, Jouni Malinen <j@w1.fi>
|
||||||
|
*
|
||||||
|
* This software may be distributed under the terms of the BSD license.
|
||||||
|
* See README for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "includes.h"
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/x509v3.h>
|
||||||
|
#ifdef OPENSSL_IS_BORINGSSL
|
||||||
|
#include <openssl/asn1.h>
|
||||||
|
#include <openssl/asn1t.h>
|
||||||
|
#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 */
|
|
@ -932,6 +932,7 @@ ifeq ($(CONFIG_TLS), openssl)
|
||||||
ifdef TLS_FUNCS
|
ifdef TLS_FUNCS
|
||||||
L_CFLAGS += -DEAP_TLS_OPENSSL
|
L_CFLAGS += -DEAP_TLS_OPENSSL
|
||||||
OBJS += src/crypto/tls_openssl.c
|
OBJS += src/crypto/tls_openssl.c
|
||||||
|
OBJS += src/crypto/tls_openssl_ocsp.c
|
||||||
LIBS += -lssl
|
LIBS += -lssl
|
||||||
endif
|
endif
|
||||||
OBJS += src/crypto/crypto_openssl.c
|
OBJS += src/crypto/crypto_openssl.c
|
||||||
|
|
|
@ -959,6 +959,7 @@ ifeq ($(CONFIG_TLS), openssl)
|
||||||
ifdef TLS_FUNCS
|
ifdef TLS_FUNCS
|
||||||
CFLAGS += -DEAP_TLS_OPENSSL
|
CFLAGS += -DEAP_TLS_OPENSSL
|
||||||
OBJS += ../src/crypto/tls_openssl.o
|
OBJS += ../src/crypto/tls_openssl.o
|
||||||
|
OBJS += ../src/crypto/tls_openssl_ocsp.o
|
||||||
LIBS += -lssl
|
LIBS += -lssl
|
||||||
endif
|
endif
|
||||||
OBJS += ../src/crypto/crypto_openssl.o
|
OBJS += ../src/crypto/crypto_openssl.o
|
||||||
|
|
Loading…
Reference in a new issue