summaryrefslogtreecommitdiffstats
path: root/src/danetool.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/danetool.c')
-rw-r--r--src/danetool.c742
1 files changed, 742 insertions, 0 deletions
diff --git a/src/danetool.c b/src/danetool.c
new file mode 100644
index 0000000..add5d05
--- /dev/null
+++ b/src/danetool.c
@@ -0,0 +1,742 @@
+/*
+ * 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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/openpgp.h>
+#include <gnutls/pkcs12.h>
+#include <gnutls/pkcs11.h>
+#include <gnutls/abstract.h>
+#include <gnutls/crypto.h>
+
+#ifdef HAVE_DANE
+#include <gnutls/dane.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/* Gnulib portability files. */
+#include <read-file.h>
+#include <minmax.h>
+
+#include <common.h>
+#include "danetool-options.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))
+ 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;i<entries;i++) {
+ size_t str_size;
+ t.data = (void*)dane_data[i];
+ t.size = dane_data_len[i];
+
+ str_size = t.size * 2 + 1;
+ str = gnutls_malloc(str_size);
+
+ ret = gnutls_hex_encode(&t, str, &str_size);
+ if (ret < 0) {
+ fprintf(stderr, "gnutls_hex_encode: %s\n",
+ dane_strerror(ret));
+ retcode = 1;
+ goto error;
+ }
+ fprintf(outfile, "[%u]: %s\n", i, str);
+ gnutls_free(str);
+ }
+ fprintf(outfile, "\n");
+ }
+
+ if (cinfo->cert) {
+ 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;i<cert_list_size;i++) {
+ ret = gnutls_pem_base64_encode_alloc("CERTIFICATE", &cert_list[i], &t);
+ if (ret < 0) {
+ fprintf(stderr, "error[%d]: %s\n", __LINE__,
+ gnutls_strerror(ret));
+ app_exit(1);
+ }
+
+ write(priv->fd, 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);
+
+ ret = gnutls_set_default_priority(session);
+ if (ret < 0) {
+ fprintf(stderr, "error[%d]: %s\n", __LINE__,
+ gnutls_strerror(ret));
+ app_exit(1);
+ }
+
+ 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