/*
* Copyright (C) 2012 Free Software Foundation, Inc.
*
* This file is part of GnuTLS.
*
* GnuTLS is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GnuTLS 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_DANE
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Gnulib portability files. */
#include
#include
#include
#include "danetool-args.h"
#include "certtool-common.h"
#include "socket.h"
static const char *obtain_cert(const char *hostname, const char *proto, const char *service,
const char *app_proto, unsigned quiet);
static void cmd_parser(int argc, char **argv);
static void dane_info(const char *host, const char *proto,
const char *service, unsigned int ca,
unsigned int domain, common_info_st * cinfo);
static void dane_check(const char *host, const char *proto,
const char *service, common_info_st * cinfo);
FILE *outfile;
static const char *outfile_name = NULL;
static gnutls_digest_algorithm_t default_dig;
/* non interactive operation if set
*/
int batch = 0;
int ask_pass = 0;
void app_exit(int val)
{
if (val != 0) {
if (outfile_name)
(void)remove(outfile_name);
}
exit(val);
}
static void tls_log_func(int level, const char *str)
{
fprintf(stderr, "|<%d>| %s", level, str);
}
int main(int argc, char **argv)
{
fix_lbuffer(0);
cmd_parser(argc, argv);
return 0;
}
static void cmd_parser(int argc, char **argv)
{
int ret, privkey_op = 0;
common_info_st cinfo;
const char *proto = "tcp";
char service[32] = "443";
optionProcess(&danetoolOptions, argc, argv);
if (HAVE_OPT(OUTFILE)) {
outfile = safe_open_rw(OPT_ARG(OUTFILE), privkey_op);
if (outfile == NULL) {
fprintf(stderr, "%s", OPT_ARG(OUTFILE));
app_exit(1);
}
outfile_name = OPT_ARG(OUTFILE);
} else
outfile = stdout;
default_dig = GNUTLS_DIG_UNKNOWN;
if (HAVE_OPT(HASH)) {
if (strcasecmp(OPT_ARG(HASH), "md5") == 0) {
fprintf(stderr,
"Warning: MD5 is broken, and should not be used any more for digital signatures.\n");
default_dig = GNUTLS_DIG_MD5;
} else if (strcasecmp(OPT_ARG(HASH), "sha1") == 0)
default_dig = GNUTLS_DIG_SHA1;
else if (strcasecmp(OPT_ARG(HASH), "sha256") == 0)
default_dig = GNUTLS_DIG_SHA256;
else if (strcasecmp(OPT_ARG(HASH), "sha224") == 0)
default_dig = GNUTLS_DIG_SHA224;
else if (strcasecmp(OPT_ARG(HASH), "sha384") == 0)
default_dig = GNUTLS_DIG_SHA384;
else if (strcasecmp(OPT_ARG(HASH), "sha512") == 0)
default_dig = GNUTLS_DIG_SHA512;
else if (strcasecmp(OPT_ARG(HASH), "rmd160") == 0)
default_dig = GNUTLS_DIG_RMD160;
else {
fprintf(stderr, "invalid hash: %s", OPT_ARG(HASH));
app_exit(1);
}
}
gnutls_global_set_log_function(tls_log_func);
if (HAVE_OPT(DEBUG)) {
gnutls_global_set_log_level(OPT_VALUE_DEBUG);
printf("Setting log level to %d\n", (int) OPT_VALUE_DEBUG);
}
if ((ret = gnutls_global_init()) < 0) {
fprintf(stderr, "global_init: %s", gnutls_strerror(ret));
app_exit(1);
}
#ifdef ENABLE_PKCS11
pkcs11_common(NULL);
#endif
memset(&cinfo, 0, sizeof(cinfo));
if (HAVE_OPT(INDER) || HAVE_OPT(INRAW))
cinfo.incert_format = GNUTLS_X509_FMT_DER;
else
cinfo.incert_format = GNUTLS_X509_FMT_PEM;
if (HAVE_OPT(VERBOSE))
cinfo.verbose = 1;
if (HAVE_OPT(LOAD_PUBKEY))
cinfo.pubkey = OPT_ARG(LOAD_PUBKEY);
if (HAVE_OPT(LOAD_CERTIFICATE))
cinfo.cert = OPT_ARG(LOAD_CERTIFICATE);
if (HAVE_OPT(PORT)) {
snprintf(service, sizeof(service), "%s", OPT_ARG(PORT));
} else {
if (HAVE_OPT(STARTTLS_PROTO))
snprintf(service, sizeof(service), "%s", starttls_proto_to_service(OPT_ARG(STARTTLS_PROTO)));
}
if (HAVE_OPT(PROTO))
proto = OPT_ARG(PROTO);
if (HAVE_OPT(TLSA_RR))
dane_info(OPT_ARG(HOST), proto, service,
HAVE_OPT(CA), ENABLED_OPT(DOMAIN), &cinfo);
else if (HAVE_OPT(CHECK))
dane_check(OPT_ARG(CHECK), proto, service, &cinfo);
else
USAGE(1);
fclose(outfile);
#ifdef ENABLE_PKCS11
gnutls_pkcs11_deinit();
#endif
gnutls_global_deinit();
}
#define MAX_CLIST_SIZE 32
static void dane_check(const char *host, const char *proto,
const char *service, common_info_st * cinfo)
{
#ifdef HAVE_DANE
dane_state_t s;
dane_query_t q;
int ret, retcode = 1;
unsigned entries;
unsigned int flags = DANE_F_IGNORE_LOCAL_RESOLVER, i;
unsigned int usage, type, match;
gnutls_datum_t data, file;
size_t size;
unsigned del = 0;
unsigned vflags = DANE_VFLAG_FAIL_IF_NOT_CHECKED;
const char *cstr;
char *str;
gnutls_x509_crt_t *clist = NULL;
unsigned int clist_size = 0;
gnutls_datum_t certs[MAX_CLIST_SIZE];
int port = service_to_port(service, proto);
if (ENABLED_OPT(LOCAL_DNS))
flags = 0;
if (HAVE_OPT(INSECURE))
flags |= DANE_F_INSECURE;
if (HAVE_OPT(CHECK_EE))
vflags |= DANE_VFLAG_ONLY_CHECK_EE_USAGE;
if (HAVE_OPT(CHECK_CA))
vflags |= DANE_VFLAG_ONLY_CHECK_CA_USAGE;
if (!cinfo->cert) {
const char *app_proto = NULL;
if (HAVE_OPT(STARTTLS_PROTO))
app_proto = OPT_ARG(STARTTLS_PROTO);
cinfo->cert = obtain_cert(host, proto, service, app_proto, HAVE_OPT(QUIET));
del = 1;
}
if (!HAVE_OPT(QUIET))
fprintf(stderr, "Querying DNS for %s (%s:%d)...\n", host, proto, port);
ret = dane_state_init(&s, flags);
if (ret < 0) {
fprintf(stderr, "dane_state_init: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
if (HAVE_OPT(DLV)) {
ret = dane_state_set_dlv_file(s, OPT_ARG(DLV));
if (ret < 0) {
fprintf(stderr, "dane_state_set_dlv_file: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
}
ret = dane_query_tlsa(s, &q, host, proto, port);
if (ret < 0) {
fprintf(stderr, "dane_query_tlsa: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
if (ENABLED_OPT(PRINT_RAW)) {
gnutls_datum_t t;
char **dane_data;
int *dane_data_len;
int secure;
int bogus;
ret = dane_query_to_raw_tlsa(q, &entries, &dane_data,
&dane_data_len, &secure, &bogus);
if (ret < 0) {
fprintf(stderr, "dane_query_to_raw_tlsa: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
for (i=0;icert) {
ret = gnutls_load_file(cinfo->cert, &file);
if (ret < 0) {
fprintf(stderr, "gnutls_load_file: %s\n",
gnutls_strerror(ret));
retcode = 1;
goto error;
}
ret =
gnutls_x509_crt_list_import2(&clist,
&clist_size,
&file,
cinfo->
incert_format, 0);
if (ret < 0) {
fprintf(stderr,
"gnutls_x509_crt_list_import2: %s\n",
gnutls_strerror(ret));
retcode = 1;
goto error;
}
if (clist_size > 0) {
for (i = 0; i < MIN(MAX_CLIST_SIZE,clist_size); i++) {
ret =
gnutls_x509_crt_export2(clist
[i],
GNUTLS_X509_FMT_DER,
&certs
[i]);
if (ret < 0) {
fprintf(stderr,
"gnutls_x509_crt_export2: %s\n",
gnutls_strerror
(ret));
retcode = 1;
goto error;
}
}
}
}
entries = dane_query_entries(q);
for (i = 0; i < entries; i++) {
ret = dane_query_data(q, i, &usage, &type, &match, &data);
if (ret < 0) {
fprintf(stderr, "dane_query_data: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
size = lbuffer_size;
ret = gnutls_hex_encode(&data, (void *) lbuffer, &size);
if (ret < 0) {
fprintf(stderr, "gnutls_hex_encode: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
if (entries > 1 && !HAVE_OPT(QUIET))
fprintf(outfile, "\n==== Entry %d ====\n", i + 1);
fprintf(outfile,
"_%u._%s.%s. IN TLSA ( %.2x %.2x %.2x %s )\n",
port, proto, host, usage, type, match, lbuffer);
if (!HAVE_OPT(QUIET)) {
cstr = dane_cert_usage_name(usage);
if (cstr == NULL) cstr= "Unknown";
fprintf(outfile, "Certificate usage: %s (%.2x)\n", cstr, usage);
cstr = dane_cert_type_name(type);
if (cstr == NULL) cstr= "Unknown";
fprintf(outfile, "Certificate type: %s (%.2x)\n", cstr, type);
cstr = dane_match_type_name(match);
if (cstr == NULL) cstr= "Unknown";
fprintf(outfile, "Contents: %s (%.2x)\n", cstr, match);
fprintf(outfile, "Data: %s\n", lbuffer);
}
/* Verify the DANE data */
if (cinfo->cert) {
unsigned int status;
gnutls_datum_t out;
ret =
dane_verify_crt(s, certs, clist_size,
GNUTLS_CRT_X509, host,
proto, port, 0, vflags,
&status);
if (ret < 0) {
fprintf(stderr,
"dane_verify_crt: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
ret =
dane_verification_status_print(status,
&out,
0);
if (ret < 0) {
fprintf(stderr,
"dane_verification_status_print: %s\n",
dane_strerror(ret));
retcode = 1;
goto error;
}
if (!HAVE_OPT(QUIET))
fprintf(outfile, "\nVerification: %s\n", out.data);
gnutls_free(out.data);
/* if there is at least one correct accept */
if (status == 0)
retcode = 0;
} else {
fprintf(stderr,
"\nCertificate could not be obtained. You can explicitly load the certificate using --load-certificate.\n");
}
}
if (clist_size > 0) {
for (i = 0; i < clist_size; i++) {
gnutls_free(certs[i].data);
gnutls_x509_crt_deinit(clist[i]);
}
gnutls_free(clist);
}
dane_query_deinit(q);
dane_state_deinit(s);
error:
if (del != 0 && cinfo->cert) {
(void)remove(cinfo->cert);
}
app_exit(retcode);
#else
fprintf(stderr,
"This functionality is disabled (GnuTLS was not compiled with support for DANE).\n");
return;
#endif
}
static void dane_info(const char *host, const char *proto,
const char *service, unsigned int ca,
unsigned int domain, common_info_st * cinfo)
{
gnutls_pubkey_t pubkey;
gnutls_x509_crt_t crt;
unsigned char digest[64];
gnutls_datum_t t;
int ret;
unsigned int usage, selector, type;
size_t size;
int port = service_to_port(service, proto);
if (proto == NULL)
proto = "tcp";
crt = load_cert(0, cinfo);
if (crt != NULL && HAVE_OPT(X509)) {
selector = 0; /* X.509 */
size = lbuffer_size;
ret =
gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER,
lbuffer, &size);
if (ret < 0) {
fprintf(stderr, "export error: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
gnutls_x509_crt_deinit(crt);
} else { /* use public key only */
selector = 1;
ret = gnutls_pubkey_init(&pubkey);
if (ret < 0) {
fprintf(stderr, "pubkey_init: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
if (crt != NULL) {
ret = gnutls_pubkey_import_x509(pubkey, crt, 0);
if (ret < 0) {
fprintf(stderr, "pubkey_import_x509: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
size = lbuffer_size;
ret =
gnutls_pubkey_export(pubkey,
GNUTLS_X509_FMT_DER,
lbuffer, &size);
if (ret < 0) {
fprintf(stderr, "pubkey_export: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
gnutls_x509_crt_deinit(crt);
} else {
pubkey = load_pubkey(1, cinfo);
size = lbuffer_size;
ret =
gnutls_pubkey_export(pubkey,
GNUTLS_X509_FMT_DER,
lbuffer, &size);
if (ret < 0) {
fprintf(stderr, "export error: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
}
gnutls_pubkey_deinit(pubkey);
}
if (default_dig != GNUTLS_DIG_SHA256
&& default_dig != GNUTLS_DIG_SHA512) {
if (default_dig != GNUTLS_DIG_UNKNOWN)
fprintf(stderr,
"Unsupported digest. Assuming SHA256.\n");
default_dig = GNUTLS_DIG_SHA256;
}
ret = gnutls_hash_fast(default_dig, lbuffer, size, digest);
if (ret < 0) {
fprintf(stderr, "hash error: %s\n", gnutls_strerror(ret));
app_exit(1);
}
if (default_dig == GNUTLS_DIG_SHA256)
type = 1;
else
type = 2;
/* DANE certificate classification crap */
if (domain == 0) {
if (ca)
usage = 0;
else
usage = 1;
} else {
if (ca)
usage = 2;
else
usage = 3;
}
t.data = digest;
t.size = gnutls_hash_get_len(default_dig);
size = lbuffer_size;
ret = gnutls_hex_encode(&t, (void *) lbuffer, &size);
if (ret < 0) {
fprintf(stderr, "hex encode error: %s\n",
gnutls_strerror(ret));
app_exit(1);
}
fprintf(outfile, "_%u._%s.%s. IN TLSA ( %.2x %.2x %.2x %s )\n",
port, proto, host, usage, selector, type, lbuffer);
}
struct priv_st {
int fd;
int found;
};
#ifdef HAVE_DANE
static int cert_callback(gnutls_session_t session)
{
const gnutls_datum_t *cert_list;
unsigned int cert_list_size = 0;
int ret;
unsigned i;
gnutls_datum_t t;
struct priv_st *priv;
cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
if (cert_list_size == 0) {
fprintf(stderr, "no certificates sent by server!\n");
return -1;
}
priv = gnutls_session_get_ptr(session);
for (i=0;ifd, t.data, t.size);
gnutls_free(t.data);
}
priv->found = 1;
return -1;
}
static gnutls_certificate_credentials_t xcred;
static int file_fd = -1;
static unsigned udp = 0;
gnutls_session_t init_tls_session(const char *hostname)
{
gnutls_session_t session;
int ret;
static struct priv_st priv;
priv.found = 0;
priv.fd = file_fd;
ret = gnutls_init(&session, (udp?GNUTLS_DATAGRAM:0)|GNUTLS_CLIENT);
if (ret < 0) {
fprintf(stderr, "error[%d]: %s\n", __LINE__,
gnutls_strerror(ret));
app_exit(1);
}
gnutls_session_set_ptr(session, &priv);
gnutls_set_default_priority(session);
if (hostname && is_ip(hostname)==0) {
gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname, strlen(hostname));
}
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
return session;
}
int do_handshake(socket_st * socket)
{
int ret;
do {
ret = gnutls_handshake(socket->session);
} while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_WARNING_ALERT_RECEIVED);
/* we don't care on the result */
return 0;
}
static const char *obtain_cert(const char *hostname, const char *proto, const char *service,
const char *app_proto, unsigned quiet)
{
socket_st hd;
const char *txt_service;
static char tmpfile[32];
int ret;
const char *str = "Obtaining certificate from";
int socket_flags = 0;
struct priv_st *priv;
ret = gnutls_certificate_allocate_credentials(&xcred);
if (ret < 0) {
fprintf(stderr, "error[%d]: %s\n", __LINE__,
gnutls_strerror(ret));
app_exit(1);
}
gnutls_certificate_set_verify_function(xcred, cert_callback);
if (strcmp(proto, "udp") == 0)
udp = 1;
else if (strcmp(proto, "tcp") != 0) {
/* we cannot handle this protocol */
return NULL;
}
strcpy(tmpfile, "danetool-certXXXXXX");
sockets_init();
txt_service = port_to_service(service, proto);
if (quiet)
str = NULL;
if (app_proto == NULL) app_proto = txt_service;
if (udp)
socket_flags |= SOCKET_FLAG_UDP;
umask(066);
file_fd = mkstemp(tmpfile);
if (file_fd == -1) {
int e = errno;
fprintf(stderr, "error[%d]: %s\n", __LINE__,
strerror(e));
app_exit(1);
}
socket_open(&hd, hostname, txt_service, app_proto, socket_flags|SOCKET_FLAG_STARTTLS, str, NULL);
close(file_fd);
ret = 0;
priv = gnutls_session_get_ptr(hd.session);
if (priv->found == 0)
ret = -1;
socket_bye(&hd, 1);
gnutls_certificate_free_credentials(xcred);
if (ret == -1)
return NULL;
else
return tmpfile;
}
#endif