diff --git a/libhttp/http.h b/libhttp/http.h index 3b79771..9bd2bf6 100644 --- a/libhttp/http.h +++ b/libhttp/http.h @@ -94,6 +94,7 @@ int serverSupportsSSL(); void serverEnableSSL(Server *server, int flag); void serverSetCertificate(Server *server, const char *filename, int autoGenerateMissing); +void serverSetCertificateFd(Server *server, int fd); void serverSetNumericHosts(Server *server, int numericHosts); void httpTransfer(HttpConnection *http, char *msg, int len); diff --git a/libhttp/libhttp.sym b/libhttp/libhttp.sym index cdc79b4..afa641d 100644 --- a/libhttp/libhttp.sym +++ b/libhttp/libhttp.sym @@ -16,6 +16,7 @@ serverLoop serverSupportsSSL serverEnableSSL serverSetCertificate +serverSetCertificateFd serverSetNumericHosts httpTransfer httpTransferPartialReply diff --git a/libhttp/server.c b/libhttp/server.c index a33ec07..d1e5486 100644 --- a/libhttp/server.c +++ b/libhttp/server.c @@ -537,6 +537,10 @@ void serverSetCertificate(struct Server *server, const char *filename, sslSetCertificate(&server->ssl, filename, autoGenerateMissing); } +void serverSetCertificateFd(struct Server *server, int fd) { + sslSetCertificateFd(&server->ssl, fd); +} + void serverSetNumericHosts(struct Server *server, int numericHosts) { server->numericHosts = numericHosts; } diff --git a/libhttp/server.h b/libhttp/server.h index 38db472..98da282 100644 --- a/libhttp/server.h +++ b/libhttp/server.h @@ -111,6 +111,7 @@ void serverLoop(struct Server *server); void serverEnableSSL(struct Server *server, int flag); void serverSetCertificate(struct Server *server, const char *filename, int autoGenerateMissing); +void serverSetCertificateFd(struct Server *server, int fd); void serverSetNumericHosts(struct Server *server, int numericHosts); struct Trie *serverGetHttpHandlers(struct Server *server); diff --git a/libhttp/ssl.c b/libhttp/ssl.c index 0b9ebbf..61a1d0c 100644 --- a/libhttp/ssl.c +++ b/libhttp/ssl.c @@ -90,7 +90,11 @@ long (*SSL_CTX_ctrl)(SSL_CTX *, int, long, void *); void (*SSL_CTX_free)(SSL_CTX *); SSL_CTX * (*SSL_CTX_new)(SSL_METHOD *); int (*SSL_CTX_use_PrivateKey_file)(SSL_CTX *, const char *, int); +int (*SSL_CTX_use_PrivateKey_ASN1)(int, SSL_CTX *, + const unsigned char *, long); int (*SSL_CTX_use_certificate_file)(SSL_CTX *, const char *, int); +int (*SSL_CTX_use_certificate_ASN1)(SSL_CTX *, long, + const unsigned char *); long (*SSL_ctrl)(SSL *, int, long, void *); void (*SSL_free)(SSL *); int (*SSL_get_error)(const SSL *, int); @@ -200,7 +204,9 @@ static void loadSSL(void) { { { &SSL_CTX_free }, "SSL_CTX_free" }, { { &SSL_CTX_new }, "SSL_CTX_new" }, { { &SSL_CTX_use_PrivateKey_file }, "SSL_CTX_use_PrivateKey_file" }, + { { &SSL_CTX_use_PrivateKey_ASN1 }, "SSL_CTX_use_PrivateKey_ASN1" }, { { &SSL_CTX_use_certificate_file },"SSL_CTX_use_certificate_file"}, + { { &SSL_CTX_use_certificate_ASN1 },"SSL_CTX_use_certificate_ASN1"}, { { &SSL_ctrl }, "SSL_ctrl" }, { { &SSL_free }, "SSL_free" }, { { &SSL_get_error }, "SSL_get_error" }, @@ -419,6 +425,156 @@ void sslSetCertificate(struct SSLSupport *ssl, const char *filename, #endif } +#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); + 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; + } 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; +} +#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 || ecSize > 0) || + !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)) || + (ecSize > 0 && + !SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, ssl->sslContext, ec, + ecSize)) || + !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); + } + ssl->generateMissing = 0; +#endif +} + int sslEnable(struct SSLSupport *ssl, int enabled) { int old = ssl->enabled; ssl->enabled = enabled; diff --git a/libhttp/ssl.h b/libhttp/ssl.h index 240b9b2..e273a65 100644 --- a/libhttp/ssl.h +++ b/libhttp/ssl.h @@ -83,7 +83,11 @@ extern long (*x_SSL_CTX_ctrl)(SSL_CTX *, int, long, void *); extern void (*x_SSL_CTX_free)(SSL_CTX *); extern SSL_CTX*(*x_SSL_CTX_new)(SSL_METHOD *); extern int (*x_SSL_CTX_use_PrivateKey_file)(SSL_CTX *, const char *, int); +extern int (*x_SSL_CTX_use_PrivateKey_ASN1)(int, SSL_CTX *, + const unsigned char *, long); extern int (*x_SSL_CTX_use_certificate_file)(SSL_CTX *, const char *, int); +extern int (*x_SSL_CTX_use_certificate_ASN1)(SSL_CTX *, long, + const unsigned char *); extern long (*x_SSL_ctrl)(SSL *, int, long, void *); extern void (*x_SSL_free)(SSL *); extern int (*x_SSL_get_error)(const SSL *, int); @@ -119,7 +123,9 @@ extern SSL_METHOD *(*x_SSLv23_server_method)(void); #define SSL_CTX_free x_SSL_CTX_free #define SSL_CTX_new x_SSL_CTX_new #define SSL_CTX_use_PrivateKey_file x_SSL_CTX_use_PrivateKey_file +#define SSL_CTX_use_PrivateKey_ASN1 x_SSL_CTX_use_PrivateKey_ASN1 #define SSL_CTX_use_certificate_file x_SSL_CTX_use_certificate_file +#define SSL_CTX_use_certificate_ASN1 x_SSL_CTX_use_certificate_ASN1 #define SSL_ctrl x_SSL_ctrl #define SSL_free x_SSL_free #define SSL_get_error x_SSL_get_error @@ -175,6 +181,7 @@ void deleteSSL(struct SSLSupport *ssl); void sslGenerateCertificate(const char *certificate, const char *serverName); void sslSetCertificate(struct SSLSupport *ssl, const char *filename, int autoGenerateMissing); +void sslSetCertificateFd(struct SSLSupport *ssl, int fd); int sslEnable(struct SSLSupport *ssl, int enabled); void sslBlockSigPipe(); int sslUnblockSigPipe(); diff --git a/shellinabox/launcher.c b/shellinabox/launcher.c index 340db01..65d8377 100644 --- a/shellinabox/launcher.c +++ b/shellinabox/launcher.c @@ -1036,7 +1036,6 @@ int forkLauncher(void) { // Temporarily drop most permissions. We still retain the ability to // switch back to root, which is necessary for launching "login". lowerPrivileges(); - NOINTR(close(pair[0])); closeAllFds((int []){ pair[1] }, 1); launcherDaemon(pair[1]); fatal("exit() failed!"); diff --git a/shellinabox/shellinaboxd.c b/shellinabox/shellinaboxd.c index 26904eb..f8d2314 100644 --- a/shellinabox/shellinaboxd.c +++ b/shellinabox/shellinaboxd.c @@ -74,10 +74,11 @@ static int port; static int portMin; static int portMax; -static int noBeep = 0; -static int numericHosts = 0; -static int enableSSL = 1; +static int noBeep = 0; +static int numericHosts = 0; +static int enableSSL = 1; static char *certificateDir; +static int certificateFd = -1; static HashMap *externalFiles; static Server *cgiServer; static char *cgiSessionKey; @@ -592,7 +593,8 @@ static void usage(void) { " ${user} - user name", !serverSupportsSSL() ? "" : " -c, --cert=CERTDIR set certificate dir " - "(default: $PWD)\n", + "(default: $PWD)\n" + " --cert-fd=FD set certificate file from fd", group, PORTNUM, !serverSupportsSSL() ? "" : " -t, --disable-ssl disable transparent SSL support\n", @@ -623,6 +625,7 @@ static void parseArgs(int argc, char * const argv[]) { { "help", 0, 0, 'h' }, { "background", 2, 0, 'b' }, { "cert", 1, 0, 'c' }, + { "cert-fd", 1, 0, 0 }, { "cgi", 2, 0, 0 }, { "debug", 0, 0, 'd' }, { "static-file", 1, 0, 'f' }, @@ -670,10 +673,30 @@ static void parseArgs(int argc, char * const argv[]) { if (!hasSSL) { warn("Ignoring certificate directory, as SSL support is unavailable"); } + if (certificateFd) { + fatal("Cannot set both a certificate directory and file handle"); + } if (certificateDir) { fatal("Only one certificate directory can be selected"); } check(certificateDir = strdup(optarg)); + } else if (!idx--) { + // Certificate file descriptor + if (!hasSSL) { + warn("Ignoring certificate directory, as SSL support is unavailable"); + } + if (certificateDir) { + fatal("Cannot set both a certificate directory and file handle"); + } + if (certificateFd >= 0) { + fatal("Only one certificate file handle can be provided"); + } + int tmpFd = strtoint(optarg, 3, INT_MAX); + certificateFd = dup(tmpFd); + if (certificateFd < 0) { + fatal("Invalid certificate file handle"); + } + check(!NOINTR(close(tmpFd))); } else if (!idx--) { // CGI if (demonize) { @@ -823,7 +846,7 @@ static void parseArgs(int argc, char * const argv[]) { if (fd >= 0) { char buf[40]; NOINTR(write(fd, buf, snprintf(buf, 40, "%d", (int)getpid()))); - NOINTR(close(fd)); + check(!NOINTR(close(fd))); } } } @@ -902,7 +925,8 @@ int main(int argc, char * const argv[]) { fflush(stdout); free(cgiRoot); check(!NOINTR(close(fds[1]))); - closeAllFds((int []){ launcherFd, serverGetFd(server) }, 2); + closeAllFds((int []){ launcherFd, serverGetFd(server), + certificateFd }, certificateFd >= 0 ? 3 : 2); logSetLogLevel(MSG_QUIET); } serverEnableSSL(server, enableSSL); @@ -910,7 +934,9 @@ int main(int argc, char * const argv[]) { // Enable SSL support (if available) if (enableSSL) { check(serverSupportsSSL()); - if (certificateDir) { + if (certificateFd >= 0) { + serverSetCertificateFd(server, certificateFd); + } else if (certificateDir) { char *tmp; if (strchr(certificateDir, '%')) { fatal("Invalid certificate directory name \"%s\".", certificateDir); diff --git a/shellinabox/shellinaboxd.man.in b/shellinabox/shellinaboxd.man.in index 11d6b82..84c8171 100755 --- a/shellinabox/shellinaboxd.man.in +++ b/shellinabox/shellinaboxd.man.in @@ -52,6 +52,7 @@ shellinaboxd \- publish command line shell through AJAX interface .B shellinaboxd [\ \fB-b\fP\ | \fB--background\fP[\fB=\fP\fIpidfile\fP]\ ] [\ \fB-c\fP\ | \fB--cert=\fP\fIcertdir\fP\ ] +[\ \fB-c\fP\ | \fB--cert-fd=\fP\fIfd\fP\ ] [\ \fB--cgi\fP[\fB=\fP\fIportrange\fP]\ ] [\ \fB-d\fP\ | \fB--debug\fP\ ] [\ \fB-f\fP\ | \fB--static-file=\fP\fIurl\fP:\fIfile\fP\ ] @@ -138,6 +139,16 @@ certificate. Due to this usability problem, and due to the perceived security implications, the use of auto-generated self-signed certificates is intended for testing or in intranet deployments, only. .TP +\fB--cert-fd=\fP\fIfd\fP +Instead of providing a +.B --cert +directory, it is also possible to provide a filedescriptor +.I fd +where the certificate and key can be retrieved. While this option disables +.B SNI +support, it does offer an alternative solution for securely providing +the private key data to the daemon. +.TP \fB--cgi\fP[\fB=\fP\fIportrange\fP] Instead of running .B shellinaboxd