* All OS-level includes are now included from webcit.h instead of from
[citadel.git] / webcit / crypto.c
index 145beb79e9e7041b6dec101fed0725aaab5c64db..98f1450795b351fb7af7a0f05d5df8ab79dcfe2d 100644 (file)
@@ -1,16 +1,11 @@
-/* $Id$ */
+/*
+ * $Id$
+ *
+ * Provides HTTPS, when the OpenSSL library is available.
+ */
 
 #ifdef HAVE_OPENSSL
 
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <pthread.h>
-
-#include <sys/time.h>
 #include "webcit.h"
 #include "webserver.h"
 
 #define CTDL_KEY_PATH          CTDL_CRYPTO_DIR "/citadel.key"
 #define CTDL_CSR_PATH          CTDL_CRYPTO_DIR "/citadel.csr"
 #define CTDL_CER_PATH          CTDL_CRYPTO_DIR "/citadel.cer"
-#define SIGN_DAYS              3650    /* Ten years */
-
-
-/* Shared Diffie-Hellman parameters */
-#define DH_P           "1A74527AEE4EE2568E85D4FB2E65E18C9394B9C80C42507D7A6A0DBE9A9A54B05A9A96800C34C7AA5297095B69C88901EEFD127F969DCA26A54C0E0B5C5473EBAEB00957D2633ECAE3835775425DE66C0DE6D024DBB17445E06E6B0C78415E589B8814F08531D02FD43778451E7685541079CFFB79EF0D26EFEEBBB69D1E80383"
-#define DH_G           "2"
-#define DH_L           1024
-#define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:@STRENGTH"  /* see ciphers(1) */
+#define SIGN_DAYS              365
 
 SSL_CTX *ssl_ctx;              /* SSL context */
 pthread_mutex_t **SSLCritters; /* Things needing locking */
 
+pthread_key_t ThreadSSL;       /* Per-thread SSL context */
+
 static unsigned long id_callback(void)
 {
        return (unsigned long) pthread_self();
 }
 
- /*
-  * Set up the cert things on the server side. We do need both the
-  * private key (in key_file) and the cert (in cert_file).
-  * Both files may be identical.
-  *
-  * This function is taken from OpenSSL apps/s_cb.c
-  */
-
-static int ctdl_install_certificate(SSL_CTX * ctx,
-                         const char *cert_file, const char *key_file)
-{
-       if (cert_file != NULL) {
-               if (SSL_CTX_use_certificate_file(ctx, cert_file,
-                                                SSL_FILETYPE_PEM) <= 0) {
-                       lprintf(3, "unable to get certificate from '%s'",
-                               cert_file);
-                       return (0);
-               }
-               if (key_file == NULL)
-                       key_file = cert_file;
-               if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
-                                               SSL_FILETYPE_PEM) <= 0) {
-                       lprintf(3, "unable to get private key from '%s'",
-                               key_file);
-                       return (0);
-               }
-               /* Now we know that a key and cert have been set against
-                * the SSL context */
-               if (!SSL_CTX_check_private_key(ctx)) {
-                       lprintf(3,
-                               "Private key does not match the certificate public key");
-                       return (0);
-               }
-       }
-       return (1);
-}
-
 
 void init_ssl(void)
 {
        SSL_METHOD *ssl_method;
-       DH *dh;
        RSA *rsa=NULL;
        X509_REQ *req = NULL;
        X509 *cer = NULL;
@@ -84,6 +36,7 @@ void init_ssl(void)
        EVP_PKEY *req_pkey = NULL;
        X509_NAME *name = NULL;
        FILE *fp;
+       char buf[SIZ];
 
        if (!access("/var/run/egd-pool", F_OK))
                RAND_egd("/var/run/egd-pool");
@@ -125,56 +78,34 @@ void init_ssl(void)
                        ERR_reason_error_string(ERR_get_error()));
                return;
        }
-       if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
-               lprintf(3, "SSL: No ciphers available\n");
-               SSL_CTX_free(ssl_ctx);
-               ssl_ctx = NULL;
-               return;
-       }
-#if 0
-#if SSLEAY_VERSION_NUMBER >= 0x00906000L
-       SSL_CTX_set_mode(ssl_ctx, SSL_CTX_get_mode(ssl_ctx) |
-                        SSL_MODE_AUTO_RETRY);
-#endif
-#endif
 
        CRYPTO_set_locking_callback(ssl_lock);
        CRYPTO_set_id_callback(id_callback);
 
