X-Git-Url: http://www.dolda2000.com/gitweb/?p=ashd.git;a=blobdiff_plain;f=src%2Fssl-gnutls.c;h=24c87525e02ed18fc4f223f23ae7a7eae3fa60b2;hp=1ee7306597d7a2d01da4bf9c06c4d40f55dfa57a;hb=5f0c1cd631b9abcca90afe15cf129babba86f7f1;hpb=1b77e192a00433e94124eeeac3b7ed000db9dc90 diff --git a/src/ssl-gnutls.c b/src/ssl-gnutls.c index 1ee7306..24c8752 100644 --- a/src/ssl-gnutls.c +++ b/src/ssl-gnutls.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -39,11 +40,23 @@ #ifdef HAVE_GNUTLS #include +#include + +struct namedcreds { + char **names; + gnutls_certificate_credentials_t creds; +}; + +struct ncredbuf { + struct namedcreds **b; + size_t s, d; +}; struct sslport { int fd; int sport; gnutls_certificate_credentials_t creds; + struct namedcreds **ncreds; }; struct sslconn { @@ -54,7 +67,117 @@ struct sslconn { struct charbuf in; }; -static gnutls_dh_params_t dhparams; +struct savedsess { + struct savedsess *next, *prev; + gnutls_datum_t key, value; +}; + +static int numconn = 0, numsess = 0; +static struct btree *sessidx = NULL; +static struct savedsess *sesslistf = NULL, *sesslistl = NULL; + +static int sesscmp(void *ap, void *bp) +{ + struct savedsess *a = ap, *b = bp; + + if(a->key.size != b->key.size) + return(a->key.size - b->key.size); + return(memcmp(a->key.data, b->key.data, a->key.size)); +} + +static gnutls_datum_t sessdbfetch(void *uudata, gnutls_datum_t key) +{ + struct savedsess *sess, lkey; + gnutls_datum_t ret; + + memset(&ret, 0, sizeof(ret)); + lkey.key = key; + if((sess = btreeget(sessidx, &lkey, sesscmp)) == NULL) + return(ret); + ret.data = memcpy(gnutls_malloc(ret.size = sess->value.size), sess->value.data, sess->value.size); + return(ret); +} + +static void freesess(struct savedsess *sess) +{ + bbtreedel(&sessidx, sess, sesscmp); + if(sess->next) + sess->next->prev = sess->prev; + if(sess->prev) + sess->prev->next = sess->next; + if(sess == sesslistf) + sesslistf = sess->next; + if(sess == sesslistl) + sesslistl = sess->prev; + free(sess->key.data); + free(sess->value.data); + free(sess); + numsess--; +} + +static int sessdbdel(void *uudata, gnutls_datum_t key) +{ + struct savedsess *sess, lkey; + + lkey.key = key; + if((sess = btreeget(sessidx, &lkey, sesscmp)) == NULL) + return(-1); + freesess(sess); + return(0); +} + +static void cleansess(void) +{ + while(numsess > (max(numconn, 1) * 100)) + freesess(sesslistl); +} + +static int sessdbstore(void *uudata, gnutls_datum_t key, gnutls_datum_t value) +{ + static int cc = 0; + struct savedsess *sess, lkey; + + if((value.data == NULL) || (value.size == 0)) { + sessdbdel(NULL, key); + return(0); + } + lkey.key = key; + if((sess = btreeget(sessidx, &lkey, sesscmp)) == NULL) { + omalloc(sess); + sess->key.data = memcpy(smalloc(sess->key.size = key.size), key.data, key.size); + sess->value.data = memcpy(smalloc(sess->value.size = value.size), value.data, value.size); + bbtreeput(&sessidx, sess, sesscmp); + sess->prev = NULL; + sess->next = sesslistf; + if(sesslistf) + sesslistf->prev = sess; + sesslistf = sess; + if(sesslistl == NULL) + sesslistl = sess; + numsess++; + } else { + free(sess->value.data); + sess->value.data = memcpy(smalloc(sess->value.size = value.size), value.data, value.size); + if(sess != sesslistf) { + if(sess->next) + sess->next->prev = sess->prev; + if(sess->prev) + sess->prev->next = sess->next; + if(sess == sesslistl) + sesslistl = sess->prev; + sess->prev = NULL; + sess->next = sesslistf; + if(sesslistf) + sesslistf->prev = sess; + sesslistf = sess; + } + } + if(cc++ > 100) { + cleansess(); + cc = 0; + } + return(0); +} static int tlsblock(int fd, gnutls_session_t sess, time_t to) { @@ -125,6 +248,9 @@ static ssize_t sslwrite(void *cookie, const char *buf, size_t len) static int sslclose(void *cookie) { + struct sslconn *ssl = cookie; + + buffree(ssl->in); return(0); } @@ -170,7 +296,7 @@ static void servessl(struct muth *muth, va_list args) int setcreds(gnutls_session_t sess) { - int i; + int i, o, u; unsigned int ntype; char nambuf[256]; size_t namlen; @@ -181,15 +307,29 @@ static void servessl(struct muth *muth, va_list args) break; if(ntype != GNUTLS_NAME_DNS) continue; + for(o = 0; pd->ncreds[o] != NULL; o++) { + for(u = 0; pd->ncreds[o]->names[u] != NULL; u++) { + if(!strcmp(pd->ncreds[o]->names[u], nambuf)) { + gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->ncreds[o]->creds); + gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); + return(0); + } + } + } } gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->creds); gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); return(0); } + numconn++; fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); gnutls_init(&sess, GNUTLS_SERVER); gnutls_set_default_priority(sess); + gnutls_db_set_retrieve_function(sess, sessdbfetch); + gnutls_db_set_store_function(sess, sessdbstore); + gnutls_db_set_remove_function(sess, sessdbdel); + gnutls_db_set_ptr(sess, NULL); gnutls_handshake_set_post_client_hello_function(sess, setcreds); gnutls_transport_set_ptr(sess, (gnutls_transport_ptr_t)(intptr_t)fd); while((ret = gnutls_handshake(sess)) != 0) { @@ -213,6 +353,7 @@ static void servessl(struct muth *muth, va_list args) out: gnutls_deinit(sess); close(fd); + numconn--; } static void listenloop(struct muth *muth, va_list args) @@ -238,6 +379,23 @@ out: free(pd); } +static gnutls_dh_params_t dhparams(void) +{ + static int inited = 0; + static gnutls_dh_params_t pars; + int ret; + + if(!inited) { + if(((ret = gnutls_dh_params_init(&pars)) != 0) || + ((ret = gnutls_dh_params_generate2(pars, 2048)) != 0)) { + flog(LOG_ERR, "GnuTLS could not generate Diffie-Hellman parameters: %s", gnutls_strerror(ret)); + exit(1); + } + inited = 1; + } + return(pars); +} + static void init(void) { static int inited = 0; @@ -250,22 +408,112 @@ static void init(void) flog(LOG_ERR, "could not initialize GnuTLS: %s", gnutls_strerror(ret)); exit(1); } - if(((ret = gnutls_dh_params_init(&dhparams)) != 0) || - ((ret = gnutls_dh_params_generate2(dhparams, 2048)) != 0)) { - flog(LOG_ERR, "GnuTLS could not generate Diffie-Hellman parameters: %s", gnutls_strerror(ret)); +} + +static struct namedcreds *readncreds(char *file) +{ + int i, fd, ret; + struct namedcreds *nc; + gnutls_x509_crt_t crt; + gnutls_x509_privkey_t key; + char cn[1024]; + size_t cnl; + gnutls_datum_t d; + struct charbuf keybuf; + struct charvbuf names; + unsigned int type; + + bufinit(keybuf); + bufinit(names); + if((fd = open(file, O_RDONLY)) < 0) { + flog(LOG_ERR, "ssl: %s: %s", file, strerror(errno)); exit(1); } + while(1) { + sizebuf(keybuf, keybuf.d + 1024); + ret = read(fd, keybuf.b + keybuf.d, keybuf.s - keybuf.d); + if(ret < 0) { + flog(LOG_ERR, "ssl: reading from %s: %s", file, strerror(errno)); + exit(1); + } else if(ret == 0) { + break; + } + keybuf.d += ret; + } + close(fd); + d.data = (unsigned char *)keybuf.b; + d.size = keybuf.d; + gnutls_x509_crt_init(&crt); + if((ret = gnutls_x509_crt_import(crt, &d, GNUTLS_X509_FMT_PEM)) != 0) { + flog(LOG_ERR, "ssl: could not load certificate from %s: %s", file, gnutls_strerror(ret)); + exit(1); + } + cnl = sizeof(cn) - 1; + if((ret = gnutls_x509_crt_get_dn_by_oid(crt, GNUTLS_OID_X520_COMMON_NAME, 0, 0, cn, &cnl)) != 0) { + flog(LOG_ERR, "ssl: could not read common name from %s: %s", file, gnutls_strerror(ret)); + exit(1); + } + cn[cnl] = 0; + bufadd(names, sstrdup(cn)); + for(i = 0; 1; i++) { + cnl = sizeof(cn) - 1; + if(gnutls_x509_crt_get_subject_alt_name2(crt, i, cn, &cnl, &type, NULL) < 0) + break; + cn[cnl] = 0; + if(type == GNUTLS_SAN_DNSNAME) + bufadd(names, sstrdup(cn)); + } + gnutls_x509_privkey_init(&key); + if((ret = gnutls_x509_privkey_import(key, &d, GNUTLS_X509_FMT_PEM)) != 0) { + flog(LOG_ERR, "ssl: could not load key from %s: %s", file, gnutls_strerror(ret)); + exit(1); + } + buffree(keybuf); + bufadd(names, NULL); + omalloc(nc); + nc->names = names.b; + gnutls_certificate_allocate_credentials(&nc->creds); + if((ret = gnutls_certificate_set_x509_key(nc->creds, &crt, 1, key)) != 0) { + flog(LOG_ERR, "ssl: could not use certificate from %s: %s", file, gnutls_strerror(ret)); + exit(1); + } + gnutls_certificate_set_dh_params(nc->creds, dhparams()); + return(nc); +} + +static void readncdir(struct ncredbuf *buf, char *dir) +{ + DIR *d; + struct dirent *e; + size_t es; + + if((d = opendir(dir)) == NULL) { + flog(LOG_ERR, "ssl: could not read certificate directory %s: %s", dir, strerror(errno)); + exit(1); + } + while((e = readdir(d)) != NULL) { + if(e->d_name[0] == '.') + continue; + if((es = strlen(e->d_name)) <= 4) + continue; + if(strcmp(e->d_name + es - 4, ".crt")) + continue; + bufadd(*buf, readncreds(sprintf3("%s/%s", dir, e->d_name))); + } + closedir(d); } void handlegnussl(int argc, char **argp, char **argv) { int i, ret, port, fd; gnutls_certificate_credentials_t creds; + struct ncredbuf ncreds; struct sslport *pd; char *crtfile, *keyfile; init(); port = 443; + bufinit(ncreds); gnutls_certificate_allocate_credentials(&creds); keyfile = crtfile = NULL; for(i = 0; i < argc; i++) { @@ -281,10 +529,24 @@ void handlegnussl(int argc, char **argp, char **argv) printf("\tcrl=CRL-FILE [no default]\n"); printf("\t\tThe name of a file to read revocation lists from.\n"); printf("\t\tMay be given multiple times.\n"); + printf("\tncert=CERT-FILE [no default]\n"); + printf("\t\tThe name of a file to read a named certificate from,\n"); + printf("\t\tfor use with SNI-enabled clients.\n"); + printf("\t\tMay be given multiple times.\n"); + printf("\tncertdir=DIR [no default]\n"); + printf("\t\tRead all *.crt files in the given directory as if they\n"); + printf("\t\twere given with `ncert' options.\n"); + printf("\t\tMay be given multiple times.\n"); printf("\tport=PORT [443]\n"); printf("\t\tThe TCP port to listen on.\n"); printf("\n"); printf("\tAll X.509 data files must be PEM-encoded.\n"); + printf("\tIf any certificates were given with `ncert' options, they will be\n"); + printf("\tused if a client explicitly names one of them with a\n"); + printf("\tserver-name indication. If a client indicates no server name,\n"); + printf("\tor if a server-name indication does not match any given\n"); + printf("\tcertificate, the certificate given with the `cert' option will\n"); + printf("\tbe used instead.\n"); exit(0); } else if(!strcmp(argp[i], "cert")) { crtfile = argv[i]; @@ -295,13 +557,29 @@ void handlegnussl(int argc, char **argp, char **argv) flog(LOG_ERR, "ssl: could not load trust file `%s': %s", argv[i], gnutls_strerror(ret)); exit(1); } + for(i = 0; i < ncreds.d; i++) { + if((ret = gnutls_certificate_set_x509_trust_file(ncreds.b[i]->creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) { + flog(LOG_ERR, "ssl: could not load trust file `%s': %s", argv[i], gnutls_strerror(ret)); + exit(1); + } + } } else if(!strcmp(argp[i], "crl")) { if((ret = gnutls_certificate_set_x509_crl_file(creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) { flog(LOG_ERR, "ssl: could not load CRL file `%s': %s", argv[i], gnutls_strerror(ret)); exit(1); } + for(i = 0; i < ncreds.d; i++) { + if((ret = gnutls_certificate_set_x509_crl_file(ncreds.b[i]->creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) { + flog(LOG_ERR, "ssl: could not load CRL file `%s': %s", argv[i], gnutls_strerror(ret)); + exit(1); + } + } } else if(!strcmp(argp[i], "port")) { port = atoi(argv[i]); + } else if(!strcmp(argp[i], "ncert")) { + bufadd(ncreds, readncreds(argv[i])); + } else if(!strcmp(argp[i], "ncertdir")) { + readncdir(&ncreds, argv[i]); } else { flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]); exit(1); @@ -311,21 +589,23 @@ void handlegnussl(int argc, char **argp, char **argv) flog(LOG_ERR, "ssl: needs certificate file at the very least"); exit(1); } + if((fd = listensock6(port)) < 0) { + flog(LOG_ERR, "could not listen on IPv6 port (port %i): %s", port, strerror(errno)); + exit(1); + } if(keyfile == NULL) keyfile = crtfile; if((ret = gnutls_certificate_set_x509_key_file(creds, crtfile, keyfile, GNUTLS_X509_FMT_PEM)) != 0) { flog(LOG_ERR, "ssl: could not load certificate or key: %s", gnutls_strerror(ret)); exit(1); } - gnutls_certificate_set_dh_params(creds, dhparams); - if((fd = listensock6(port)) < 0) { - flog(LOG_ERR, "could not listen on IPv6 port (port %i): %s", port, strerror(errno)); - exit(1); - } + gnutls_certificate_set_dh_params(creds, dhparams()); + bufadd(ncreds, NULL); omalloc(pd); pd->fd = fd; pd->sport = port; pd->creds = creds; + pd->ncreds = ncreds.b; mustart(listenloop, pd); if((fd = listensock6(port)) < 0) { if(errno != EADDRINUSE) {