X-Git-Url: http://www.dolda2000.com/gitweb/?a=blobdiff_plain;f=src%2Fssl-gnutls.c;h=5a11f94c6870df4d66f4b8d2f64c5c18fa0ce8a3;hb=HEAD;hp=d4862d4d8535c822143081d5feada70dbe5b30e5;hpb=aa06d1b36cfaa0610205cf49003277e6a12c874b;p=ashd.git diff --git a/src/ssl-gnutls.c b/src/ssl-gnutls.c index d4862d4..5a11f94 100644 --- a/src/ssl-gnutls.c +++ b/src/ssl-gnutls.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "htparser.h" @@ -53,9 +54,9 @@ struct ncredbuf { }; struct sslport { - int fd; - int sport; + int fd, sport, clreq; gnutls_certificate_credentials_t creds; + gnutls_priority_t ciphers; struct namedcreds **ncreds; }; @@ -72,6 +73,11 @@ struct savedsess { gnutls_datum_t key, value; }; +struct certbuffer { + gnutls_x509_crt_t *b; + size_t s, d; +}; + static int numconn = 0, numsess = 0; static struct btree *sessidx = NULL; static struct savedsess *sesslistf = NULL, *sesslistl = NULL; @@ -187,7 +193,7 @@ static int tlsblock(int fd, gnutls_session_t sess, time_t to) return(block(fd, EV_READ, to)); } -static ssize_t sslread(void *cookie, char *buf, size_t len) +static ssize_t sslread(void *cookie, void *buf, size_t len) { struct sslconn *ssl = cookie; ssize_t xf; @@ -216,7 +222,7 @@ static ssize_t sslread(void *cookie, char *buf, size_t len) return(xf); } -static ssize_t sslwrite(void *cookie, const char *buf, size_t len) +static ssize_t sslwrite(void *cookie, const void *buf, size_t len) { struct sslconn *ssl = cookie; int ret; @@ -254,7 +260,7 @@ static int sslclose(void *cookie) return(0); } -static cookie_io_functions_t iofuns = { +static struct bufioops iofuns = { .read = sslread, .write = sslwrite, .close = sslclose, @@ -265,21 +271,56 @@ static int initreq(struct conn *conn, struct hthead *req) struct sslconn *ssl = conn->pdata; struct sockaddr_storage sa; socklen_t salen; - char nmbuf[256]; + gnutls_datum_t sessid; + char *esessid; headappheader(req, "X-Ash-Address", formathaddress((struct sockaddr *)&ssl->name, sizeof(sa))); - if(ssl->name.ss_family == AF_INET) { - headappheader(req, "X-Ash-Address", inet_ntop(AF_INET, &((struct sockaddr_in *)&ssl->name)->sin_addr, nmbuf, sizeof(nmbuf))); + if(ssl->name.ss_family == AF_INET) headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&ssl->name)->sin_port))); - } else if(ssl->name.ss_family == AF_INET6) { - headappheader(req, "X-Ash-Address", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&ssl->name)->sin6_addr, nmbuf, sizeof(nmbuf))); + else if(ssl->name.ss_family == AF_INET6) headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&ssl->name)->sin6_port))); - } salen = sizeof(sa); if(!getsockname(ssl->fd, (struct sockaddr *)&sa, &salen)) headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, sizeof(sa))); headappheader(req, "X-Ash-Server-Port", sprintf3("%i", ssl->port->sport)); headappheader(req, "X-Ash-Protocol", "https"); + if(gnutls_session_get_id2(ssl->sess, &sessid) == GNUTLS_E_SUCCESS) { + esessid = base64encode((void *)sessid.data, sessid.size); + headappheader(req, "X-Ash-TLS-Session", esessid); + free(esessid); + } + return(0); +} + +static int setcreds(gnutls_session_t sess) +{ + int i, o, u; + struct sslport *pd; + unsigned int ntype; + char nambuf[256]; + size_t namlen; + + pd = gnutls_session_get_ptr(sess); + for(i = 0; 1; i++) { + namlen = sizeof(nambuf); + if(gnutls_server_name_get(sess, nambuf, &namlen, &ntype, i) != 0) + 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); + if(pd->clreq) + gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); + return(0); + } + } + } + } + gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->creds); + if(pd->clreq) + gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); return(0); } @@ -292,44 +333,16 @@ static void servessl(struct muth *muth, va_list args) struct sslconn ssl; gnutls_session_t sess; int ret; - FILE *in; - - int setcreds(gnutls_session_t sess) - { - int i, o, u; - unsigned int ntype; - char nambuf[256]; - size_t namlen; - - for(i = 0; 1; i++) { - namlen = sizeof(nambuf); - if(gnutls_server_name_get(sess, nambuf, &namlen, &ntype, i) != 0) - 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_priority_set(sess, pd->ciphers); 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_session_set_ptr(sess, pd); 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) { @@ -347,8 +360,7 @@ static void servessl(struct muth *muth, va_list args) ssl.name = name; ssl.sess = sess; bufinit(ssl.in); - in = fopencookie(&ssl, "r+", iofuns); - serve(in, &conn); + serve(bioopen(&ssl, &iofuns), fd, &conn); out: gnutls_deinit(sess); @@ -359,24 +371,39 @@ out: static void listenloop(struct muth *muth, va_list args) { vavar(struct sslport *, pd); - int ns; + int i, ns, n; struct sockaddr_storage name; socklen_t namelen; + fcntl(pd->fd, F_SETFL, fcntl(pd->fd, F_GETFL) | O_NONBLOCK); while(1) { namelen = sizeof(name); - block(pd->fd, EV_READ, 0); - ns = accept(pd->fd, (struct sockaddr *)&name, &namelen); - if(ns < 0) { - flog(LOG_ERR, "accept: %s", strerror(errno)); + if(block(pd->fd, EV_READ, 0) == 0) goto out; + n = 0; + while(1) { + ns = accept(pd->fd, (struct sockaddr *)&name, &namelen); + if(ns < 0) { + if(errno == EAGAIN) + break; + if(errno == ECONNABORTED) + continue; + flog(LOG_ERR, "accept: %s", strerror(errno)); + goto out; + } + mustart(servessl, ns, name, pd); + if(++n >= 100) + break; } - mustart(servessl, ns, name, pd); } out: close(pd->fd); free(pd); + for(i = 0; i < listeners.d; i++) { + if(listeners.b[i] == muth) + bufdel(listeners, i); + } } static gnutls_dh_params_t dhparams(void) @@ -410,20 +437,59 @@ static void init(void) } } -static struct namedcreds *readncreds(char *file) +/* This implementation seems somewhat ugly, but it's the way the + * GnuTLS implements the same thing internally, so it should probably + * be interoperable, at least. */ +static int readcrtchain(struct certbuffer *ret, struct charbuf *pem) +{ + static char *headers[] = {"-----BEGIN CERTIFICATE", "-----BEGIN X509 CERTIFICATE"}; + int i, rv; + char *p, *p2, *f; + gnutls_x509_crt_t crt; + + for(i = 0, p = NULL; i < sizeof(headers) / sizeof(*headers); i++) { + f = memmem(pem->b, pem->d, headers[i], strlen(headers[i])); + if((f != NULL) && ((p == NULL) || (f < p))) + p = f; + } + if(p == NULL) + return(-GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + do { + if((rv = gnutls_x509_crt_init(&crt)) < 0) + goto error; + if((rv = gnutls_x509_crt_import(crt, &(gnutls_datum_t){.data = (unsigned char *)p, .size = pem->d - (p - pem->b)}, GNUTLS_X509_FMT_PEM)) < 0) { + gnutls_x509_crt_deinit(crt); + goto error; + } + bufadd(*ret, crt); + for(i = 0, p2 = NULL; i < sizeof(headers) / sizeof(*headers); i++) { + f = memmem(p + 1, pem->d - (p + 1 - pem->b), headers[i], strlen(headers[i])); + if((f != NULL) && ((p2 == NULL) || (f < p2))) + p2 = f; + } + } while((p = p2) != NULL); + return(0); +error: + for(i = 0; i < ret->d; i++) + gnutls_x509_crt_deinit(ret->b[i]); + ret->d = 0; + return(rv); +} + +static struct namedcreds *readncreds(char *file, gnutls_x509_privkey_t defkey) { int i, fd, ret; struct namedcreds *nc; - gnutls_x509_crt_t crt; + struct certbuffer crts; 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(crts); bufinit(names); if((fd = open(file, O_RDONLY)) < 0) { flog(LOG_ERR, "ssl: %s: %s", file, strerror(errno)); @@ -441,15 +507,12 @@ static struct namedcreds *readncreds(char *file) 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)); + if((ret = readcrtchain(&crts, &keybuf)) != 0) { + flog(LOG_ERR, "ssl: could not load certificate chain 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) { + if((ret = gnutls_x509_crt_get_dn_by_oid(crts.b[0], 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); } @@ -457,23 +520,28 @@ static struct namedcreds *readncreds(char *file) 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) + if(gnutls_x509_crt_get_subject_alt_name2(crts.b[0], 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); + if((ret = gnutls_x509_privkey_import(key, &(gnutls_datum_t){.data = (unsigned char *)keybuf.b, .size = keybuf.d}, GNUTLS_X509_FMT_PEM)) != 0) { + if(ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + gnutls_x509_privkey_deinit(key); + key = defkey; + } else { + 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) { + if((ret = gnutls_certificate_set_x509_key(nc->creds, crts.b, crts.d, key)) != 0) { flog(LOG_ERR, "ssl: could not use certificate from %s: %s", file, gnutls_strerror(ret)); exit(1); } @@ -481,7 +549,7 @@ static struct namedcreds *readncreds(char *file) return(nc); } -static void readncdir(struct ncredbuf *buf, char *dir) +static void readncdir(struct ncredbuf *buf, char *dir, gnutls_x509_privkey_t defkey) { DIR *d; struct dirent *e; @@ -498,24 +566,31 @@ static void readncdir(struct ncredbuf *buf, char *dir) continue; if(strcmp(e->d_name + es - 4, ".crt")) continue; - bufadd(*buf, readncreds(sprintf3("%s/%s", dir, e->d_name))); + bufadd(*buf, readncreds(sprintf3("%s/%s", dir, e->d_name), defkey)); } closedir(d); } void handlegnussl(int argc, char **argp, char **argv) { - int i, ret, port, fd; + int i, ret, port, fd, clreq; gnutls_certificate_credentials_t creds; + gnutls_priority_t ciphers; + gnutls_x509_privkey_t defkey; struct ncredbuf ncreds; + struct charvbuf ncertf, ncertd; struct sslport *pd; - char *crtfile, *keyfile; + char *crtfile, *keyfile, *perr; init(); port = 443; + clreq = 0; bufinit(ncreds); + bufinit(ncertf); + bufinit(ncertd); gnutls_certificate_allocate_credentials(&creds); keyfile = crtfile = NULL; + ciphers = NULL; for(i = 0; i < argc; i++) { if(!strcmp(argp[i], "help")) { printf("ssl handler parameters:\n"); @@ -523,6 +598,8 @@ void handlegnussl(int argc, char **argp, char **argv) printf("\t\tThe name of the file to read the certificate from.\n"); printf("\tkey=KEY-FILE [same as CERT-FILE]\n"); printf("\t\tThe name of the file to read the private key from.\n"); + printf("\tprio=PRIORITIES [NORMAL]\n"); + printf("\t\tCiphersuite priorities, as a GnuTLS priority string.\n"); printf("\ttrust=CA-FILE [no default]\n"); printf("\t\tThe name of a file to read trusted certificates from.\n"); printf("\t\tMay be given multiple times.\n"); @@ -552,6 +629,17 @@ void handlegnussl(int argc, char **argp, char **argv) crtfile = argv[i]; } else if(!strcmp(argp[i], "key")) { keyfile = argv[i]; + } else if(!strcmp(argp[i], "prio")) { + if(ciphers != NULL) + gnutls_priority_deinit(ciphers); + ret = gnutls_priority_init(&ciphers, argv[i], (const char **)&perr); + if(ret == GNUTLS_E_INVALID_REQUEST) { + flog(LOG_ERR, "ssl: invalid cipher priority string, at `%s'", perr); + exit(1); + } else if(ret != 0) { + flog(LOG_ERR, "ssl: could not initialize cipher priorities: %s", gnutls_strerror(ret)); + exit(1); + } } else if(!strcmp(argp[i], "trust")) { if((ret = gnutls_certificate_set_x509_trust_file(creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) { flog(LOG_ERR, "ssl: could not load trust file `%s': %s", argv[i], gnutls_strerror(ret)); @@ -563,6 +651,7 @@ void handlegnussl(int argc, char **argp, char **argv) exit(1); } } + clreq = 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)); @@ -574,12 +663,13 @@ void handlegnussl(int argc, char **argp, char **argv) exit(1); } } + clreq = 1; } else if(!strcmp(argp[i], "port")) { port = atoi(argv[i]); } else if(!strcmp(argp[i], "ncert")) { - bufadd(ncreds, readncreds(argv[i])); + bufadd(ncertf, argv[i]); } else if(!strcmp(argp[i], "ncertdir")) { - readncdir(&ncreds, argv[i]); + bufadd(ncertd, argv[i]); } else { flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]); exit(1); @@ -599,14 +689,30 @@ void handlegnussl(int argc, char **argp, char **argv) flog(LOG_ERR, "ssl: could not load certificate or key: %s", gnutls_strerror(ret)); exit(1); } + if((ciphers == NULL) && ((ret = gnutls_priority_init(&ciphers, "NORMAL", NULL)) != 0)) { + flog(LOG_ERR, "ssl: could not initialize cipher priorities: %s", gnutls_strerror(ret)); + exit(1); + } + if((ret = gnutls_certificate_get_x509_key(creds, 0, &defkey)) != 0) { + flog(LOG_ERR, "ssl: could not get default key: %s", gnutls_strerror(ret)); + exit(1); + } + for(i = 0; i < ncertf.d; i++) + bufadd(ncreds, readncreds(ncertf.b[i], defkey)); + for(i = 0; i < ncertd.d; i++) + readncdir(&ncreds, ncertd.b[i], defkey); + buffree(ncertf); + buffree(ncertd); gnutls_certificate_set_dh_params(creds, dhparams()); bufadd(ncreds, NULL); omalloc(pd); pd->fd = fd; pd->sport = port; + pd->clreq = clreq; pd->creds = creds; pd->ncreds = ncreds.b; - mustart(listenloop, pd); + pd->ciphers = ciphers; + bufadd(listeners, mustart(listenloop, pd)); if((fd = listensock4(port)) < 0) { if(errno != EADDRINUSE) { flog(LOG_ERR, "could not listen on IPv4 port (port %i): %s", port, strerror(errno)); @@ -617,7 +723,9 @@ void handlegnussl(int argc, char **argp, char **argv) pd->fd = fd; pd->sport = port; pd->creds = creds; - mustart(listenloop, pd); + pd->ncreds = ncreds.b; + pd->ciphers = ciphers; + bufadd(listeners, mustart(listenloop, pd)); } }