]> andersk Git - test.git/commitdiff
Add support for chained SSL certificates.
authorMarkus Gutschke <markus@shellinabox.com>
Sun, 29 Mar 2009 21:52:18 +0000 (21:52 +0000)
committerMarkus Gutschke <markus@shellinabox.com>
Sun, 29 Mar 2009 21:52:18 +0000 (21:52 +0000)
config.h
configure
configure.ac
libhttp/ssl.c
libhttp/ssl.h
make-chained-cert.sh [new file with mode: 0755]

index 426341357abbdf64fff1d9b578db8c7cde388c85..a04ce5e7ae0373bcd27641518fcf249df6d4577e 100644 (file)
--- a/config.h
+++ b/config.h
@@ -95,7 +95,7 @@
 #define STDC_HEADERS 1
 
 /* Most recent revision number in the version control system */
-#define VCS_REVISION "89"
+#define VCS_REVISION "90"
 
 /* Version number of package */
 #define VERSION "2.5"
index 4639fac1aaf00ee34e523dbee9088d5b43fd1b32..a3ecf8eecb1d83c6add041678fa34b0987709d00 100755 (executable)
--- a/configure
+++ b/configure
@@ -2055,7 +2055,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
-VCS_REVISION=89
+VCS_REVISION=90
 
 
 cat >>confdefs.h <<_ACEOF
index d14629a82f0343f917075d47c8f8f0cf42539947..7fb05d0148ade596f7e1faec1d68bdd28cba7f2f 100644 (file)
@@ -2,7 +2,7 @@ AC_PREREQ(2.57)
 
 dnl This is the one location where the authoritative version number is stored
 AC_INIT(shellinabox, 2.5, markus@shellinabox.com)
-VCS_REVISION=89
+VCS_REVISION=90
 AC_SUBST(VCS_REVISION)
 AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
                    [Most recent revision number in the version control system])
index ba20a927db7a26ecf30e7a838693929cbd25939d..d867b03bc3dcd31ac64d3eb711f3e13534bd3591 100644 (file)
 
 #include <dlfcn.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <netdb.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include "libhttp/ssl.h"
@@ -116,6 +119,8 @@ int           (*SSL_set_ex_data)(SSL *, int, void *);
 int           (*SSL_shutdown)(SSL *);
 int           (*SSL_write)(SSL *, const void *, int);
 SSL_METHOD *  (*SSLv23_server_method)(void);
