Hello,

As an occasional user of Tomcat I was missing HTTP Public Key Pinning
header support¹. So I have added it to the existing
"HttpHeaderSecurityFilter" class and would like to share it with you in
case you are interested. Please see the attached patch.

Best Regards,
Patrick Beckmann

¹ https://tools.ietf.org/html/rfc7469
Index: conf/web.xml
===================================================================
--- conf/web.xml	(revision 1734844)
+++ conf/web.xml	(working copy)
@@ -436,6 +436,36 @@
   <!--                       Should the includeSubDomains parameter be      -->
   <!--                       included in the HSTS header.                   -->
   <!--                                                                      -->
+  <!--   hpkpEnabled         Should the HTTP Public Key Pinning (HPKP)      -->
+  <!--                       header be added to the response? See RFC 7469  -->
+  <!--                       for more information on HPKP. [false]          -->
+  <!--                                                                      -->
+  <!--   hpkpReportOnly      Should a HPKP "report only" header be set      -->
+  <!--                       instead of a HPKP "enforcing" header? [false]  -->
+  <!--                                                                      -->
+  <!--   hpkpMaxAgeSeconds   The max age value that should be used in the   -->
+  <!--                       HPKP header. Negative values will be treated   -->
+  <!--                       as zero. [0]                                   -->
+  <!--                                                                      -->
+  <!--   hpkpIncludeSubDomains                                              -->
+  <!--                       Should the includeSubDomains parameter be      -->
+  <!--                       included in the HPKP header. [false]           -->
+  <!--                                                                      -->
+  <!--   hpkpReportUri       The URI that should be used in the HPKP        -->
+  <!--                       header's report URI directive. []              -->
+  <!--                                                                      -->
+  <!--   hpkpPin1            A sequence of Base64 digits representing a     -->
+  <!--                       SPKI fingerprint, that should be used in a     -->
+  <!--                       HPKP header's Pin directive. []                -->
+  <!--                                                                      -->
+  <!--   hpkpPin2            A sequence of Base64 digits representing a     -->
+  <!--                       SPKI fingerprint, that should be used in a     -->
+  <!--                       HPKP header's Pin directive. []                -->
+  <!--                                                                      -->
+  <!--   hpkpPin3            A sequence of Base64 digits representing a     -->
+  <!--                       SPKI fingerprint, that should be used in a     -->
+  <!--                       HPKP header's Pin directive. []                -->
+  <!--                                                                      -->
   <!--   antiClickJackingEnabled                                            -->
   <!--                       Should the anti click-jacking header           -->
   <!--                       X-Frame-Options be added to every response?    -->
Index: java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java
===================================================================
--- java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java	(revision 1734844)
+++ java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java	(working copy)
@@ -45,6 +45,19 @@
     private boolean hstsIncludeSubDomains = false;
     private String hstsHeaderValue;
 
+    // HPKP
+    private static final String HPKP_HEADER_NAME = "Public-Key-Pins";
+    private static final String HPKP_RO_HEADER_NAME = "Public-Key-Pins-Report-Only";
+    private boolean hpkpEnabled = false;
+    private boolean hpkpReportOnly = false;
+    private int hpkpMaxAgeSeconds = 0;
+    private boolean hpkpIncludeSubDomains = false;
+    private String hpkpReportUri = null;
+    private String hpkpPin1 = null;
+    private String hpkpPin2 = null;
+    private String hpkpPin3 = null;
+    private String hpkpHeaderValue;
+
     // Click-jacking protection
     private static final String ANTI_CLICK_JACKING_HEADER_NAME = "X-Frame-Options";
     private boolean antiClickJackingEnabled = true;
@@ -74,6 +87,34 @@
         }
         hstsHeaderValue = hstsValue.toString();
 
+        // Build HPKP header value
+        StringBuilder hpkpValue = new StringBuilder("max-age=");
+        hpkpValue.append(hpkpMaxAgeSeconds);
+        if (hpkpIncludeSubDomains) {
+            hpkpValue.append("; includeSubDomains");
+        }
+        if (hpkpReportUri != null) {
+            hpkpValue.append("; report-uri=\"");
+            hpkpValue.append(hpkpReportUri);
+            hpkpValue.append("\"");
+        }
+        if (hpkpPin1 != null) {
+            hpkpValue.append("; pin-sha256=\"");
+            hpkpValue.append(hpkpPin1);
+            hpkpValue.append("\"");
+        }
+        if (hpkpPin2 != null) {
+            hpkpValue.append("; pin-sha256=\"");
+            hpkpValue.append(hpkpPin2);
+            hpkpValue.append("\"");
+        }
+        if (hpkpPin3 != null) {
+            hpkpValue.append("; pin-sha256=\"");
+            hpkpValue.append(hpkpPin3);
+            hpkpValue.append("\"");
+        }
+        hpkpHeaderValue = hpkpValue.toString();
+
         // Anti click-jacking
         StringBuilder cjValue = new StringBuilder(antiClickJackingOption.headerValue);
         if (antiClickJackingOption == XFrameOption.ALLOW_FROM) {
@@ -100,6 +141,15 @@
                 httpResponse.setHeader(HSTS_HEADER_NAME, hstsHeaderValue);
             }
 
+            // HPKP
+            if (hpkpEnabled && request.isSecure()) {
+                if (hpkpReportOnly) {
+                    httpResponse.setHeader(HPKP_RO_HEADER_NAME, hpkpHeaderValue);
+                } else {
+                    httpResponse.setHeader(HPKP_HEADER_NAME, hpkpHeaderValue);
+                }
+            }
+
             // anti click-jacking
             if (antiClickJackingEnabled) {
                 httpResponse.setHeader(ANTI_CLICK_JACKING_HEADER_NAME, antiClickJackingHeaderValue);
@@ -169,7 +219,90 @@
     }
 
 
