summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@gnutls.org>2012-02-14 21:47:49 +0100
committerNikos Mavrogiannopoulos <nmav@gnutls.org>2012-02-14 21:47:49 +0100
commitafd7a0a2adb959f854d9ed93c4e3becc5d120eb4 (patch)
tree9c34926985346ff1bbac1aae7bc3ed12ba17a278
parent63918fc4dbfbec9615ffe788a7237fb9e49375f2 (diff)
downloadgnutls-afd7a0a2adb959f854d9ed93c4e3becc5d120eb4.tar.gz
Added ability to store commitments (hashes) of public keys.
-rw-r--r--doc/cha-bib.texi4
-rw-r--r--doc/cha-cert-auth.texi24
-rw-r--r--doc/latex/gnutls.bib9
-rw-r--r--lib/includes/gnutls/gnutls.h.in17
-rw-r--r--lib/verify-ssh.c260
5 files changed, 277 insertions, 37 deletions
diff --git a/doc/cha-bib.texi b/doc/cha-bib.texi
index c88d9f6ac5..965eeaf358 100644
--- a/doc/cha-bib.texi
+++ b/doc/cha-bib.texi
@@ -17,6 +17,10 @@ Peter Gutmann, "Everything you never wanted to know about PKI but were
forced to find out", Available from
@url{http://www.cs.auckland.ac.nz/~pgut001/}.
+@item @anchor{KEYPIN}[KEYPIN]
+Chris Evans and Chris Palmer, "Public Key Pinning Extension for HTTP",
+Available from @url{http://tools.ietf.org/html/draft-ietf-websec-key-pinning-01}.
+
@item @anchor{NISTSP80057}[NISTSP80057]
NIST Special Publication 800-57, "Recommendation for Key Management -
Part 1: General (Revised)", March 2007, available from
diff --git a/doc/cha-cert-auth.texi b/doc/cha-cert-auth.texi
index f6824070e5..4cb6fb5846 100644
--- a/doc/cha-cert-auth.texi
+++ b/doc/cha-cert-auth.texi
@@ -297,6 +297,11 @@ shown in @ref{Simple client example with SSH-style certificate verification}.
@showfuncdesc{gnutls_verify_stored_pubkey}
@showfuncdesc{gnutls_store_pubkey}
+@showfuncdesc{gnutls_store_commitment}
+
+The @funcref{gnutls_store_commitment} may be used to implement a
+key-pinning architecture as in @xcite{KEYPIN}.
+http://tools.ietf.org/html/draft-ietf-websec-key-pinning-01
The storage and verification functions may be used with the default
text file based backend, or another backend may be specified. Such
@@ -308,16 +313,25 @@ of those functions is shown below.
const char* service,
time_t expiration,
const gnutls_datum_t* pubkey);
-
+ typedef int (*gnutls_trust_db_store_commitment_func) (const char* db_name,
+ const char* host,
+ const char* service,
+ time_t expiration,
+ gnutls_digest_algorithm_t halgo,
+ const char* hash);
+
typedef int (*gnutls_trust_db_retr_func) (const char* db_name,
const char* host,
const char* service,
const gnutls_datum_t *pubkey);
- typedef struct @{
- gnutls_trust_db_store_func store;
- gnutls_trust_db_retr_func retrieve;
- @} trust_storage_st;
+ typedef struct
+ @{
+ gnutls_trust_db_store_func store;
+ gnutls_trust_db_store_commitment_func cstore;
+ gnutls_trust_db_retr_func retrieve;
+ @}
+ trust_storage_st;
@end example
@node OpenPGP certificates
diff --git a/doc/latex/gnutls.bib b/doc/latex/gnutls.bib
index 98afad6d2c..68c7e1fbdd 100644
--- a/doc/latex/gnutls.bib
+++ b/doc/latex/gnutls.bib
@@ -1,3 +1,12 @@
+@Misc{KEYPIN,
+ author = "Chris Evans and Chris Palmer",
+ title = "{Public Key Pinning Extension for HTTP}",
+ month = "December",
+ year = "2011",
+ note = "Available from \url{http://tools.ietf.org/html/draft-ietf-websec-key-pinning-01}",
+ url = "http://tools.ietf.org/html/draft-ietf-websec-key-pinning-01"
+}
+
@Misc{ RFC2246,
author = "Tim Dierks and Christopher Allen",
title = "{The TLS Protocol Version 1.0}",
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index c285427014..0c26301e7f 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -1665,6 +1665,13 @@ gnutls_ecc_curve_t gnutls_ecc_curve_get(gnutls_session_t session);
const char* service,
time_t expiration,
const gnutls_datum_t* pubkey);
+
+ typedef int (*gnutls_trust_db_store_commitment_func) (const char* db_name,
+ const char* host,
+ const char* service,
+ time_t expiration,
+ gnutls_digest_algorithm_t hash_algo,
+ const char* hash);
/* searches for the provided host/service pair that match the
* provided public key in the database. */
@@ -1675,6 +1682,7 @@ gnutls_ecc_curve_t gnutls_ecc_curve_get(gnutls_session_t session);
typedef struct {
gnutls_trust_db_store_func store;
+ gnutls_trust_db_store_commitment_func cstore;
gnutls_trust_db_retr_func retrieve;
} trust_storage_st;
@@ -1685,6 +1693,15 @@ gnutls_ecc_curve_t gnutls_ecc_curve_get(gnutls_session_t session);
gnutls_certificate_type_t cert_type,
const gnutls_datum_t * cert, unsigned int flags);
+ 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 char* hash,
+ time_t expiration,
+ unsigned int flags);
+
int gnutls_store_pubkey(const char* db_name,
const trust_storage_st * tdb,
const char* host,
diff --git a/lib/verify-ssh.c b/lib/verify-ssh.c
index 9135b41a50..f11e3205de 100644
--- a/lib/verify-ssh.c
+++ b/lib/verify-ssh.c
@@ -35,12 +35,18 @@
#include <gnutls/abstract.h>
#include <system.h>
-static int raw_pubkey_to_base64(gnutls_datum_t* pubkey);
+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 char* hash);
static
int store_pubkey(const char* db_name, const char* host,
const char* service, time_t expiration, const gnutls_datum_t* pubkey);
@@ -51,6 +57,7 @@ static int find_config_file(char* file, size_t max_size);
static const trust_storage_st default_storage =
{
store_pubkey,
+ store_commitment,
find_stored_pubkey
};
@@ -125,13 +132,6 @@ char local_file[MAX_FILENAME];
goto cleanup;
}
- ret = raw_pubkey_to_base64(&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);
@@ -141,7 +141,7 @@ cleanup:
return ret;
}
-static int parse_line(char* line,
+static int parse_commitment_line(char* line,
const char* host, size_t host_len,
const char* service, size_t service_len,
time_t now,
@@ -149,6 +149,88 @@ static int parse_line(char* line,
{
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;
@@ -157,6 +239,9 @@ time_t expiration;
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);
@@ -164,7 +249,7 @@ time_t expiration;
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);
@@ -194,10 +279,10 @@ time_t expiration;
if (p != NULL) *p = 0;
kp_len = strlen(kp);
- if (kp_len != skey->size)
+ if (kp_len != b64key->size)
return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH);
- if (memcmp(kp, skey->data, skey->size) != 0)
+ if (memcmp(kp, b64key->data, b64key->size) != 0)
return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH);
/* key found and matches */
@@ -208,7 +293,7 @@ time_t expiration;
*/
static int find_stored_pubkey(const char* file,
const char* host, const char* service,
- const gnutls_datum_t* skey)
+ const gnutls_datum_t* pubkey)
{
FILE* fd;
char* line = NULL;
@@ -216,20 +301,28 @@ 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)
- return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
+ {
+ 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, skey);
+ ret = parse_line(line, host, host_len, service, service_len, now, pubkey, &b64key);
if (ret == 0) /* found */
{
goto cleanup;
@@ -247,24 +340,25 @@ time_t now = gnutls_time(0);
cleanup:
free(line);
- fclose(fd);
+ if (fd != NULL)
+ fclose(fd);
+ gnutls_free(b64key.data);
return ret;
}
-static int raw_pubkey_to_base64(gnutls_datum_t* pubkey)
+static int raw_pubkey_to_base64(const gnutls_datum_t* raw, gnutls_datum_t * b64)
{
int ret;
char* out;
- ret = base64_encode_alloc((void*)pubkey->data, pubkey->size, &out);
+ ret = base64_encode_alloc((void*)raw->data, raw->size, &out);
if (ret == 0)
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
- gnutls_free(pubkey->data);
- pubkey->data = (void*)out;
- pubkey->size = ret;
-
+ b64->data = (void*)out;
+ b64->size = ret;
+
return 0;
}
@@ -407,17 +501,54 @@ int store_pubkey(const char* db_name, const char* host,
const char* service, time_t expiration,
const gnutls_datum_t* pubkey)
{
-FILE* fd;
+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)
- return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
+ {
+ 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 char* hash)
+{
+FILE* fd;
+
+ 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, hash);
fclose(fd);
@@ -435,7 +566,7 @@ FILE* fd;
* @expiration: The expiration time (use 0 to disable expiration)
* @flags: should be 0.
*
- * This function will store to verify the provided certificate to
+ * 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.
*
@@ -495,13 +626,6 @@ char local_file[MAX_FILENAME];
gnutls_assert();
goto cleanup;
}
-
- ret = raw_pubkey_to_base64(&pubkey);
- if (ret < 0)
- {
- gnutls_assert();
- goto cleanup;
- }
_gnutls_debug_log("Configuration file: %s\n", db_name);
@@ -516,6 +640,78 @@ cleanup:
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 hex-encoded 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 char* 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 (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)