diff options
author | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2017-12-12 14:55:29 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2018-01-27 16:09:54 +0100 |
commit | 70aa2a61654882362d73bfed2b81c0cfd6fd06a5 (patch) | |
tree | 7ea8300e99a9c5d54b350be01827a2f78e6e9ac1 | |
parent | 7383c37620939a5a121ccd3462884adfdb6b8516 (diff) | |
download | gnutls-70aa2a61654882362d73bfed2b81c0cfd6fd06a5.tar.gz |
x509/cert: reorganized
Split functionality related to certificate credentials and
session certificate handling in cert-cred.c and cert-session.c
Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
-rw-r--r-- | lib/Makefile.am | 4 | ||||
-rw-r--r-- | lib/cert-cred-x509.c (renamed from lib/x509.c) | 355 | ||||
-rw-r--r-- | lib/cert-cred.c (renamed from lib/cert.c) | 278 | ||||
-rw-r--r-- | lib/cert-session.c | 629 |
4 files changed, 631 insertions, 635 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index e753145605..7e8b85b066 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -69,11 +69,11 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c handshake-tls13.c \ mbuffers.c buffers.c handshake.c num.c errors.c dh.c kx.c \ priority.c hash_int.c cipher_int.c session.c db.c x509_b64.c \ hello_ext.c auth.c sslv2_compat.c datum.c session_pack.c mpi.c \ - pk.c cert.c global.c constate.c anon_cred.c pkix_asn1_tab.c gnutls_asn1_tab.c \ + pk.c cert-cred.c global.c constate.c anon_cred.c pkix_asn1_tab.c gnutls_asn1_tab.c \ mem.c fingerprint.c tls-sig.c ecc.c alert.c privkey_raw.c atomic.h \ system/certs.c system/threads.c system/fastopen.c system/sockets.c \ system/inet_ntop.c str-iconv.c system/vasprintf.c vasprintf.h system.c \ - str.c str-unicode.c str-idna.c state.c x509.c file.c supplemental.c \ + str.c str-unicode.c str-idna.c state.c cert-cred-x509.c file.c supplemental.c \ random.c crypto-api.c crypto-api.h privkey.c pcert.c pubkey.c locks.c dtls.c \ system_override.c crypto-backend.c verify-tofu.c pin.c tpm.c fips.c \ safe-memfuncs.c system/inet_pton.c atfork.c atfork.h randomart.c \ diff --git a/lib/x509.c b/lib/cert-cred-x509.c index 6a536cd1e5..a3ce796ad8 100644 --- a/lib/x509.c +++ b/lib/cert-cred-x509.c @@ -24,7 +24,6 @@ #include "gnutls_int.h" #include "auth.h" #include "errors.h" -#include "hello_ext.h" #include <auth/cert.h> #include "dh.h" #include "num.h" @@ -40,7 +39,6 @@ #include <debug.h> #include <x509_b64.h> #include <x509.h> -#include <gnutls/ocsp.h> #include "x509/common.h" #include "x509/x509_int.h" #include <str_array.h> @@ -48,13 +46,13 @@ #include "read-file.h" #include "system-keys.h" #include "urls.h" -#include "ocsp.h" #ifdef _WIN32 #include <wincrypt.h> #endif /* - * some x509 certificate parsing functions. + * This file includes functions related to adding certificates and other + * related objects in a certificate credentials structure. */ static int @@ -70,355 +68,6 @@ certificate_credential_append_crt_list(gnutls_certificate_credentials_t res, return 0; \ } -/* fifteen days */ -#ifdef ENABLE_OCSP -/* If the certificate is revoked status will be GNUTLS_CERT_REVOKED. - * - * Returns: - * Zero on success, a negative error code otherwise. - */ -static int -check_ocsp_response(gnutls_session_t session, gnutls_x509_crt_t cert, - gnutls_x509_trust_list_t tl, - unsigned verify_flags, - gnutls_x509_crt_t *cand_issuers, unsigned cand_issuers_size, - gnutls_datum_t * data, unsigned int *ostatus) -{ - gnutls_ocsp_resp_t resp; - int ret; - unsigned int status, cert_status; - time_t rtime, vtime, ntime, now; - int check_failed = 0; - - now = gnutls_time(0); - - ret = gnutls_ocsp_resp_init(&resp); - if (ret < 0) - return gnutls_assert_val(ret); - - ret = gnutls_ocsp_resp_import(resp, data); - if (ret < 0) { - _gnutls_audit_log(session, - "There was an error parsing the OCSP response: %s.\n", - gnutls_strerror(ret)); - ret = gnutls_assert_val(0); - check_failed = 1; - *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; - goto cleanup; - } - - ret = gnutls_ocsp_resp_check_crt(resp, 0, cert); - if (ret < 0) { - ret = gnutls_assert_val(0); - _gnutls_audit_log(session, - "Got OCSP response with an unrelated certificate.\n"); - check_failed = 1; - *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; - goto cleanup; - } - - /* Attempt to verify against our trusted list */ - ret = gnutls_ocsp_resp_verify(resp, tl, &status, verify_flags); - if ((ret < 0 || status != 0) && cand_issuers_size > 0) { - /* Attempt to verify against the certificate list provided by the server */ - - ret = gnutls_ocsp_resp_verify_direct(resp, cand_issuers[0], &status, verify_flags); - /* if verification fails attempt to find whether any of the other - * bundled CAs is an issuer of the OCSP response */ - if ((ret < 0 || status != 0) && cand_issuers_size > 1) { - int ret2; - unsigned status2, i; - - for (i=1;i<cand_issuers_size;i++) { - ret2 = gnutls_ocsp_resp_verify_direct(resp, cand_issuers[i], &status2, verify_flags); - if (ret2 >= 0 && status2 == 0) { - status = status2; - ret = ret2; - break; - } - } - } - } - - if (ret < 0) { - ret = gnutls_assert_val(0); - gnutls_assert(); - check_failed = 1; - *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; - goto cleanup; - } - - /* do not consider revocation data if response was not verified */ - if (status != 0) { - ret = gnutls_assert_val(0); - check_failed = 1; - *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; - goto cleanup; - } - - ret = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL, - &cert_status, &vtime, &ntime, - &rtime, NULL); - if (ret < 0) { - _gnutls_audit_log(session, - "There was an error parsing the OCSP response: %s.\n", - gnutls_strerror(ret)); - ret = gnutls_assert_val(0); - check_failed = 1; - *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; - goto cleanup; - } - - if (cert_status == GNUTLS_OCSP_CERT_REVOKED) { - _gnutls_audit_log(session, - "The certificate was revoked via OCSP\n"); - check_failed = 1; - *ostatus |= GNUTLS_CERT_REVOKED; - ret = gnutls_assert_val(0); - goto cleanup; - } - - /* Report but do not fail on the following errors. That is - * because including the OCSP response in the handshake shouldn't - * cause more problems that not including it. - */ - if (ntime == -1) { - if (now - vtime > MAX_OCSP_VALIDITY_SECS) { - _gnutls_audit_log(session, - "The OCSP response is old\n"); - check_failed = 1; - *ostatus |= GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED; - goto cleanup; - } - } else { - /* there is a newer OCSP answer, don't trust this one */ - if (ntime < now) { - _gnutls_audit_log(session, - "There is a newer OCSP response but was not provided by the server\n"); - check_failed = 1; - *ostatus |= GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED; - goto cleanup; - } - } - - ret = 0; - cleanup: - if (check_failed == 0) - session->internals.ocsp_check_ok = 1; - - gnutls_ocsp_resp_deinit(resp); - - return ret; -} - -#endif - -#define CLEAR_CERTS for(x=0;x<peer_certificate_list_size;x++) { \ - if (peer_certificate_list[x]) \ - gnutls_x509_crt_deinit(peer_certificate_list[x]); \ - } \ - gnutls_free( peer_certificate_list) - -#ifdef ENABLE_OCSP -static int -_gnutls_ocsp_verify_mandatory_stapling(gnutls_session_t session, - gnutls_x509_crt_t cert, - unsigned int * ocsp_status) -{ - gnutls_x509_tlsfeatures_t tlsfeatures; - int i, ret; - unsigned feature; - - /* RFC 7633: If cert has TLS feature GNUTLS_EXTENSION_STATUS_REQUEST, stapling is mandatory. - * - * At this point, we know that we did not get the certificate status. - * - * To proceed, first check whether we have requested the certificate status - */ - if (!_gnutls_hello_ext_is_present(session, GNUTLS_EXTENSION_STATUS_REQUEST)) { - return 0; - } - - ret = gnutls_x509_tlsfeatures_init(&tlsfeatures); - if (ret < 0) { - gnutls_assert(); - return ret; - } - - /* We have requested the status, now check whether the certificate mandates a response */ - if (gnutls_x509_crt_get_tlsfeatures(cert, tlsfeatures, 0, NULL) == 0) { - for (i = 0;; ++i) { - ret = gnutls_x509_tlsfeatures_get(tlsfeatures, i, &feature); - if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - break; - } - - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } - - if (feature == 5 /* TLS ID for status request */) { - /* We sent a status request, the certificate mandates a reply, but we did not get any. */ - *ocsp_status |= GNUTLS_CERT_MISSING_OCSP_STATUS; - break; - } - } - } - - ret = 0; - cleanup: - gnutls_x509_tlsfeatures_deinit(tlsfeatures); - return ret; -} -#endif - -/*- - * _gnutls_x509_cert_verify_peers - return the peer's certificate status - * @session: is a gnutls session - * - * This function will try to verify the peer's certificate and return its status (TRUSTED, REVOKED etc.). - * The return value (status) should be one of the gnutls_certificate_status_t enumerated elements. - * However you must also check the peer's name in order to check if the verified certificate belongs to the - * actual peer. Returns a negative error code in case of an error, or GNUTLS_E_NO_CERTIFICATE_FOUND if no certificate was sent. - -*/ -int -_gnutls_x509_cert_verify_peers(gnutls_session_t session, - gnutls_typed_vdata_st * data, - unsigned int elements, - unsigned int *status) -{ - cert_auth_info_t info; - gnutls_certificate_credentials_t cred; - gnutls_x509_crt_t *peer_certificate_list; - gnutls_datum_t resp; - int peer_certificate_list_size, i, x, ret; - gnutls_x509_crt_t *cand_issuers; - unsigned cand_issuers_size; - unsigned int ocsp_status = 0; - unsigned int verify_flags; - - /* No OCSP check so far */ - session->internals.ocsp_check_ok = 0; - - CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); - - info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); - if (info == NULL) { - gnutls_assert(); - return GNUTLS_E_INVALID_REQUEST; - } - - cred = (gnutls_certificate_credentials_t) - _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); - if (cred == NULL) { - gnutls_assert(); - return GNUTLS_E_INSUFFICIENT_CREDENTIALS; - } - - if (info->raw_certificate_list == NULL || info->ncerts == 0) - return GNUTLS_E_NO_CERTIFICATE_FOUND; - - if (info->ncerts > cred->verify_depth && cred->verify_depth > 0) { - gnutls_assert(); - return GNUTLS_E_CONSTRAINT_ERROR; - } - - verify_flags = - cred->verify_flags | session->internals.additional_verify_flags; - /* generate a list of gnutls_certs based on the auth info - * raw certs. - */ - peer_certificate_list_size = info->ncerts; - peer_certificate_list = - gnutls_calloc(peer_certificate_list_size, - sizeof(gnutls_x509_crt_t)); - if (peer_certificate_list == NULL) { - gnutls_assert(); - return GNUTLS_E_MEMORY_ERROR; - } - - for (i = 0; i < peer_certificate_list_size; i++) { - ret = gnutls_x509_crt_init(&peer_certificate_list[i]); - if (ret < 0) { - gnutls_assert(); - CLEAR_CERTS; - return ret; - } - - ret = - gnutls_x509_crt_import(peer_certificate_list[i], - &info->raw_certificate_list[i], - GNUTLS_X509_FMT_DER); - if (ret < 0) { - gnutls_assert(); - CLEAR_CERTS; - return ret; - } - } - - /* Use the OCSP extension if any */ -#ifdef ENABLE_OCSP - if (verify_flags & GNUTLS_VERIFY_DISABLE_CRL_CHECKS) - goto skip_ocsp; - - for (i=0;i<peer_certificate_list_size;i++) { - ret = gnutls_ocsp_status_request_get2(session, i, &resp); - if (ret < 0) { - ret = _gnutls_ocsp_verify_mandatory_stapling(session, peer_certificate_list[i], &ocsp_status); - if (ret < 0) { - gnutls_assert(); - CLEAR_CERTS; - return ret; - } - - continue; - } - - cand_issuers = NULL; - cand_issuers_size = 0; - if (peer_certificate_list_size > i+1) { - cand_issuers = &peer_certificate_list[i+1]; - cand_issuers_size = peer_certificate_list_size-i-1; - } - - ret = - check_ocsp_response(session, - peer_certificate_list[i], - cred->tlist, - verify_flags, cand_issuers, - cand_issuers_size, - &resp, &ocsp_status); - - if (ret < 0) { - CLEAR_CERTS; - return gnutls_assert_val(ret); - } - } -#endif - - skip_ocsp: - /* Verify certificate - */ - ret = - gnutls_x509_trust_list_verify_crt2(cred->tlist, - peer_certificate_list, - peer_certificate_list_size, - data, elements, - verify_flags, status, NULL); - - if (ret < 0) { - gnutls_assert(); - CLEAR_CERTS; - return ret; - } - - CLEAR_CERTS; - - *status |= ocsp_status; - - return 0; -} static int str_array_append_idna(gnutls_str_array_t * head, const char *name, size_t size) { diff --git a/lib/cert.c b/lib/cert-cred.c index e3cedad0c3..7734eba232 100644 --- a/lib/cert.c +++ b/lib/cert-cred.c @@ -668,284 +668,6 @@ void cred->verify_callback = func; } -/*- - * _gnutls_x509_extract_certificate_activation_time - return the peer's certificate activation time - * @cert: should contain an X.509 DER encoded certificate - * - * This function will return the certificate's activation time in UNIX time - * (ie seconds since 00:00:00 UTC January 1, 1970). - * - * Returns a (time_t) -1 in case of an error. - * - -*/ -static time_t -_gnutls_x509_get_raw_crt_activation_time(const gnutls_datum_t * cert) -{ - gnutls_x509_crt_t xcert; - time_t result; - - result = gnutls_x509_crt_init(&xcert); - if (result < 0) - return (time_t) - 1; - - result = gnutls_x509_crt_import(xcert, cert, GNUTLS_X509_FMT_DER); - if (result < 0) { - gnutls_x509_crt_deinit(xcert); - return (time_t) - 1; - } - - result = gnutls_x509_crt_get_activation_time(xcert); - - gnutls_x509_crt_deinit(xcert); - - return result; -} - -/*- - * gnutls_x509_extract_certificate_expiration_time: - * @cert: should contain an X.509 DER encoded certificate - * - * This function will return the certificate's expiration time in UNIX - * time (ie seconds since 00:00:00 UTC January 1, 1970). Returns a - * - * (time_t) -1 in case of an error. - * - -*/ -static time_t -_gnutls_x509_get_raw_crt_expiration_time(const gnutls_datum_t * cert) -{ - gnutls_x509_crt_t xcert; - time_t result; - - result = gnutls_x509_crt_init(&xcert); - if (result < 0) - return (time_t) - 1; - - result = gnutls_x509_crt_import(xcert, cert, GNUTLS_X509_FMT_DER); - if (result < 0) { - gnutls_x509_crt_deinit(xcert); - return (time_t) - 1; - } - - result = gnutls_x509_crt_get_expiration_time(xcert); - - gnutls_x509_crt_deinit(xcert); - - return result; -} - -/** - * gnutls_certificate_verify_peers2: - * @session: is a gnutls session - * @status: is the output of the verification - * - * This function will verify the peer's certificate and store - * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t - * values or zero if the certificate is trusted. Note that value in @status - * is set only when the return value of this function is success (i.e, failure - * to trust a certificate does not imply a negative return value). - * The default verification flags used by this function can be overridden - * using gnutls_certificate_set_verify_flags(). - * - * This function will take into account the OCSP Certificate Status TLS extension, - * as well as the following X.509 certificate extensions: Name Constraints, - * Key Usage, and Basic Constraints (pathlen). - * - * To avoid denial of service attacks some - * default upper limits regarding the certificate key size and chain - * size are set. To override them use gnutls_certificate_set_verify_limits(). - * - * Note that you must also check the peer's name in order to check if - * the verified certificate belongs to the actual peer, see gnutls_x509_crt_check_hostname(), - * or use gnutls_certificate_verify_peers3(). - * - * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. - * A successful error code means that the @status parameter must be checked to obtain the validation status. - **/ -int -gnutls_certificate_verify_peers2(gnutls_session_t session, - unsigned int *status) -{ - return gnutls_certificate_verify_peers(session, NULL, 0, status); -} - -/** - * gnutls_certificate_verify_peers3: - * @session: is a gnutls session - * @hostname: is the expected name of the peer; may be %NULL - * @status: is the output of the verification - * - * This function will verify the peer's certificate and store the - * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t - * values or zero if the certificate is trusted. Note that value in @status - * is set only when the return value of this function is success (i.e, failure - * to trust a certificate does not imply a negative return value). - * The default verification flags used by this function can be overridden - * using gnutls_certificate_set_verify_flags(). See the documentation - * of gnutls_certificate_verify_peers2() for details in the verification process. - * - * If the @hostname provided is non-NULL then this function will compare - * the hostname in the certificate against it. The comparison will follow - * the RFC6125 recommendations. If names do not match the - * %GNUTLS_CERT_UNEXPECTED_OWNER status flag will be set. - * - * In order to verify the purpose of the end-certificate (by checking the extended - * key usage), use gnutls_certificate_verify_peers(). - * - * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. - * A successful error code means that the @status parameter must be checked to obtain the validation status. - * - * Since: 3.1.4 - **/ -int -gnutls_certificate_verify_peers3(gnutls_session_t session, - const char *hostname, - unsigned int *status) -{ -gnutls_typed_vdata_st data; - - data.type = GNUTLS_DT_DNS_HOSTNAME; - data.size = 0; - data.data = (void*)hostname; - - return gnutls_certificate_verify_peers(session, &data, 1, status); -} - -/** - * gnutls_certificate_verify_peers: - * @session: is a gnutls session - * @data: an array of typed data - * @elements: the number of data elements - * @status: is the output of the verification - * - * This function will verify the peer's certificate and store the - * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t - * values or zero if the certificate is trusted. Note that value in @status - * is set only when the return value of this function is success (i.e, failure - * to trust a certificate does not imply a negative return value). - * The default verification flags used by this function can be overridden - * using gnutls_certificate_set_verify_flags(). See the documentation - * of gnutls_certificate_verify_peers2() for details in the verification process. - * - * The acceptable @data types are %GNUTLS_DT_DNS_HOSTNAME, %GNUTLS_DT_RFC822NAME and %GNUTLS_DT_KEY_PURPOSE_OID. - * The former two accept as data a null-terminated hostname or email address, and the latter a null-terminated - * object identifier (e.g., %GNUTLS_KP_TLS_WWW_SERVER). - * - * If a DNS hostname is provided then this function will compare - * the hostname in the certificate against the given. If names do not match the - * %GNUTLS_CERT_UNEXPECTED_OWNER status flag will be set. - * If a key purpose OID is provided and the end-certificate contains the extended key - * usage PKIX extension, it will be required to be have the provided key purpose - * or be marked for any purpose, otherwise verification status will have the - * %GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE flag set. - * - * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. - * A successful error code means that the @status parameter must be checked to obtain the validation status. - * - * Since: 3.3.0 - **/ -int -gnutls_certificate_verify_peers(gnutls_session_t session, - gnutls_typed_vdata_st * data, - unsigned int elements, - unsigned int *status) -{ - cert_auth_info_t info; - - CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); - - info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); - if (info == NULL) { - return GNUTLS_E_NO_CERTIFICATE_FOUND; - } - - if (info->raw_certificate_list == NULL || info->ncerts == 0) - return GNUTLS_E_NO_CERTIFICATE_FOUND; - - - switch (gnutls_certificate_type_get(session)) { - case GNUTLS_CRT_X509: - return _gnutls_x509_cert_verify_peers(session, data, elements, - status); - default: - return GNUTLS_E_INVALID_REQUEST; - } -} - -/** - * gnutls_certificate_expiration_time_peers: - * @session: is a gnutls session - * - * This function will return the peer's certificate expiration time. - * - * Returns: (time_t)-1 on error. - * - * Deprecated: gnutls_certificate_verify_peers2() now verifies expiration times. - **/ -time_t gnutls_certificate_expiration_time_peers(gnutls_session_t session) -{ - cert_auth_info_t info; - - CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); - - info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); - if (info == NULL) { - return (time_t) - 1; - } - - if (info->raw_certificate_list == NULL || info->ncerts == 0) { - gnutls_assert(); - return (time_t) - 1; - } - - switch (gnutls_certificate_type_get(session)) { - case GNUTLS_CRT_X509: - return - _gnutls_x509_get_raw_crt_expiration_time(&info-> - raw_certificate_list - [0]); - default: - return (time_t) - 1; - } -} - -/** - * gnutls_certificate_activation_time_peers: - * @session: is a gnutls session - * - * This function will return the peer's certificate activation time. - * - * Returns: (time_t)-1 on error. - * - * Deprecated: gnutls_certificate_verify_peers2() now verifies activation times. - **/ -time_t gnutls_certificate_activation_time_peers(gnutls_session_t session) -{ - cert_auth_info_t info; - - CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); - - info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); - if (info == NULL) { - return (time_t) - 1; - } - - if (info->raw_certificate_list == NULL || info->ncerts == 0) { - gnutls_assert(); - return (time_t) - 1; - } - - switch (gnutls_certificate_type_get(session)) { - case GNUTLS_CRT_X509: - return - _gnutls_x509_get_raw_crt_activation_time(&info-> - raw_certificate_list - [0]); - default: - return (time_t) - 1; - } -} - #define TEST_TEXT "test text" /* returns error if the certificate has different algorithm than * the given key parameters. diff --git a/lib/cert-session.c b/lib/cert-session.c index e7a529a96a..2b415be30f 100644 --- a/lib/cert-session.c +++ b/lib/cert-session.c @@ -35,8 +35,10 @@ #include <state.h> #include <datum.h> #include <algorithms.h> - -/* CERTIFICATE STUFF */ +#include <gnutls/ocsp.h> +#include "x509.h" +#include "hello_ext.h" +#include "x509/ocsp.h" /** * gnutls_certificate_get_ours: @@ -216,3 +218,626 @@ gnutls_certificate_set_verify_limits(gnutls_certificate_credentials_t res, res->verify_bits = max_bits; } +#ifdef ENABLE_OCSP +/* If the certificate is revoked status will be GNUTLS_CERT_REVOKED. + * + * Returns: + * Zero on success, a negative error code otherwise. + */ +static int +check_ocsp_response(gnutls_session_t session, gnutls_x509_crt_t cert, + gnutls_x509_trust_list_t tl, + unsigned verify_flags, + gnutls_x509_crt_t *cand_issuers, unsigned cand_issuers_size, + gnutls_datum_t * data, unsigned int *ostatus) +{ + gnutls_ocsp_resp_t resp; + int ret; + unsigned int status, cert_status; + time_t rtime, vtime, ntime, now; + int check_failed = 0; + + now = gnutls_time(0); + + ret = gnutls_ocsp_resp_init(&resp); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = gnutls_ocsp_resp_import(resp, data); + if (ret < 0) { + _gnutls_audit_log(session, + "There was an error parsing the OCSP response: %s.\n", + gnutls_strerror(ret)); + ret = gnutls_assert_val(0); + check_failed = 1; + *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; + goto cleanup; + } + + ret = gnutls_ocsp_resp_check_crt(resp, 0, cert); + if (ret < 0) { + ret = gnutls_assert_val(0); + _gnutls_audit_log(session, + "Got OCSP response with an unrelated certificate.\n"); + check_failed = 1; + *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; + goto cleanup; + } + + /* Attempt to verify against our trusted list */ + ret = gnutls_ocsp_resp_verify(resp, tl, &status, verify_flags); + if ((ret < 0 || status != 0) && cand_issuers_size > 0) { + /* Attempt to verify against the certificate list provided by the server */ + + ret = gnutls_ocsp_resp_verify_direct(resp, cand_issuers[0], &status, verify_flags); + /* if verification fails attempt to find whether any of the other + * bundled CAs is an issuer of the OCSP response */ + if ((ret < 0 || status != 0) && cand_issuers_size > 1) { + int ret2; + unsigned status2, i; + + for (i=1;i<cand_issuers_size;i++) { + ret2 = gnutls_ocsp_resp_verify_direct(resp, cand_issuers[i], &status2, verify_flags); + if (ret2 >= 0 && status2 == 0) { + status = status2; + ret = ret2; + break; + } + } + } + } + + if (ret < 0) { + ret = gnutls_assert_val(0); + gnutls_assert(); + check_failed = 1; + *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; + goto cleanup; + } + + /* do not consider revocation data if response was not verified */ + if (status != 0) { + ret = gnutls_assert_val(0); + check_failed = 1; + *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; + goto cleanup; + } + + ret = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL, + &cert_status, &vtime, &ntime, + &rtime, NULL); + if (ret < 0) { + _gnutls_audit_log(session, + "There was an error parsing the OCSP response: %s.\n", + gnutls_strerror(ret)); + ret = gnutls_assert_val(0); + check_failed = 1; + *ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS; + goto cleanup; + } + + if (cert_status == GNUTLS_OCSP_CERT_REVOKED) { + _gnutls_audit_log(session, + "The certificate was revoked via OCSP\n"); + check_failed = 1; + *ostatus |= GNUTLS_CERT_REVOKED; + ret = gnutls_assert_val(0); + goto cleanup; + } + + /* Report but do not fail on the following errors. That is + * because including the OCSP response in the handshake shouldn't + * cause more problems that not including it. + */ + if (ntime == -1) { + if (now - vtime > MAX_OCSP_VALIDITY_SECS) { + _gnutls_audit_log(session, + "The OCSP response is old\n"); + check_failed = 1; + *ostatus |= GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED; + goto cleanup; + } + } else { + /* there is a newer OCSP answer, don't trust this one */ + if (ntime < now) { + _gnutls_audit_log(session, + "There is a newer OCSP response but was not provided by the server\n"); + check_failed = 1; + *ostatus |= GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED; + goto cleanup; + } + } + + ret = 0; + cleanup: + if (check_failed == 0) + session->internals.ocsp_check_ok = 1; + + gnutls_ocsp_resp_deinit(resp); + + return ret; +} + +static int +_gnutls_ocsp_verify_mandatory_stapling(gnutls_session_t session, + gnutls_x509_crt_t cert, + unsigned int * ocsp_status) +{ + gnutls_x509_tlsfeatures_t tlsfeatures; + int i, ret; + unsigned feature; + + /* RFC 7633: If cert has TLS feature GNUTLS_EXTENSION_STATUS_REQUEST, stapling is mandatory. + * + * At this point, we know that we did not get the certificate status. + * + * To proceed, first check whether we have requested the certificate status + */ + if (!_gnutls_hello_ext_is_present(session, GNUTLS_EXTENSION_STATUS_REQUEST)) { + return 0; + } + + ret = gnutls_x509_tlsfeatures_init(&tlsfeatures); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + /* We have requested the status, now check whether the certificate mandates a response */ + if (gnutls_x509_crt_get_tlsfeatures(cert, tlsfeatures, 0, NULL) == 0) { + for (i = 0;; ++i) { + ret = gnutls_x509_tlsfeatures_get(tlsfeatures, i, &feature); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + break; + } + + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (feature == 5 /* TLS ID for status request */) { + /* We sent a status request, the certificate mandates a reply, but we did not get any. */ + *ocsp_status |= GNUTLS_CERT_MISSING_OCSP_STATUS; + break; + } + } + } + + ret = 0; + cleanup: + gnutls_x509_tlsfeatures_deinit(tlsfeatures); + return ret; +} +#endif + +#define CLEAR_CERTS for(x=0;x<peer_certificate_list_size;x++) { \ + if (peer_certificate_list[x]) \ + gnutls_x509_crt_deinit(peer_certificate_list[x]); \ + } \ + gnutls_free( peer_certificate_list) + +/*- + * _gnutls_x509_cert_verify_peers - return the peer's certificate status + * @session: is a gnutls session + * + * This function will try to verify the peer's certificate and return its status (TRUSTED, REVOKED etc.). + * The return value (status) should be one of the gnutls_certificate_status_t enumerated elements. + * However you must also check the peer's name in order to check if the verified certificate belongs to the + * actual peer. Returns a negative error code in case of an error, or GNUTLS_E_NO_CERTIFICATE_FOUND if no certificate was sent. + -*/ +int +_gnutls_x509_cert_verify_peers(gnutls_session_t session, + gnutls_typed_vdata_st * data, + unsigned int elements, + unsigned int *status) +{ + cert_auth_info_t info; + gnutls_certificate_credentials_t cred; + gnutls_x509_crt_t *peer_certificate_list; + gnutls_datum_t resp; + int peer_certificate_list_size, i, x, ret; + gnutls_x509_crt_t *cand_issuers; + unsigned cand_issuers_size; + unsigned int ocsp_status = 0; + unsigned int verify_flags; + + /* No OCSP check so far */ + session->internals.ocsp_check_ok = 0; + + CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + if (info == NULL) { + gnutls_assert(); + return GNUTLS_E_INVALID_REQUEST; + } + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (cred == NULL) { + gnutls_assert(); + return GNUTLS_E_INSUFFICIENT_CREDENTIALS; + } + + if (info->raw_certificate_list == NULL || info->ncerts == 0) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + if (info->ncerts > cred->verify_depth && cred->verify_depth > 0) { + gnutls_assert(); + return GNUTLS_E_CONSTRAINT_ERROR; + } + + verify_flags = + cred->verify_flags | session->internals.additional_verify_flags; + /* generate a list of gnutls_certs based on the auth info + * raw certs. + */ + peer_certificate_list_size = info->ncerts; + peer_certificate_list = + gnutls_calloc(peer_certificate_list_size, + sizeof(gnutls_x509_crt_t)); + if (peer_certificate_list == NULL) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + + for (i = 0; i < peer_certificate_list_size; i++) { + ret = gnutls_x509_crt_init(&peer_certificate_list[i]); + if (ret < 0) { + gnutls_assert(); + CLEAR_CERTS; + return ret; + } + + ret = + gnutls_x509_crt_import(peer_certificate_list[i], + &info->raw_certificate_list[i], + GNUTLS_X509_FMT_DER); + if (ret < 0) { + gnutls_assert(); + CLEAR_CERTS; + return ret; + } + } + + /* Use the OCSP extension if any */ +#ifdef ENABLE_OCSP + if (verify_flags & GNUTLS_VERIFY_DISABLE_CRL_CHECKS) + goto skip_ocsp; + + for (i=0;i<peer_certificate_list_size;i++) { + ret = gnutls_ocsp_status_request_get2(session, i, &resp); + if (ret < 0) { + ret = _gnutls_ocsp_verify_mandatory_stapling(session, peer_certificate_list[i], &ocsp_status); + if (ret < 0) { + gnutls_assert(); + CLEAR_CERTS; + return ret; + } + + continue; + } + + cand_issuers = NULL; + cand_issuers_size = 0; + if (peer_certificate_list_size > i+1) { + cand_issuers = &peer_certificate_list[i+1]; + cand_issuers_size = peer_certificate_list_size-i-1; + } + + ret = + check_ocsp_response(session, + peer_certificate_list[i], + cred->tlist, + verify_flags, cand_issuers, + cand_issuers_size, + &resp, &ocsp_status); + + if (ret < 0) { + CLEAR_CERTS; + return gnutls_assert_val(ret); + } + } +#endif + + skip_ocsp: + /* Verify certificate + */ + ret = + gnutls_x509_trust_list_verify_crt2(cred->tlist, + peer_certificate_list, + peer_certificate_list_size, + data, elements, + verify_flags, status, NULL); + + if (ret < 0) { + gnutls_assert(); + CLEAR_CERTS; + return ret; + } + + CLEAR_CERTS; + + *status |= ocsp_status; + + return 0; +} + +/** + * gnutls_certificate_verify_peers2: + * @session: is a gnutls session + * @status: is the output of the verification + * + * This function will verify the peer's certificate and store + * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t + * values or zero if the certificate is trusted. Note that value in @status + * is set only when the return value of this function is success (i.e, failure + * to trust a certificate does not imply a negative return value). + * The default verification flags used by this function can be overridden + * using gnutls_certificate_set_verify_flags(). + * + * This function will take into account the OCSP Certificate Status TLS extension, + * as well as the following X.509 certificate extensions: Name Constraints, + * Key Usage, and Basic Constraints (pathlen). + * + * To avoid denial of service attacks some + * default upper limits regarding the certificate key size and chain + * size are set. To override them use gnutls_certificate_set_verify_limits(). + * + * Note that you must also check the peer's name in order to check if + * the verified certificate belongs to the actual peer, see gnutls_x509_crt_check_hostname(), + * or use gnutls_certificate_verify_peers3(). + * + * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. + * A successful error code means that the @status parameter must be checked to obtain the validation status. + **/ +int +gnutls_certificate_verify_peers2(gnutls_session_t session, + unsigned int *status) +{ + return gnutls_certificate_verify_peers(session, NULL, 0, status); +} + +/** + * gnutls_certificate_verify_peers3: + * @session: is a gnutls session + * @hostname: is the expected name of the peer; may be %NULL + * @status: is the output of the verification + * + * This function will verify the peer's certificate and store the + * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t + * values or zero if the certificate is trusted. Note that value in @status + * is set only when the return value of this function is success (i.e, failure + * to trust a certificate does not imply a negative return value). + * The default verification flags used by this function can be overridden + * using gnutls_certificate_set_verify_flags(). See the documentation + * of gnutls_certificate_verify_peers2() for details in the verification process. + * + * If the @hostname provided is non-NULL then this function will compare + * the hostname in the certificate against it. The comparison will follow + * the RFC6125 recommendations. If names do not match the + * %GNUTLS_CERT_UNEXPECTED_OWNER status flag will be set. + * + * In order to verify the purpose of the end-certificate (by checking the extended + * key usage), use gnutls_certificate_verify_peers(). + * + * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. + * A successful error code means that the @status parameter must be checked to obtain the validation status. + * + * Since: 3.1.4 + **/ +int +gnutls_certificate_verify_peers3(gnutls_session_t session, + const char *hostname, + unsigned int *status) +{ +gnutls_typed_vdata_st data; + + data.type = GNUTLS_DT_DNS_HOSTNAME; + data.size = 0; + data.data = (void*)hostname; + + return gnutls_certificate_verify_peers(session, &data, 1, status); +} + +/** + * gnutls_certificate_verify_peers: + * @session: is a gnutls session + * @data: an array of typed data + * @elements: the number of data elements + * @status: is the output of the verification + * + * This function will verify the peer's certificate and store the + * the status in the @status variable as a bitwise OR of gnutls_certificate_status_t + * values or zero if the certificate is trusted. Note that value in @status + * is set only when the return value of this function is success (i.e, failure + * to trust a certificate does not imply a negative return value). + * The default verification flags used by this function can be overridden + * using gnutls_certificate_set_verify_flags(). See the documentation + * of gnutls_certificate_verify_peers2() for details in the verification process. + * + * The acceptable @data types are %GNUTLS_DT_DNS_HOSTNAME, %GNUTLS_DT_RFC822NAME and %GNUTLS_DT_KEY_PURPOSE_OID. + * The former two accept as data a null-terminated hostname or email address, and the latter a null-terminated + * object identifier (e.g., %GNUTLS_KP_TLS_WWW_SERVER). + * + * If a DNS hostname is provided then this function will compare + * the hostname in the certificate against the given. If names do not match the + * %GNUTLS_CERT_UNEXPECTED_OWNER status flag will be set. + * If a key purpose OID is provided and the end-certificate contains the extended key + * usage PKIX extension, it will be required to be have the provided key purpose + * or be marked for any purpose, otherwise verification status will have the + * %GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE flag set. + * + * Returns: %GNUTLS_E_SUCCESS (0) when the validation is performed, or a negative error code otherwise. + * A successful error code means that the @status parameter must be checked to obtain the validation status. + * + * Since: 3.3.0 + **/ +int +gnutls_certificate_verify_peers(gnutls_session_t session, + gnutls_typed_vdata_st * data, + unsigned int elements, + unsigned int *status) +{ + cert_auth_info_t info; + + CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + if (info == NULL) { + return GNUTLS_E_NO_CERTIFICATE_FOUND; + } + + if (info->raw_certificate_list == NULL || info->ncerts == 0) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + + switch (gnutls_certificate_type_get(session)) { + case GNUTLS_CRT_X509: + return _gnutls_x509_cert_verify_peers(session, data, elements, + status); + default: + return GNUTLS_E_INVALID_REQUEST; + } +} + +/*- + * _gnutls_x509_extract_certificate_activation_time - return the peer's certificate activation time + * @cert: should contain an X.509 DER encoded certificate + * + * This function will return the certificate's activation time in UNIX time + * (ie seconds since 00:00:00 UTC January 1, 1970). + * + * Returns a (time_t) -1 in case of an error. + * + -*/ +static time_t +_gnutls_x509_get_raw_crt_activation_time(const gnutls_datum_t * cert) +{ + gnutls_x509_crt_t xcert; + time_t result; + + result = gnutls_x509_crt_init(&xcert); + if (result < 0) + return (time_t) - 1; + + result = gnutls_x509_crt_import(xcert, cert, GNUTLS_X509_FMT_DER); + if (result < 0) { + gnutls_x509_crt_deinit(xcert); + return (time_t) - 1; + } + + result = gnutls_x509_crt_get_activation_time(xcert); + + gnutls_x509_crt_deinit(xcert); + + return result; +} + +/*- + * gnutls_x509_extract_certificate_expiration_time: + * @cert: should contain an X.509 DER encoded certificate + * + * This function will return the certificate's expiration time in UNIX + * time (ie seconds since 00:00:00 UTC January 1, 1970). Returns a + * + * (time_t) -1 in case of an error. + * + -*/ +static time_t +_gnutls_x509_get_raw_crt_expiration_time(const gnutls_datum_t * cert) +{ + gnutls_x509_crt_t xcert; + time_t result; + + result = gnutls_x509_crt_init(&xcert); + if (result < 0) + return (time_t) - 1; + + result = gnutls_x509_crt_import(xcert, cert, GNUTLS_X509_FMT_DER); + if (result < 0) { + gnutls_x509_crt_deinit(xcert); + return (time_t) - 1; + } + + result = gnutls_x509_crt_get_expiration_time(xcert); + + gnutls_x509_crt_deinit(xcert); + + return result; +} + +/** + * gnutls_certificate_expiration_time_peers: + * @session: is a gnutls session + * + * This function will return the peer's certificate expiration time. + * + * Returns: (time_t)-1 on error. + * + * Deprecated: gnutls_certificate_verify_peers2() now verifies expiration times. + **/ +time_t gnutls_certificate_expiration_time_peers(gnutls_session_t session) +{ + cert_auth_info_t info; + + CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + if (info == NULL) { + return (time_t) - 1; + } + + if (info->raw_certificate_list == NULL || info->ncerts == 0) { + gnutls_assert(); + return (time_t) - 1; + } + + switch (gnutls_certificate_type_get(session)) { + case GNUTLS_CRT_X509: + return + _gnutls_x509_get_raw_crt_expiration_time(&info-> + raw_certificate_list + [0]); + default: + return (time_t) - 1; + } +} + +/** + * gnutls_certificate_activation_time_peers: + * @session: is a gnutls session + * + * This function will return the peer's certificate activation time. + * + * Returns: (time_t)-1 on error. + * + * Deprecated: gnutls_certificate_verify_peers2() now verifies activation times. + **/ +time_t gnutls_certificate_activation_time_peers(gnutls_session_t session) +{ + cert_auth_info_t info; + + CHECK_AUTH(GNUTLS_CRD_CERTIFICATE, GNUTLS_E_INVALID_REQUEST); + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + if (info == NULL) { + return (time_t) - 1; + } + + if (info->raw_certificate_list == NULL || info->ncerts == 0) { + gnutls_assert(); + return (time_t) - 1; + } + + switch (gnutls_certificate_type_get(session)) { + case GNUTLS_CRT_X509: + return + _gnutls_x509_get_raw_crt_activation_time(&info-> + raw_certificate_list + [0]); + default: + return (time_t) - 1; + } +} |