Index: conf/web.xml =================================================================== --- conf/web.xml (revision 1734933) +++ conf/web.xml (working copy) @@ -436,6 +436,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java =================================================================== --- java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java (revision 1734933) +++ java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java (working copy) @@ -45,6 +45,17 @@ 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 hpkpPins = 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 +85,33 @@ } 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 (hpkpPins != null) { + String[] hpkpPinArray = hpkpPins.split(",\\s*"); + String hpkpHashType, hpkpHashValue; + for (int i = 0; i < hpkpPinArray.length / 2; i++) { + hpkpHashType = hpkpPinArray[2 * i]; + hpkpHashValue = hpkpPinArray[2 * i + 1]; + hpkpValue.append("; pin-"); + hpkpValue.append(hpkpHashType); + hpkpValue.append(""); + hpkpValue.append("=\""); + hpkpValue.append(hpkpHashValue); + hpkpValue.append("\""); + } + } + hpkpHeaderValue = hpkpValue.toString(); + // Anti click-jacking StringBuilder cjValue = new StringBuilder(antiClickJackingOption.headerValue); if (antiClickJackingOption == XFrameOption.ALLOW_FROM) { @@ -100,6 +138,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 +216,70 @@ } + 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 getHpkpPins() { + return this.hpkpPins; + } + + + public void setHpkpPins(String hpkpPins) { + this.hpkpPins = hpkpPins; + } + + public boolean isAntiClickJackingEnabled() { return antiClickJackingEnabled; } Index: webapps/docs/config/filter.xml =================================================================== --- webapps/docs/config/filter.xml (revision 1734933) +++ webapps/docs/config/filter.xml (working copy) @@ -899,6 +899,51 @@ be used.

+ +

Will an HTTP Public Key Pinning (HPKP) header (either + Public-Key-Pins or + Public-Key-Pins-Report-Only) be set on the response for + secure requests. Any HPKP header already present will be replaced. See + RFC 7469 for further + details of HPKP. If not specified, the default value of + false will be used.

+
+ + +

Will a HPKP "report only" header + (Public-Key-Pins-Report-Only) be set instead of a HPKP + "enforcing" header (Public-Key-Pins). If not specified, + the default value of false will be used. +

+
+ + +

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 + 0 will be used.

+
+ + +

Should the includeSubDomains parameter be included in the HSTS + header. If not specified, the default value of false will + be used.

+
+ + +

The URI that should be used in the HPKP header's report URI + directive (report-uri). If not specified, the directive + will be omitted in the HPKP header.

+
+ + +

A comma separated list of values specifying the HPKP pins. Values + must occur in pairs. The first value of each pair is the hash + algorithm (sha256), and the second value is a sequence of + Base64 digits representing an according SPKI fingerprint. Whitespace + after each comma is allowed. If not specified, the directive will be + omitted in the HPKP header.

+
+

Should the anti click-jacking header (X-Frame-Options) be set on the response. Any anti click-jacking header already present