/*
* Copyright (C) 2012 KU Leuven
*
* Author: Nikos Mavrogiannopoulos
*
* This file is part of libdane.
*
* The libdane library 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 2.1 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
#include
#include
#include
#include
#include
#include
#include "../lib/gnutls_int.h"
#define MAX_DATA_ENTRIES 4
#ifdef DEBUG
# define gnutls_assert() fprintf(stderr, "ASSERT: %s: %d\n", __FILE__, __LINE__);
# define gnutls_assert_val(x) gnutls_assert_val_int(x, __FILE__, __LINE__)
static int gnutls_assert_val_int (int val, const char *file, int line)
{
fprintf(stderr, "ASSERT: %s: %d\n", file, line);
return val;
}
#else
# define gnutls_assert()
# define gnutls_assert_val(x) (x)
#endif
struct dane_state_st
{
struct ub_ctx* ctx;
unsigned int flags;
};
struct dane_query_st
{
struct ub_result* result;
unsigned int data_entries;
dane_cert_usage_t usage[MAX_DATA_ENTRIES];
dane_cert_type_t type[MAX_DATA_ENTRIES];
dane_match_type_t match[MAX_DATA_ENTRIES];
gnutls_datum_t data[MAX_DATA_ENTRIES];
unsigned int flags;
dane_query_status_t status;
};
/**
* dane_query_status:
* @q: The query result structure
*
* This function will return the status of the query response.
* See %dane_query_status_t for the possible types.
*
* Returns: The status type.
**/
dane_query_status_t dane_query_status(dane_query_t q)
{
return q->status;
}
/**
* dane_query_entries:
* @q: The query result structure
*
* This function will return the number of entries in a query.
*
* Returns: The number of entries.
**/
unsigned int dane_query_entries(dane_query_t q)
{
return q->data_entries;
}
/**
* dane_query_data:
* @q: The query result structure
* @idx: The index of the query response.
* @usage: The certificate usage (see %dane_cert_usage_t)
* @type: The certificate type (see %dane_cert_type_t)
* @match: The DANE matching type (see %dane_match_type_t)
* @data: The DANE data.
*
* This function will provide the DANE data from the query
* response.
*
* Returns: On success, %DANE_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int dane_query_data(dane_query_t q, unsigned int idx,
unsigned int *usage, unsigned int *type,
unsigned int *match, gnutls_datum_t * data)
{
if (idx >= q->data_entries)
return gnutls_assert_val(DANE_E_REQUESTED_DATA_NOT_AVAILABLE);
if (usage)
*usage = q->usage[idx];
if (type)
*type = q->type[idx];
if (match)
*match = q->match[idx];
if (data) {
data->data = q->data[idx].data;
data->size = q->data[idx].size;
}
return DANE_E_SUCCESS;
}
/**
* dane_state_init:
* @s: The structure to be initialized
* @flags: flags from the %dane_state_flags enumeration
*
* This function will initialize a DANE query structure.
*
* Returns: On success, %DANE_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int dane_state_init(dane_state_t* s, unsigned int flags)
{
struct ub_ctx* ctx;
int ret;
*s = calloc(1, sizeof(struct dane_state_st));
if (*s == NULL)
return gnutls_assert_val(DANE_E_MEMORY_ERROR);
ctx = ub_ctx_create();
if(!ctx) {
gnutls_assert();
ret = DANE_E_INITIALIZATION_ERROR;
goto cleanup;
}
ub_ctx_debugout(ctx, stderr);
if (!(flags & DANE_F_IGNORE_LOCAL_RESOLVER)) {
if( (ret=ub_ctx_resolvconf(ctx, NULL)) != 0) {
gnutls_assert();
ret = DANE_E_INITIALIZATION_ERROR;
goto cleanup;
}
if( (ret=ub_ctx_hosts(ctx, NULL)) != 0) {
gnutls_assert();
ret = DANE_E_INITIALIZATION_ERROR;
goto cleanup;
}
}
/* read public keys for DNSSEC verification */
if( (ret=ub_ctx_add_ta_file(ctx, (char*)UNBOUND_ROOT_KEY_FILE)) != 0) {
gnutls_assert();
ret = DANE_E_INITIALIZATION_ERROR;
goto cleanup;
}
(*s)->ctx = ctx;
(*s)->flags = flags;
return DANE_E_SUCCESS;
cleanup:
if (ctx)
ub_ctx_delete(ctx);
free(*s);
return ret;
}
/**
* dane_state_deinit:
* @s: The structure to be deinitialized
*
* This function will deinitialize a DANE query structure.
*
**/
void dane_state_deinit(dane_state_t s)
{
ub_ctx_delete(s->ctx);
free(s);
}
/**
* dane_state_set_dlv_file:
* @s: The structure to be deinitialized
* @file: The file holding the DLV keys.
*
* This function will set a file with trusted keys
* for DLV (DNSSEC Lookaside Validation).
*
**/
int dane_state_set_dlv_file(dane_state_t s, const char* file)
{
int ret;
ret = ub_ctx_set_option(s->ctx, (char*)"dlv-anchor-file:", (void*)file);
if (ret != 0)
return gnutls_assert_val(DANE_E_FILE_ERROR);
return 0;
}
/**
* dane_query_deinit:
* @q: The structure to be deinitialized
*
* This function will deinitialize a DANE query result structure.
*
**/
void dane_query_deinit(dane_query_t q)
{
ub_resolve_free(q->result);
free(q);
}
/**
* dane_query_tlsa:
* @s: The DANE state structure
* @r: A structure to place the result
* @host: The host name to resolve.
* @proto: The protocol type (tcp, udp, etc.)
* @port: The service port number (eg. 443).
*
* This function will query the DNS server for the TLSA (DANE)
* data for the given host.
*
* Returns: On success, %DANE_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int dane_query_tlsa(dane_state_t s, dane_query_t *r, const char* host, const char* proto, unsigned int port)
{
char ns[1024];
int ret;
unsigned int i;
*r = calloc(1, sizeof(struct dane_query_st));
if (*r == NULL)
return gnutls_assert_val(DANE_E_MEMORY_ERROR);
snprintf(ns, sizeof(ns), "_%u._%s.%s", port, proto, host);
/* query for webserver */
ret = ub_resolve(s->ctx, ns, 52, 1, &(*r)->result);
if(ret != 0) {
return gnutls_assert_val(DANE_E_RESOLVING_ERROR);
}
/* show first result */
if(!(*r)->result->havedata) {
return gnutls_assert_val(DANE_E_NO_DANE_DATA);
}
i = 0;
do {
if ((*r)->result->len[i] > 3)
ret = DANE_E_SUCCESS;
else {
return gnutls_assert_val(DANE_E_RECEIVED_CORRUPT_DATA);
}
(*r)->usage[i] = (*r)->result->data[i][0];
(*r)->type[i] = (*r)->result->data[i][1];
(*r)->match[i] = (*r)->result->data[i][2];
(*r)->data[i].data = (void*)&(*r)->result->data[i][3];
(*r)->data[i].size = (*r)->result->len[i] - 3;
i++;
} while((*r)->result->data[i] != NULL);
(*r)->data_entries = i;
if (!(s->flags & DANE_F_INSECURE) && !(*r)->result->secure) {
if ((*r)->result->bogus)
ret = gnutls_assert_val(DANE_E_INVALID_DNSSEC_SIG);
else
ret = gnutls_assert_val(DANE_E_NO_DNSSEC_SIG);
}
/* show security status */
if ((*r)->result->secure) {
(*r)->status = DANE_QUERY_DNSSEC_VERIFIED;
} else if ((*r)->result->bogus) {
gnutls_assert();
(*r)->status = DANE_QUERY_BOGUS;
} else {
gnutls_assert();
(*r)->status = DANE_QUERY_NO_DNSSEC;
}
return ret;
}
static unsigned int matches(const gnutls_datum_t *raw1, const gnutls_datum_t *raw2,
dane_match_type_t match)
{
uint8_t digest[64];
int ret;
if (match == DANE_MATCH_EXACT) {
if (raw1->size != raw2->size)
return gnutls_assert_val(0);
if (memcmp(raw1->data, raw2->data, raw1->size) != 0)
return gnutls_assert_val(0);
return 1;
} else if (match == DANE_MATCH_SHA2_256) {
if (raw2->size != 32)
return gnutls_assert_val(0);
ret = gnutls_hash_fast(GNUTLS_DIG_SHA256, raw1->data, raw1->size, digest);
if (ret < 0)
return gnutls_assert_val(0);
if (memcmp(digest, raw2->data, 32) != 0)
return gnutls_assert_val(0);
return 1;
} else if (match == DANE_MATCH_SHA2_512) {
if (raw2->size != 64)
return gnutls_assert_val(0);
ret = gnutls_hash_fast(GNUTLS_DIG_SHA512, raw1->data, raw1->size, digest);
if (ret < 0)
return gnutls_assert_val(0);
if (memcmp(digest, raw2->data, 64) != 0)
return gnutls_assert_val(0);
return 1;
}
return gnutls_assert_val(0);
}
static int crt_to_pubkey(const gnutls_datum_t *raw_crt, gnutls_datum_t * out)
{
gnutls_pubkey_t pub = NULL;
gnutls_x509_crt_t crt = NULL;
int ret;
out->data = NULL;
ret = gnutls_x509_crt_init(&crt);
if (ret < 0)
return gnutls_assert_val(DANE_E_PUBKEY_ERROR);
ret = gnutls_pubkey_init( &pub);
if (ret < 0) {
gnutls_assert();
ret = DANE_E_PUBKEY_ERROR;
goto cleanup;
}
ret = gnutls_x509_crt_import(crt, raw_crt, GNUTLS_X509_FMT_DER);
if (ret < 0) {
gnutls_assert();
ret = DANE_E_PUBKEY_ERROR;
goto cleanup;
}
ret = gnutls_pubkey_import_x509(pub, crt, 0);
if (ret < 0) {
gnutls_assert();
ret = DANE_E_PUBKEY_ERROR;
goto cleanup;
}
ret = gnutls_pubkey_export2(pub, GNUTLS_X509_FMT_DER, out);
if (ret < 0) {
gnutls_assert();
ret = DANE_E_PUBKEY_ERROR;
goto cleanup;
}
ret = 0;
goto clean_certs;
cleanup:
free(out->data);
clean_certs:
if (pub)
gnutls_pubkey_deinit(pub);
if (crt)
gnutls_x509_crt_deinit(crt);
return ret;
}
static int verify_ca(const gnutls_datum_t *raw_crt, unsigned raw_crt_size,
gnutls_certificate_type_t crt_type,
dane_cert_type_t ctype,
dane_match_type_t match, gnutls_datum_t * data,
unsigned int *verify)
{
gnutls_datum_t pubkey = {NULL, 0};
int ret;
if (raw_crt_size < 2)
return gnutls_assert_val(DANE_E_INVALID_REQUEST);
if (ctype == DANE_CERT_X509 && crt_type == GNUTLS_CRT_X509) {
if (!matches(&raw_crt[1], data, match)) {
gnutls_assert();
*verify |= DANE_VERIFY_CA_CONSTRAINS_VIOLATED;
}
} else if (ctype == DANE_CERT_PK && crt_type == GNUTLS_CRT_X509) {
ret = crt_to_pubkey(&raw_crt[1], &pubkey);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
if (!matches(&pubkey, data, match)) {
gnutls_assert();
*verify |= DANE_VERIFY_CA_CONSTRAINS_VIOLATED;
}
}
ret = 0;
cleanup:
free(pubkey.data);
return ret;
}
static int verify_ee(const gnutls_datum_t *raw_crt, gnutls_certificate_type_t crt_type,
dane_cert_type_t ctype, dane_match_type_t match, gnutls_datum_t * data,
unsigned int *verify)
{
gnutls_datum_t pubkey = {NULL, 0};
int ret;
if (ctype == DANE_CERT_X509 && crt_type == GNUTLS_CRT_X509) {
if (!matches(raw_crt, data, match)) {
gnutls_assert();
*verify |= DANE_VERIFY_CERT_DIFFERS;
}
} else if (ctype == DANE_CERT_PK && crt_type == GNUTLS_CRT_X509) {
ret = crt_to_pubkey(raw_crt, &pubkey);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
if (!matches(&pubkey, data, match)) {
gnutls_assert();
*verify |= DANE_VERIFY_CERT_DIFFERS;
}
}
ret = 0;
cleanup:
free(pubkey.data);
return ret;
}
/**
* dane_verify_crt:
* @s: A DANE state structure (may be NULL)
* @chain: A certificate chain
* @chain_size: The size of the chain
* @chain_type: The type of the certificate chain
* @hostname: The hostname associated with the chain
* @proto: The protocol of the service connecting (e.g. tcp)
* @port: The port of the service connecting (e.g. 443)
* @sflags: Flags for the the initialization of @s (if NULL)
* @vflags: Verification flags; should be zero
* @verify: An OR'ed list of %dane_verify_status_t.
*
* This function will verify the given certificate chain against the
* CA constrains and/or the certificate available via DANE.
* If no information via DANE can be obtained the flag %DANE_VERIFY_NO_DANE_INFO
* is set. If a DNSSEC signature is not available for the DANE
* record then the verify flag %DANE_VERIFY_NO_DNSSEC_DATA is set.
*
* Due to the many possible options of DANE, there is no single threat
* model countered. When notifying the user about DANE verification results
* it may be better to mention: DANE verification did not reject the certificate,
* rather than mentioning a successful DANE verication.
*
* If the @q parameter is provided it will be used for caching entries.
*
* Returns: On success, %DANE_E_SUCCESS (0) is returned, otherwise a
* negative error value.
*
**/
int dane_verify_crt (dane_state_t s,
const gnutls_datum_t *chain, unsigned chain_size,
gnutls_certificate_type_t chain_type,
const char * hostname, const char* proto, unsigned int port,
unsigned int sflags, unsigned int vflags,
unsigned int *verify)
{
dane_state_t _s = NULL;
dane_query_t r = NULL;
int ret;
unsigned int usage, type, match, idx;
gnutls_datum_t data;
if (chain_type != GNUTLS_CRT_X509)
return gnutls_assert_val(DANE_E_INVALID_REQUEST);
*verify = 0;
if (s == NULL) {
ret = dane_state_init(&_s, sflags);
if (ret < 0) {
gnutls_assert();
return ret;
}
} else
_s = s;
ret = dane_query_tlsa(_s, &r, hostname, proto, port);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
idx = 0;
do {
ret = dane_query_data(r, idx++, &usage, &type, &match, &data);
if (ret == DANE_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
if (usage == DANE_CERT_USAGE_LOCAL_CA || usage == DANE_CERT_USAGE_CA) {
ret = verify_ca(chain, chain_size, chain_type, type, match, &data, verify);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
} else if (usage == DANE_CERT_USAGE_LOCAL_EE || usage == DANE_CERT_USAGE_EE) {
ret = verify_ee(&chain[0], chain_type, type, match, &data, verify);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
}
} while(1);
ret = 0;
cleanup:
if (s == NULL) dane_state_deinit(_s);
if (r != NULL) dane_query_deinit(r);
return ret;
}
/**
* dane_verify_session_crt:
* @s: A DANE state structure (may be NULL)
* @session: A gnutls session
* @hostname: The hostname associated with the chain
* @proto: The protocol of the service connecting (e.g. tcp)
* @port: The port of the service connecting (e.g. 443)
* @sflags: Flags for the the initialization of @s (if NULL)
* @vflags: Verification flags; should be zero
* @verify: An OR'ed list of %dane_verify_status_t.
*
* This function will verify session's certificate chain against the
* CA constrains and/or the certificate available via DANE.
* See dane_verify_crt() for more information.
*
* Returns: On success, %DANE_E_SUCCESS (0) is returned, otherwise a
* negative error value.
*
**/
int dane_verify_session_crt (
dane_state_t s,
gnutls_session_t session,
const char * hostname, const char* proto, unsigned int port,
unsigned int sflags, unsigned int vflags,
unsigned int *verify)
{
const gnutls_datum_t *cert_list;
unsigned int cert_list_size = 0;
unsigned int type;
cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
if (cert_list_size == 0) {
return gnutls_assert_val(DANE_E_NO_CERT);
}
type = gnutls_certificate_type_get(session);
return dane_verify_crt(s, cert_list, cert_list_size, type, hostname, proto, port, sflags, vflags, verify);
}
/**
* dane_verification_status_print:
* @status: The status flags to be printed
* @type: The certificate type
* @out: Newly allocated datum with (0) terminated string.
* @flags: should be zero
*
* This function will pretty print the status of a verification
* process -- eg. the one obtained by dane_verify_crt().
*
* The output @out needs to be deallocated using gnutls_free().
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
dane_verification_status_print (unsigned int status,
gnutls_datum_t * out, unsigned int flags)
{
gnutls_buffer_st str;
int ret;
_gnutls_buffer_init (&str);
if (status == 0)
_gnutls_buffer_append_str (&str, _("Certificate matches. "));
else
_gnutls_buffer_append_str (&str, _("Verification failed. "));
if (status & DANE_VERIFY_CA_CONSTRAINS_VIOLATED)
_gnutls_buffer_append_str (&str, _("CA constrains were violated. "));
if (status & DANE_VERIFY_CERT_DIFFERS)
_gnutls_buffer_append_str (&str, _("The certificate differs. "));
if (status & DANE_VERIFY_NO_DANE_INFO)
_gnutls_buffer_append_str (&str, _("There were no DANE information. "));
ret = _gnutls_buffer_to_datum( &str, out);
if (out->size > 0) out->size--;
return ret;
}