-       /* Load DH parameters into the context */
-       dh = DH_new();
-       if (!dh) {
-               lprintf(3, "init_ssl() can't allocate a DH object: %s\n",
-                       ERR_reason_error_string(ERR_get_error()));
-               SSL_CTX_free(ssl_ctx);
-               ssl_ctx = NULL;
-               return;
-       }
-       if (!(BN_hex2bn(&(dh->p), DH_P))) {
-               lprintf(3, "init_ssl() can't assign DH_P: %s\n",
-                       ERR_reason_error_string(ERR_get_error()));
-               SSL_CTX_free(ssl_ctx);
-               ssl_ctx = NULL;
-               return;
-       }
-       if (!(BN_hex2bn(&(dh->g), DH_G))) {
-               lprintf(3, "init_ssl() can't assign DH_G: %s\n",
-                       ERR_reason_error_string(ERR_get_error()));
-               SSL_CTX_free(ssl_ctx);
-               ssl_ctx = NULL;
-               return;
-       }
-       dh->length = DH_L;
-       SSL_CTX_set_tmp_dh(ssl_ctx, dh);
-       DH_free(dh);
-
        /* Get our certificates in order.
         * First, create the key/cert directory if it's not there already...
         */
        mkdir(CTDL_CRYPTO_DIR, 0700);
 
        /*
-        * Generate a key pair if we don't have one.
+        * Before attempting to generate keys/certificates, first try
+        * link to them from the Citadel server if it's on the same host.
+        * We ignore any error return because it either meant that there
+        * was nothing in Citadel to link from (in which case we just
+        * generate new files) or the target files already exist (which
+        * is not fatal either).
+        */
+       if (!strcasecmp(ctdlhost, "uds")) {
+               sprintf(buf, "%s/keys/citadel.key", ctdlport);
+               symlink(buf, CTDL_KEY_PATH);
+               sprintf(buf, "%s/keys/citadel.csr", ctdlport);
+               symlink(buf, CTDL_CSR_PATH);
+               sprintf(buf, "%s/keys/citadel.cer", ctdlport);
+               symlink(buf, CTDL_CER_PATH);
+       }
+
+       /*
+        * If we still don't have a private key, generate one.
         */
        if (access(CTDL_KEY_PATH, R_OK) != 0) {
                lprintf(5, "Generating RSA key pair.\n");
@@ -324,10 +255,12 @@ void init_ssl(void)
                        if (req) {
                                if (cer = X509_new(), cer != NULL) {
 
+                                       ASN1_INTEGER_set(X509_get_serialNumber(cer), 0);
                                        X509_set_issuer_name(cer, req->req_info->subject);
                                        X509_set_subject_name(cer, req->req_info->subject);
-                                       X509_gmtime_adj(X509_get_notBefore(cer),0);
+                                       X509_gmtime_adj(X509_get_notBefore(cer), 0);
                                        X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS);
+
                                        req_pkey = X509_REQ_get_pubkey(req);
                                        X509_set_pubkey(cer, req_pkey);
                                        EVP_PKEY_free(req_pkey);
@@ -353,44 +286,45 @@ void init_ssl(void)
                }
        }
 
-
        /*
         * Now try to bind to the key and certificate.
+        * Note that we use SSL_CTX_use_certificate_chain_file() which allows
+        * the certificate file to contain intermediate certificates.
         */
-       if (ctdl_install_certificate(ssl_ctx,
-                       CTDL_CER_PATH,
-                       CTDL_KEY_PATH) != 1)
-       {
+       SSL_CTX_use_certificate_chain_file(ssl_ctx, CTDL_CER_PATH);
+       SSL_CTX_use_PrivateKey_file(ssl_ctx, CTDL_KEY_PATH, SSL_FILETYPE_PEM);
+       if ( !SSL_CTX_check_private_key(ssl_ctx) ) {
                lprintf(3, "Cannot install certificate: %s\n",
                                ERR_reason_error_string(ERR_get_error()));
        }
-
+       
 }
 
 
 /*
  * starttls() starts SSL/TLS encryption for the current session.
  */
