[Pound Mailing List] [Patch] HTTP Strict Transport Security option

"Frank Schmirler" <frank.schmirler-PfwzoFMxJyVWk0Htik3J/[email protected]>
Newsgroups gmane.comp.web.pound.general
Message-ID <[email protected]>
Hi there,

HTTP Strict Transport Security (HSTS) uses an HTTPS response header, telling
the browser to always use an encrypted connection for this hostname and to not
let the user override any certificate warnings when it re-visits the site in
the future. These restrictions apply for a certain period of time. The goal is
to make man-in-the-middle attacks more difficult. Of the major browsers, only
IE doesn't support it yet.

* http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
* http://tools.ietf.org/html/rfc6797

Even though you could configure HSTS on the backend and have pound simply
forward the header, I thought it would be a better idea to configure it in pound.

The attached patch is against 2.7c and introduces the new service level
instruction "StrictTransportSecurity <seconds>". It is only acceptable inside
HTTPSListeners. The parameter tells the browser for how long it should
remember HSTS. On production systems you should use something in the range of
several months (for ssllabs.com you need to configure at least 180 days to
have it improve your rating). The special value 0 instructs the browser to
delete the HSTS entry. This of course requires the browser to re-visit your site.

When HSTS is configured in a pound service it will override any HSTS headers
received from the backend. Otherwise the backend's HSTS header is simply
passed through.

Before you enable HSTS, make sure you understand its implications:
- Even though you can configure HSTS by service, it actually affects the
(virtual) hostname, i.e. any service and even HTTPListeners or other web
servers which can be addressed with the same hostname
- So for clarity I recommend to configure HSTS with the same value in all
affected pound services
- Do not enable if an unencrypted (HTTP only) site is running on any (virtual)
hostname your HTTPS service is listening to
- Be aware that you will no longer be able to simply switch back to HTTP

Some things the patch does not implement but could easily be added if you
think this is necessary:
- No HSTS header for Redirect backends
- No support for the HSTS includeSubDomains option
- No stripping of HSTS headers in HTTPListeners

Regards,
Frank
pound-2.7c-sts.diff (application/x-download, 6.4 KB)
diff -ru Pound-2.7c.orig/config.c Pound-2.7c/config.c
--- Pound-2.7c.orig/config.c	2014-04-21 13:16:08.000000000 +0200
+++ Pound-2.7c/config.c	2014-09-18 15:24:27.261424603 +0200
@@ -76,7 +76,7 @@
 static regex_t  Empty, Comment, User, Group, RootJail, Daemon, LogFacility, LogLevel, Alive, SSLEngine, Control;
 static regex_t  ListenHTTP, ListenHTTPS, End, Address, Port, Cert, xHTTP, Client, CheckURL;
 static regex_t  Err414, Err500, Err501, Err503, MaxRequest, HeadRemove, RewriteLocation, RewriteDestination;
-static regex_t  Service, ServiceName, URL, HeadRequire, HeadDeny, BackEnd, Emergency, Priority, HAport, HAportAddr;
+static regex_t  Service, ServiceName, URL, HeadRequire, HeadDeny, BackEnd, Emergency, Priority, HAport, HAportAddr, StrictTransportSecurity;
 static regex_t  Redirect, RedirectN, TimeOut, Session, Type, TTL, ID, DynScale;
 static regex_t  ClientCert, AddHeader, DisableSSLv2, SSLAllowClientRenegotiation, SSLHonorCipherOrder, Ciphers;
 static regex_t  CAlist, VerifyList, CRLlist, NoHTTPS11, Grace, Include, ConnTO, IgnoreCase, HTTPS, HTTPSCert;
@@ -531,6 +531,7 @@
     memset(res, 0, sizeof(SERVICE));
     res->sess_type = SESS_NONE;
     res->dynscale = dynscale;
+    res->sts = -1;
     pthread_mutex_init(&res->mut, NULL);
     if(svc_name)
         strncpy(res->name, svc_name, KEY_SIZE);
@@ -592,6 +593,8 @@
             lin[matches[1].rm_eo] = '\0';
             if(regcomp(&m->pat, lin + matches[1].rm_so, REG_ICASE | REG_NEWLINE | REG_EXTENDED))
                 conf_err("HeadDeny bad pattern - aborted");
