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