Franziskus Kiefer wrote:
Thanks for your reply. But that doesn't seem to change anything :/
I'm not sure. I worked on this in the spring and made a little bit of progress when using curl as a client but failed with Firefox. I'll attach the patch I was working on. Be aware that it is pretty much a mess with lots of hardcoded values and such.
rob
Cheers On Tue, Jul 19, 2016 at 3:32 PM, Rob Crittenden <[email protected] <mailto:[email protected]>> wrote: Franziskus Kiefer wrote: Hi Rob, all, I was wondering if you or anyone else has more infos on [1], i.e. the comment that this requires changes to mod_http2 in order to get H2 running. I have a patch that adds ALPN support to mod_nss but I don't get Apache to actually do H2 (it negotiates H2 but then doesn't send necessary messages such as settings). In Apache modules/http2/mod_http2.c in h2_hooks() you need to include mod_nss in the SSL modules ordering list: static const char *const mod_ssl[] = { "mod_ssl.c", "mod_nss.c", NULL}; rob
>From e0eafd6381a2d00417d5cf1be3b04235cddadf8f Mon Sep 17 00:00:00 2001 From: Rob Crittenden <[email protected]> Date: Mon, 28 Mar 2016 22:11:54 -0400 Subject: [PATCH] ALPN WIP - basically works, no client proxy Tests pass with hardcoded alpn values, and no alpn module loaded proxy test fails --- mod_nss.c | 70 ++++++++++++++++++++++- mod_nss.h | 11 ++++ nss_engine_init.c | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++- nss_engine_io.c | 27 ++++++++- 4 files changed, 268 insertions(+), 6 deletions(-) diff --git a/mod_nss.c b/mod_nss.c index 38098c8..ab26413 100644 --- a/mod_nss.c +++ b/mod_nss.c @@ -350,11 +350,20 @@ static int nss_hook_pre_connection(conn_rec *c, void *csd) SSLConnRec *sslconn = myConnConfig(c); modnss_ctx_t *mctx; +#if 0 + /* FIXME: Do I want to add server to construct? */ + if (sslconn) { + sc = mySrvConfig(sslconn->server); + } else { + sc = mySrvConfig(c->base_server); + } +#endif + /* * Immediately stop processing if SSL is disabled for this connection */ - if (!(sc && (sc->enabled || - (sslconn && sslconn->is_proxy)))) + if (c->master || !(sc && (sc->enabled || + (sslconn && sslconn->is_proxy)))) { return DECLINED; } @@ -433,9 +442,64 @@ static int nss_hook_pre_connection(conn_rec *c, void *csd) } } +#ifdef SSL_ENABLE_ALPN +#define MAX_ALPN_LENGTH 255 + core_server_config *conf; + unsigned char protos[MAX_ALPN_LENGTH]; + size_t offset = 0; + + /* Cheat and get the allowed protocols and see that into NSS */ + conf = ap_get_core_module_config(c->base_server->module_config); + if (conf->protocols->nelts <= 0) { + strcpy(protos, "\bhttp/1.1\0"); /* HTTP/1.1 */ + offset = 9; + } else { + unsigned int len = 0; + int i; + + for (i = 0; i < conf->protocols->nelts; ++i) { + const char *p = APR_ARRAY_IDX(conf->protocols, i, const char *); + len = strlen(p); + protos[offset++] = len; + memcpy(protos + offset, p, len); + offset += len; + } + protos[offset] = '\0'; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, + "protos %s offset %d", protos, offset); + } + SSL_HandshakeCallback(ssl, (SSLHandshakeCallback)NSSHandshakeCallback, c); + if (SSL_SetNextProtoNego(ssl, "\bhttp/1.1\002h2", 12) != SECSuccess) { +// if (SSL_SetNextProtoNego(ssl, "\002h2", 3) != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, + "Unable to set ALPN string."); + return DECLINED; + } +#endif + return APR_SUCCESS; } +static int nss_hook_process_connection(conn_rec* c) +{ + SSLConnRec *sslconn = myConnConfig(c); + + if (sslconn && !sslconn->disabled) { + /* On an active SSL connection, let the input filters initialize + * themselves which triggers the handshake, which again triggers + * all kinds of useful things such as SNI and ALPN. + */ + apr_bucket_brigade* temp; + + temp = apr_brigade_create(c->pool, c->bucket_alloc); + ap_get_brigade(c->input_filters, temp, + AP_MODE_INIT, APR_BLOCK_READ, 0); + apr_brigade_destroy(temp); + } + + return DECLINED; +} + static const char *nss_hook_http_scheme(const request_rec *r) { SSLSrvConfigRec *sc = mySrvConfig(r->server); @@ -471,6 +535,8 @@ static void nss_register_hooks(apr_pool_t *p) nss_io_filter_register(p); ap_hook_pre_connection(nss_hook_pre_connection,NULL,NULL, APR_HOOK_MIDDLE); + ap_hook_process_connection(nss_hook_process_connection, + NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config (nss_init_Module, NULL,NULL, APR_HOOK_MIDDLE); #if AP_SERVER_MINORVERSION_NUMBER < 2 /* See comment in mod_nss.h */ ap_hook_http_method (nss_hook_http_scheme, NULL,NULL, APR_HOOK_MIDDLE); diff --git a/mod_nss.h b/mod_nss.h index 226f7a8..ddf324f 100644 --- a/mod_nss.h +++ b/mod_nss.h @@ -520,4 +520,15 @@ const char * SECItem_to_ascii(apr_pool_t *p, SECItem *item); const char * SECItem_to_hex(apr_pool_t *p,const SECItem * item); const char * SECItem_to_ipaddr(apr_pool_t *p, SECItem *item); const char * SECItem_get_oid(apr_pool_t *p, SECItem *oid); + +/* MOVE ME */ +SECStatus ownALPNCallback( void *arg, + PRFileDesc *fd, + const unsigned char* protos, + unsigned int protosLen, + unsigned char* protoOut, + unsigned int* protoOutLen, + unsigned int protoMaxOut); + +SECStatus NSSHandshakeCallback(PRFileDesc *socket, void *arg); #endif /* __MOD_NSS_H__ */ diff --git a/nss_engine_init.c b/nss_engine_init.c index cd71989..00f4838 100644 --- a/nss_engine_init.c +++ b/nss_engine_init.c @@ -32,7 +32,7 @@ static SECStatus ownBadCertHandler(void *arg, PRFileDesc * socket); static SECStatus ownHandshakeCallback(PRFileDesc * socket, void *arg); -static SECStatus NSSHandshakeCallback(PRFileDesc *socket, void *arg); +//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 nssSSLSNISocketConfig(PRFileDesc *fd, const SECItem *sniNameArr, PRUint32 sniNameArrSize, void *arg); @@ -687,6 +687,31 @@ static void nss_init_ctx_socket(server_rec *s, nss_die(); } #endif +#ifdef SSL_ENABLE_ALPN + if (mctx->as_server) { + if (SSL_OptionSet(mctx->model, SSL_ENABLE_NPN, PR_FALSE) + != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Unable to disable NPN."); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); + nss_die(); + } + if (SSL_OptionSet(mctx->model, SSL_ENABLE_ALPN, PR_TRUE) + != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Unable to enable ALPN."); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); + nss_die(); + } + if (SSL_SetNextProtoNego(mctx->model, "\bhttp/1.1\002h2", 12) + != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Unable to set ALPN string."); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); + nss_die(); + } + } +#endif } static void nss_init_ctx_protocol(server_rec *s, @@ -1404,7 +1429,7 @@ static void nss_init_server_certs(server_rec *s, "Error setting PKCS11 pin argument: '%s'", mctx->nickname); nss_die(); } - secstatus = (SECStatus)SSL_HandshakeCallback(mctx->model, (SSLHandshakeCallback)NSSHandshakeCallback, NULL); + secstatus = (SECStatus)SSL_HandshakeCallback(mctx->model, (SSLHandshakeCallback)NSSHandshakeCallback, s); if (secstatus != SECSuccess) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, @@ -1417,6 +1442,15 @@ static void nss_init_server_certs(server_rec *s, "Configured proxy nickname as '%s'", mctx->nickname); } } +#ifdef SSL_ENABLE_ALPN + /* Bug in NSS that this isn't being copied in ImportFD */ + if (SSL_SetNextProtoCallback(mctx->model, ownALPNCallback, NULL) != SECSuccess) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "SSL_SetNextProtoCallback failed"); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, s); + nss_die(); + } +#endif } @@ -1659,6 +1693,30 @@ SECStatus ownHandshakeCallback(PRFileDesc * socket, void *arg) } /* + * arg should be the list of supported protocols. This will need to + * be set when the callback is added. Not sure how I can do this in + * a child. + * + * Incoming looks like: \bhttp/1.1\002h2 (len 12) + * + * <length><string> until EOF + * + * See ssl_callback_alpn_select in ssl_engine_kernel.c for how mod_ssl + * selects protocol. It uses Apache fn ap_select_protocol to do the + * real work, after creating the right lists. + */ +SECStatus ownALPNCallback(void *arg, PRFileDesc *fd, + const unsigned char* protos, unsigned int protosLen, + unsigned char* protoOut, unsigned int* protoOutLen, + unsigned int protoMaxOut) +{ + strcpy(protoOut, "h2"); + *protoOutLen = strlen(protoOut); + + return SECSuccess; +} + +/* * Duplicated, non-exported function from NSS that compares 2 certificate * times. */ @@ -1806,8 +1864,112 @@ FindServerCertFromNickname(const char* name, const CERTCertList* clist) * Executed automatically when the SSL handshake is completed. * We don't do anything special here. */ +#define MAX_ALPN_LENGTH 255 /* really? */ SECStatus NSSHandshakeCallback(PRFileDesc *socket, void *arg) { + + /* TODO: check if ALPN is set at all */ + + server_rec *s; + conn_rec *c; + SSLNextProtoState alpnState; + char chosenAlpn[MAX_ALPN_LENGTH]; + unsigned int chosenAlpnLen; + char * protocol = NULL; + + const char *proposed; + apr_array_header_t *client_protos; + + if (!arg) + return SECFailure; + + c = (conn_rec *) arg; + s = c->base_server; + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, + "SSL Handshake is done, in NSSHandshakeCallback"); + + SECStatus rv = SSL_GetNextProto(socket, &alpnState, + (unsigned char*)chosenAlpn, + &chosenAlpnLen, sizeof(chosenAlpn)); + if (rv != SECSuccess) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ALPN error"); + nss_log_nss_error(APLOG_MARK, APLOG_ERR, NULL); + return SECFailure; + } + + switch (alpnState) { + case SSL_NEXT_PROTO_SELECTED: + case SSL_NEXT_PROTO_NEGOTIATED: + break; // OK + case SSL_NEXT_PROTO_NO_SUPPORT: + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ALPN not negotiated"); + /* TODO: set some sort of default? */ + return SECSuccess; + case SSL_NEXT_PROTO_NO_OVERLAP: + // This only happens if there is a custom NPN/ALPN callback + // installed and that callback doesn't properly handle ALPN. + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "error in ALPN selection callback"); + return SECFailure; + } + + // The ALPN string isn't null-terminated. + protocol = (char *)malloc(chosenAlpnLen + 1); + strncpy(protocol, chosenAlpn, chosenAlpnLen); + protocol[chosenAlpnLen] = '\0'; + + /* FIXME: Needs to happen so we can actually propose a proto */ + client_protos = apr_array_make(c->pool, 0, sizeof(char *)); + APR_ARRAY_PUSH(client_protos, char *) = + apr_pstrndup(c->pool, (const char *)protocol, chosenAlpnLen); + + proposed = ap_select_protocol(c, NULL, c->base_server, client_protos); + if (!proposed) { + proposed = ap_get_protocol(c); + } + + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "Selected ALPN string: %s", protocol); + if (strcmp(protocol, ap_get_protocol(c))) { + apr_status_t status; + /* FIXME: don't use base_server here, need real server */ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "Switching protocol"); + status = ap_switch_protocol(c, NULL, c->base_server, protocol); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, + "protocol switch to '%s' failed", protocol); + return SECFailure; + } + } + + free(protocol); + + /* TODO: ensure the chosen string is in allowed list */ +/* + if (alpn_allowed_.find(chosen) == alpn_allowed_.end()) { + // Maybe our peer chose a protocol we didn't offer (when we are client), or + // something is seriously wrong. + std::ostringstream ss; + for (auto i = alpn_allowed_.begin(); i != alpn_allowed_.end(); ++i) { + ss << (i == alpn_allowed_.begin() ? " '" : ", '") << *i << "'"; + } + MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Bad ALPN string: '" << chosen + << "'; permitted:" << ss.str()); + return false; + } + alpn_ = chosen; +*/ + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, + "Protocol: %s, Cipher: %s (%s/%s bits)", + nss_var_lookup(NULL, s, c, NULL, "SSL_PROTOCOL"), + nss_var_lookup(NULL, s, c, NULL, "SSL_CIPHER"), + nss_var_lookup(NULL, s, c, NULL, "SSL_CIPHER_USEKEYSIZE"), + nss_var_lookup(NULL, s, c, NULL, "SSL_CIPHER_ALGKEYSIZE")); + return SECSuccess; } diff --git a/nss_engine_io.c b/nss_engine_io.c index de57a20..2a9b158 100644 --- a/nss_engine_io.c +++ b/nss_engine_io.c @@ -351,6 +351,10 @@ static apr_status_t nss_io_input_read(nspr_filter_in_ctx_t *inctx, PR_SetError(0, 0); rc = PR_Read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes); +#ifdef READ_DEBUG + ap_log_error(APLOG_MARK, APLOG_INFO, inctx->rc, c->base_server, + "mode: %d read: %s len %d wanted %d", inctx->mode, buf, rc, wanted); +#endif if (rc > 0) { *len += rc; @@ -401,6 +405,8 @@ static apr_status_t nss_io_input_read(nspr_filter_in_ctx_t *inctx, * renegotation which is handled implicitly by NSS.) */ inctx->rc = APR_EAGAIN; + ap_log_error(APLOG_MARK, APLOG_INFO, inctx->rc, c->base_server, + "PR_WOULD_BLOCK_ERROR, len %d", *len); if (*len > 0) { inctx->rc = APR_SUCCESS; @@ -685,7 +691,10 @@ static apr_status_t nss_io_filter_handshake(ap_filter_t *f) { conn_rec *c = f->c; SSLConnRec *sslconn = myConnConfig(c); - SECStatus rv; + apr_status_t status; + nspr_filter_in_ctx_t *inctx = f->ctx; + char buf[1024]; + apr_size_t len = 0; /* * Enable SNI for proxy backend requests. Make sure we don't do it for @@ -713,7 +722,7 @@ static apr_status_t nss_io_filter_handshake(ap_filter_t *f) apr_ipsubnet_create(&ip, hostname_note, NULL, c->pool) != APR_SUCCESS) { - if ((rv = SSL_SetURL(sslconn->ssl, hostname_note)) != SECSuccess) { + if (SSL_SetURL(sslconn->ssl, hostname_note) != SECSuccess) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, "Error setting SNI extension for SSL Proxy request: %d", PR_GetError()); @@ -728,6 +737,13 @@ static apr_status_t nss_io_filter_handshake(ap_filter_t *f) } } + status = nss_io_input_read(inctx, buf, &len); + + /* don't want it being EOF, check first */ + inctx->rc = APR_SUCCESS; + + /* FIXME: do something with status */ + return APR_SUCCESS; } @@ -766,9 +782,12 @@ static apr_status_t nss_io_filter_input(ap_filter_t *f, inctx->mode = mode; inctx->block = block; + /* FIXME: be sure to check that proxy client still works */ + if (mode == AP_MODE_INIT) { if ((status = nss_io_filter_handshake(f)) != APR_SUCCESS) { return nss_io_filter_error(f, bb, status); } + } if (is_init) { /* protocol module needs to handshake before sending @@ -891,9 +910,13 @@ static apr_status_t nss_io_filter_output(ap_filter_t *f, inctx->mode = AP_MODE_READBYTES; inctx->block = APR_BLOCK_READ; + + /* FIXME: is this really needed? */ +#if 0 if ((status = nss_io_filter_handshake(f)) != APR_SUCCESS) { return nss_io_filter_error(f, bb, status); } +#endif while (!APR_BRIGADE_EMPTY(bb)) { apr_bucket *bucket = APR_BRIGADE_FIRST(bb); -- 2.5.5
_______________________________________________ Mod_nss-list mailing list [email protected] https://www.redhat.com/mailman/listinfo/mod_nss-list