+    public boolean isHpkpEnabled() {
+        return hpkpEnabled;
+    }
 
+
+    public void setHpkpEnabled(boolean hpkpEnabled) {
+        this.hpkpEnabled = hpkpEnabled;
+    }
+
+
+    public boolean isHpkpReportOnly() {
+        return hpkpReportOnly;
+    }
+
+
+    public void setHpkpReportOnly(boolean hpkpReportOnly) {
+        this.hpkpReportOnly = hpkpReportOnly;
+    }
+
+
+    public int getHpkpMaxAgeSeconds() {
+        return hpkpMaxAgeSeconds;
+    }
+
+
+    public void setHpkpMaxAgeSeconds(int hpkpMaxAgeSeconds) {
+        if (hpkpMaxAgeSeconds < 0) {
+            this.hpkpMaxAgeSeconds = 0;
+        } else {
+            this.hpkpMaxAgeSeconds = hpkpMaxAgeSeconds;
+        }
+    }
+
+
+    public boolean isHpkpIncludeSubDomains() {
+        return hpkpIncludeSubDomains;
+    }
+
+
+    public void setHpkpIncludeSubDomains(boolean hpkpIncludeSubDomains) {
+        this.hpkpIncludeSubDomains = hpkpIncludeSubDomains;
+    }
+
+
+    public String getHpkpReportUri() {
+        return this.hpkpReportUri;
+    }
+
+
+    public void setHpkpReportUri(String hpkpReportUri) {
+        this.hpkpReportUri = hpkpReportUri;
+    }
+
+
+    public String getHpkpPin1() {
+        return this.hpkpPin1;
+    }
+
+
+    public void setHpkpPin1(String hpkpPin1) {
+        this.hpkpPin1 = hpkpPin1;
+    }
+
+
+    public String getHpkpPin2() {
+        return this.hpkpPin2;
+    }
+
+
+    public void setHpkpPin2(String hpkpPin2) {
+        this.hpkpPin2 = hpkpPin2;
+    }
+
+
+    public String getHpkpPin3() {
+        return this.hpkpPin3;
+    }
+
+
+    public void setHpkpPin3(String hpkpPin3) {
+        this.hpkpPin3 = hpkpPin3;
+    }
+
+
     public boolean isAntiClickJackingEnabled() {
         return antiClickJackingEnabled;
     }
Index: webapps/docs/config/filter.xml
===================================================================
--- webapps/docs/config/filter.xml	(revision 1734844)
+++ webapps/docs/config/filter.xml	(working copy)
@@ -899,6 +899,63 @@
         be used.</p>
       </attribute>
 
+      <attribute name="hpkpEnabled" required="false">
+        <p>Will an HTTP Public Key Pinning (HPKP) header (either
+        <code>Public-Key-Pins</code> or
+        <code>Public-Key-Pins-Report-Only</code>) be set on the response for
+        secure requests. Any HPKP header already present will be replaced. See
+        <a href="http://tools.ietf.org/html/rfc7469";>RFC 7469</a> for further
+        details of HPKP. If not specified, the default value of
+        <code>false</code> will be used.</p>
+      </attribute>
+
+      <attribute name="hpkpReportOnly" required="false">
+        <p>Will a HPKP "report only" header
+        (<code>Public-Key-Pins-Report-Only</code>) be set instead of a HPKP
+        "enforcing" header (<code>Public-Key-Pins</code>). If not specified,
+        the default value of <code>false</code> will be used.
+        </p>
+      </attribute>
+
+      <attribute name="hpkpMaxAgeSeconds" required="false">
+        <p>The max age value that should be used in the HPKP header. Negative
+        values will be treated as zero. If not specified, the default value of
+        <code>0</code> will be used.</p>
+      </attribute>
+
+      <attribute name="hpkpIncludeSubDomains" required="false">
+        <p>Should the includeSubDomains parameter be included in the HSTS
+        header. If not specified, the default value of <code>false</code> will
+        be used.</p>
+      </attribute>
+
+      <attribute name="hpkpReportUri" required="false">
+        <p>The URI that should be used in the HPKP header's report URI
+        directive (<code>report-uri</code>). If not specified, the directive
+        will be omitted in the HPKP header.</p>
+      </attribute>
+
+      <attribute name="hpkpPin1" required="false">
+        <p>A sequence of Base64 digits representing a SPKI fingerprint, that
+        should be used in a HPKP header's Pin directive
+        (<code>pin-sha256</code>). If not specified, the directive will be
+        omitted in the HPKP header.</p>
+      </attribute>
+
+      <attribute name="hpkpPin2" required="false">
+        <p>A sequence of Base64 digits representing a SPKI fingerprint, that
+        should be used in a HPKP header's Pin directive
+        (<code>pin-sha256</code>). If not specified, the directive will be
+        omitted in the HPKP header.</p>
+      </attribute>
+
+      <attribute name="hpkpPin3" required="false">
+        <p>A sequence of Base64 digits representing a SPKI fingerprint, that
+        should be used in a HPKP header's Pin directive
+        (<code>pin-sha256</code>). If not specified, the directive will be
+        omitted in the HPKP header.</p>
+      </attribute>
+
       <attribute name="antiClickJackingEnabled" required="false">
         <p>Should the anti click-jacking header (<code>X-Frame-Options</code>)
         be set on the response. Any anti click-jacking header already present

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to