Allow SSL certificate and key to be passed in through a file handle.

git-svn-id: https://shellinabox.googlecode.com/svn/trunk@46 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
zodiac 2009-01-17 03:37:20 +00:00
parent f16b2cb543
commit e40a555cbf
9 changed files with 214 additions and 8 deletions

View file

@ -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);

View file

@ -16,6 +16,7 @@ serverLoop
serverSupportsSSL
serverEnableSSL
serverSetCertificate
serverSetCertificateFd
serverSetNumericHosts
httpTransfer
httpTransferPartialReply

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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!");

View file

@ -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);

View file

@ -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