-int starttls(void) {
-
+int starttls(int sock) {
        int retval, bits, alg_bits;
+       SSL *newssl;
+
+       pthread_setspecific(ThreadSSL, NULL);
 
        if (!ssl_ctx) {
                return(1);
        }
-       if (!(WC->ssl = SSL_new(ssl_ctx))) {
+       if (!(newssl = SSL_new(ssl_ctx))) {
                lprintf(3, "SSL_new failed: %s\n",
                                ERR_reason_error_string(ERR_get_error()));
                return(2);
        }
-       if (!(SSL_set_fd(WC->ssl, WC->http_sock))) {
+       if (!(SSL_set_fd(newssl, sock))) {
                lprintf(3, "SSL_set_fd failed: %s\n",
                        ERR_reason_error_string(ERR_get_error()));
-               SSL_free(WC->ssl);
-               WC->ssl = NULL;
+               SSL_free(newssl);
                return(3);
        }
-       retval = SSL_accept(WC->ssl);
+       retval = SSL_accept(newssl);
        if (retval < 1) {
                /*
                 * Can't notify the client of an error here; they will
@@ -399,21 +333,23 @@ int starttls(void) {
                 */
                long errval;
 
-               errval = SSL_get_error(WC->ssl, retval);
+               errval = SSL_get_error(newssl, retval);
                lprintf(3, "SSL_accept failed: %s\n",
                        ERR_reason_error_string(ERR_get_error()));
-               SSL_free(WC->ssl);
-               WC->ssl = NULL;
+               SSL_free(newssl);
+               newssl = NULL;
                return(4);
        }
-       BIO_set_close(WC->ssl->rbio, BIO_NOCLOSE);
+       BIO_set_close(newssl->rbio, BIO_NOCLOSE);
        bits =
-           SSL_CIPHER_get_bits(SSL_get_current_cipher(WC->ssl),
+           SSL_CIPHER_get_bits(SSL_get_current_cipher(newssl),
                                &alg_bits);
        lprintf(5, "SSL/TLS using %s on %s (%d of %d bits)\n",
-               SSL_CIPHER_get_name(SSL_get_current_cipher(WC->ssl)),
-               SSL_CIPHER_get_version(SSL_get_current_cipher(WC->ssl)),
+               SSL_CIPHER_get_name(SSL_get_current_cipher(newssl)),
+               SSL_CIPHER_get_version(SSL_get_current_cipher(newssl)),
                bits, alg_bits);
+
+       pthread_setspecific(ThreadSSL, newssl);
        return(0);
 }
 
@@ -428,9 +364,9 @@ int starttls(void) {
 void endtls(void)
 {
        lprintf(5, "Ending SSL/TLS\n");
-       SSL_shutdown(WC->ssl);
-       SSL_free(WC->ssl);
-       WC->ssl = NULL;
+       SSL_shutdown(THREADSSL);
+       SSL_free(THREADSSL);
+       pthread_setspecific(ThreadSSL, NULL);
 }
 
 
@@ -445,4 +381,102 @@ void ssl_lock(int mode, int n, const char *file, int line)
                pthread_mutex_unlock(SSLCritters[n]);
 }
 
+/*
+ * client_write_ssl() Send binary data to the client encrypted.
+ */
+void client_write_ssl(char *buf, int nbytes)
+{
+       int retval;
+       int nremain;
+       char junk[1];
+
+       nremain = nbytes;
+
+       while (nremain > 0) {
+               if (SSL_want_write(THREADSSL)) {
+                       if ((SSL_read(THREADSSL, junk, 0)) < 1) {
+                               lprintf(9, "SSL_read in client_write: %s\n", ERR_reason_error_string(ERR_get_error()));
+                       }
+               }
+               retval =
+                   SSL_write(THREADSSL, &buf[nbytes - nremain], nremain);
+               if (retval < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(THREADSSL, retval);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                           errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       lprintf(9, "SSL_write got error %ld, ret %d\n", errval, retval);
+                       if (retval == -1)
+                               lprintf(9, "errno is %d\n", errno);
+                       endtls();
+                       return;
+               }
+               nremain -= retval;
+       }
+}
+
+
+/*
+ * client_read_ssl() - read data from the encrypted layer.
+ */
+int client_read_ssl(char *buf, int bytes, int timeout)
+{
+#if 0
+       fd_set rfds;
+       struct timeval tv;
+       int retval;
+       int s;
+#endif
+       int len, rlen;
+       char junk[1];
+
+       len = 0;
+       while (len < bytes) {
+#if 0
+               /*
+                * This code is disabled because we don't need it when
+                * using blocking reads (which we are). -IO
+                */
+               FD_ZERO(&rfds);
+               s = BIO_get_fd(THREADSSL->rbio, NULL);
+               FD_SET(s, &rfds);
+               tv.tv_sec = timeout;
+               tv.tv_usec = 0;
+
+               retval = select(s + 1, &rfds, NULL, NULL, &tv);
+
+               if (FD_ISSET(s, &rfds) == 0) {
+                       return (0);
+               }
+
+#endif
+               if (SSL_want_read(THREADSSL)) {
+                       if ((SSL_write(THREADSSL, junk, 0)) < 1) {
+                               lprintf(9, "SSL_write in client_read: %s\n", ERR_reason_error_string(ERR_get_error()));
+                       }
+               }
+               rlen = SSL_read(THREADSSL, &buf[len], bytes - len);
+               if (rlen < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(THREADSSL, rlen);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                           errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       lprintf(9, "SSL_read got error %ld\n", errval);
+                       endtls();
+                       return (0);
+               }
+               len += rlen;
+       }
+       return (1);
+}
+
+
 #endif                         /* HAVE_OPENSSL */