+X509 *        (*d2i_X509)(X509 **px, const unsigned char **in, int len);
+void          (*X509_free)(X509 *a);
 #endif
 
 static void sslDestroyCachedContext(void *ssl_, char *context_) {
@@ -235,7 +240,9 @@ static void loadSSL(void) {
     { { &SSL_set_ex_data },             "SSL_set_ex_data" },
     { { &SSL_shutdown },                "SSL_shutdown" },
     { { &SSL_write },                   "SSL_write" },
-    { { &SSLv23_server_method },        "SSLv23_server_method" }
+    { { &SSLv23_server_method },        "SSLv23_server_method" },
+    { { &d2i_X509 },                    "d2i_X509" },
+    { { &X509_free },                   "X509_free" }
   };
   for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
     if (!(*symbols[i].var = loadSymbol("libssl.so", symbols[i].fn))) {
@@ -301,6 +308,193 @@ static void sslGenerateCertificate(const char *certificate,
   }
   free(cmd);
 }
+
+static const unsigned char *sslSecureReadASCIIFileToMem(int fd) {
+  size_t inc          = 16384;
+  size_t bufSize      = inc;
+  size_t len          = 0;
+  unsigned char *buf;
+  check((buf          = malloc(bufSize)) != NULL);
+  for (;;) {
+    check(len < bufSize - 1);
+    size_t  readLen   = bufSize - len - 1;
+    ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
+    if (bytesRead > 0) {
+      len            += bytesRead;
+    }
+    if (bytesRead != readLen) {
+      break;
+    }
+
+    // Instead of calling realloc(), allocate a new buffer, copy the data,
+    // and then clear the old buffer. This way, we are not accidentally
+    // leaving key material in memory.
+    unsigned char *newBuf;
+    check((newBuf     = malloc(bufSize + inc)) != NULL);
+    memcpy(newBuf, buf, len);
+    memset(buf, 0, bufSize);
+    free(buf);
+    buf               = newBuf;
+    bufSize          += inc;
+  }
+  check(len < bufSize);
+  buf[len]            = '\000';
+  return buf;
+}
+
+static const unsigned char *sslPEMtoASN1(const unsigned char *pem,
+                                         const char *record,
+                                         long *size,
+                                         const unsigned char **eor) {
+  if (eor) {
+    *eor             = NULL;
+  }
+  *size              = 0;
+  char *marker;
+  check((marker      = stringPrintf(NULL, "-----BEGIN %s-----",record))!=NULL);
+  unsigned char *ptr = (unsigned char *)strstr((char *)pem, marker);
+  if (!ptr) {
+    free(marker);
+    return NULL;
+  } else {
+    ptr             += strlen(marker);
+  }
+  *marker            = '\000';
+  check((marker      = stringPrintf(marker, "-----END %s-----",record))!=NULL);
+  unsigned char *end = (unsigned char *)strstr((char *)ptr, marker);
+  if (eor) {
+    *eor             = end + strlen(marker);
+  }
+  free(marker);
+  if (!end) {
+    return NULL;
+  }
+  unsigned char *ret;
+  size_t maxSize     = (((end - ptr)*6)+7)/8;
+  check((ret         = malloc(maxSize)) != NULL);
+  unsigned char *out = ret;
+  unsigned bits      = 0;
+  int count          = 0;
+  while (ptr < end) {
+    unsigned char ch = *ptr++;
+    if (ch >= 'A' && ch <= 'Z') {
+      ch            -= 'A';
+    } else if (ch >= 'a' && ch <= 'z') {
+      ch            -= 'a' - 26;
+    } else if (ch >= '0' && ch <= '9') {
+      ch            += 52 - '0';
+    } else if (ch == '+') {
+      ch            += 62 - '+';
+    } else if (ch == '/') {
+      ch            += 63 - '/';
+    } else if (ch == '=') {
+      while (ptr < end) {
+        if ((ch      = *ptr++) != '=' && ch > ' ') {
+          goto err;
+        }
+      }
+      break;
+    } else if (ch <= ' ') {
+      continue;
+    } else {
+   err:
+      free(ret);
+      return NULL;
+    }
+    check(ch <= 63);
+    check(count >= 0);
+    check(count <= 6);
+    bits             = (bits << 6) | ch;
+    count           += 6;
+    if (count >= 8) {
+      *out++         = (bits >> (count -= 8)) & 0xFF;
+    }
+  }
+  check(out - ret <= maxSize);
+  *size              = out - ret;
+  return ret;
+}
+
+static int sslSetCertificateFromFd(SSL_CTX *context, int fd) {
+  int rc                       = 0;
+  check(serverSupportsSSL());
+  check(fd >= 0);
+  const unsigned char *data    = sslSecureReadASCIIFileToMem(fd);
+  check(!NOINTR(close(fd)));
+  long dataSize                = (long)strlen((const char *)data);
+  long certSize, rsaSize, dsaSize, ecSize;
+  const unsigned char *record;
+  const unsigned char *cert    = sslPEMtoASN1(data, "CERTIFICATE", &certSize,
+                                              &record);
+  const unsigned char *rsa     = sslPEMtoASN1(data, "RSA PRIVATE KEY",&rsaSize,
+                                              NULL);
+  const unsigned char *dsa     = sslPEMtoASN1(data, "DSA PRIVATE KEY",&dsaSize,
+                                              NULL);
+  const unsigned char *ec      = sslPEMtoASN1(data, "EC PRIVATE KEY",  &ecSize,
+                                              NULL);
+  if (certSize && (rsaSize || dsaSize
+#ifdef EVP_PKEY_EC
+                                      || ecSize
+#endif
+                                               ) &&
+      SSL_CTX_use_certificate_ASN1(context, certSize, cert) &&
+      (!rsaSize ||
+       SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, context, rsa, rsaSize)) &&
+      (!dsaSize ||
+       SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_DSA, context, dsa, dsaSize))
+#ifdef EVP_PKEY_EC
+      &&
+      (!ecSize ||
+       SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, context, ec, ecSize))
+#endif
+      ) {
+    memset((char *)cert, 0, certSize);
+    free((char *)cert);
+    while (record) {
+      cert                     = sslPEMtoASN1(record, "CERTIFICATE", &certSize,
+                                              &record);
+      if (cert) {
+        X509 *x509;
+        const unsigned char *c = cert;
+        check(x509             = d2i_X509(NULL, &c, certSize));
+        memset((char *)cert, 0, certSize);
+        free((char *)cert);
+        if (!SSL_CTX_add_extra_chain_cert(context, x509)) {
+          X509_free(x509);
+          break;
+        }
+      }
+    }
+    if (!record && SSL_CTX_check_private_key(context)) {
+      rc                       = 1;
+    }
+    dcheck(!ERR_peek_error());
+    ERR_clear_error();
+  } else {
+    memset((char *)cert, 0, certSize);
+    free((char *)cert);
+  }
+  memset((char *)data, 0, dataSize);
+  free((char *)data);
+  memset((char *)rsa, 0, rsaSize);
+  free((char *)rsa);
+  memset((char *)dsa, 0, dsaSize);
+  free((char *)dsa);
+  memset((char *)ec, 0, ecSize);
+  free((char *)ec);
+  return rc;
+}
+
+static int sslSetCertificateFromFile(SSL_CTX *context,
+                                     const char *filename) {
+  int fd = open(filename, O_RDONLY);
+  if (fd < 0) {
+    return -1;
+  }
+  int rc = sslSetCertificateFromFd(context, fd);
+  NOINTR(close(fd));
+  return rc;
+}
 #endif
 
 #ifdef HAVE_TLSEXT
