GnuTLS: Move peer certificate validation into callback function

GnuTLS 2.10.0 added gnutls_certificate_set_verify_function() that can be
used to move peer certificate validation to an earlier point in the
handshake. Use that to get similar validation behavior to what was done
with OpenSSL, i.e., reject the handshake immediately after receiving the
peer certificate rather than at the completion of handshake.

Signed-off-by: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2015-01-11 12:43:17 +02:00
parent 7c8245798f
commit 65ec7f4c12

View File

@ -48,6 +48,9 @@ struct tls_connection {
}; };
static int tls_connection_verify_peer(gnutls_session_t session);
static void tls_log_func(int level, const char *msg) static void tls_log_func(int level, const char *msg)
{ {
char *s, *pos; char *s, *pos;
@ -190,6 +193,7 @@ static int tls_gnutls_init_session(struct tls_global *global,
gnutls_transport_set_pull_function(conn->session, tls_pull_func); gnutls_transport_set_pull_function(conn->session, tls_pull_func);
gnutls_transport_set_push_function(conn->session, tls_push_func); gnutls_transport_set_push_function(conn->session, tls_push_func);
gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) conn); gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) conn);
gnutls_session_set_ptr(conn->session, conn);
return 0; return 0;
@ -381,6 +385,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
if (params->ca_cert || params->ca_cert_blob) { if (params->ca_cert || params->ca_cert_blob) {
conn->verify_peer = 1; conn->verify_peer = 1;
gnutls_certificate_set_verify_function(
conn->xcred, tls_connection_verify_peer);
if (params->flags & TLS_CONN_ALLOW_SIGN_RSA_MD5) { if (params->flags & TLS_CONN_ALLOW_SIGN_RSA_MD5) {
gnutls_certificate_set_verify_flags( gnutls_certificate_set_verify_flags(
@ -621,62 +627,75 @@ int tls_connection_prf(void *tls_ctx, struct tls_connection *conn,
} }
static int tls_connection_verify_peer(struct tls_connection *conn, static int tls_connection_verify_peer(gnutls_session_t session)
gnutls_alert_description_t *err)
{ {
struct tls_connection *conn;
unsigned int status, num_certs, i; unsigned int status, num_certs, i;
struct os_time now; struct os_time now;
const gnutls_datum_t *certs; const gnutls_datum_t *certs;
gnutls_x509_crt_t cert; gnutls_x509_crt_t cert;
gnutls_alert_description_t err;
if (gnutls_certificate_verify_peers2(conn->session, &status) < 0) { conn = gnutls_session_get_ptr(session);
if (!conn->verify_peer) {
wpa_printf(MSG_DEBUG,
"GnuTLS: No peer certificate verification enabled");
return 0;
}
wpa_printf(MSG_DEBUG, "GnuTSL: Verifying peer certificate");
if (gnutls_certificate_verify_peers2(session, &status) < 0) {
wpa_printf(MSG_INFO, "TLS: Failed to verify peer " wpa_printf(MSG_INFO, "TLS: Failed to verify peer "
"certificate chain"); "certificate chain");
*err = GNUTLS_A_INTERNAL_ERROR; err = GNUTLS_A_INTERNAL_ERROR;
return -1; goto out;
} }
if (conn->verify_peer && (status & GNUTLS_CERT_INVALID)) { if (conn->verify_peer && (status & GNUTLS_CERT_INVALID)) {
wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted"); wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted");
*err = GNUTLS_A_INTERNAL_ERROR;
if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
wpa_printf(MSG_INFO, "TLS: Certificate uses insecure " wpa_printf(MSG_INFO, "TLS: Certificate uses insecure "
"algorithm"); "algorithm");
*err = GNUTLS_A_INSUFFICIENT_SECURITY; err = GNUTLS_A_INSUFFICIENT_SECURITY;
goto out;
} }
if (status & GNUTLS_CERT_NOT_ACTIVATED) { if (status & GNUTLS_CERT_NOT_ACTIVATED) {
wpa_printf(MSG_INFO, "TLS: Certificate not yet " wpa_printf(MSG_INFO, "TLS: Certificate not yet "
"activated"); "activated");
*err = GNUTLS_A_CERTIFICATE_EXPIRED; err = GNUTLS_A_CERTIFICATE_EXPIRED;
goto out;
} }
if (status & GNUTLS_CERT_EXPIRED) { if (status & GNUTLS_CERT_EXPIRED) {
wpa_printf(MSG_INFO, "TLS: Certificate expired"); wpa_printf(MSG_INFO, "TLS: Certificate expired");
*err = GNUTLS_A_CERTIFICATE_EXPIRED; err = GNUTLS_A_CERTIFICATE_EXPIRED;
goto out;
} }
return -1; err = GNUTLS_A_INTERNAL_ERROR;
goto out;
} }
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
wpa_printf(MSG_INFO, "TLS: Peer certificate does not have a " wpa_printf(MSG_INFO, "TLS: Peer certificate does not have a "
"known issuer"); "known issuer");
*err = GNUTLS_A_UNKNOWN_CA; err = GNUTLS_A_UNKNOWN_CA;
return -1; goto out;
} }
if (status & GNUTLS_CERT_REVOKED) { if (status & GNUTLS_CERT_REVOKED) {
wpa_printf(MSG_INFO, "TLS: Peer certificate has been revoked"); wpa_printf(MSG_INFO, "TLS: Peer certificate has been revoked");
*err = GNUTLS_A_CERTIFICATE_REVOKED; err = GNUTLS_A_CERTIFICATE_REVOKED;
return -1; goto out;
} }
os_get_time(&now); os_get_time(&now);
certs = gnutls_certificate_get_peers(conn->session, &num_certs); certs = gnutls_certificate_get_peers(session, &num_certs);
if (certs == NULL) { if (certs == NULL) {
wpa_printf(MSG_INFO, "TLS: No peer certificate chain " wpa_printf(MSG_INFO, "TLS: No peer certificate chain "
"received"); "received");
*err = GNUTLS_A_UNKNOWN_CA; err = GNUTLS_A_UNKNOWN_CA;
return -1; goto out;
} }
for (i = 0; i < num_certs; i++) { for (i = 0; i < num_certs; i++) {
@ -685,8 +704,8 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
if (gnutls_x509_crt_init(&cert) < 0) { if (gnutls_x509_crt_init(&cert) < 0) {
wpa_printf(MSG_INFO, "TLS: Certificate initialization " wpa_printf(MSG_INFO, "TLS: Certificate initialization "
"failed"); "failed");
*err = GNUTLS_A_BAD_CERTIFICATE; err = GNUTLS_A_BAD_CERTIFICATE;
return -1; goto out;
} }
if (gnutls_x509_crt_import(cert, &certs[i], if (gnutls_x509_crt_import(cert, &certs[i],
@ -694,8 +713,8 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
wpa_printf(MSG_INFO, "TLS: Could not parse peer " wpa_printf(MSG_INFO, "TLS: Could not parse peer "
"certificate %d/%d", i + 1, num_certs); "certificate %d/%d", i + 1, num_certs);
gnutls_x509_crt_deinit(cert); gnutls_x509_crt_deinit(cert);
*err = GNUTLS_A_BAD_CERTIFICATE; err = GNUTLS_A_BAD_CERTIFICATE;
return -1; goto out;
} }
gnutls_x509_crt_get_dn(cert, NULL, &len); gnutls_x509_crt_get_dn(cert, NULL, &len);
@ -722,14 +741,19 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
"not valid at this time", "not valid at this time",
i + 1, num_certs); i + 1, num_certs);
gnutls_x509_crt_deinit(cert); gnutls_x509_crt_deinit(cert);
*err = GNUTLS_A_CERTIFICATE_EXPIRED; err = GNUTLS_A_CERTIFICATE_EXPIRED;
return -1; goto out;
} }
gnutls_x509_crt_deinit(cert); gnutls_x509_crt_deinit(cert);
} }
return 0; return 0;
out:
conn->failed++;
gnutls_alert_send(session, GNUTLS_AL_FATAL, err);
return GNUTLS_E_CERTIFICATE_ERROR;
} }
@ -809,16 +833,6 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
} }
} else { } else {
size_t size; size_t size;
gnutls_alert_description_t err;
if (conn->verify_peer &&
tls_connection_verify_peer(conn, &err)) {
wpa_printf(MSG_INFO, "TLS: Peer certificate chain "
"failed validation");
conn->failed++;
gnutls_alert_send(conn->session, GNUTLS_AL_FATAL, err);
goto out;
}
wpa_printf(MSG_DEBUG, "TLS: Handshake completed successfully"); wpa_printf(MSG_DEBUG, "TLS: Handshake completed successfully");
conn->established = 1; conn->established = 1;
@ -844,7 +858,6 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
*appl_data = gnutls_get_appl_data(conn); *appl_data = gnutls_get_appl_data(conn);
} }
out:
out_data = conn->push_buf; out_data = conn->push_buf;
conn->push_buf = NULL; conn->push_buf = NULL;
return out_data; return out_data;