Mercurial > hg > xemacs-beta
diff src/tls.c @ 5814:a216b3c2b09e
Add TLS support. See xemacs-patches message with ID
<CAHCOHQk6FNm2xf=XiGEpPq43+7WOzNZ=SuD9V79o3wb9WVCTrQ@mail.gmail.com>.
author | Jerry James <james@xemacs.org> |
---|---|
date | Tue, 07 Oct 2014 21:16:10 -0600 |
parents | |
children | d59bfb050ca8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tls.c Tue Oct 07 21:16:10 2014 -0600 @@ -0,0 +1,1191 @@ +/* Transport Layer Security implementation. + Copyright (C) 2014 Jerry James + +This file is part of XEmacs. + +XEmacs 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. + +XEmacs 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 XEmacs. If not, see <http://www.gnu.org/licenses/>. */ + +/* Synched up with: Not in FSF. */ + +/* Written by Jerry James. */ + +#include <config.h> +#include "lisp.h" +#include "lstream.h" +#include "tls.h" +#include <errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +static Lisp_Object prompt; +static Lisp_Object Qread_password; +Lisp_Object Qtls_error; + +#ifdef HAVE_NSS +#include <prinit.h> +#include <private/pprio.h> +#include <nss.h> +#include <pk11pub.h> +#include <secerr.h> +#include <secmod.h> +#include <ssl.h> + +#define NSS_ERRSTR build_extstring (PR_ErrorToName (PR_GetError ()), Qnative) + +/* 0 == initialization of NSPR or NSS failed + * 1 == the NSPR and NSS libraries have been initialized successfully + */ +static int nss_inited; + +/* The model file descriptor */ +static PRFileDesc *nss_model; + +/* The PEM module */ +static SECMODModule *nss_pem_module; + +/* CA and trust objects go into slot 0. User certificates start in slot 1. */ +static CK_SLOT_ID nss_slot_count = 1; + +int +tls_get_fd (tls_state_t *state) +{ + return PR_FileDesc2NativeHandle (state->tls_file_desc); +} + +Bytecount +tls_read (tls_state_t *state, unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + if (allow_quit) + QUIT; + return (Bytecount) PR_Recv (state->tls_file_desc, data, size, 0, 0); +} + +Bytecount +tls_write (tls_state_t *state, const unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + if (allow_quit) + QUIT; + return (Bytecount) PR_Send (state->tls_file_desc, data, size, 0, 0); +} + +int +tls_close (tls_state_t *state) +{ + if (--state->tls_refcount == 0) + { + PRStatus status = PR_Shutdown (state->tls_file_desc, PR_SHUTDOWN_BOTH); + PR_Close (state->tls_file_desc); + xfree (state); + return (int) status; + } + return 0; +} + +tls_state_t * +tls_open (int s, const Extbyte *hostname) +{ + struct sockaddr *addr; + socklen_t addrlen; + PRNetAddr pr_addr; + tls_state_t *nspr; + const int val = 1; + + /* Disable Nagle's algorithm */ + setsockopt (s, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + + if (!nss_inited) + { + warn_when_safe (Qtls_error, Qerror, "Cannot use NSS functions"); + return NULL; + } + + /* Get the socket address */ + addrlen = 256; + addr = (struct sockaddr *) xmalloc (addrlen); + if (getsockname (s, addr, &addrlen) == 0 && addrlen > 256) + { + addr = (struct sockaddr *) xrealloc (addr, addrlen); + getsockname (s, addr, &addrlen); + } + + /* Create the socket */ + nspr = xmalloc (sizeof (*nspr)); + nspr->tls_refcount = 2; + nspr->tls_file_desc = + SSL_ImportFD (nss_model, PR_OpenTCPSocket (addr->sa_family)); + if (nspr->tls_file_desc == NULL) + { + xfree (addr); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, "NSS unable to open socket: %s", + PR_ErrorToName (PR_GetError ())); + return NULL; + } + + /* Connect to the server */ + memset (&pr_addr, 0, sizeof (pr_addr)); + if (addr->sa_family == AF_INET) + { + struct sockaddr_in *in_addr = (struct sockaddr_in *) addr; + pr_addr.inet.family = in_addr->sin_family; + pr_addr.inet.port = in_addr->sin_port; + pr_addr.inet.ip = in_addr->sin_addr.s_addr; + } + else + { + struct sockaddr_in6 *in_addr = (struct sockaddr_in6 *) addr; + pr_addr.ipv6.family = in_addr->sin6_family; + pr_addr.ipv6.port = in_addr->sin6_port; + pr_addr.ipv6.flowinfo = in_addr->sin6_flowinfo; + memcpy (pr_addr.ipv6.ip.pr_s6_addr, in_addr->sin6_addr.s6_addr, + sizeof (pr_addr.ipv6.ip.pr_s6_addr)); + pr_addr.ipv6.scope_id = in_addr->sin6_scope_id; + } + xfree (addr); + if (PR_Connect (nspr->tls_file_desc, &pr_addr, PR_INTERVAL_NO_TIMEOUT) + != PR_SUCCESS) + { + if (PR_GetError () == PR_IN_PROGRESS_ERROR) + { + PRPollDesc pollset[2]; + + pollset[0].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT; + pollset[0].out_flags = 0; + pollset[0].fd = nspr->tls_file_desc; + for (;;) + { + PRInt32 num_fds = PR_Poll (pollset, 1, PR_INTERVAL_NO_TIMEOUT); + if (num_fds < 0) + { + PR_Close (nspr->tls_file_desc); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, + "NSS unable to connect: %s", + PR_ErrorToName (PR_GetError ())); + return NULL; + } + if (PR_GetConnectStatus (pollset) == PR_SUCCESS) + break; + } + } + else + { + PR_Close (nspr->tls_file_desc); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, "NSS unable to connect: %s", + PR_ErrorToName (PR_GetError ())); + return NULL; + } + } + + /* Perform the handshake */ + if (SSL_ResetHandshake (nspr->tls_file_desc, PR_FALSE) != SECSuccess) + { + PR_Close (nspr->tls_file_desc); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, "NSS unable to reset handshake: %s", + PR_ErrorToName (PR_GetError ())); + errno = EACCES; + return NULL; + } + if (hostname != NULL && + SSL_SetURL (nspr->tls_file_desc, hostname) != SECSuccess) + { + PR_Close (nspr->tls_file_desc); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, "NSS unable to set URL (%s): %s", + hostname, PR_ErrorToName (PR_GetError ())); + errno = EACCES; + return NULL; + } + if (SSL_ForceHandshake (nspr->tls_file_desc) != SECSuccess) + { + PR_Close (nspr->tls_file_desc); + xfree (nspr); + warn_when_safe (Qtls_error, Qerror, + "NSS unable to complete handshake: %s", + PR_ErrorToName (PR_GetError ())); + errno = EACCES; + return NULL; + } + return nspr; +} + +/* Set the key and certificate files to use */ +static void +tls_set_x509_key_file (const Extbyte *certfile, const Extbyte *keyfile) +{ + char name[32]; + void *proto_win = NULL; + PK11SlotInfo *slot = NULL; + PK11GenericObject *obj; + CERTCertificate *cert; + CK_ATTRIBUTE attrs[4]; + CK_BBOOL cktrue = CK_TRUE, ckfalse = CK_FALSE; + CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY; + CK_SLOT_ID slot_id = nss_slot_count++; + int retry_count = 0; + + /* Load the PEM module if it hasn't already been loaded */ + if (nss_pem_module == NULL) + { + nss_pem_module = SECMOD_LoadUserModule ("library=%s name=PEM parameters=\"\"", NULL, PR_FALSE); + if (nss_pem_module == NULL) + signal_error (Qtls_error, "Cannot find NSS PEM module", NSS_ERRSTR); + if (!nss_pem_module->loaded) + signal_error (Qtls_error, "Cannot load NSS PEM module", NSS_ERRSTR); + } + + snprintf (name, 32U, "PEM_Token %ld", slot_id); + slot = PK11_FindSlotByName (name); + if (slot == NULL) + signal_error (Qtls_error, "Error finding NSS slot", NSS_ERRSTR); + + /* Set up the attributes for the keyfile */ + attrs[0].type = CKA_CLASS; + attrs[0].pValue = &objClass; + attrs[0].ulValueLen = sizeof (objClass); + attrs[1].type = CKA_TOKEN; + attrs[1].pValue = &cktrue; + attrs[1].ulValueLen = sizeof (CK_BBOOL); + attrs[2].type = CKA_LABEL; + attrs[2].pValue = (void *) keyfile; + attrs[2].ulValueLen = strlen (keyfile) + 1U; + + /* When adding an encrypted key, the PKCS#11 will be set as removed. */ + obj = PK11_CreateGenericObject (slot, attrs, 3, PR_FALSE); + if (obj == NULL) + { + PR_SetError (SEC_ERROR_BAD_KEY, 0); + signal_error (Qtls_error, "Bad key file", NSS_ERRSTR); + } + + /* This will force the token to be seen as reinserted */ + SECMOD_WaitForAnyTokenEvent (nss_pem_module, 0, 0); + PK11_IsPresent (slot); + + if (PK11_Authenticate (slot, PR_TRUE, &retry_count) != SECSuccess) + signal_error (Qtls_error, "NSS: Unable to authenticate", NSS_ERRSTR); + + /* Set up the attributes for the certfile */ + objClass = CKO_CERTIFICATE; + attrs[2].pValue = (void *) certfile; + attrs[2].ulValueLen = strlen (certfile) + 1U; + attrs[3].type = CKA_TRUST; + attrs[3].pValue = &ckfalse; + attrs[3].ulValueLen = sizeof (CK_BBOOL); + + obj = PK11_CreateGenericObject (slot, attrs, 4, PR_FALSE); + PK11_FreeSlot (slot); + if (obj == NULL) + signal_error (Qtls_error, "Bad certificate file", NSS_ERRSTR); + cert = PK11_FindCertFromNickname (name, proto_win); + if (cert == NULL) + signal_error (Qtls_error, "Cannot find certificate nickname", NSS_ERRSTR); + CERT_DestroyCertificate (cert); +} + +/* Function that gathers passwords for PKCS #11 tokens. */ +static char * +nss_pk11_password (PK11SlotInfo *slot, PRBool retry, void * UNUSED (arg)) +{ + Lisp_Object lsp_password, args[2]; + Extbyte *c_password, *nss_password; + const Extbyte *token_name; + + if (retry) + return NULL; + + token_name = PK11_GetTokenName (slot); + if (token_name == NULL) + token_name = "security token"; + lsp_password = + call1 (Qread_password, concat2 (prompt, + build_extstring (token_name, Qnative))); + c_password = LISP_STRING_TO_EXTERNAL (lsp_password, Qnative); + nss_password = PL_strdup (c_password); + + /* Wipe out the password on the stack and in the Lisp string */ + args[0] = lsp_password; + args[1] = make_char ('*'); + Ffill (2, args); + memset (c_password, '*', strlen (c_password)); + return nss_password; +} + +void +init_tls (void) +{ + SECMODModule *module; + + /* Check that we are using compatible versions */ + if (PR_VersionCheck(PR_VERSION) == PR_FALSE) + signal_error (Qinternal_error, + "NSPR version mismatch: expected " PR_VERSION, Qnil); + if (NSS_VersionCheck(NSS_VERSION) == PR_FALSE) + signal_error (Qinternal_error, + "NSS version mismatch: expected " NSS_VERSION, Qnil); + + /* Basic initialization of both libraries */ + PR_Init (PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + if (NSS_Init ("sql:/etc/pki/nssdb") != SECSuccess) + signal_error (Qtls_error, "Error initializing NSS", NSS_ERRSTR); + + /* Set the cipher suite policy */ + if (NSS_SetDomesticPolicy() != SECSuccess) + signal_error (Qtls_error, "NSS unable to set policy", NSS_ERRSTR); + + /* Load the root certificates */ + module = SECMOD_LoadUserModule ("library=libnssckbi.so name=\"Root Certs\"", + NULL, PR_FALSE); + if (module == NULL || !module->loaded) + signal_error (Qtls_error, "NSS unable to load root certificates", + NSS_ERRSTR); + + /* Setup password gathering */ + PK11_SetPasswordFunc (nss_pk11_password); + + /* Create the model file descriptors */ + nss_model = SSL_ImportFD (NULL, PR_OpenTCPSocket (PR_AF_INET)); + if (nss_model == NULL) + { + nss_model = SSL_ImportFD (NULL, PR_OpenTCPSocket (PR_AF_INET6)); + if (nss_model == NULL) + signal_error (Qtls_error, "NSS cannot create model socket", + NSS_ERRSTR); + } + + /* Set options on the model socket */ + if (SSL_OptionSet (nss_model, SSL_SECURITY, PR_TRUE) != SECSuccess) + signal_error (Qtls_error, "NSS cannot enable model socket", NSS_ERRSTR); + if (SSL_OptionSet (nss_model, SSL_ENABLE_SSL2, PR_FALSE) != SECSuccess) + signal_error (Qtls_error, "NSS unable to disable SSLv2", NSS_ERRSTR); + if (SSL_OptionSet (nss_model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE) + != SECSuccess) + signal_error (Qtls_error, "NSS unable to disable SSLv2 handshake", + NSS_ERRSTR); + if (SSL_OptionSet (nss_model, SSL_ENABLE_DEFLATE, PR_FALSE) != SECSuccess) + signal_error (Qtls_error, "NSS unable to disable deflate", NSS_ERRSTR); + if (SSL_OptionSet (nss_model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) + != SECSuccess) + signal_error (Qtls_error, "NSS unable to ensable handshake as client", + NSS_ERRSTR); + + nss_inited = 1; +} +#endif /* HAVE_NSS */ + +#ifdef HAVE_GNUTLS +#include <gnutls/pkcs11.h> +#include <gnutls/x509.h> +#include "sysfile.h" + +#define GNUTLS_ERRSTR(err) build_extstring (gnutls_strerror (err), Qnative) + +/* The global credentials object */ +static gnutls_certificate_credentials_t global_cred; + +int +tls_get_fd (tls_state_t *state) +{ + return (int)(unsigned long)gnutls_transport_get_ptr (state->tls_session); +} + +Bytecount +tls_read (tls_state_t *state, unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + ssize_t bytes; + + again: + do + { + if (allow_quit) + QUIT; + bytes = gnutls_record_recv (state->tls_session, data, size); + } + while (bytes == GNUTLS_E_INTERRUPTED || bytes == GNUTLS_E_AGAIN); + switch (bytes) + { + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + bytes = 0; + break; + case GNUTLS_E_REHANDSHAKE: + { + int err; + + do + err = gnutls_handshake (state->tls_session); + while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED); + if (err == GNUTLS_E_SUCCESS) + goto again; + } + errno = EACCES; + bytes = -1; + break; + default: + if (bytes < 0 && errno == 0) + { + errno = EPIPE; + bytes = -1; + } + break; + } + return (Bytecount) bytes; +} + +Bytecount +tls_write (tls_state_t *state, const unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + ssize_t bytes; + + do + { + if (allow_quit) + QUIT; + bytes = gnutls_record_send (state->tls_session, data, size); + } + while (bytes == GNUTLS_E_INTERRUPTED || bytes == GNUTLS_E_AGAIN); + if (bytes == GNUTLS_E_LARGE_PACKET) + { + errno = EMSGSIZE; + bytes = -1; + } + else if (bytes < 0 && errno == 0) + { + errno = EPIPE; + bytes = -1; + } + return (Bytecount) bytes; +} + +int +tls_close (tls_state_t *state) +{ + if (--state->tls_refcount == 0) + { + int fd, err; + + fd = (int)(unsigned long)gnutls_transport_get_ptr (state->tls_session); + gnutls_bye (state->tls_session, GNUTLS_SHUT_RDWR); + err = retry_close (fd); + gnutls_deinit (state->tls_session); + xfree (state); + return err; + } + return 0; +} + +tls_state_t * +tls_open (int s, const Extbyte *hostname) +{ +#ifndef HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 + gnutls_x509_crt_t cert; +#endif + tls_state_t *gnutls; + const char *errptr = NULL; + const gnutls_datum_t *certs; + unsigned int status, certslen = 0U; + int err; + const int val = 1; + + /* Disable Nagle's algorithm */ + setsockopt (s, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + + /* Create the state object */ + gnutls = xmalloc (sizeof (*gnutls)); + gnutls->tls_refcount = 2; + + /* Initialize the session object */ + err = gnutls_init (&gnutls->tls_session, GNUTLS_CLIENT); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, "GNUTLS error in gnutls_init: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + + /* Configure the cipher preferences */ + err = gnutls_priority_set_direct (gnutls->tls_session, "NORMAL", &errptr); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_priority_set_direct: %s at %s", + gnutls_strerror (err), errptr); + errno = EACCES; + return NULL; + } + + /* Install the trusted certificates */ + err = gnutls_credentials_set (gnutls->tls_session, GNUTLS_CRD_CERTIFICATE, + global_cred); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_credentials_set: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + + /* Associate the socket with the session object */ + gnutls_transport_set_ptr (gnutls->tls_session, + (gnutls_transport_ptr_t)(unsigned long)s); + + /* Set the server name */ + if (hostname != NULL) + { + err = gnutls_server_name_set (gnutls->tls_session, GNUTLS_NAME_DNS, + hostname, strlen (hostname)); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_server_name_set: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + } + + /* Perform the handshake */ + do + err = gnutls_handshake (gnutls->tls_session); + while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_handshake: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + + /* Get the server certificate chain */ + certs = gnutls_certificate_get_peers (gnutls->tls_session, &certslen); + if (certs == NULL || certslen == 0U) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS could not get peer certificate: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + + /* Validate the server certificate chain */ + status = (unsigned int) -1; +#ifdef HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 + if (hostname != NULL) + err = gnutls_certificate_verify_peers3 (gnutls->tls_session, hostname, + &status); + else +#endif /* HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 */ + err = gnutls_certificate_verify_peers2 (gnutls->tls_session, &status); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS could not verify peer certificate: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + if (status != 0U) + { + gnutls_datum_t msg; + +#ifdef HAVE_GNUTLS_CERTIFICATE_VERIFICATION_STATUS_PRINT + int type = gnutls_certificate_type_get (gnutls->tls_session); + err = + gnutls_certificate_verification_status_print (status, type, &msg, 0); +#else + err = -1; +#endif /* HAVE_GNUTLS_CERTIFICATE_VERIFICATION_STATUS_PRINT */ + xfree (gnutls); + if (err == 0) + { + warn_when_safe (Qtls_error, Qerror, + "GNUTLS: certificate validation failed: %s", + msg.data); + gnutls_free(msg.data); + errno = EACCES; + return NULL; + } + else + { + warn_when_safe (Qtls_error, Qerror, + "GNUTLS: certificate validation failed with code %u", + status); + errno = EACCES; + return NULL; + } + } + +#ifndef HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 + if (hostname != NULL) + { + /* Match the peer certificate against the host name */ + err = gnutls_x509_crt_init (&cert); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_x509_crt_init: %s", + gnutls_strerror (err)); + errno = EACCES; + return NULL; + } + + /* The peer certificate is the first certificate in the list */ + err = gnutls_x509_crt_import (cert, certs, GNUTLS_X509_FMT_DER); + if (err != GNUTLS_E_SUCCESS) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS error in gnutls_x509_crt_import: %s", + gnutls_strerror (err)); + gnutls_x509_crt_deinit (cert); + errno = EACCES; + return NULL; + } + + err = gnutls_x509_crt_check_hostname (cert, hostname); + if (err == 0) + { + xfree (gnutls); + warn_when_safe (Qtls_error, Qerror, + "GNUTLS: hostname does not match certificate: %s", + gnutls_strerror (err)); + gnutls_x509_crt_deinit (cert); + errno = EACCES; + return NULL; + } + gnutls_x509_crt_deinit (cert); + } +#endif /* HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 */ + + return gnutls; +} + +/* Set the key and certificate files to use */ +static void +tls_set_x509_key_file (const Extbyte *certfile, const Extbyte *keyfile) +{ + int err; + + err = gnutls_certificate_set_x509_key_file (global_cred, certfile, keyfile, + GNUTLS_X509_FMT_PEM); + if (err < GNUTLS_E_SUCCESS) + signal_error (Qtls_error, "gnutls_certificate_set_x509_key_file", + GNUTLS_ERRSTR (err)); +} + +/* Function that gathers PKCS #11 passwords. */ +static int +gnutls_pk11_password (void * UNUSED (userdata), int UNUSED (attempt), + const char *token_url, const char *token_label, + unsigned int UNUSED (flags), char *pin, size_t pin_max) +{ + Lisp_Object lsp_password, args[5]; + Extbyte *c_password; + size_t len; + + /* Get the password from the user */ + args[0] = prompt; + args[1] = build_extstring (token_label, Qnative); + args[2] = build_ascstring (" ("); + args[3] = build_extstring (token_url, Qnative); + args[4] = build_ascstring (")"); + lsp_password = call1 (Qread_password, Fconcat (5, args)); + c_password = LISP_STRING_TO_EXTERNAL (lsp_password, Qnative); + + /* Insert the password */ + len = strlen (c_password); + if (len > pin_max) + len = pin_max; + memcpy (pin, c_password, len); + pin[len] = '\0'; + + /* Wipe out the password on the stack and in the Lisp string */ + args[0] = lsp_password; + args[1] = make_char ('*'); + Ffill (2, args); + memset (c_password, '*', strlen (c_password)); + return GNUTLS_E_SUCCESS; +} + +static void xfree_for_gnutls (void *ptr) +{ + /* GnuTLS sometimes tries to free NULL */ + if (ptr != NULL) + xfree (ptr); +} + +void +init_tls (void) +{ + int err = GNUTLS_E_SUCCESS; + + /* Tell gnutls to use our memory allocation functions */ + gnutls_global_set_mem_functions ((void * (*)(size_t)) xmalloc, + (void * (*)(size_t)) xmalloc, + NULL, + (void * (*)(void *, size_t)) xrealloc, + xfree_for_gnutls); + + /* Initialize the library */ + err = gnutls_global_init (); + if (err != GNUTLS_E_SUCCESS) + signal_error (Qtls_error, "gnutls_global_init", GNUTLS_ERRSTR (err)); + + /* Load the trusted CA certificates */ + err = gnutls_certificate_allocate_credentials (&global_cred); + if (err != GNUTLS_E_SUCCESS) + signal_error (Qtls_error, "gnutls_certificate_allocate_credentials", + GNUTLS_ERRSTR (err)); + err = gnutls_certificate_set_x509_system_trust (global_cred); + if (err == 0) + signal_error (Qtls_error, "gnutls: no system certificates found", Qnil); + if (err < 0) + signal_error (Qtls_error, "gnutls_certificate_set_x509_system_trust", + GNUTLS_ERRSTR (err)); + + /* Setup password gathering */ + gnutls_pkcs11_set_pin_function (gnutls_pk11_password, NULL); +} +#endif /* HAVE_GNUTLS */ + +#ifdef HAVE_OPENSSL +#include <unistd.h> +#include <openssl/conf.h> +#include <openssl/err.h> + +/* The context used to create connections */ +static SSL_CTX *ssl_ctx; + +static Lisp_Object +openssl_error_string (void) +{ + Lisp_Object args[5]; + unsigned long err = ERR_get_error (); + + args[0] = build_ascstring (ERR_lib_error_string (err)); + args[1] = build_ascstring (":"); + args[2] = build_ascstring (ERR_func_error_string (err)); + args[3] = build_ascstring (":"); + args[4] = build_ascstring (ERR_reason_error_string (err)); + return Fconcat (5, args); +} + +static unsigned long +openssl_report_error_stack (const char *msg, const SSL *ssl) +{ + unsigned long err = ERR_get_error (); + if (err > 0UL) + { + if (ERR_GET_LIB (err) == ERR_LIB_SSL && + ERR_GET_REASON (err) == SSL_R_CERTIFICATE_VERIFY_FAILED) + { + long cert_err = SSL_get_verify_result (ssl); + warn_when_safe (Qtls_error, Qerror, "%s:%s", msg, + X509_verify_cert_error_string (cert_err)); + } + else + { + const char *lib = ERR_lib_error_string (err); + const char *func = ERR_func_error_string (err); + const char *reason = ERR_reason_error_string (err); + warn_when_safe (Qtls_error, Qerror, "%s:%s:%s:%s", msg, + lib == NULL ? "<unknown>" : lib, + func == NULL ? "<unknown>" : func, + reason == NULL ? "<unknown>" : reason); + } + } + return err; +} + +/* Return values: + * -1 = fatal error, caller should exit + * 0 = no error, caller should continue + * 1 = nonfatal error, caller should retry + */ +static int +openssl_report_error_num (const char *msg, const SSL *ssl, int ret, int retry) +{ + int errno_copy = errno; + int ssl_error = SSL_get_error (ssl, ret); + int err; + + switch (ssl_error) + { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + err = 0; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + err = retry; + break; + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_WANT_X509_LOOKUP: + err = 1; + break; + case SSL_ERROR_SYSCALL: + if (openssl_report_error_stack (msg, ssl) == 0UL && ret < 0) + warn_when_safe (Qtls_error, Qerror, "%s: %s", msg, + strerror (errno_copy)); + err = ret; + break; + case SSL_ERROR_SSL: + openssl_report_error_stack (msg, ssl); + err = -1; + break; + default: + warn_when_safe (Qtls_error, Qerror, "%s: error %d", msg, ssl_error); + err = -1; + break; + } + errno = errno_copy; + return err; +} + +int +tls_get_fd (tls_state_t *state) +{ + return SSL_get_fd (state->tls_connection); +} + +Bytecount +tls_read (tls_state_t *state, unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + int action, bytes; + + if (SSL_get_shutdown (state->tls_connection)) + return 0; + + bytes = SSL_read (state->tls_connection, data, size); + action = (bytes > 0) ? 0 + : openssl_report_error_num ("SSL_read", state->tls_connection, bytes, 0); + while (bytes <= 0 && action > 0) + { + if (allow_quit) + QUIT; + bytes = SSL_read (state->tls_connection, data, size); + action = (bytes > 0) ? 0 + : openssl_report_error_num ("SSL_read", state->tls_connection, + bytes, 0); + } + return (Bytecount) bytes; +} + +Bytecount +tls_write (tls_state_t *state, const unsigned char *data, Bytecount size, + unsigned int allow_quit) +{ + int action, bytes; + + if (SSL_get_shutdown (state->tls_connection)) + return 0; + + bytes = SSL_write (state->tls_connection, data, size); + action = (bytes > 0) ? 0 + : openssl_report_error_num ("SSL_write", state->tls_connection, bytes, 0); + while (bytes <= 0 && action > 0) + { + if (allow_quit) + QUIT; + bytes = SSL_write (state->tls_connection, data, size); + action = (bytes > 0) ? 0 + : openssl_report_error_num ("SSL_write", state->tls_connection, + bytes, 0); + } + return (Bytecount) bytes; +} + +int +tls_close (tls_state_t *state) +{ + if (--state->tls_refcount == 0) + { + int err, fd; + + fd = SSL_get_fd (state->tls_connection); + if (SSL_get_shutdown (state->tls_connection) == 0) + { + err = SSL_shutdown (state->tls_connection); + if (err < 0 && errno == EBADF) + err = 0; + if (err < 0) + openssl_report_error_num ("SSL_shutdown failed", + state->tls_connection, err, 0); + } + else + { + err = 0; + } + close (fd); + SSL_free (state->tls_connection); + xfree (state); + return err > 0 ? 0 : err; + } + return 0; +} + +tls_state_t * +tls_open (int s, const Extbyte *hostname) +{ + tls_state_t *openssl; + X509 *peer_cert = NULL; + const int val = 1; + int err; + long cert_err; + + /* Disable Nagle's algorithm */ + setsockopt (s, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + + /* Create the state object */ + openssl = xmalloc (sizeof (*openssl)); + openssl->tls_refcount = 2; + + /* Create the connection object */ + openssl->tls_connection = SSL_new (ssl_ctx); + if (openssl->tls_connection == NULL) + { + openssl_report_error_stack ("SSL_new failed", NULL); + goto error; + } + if (SSL_set_fd (openssl->tls_connection, s) == 0) + { + openssl_report_error_stack ("SSL_set_fd", openssl->tls_connection); + goto error; + } + + /* Enable the ServerNameIndication extension */ + if (hostname != NULL && + !SSL_set_tlsext_host_name (openssl->tls_connection, hostname)) + { + openssl_report_error_stack ("SSL_set_tlsext_host_name failed", + openssl->tls_connection); + goto error; + } + + /* Perform the handshake */ + err = SSL_connect (openssl->tls_connection); + while (err != 1) + { + int action = openssl_report_error_num ("SSL_connect failed", + openssl->tls_connection, err, 1); + if (action < 0) + goto error; + err = SSL_connect (openssl->tls_connection); + } + + /* Get the server certificate */ + peer_cert = SSL_get_peer_certificate (openssl->tls_connection); + if (peer_cert == NULL) + { + warn_when_safe (Qtls_error, Qerror, + "Peer did not present a certificate"); + goto error; + } + + cert_err = SSL_get_verify_result (openssl->tls_connection); + if (cert_err != X509_V_OK) + { + warn_when_safe (Qtls_error, Qerror, + "Peer certificate verification failure:%s", + X509_verify_cert_error_string (cert_err)); + goto error; + } + +#ifdef HAVE_X509_CHECK_HOST + err = X509_check_host (peer_cert, (const unsigned char *) hostname, + strlen (hostname), 0); + if (err < 0) + { + warn_when_safe (Qtls_error, Qerror, + "Out of memory while checking certificate"); + goto error; + } + if (err == 0) + { + warn_when_safe (Qtls_error, Qerror, + "Peer certificate verification failure"); + goto error; + } +#endif + X509_free (peer_cert); + + return openssl; + + error: + if (openssl->tls_connection != NULL) + SSL_free (openssl->tls_connection); + xfree (openssl); + errno = EACCES; + return NULL; +} + +/* Set the key and certificate files to use */ +static void +tls_set_x509_key_file (const Extbyte *certfile, const Extbyte *keyfile) +{ + int err; + + err = SSL_CTX_use_PrivateKey_file (ssl_ctx, keyfile, SSL_FILETYPE_PEM); + if (err <= 0) + signal_error (Qtls_error, "SSL_CTX_use_PrivateKey_file", + openssl_error_string ()); + err = SSL_CTX_use_certificate_file (ssl_ctx, certfile, SSL_FILETYPE_PEM); + if (err <= 0) + signal_error (Qtls_error, "SSL_CTX_use_certificate_file", + openssl_error_string ()); +} + +/* Function that gathers passwords for PKCS #11 tokens. */ +static int +openssl_password (char *buf, int size, int UNUSED (rwflag), + void *UNUSED (userdata)) +{ + Lisp_Object lsp_password, args[2]; + Extbyte *c_password; + + lsp_password = + call1 (Qread_password, concat2 (prompt, build_ascstring ("PEM"))); + c_password = LISP_STRING_TO_EXTERNAL (lsp_password, Qnative); + strncpy (buf, c_password, size); + + /* Wipe out the password on the stack and in the Lisp string */ + args[0] = lsp_password; + args[1] = make_char ('*'); + Ffill (2, args); + memset (c_password, '*', strlen (c_password)); + return (int) strlen (buf); +} + +void +init_tls (void) +{ + /* Load the default configuration */ + OPENSSL_config (NULL); + + /* Tell openssl to use our memory allocation functions */ + CRYPTO_set_mem_functions ((void * (*)(size_t)) xmalloc, + (void * (*)(void *, size_t)) xrealloc, + xfree_1); + + /* Load human-readable error messages */ + SSL_load_error_strings (); + + /* Initialize the library */ + SSL_library_init (); + + /* Configure a client connection context, and send a handshake for the + * highest supported TLS version. */ + ssl_ctx = SSL_CTX_new (SSLv23_client_method ()); + if (ssl_ctx == NULL) + signal_error (Qtls_error, "SSL_CTX_new failed", openssl_error_string ()); + + /* Disallow SSLv2 and disable compression. */ + SSL_CTX_set_options (ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + + /* Set various useful mode bits */ + SSL_CTX_set_mode (ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_AUTO_RETRY | SSL_MODE_RELEASE_BUFFERS); + + /* Let the system select the ciphers */ + if (SSL_CTX_set_cipher_list (ssl_ctx, "PROFILE=SYSTEM") != 1) + signal_error (Qtls_error, "SSL_CTX_set_cipher_list failed", + openssl_error_string ()); + + /* Load the set of trusted root certificates. */ + if (!SSL_CTX_set_default_verify_paths (ssl_ctx)) + signal_error (Qtls_error, "SSL_CTX_set_default_verify_paths failed", + openssl_error_string ()); + + /* Setup password gathering */ + SSL_CTX_set_default_passwd_cb (ssl_ctx, openssl_password); +} +#endif /* HAVE_OPENSSL */ + +#ifdef WITH_TLS +tls_state_t * +tls_negotiate (int fd, const Extbyte *host, Lisp_Object keylist) +{ + Lisp_Object tail; + + for (tail = keylist; CONSP (tail); tail = XCDR (tail)) + { + Lisp_Object keyfile = Fcar (XCAR (tail)); + Lisp_Object certfile = Fcar (Fcdr (XCAR (tail))); + Extbyte *c_keyfile, *c_certfile; + + if (!STRINGP (keyfile)) + invalid_argument ("Keyfile must be a filename", keyfile); + if (!STRINGP (certfile)) + invalid_argument ("Certfile must be a filename", certfile); + + c_keyfile = LISP_STRING_TO_EXTERNAL (keyfile, Qfile_name); + c_certfile = LISP_STRING_TO_EXTERNAL (certfile, Qfile_name); + tls_set_x509_key_file (c_certfile, c_keyfile); + } + return tls_open (fd, host); +} +#endif /* WITH_TLS */ + +#ifndef WITH_TLS +void +init_tls (void) +{ +} +#endif /* !WITH_TLS */ + +void +syms_of_tls (void) +{ +#ifdef WITH_TLS + DEFSYMBOL (Qread_password); +#endif + DEFERROR (Qtls_error, "TLS error", Qerror); +} + +void +vars_of_tls (void) +{ +#ifdef WITH_TLS + staticpro (&prompt); + prompt = build_ascstring ("Password for "); + Fprovide (intern ("tls")); +#ifdef HAVE_NSS + Fprovide (intern ("tls-nss")); +#endif +#ifdef HAVE_GNUTLS + Fprovide (intern ("tls-gnutls")); +#endif +#ifdef HAVE_OPENSSL + Fprovide (intern ("tls-openssl")); +#endif +#endif +}