/* * Copyright (C) 2011 Free Software Foundation, Inc. * * Author: Nikos Mavrogiannopoulos * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ #include #include #include #include #include /* MAX */ #include #include #include #include #include "x509_int.h" #include #include "verify-high.h" struct named_cert_st { gnutls_x509_crt_t cert; uint8_t name[MAX_NAME_SIZE]; unsigned int name_size; }; struct node_st { /* The trusted certificates */ gnutls_x509_crt_t *trusted_cas; unsigned int trusted_ca_size; struct named_cert_st *named_certs; unsigned int named_cert_size; /* The trusted CRLs */ gnutls_x509_crl_t *crls; unsigned int crl_size; }; struct gnutls_x509_trust_list_st { int size; struct node_st *node; }; #define INIT_HASH 0x33a1 #define DEFAULT_SIZE 503 /** * gnutls_x509_trust_list_init: * @list: The structure to be initialized * @size: The size of the internal hash table. Use (0) for default size. * * This function will initialize an X.509 trust list structure. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_init(gnutls_x509_trust_list_t * list, unsigned int size) { gnutls_x509_trust_list_t tmp = gnutls_calloc(1, sizeof(struct gnutls_x509_trust_list_st)); if (!tmp) return GNUTLS_E_MEMORY_ERROR; if (size == 0) size = DEFAULT_SIZE; tmp->size = size; tmp->node = gnutls_calloc(1, tmp->size * sizeof(tmp->node[0])); if (tmp->node == NULL) { gnutls_assert(); gnutls_free(tmp); return GNUTLS_E_MEMORY_ERROR; } *list = tmp; return 0; /* success */ } /** * gnutls_x509_trust_list_deinit: * @list: The structure to be deinitialized * @all: if non-(0) it will deinitialize all the certificates and CRLs contained in the structure. * * This function will deinitialize a trust list. * * Since: 3.0.0 **/ void gnutls_x509_trust_list_deinit(gnutls_x509_trust_list_t list, unsigned int all) { int i, j; if (!list) return; for (i = 0; i < list->size; i++) { if (all) for (j = 0; j < list->node[i].trusted_ca_size; j++) { gnutls_x509_crt_deinit(list->node[i].trusted_cas[j]); } gnutls_free(list->node[i].trusted_cas); if (all) for (j = 0; j < list->node[i].crl_size; j++) { gnutls_x509_crl_deinit(list->node[i].crls[j]); } gnutls_free(list->node[i].crls); gnutls_free(list->node[i].named_certs); } gnutls_free(list->node); gnutls_free(list); } /** * gnutls_x509_trust_list_add_cas: * @list: The structure of the list * @clist: A list of CAs * @clist_size: The length of the CA list * @flags: should be 0. * * This function will add the given certificate authorities * to the trusted list. The list of CAs must not be deinitialized * during this structure's lifetime. * * Returns: The number of added elements is returned. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_add_cas(gnutls_x509_trust_list_t list, const gnutls_x509_crt_t * clist, int clist_size, unsigned int flags) { gnutls_datum_t dn; int ret, i; uint32_t hash; for (i = 0; i < clist_size; i++) { ret = gnutls_x509_crt_get_raw_dn(clist[i], &dn); if (ret < 0) { gnutls_assert(); return i; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); list->node[hash].trusted_cas = gnutls_realloc_fast(list->node[hash].trusted_cas, (list->node[hash].trusted_ca_size + 1) * sizeof(list->node[hash].trusted_cas[0])); if (list->node[hash].trusted_cas == NULL) { gnutls_assert(); return i; } list->node[hash].trusted_cas[list->node[hash].trusted_ca_size] = clist[i]; list->node[hash].trusted_ca_size++; } return i; } /** * gnutls_x509_trust_list_add_named_crt: * @list: The structure of the list * @cert: A certificate * @name: An identifier for the certificate * @name_size: The size of the identifier * @flags: should be 0. * * This function will add the given certificate to the trusted * list and associate it with a name. The certificate will not be * be used for verification with gnutls_x509_trust_list_verify_crt() * but only with gnutls_x509_trust_list_verify_named_crt(). * * In principle this function can be used to set individual "server" * certificates that are trusted by the user for that specific server * but for no other purposes. * * The certificate must not be deinitialized during the lifetime * of the trusted list. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_add_named_crt(gnutls_x509_trust_list_t list, gnutls_x509_crt_t cert, const void *name, size_t name_size, unsigned int flags) { gnutls_datum_t dn; int ret; uint32_t hash; if (name_size >= MAX_NAME_SIZE) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = gnutls_x509_crt_get_raw_issuer_dn(cert, &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); list->node[hash].named_certs = gnutls_realloc_fast(list->node[hash].named_certs, (list->node[hash].named_cert_size + 1) * sizeof(list->node[hash].named_certs[0])); if (list->node[hash].named_certs == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); list->node[hash].named_certs[list->node[hash].named_cert_size].cert = cert; memcpy(list->node[hash].named_certs[list->node[hash].named_cert_size]. name, name, name_size); list->node[hash].named_certs[list->node[hash].named_cert_size]. name_size = name_size; list->node[hash].named_cert_size++; return 0; } /** * gnutls_x509_trust_list_add_crls: * @list: The structure of the list * @crl_list: A list of CRLs * @crl_size: The length of the CRL list * @flags: if GNUTLS_TL_VERIFY_CRL is given the CRLs will be verified before being added. * @verification_flags: gnutls_certificate_verify_flags if flags specifies GNUTLS_TL_VERIFY_CRL * * This function will add the given certificate revocation lists * to the trusted list. The list of CRLs must not be deinitialized * during this structure's lifetime. * * This function must be called after gnutls_x509_trust_list_add_cas() * to allow verifying the CRLs for validity. * * Returns: The number of added elements is returned. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_add_crls(gnutls_x509_trust_list_t list, const gnutls_x509_crl_t * crl_list, int crl_size, unsigned int flags, unsigned int verification_flags) { int ret, i, j = 0; gnutls_datum_t dn; unsigned int vret = 0; uint32_t hash; /* Probably we can optimize things such as removing duplicates * etc. */ if (crl_size == 0 || crl_list == NULL) return 0; for (i = 0; i < crl_size; i++) { ret = gnutls_x509_crl_get_raw_issuer_dn(crl_list[i], &dn); if (ret < 0) { gnutls_assert(); return i; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); if (flags & GNUTLS_TL_VERIFY_CRL) { ret = gnutls_x509_crl_verify(crl_list[i], list->node[hash].trusted_cas, list->node[hash].trusted_ca_size, verification_flags, &vret); if (ret < 0 || vret != 0) continue; } list->node[hash].crls = gnutls_realloc_fast(list->node[hash].crls, (list->node[hash].crl_size + 1) * sizeof(list->node[hash].trusted_cas[0])); if (list->node[hash].crls == NULL) { gnutls_assert(); return i; } list->node[hash].crls[list->node[hash].crl_size] = crl_list[i]; list->node[hash].crl_size++; j++; } return j; } /* Takes a certificate list and shortens it if there are * intermedia certificates already trusted by us. * * FIXME: This is very similar to _gnutls_x509_verify_certificate(). * * Returns the new size of the list or a negative number on error. */ static int shorten_clist(gnutls_x509_trust_list_t list, gnutls_x509_crt_t * certificate_list, int clist_size) { int i, ret; uint32_t hash; gnutls_datum_t dn; if (clist_size > 1) { /* Check if the last certificate in the path is self signed. * In that case ignore it (a certificate is trusted only if it * leads to a trusted party by us, not the server's). * * This prevents from verifying self signed certificates against * themselves. This (although not bad) caused verification * failures on some root self signed certificates that use the * MD2 algorithm. */ if (gnutls_x509_crt_check_issuer(certificate_list[clist_size - 1], certificate_list[clist_size - 1]) > 0) { clist_size--; } } /* We want to shorten the chain by removing the cert that matches * one of the certs we trust and all the certs after that i.e. if * cert chain is A signed-by B signed-by C signed-by D (signed-by * self-signed E but already removed above), and we trust B, remove * B, C and D. */ for (i = 1; i < clist_size; i++) { int j; ret = gnutls_x509_crt_get_raw_issuer_dn(certificate_list[i], &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); for (j = 0; j < list->node[hash].trusted_ca_size; j++) { if (check_if_same_cert (certificate_list[i], list->node[hash].trusted_cas[j]) == 0) { /* cut the list at the point of first the trusted certificate */ clist_size = i + 1; break; } } /* clist_size may have been changed which gets out of loop */ } return clist_size; } /** * gnutls_x509_trust_list_get_issuer: * @list: The structure of the list * @cert: is the certificate to find issuer for * @issuer: Will hold the issuer if any. Should be treated as constant. * @flags: Use (0). * * This function will attempt to find the issuer of the * given certificate. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_get_issuer(gnutls_x509_trust_list_t list, gnutls_x509_crt_t cert, gnutls_x509_crt_t * issuer, unsigned int flags) { gnutls_datum_t dn; int ret, i; uint32_t hash; ret = gnutls_x509_crt_get_raw_issuer_dn(cert, &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); for (i = 0; i < list->node[hash].trusted_ca_size; i++) { ret = gnutls_x509_crt_check_issuer(cert, list->node[hash].trusted_cas[i]); if (ret > 0) { *issuer = list->node[hash].trusted_cas[i]; return 0; } } return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; } /** * gnutls_x509_trust_list_verify_crt: * @list: The structure of the list * @cert_list: is the certificate list to be verified * @cert_list_size: is the certificate list size * @flags: Flags that may be used to change the verification algorithm. Use OR of the gnutls_certificate_verify_flags enumerations. * @verify: will hold the certificate verification output. * @func: If non-null will be called on each chain element verification with the output. * * This function will try to verify the given certificate and return * its status. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_verify_crt(gnutls_x509_trust_list_t list, gnutls_x509_crt_t * cert_list, unsigned int cert_list_size, unsigned int flags, unsigned int *verify, gnutls_verify_output_function func) { gnutls_datum_t dn; int ret, i; uint32_t hash; if (cert_list == NULL || cert_list_size < 1) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); cert_list_size = shorten_clist(list, cert_list, cert_list_size); if (cert_list_size <= 0) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ret = gnutls_x509_crt_get_raw_issuer_dn(cert_list[cert_list_size - 1], &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); *verify = _gnutls_x509_verify_certificate(cert_list, cert_list_size, list->node[hash].trusted_cas, list->node[hash]. trusted_ca_size, flags, func); if (*verify != 0 || (flags & GNUTLS_VERIFY_DISABLE_CRL_CHECKS)) return 0; /* Check revocation of individual certificates. * start with the last one that we already have its hash */ ret = _gnutls_x509_crt_check_revocation(cert_list[cert_list_size - 1], list->node[hash].crls, list->node[hash].crl_size, func); if (ret == 1) { /* revoked */ *verify |= GNUTLS_CERT_REVOKED; *verify |= GNUTLS_CERT_INVALID; return 0; } for (i = 0; i < cert_list_size - 1; i++) { ret = gnutls_x509_crt_get_raw_issuer_dn(cert_list[i], &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); ret = _gnutls_x509_crt_check_revocation(cert_list[i], list->node[hash].crls, list->node[hash].crl_size, func); if (ret == 1) { /* revoked */ *verify |= GNUTLS_CERT_REVOKED; *verify |= GNUTLS_CERT_INVALID; return 0; } } return 0; } /** * gnutls_x509_trust_list_verify_named_crt: * @list: The structure of the list * @cert: is the certificate to be verified * @name: is the certificate's name * @name_size: is the certificate's name size * @flags: Flags that may be used to change the verification algorithm. Use OR of the gnutls_certificate_verify_flags enumerations. * @verify: will hold the certificate verification output. * @func: If non-null will be called on each chain element verification with the output. * * This function will try to find a matching named certificate. If a * match is found the certificate is considered valid. In addition to that * this function will also check CRLs. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0.0 **/ int gnutls_x509_trust_list_verify_named_crt(gnutls_x509_trust_list_t list, gnutls_x509_crt_t cert, const void *name, size_t name_size, unsigned int flags, unsigned int *verify, gnutls_verify_output_function func) { gnutls_datum_t dn; int ret, i; uint32_t hash; ret = gnutls_x509_crt_get_raw_issuer_dn(cert, &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum(&dn); *verify = GNUTLS_CERT_INVALID; for (i = 0; i < list->node[hash].named_cert_size; i++) { if (check_if_same_cert(cert, list->node[hash].named_certs[i].cert) == 0) { /* check if name matches */ if (list->node[hash].named_certs[i].name_size == name_size && memcmp(list->node[hash].named_certs[i].name, name, name_size) == 0) { *verify = 0; break; } } } if (*verify != 0 || (flags & GNUTLS_VERIFY_DISABLE_CRL_CHECKS)) return 0; /* Check revocation of individual certificates. * start with the last one that we already have its hash */ ret = _gnutls_x509_crt_check_revocation(cert, list->node[hash].crls, list->node[hash].crl_size, func); if (ret == 1) { /* revoked */ *verify |= GNUTLS_CERT_REVOKED; *verify |= GNUTLS_CERT_INVALID; return 0; } return 0; } int _gnutls_trustlist_inlist_p (gnutls_x509_trust_list_t list, gnutls_x509_crt_t cert) { gnutls_datum_t dn; int ret, i; uint32_t hash; ret = gnutls_x509_crt_get_raw_dn (cert, &dn); if (ret < 0) { gnutls_assert(); return ret; } hash = _gnutls_bhash(dn.data, dn.size, INIT_HASH); hash %= list->size; _gnutls_free_datum (&dn); for (i = 0; i < list->node[hash].trusted_ca_size; i++) { ret = check_if_same_cert (cert, list->node[hash].trusted_cas[i]); if (ret < 0) { gnutls_assert (); return ret; } if (ret == 1) return 1; } return 0; }