@@ -346,20 +540,14 @@ static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
     char *certificate     = stringPrintfUnchecked(NULL,
                                                   ssl->sniCertificatePattern,
                                                   serverName);
-    if (!SSL_CTX_use_certificate_file(context, certificate, SSL_FILETYPE_PEM)||
-        !SSL_CTX_use_PrivateKey_file(context, certificate, SSL_FILETYPE_PEM) ||
-        !SSL_CTX_check_private_key(context)) {
+    if (sslSetCertificateFromFile(context, certificate) < 0) {
       if (ssl->generateMissing) {
         sslGenerateCertificate(certificate, serverName + 1);
-        if (!SSL_CTX_use_certificate_file(context, certificate,
-                                          SSL_FILETYPE_PEM) ||
-            !SSL_CTX_use_PrivateKey_file(context, certificate,
-                                         SSL_FILETYPE_PEM) ||
-            !SSL_CTX_check_private_key(context)) {
-          goto certificate_missing;
-        }
+
+        // No need to check the certificate. If we fail to set it, we will use
+        // the default certificate, instead.
+        sslSetCertificateFromFile(context, certificate);
       } else {
-      certificate_missing:
         warn("Could not find matching certificate \"%s\" for \"%s\"",
              certificate, serverName + 1);
         SSL_CTX_free(context);
@@ -393,13 +581,10 @@ void sslSetCertificate(struct SSLSupport *ssl, const char *filename,
     memmove(ptr, ptr + 2, strlen(ptr)-1);
   }
 
+  // Try to set the default certificate. If necessary, (re-)generate it.
   check(ssl->sslContext              = SSL_CTX_new(SSLv23_server_method()));
   if (autoGenerateMissing) {
-    if (!SSL_CTX_use_certificate_file(ssl->sslContext, defaultCertificate,
-                                      SSL_FILETYPE_PEM) ||
-        !SSL_CTX_use_PrivateKey_file(ssl->sslContext, defaultCertificate,
-                                     SSL_FILETYPE_PEM) ||
-        !SSL_CTX_check_private_key(ssl->sslContext)) {
+    if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
       char hostname[256], buf[4096];
       check(!gethostname(hostname, sizeof(hostname)));
       struct hostent he_buf, *he;
@@ -414,17 +599,15 @@ void sslSetCertificate(struct SSLSupport *ssl, const char *filename,
       goto valid_certificate;
     }
   }
-  if (!SSL_CTX_use_certificate_file(ssl->sslContext, defaultCertificate,
-                                    SSL_FILETYPE_PEM) ||
-      !SSL_CTX_use_PrivateKey_file(ssl->sslContext, defaultCertificate,
-                                   SSL_FILETYPE_PEM) ||
-      !SSL_CTX_check_private_key(ssl->sslContext)) {
+  if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
     fatal("Cannot read valid certificate from \"%s\". "
           "Check file permissions and file format.", defaultCertificate);
   }
  valid_certificate:
   free(defaultCertificate);
 
+  // Enable SNI support so that we can set a different certificate, if the
+  // client asked for it.
 #ifdef HAVE_TLSEXT
   if (ptr != NULL) {
     check(ssl->sniCertificatePattern = strdup(filename));
@@ -440,159 +623,52 @@ void sslSetCertificate(struct SSLSupport *ssl, const char *filename,
 #endif
 }
 
+// Convert the file descriptor to a human-readable format. Attempts to
+// retrieve the original file name where possible.
 #ifdef HAVE_OPENSSL
-static const unsigned char *sslSecureReadASCIIFileToMem(int fd) {
-  size_t inc          = 16384;
-  size_t bufSize      = inc;
-  size_t len          = 0;
-  unsigned char *buf;
-  check((buf          = malloc(bufSize)) != NULL);
+static char *sslFdToFilename(int fd) {
+  char *proc, *buf;
+  int  len         = 128;
+  check(proc       = stringPrintf(NULL, "/proc/self/fd/%d", fd));
+  check(buf        = malloc(len));
   for (;;) {
-    check(len < bufSize - 1);
-    size_t  readLen   = bufSize - len - 1;
-    ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
-    if (bytesRead > 0) {
-      len            += bytesRead;
-    }
-    if (bytesRead != readLen) {
-      break;
-    }
-
-    // Instead of calling realloc(), allocate a new buffer, copy the data,
-    // and then clear the old buffer. This way, we are not accidentally
-    // leaving key material in memory.
-    unsigned char *newBuf;
-    check((newBuf     = malloc(bufSize + inc)) != NULL);
-    memcpy(newBuf, buf, len);
-    memset(buf, 0, bufSize);
-    free(buf);
-    buf               = newBuf;
-    bufSize          += inc;
-  }
-  check(len < bufSize);
-  buf[len]            = '\000';
-  return buf;
-}
-
-static const unsigned char *sslPEMtoASN1(const unsigned char *pem,
-                                         const char *record,
-                                         long *size) {
-  *size              = -1;
-  char *marker;
-  check((marker      = stringPrintf(NULL, "-----BEGIN %s-----",record))!=NULL);
-  unsigned char *ptr = (unsigned char *)strstr((char *)pem, marker);
-  if (!ptr) {
-    free(marker);
-    return NULL;
-  } else {
-    ptr             += strlen(marker);
-  }
-  *marker            = '\000';
-  check((marker      = stringPrintf(marker, "-----END %s-----",record))!=NULL);
-  unsigned char *end = (unsigned char *)strstr((char *)ptr, marker);
-  free(marker);
-  if (!end) {
-    return NULL;
-  }
-  unsigned char *ret;
-  size_t maxSize     = (((end - ptr)*6)+7)/8;
-  check((ret         = malloc(maxSize)) != NULL);
-  unsigned char *out = ret;
-  unsigned bits      = 0;
-  int count          = 0;
-  while (ptr < end) {
-    unsigned char ch = *ptr++;
-    if (ch >= 'A' && ch <= 'Z') {
-      ch            -= 'A';
-    } else if (ch >= 'a' && ch <= 'z') {
-      ch            -= 'a' - 26;
-    } else if (ch >= '0' && ch <= '9') {
-      ch            += 52 - '0';
-    } else if (ch == '+') {
-      ch            += 62 - '+';
-    } else if (ch == '/') {
-      ch            += 63 - '/';
-    } else if (ch == '=') {
-      while (ptr < end) {
-        if ((ch      = *ptr++) != '=' && ch > ' ') {
-          goto err;
-        }
-      }
-      break;
-    } else if (ch <= ' ') {
-      continue;
+    ssize_t i;
+    if ((i = readlink(proc, buf + 1, len-3)) < 0) {
+      free(proc);
+      free(buf);
+      check(buf    = stringPrintf(NULL, "fd %d", fd));
+      return buf;
+    } else if (i >= len-3) {
+      len         += 512;
+      check(buf    = realloc(buf, len));
     } else {
-   err:
-      free(ret);
-      return NULL;
-    }
-    check(ch <= 63);
-    check(count >= 0);
-    check(count <= 6);
-    bits             = (bits << 6) | ch;
-    count           += 6;
-    if (count >= 8) {
-      *out++         = (bits >> (count -= 8)) & 0xFF;
+      free(proc);
+      check(i >= 0 && i < len);
+      buf[i+1]     = '\000';
+      struct stat sb;
+      if (!stat(buf + 1, &sb) && S_ISREG(sb.st_mode)) {
+        *buf       = '"';
+        buf[i + 1] = '"';
+        buf[i + 2] = '\000';
+        return buf;
+      } else {
+        free(buf);
+        check(buf  = stringPrintf(NULL, "fd %d", fd));
+        return buf;
+      }
     }
   }
-  check(out - ret <= maxSize);
-  *size              = out - ret;
-  return ret;
 }
 #endif
 
 void sslSetCertificateFd(struct SSLSupport *ssl, int fd) {
 #ifdef HAVE_OPENSSL
-  check(serverSupportsSSL());
-  check(fd >= 0);
-  check(ssl->sslContext     = SSL_CTX_new(SSLv23_server_method()));
-  const unsigned char *data = sslSecureReadASCIIFileToMem(fd);
-  check(!NOINTR(close(fd)));
-  long dataSize             = (long)strlen((const char *)data);
-  long certSize, rsaSize, dsaSize, ecSize;
-  const unsigned char *cert = sslPEMtoASN1(data, "CERTIFICATE", &certSize);
-  const unsigned char *rsa  = sslPEMtoASN1(data, "RSA PRIVATE KEY", &rsaSize);
-  const unsigned char *dsa  = sslPEMtoASN1(data, "DSA PRIVATE KEY", &dsaSize);
-  const unsigned char *ec   = sslPEMtoASN1(data, "EC PRIVATE KEY",  &ecSize);
-  if (!certSize || !(rsaSize > 0 || dsaSize > 0
-#ifdef EVP_PKEY_EC
-                                                || ecSize > 0
-#endif
-                                                             ) ||
-      !SSL_CTX_use_certificate_ASN1(ssl->sslContext, certSize, cert) ||
-      (rsaSize > 0 &&
-       !SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ssl->sslContext, rsa,
-                                    rsaSize)) ||
-      (dsaSize > 0 &&
-       !SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_DSA, ssl->sslContext, dsa,
-                                    dsaSize)) ||
-#ifdef EVP_PKEY_EC
-      (ecSize > 0 &&
-       !SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, ssl->sslContext, ec,
-                                    ecSize)) ||
-#endif
-      !SSL_CTX_check_private_key(ssl->sslContext)) {
-    fatal("Cannot read valid certificate from fd %d. Check file format.", fd);
-  }
-  dcheck(!ERR_peek_error());
-  ERR_clear_error();
-  memset((char *)data, 0, dataSize);
-  free((char *)data);
-  memset((char *)cert, 0, certSize);
-  free((char *)cert);
-  if (rsaSize > 0) {
-    memset((char *)rsa, 0, rsaSize);
-    free((char *)rsa);
-  }
-  if (dsaSize > 0) {
-    memset((char *)dsa, 0, dsaSize);
-    free((char *)dsa);
-  }
-  if (ecSize > 0) {
-    memset((char *)ec, 0, ecSize);
-    free((char *)ec);
+  check(ssl->sslContext = SSL_CTX_new(SSLv23_server_method()));
+  if (!sslSetCertificateFromFd(ssl->sslContext, fd)) {
+    fatal("Cannot read valid certificate from %s. Check file format.",
+          sslFdToFilename(fd));
   }
-  ssl->generateMissing     = 0;
+  ssl->generateMissing  = 0;
 #endif
 }
 
index c2d2f42e710e0be1f9cbb8a95e6065e37839fc27..f7e49d650149edfcb841d38a97c6f58fea3fb847 100644 (file)
@@ -108,6 +108,8 @@ extern int     (*x_SSL_set_ex_data)(SSL *, int, void *);
 extern int     (*x_SSL_shutdown)(SSL *);
 extern int     (*x_SSL_write)(SSL *, const void *, int);
 extern SSL_METHOD *(*x_SSLv23_server_method)(void);
+extern X509 *  (*x_d2i_X509)(X509 **px, const unsigned char **in, int len);
+extern void    (*x_X509_free)(X509 *a);
 
 #define BIO_ctrl                     x_BIO_ctrl
 #define BIO_f_buffer                 x_BIO_f_buffer
@@ -146,6 +148,8 @@ extern SSL_METHOD *(*x_SSLv23_server_method)(void);
 #define SSL_shutdown                 x_SSL_shutdown
 #define SSL_write                    x_SSL_write
 #define SSLv23_server_method         x_SSLv23_server_method
+#define d2i_X509                     x_d2i_X509
+#define X509_free                    x_X509_free
 
 #undef  BIO_set_buffer_read_data
 #undef  SSL_CTX_set_tlsext_servername_arg
diff --git a/make-chained-cert.sh b/make-chained-cert.sh
new file mode 100755 (executable)
index 0000000..eab1a6f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash -e
+
+tmp=/tmp/make-chained-cert.$$
+trap 'echo; tput bel; echo FAILURE; rm -rf "${tmp}"; exit 1' EXIT INT TERM QUIT
+mkdir -p "${tmp}/demoCA/newcerts"
+printf '%08x' $$ >"${tmp}/demoCA/serial"
+touch "${tmp}/demoCA/index.txt"
+cd "${tmp}"
+
+openssl req -nodes -new -x509 -keyout "${tmp}/ca-key.pem"                     \
+            -out "${tmp}/ca-cert.pem" -days 7300                              \
+            -subj "/CN=Demo CA/" 2>/dev/null
+
+openssl x509 -in "${tmp}/ca-cert.pem" -out "${tmp}/ca-cert.crt" 2>/dev/null
+
+openssl req -nodes -new -keyout /dev/stdout                                   \
+            -out "${tmp}/ssl-req.pem" -days 7300 -subj "/CN=$(hostname -f)/"  \
+            2>/dev/null | cat
+
+openssl ca -batch -keyfile "${tmp}/ca-key.pem" -cert "${tmp}/ca-cert.crt"     \
+           -notext -policy policy_anything -out /dev/stdout                   \
+           -infiles "${tmp}/ssl-req.pem" 2>/dev/null | cat
+cat "${tmp}/ca-cert.crt"
+
+trap 'rm -rf "${tmp}"' EXIT INT TERM QUIT
+
+exit 0
This page took 0.070881 seconds and 5 git commands to generate.