From 1b4116cce21ab58e7a1b9f6ff46de0adce6b9ff0 Mon Sep 17 00:00:00 2001 From: standa Date: Thu, 25 Jun 2015 17:14:56 +0200 Subject: [PATCH] SNI check with NameVirtualHosts --- docs/mod_nss.html | 14 ++++- mod_nss.c | 3 ++ mod_nss.h | 21 ++++++++ nss_engine_config.c | 11 ++++ nss_engine_init.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++------ nss_engine_kernel.c | 51 ++++++++++++++++++ nss_util.c | 72 ++++++++++++++++++++++++- 7 files changed, 303 insertions(+), 18 deletions(-) Index: mod_nss-1.0.8/docs/mod_nss.html =================================================================== --- mod_nss-1.0.8.orig/docs/mod_nss.html +++ mod_nss-1.0.8/docs/mod_nss.html @@ -195,7 +195,9 @@ following line to httpd.conf (location r
This has Apache load the mod_nss configuration file, nss.conf. It is here that you will setup your VirtualServer entries to and -configure your SSL servers.
+configure your SSL servers. If you have a certificate with the Subject +Alternative Names then you will set up these names like ServerAlias for your virtual host.
+

Certificate Generation

A ksh script, gencert, is included to automatically generate a self-signed CA plus one server certificate. This is fine for @@ -1079,6 +1081,16 @@ components of the client certificate, th
NSSRequire

+NSSSNI
+
+Enables or disables Server Name Identification(SNI) extension check for +SSL. This option is turn on by default. SNI vhost_id gets from HTTPS header. +
+
+Example
+
+NSSSNI off
+
NSSProxyEngine

Enables or disables mod_nss HTTPS support for mod_proxy.
Index: mod_nss-1.0.8/mod_nss.c =================================================================== --- mod_nss-1.0.8.orig/mod_nss.c +++ mod_nss-1.0.8/mod_nss.c @@ -85,6 +85,9 @@ static const command_rec nss_config_cmds SSL_CMD_SRV(FIPS, FLAG, "FIPS 140-1 mode " "(`on', `off')") + SSL_CMD_SRV(SNI, FLAG, + "SNI" + "(`on', `off')") SSL_CMD_ALL(CipherSuite, TAKE1, "Comma-delimited list of permitted SSL Ciphers, + to enable, - to disable " "(`[+-]XXX,...,[+-]XXX' - see manual)") Index: mod_nss-1.0.8/mod_nss.h =================================================================== --- mod_nss-1.0.8.orig/mod_nss.h +++ mod_nss-1.0.8/mod_nss.h @@ -308,6 +308,7 @@ struct SSLSrvConfigRec { const char *ocsp_name; BOOL ocsp; BOOL enabled; + BOOL sni; BOOL proxy_enabled; const char *vhost_id; int vhost_id_len; @@ -343,6 +344,20 @@ typedef struct PRInt32 version; /* protocol version valid for this cipher */ } cipher_properties; +typedef struct { + enum { + PW_NONE = 0, + PW_FROMFILE = 1, + PW_PLAINTEXT = 2, + PW_EXTERNAL = 3 + } source; + char *data; +} secuPWData; + +/* pool and hash which will contain ServerName and NSSNickname */ +apr_pool_t *mp; +apr_hash_t *ht; + /* Compatibility between Apache 2.0.x and 2.2.x. The numeric version of * the version first appeared in Apache 2.0.56-dev. I picked 2.0.55 as it * is the last version without this define. This is used for more than just @@ -384,6 +399,7 @@ void *nss_config_perdir_merge(apr_pool_t void *nss_config_server_create(apr_pool_t *p, server_rec *s); void *nss_config_server_merge(apr_pool_t *p, void *basev, void *addv); const char *nss_cmd_NSSFIPS(cmd_parms *, void *, int); +const char *nss_cmd_NSSSNI(cmd_parms *, void *, int); const char *nss_cmd_NSSEngine(cmd_parms *, void *, int); const char *nss_cmd_NSSOCSP(cmd_parms *, void *, int); const char *nss_cmd_NSSOCSPDefaultResponder(cmd_parms *, void *, int); @@ -471,6 +487,9 @@ apr_file_t *nss_util_ppopen(server_rec void nss_util_ppclose(server_rec *, apr_pool_t *, apr_file_t *); char *nss_util_readfilter(server_rec *, apr_pool_t *, const char *, const char * const *); +char *searchHashVhostNick(char *vhost_id); +char *searchHashVhostNick_match(char *vhost_id); +void addHashVhostNick(char *vhost_id, char *nickname); /* ssl_io_buffer_fill fills the setaside buffering of the HTTP request * to allow an SSL renegotiation to take place. */ int nss_io_buffer_fill(request_rec *r); Index: mod_nss-1.0.8/nss_engine_config.c =================================================================== --- mod_nss-1.0.8.orig/nss_engine_config.c +++ mod_nss-1.0.8/nss_engine_config.c @@ -135,6 +135,7 @@ static SSLSrvConfigRec *nss_config_serve sc->ocsp_name = NULL; sc->fips = UNSET; sc->enabled = UNSET; + sc->sni = TRUE; sc->proxy_enabled = UNSET; sc->vhost_id = NULL; /* set during module init */ sc->vhost_id_len = 0; /* set during module init */ @@ -214,6 +215,7 @@ void *nss_config_server_merge(apr_pool_t cfgMerge(ocsp_name, NULL); cfgMergeBool(fips); cfgMergeBool(enabled); + cfgMergeBool(sni); cfgMergeBool(proxy_enabled); cfgMergeBool(proxy_ssl_check_peer_cn); @@ -321,6 +323,15 @@ const char *nss_cmd_NSSFIPS(cmd_parms *c return NULL; } +const char *nss_cmd_NSSSNI(cmd_parms *cmd, void *dcfg, int flag) +{ + SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + + sc->sni = flag ? TRUE : FALSE; + + return NULL; +} + const char *nss_cmd_NSSOCSP(cmd_parms *cmd, void *dcfg, int flag) { SSLSrvConfigRec *sc = mySrvConfig(cmd->server); Index: mod_nss-1.0.8/nss_engine_init.c =================================================================== --- mod_nss-1.0.8.orig/nss_engine_init.c +++ mod_nss-1.0.8/nss_engine_init.c @@ -28,6 +28,8 @@ static SECStatus ownHandshakeCallback(PR static SECStatus NSSHandshakeCallback(PRFileDesc *socket, void *arg); static CERTCertificate* FindServerCertFromNickname(const char* name, const CERTCertList* clist); SECStatus nss_AuthCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer); +PRInt32 ownSSLSNISocketConfig(PRFileDesc *fd, const SECItem *sniNameArr, + PRUint32 sniNameArrSize, void *arg); /* * Global variables defined in this file. @@ -222,11 +224,10 @@ static void nss_init_SSLLibrary(server_r NSS_Shutdown(); ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, "NSS_Initialize failed. Certificate database: %s.", mc->pCertificateDatabase != NULL ? mc->pCertificateDatabase : "not set in configuration"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Please check access rights for user:%s!!!", mc->user); nss_log_nss_error(APLOG_MARK, APLOG_ERR, base_server); - if (mc->nInitCount == 1) - nss_die(); - else - return; + nss_die(); } if (fipsenabled) { @@ -325,6 +326,8 @@ int nss_init_Module(apr_pool_t *p, apr_p int fipsenabled = FALSE; int threaded = 0; struct semid_ds status; + char *split_vhost_id = NULL; + char *last1; mc->nInitCount++; @@ -381,6 +384,12 @@ int nss_init_Module(apr_pool_t *p, apr_p */ sc->vhost_id = nss_util_vhostid(p, s); sc->vhost_id_len = strlen(sc->vhost_id); + + if (sc->server->nickname != NULL && sc->vhost_id != NULL) { + split_vhost_id = apr_strtok(sc->vhost_id, ":", &last1); + ap_str_tolower(split_vhost_id); + addHashVhostNick(split_vhost_id, (char *)sc->server->nickname); + } /* Fix up stuff that may not have been set */ if (sc->fips == UNSET) { @@ -534,7 +543,7 @@ int nss_init_Module(apr_pool_t *p, apr_p ap_log_error(APLOG_MARK, APLOG_INFO, 0, base_server, "Init: Initializing (virtual) servers for SSL"); - CERTCertList* clist = PK11_ListCerts(PK11CertListUser, NULL); + CERTCertList* clist = PK11_ListCerts(PK11CertListUserUnique, NULL); for (s = base_server; s; s = s->next) { sc = mySrvConfig(s); @@ -547,7 +556,7 @@ int nss_init_Module(apr_pool_t *p, apr_p /* * Read the server certificate and key */ - nss_init_ConfigureServer(s, p, ptemp, sc, clist); + nss_init_ConfigureServer(s, p, ptemp, sc, clist); } if (clist) { @@ -1132,6 +1141,12 @@ static void nss_init_certificate(server_ SECStatus secstatus; PK11SlotInfo* slot = NULL; + CERTCertNicknames *certNickDNS = NULL; + char **nnptr = NULL; + int nn = 0; + apr_array_header_t *names = NULL; + apr_array_header_t *wild_names = NULL; + int i, j; if (nickname == NULL) { return; @@ -1198,17 +1213,52 @@ static void nss_init_certificate(server_ *KEAtype = NSS_FindCertKEAType(*servercert); + /* get ServerAlias entries to hash */ + names = s->names; + if (names) { + char **name = (char **)names->elts; + for (i = 0; i < names->nelts; ++i) { + ap_str_tolower(name[i]); + addHashVhostNick(name[i], (char *)nickname); + } + } + + /* get ServerAlias entries with wildcards */ + wild_names = s->wild_names; + if (wild_names) { + char **wild_name = (char **)wild_names->elts; + for (j = 0; j < wild_names->nelts; ++j) { + ap_str_tolower(wild_name[j]); + addHashVhostNick(wild_name[j], (char *)nickname); + } + } + + /* get valid DNS names from certificate to hash */ + certNickDNS = CERT_GetValidDNSPatternsFromCert(*servercert); + + if (certNickDNS) { + nnptr = certNickDNS->nicknames; + nn = certNickDNS->numnicknames; + + while ( nn > 0 ) { + ap_str_tolower(*nnptr); + addHashVhostNick(*nnptr, (char *)nickname); + nnptr++; + nn--; + } + + } + /* Subject/hostname check */ secstatus = CERT_VerifyCertName(*servercert, s->server_hostname); if (secstatus != SECSuccess) { char *cert_dns = CERT_GetCommonName(&(*servercert)->subject); ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, - "Misconfiguration of certificate's CN and virtual name." - " The certificate CN has %s. We expected %s as virtual" - " name.", cert_dns, s->server_hostname); + "Misconfiguration of certificate's CN and virtual name." + " The certificate CN has %s. We expected %s as virtual" + " name.", cert_dns, s->server_hostname); PORT_Free(cert_dns); } - /* * Check for certs that are expired or not yet valid and WARN about it. * No need to refuse working - the client gets a warning. @@ -1233,13 +1283,21 @@ static void nss_init_certificate(server_ break; } - secstatus = SSL_ConfigSecureServer(model, *servercert, *serverkey, *KEAtype); + secstatus = SSL_ConfigSecureServer(model, *servercert, *serverkey, *KEAtype); if (secstatus != SECSuccess) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "SSL error configuring server: '%s'", nickname); nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); nss_die(); - } + } + + /* SNI */ + if (SSL_SNISocketConfigHook(model, (SSLSNISocketConfig) ownSSLSNISocketConfig, (void*) s) != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "SSL_SNISocketConfigHook failed"); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); + nss_die(); + } } @@ -1308,6 +1366,7 @@ static void nss_init_server_certs(server nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); nss_die(); } + } static void nss_init_proxy_ctx(server_rec *s, @@ -1374,7 +1433,6 @@ void nss_init_Child(apr_pool_t *p, serve /* If any servers have SSL, we want sslenabled set so we * can perform further initialization */ - if (sc->enabled == UNSET) { sc->enabled = FALSE; } @@ -1404,11 +1462,12 @@ void nss_init_Child(apr_pool_t *p, serve nss_init_SSLLibrary(base_server); /* Configure all virtual servers */ - CERTCertList* clist = PK11_ListCerts(PK11CertListUser, NULL); + CERTCertList* clist = PK11_ListCerts(PK11CertListUserUnique, NULL); for (s = base_server; s; s = s->next) { sc = mySrvConfig(s); - if (sc->server->servercert == NULL && NSS_IsInitialized()) - nss_init_ConfigureServer(s, p, mc->ptemp, sc, clist); + if (sc->server->servercert == NULL && NSS_IsInitialized()) { + nss_init_ConfigureServer(s, p, mc->ptemp, sc, clist); + } } if (clist) { CERT_DestroyCertList(clist); @@ -1741,3 +1800,67 @@ int nss_parse_ciphers(server_rec *s, cha return 0; } + +PRInt32 ownSSLSNISocketConfig(PRFileDesc *fd, const SECItem *sniNameArr, + PRUint32 sniNameArrSize, void *arg) +{ + server_rec *s = (server_rec *)arg; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "start function ownSSLSNISocketConfig for SNI"); + + secuPWData *pwdata; + CERTCertificate * cert = NULL; + SECKEYPrivateKey * privKey = NULL; + char *nickName = NULL; + char *vhost = NULL; + apr_pool_t *str_p; + + PORT_Assert(fd && sniNameArr); + if (!fd || !sniNameArr) { + nss_die(); + } + apr_pool_create(&str_p, NULL); + vhost = apr_pstrndup(str_p, (char *) sniNameArr->data, sniNameArr->len); + + /* rfc6125 - Checking of Traditional Domain Names*/ + ap_str_tolower(vhost); + + nickName = searchHashVhostNick(vhost); + if (nickName == NULL) { + /* search wild_names in serverAlises */ + nickName = searchHashVhostNick_match(vhost); + if (nickName == NULL) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,"Search [val = %s] failed, unrecognized name.", vhost); + nss_die(); + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,"Search passed [value = %s] for key:%s", nickName, vhost); + + pwdata = SSL_RevealPinArg(fd); + + /* if pwdata is NULL, then we would not get the key and + * return an error status. */ + cert = PK11_FindCertFromNickname(nickName, &pwdata); + if (cert == NULL) { + nss_die(); + } + privKey = PK11_FindKeyByAnyCert(cert, &pwdata); + if (privKey == NULL) { + nss_die(); + } + SSLKEAType certKEA = NSS_FindCertKEAType(cert); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "start configure vhost:%s", vhost); + if (SSL_ConfigSecureServer(fd, cert, privKey, certKEA) != SECSuccess) { + nss_die(); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "successfull setting vhost with nick:%s", nickName); + SECKEY_DestroyPrivateKey(privKey); + CERT_DestroyCertificate(cert); + apr_pool_destroy(str_p); + return 0; + +} Index: mod_nss-1.0.8/nss_engine_kernel.c =================================================================== --- mod_nss-1.0.8.orig/nss_engine_kernel.c +++ mod_nss-1.0.8/nss_engine_kernel.c @@ -71,6 +71,59 @@ int nss_hook_ReadReq(request_rec *r) } /* + * SNI check is default on. In same cases you switch of by NSSSNI off + * sc->sni parameter gets vhost from HTTPS header + */ + SSLSrvConfigRec *sc = mySrvConfig(r->server); + + SECItem *hostInfo = NULL; + hostInfo = SSL_GetNegotiatedHostInfo(ssl); + if (hostInfo != NULL && sc->sni) { + if (ap_is_initial_req(r) && (hostInfo->len != 0)) { + char *servername = NULL; + char *host, *scope_id; + apr_port_t port; + apr_status_t rv; + apr_pool_t *s_p; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "SNI hostInfo hostInfo->data:%s and hostInfo->len:%d" + ,(char *) hostInfo->data, hostInfo->len); + + apr_pool_create(&s_p, NULL); + servername = apr_pstrndup(s_p, (char *) hostInfo->data, hostInfo->len); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "SNI hostInfo servername:%s, lenght:%d" + , servername, (unsigned)strlen(servername)); + + if (!r->hostname) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "Hostname %s provided via SNI, but no hostname" + " provided in HTTP request", servername); + return HTTP_BAD_REQUEST; + } + + rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); + if (rv != APR_SUCCESS || scope_id) { + return HTTP_BAD_REQUEST; + } + + if (strcasecmp(host, servername)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "Hostname %s provided via SNI and hostname %s provided" + " via HTTP are different", servername, host); + + SECITEM_FreeItem(hostInfo, PR_TRUE); + apr_pool_destroy(s_p); + return HTTP_BAD_REQUEST; + } else { + SECITEM_FreeItem(hostInfo, PR_TRUE); + apr_pool_destroy(s_p); + } + } + } + /* * Log information about incoming HTTPS requests */ if (r->server->loglevel >= APLOG_INFO && ap_is_initial_req(r)) { Index: mod_nss-1.0.8/nss_util.c =================================================================== --- mod_nss-1.0.8.orig/nss_util.c +++ mod_nss-1.0.8/nss_util.c @@ -13,7 +13,6 @@ * limitations under the License. */ - #include "mod_nss.h" #include "ap_mpm.h" #include "apr_thread_mutex.h" @@ -100,3 +99,47 @@ char *nss_util_readfilter(server_rec *s, return buf; } + +static void initializeHashVhostNick() { + apr_pool_create(&mp, NULL); + ht = apr_hash_make(mp); +} + +char *searchHashVhostNick(char *vhost_id) { + char *searchVal = NULL; + + searchVal = apr_hash_get(ht, vhost_id, APR_HASH_KEY_STRING); + + return searchVal; +} + +char *searchHashVhostNick_match(char *vhost_id) +{ + char *searchValReg = NULL; + apr_hash_index_t *hi; + for (hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) { + const char *k = NULL; + const char *v = NULL; + + apr_hash_this(hi, (const void**)&k, NULL, (void**)&v); + if (!ap_strcasecmp_match(vhost_id, k)) { + searchValReg = apr_hash_get(ht, k, APR_HASH_KEY_STRING); + return searchValReg; + } + } + return NULL; +} + +void addHashVhostNick(char *vhost_id, char *nickname) { + + if (ht == NULL) { + initializeHashVhostNick(); + } + + if(searchHashVhostNick(vhost_id) == NULL) { + apr_hash_set(ht, apr_pstrdup(mp, vhost_id), APR_HASH_KEY_STRING, + apr_pstrdup(mp, nickname)); + } + return; +} +