Mercurial > hg > xemacs-beta
view src/tls.c @ 5891:a0e751d6c3ad
Import the #'clear-string API from GNU, use it in tls.c
src/ChangeLog addition:
2015-04-18 Aidan Kehoe <kehoea@parhasard.net>
* sequence.c (Fclear_string): New, API from GNU. Zero a string's
contents, making sure the text is not kept around even when the
string's data is reallocated because of a changed character
length.
* sequence.c (syms_of_sequence): Make it available to Lisp.
* lisp.h: Make it available to C code.
* tls.c (nss_pk11_password): Use it.
* tls.c (gnutls_pk11_password): Use it.
* tls.c (openssl_password): Use it.
tests/ChangeLog addition:
2015-04-18 Aidan Kehoe <kehoea@parhasard.net>
* automated/lisp-tests.el:
Test #'clear-string, just added. Unfortunately there's no way to
be certain from Lisp that the old password data has been erased
after realloc; it may be worth adding a test to tests.c, but
*we'll be reading memory we shouldn't be*, so that gives me pause.
author | Aidan Kehoe <kehoea@parhasard.net> |
---|---|
date | Sat, 18 Apr 2015 23:00:14 +0100 |
parents | a85efdabe237 |
children |
line wrap: on
line source
/* 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_passwd; 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 = (tls_state_t *) 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; 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_passwd, 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 */ Fclear_string (lsp_password); 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 = (tls_state_t *) 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 gnutls_certificate_type_t type; 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_passwd, 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 */ Fclear_string (lsp_password); 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> #ifdef HAVE_X509_CHECK_HOST #include <openssl/x509v3.h> #endif /* 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 = (tls_state_t *) 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 char *) hostname, strlen (hostname), 0, NULL); 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; Extbyte *c_password; lsp_password = call1 (Qread_passwd, 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 */ Fclear_string (lsp_password); 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, "DEFAULT") != 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_passwd); #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 }