diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2012-02-15 08:54:54 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2012-02-15 08:54:54 +0100 |
commit | 1f245ddb9ab672a0b253b711e58c186dd6d1ae4f (patch) | |
tree | f6cce0df88ae3090190c96c31107c8139ecd8cd3 /lib/verify-tofu.c | |
parent | 0b215d3f27f52808ca7bf2b5a8192fe61ae9203f (diff) | |
download | gnutls-1f245ddb9ab672a0b253b711e58c186dd6d1ae4f.tar.gz |
The hash in gnutls_store_commitment() is specified in raw format.
Diffstat (limited to 'lib/verify-tofu.c')
-rw-r--r-- | lib/verify-tofu.c | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/lib/verify-tofu.c b/lib/verify-tofu.c new file mode 100644 index 0000000000..d4461eeca4 --- /dev/null +++ b/lib/verify-tofu.c @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2012 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 <http://www.gnu.org/licenses/> + * + */ + +#include <gnutls_int.h> +#include <gnutls_errors.h> +#include <libtasn1.h> +#include <gnutls_global.h> +#include <gnutls_num.h> /* MAX */ +#include <gnutls_sig.h> +#include <gnutls_str.h> +#include <gnutls_datum.h> +#include <hash.h> +#include "x509_int.h" +#include <common.h> +#include <base64.h> +#include <gnutls/abstract.h> +#include <system.h> + +static int raw_pubkey_to_base64(const gnutls_datum_t* raw, gnutls_datum_t * b64); +static int x509_crt_to_raw_pubkey(const gnutls_datum_t * cert, gnutls_datum_t *rpubkey); +static int pgp_crt_to_raw_pubkey(const gnutls_datum_t * cert, gnutls_datum_t *rpubkey); +static int find_stored_pubkey(const char* file, + const char* host, const char* service, + const gnutls_datum_t* skey); + +static +int store_commitment(const char* db_name, const char* host, + const char* service, time_t expiration, + gnutls_digest_algorithm_t hash_algo, + const gnutls_datum_t* hash); +static +int store_pubkey(const char* db_name, const char* host, + const char* service, time_t expiration, const gnutls_datum_t* pubkey); + +static int find_config_file(char* file, size_t max_size); +#define MAX_FILENAME 512 + +static const trust_storage_st default_storage = +{ + store_pubkey, + store_commitment, + find_stored_pubkey +}; + +/** + * gnutls_verify_stored_pubkey: + * @db_name: A file specifying the stored keys (use NULL for the default) + * @tdb: A database structure or NULL to use the default + * @host: The peer's name + * @service: non-NULL if this key is specific to a service (e.g. http) + * @cert_type: The type of the certificate + * @cert: The raw (der) data of the certificate + * @flags: should be 0. + * + * This function will try to verify the provided certificate using + * a list of stored public keys. The @service field if non-NULL should + * be a port number. + * + * The @tdb variable if non-null specifies a custom backend for + * the storage and retrieval of entries. If it is NULL then the + * default file backend will be used. In POSIX-like systems the + * file backend uses the $HOME/.gnutls/known_hosts file. + * + * Note that if the custom storage backend is provided the + * retrieval function should return %GNUTLS_E_CERTIFICATE_KEY_MISMATCH + * if the host/service pair is found but key doesn't match, + * %GNUTLS_E_NO_CERTIFICATE_FOUND if no such host/service with + * the given key is found, and 0 if it was found. The storage + * function should return 0 on success. + * + * Returns: If no associated public key is found + * then %GNUTLS_E_NO_CERTIFICATE_FOUND will be returned. If a key + * is found but does not match %GNUTLS_E_CERTIFICATE_KEY_MISMATCH + * is returned. On success, %GNUTLS_E_SUCCESS (0) is returned, + * or a negative error value on other errors. + * + * Since: 3.0.0 + **/ +int +gnutls_verify_stored_pubkey(const char* db_name, + const trust_storage_st *tdb, + const char* host, + const char* service, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t * cert, unsigned int flags) +{ +gnutls_datum_t pubkey = { NULL, 0 }; +int ret; +char local_file[MAX_FILENAME]; + + if (cert_type != GNUTLS_CRT_X509 && cert_type != GNUTLS_CRT_OPENPGP) + return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE); + + if (db_name == NULL && tdb == NULL) + { + ret = find_config_file(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + db_name = local_file; + } + + if (tdb == NULL) + tdb = &default_storage; + + if (cert_type == GNUTLS_CRT_X509) + ret = x509_crt_to_raw_pubkey(cert, &pubkey); + else + ret = pgp_crt_to_raw_pubkey(cert, &pubkey); + + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = tdb->retrieve(db_name, host, service, &pubkey); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND); + +cleanup: + gnutls_free(pubkey.data); + return ret; +} + +static int parse_commitment_line(char* line, + const char* host, size_t host_len, + const char* service, size_t service_len, + time_t now, + const gnutls_datum_t *skey) +{ +char* p, *kp; +char* savep = NULL; +size_t kp_len, phash_size; +time_t expiration; +int ret; +gnutls_digest_algorithm_t hash_algo; +uint8_t phash[MAX_HASH_SIZE]; +uint8_t hphash[MAX_HASH_SIZE*2+1]; + + /* read host */ + p = strtok_r(line, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (p[0] != '*' && strcmp(p, host) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read service */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (p[0] != '*' && strcmp(p, service) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read expiration */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + expiration = (time_t)atol(p); + if (expiration > 0 && now > expiration) + return gnutls_assert_val(GNUTLS_E_EXPIRED); + + /* read hash algorithm */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + hash_algo = (time_t)atol(p); + if (_gnutls_digest_get_name(hash_algo) == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read hash */ + kp = strtok_r(NULL, "|", &savep); + if (kp == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + p = strpbrk(kp, "\n \r\t|"); + if (p != NULL) *p = 0; + + /* hash and hex encode */ + ret = _gnutls_hash_fast (hash_algo, skey->data, skey->size, phash); + if (ret < 0) + return gnutls_assert_val(ret); + + phash_size = _gnutls_hash_get_algo_len(hash_algo); + + p = _gnutls_bin2hex (phash, phash_size,(void*) hphash, + sizeof(hphash), NULL); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + kp_len = strlen(kp); + if (kp_len != phash_size*2) + return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH); + + if (memcmp(kp, hphash, kp_len) != 0) + return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH); + + /* key found and matches */ + return 0; +} + + +static int parse_line(char* line, + const char* host, size_t host_len, + const char* service, size_t service_len, + time_t now, + const gnutls_datum_t *rawkey, + const gnutls_datum_t *b64key) +{ +char* p, *kp; +char* savep = NULL; +size_t kp_len; +time_t expiration; + + /* read version */ + p = strtok_r(line, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (strncmp(p, "c0", 2) == 0) + return parse_commitment_line(p+3, host, host_len, service, service_len, now, rawkey); + + if (strncmp(p, "g0", 2) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read host */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (p[0] != '*' && strcmp(p, host) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read service */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (p[0] != '*' && strcmp(p, service) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read expiration */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + expiration = (time_t)atol(p); + if (expiration > 0 && now > expiration) + return gnutls_assert_val(GNUTLS_E_EXPIRED); + + /* read key */ + kp = strtok_r(NULL, "|", &savep); + if (kp == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + p = strpbrk(kp, "\n \r\t|"); + if (p != NULL) *p = 0; + + kp_len = strlen(kp); + if (kp_len != b64key->size) + return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH); + + if (memcmp(kp, b64key->data, b64key->size) != 0) + return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH); + + /* key found and matches */ + return 0; +} + +/* Returns the base64 key if found + */ +static int find_stored_pubkey(const char* file, + const char* host, const char* service, + const gnutls_datum_t* pubkey) +{ +FILE* fd; +char* line = NULL; +size_t line_size = 0; +int ret, l2, mismatch = 0; +size_t host_len = 0, service_len = 0; +time_t now = gnutls_time(0); +gnutls_datum_t b64key = { NULL, 0 }; + + ret = raw_pubkey_to_base64(pubkey, &b64key); + if (ret < 0) + return gnutls_assert_val(ret); + + if (host != NULL) host_len = strlen(host); + if (service != NULL) service_len = strlen(service); + + fd = fopen(file, "rb"); + if (fd == NULL) + { + ret = gnutls_assert_val(GNUTLS_E_FILE_ERROR); + goto cleanup; + } + + do + { + l2 = getline(&line, &line_size, fd); + if (l2 > 0) + { + ret = parse_line(line, host, host_len, service, service_len, now, pubkey, &b64key); + if (ret == 0) /* found */ + { + goto cleanup; + } + else if (ret == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) + mismatch = 1; + } + } + while(l2 >= 0); + + if (mismatch) + ret = GNUTLS_E_CERTIFICATE_KEY_MISMATCH; + else + ret = GNUTLS_E_NO_CERTIFICATE_FOUND; + +cleanup: + free(line); + if (fd != NULL) + fclose(fd); + gnutls_free(b64key.data); + + return ret; +} + +static int raw_pubkey_to_base64(const gnutls_datum_t* raw, gnutls_datum_t * b64) +{ + int ret; + char* out; + + ret = base64_encode_alloc((void*)raw->data, raw->size, &out); + if (ret == 0) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + b64->data = (void*)out; + b64->size = ret; + + return 0; +} + +static int x509_crt_to_raw_pubkey(const gnutls_datum_t * cert, gnutls_datum_t *rpubkey) +{ +gnutls_x509_crt_t crt = NULL; +gnutls_pubkey_t pubkey = NULL; +size_t size; +int ret; + + ret = gnutls_x509_crt_init(&crt); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = gnutls_pubkey_init(&pubkey); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_x509_crt_import(crt, cert, GNUTLS_X509_FMT_DER); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_pubkey_import_x509 (pubkey, crt, 0); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + size = 0; + ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, NULL, &size); + if (ret < 0 && ret != GNUTLS_E_SHORT_MEMORY_BUFFER) + { + gnutls_assert(); + goto cleanup; + } + + rpubkey->data = gnutls_malloc(size); + if (rpubkey->data == NULL) + if (ret < 0 && ret != GNUTLS_E_SHORT_MEMORY_BUFFER) + { + ret = GNUTLS_E_MEMORY_ERROR; + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, rpubkey->data, &size); + if (ret < 0) + { + gnutls_free(rpubkey->data); + gnutls_assert(); + goto cleanup; + } + + rpubkey->size = size; + ret = 0; + +cleanup: + gnutls_x509_crt_deinit(crt); + gnutls_pubkey_deinit(pubkey); + + return ret; +} + +static int pgp_crt_to_raw_pubkey(const gnutls_datum_t * cert, gnutls_datum_t *rpubkey) +{ +gnutls_openpgp_crt_t crt = NULL; +gnutls_pubkey_t pubkey = NULL; +size_t size; +int ret; + + ret = gnutls_openpgp_crt_init(&crt); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = gnutls_pubkey_init(&pubkey); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_openpgp_crt_import(crt, cert, GNUTLS_OPENPGP_FMT_RAW); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_pubkey_import_openpgp (pubkey, crt, 0); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + size = 0; + ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, NULL, &size); + if (ret < 0 && ret != GNUTLS_E_SHORT_MEMORY_BUFFER) + { + gnutls_assert(); + goto cleanup; + } + + rpubkey->data = gnutls_malloc(size); + if (rpubkey->data == NULL) + if (ret < 0 && ret != GNUTLS_E_SHORT_MEMORY_BUFFER) + { + ret = GNUTLS_E_MEMORY_ERROR; + gnutls_assert(); + goto cleanup; + } + + ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, rpubkey->data, &size); + if (ret < 0) + { + gnutls_free(rpubkey->data); + gnutls_assert(); + goto cleanup; + } + + rpubkey->size = size; + ret = 0; + +cleanup: + gnutls_openpgp_crt_deinit(crt); + gnutls_pubkey_deinit(pubkey); + + return ret; +} + +static +int store_pubkey(const char* db_name, const char* host, + const char* service, time_t expiration, + const gnutls_datum_t* pubkey) +{ +FILE* fd = NULL; +gnutls_datum_t b64key; +int ret; + + ret = raw_pubkey_to_base64(pubkey, &b64key); + if (ret < 0) + return gnutls_assert_val(ret); + + fd = fopen(db_name, "ab+"); + if (fd == NULL) + { + ret = gnutls_assert_val(GNUTLS_E_FILE_ERROR); + goto cleanup; + } + + if (service == NULL) service = "*"; + if (host == NULL) host = "*"; + + fprintf(fd, "|g0|%s|%s|%lu|%.*s\n", host, service, (unsigned long)expiration, + pubkey->size, pubkey->data); + + ret = 0; + +cleanup: + if (fd != NULL) + fclose(fd); + gnutls_free(b64key.data); + + return ret; +} + +static +int store_commitment(const char* db_name, const char* host, + const char* service, time_t expiration, + gnutls_digest_algorithm_t hash_algo, + const gnutls_datum_t* hash) +{ +FILE* fd; +char buffer[MAX_HASH_SIZE*2+1]; + + fd = fopen(db_name, "ab+"); + if (fd == NULL) + return gnutls_assert_val(GNUTLS_E_FILE_ERROR); + + if (service == NULL) service = "*"; + if (host == NULL) host = "*"; + + fprintf(fd, "|c0|%s|%s|%lu|%u|%s\n", host, service, (unsigned long)expiration, + (unsigned)hash_algo, _gnutls_bin2hex(hash->data, hash->size, buffer, sizeof(buffer), NULL)); + + fclose(fd); + + return 0; +} + +/** + * gnutls_store_pubkey: + * @db_name: A file specifying the stored keys (use NULL for the default) + * @tdb: A database structure or NULL to use the default + * @host: The peer's name + * @service: non-NULL if this key is specific to a service (e.g. http) + * @cert_type: The type of the certificate + * @cert: The data of the certificate + * @expiration: The expiration time (use 0 to disable expiration) + * @flags: should be 0. + * + * This function will store the provided certificate to + * the list of stored public keys. The key will be considered valid until + * the provided expiration time. + * + * The @tdb variable if non-null specifies a custom backend for + * the storage and retrieval of entries. If it is NULL then the + * default file backend will be used. + * + * Note that this function is not thread safe with the default backend. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. + * + * Since: 3.0.0 + **/ +int +gnutls_store_pubkey(const char* db_name, + const trust_storage_st* tdb, + const char* host, + const char* service, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t * cert, + time_t expiration, + unsigned int flags) +{ +FILE* fd = NULL; +gnutls_datum_t pubkey = { NULL, 0 }; +int ret; +char local_file[MAX_FILENAME]; + + if (cert_type != GNUTLS_CRT_X509 && cert_type != GNUTLS_CRT_OPENPGP) + return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE); + + if (db_name == NULL && tdb == NULL) + { + ret = _gnutls_find_config_path(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_debug_log("Configuration path: %s\n", local_file); + mkdir(local_file, 0700); + + ret = find_config_file(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + db_name = local_file; + } + + if (tdb == NULL) + tdb = &default_storage; + + if (cert_type == GNUTLS_CRT_X509) + ret = x509_crt_to_raw_pubkey(cert, &pubkey); + else + ret = pgp_crt_to_raw_pubkey(cert, &pubkey); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + _gnutls_debug_log("Configuration file: %s\n", db_name); + + tdb->store(db_name, host, service, expiration, &pubkey); + + ret = 0; + +cleanup: + gnutls_free(pubkey.data); + if (fd != NULL) fclose(fd); + + return ret; +} + +/** + * gnutls_store_commitment: + * @db_name: A file specifying the stored keys (use NULL for the default) + * @tdb: A database structure or NULL to use the default + * @host: The peer's name + * @service: non-NULL if this key is specific to a service (e.g. http) + * @hash_algo: The hash algorithm type + * @hash: The raw hash + * @expiration: The expiration time (use 0 to disable expiration) + * @flags: should be 0. + * + * This function will store the provided hash commitment to + * the list of stored public keys. The key with the given + * hash will be considered valid until the provided expiration time. + * + * The @tdb variable if non-null specifies a custom backend for + * the storage and retrieval of entries. If it is NULL then the + * default file backend will be used. + * + * Note that this function is not thread safe with the default backend. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. + * + * Since: 3.0.0 + **/ +int +gnutls_store_commitment(const char* db_name, + const trust_storage_st* tdb, + const char* host, + const char* service, + gnutls_digest_algorithm_t hash_algo, + const gnutls_datum_t* hash, + time_t expiration, + unsigned int flags) +{ +FILE* fd = NULL; +int ret; +char local_file[MAX_FILENAME]; + + if (hash_algo == GNUTLS_DIG_MD5 || hash_algo == GNUTLS_DIG_MD2) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + if (_gnutls_hash_get_algo_len(hash_algo) != hash->size) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (db_name == NULL && tdb == NULL) + { + ret = _gnutls_find_config_path(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_debug_log("Configuration path: %s\n", local_file); + mkdir(local_file, 0700); + + ret = find_config_file(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + db_name = local_file; + } + + if (tdb == NULL) + tdb = &default_storage; + + _gnutls_debug_log("Configuration file: %s\n", db_name); + + tdb->cstore(db_name, host, service, expiration, hash_algo, hash); + + ret = 0; + + if (fd != NULL) fclose(fd); + + return ret; +} + +#define CONFIG_FILE "known_hosts" + +static int find_config_file(char* file, size_t max_size) +{ +char path[MAX_FILENAME]; +int ret; + + ret = _gnutls_find_config_path(path, sizeof(path)); + if (ret < 0) + return gnutls_assert_val(ret); + + if (path[0] == 0) + snprintf(file, max_size, "%s", CONFIG_FILE); + else + snprintf(file, max_size, "%s/%s", path, CONFIG_FILE); + + return 0; +} |