diff options
30 files changed, 1124 insertions, 127 deletions
@@ -4,12 +4,18 @@ See the end for copying conditions. * Version 3.0.13 (unreleased) +** gnutls-cli: If no --x509cafile is provided a default is +assumed (/etc/ssl/certs/ca-certificates.crt). + ** ocsptool: Added --ask parameter, to verify a certificate's status from an ocsp server. ** command line apps: Use gnu autogen (libopts) to parse command line arguments and template files. +** libgnutls: Added new functions to easily allow the usage of +an SSH-style authentication. + ** libgnutls: SUITEB128 and SUITEB192 priority strings account for the RFC6460 requirements. @@ -28,6 +34,8 @@ of PKCS #11 modules. This is required on the child process after a fork. ** API and ABI modifications: +gnutls_verify_stored_pubkey: Added +gnutls_store_pubkey: Added gnutls_x509_crt_get_authority_key_gn_serial: Added gnutls_x509_crl_get_authority_key_gn_serial: Added gnutls_pkcs11_reinit: Added diff --git a/configure.ac b/configure.ac index 7531ae524a..42a84d498f 100644 --- a/configure.ac +++ b/configure.ac @@ -117,9 +117,8 @@ AC_C_BIGENDIAN dnl No fork on MinGW, disable some self-tests until we fix them. -AC_CHECK_FUNCS(fork,,) +AC_CHECK_FUNCS([fork getrusage getpwuid_r],,) AM_CONDITIONAL(HAVE_FORK, test "$ac_cv_func_fork" != "no") -AC_CHECK_FUNCS(getrusage,,) AC_LIB_HAVE_LINKFLAGS(pthread,, [#include <pthread.h>], [pthread_mutex_lock (0);]) dnl Check for p11-kit diff --git a/doc/cha-cert-auth.texi b/doc/cha-cert-auth.texi index 5253f5b368..7fb41a60dd 100644 --- a/doc/cha-cert-auth.texi +++ b/doc/cha-cert-auth.texi @@ -87,6 +87,7 @@ acceptable. The framework is illustrated on @ref{fig:x509}. * X.509 distinguished names:: * Verifying X.509 certificate paths:: * Verifying a certificate in the context of TLS session:: +* Verifying a certificate using SSH-style authentication:: @end menu @node X.509 certificate structure @@ -276,7 +277,20 @@ about the peer's identity. It is required to verify if the certificate's owner is the one you expect. For more information consult @xcite{RFC2818} and section @ref{ex:verify} for an example. +@node Verifying a certificate using SSH-style authentication +@subsection Verifying a certificate using SSH-style authentication +@cindex verifying certificate paths +@cindex SSH-style authentication +@tindex gnutls_certificate_verify_flags + +It is possible to use an SSH-style authentication method in GnuTLS. +That means that having seen and associated a public key with a host +is enough to trust it on the subsequent connections. +A hybrid system with X.509 and SSH authentication is +shown in @ref{Simple client example with SSH-style certificate verification}. +@showfuncdesc{gnutls_verify_stored_pubkey} +@showfuncdesc{gnutls_store_pubkey} @node OpenPGP certificates @section @acronym{OpenPGP} certificates diff --git a/doc/cha-gtls-examples.texi b/doc/cha-gtls-examples.texi index 8fbf6b8b9f..9d253a0045 100644 --- a/doc/cha-gtls-examples.texi +++ b/doc/cha-gtls-examples.texi @@ -25,6 +25,7 @@ implemented by another example. @menu * Simple client example with anonymous authentication:: * Simple client example with X.509 certificate support:: +* Simple client example with SSH-style certificate verification:: * Simple Datagram TLS client example:: * Obtaining session information:: * Using a callback to select the certificate to use:: @@ -47,6 +48,8 @@ is vulnerable to man-in-the-middle (active or redirection) attacks. However, the data are integrity protected and encrypted from passive eavesdroppers. +Note that the server must support anonymous authentication as well. + @verbatiminclude examples/ex-client-anon.c @node Simple client example with X.509 certificate support @@ -62,6 +65,16 @@ resumption. @verbatiminclude examples/ex-client-x509.c +@node Simple client example with SSH-style certificate verification +@subsection Simple client example with SSH-style certificate verification + +This is an alternative verification function that will use the +X.509 certificate authorities for verification, but also assume an +SSH-like authentication system. That is the user is prompted on unknown +public keys and known public keys are considered trusted. + +@verbatiminclude examples/ex-verify-ssh.c + @node Simple Datagram TLS client example @subsection Simple datagram @acronym{TLS} client example diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am index ba6d047591..ed1592cb06 100644 --- a/doc/examples/Makefile.am +++ b/doc/examples/Makefile.am @@ -84,4 +84,4 @@ endif libexamples_la_SOURCES = examples.h ex-alert.c ex-pkcs12.c \ ex-session-info.c ex-x509-info.c ex-verify.c \ - tcp.c udp.c ex-pkcs11-list.c verify.c + tcp.c udp.c ex-pkcs11-list.c verify.c ex-verify-ssh.c diff --git a/doc/examples/ex-cert-select-pkcs11.c b/doc/examples/ex-cert-select-pkcs11.c index bd7851c0be..e8cb21e367 100644 --- a/doc/examples/ex-cert-select-pkcs11.c +++ b/doc/examples/ex-cert-select-pkcs11.c @@ -26,7 +26,7 @@ #define MSG "GET / HTTP/1.0\r\n\r\n" #define MIN(x,y) (((x)<(y))?(x):(y)) -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" /* The URLs of the objects can be obtained * using p11tool --list-all --login diff --git a/doc/examples/ex-cert-select.c b/doc/examples/ex-cert-select.c index 43f666fda8..d45e8e583a 100644 --- a/doc/examples/ex-cert-select.c +++ b/doc/examples/ex-cert-select.c @@ -26,7 +26,7 @@ #define CERT_FILE "cert.pem" #define KEY_FILE "key.pem" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" extern int tcp_connect (void); extern void tcp_close (int sd); diff --git a/doc/examples/ex-client-dtls.c b/doc/examples/ex-client-dtls.c index 222762a0b5..3c9d1ea679 100644 --- a/doc/examples/ex-client-dtls.c +++ b/doc/examples/ex-client-dtls.c @@ -18,7 +18,7 @@ */ #define MAX_BUF 1024 -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define MSG "GET / HTTP/1.0\r\n\r\n" extern int udp_connect (void); diff --git a/doc/examples/ex-client-resume.c b/doc/examples/ex-client-resume.c index 5aeae58c92..1a041d196d 100644 --- a/doc/examples/ex-client-resume.c +++ b/doc/examples/ex-client-resume.c @@ -16,7 +16,7 @@ extern int tcp_connect (void); extern void tcp_close (int sd); #define MAX_BUF 1024 -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define MSG "GET / HTTP/1.0\r\n\r\n" int diff --git a/doc/examples/ex-client-srp.c b/doc/examples/ex-client-srp.c index 5a753abff3..89d5165777 100644 --- a/doc/examples/ex-client-srp.c +++ b/doc/examples/ex-client-srp.c @@ -18,7 +18,7 @@ extern void tcp_close (int sd); #define MAX_BUF 1024 #define USERNAME "user" #define PASSWORD "pass" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define MSG "GET / HTTP/1.0\r\n\r\n" int diff --git a/doc/examples/ex-client-x509.c b/doc/examples/ex-client-x509.c index 0ea151dd72..c5ed190787 100644 --- a/doc/examples/ex-client-x509.c +++ b/doc/examples/ex-client-x509.c @@ -16,7 +16,7 @@ */ #define MAX_BUF 1024 -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define MSG "GET / HTTP/1.0\r\n\r\n" extern int tcp_connect (void); diff --git a/doc/examples/ex-serv-dtls.c b/doc/examples/ex-serv-dtls.c index 355f7b9839..a32a984051 100644 --- a/doc/examples/ex-serv-dtls.c +++ b/doc/examples/ex-serv-dtls.c @@ -20,7 +20,7 @@ #define KEYFILE "key.pem" #define CERTFILE "cert.pem" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define CRLFILE "crl.pem" /* This is a sample DTLS echo server, using X.509 authentication. diff --git a/doc/examples/ex-serv-psk.c b/doc/examples/ex-serv-psk.c index 70732f60e3..1f2af6c472 100644 --- a/doc/examples/ex-serv-psk.c +++ b/doc/examples/ex-serv-psk.c @@ -17,7 +17,7 @@ #define KEYFILE "key.pem" #define CERTFILE "cert.pem" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define CRLFILE "crl.pem" /* This is a sample TLS echo server, supporting X.509 and PSK diff --git a/doc/examples/ex-serv-srp.c b/doc/examples/ex-serv-srp.c index 70ae1660bb..5873b39920 100644 --- a/doc/examples/ex-serv-srp.c +++ b/doc/examples/ex-serv-srp.c @@ -20,7 +20,7 @@ #define KEYFILE "key.pem" #define CERTFILE "cert.pem" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" /* This is a sample TLS-SRP echo server. */ diff --git a/doc/examples/ex-serv-x509.c b/doc/examples/ex-serv-x509.c index 44c671a3f4..c06f13ec09 100644 --- a/doc/examples/ex-serv-x509.c +++ b/doc/examples/ex-serv-x509.c @@ -17,7 +17,7 @@ #define KEYFILE "key.pem" #define CERTFILE "cert.pem" -#define CAFILE "ca.pem" +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" #define CRLFILE "crl.pem" /* This is a sample TLS 1.0 echo server, using X.509 authentication. diff --git a/doc/examples/ex-verify-ssh.c b/doc/examples/ex-verify-ssh.c new file mode 100644 index 0000000000..c9fab66e28 --- /dev/null +++ b/doc/examples/ex-verify-ssh.c @@ -0,0 +1,138 @@ +/* This example code is placed in the public domain. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include "examples.h" + +/* This function will verify the peer's certificate, check + * if the hostname matches. In addition it will perform an + * SSH-style authentication, where ultimately trusted keys + * are only the keys that have been seen before. + */ +int +_ssh_verify_certificate_callback (gnutls_session_t session) +{ + unsigned int status; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + int ret; + gnutls_x509_crt_t cert; + const char *hostname; + + /* read hostname */ + hostname = gnutls_session_get_ptr (session); + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + ret = gnutls_certificate_verify_peers2 (session, &status); + if (ret < 0) + { + printf ("Error\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (status & GNUTLS_CERT_INVALID) + printf ("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + printf ("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + printf ("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_EXPIRED) + printf ("The certificate has expired\n"); + + if (status & GNUTLS_CERT_NOT_ACTIVATED) + printf ("The certificate is not yet activated\n"); + + /* Up to here the process is the same for X.509 certificates and + * OpenPGP keys. From now on X.509 certificates are assumed. This can + * be easily extended to work with openpgp keys as well. + */ + if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) + return GNUTLS_E_CERTIFICATE_ERROR; + + if (gnutls_x509_crt_init (&cert) < 0) + { + printf ("error in initialization\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list == NULL) + { + printf ("No certificate was found!\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* This is not a real world example, since we only check the first + * certificate in the given chain. + */ + if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + { + printf ("error parsing certificate\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (!gnutls_x509_crt_check_hostname (cert, hostname)) + { + printf ("The certificate's owner does not match hostname '%s'\n", + hostname); + status |= GNUTLS_CERT_INVALID; + } + + gnutls_x509_crt_deinit (cert); + + ret = gnutls_verify_stored_pubkey(NULL, NULL, hostname, "443", + GNUTLS_CRT_X509, &cert_list[0], 0); + if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + fprintf(stderr, "Host %s is not known.", hostname); + if (status == 0) + fprintf(stderr, "Its certificate is valid for %s.\n", hostname); + + /* the certificate must be printed and user must be asked on + * whether it is trustworthy. --see gnutls_x509_crt_print() */ + + /* if not trusted */ + return GNUTLS_E_CERTIFICATE_ERROR; + } + else if (ret == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) + { + fprintf(stderr, "Warning: host %s is known but has another key associated.", hostname); + fprintf(stderr, "It might be that the server has multiple keys, or you are under attack\n"); + if (status == 0) + fprintf(stderr, "Its certificate is valid for %s.\n", hostname); + + /* the certificate must be printed and user must be asked on + * whether it is trustworthy. --see gnutls_x509_crt_print() */ + + /* if not trusted */ + return GNUTLS_E_CERTIFICATE_ERROR; + } + else if (ret < 0) + { + fprintf(stderr, "gnutls_verify_stored_pubkey: %s\n", gnutls_strerror(ret)); + return ret; + } + + /* user trusts the key -> store it */ + ret = gnutls_store_pubkey(NULL, NULL, hostname, "443", GNUTLS_CRT_X509, &cert_list[0], 0); + if (ret < 0) + { + fprintf(stderr, "gnutls_store_pubkey: %s\n", gnutls_strerror(ret)); + } + + /* notify gnutls to continue handshake normally */ + return 0; +} + diff --git a/doc/examples/examples.h b/doc/examples/examples.h index e96cb26080..0c2dbb3372 100644 --- a/doc/examples/examples.h +++ b/doc/examples/examples.h @@ -12,6 +12,9 @@ int print_info (gnutls_session_t session); void print_x509_certificate_info (gnutls_session_t session); +int +_ssh_verify_certificate_callback (gnutls_session_t session); + void verify_certificate_chain (const char *hostname, const gnutls_datum_t * cert_chain, diff --git a/lib/Makefile.am b/lib/Makefile.am index d692a5864f..820f09062d 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -74,7 +74,7 @@ COBJECTS = gnutls_record.c gnutls_compress.c debug.c gnutls_cipher.c \ gnutls_rsa_export.c gnutls_helper.c gnutls_supplemental.c \ random.c crypto-api.c gnutls_privkey.c gnutls_pcert.c \ gnutls_pubkey.c locks.c hash.c gnutls_dtls.c system_override.c \ - crypto-backend.c + crypto-backend.c verify-ssh.c if ENABLE_PKCS11 COBJECTS += pkcs11.c pkcs11_privkey.c pkcs11_write.c pkcs11_secret.c diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 45008ae8d5..415e282719 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -526,6 +526,7 @@ extern "C" * @GNUTLS_CRT_UNKNOWN: Unknown certificate type. * @GNUTLS_CRT_X509: X.509 Certificate. * @GNUTLS_CRT_OPENPGP: OpenPGP certificate. + * @GNUTLS_CRT_RAW: Raw public key (SubjectPublicKey) * * Enumeration of different certificate types. */ @@ -533,7 +534,8 @@ extern "C" { GNUTLS_CRT_UNKNOWN = 0, GNUTLS_CRT_X509 = 1, - GNUTLS_CRT_OPENPGP = 2 + GNUTLS_CRT_OPENPGP = 2, + GNUTLS_CRT_RAW = 3 } gnutls_certificate_type_t; /** @@ -552,6 +554,7 @@ extern "C" /** * gnutls_certificate_print_formats_t: * @GNUTLS_CRT_PRINT_FULL: Full information about certificate. + * @GNUTLS_CRT_PRINT_COMPACT: Information about certificate name in one line, plus identification of the public key. * @GNUTLS_CRT_PRINT_ONELINE: Information about certificate in one line. * @GNUTLS_CRT_PRINT_UNSIGNED_FULL: All info for an unsigned certificate. * @@ -561,7 +564,8 @@ extern "C" { GNUTLS_CRT_PRINT_FULL = 0, GNUTLS_CRT_PRINT_ONELINE = 1, - GNUTLS_CRT_PRINT_UNSIGNED_FULL = 2 + GNUTLS_CRT_PRINT_UNSIGNED_FULL = 2, + GNUTLS_CRT_PRINT_COMPACT = 3 } gnutls_certificate_print_formats_t; #define GNUTLS_PK_ECC GNUTLS_PK_EC @@ -1652,6 +1656,22 @@ gnutls_ecc_curve_t gnutls_ecc_curve_get(gnutls_session_t session); int gnutls_hex2bin (const char *hex_data, size_t hex_size, void *bin_data, size_t * bin_size); + /* ssh style functions */ + int gnutls_verify_stored_pubkey(const char* file, + const char* application, + const char* host, + const char* service, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t * cert, unsigned int flags); + + int gnutls_store_pubkey(const char* file, + const char* application, + const char* host, + const char* service, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t * cert, unsigned int flags); + + /* Gnutls error codes. The mapping to a TLS alert is also shown in * comments. */ diff --git a/lib/libgnutls.map b/lib/libgnutls.map index e169a06170..40765f8d54 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -771,6 +771,8 @@ GNUTLS_3_0_0 { gnutls_priority_protocol_list; gnutls_priority_compression_list; gnutls_priority_ecc_curve_list; + gnutls_verify_stored_pubkey; + gnutls_store_pubkey; } GNUTLS_2_12; GNUTLS_PRIVATE { diff --git a/lib/openpgp/output.c b/lib/openpgp/output.c index fdd176ac3a..88f522db70 100644 --- a/lib/openpgp/output.c +++ b/lib/openpgp/output.c @@ -506,6 +506,13 @@ gnutls_openpgp_crt_print (gnutls_openpgp_crt_t cert, if (format == GNUTLS_CRT_PRINT_ONELINE) print_oneline (&str, cert); + else if (format == GNUTLS_CRT_PRINT_COMPACT) + { + print_oneline (&str, cert); + + _gnutls_buffer_append_data (&str, "\n", 1); + print_key_fingerprint (&str, cert); + } else { _gnutls_buffer_append_str (&str, diff --git a/lib/system.c b/lib/system.c index eddb1053a4..476b313787 100644 --- a/lib/system.c +++ b/lib/system.c @@ -25,14 +25,20 @@ #include <gnutls_errors.h> #include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> #ifdef _WIN32 -#include <windows.h> +# include <windows.h> #else -#ifdef HAVE_PTHREAD_LOCKS -#include <pthread.h> -#endif +# ifdef HAVE_PTHREAD_LOCKS +# include <pthread.h> +# endif + +# if defined(HAVE_GETPWUID_R) +# include <pwd.h> +# endif #endif /* We need to disable gnulib's replacement wrappers to get native @@ -278,3 +284,65 @@ mutex_init_func gnutls_mutex_init = gnutls_system_mutex_init; mutex_deinit_func gnutls_mutex_deinit = gnutls_system_mutex_deinit; mutex_lock_func gnutls_mutex_lock = gnutls_system_mutex_lock; mutex_unlock_func gnutls_mutex_unlock = gnutls_system_mutex_unlock; + +#define CONFIG_PATH ".gnutls" + +/* Returns a path to store user-specific configuration + * data. + */ +int _gnutls_find_config_path(char* path, size_t max_size) +{ +char tmp_home_dir[1024]; +const char *home_dir = getenv ("HOME"); + +#ifdef _WIN32 + if (home_dir == NULL || home_dir[0] == '\0') + { + const char *home_drive = getenv ("HOMEDRIVE"); + const char *home_path = getenv ("HOMEPATH"); + + if (home_drive != NULL && home_path != NULL) + { + snprintf(tmp_home_dir, sizeof(tmp_home_dir), "%s%s", home_drive, home_path); + } + else + { + tmp_home_dir[0] = 0; + } + + home_dir = tmp_home_dir; + } +#elsif defined(HAVE_GETPWUID_R) + if (home_dir == NULL || home_dir[0] == '\0') + { + struct passwd *pwd; + struct passwd _pwd; + char buf[1024]; + + getpwuid_r(getuid(), &_pwd, buf, sizeof(buf), &pwd); + if (pwd != NULL) + { + snprintf(tmp_home_dir, sizeof(tmp_home_dir), "%s", pwd->pw_dir); + } + else + { + tmp_home_dir[0] = 0; + } + + home_dir = tmp_home_dir; + } +#else + if (home_dir == NULL || home_dir[0] == '\0') + { + tmp_home_dir[0] = 0; + home_dir = tmp_home_dir; + } +#endif + + if (home_dir == NULL || home_dir[0] == 0) + path[0] = 0; + else + snprintf(path, max_size, "%s/"CONFIG_PATH, home_dir); + + return 0; +} diff --git a/lib/system.h b/lib/system.h index df4ee5a36d..0d2f5a501f 100644 --- a/lib/system.h +++ b/lib/system.h @@ -71,4 +71,6 @@ struct timespec ts; #endif } +int _gnutls_find_config_path(char* path, size_t max_size); + #endif /* SYSTEM_H */ diff --git a/lib/verify-ssh.c b/lib/verify-ssh.c new file mode 100644 index 0000000000..8d6562f705 --- /dev/null +++ b/lib/verify-ssh.c @@ -0,0 +1,489 @@ +/* + * 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(gnutls_datum_t* pubkey); +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* application, + const char* host, const char* service, + const gnutls_datum_t* skey); +static int find_config_file(char* file, size_t max_size); +#define MAX_FILENAME 512 + +/** + * gnutls_verify_stored_pubkey: + * @file: A file specifying the stored keys (use NULL for the default) + * @application: non-NULL with an application name if this key is application-specific + * @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. + * + * 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* file, + const char* application, + 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 (file == NULL) + { + ret = find_config_file(local_file, sizeof(local_file)); + if (ret < 0) + return gnutls_assert_val(ret); + file = local_file; + } + + 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 = raw_pubkey_to_base64(&pubkey); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + ret = find_stored_pubkey(file, application, 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_line(char* line, const char* application, + size_t application_len, + const char* host, size_t host_len, + const char* service, size_t service_len, + const gnutls_datum_t *skey) +{ +char* p, *kp; +char* savep = NULL; +size_t kp_len; + + /* read version */ + p = strtok_r(line, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (strncmp(p, "g0", 2) != 0) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + /* read application */ + p = strtok_r(NULL, "|", &savep); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); + + if (p[0] != '*' && strcmp(p, application)!=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 service */ + 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 != skey->size) + return gnutls_assert_val(GNUTLS_E_CERTIFICATE_KEY_MISMATCH); + + if (memcmp(kp, skey->data, skey->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* application, + const char* host, const char* service, + const gnutls_datum_t* skey) +{ +FILE* fd; +char* line = NULL; +size_t line_size = 0; +int ret, l2, mismatch = 0; +size_t application_len = 0, host_len = 0, service_len = 0; + + if (host != NULL) host_len = strlen(host); + if (service != NULL) service_len = strlen(service); + if (application != NULL) application_len = strlen(application); + + fd = fopen(file, "rb"); + if (fd == NULL) + return gnutls_assert_val(GNUTLS_E_FILE_ERROR); + + do + { + l2 = getline(&line, &line_size, fd); + if (l2 > 0) + { + ret = parse_line(line, application, application_len, + host, host_len, service, service_len, skey); + 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); + fclose(fd); + + return ret; +} + +static int raw_pubkey_to_base64(gnutls_datum_t* pubkey) +{ + int ret; + char* out; + + ret = base64_encode_alloc((void*)pubkey->data, pubkey->size, &out); + if (ret == 0) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + gnutls_free(pubkey->data); + pubkey->data = (void*)out; + pubkey->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; +} + +/** + * gnutls_store_pubkey: + * @file: A file specifying the stored keys (use NULL for the default) + * @application: non-NULL with an application name if this key is application-specific + * @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 + * @flags: should be 0. + * + * This function will store to verify the provided certificate to + * the list of stored public keys. + * + * Note that this function is not thread safe. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. + * + * Since: 3.0.0 + **/ +int +gnutls_store_pubkey(const char* file, + const char* application, + const char* host, + const char* service, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t * cert, 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 (file == 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); + file = local_file; + } + + 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 = raw_pubkey_to_base64(&pubkey); + if (ret < 0) + { + gnutls_assert(); + goto cleanup; + } + + _gnutls_debug_log("Configuration file: %s\n", file); + + fd = fopen(file, "ab+"); + if (fd == NULL) + { + ret = gnutls_assert_val(GNUTLS_E_FILE_ERROR); + goto cleanup; + } + + if (application == NULL) application = "*"; + if (service == NULL) service = "*"; + if (host == NULL) host = "*"; + + fprintf(fd, "|g0|%s|%s|%s|%.*s\n", application, host, service, pubkey.size, pubkey.data); + + ret = 0; + +cleanup: + gnutls_free(pubkey.data); + 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; +} diff --git a/lib/x509/output.c b/lib/x509/output.c index 564004fb35..827845f427 100644 --- a/lib/x509/output.c +++ b/lib/x509/output.c @@ -1700,6 +1700,22 @@ gnutls_x509_crt_print (gnutls_x509_crt_t cert, return ret; } + else if (format == GNUTLS_CRT_PRINT_COMPACT) + { + _gnutls_buffer_init (&str); + + print_oneline (&str, cert); + + _gnutls_buffer_append_data (&str, "\n", 1); + print_keyid (&str, cert); + + _gnutls_buffer_append_data (&str, "\0", 1); + + ret = _gnutls_buffer_to_datum( &str, out); + if (out->size > 0) out->size--; + + return ret; + } else if (format == GNUTLS_CRT_PRINT_ONELINE) { _gnutls_buffer_init (&str); diff --git a/src/cli-args.def.in b/src/cli-args.def.in index a691c8623f..92c3bd63f8 100644 --- a/src/cli-args.def.in +++ b/src/cli-args.def.in @@ -37,6 +37,12 @@ flag = { }; flag = { + name = ssh; + descrip = "Enable SSH-style authentication"; + doc = "This option will, in addition to certificate authentication, perform authentication based on stored public keys."; +}; + +flag = { name = resume; value = r; descrip = "Connect, establish a session. Connect again and resume this session"; @@ -229,7 +235,7 @@ flag = { name = port; value = p; arg-type = string; - descrip = "The port to connect to"; + descrip = "The port or service to connect to"; doc = ""; }; @@ -70,6 +70,8 @@ int crlf; unsigned int verbose = 0; extern int print_cert; +#define DEFAULT_CA_FILE "/etc/ssl/certs/ca-certificates.crt" + const char *srp_passwd = NULL; const char *srp_username = NULL; const char *pgp_keyfile = NULL; @@ -423,23 +425,93 @@ load_keys (void) } +#define IS_NEWLINE(x) ((x[0] == '\n') || (x[0] == '\r')) +static int +read_yesno (const char *input_str) +{ + char input[128]; + + fputs (input_str, stderr); + if (fgets (input, sizeof (input), stdin) == NULL) + return 0; + + if (IS_NEWLINE(input)) + return 0; + + if (input[0] == 'y' || input[0] == 'Y') + return 1; + + return 0; +} + static int cert_verify_callback (gnutls_session_t session) { int rc; - unsigned int status; + unsigned int status = 0; + int ssh = HAVE_OPT(SSH); if (!x509_cafile && !pgp_keyring) return 0; - rc = gnutls_certificate_verify_peers2 (session, &status); - if (rc != 0 || status != 0) + rc = cert_verify(session, hostname); + if (rc == 0) { printf ("*** Verifying server certificate failed...\n"); - if (!insecure) + if (!insecure && !ssh) return -1; } + if (ssh) /* try ssh auth */ + { + unsigned int list_size; + const gnutls_datum_t * cert; + + cert = gnutls_certificate_get_peers(session, &list_size); + if (cert == NULL) + { + fprintf(stderr, "Cannot obtain peer's certificate!\n"); + return -1; + } + + rc = gnutls_verify_stored_pubkey(NULL, NULL, hostname, service, GNUTLS_CRT_X509, + cert, 0); + if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + print_cert_info(session, GNUTLS_CRT_PRINT_COMPACT); + fprintf(stderr, "Host %s has never been contacted before and is not in the trusted list.\n", hostname); + if (status == 0) + fprintf(stderr, "Its certificate is valid for %s.\n", hostname); + + rc = read_yesno("Are you sure you want to trust it? (y/N): "); + if (rc == 0) + return -1; + } + else if (rc == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) + { + print_cert_info(session, GNUTLS_CRT_PRINT_COMPACT); + fprintf(stderr, "Warning: host %s is known and it is associated with a different key.\n", hostname); + fprintf(stderr, "It might be that the server has multiple keys, or an attacker replaced the key to eavesdrop this connection .\n"); + if (status == 0) + fprintf(stderr, "Its certificate is valid for %s.\n", hostname); + + rc = read_yesno("Do you trust the received key? (y/N): "); + if (rc == 0) + return -1; + } + else if (rc < 0) + { + fprintf(stderr, "gnutls_verify_stored_pubkey: %s\n", gnutls_strerror(rc)); + return -1; + } + + rc = gnutls_store_pubkey(NULL, NULL, hostname, service, GNUTLS_CRT_X509, cert, 0); + if (rc < 0) + { + fprintf(stderr, "Could not store key: %s\n", gnutls_strerror(rc)); + } + } + return 0; } @@ -1018,6 +1090,7 @@ const char* rest = NULL; resume = HAVE_OPT(RESUME); rehandshake = HAVE_OPT(REHANDSHAKE); insecure = HAVE_OPT(INSECURE); + udp = HAVE_OPT(UDP); mtu = OPT_VALUE_MTU; @@ -1046,6 +1119,11 @@ const char* rest = NULL; if (HAVE_OPT(X509CAFILE)) x509_cafile = OPT_ARG(X509CAFILE); + else + { + if (access(DEFAULT_CA_FILE, R_OK) == 0) + x509_cafile = DEFAULT_CA_FILE; + } if (HAVE_OPT(X509CRLFILE)) x509_crlfile = OPT_ARG(X509CRLFILE); @@ -1151,8 +1229,6 @@ do_handshake (socket_st * socket) { /* print some information */ print_info (socket->session, socket->hostname, HAVE_OPT(INSECURE)); - - socket->secure = 1; } else diff --git a/src/common.c b/src/common.c index 40aa5b05d5..38ea2cffb6 100644 --- a/src/common.c +++ b/src/common.c @@ -68,14 +68,53 @@ raw_to_string (const unsigned char *raw, size_t raw_size) } static void -print_x509_info (gnutls_session_t session, const char *hostname, - int insecure) +print_x509_info_compact (gnutls_session_t session, int flag) +{ + gnutls_x509_crt_t crt; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + int ret; + gnutls_datum_t cinfo; + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list_size == 0) + { + fprintf (stderr, "No certificates found!\n"); + return; + } + + gnutls_x509_crt_init (&crt); + ret = + gnutls_x509_crt_import (crt, &cert_list[0], + GNUTLS_X509_FMT_DER); + if (ret < 0) + { + fprintf (stderr, "Decoding error: %s\n", + gnutls_strerror (ret)); + return; + } + + ret = + gnutls_x509_crt_print (crt, flag, &cinfo); + if (ret == 0) + { + printf ("- X.509 cert: %s\n", cinfo.data); + gnutls_free (cinfo.data); + } + + gnutls_x509_crt_deinit (crt); +} + +static void +print_x509_info (gnutls_session_t session, int flag) { gnutls_x509_crt_t crt; const gnutls_datum_t *cert_list; unsigned int cert_list_size = 0, j; - int hostname_ok = 0; int ret; + + if (flag == GNUTLS_CRT_PRINT_COMPACT) + return print_x509_info_compact(session, flag); cert_list = gnutls_certificate_get_peers (session, &cert_list_size); if (cert_list_size == 0) @@ -84,6 +123,7 @@ print_x509_info (gnutls_session_t session, const char *hostname, return; } + printf (" - Certificate type: X.509\n"); printf (" - Got a certificate list of %d certificates.\n", cert_list_size); @@ -104,14 +144,8 @@ print_x509_info (gnutls_session_t session, const char *hostname, printf (" - Certificate[%d] info:\n - ", j); - if (verbose) - ret = - gnutls_x509_crt_print (crt, GNUTLS_CRT_PRINT_FULL, - &cinfo); - else - ret = - gnutls_x509_crt_print (crt, GNUTLS_CRT_PRINT_ONELINE, - &cinfo); + ret = + gnutls_x509_crt_print (crt, flag, &cinfo); if (ret == 0) { printf ("%s\n", cinfo.data); @@ -153,46 +187,118 @@ print_x509_info (gnutls_session_t session, const char *hostname, gnutls_free (p); } - if (j == 0 && hostname != NULL) - { - /* Check the hostname of the first certificate if it matches - * the name of the host we connected to. - */ - if (gnutls_x509_crt_check_hostname (crt, hostname) == 0) - hostname_ok = 1; - else - hostname_ok = 2; - } - gnutls_x509_crt_deinit (crt); } +} - if (hostname_ok == 1) - { - printf - ("- The hostname in the certificate does NOT match '%s'\n", - hostname); - if (!insecure) - exit (1); - } - else if (hostname_ok == 2) - { - printf ("- The hostname in the certificate matches '%s'.\n", - hostname); - } +/* returns true or false, depending on whether the hostname + * matches to certificate */ +static int +verify_x509_hostname (gnutls_session_t session, const char *hostname) +{ + gnutls_x509_crt_t crt; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + int ret; + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list_size == 0) + { + fprintf (stderr, "No certificates found!\n"); + return 0; + } + + gnutls_x509_crt_init (&crt); + ret = + gnutls_x509_crt_import (crt, &cert_list[0], + GNUTLS_X509_FMT_DER); + if (ret < 0) + { + fprintf (stderr, "Decoding error: %s\n", + gnutls_strerror (ret)); + return 0; + } + + /* Check the hostname of the first certificate if it matches + * the name of the host we connected to. + */ + if (gnutls_x509_crt_check_hostname (crt, hostname) == 0) + { + printf + ("- The hostname in the certificate does NOT match '%s'\n", + hostname); + ret = 0; + } + else + { + printf ("- The hostname in the certificate matches '%s'.\n", + hostname); + ret = 1; + } + + gnutls_x509_crt_deinit (crt); + + return ret; } #ifdef ENABLE_OPENPGP +/* returns true or false, depending on whether the hostname + * matches to certificate */ +static int +verify_openpgp_hostname (gnutls_session_t session, const char *hostname) +{ + gnutls_openpgp_crt_t crt; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + int ret; + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list_size == 0) + { + fprintf (stderr, "No certificates found!\n"); + return 0; + } + + gnutls_openpgp_crt_init (&crt); + ret = + gnutls_openpgp_crt_import (crt, &cert_list[0], + GNUTLS_OPENPGP_FMT_RAW); + if (ret < 0) + { + fprintf (stderr, "Decoding error: %s\n", + gnutls_strerror (ret)); + return 0; + } + + /* Check the hostname of the first certificate if it matches + * the name of the host we connected to. + */ + if (gnutls_openpgp_crt_check_hostname (crt, hostname) == 0) + { + printf + ("- The hostname in the certificate does NOT match '%s'\n", + hostname); + ret = 0; + } + else + { + printf ("- The hostname in the certificate matches '%s'.\n", + hostname); + ret = 1; + } + + gnutls_openpgp_crt_deinit (crt); + + return ret; +} static void -print_openpgp_info (gnutls_session_t session, const char *hostname, - int insecure) +print_openpgp_info_compact (gnutls_session_t session, int flag) { gnutls_openpgp_crt_t crt; const gnutls_datum_t *cert_list; unsigned int cert_list_size = 0; - int hostname_ok = 0; int ret; cert_list = gnutls_certificate_get_peers (session, &cert_list_size); @@ -211,14 +317,50 @@ print_openpgp_info (gnutls_session_t session, const char *hostname, return; } - if (verbose) - ret = - gnutls_openpgp_crt_print (crt, GNUTLS_CRT_PRINT_FULL, - &cinfo); - else - ret = - gnutls_openpgp_crt_print (crt, GNUTLS_CRT_PRINT_ONELINE, - &cinfo); + ret = + gnutls_openpgp_crt_print (crt, flag, &cinfo); + if (ret == 0) + { + printf ("- OpenPGP cert: %s\n", cinfo.data); + gnutls_free (cinfo.data); + } + + gnutls_openpgp_crt_deinit (crt); + } +} + +static void +print_openpgp_info (gnutls_session_t session, int flag) +{ + + gnutls_openpgp_crt_t crt; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + int ret; + + if (flag == GNUTLS_CRT_PRINT_COMPACT) + print_openpgp_info_compact(session, flag); + + printf (" - Certificate type: OpenPGP\n"); + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + + if (cert_list_size > 0) + { + gnutls_datum_t cinfo; + + gnutls_openpgp_crt_init (&crt); + ret = gnutls_openpgp_crt_import (crt, &cert_list[0], + GNUTLS_OPENPGP_FMT_RAW); + if (ret < 0) + { + fprintf (stderr, "Decoding error: %s\n", + gnutls_strerror (ret)); + return; + } + + ret = + gnutls_openpgp_crt_print (crt, flag, &cinfo); if (ret == 0) { printf (" - %s\n", cinfo.data); @@ -261,59 +403,38 @@ print_openpgp_info (gnutls_session_t session, const char *hostname, gnutls_free (p); } - if (hostname != NULL) - { - /* Check the hostname of the first certificate if it matches - * the name of the host we connected to. - */ - if (gnutls_openpgp_crt_check_hostname (crt, hostname) == 0) - hostname_ok = 1; - else - hostname_ok = 2; - } - gnutls_openpgp_crt_deinit (crt); } - - if (hostname_ok == 1) - { - printf - ("- The hostname in the certificate does NOT match '%s'\n", - hostname); - if (!insecure) - exit (1); - } - else if (hostname_ok == 2) - { - printf ("- The hostname in the certificate matches '%s'.\n", - hostname); - } } #endif -static void -print_cert_vrfy (gnutls_session_t session) +/* returns false (0) if not verified, or true (1) otherwise */ +int +cert_verify (gnutls_session_t session, const char* hostname) { int rc; - unsigned int status; + unsigned int status = 0; + int type; rc = gnutls_certificate_verify_peers2 (session, &status); + if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + printf ("- Peer did not send any certificate.\n"); + return 0; + } + if (rc < 0) { printf ("- Could not verify certificate (err: %s)\n", gnutls_strerror (rc)); - return; + return 0; } - if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) + type = gnutls_certificate_type_get (session); + if (type == GNUTLS_CRT_X509) { - printf ("- Peer did not send any certificate.\n"); - return; - } - if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) - { if (status & GNUTLS_CERT_REVOKED) printf ("- Peer's certificate chain revoked\n"); if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) @@ -333,8 +454,11 @@ print_cert_vrfy (gnutls_session_t session) printf ("- Peer's certificate is NOT trusted\n"); else printf ("- Peer's certificate is trusted\n"); + + rc = verify_x509_hostname (session, hostname); + if (rc == 0) status |= GNUTLS_CERT_INVALID; } - else + else if (type == GNUTLS_CRT_OPENPGP) { if (status & GNUTLS_CERT_INVALID) printf ("- Peer's key is invalid\n"); @@ -342,7 +466,20 @@ print_cert_vrfy (gnutls_session_t session) printf ("- Peer's key is valid\n"); if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) printf ("- Could not find a signer of the peer's key\n"); + + rc = verify_openpgp_hostname (session, hostname); + if (rc == 0) status |= GNUTLS_CERT_INVALID; + } + else + { + fprintf(stderr, "Unknown certificate type\n"); + status |= GNUTLS_CERT_INVALID; } + + if (status) + return 0; + + return 1; } static void @@ -450,6 +587,7 @@ print_info (gnutls_session_t session, const char *hostname, int insecure) gnutls_credentials_type_t cred; gnutls_kx_algorithm_t kx; unsigned char session_id[33]; + int ret; size_t session_id_size = sizeof (session_id); /* print session ID */ @@ -517,9 +655,14 @@ print_info (gnutls_session_t session, const char *hostname, int insecure) } } - print_cert_info (session, hostname, insecure); + print_cert_info (session, verbose?GNUTLS_CRT_PRINT_FULL:GNUTLS_CRT_PRINT_COMPACT); - print_cert_vrfy (session); + ret = cert_verify (session, hostname); + if (insecure == 0 && ret == 0) + { + fprintf(stderr, "Exiting because verification failed (use --insecure to force connection)\n"); + exit(1); + } if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) print_dh_info (session, "Ephemeral "); @@ -578,32 +721,25 @@ print_info (gnutls_session_t session, const char *hostname, int insecure) } void -print_cert_info (gnutls_session_t session, const char *hostname, - int insecure) +print_cert_info (gnutls_session_t session, int flag) { if (gnutls_certificate_client_get_request_status (session) != 0) printf ("- Server has requested a certificate.\n"); - printf ("- Certificate type: "); switch (gnutls_certificate_type_get (session)) { - case GNUTLS_CRT_UNKNOWN: - printf ("Unknown\n"); - - if (!insecure) - exit (1); - break; case GNUTLS_CRT_X509: - printf ("X.509\n"); - print_x509_info (session, hostname, insecure); + print_x509_info (session, flag); break; #ifdef ENABLE_OPENPGP case GNUTLS_CRT_OPENPGP: - printf ("OpenPGP\n"); - print_openpgp_info (session, hostname, insecure); + print_openpgp_info (session, flag); break; #endif + default: + printf ("Unknown type\n"); + break; } } diff --git a/src/common.h b/src/common.h index dd6d569881..aeeb395e9f 100644 --- a/src/common.h +++ b/src/common.h @@ -50,9 +50,9 @@ extern const char str_unknown[]; int print_info (gnutls_session_t state, const char *hostname, int insecure); -void print_cert_info (gnutls_session_t state, const char *hostname, - int insecure); +void print_cert_info (gnutls_session_t, int flag); void print_list (const char* priorities, int verbose); +int cert_verify (gnutls_session_t session, const char* hostname); const char *raw_to_string (const unsigned char *raw, size_t raw_size); void pkcs11_common (void); diff --git a/src/tests.c b/src/tests.c index ede92c3eb4..cbed4680fa 100644 --- a/src/tests.c +++ b/src/tests.c @@ -1056,7 +1056,7 @@ test_certificate (gnutls_session_t session) return ret; printf ("\n"); - print_cert_info (session, hostname, 1); + print_cert_info (session, GNUTLS_CRT_PRINT_FULL); return TEST_SUCCEED; } |