+        } else if(!regexec(&StrictTransportSecurity, lin, 4, matches, 0)) {
+            res->sts = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&Redirect, lin, 4, matches, 0)) {
             if(res->backends) {
                 for(be = res->backends; be->next; be = be->next)
@@ -818,12 +821,16 @@
         } else if(!regexec(&LogLevel, lin, 4, matches, 0)) {
             res->log_level = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&Service, lin, 4, matches, 0)) {
-            if(res->services == NULL)
+            if(res->services == NULL) {
                 res->services = parse_service(NULL);
-            else {
+                if(res->services->sts >= 0)
+                    conf_err("StrictTransportSecurity not allowed in HTTP listener - aborted");
+            } else {
                 for(svc = res->services; svc->next; svc = svc->next)
                     ;
                 svc->next = parse_service(NULL);
+                if(svc->next->sts >= 0)
+                    conf_err("StrictTransportSecurity not allowed in HTTP listener - aborted");
             }
         } else if(!regexec(&ServiceName, lin, 4, matches, 0)) {
             lin[matches[1].rm_eo] = '\0';
@@ -1404,6 +1411,7 @@
     || regcomp(&URL, "^[ \t]*URL[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&HeadRequire, "^[ \t]*HeadRequire[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&HeadDeny, "^[ \t]*HeadDeny[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
+    || regcomp(&StrictTransportSecurity, "^[ \t]*StrictTransportSecurity[ \    t]+([0-9]+)[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&BackEnd, "^[ \t]*BackEnd[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&Emergency, "^[ \t]*Emergency[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&Priority, "^[ \t]*Priority[ \t]+([1-9])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
@@ -1566,6 +1574,7 @@
     regfree(&URL);
     regfree(&HeadRequire);
     regfree(&HeadDeny);
+    regfree(&StrictTransportSecurity);
     regfree(&BackEnd);
     regfree(&Emergency);
     regfree(&Priority);
diff -ru Pound-2.7c.orig/http.c Pound-2.7c/http.c
--- Pound-2.7c.orig/http.c	2014-04-21 13:16:08.000000000 +0200
+++ Pound-2.7c/http.c	2014-09-18 15:57:35.779295210 +0200
@@ -1379,6 +1379,8 @@
             if(!no_cont && !regexec(&RESP_IGN, response, 0, NULL, 0))
                 no_cont = 1;
 
+            for(n = 0; n < MAXHEADERS; n++)
+                headers_ok[n] = 1;
             for(chunked = 0, cont = -1L, n = 1; n < MAXHEADERS && headers[n]; n++) {
                 switch(check_header(headers[n], buf)) {
                 case HEADER_CONNECTION:
@@ -1429,6 +1431,11 @@
                         }
                     }
                     break;
+                case HEADER_STRICT_TRANSPORT_SECURITY:
+                    /* enforce pound's STS header */
+                    if(svc->sts >= 0)
+                        headers_ok[n] = 0;
+                    break;
                 }
             }
 
@@ -1438,6 +1445,8 @@
             /* send the response */
             if(!skip)
                 for(n = 0; n < MAXHEADERS && headers[n]; n++) {
+                    if(!headers_ok[n])
+                        continue;
                     if(BIO_printf(cl, "%s\r\n", headers[n]) <= 0) {
                         if(errno) {
                             addr2str(caddr, MAXBUF - 1, &from_host, 1);
@@ -1449,6 +1458,8 @@
                     }
                 }
             free_headers(headers);
+            if(!skip && ssl && svc->sts >= 0)
+                BIO_printf(cl, "Strict-Transport-Security: max-age=%d\r\n", svc->sts);
 
             /* final CRLF */
             if(!skip)
diff -ru Pound-2.7c.orig/pound.h Pound-2.7c/pound.h
--- Pound-2.7c.orig/pound.h	2014-04-21 13:16:08.000000000 +0200
+++ Pound-2.7c/pound.h	2014-09-18 15:58:30.597645409 +0200
@@ -370,6 +370,7 @@
 #endif
     int                 dynscale;   /* true if the back-ends should be dynamically rescaled */
     int                 disabled;   /* true if the service is disabled */
+    int                 sts;        /* strict transport security */
     struct _service     *next;
 }   SERVICE;
 
@@ -441,6 +442,7 @@
 #define HEADER_URI                  9
 #define HEADER_DESTINATION          10
 #define HEADER_EXPECT               11
+#define HEADER_STRICT_TRANSPORT_SECURITY 12
 
 /* control request stuff */
 typedef enum    {
diff -ru Pound-2.7c.orig/svc.c Pound-2.7c/svc.c
--- Pound-2.7c.orig/svc.c	2014-04-21 13:16:08.000000000 +0200
+++ Pound-2.7c/svc.c	2014-09-18 15:58:57.444755396 +0200
@@ -391,6 +391,7 @@
         { "User-agent",         10, HEADER_USER_AGENT },
         { "Destination",        11, HEADER_DESTINATION },
         { "Expect",             6,  HEADER_EXPECT },
+        { "Strict-Transport-Security", 25, HEADER_STRICT_TRANSPORT_SECURITY },
         { "",                   0,  HEADER_OTHER },
     };
     int i;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.