mirror of
https://github.com/apache/httpd.git
synced 2025-08-08 15:02:10 +03:00
Revamp CRL checking for client and remote servers:
- completely delegate CRL processing to OpenSSL - introduce a new [Proxy]CARevocationCheck directive - drop ssl_callback_SSLVerify_CRL from ssl_engine_kernel.c - remove X509_STORE from modssl_ctx_t - drop CRL store helper functions from ssl_util_ssl.c - avoid sending "certificate_expired" SSL alerts to peers when the nextUpdate field of a CRL is in the past git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1165056 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -1429,8 +1429,11 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
|
||||
*/
|
||||
ssl_log_cxerror(SSLLOG_MARK, APLOG_DEBUG, 0, conn,
|
||||
X509_STORE_CTX_get_current_cert(ctx),
|
||||
"Certificate Verification, depth %d",
|
||||
errdepth);
|
||||
"Certificate Verification, depth %d, "
|
||||
"CRL checking mode: %s", errdepth,
|
||||
mctx->crl_check_mode == SSL_CRLCHECK_CHAIN ?
|
||||
"chain" : (mctx->crl_check_mode == SSL_CRLCHECK_LEAF ?
|
||||
"leaf" : "none"));
|
||||
|
||||
/*
|
||||
* Check for optionally acceptable non-verifiable issuer situation
|
||||
@@ -1464,19 +1467,30 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform OCSP/CRL-based revocation checks
|
||||
* Expired certificates vs. "expired" CRLs: by default, OpenSSL
|
||||
* turns X509_V_ERR_CRL_HAS_EXPIRED into a "certificate_expired(45)"
|
||||
* SSL alert, but that's not really the message we should convey to the
|
||||
* peer (at the very least, it's confusing, and in many cases, it's also
|
||||
* inaccurate, as the certificate itself may very well not have expired
|
||||
* yet). We set the X509_STORE_CTX error to something which OpenSSL's
|
||||
* s3_both.c:ssl_verify_alarm_type() maps to SSL_AD_CERTIFICATE_UNKNOWN,
|
||||
* i.e. the peer will receive a "certificate_unknown(46)" alert.
|
||||
* We do not touch errnum, though, so that later on we will still log
|
||||
* the "real" error, as returned by OpenSSL.
|
||||
*/
|
||||
if (ok) {
|
||||
if (!(ok = ssl_callback_SSLVerify_CRL(ok, ctx, conn))) {
|
||||
errnum = X509_STORE_CTX_get_error(ctx);
|
||||
}
|
||||
|
||||
if (!ok && errnum == X509_V_ERR_CRL_HAS_EXPIRED) {
|
||||
X509_STORE_CTX_set_error(ctx, -1);
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_OCSP
|
||||
/*
|
||||
* Perform OCSP-based revocation checks
|
||||
*/
|
||||
if (ok && sc->server->ocsp_enabled) {
|
||||
/* If there was an optional verification error, it's not
|
||||
* possible to perform OCSP validation since the issuer may be
|
||||
* missing/untrusted. Fail in that case. */
|
||||
if (ok && ssl_verify_error_is_optional(errnum)
|
||||
&& sc->server->ocsp_enabled) {
|
||||
if (ssl_verify_error_is_optional(errnum)) {
|
||||
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
|
||||
errnum = X509_V_ERR_APPLICATION_VERIFICATION;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn,
|
||||
@@ -1484,52 +1498,28 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
|
||||
"if issuer has not been verified "
|
||||
"(optional_no_ca configured)");
|
||||
ok = FALSE;
|
||||
}
|
||||
|
||||
if (ok && sc->server->ocsp_enabled) {
|
||||
} else {
|
||||
ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
|
||||
if (!ok) {
|
||||
errnum = X509_STORE_CTX_get_error(ctx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If we already know it's not ok, log the real reason
|
||||
*/
|
||||
if (!ok) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn,
|
||||
"Certificate Verification: Error (%d): %s",
|
||||
errnum, X509_verify_cert_error_string(errnum));
|
||||
if (APLOGcinfo(conn)) {
|
||||
X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
char buff[512]; /* should be plenty */
|
||||
int n;
|
||||
|
||||
if (bio) {
|
||||
BIO_puts(bio, "Failed certificate: subject: '");
|
||||
X509_NAME_print_ex(bio, X509_get_subject_name(cert), 0,
|
||||
XN_FLAG_ONELINE);
|
||||
|
||||
BIO_puts(bio, "', issuer: '");
|
||||
X509_NAME_print_ex(bio, X509_get_issuer_name(cert), 0,
|
||||
XN_FLAG_ONELINE);
|
||||
|
||||
BIO_puts(bio, "', notbefore: ");
|
||||
ASN1_UTCTIME_print(bio, X509_get_notBefore(cert));
|
||||
|
||||
BIO_puts(bio, ", notafter: ");
|
||||
ASN1_UTCTIME_print(bio, X509_get_notAfter(cert));
|
||||
|
||||
n = BIO_read(bio, buff, sizeof(buff) - 1);
|
||||
BIO_free(bio);
|
||||
if (n > 0) {
|
||||
buff[n] = '\0';
|
||||
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, conn, "%s", buff);
|
||||
}
|
||||
}
|
||||
ssl_log_cxerror(SSLLOG_MARK, APLOG_INFO, 0, conn,
|
||||
X509_STORE_CTX_get_current_cert(ctx),
|
||||
"Certificate Verification: Error (%d): %s",
|
||||
errnum, X509_verify_cert_error_string(errnum));
|
||||
} else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn,
|
||||
"Certificate Verification: Error (%d): %s",
|
||||
errnum, X509_verify_cert_error_string(errnum));
|
||||
}
|
||||
|
||||
if (sslconn->client_cert) {
|
||||
@@ -1569,195 +1559,6 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
|
||||
return ok;
|
||||
}
|
||||
|
||||
int ssl_callback_SSLVerify_CRL(int ok, X509_STORE_CTX *ctx, conn_rec *c)
|
||||
{
|
||||
SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
|
||||
SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
request_rec *r = (request_rec *)SSL_get_app_data2(ssl);
|
||||
server_rec *s = r ? r->server : mySrvFromConn(c);
|
||||
SSLSrvConfigRec *sc = mySrvConfig(s);
|
||||
SSLConnRec *sslconn = myConnConfig(c);
|
||||
modssl_ctx_t *mctx = myCtxConfig(sslconn, sc);
|
||||
X509_OBJECT obj;
|
||||
X509_NAME *subject, *issuer;
|
||||
X509 *cert;
|
||||
X509_CRL *crl;
|
||||
EVP_PKEY *pubkey;
|
||||
int i, n, rc;
|
||||
|
||||
/*
|
||||
* Unless a revocation store for CRLs was created we
|
||||
* cannot do any CRL-based verification, of course.
|
||||
*/
|
||||
if (!mctx->crl) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine certificate ingredients in advance
|
||||
*/
|
||||
cert = X509_STORE_CTX_get_current_cert(ctx);
|
||||
subject = X509_get_subject_name(cert);
|
||||
issuer = X509_get_issuer_name(cert);
|
||||
|
||||
/*
|
||||
* OpenSSL provides the general mechanism to deal with CRLs but does not
|
||||
* use them automatically when verifying certificates, so we do it
|
||||
* explicitly here. We will check the CRL for the currently checked
|
||||
* certificate, if there is such a CRL in the store.
|
||||
*
|
||||
* We come through this procedure for each certificate in the certificate
|
||||
* chain, starting with the root-CA's certificate. At each step we've to
|
||||
* both verify the signature on the CRL (to make sure it's a valid CRL)
|
||||
* and its revocation list (to make sure the current certificate isn't
|
||||
* revoked). But because to check the signature on the CRL we need the
|
||||
* public key of the issuing CA certificate (which was already processed
|
||||
* one round before), we've a little problem. But we can both solve it and
|
||||
* at the same time optimize the processing by using the following
|
||||
* verification scheme (idea and code snippets borrowed from the GLOBUS
|
||||
* project):
|
||||
*
|
||||
* 1. We'll check the signature of a CRL in each step when we find a CRL
|
||||
* through the _subject_ name of the current certificate. This CRL
|
||||
* itself will be needed the first time in the next round, of course.
|
||||
* But we do the signature processing one round before this where the
|
||||
* public key of the CA is available.
|
||||
*
|
||||
* 2. We'll check the revocation list of a CRL in each step when
|
||||
* we find a CRL through the _issuer_ name of the current certificate.
|
||||
* This CRLs signature was then already verified one round before.
|
||||
*
|
||||
* This verification scheme allows a CA to revoke its own certificate as
|
||||
* well, of course.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Try to retrieve a CRL corresponding to the _subject_ of
|
||||
* the current certificate in order to verify its integrity.
|
||||
*/
|
||||
memset((char *)&obj, 0, sizeof(obj));
|
||||
rc = SSL_X509_STORE_lookup(mctx->crl,
|
||||
X509_LU_CRL, subject, &obj);
|
||||
crl = obj.data.crl;
|
||||
|
||||
if ((rc > 0) && crl) {
|
||||
/*
|
||||
* Log information about CRL
|
||||
* (A little bit complicated because of ASN.1 and BIOs...)
|
||||
*/
|
||||
if (APLOGtrace1(s)) {
|
||||
char buff[512]; /* should be plenty */
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
|
||||
BIO_printf(bio, "CA CRL: Issuer: ");
|
||||
X509_NAME_print(bio, issuer, 0);
|
||||
|
||||
BIO_printf(bio, ", lastUpdate: ");
|
||||
ASN1_UTCTIME_print(bio, X509_CRL_get_lastUpdate(crl));
|
||||
|
||||
BIO_printf(bio, ", nextUpdate: ");
|
||||
ASN1_UTCTIME_print(bio, X509_CRL_get_nextUpdate(crl));
|
||||
|
||||
n = BIO_read(bio, buff, sizeof(buff) - 1);
|
||||
buff[n] = '\0';
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "%s", buff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify the signature on this CRL
|
||||
*/
|
||||
pubkey = X509_get_pubkey(cert);
|
||||
rc = X509_CRL_verify(crl, pubkey);
|
||||
if (pubkey)
|
||||
EVP_PKEY_free(pubkey);
|
||||
if (rc <= 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
|
||||
"Invalid signature on CRL");
|
||||
|
||||
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check date of CRL to make sure it's not expired
|
||||
*/
|
||||
i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
|
||||
|
||||
if (i == 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
|
||||
"Found CRL has invalid nextUpdate field");
|
||||
|
||||
X509_STORE_CTX_set_error(ctx,
|
||||
X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
|
||||
"Found CRL is expired - "
|
||||
"revoking all certificates until you get updated CRL");
|
||||
|
||||
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to retrieve a CRL corresponding to the _issuer_ of
|
||||
* the current certificate in order to check for revocation.
|
||||
*/
|
||||
memset((char *)&obj, 0, sizeof(obj));
|
||||
rc = SSL_X509_STORE_lookup(mctx->crl,
|
||||
X509_LU_CRL, issuer, &obj);
|
||||
|
||||
crl = obj.data.crl;
|
||||
if ((rc > 0) && crl) {
|
||||
/*
|
||||
* Check if the current certificate is revoked by this CRL
|
||||
*/
|
||||
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
X509_REVOKED *revoked =
|
||||
sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
|
||||
|
||||
ASN1_INTEGER *sn = revoked->serialNumber;
|
||||
|
||||
if (!ASN1_INTEGER_cmp(sn, X509_get_serialNumber(cert))) {
|
||||
if (APLOGdebug(s)) {
|
||||
char *cp = X509_NAME_oneline(issuer, NULL, 0);
|
||||
long serial = ASN1_INTEGER_get(sn);
|
||||
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
|
||||
"Certificate with serial %ld (0x%lX) "
|
||||
"revoked per CRL from issuer %s",
|
||||
serial, serial, cp);
|
||||
OPENSSL_free(cp);
|
||||
}
|
||||
|
||||
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
X509_OBJECT_free_contents(&obj);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
#define SSLPROXY_CERT_CB_LOG_FMT \
|
||||
"Proxy client certificate callback: (%s) "
|
||||
|
||||
|
Reference in New Issue
Block a user