Polish HPKP

* Javadoc polish
* Whitespace cleanup

Issue gh-3706
This commit is contained in:
Rob Winch 2016-03-03 15:11:40 -06:00
parent a7b0f74803
commit db81977a1a
6 changed files with 196 additions and 195 deletions

View File

@ -166,7 +166,6 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
loadParsers(); loadParsers();
} }
@SuppressWarnings("deprecation")
private void loadParsers() { private void loadParsers() {
// Parsers // Parsers
parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser()); parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());

View File

@ -218,11 +218,11 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
hash = "sha256"; hash = "sha256";
} }
Node pinValueNode = pinElement.getFirstChild(); Node pinValueNode = pinElement.getFirstChild();
if (pinValueNode == null) { if (pinValueNode == null) {
context.getReaderContext().warning("Missing value for pin entry.", hpkpElement); context.getReaderContext().warning("Missing value for pin entry.", hpkpElement);
continue; continue;
} }
String fingerprint = pinElement.getFirstChild().getTextContent(); String fingerprint = pinElement.getFirstChild().getTextContent();

View File

@ -35,12 +35,12 @@ class HeadersConfigurerTests extends BaseSpringSpec {
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
responseHeaders == ['X-Content-Type-Options':'nosniff', responseHeaders == ['X-Content-Type-Options':'nosniff',
'X-Frame-Options':'DENY', 'X-Frame-Options':'DENY',
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Expires' : '0', 'Expires' : '0',
'Pragma':'no-cache', 'Pragma':'no-cache',
'X-XSS-Protection' : '1; mode=block'] 'X-XSS-Protection' : '1; mode=block']
} }
@EnableWebSecurity @EnableWebSecurity
@ -123,8 +123,8 @@ class HeadersConfigurerTests extends BaseSpringSpec {
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
responseHeaders == ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', responseHeaders == ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Expires' : '0', 'Expires' : '0',
'Pragma':'no-cache'] 'Pragma':'no-cache']
} }
@EnableWebSecurity @EnableWebSecurity
@ -168,12 +168,12 @@ class HeadersConfigurerTests extends BaseSpringSpec {
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
responseHeaders == ['X-Content-Type-Options':'nosniff', responseHeaders == ['X-Content-Type-Options':'nosniff',
'X-Frame-Options':'SAMEORIGIN', 'X-Frame-Options':'SAMEORIGIN',
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Expires' : '0', 'Expires' : '0',
'Pragma':'no-cache', 'Pragma':'no-cache',
'X-XSS-Protection' : '1; mode=block'] 'X-XSS-Protection' : '1; mode=block']
} }
@EnableWebSecurity @EnableWebSecurity

View File

@ -30,12 +30,12 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher
*/ */
class HttpHeadersConfigTests extends AbstractHttpConfigTests { class HttpHeadersConfigTests extends AbstractHttpConfigTests {
def defaultHeaders = ['X-Content-Type-Options':'nosniff', def defaultHeaders = ['X-Content-Type-Options':'nosniff',
'X-Frame-Options':'DENY', 'X-Frame-Options':'DENY',
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Expires' : '0', 'Expires' : '0',
'Pragma':'no-cache', 'Pragma':'no-cache',
'X-XSS-Protection' : '1; mode=block'] 'X-XSS-Protection' : '1; mode=block']
def 'headers disabled'() { def 'headers disabled'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
@ -294,7 +294,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
then: then:
assertHeaders(response, ['abc':'def']) assertHeaders(response, ['abc':'def'])
} }
def 'http headers header no name produces error'() { def 'http headers header no name produces error'() {
@ -404,8 +404,8 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', assertHeaders(response, ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Expires' : '0', 'Expires' : '0',
'Pragma':'no-cache']) 'Pragma':'no-cache'])
} }
def 'http headers hsts'() { def 'http headers hsts'() {
@ -458,35 +458,35 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
assertHeaders(response, ['Strict-Transport-Security': 'max-age=1']) assertHeaders(response, ['Strict-Transport-Security': 'max-age=1'])
} }
def 'http headers hpkp no pins'() { def 'http headers hpkp no pins'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'() 'hpkp'()
} }
} }
when: when:
createAppContext() createAppContext()
then: then:
XmlBeanDefinitionStoreException expected = thrown() XmlBeanDefinitionStoreException expected = thrown()
expected.message.contains 'The content of element \'hpkp\' is not complete' expected.message.contains 'The content of element \'hpkp\' is not complete'
} }
def 'http headers hpkp no pin'() { def 'http headers hpkp no pin'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'() { 'hpkp'() {
'pins'() 'pins'()
} }
} }
} }
when: when:
createAppContext() createAppContext()
then: then:
XmlBeanDefinitionStoreException expected = thrown() XmlBeanDefinitionStoreException expected = thrown()
expected.message.contains 'The content of element \'pins\' is not complete' expected.message.contains 'The content of element \'pins\' is not complete'
} }
def 'http headers hpkp'() { def 'http headers hpkp'() {
setup: setup:
@ -508,125 +508,125 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
} }
def 'http headers hpkp with default algorithm'() { def 'http headers hpkp with default algorithm'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'() { 'hpkp'() {
'pins'() { 'pins'() {
'pin'('d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') 'pin'('d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
} }
def 'http headers hpkp only invokes on HttpServletRequest.isSecure = true'() { def 'http headers hpkp only invokes on HttpServletRequest.isSecure = true'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'() { 'hpkp'() {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
then: then:
response.headerNames.empty response.headerNames.empty
} }
def 'http headers hpkp with custom max age'() { def 'http headers hpkp with custom max age'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'('max-age-seconds':'604800') { 'hpkp'('max-age-seconds':'604800') {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'd6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') 'pin'('algorithm':'sha256', 'd6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
} }
def 'http headers hpkp@reportOnly=false'() { def 'http headers hpkp@reportOnly=false'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'('report-only':'false') { 'hpkp'('report-only':'false') {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="']) assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="'])
} }
def 'http headers hpkp@includeSubDomains=true'() { def 'http headers hpkp@includeSubDomains=true'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'('include-subdomains':'true') { 'hpkp'('include-subdomains':'true') {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains']) assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains'])
} }
def 'http headers hpkp with report-uri'() { def 'http headers hpkp with report-uri'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'('defaults-disabled':true) { 'headers'('defaults-disabled':true) {
'hpkp'('report-uri':'http://example.net/pkp-report') { 'hpkp'('report-uri':'http://example.net/pkp-report') {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
then: then:
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"']) assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"'])
} }
// --- disable single default header --- // --- disable single default header ---
@ -688,23 +688,23 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
def 'http headers hpkp@disabled=true'() { def 'http headers hpkp@disabled=true'() {
setup: setup:
httpAutoConfig { httpAutoConfig {
'headers'() { 'headers'() {
'hpkp'(disabled:true) { 'hpkp'(disabled:true) {
'pins'() { 'pins'() {
'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=')
} }
} }
} }
} }
createAppContext() createAppContext()
def springSecurityFilterChain = appContext.getBean(FilterChainProxy) def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse() MockHttpServletResponse response = new MockHttpServletResponse()
def expectedHeaders = [:] << defaultHeaders def expectedHeaders = [:] << defaultHeaders
when: when:
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
then: then:
assertHeaders(response, expectedHeaders) assertHeaders(response, expectedHeaders)
} }
def 'http headers frame-options@disabled=true'() { def 'http headers frame-options@disabled=true'() {

View File

@ -3844,8 +3844,8 @@ Opposed to the other headers, Spring Security does not add HPKP by default. You
include-subdomains="true" include-subdomains="true"
report-uri="http://example.net/pkp-report"> report-uri="http://example.net/pkp-report">
<pins> <pins>
<pin algorithm="sha256">d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=</pin> <pin algorithm="sha256">d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=</pin>
<pin algorithm="sha256">E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=</pin> <pin algorithm="sha256">E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=</pin>
</pins> </pins>
</hpkp> </hpkp>
</headers> </headers>
@ -3860,16 +3860,16 @@ Similarly, you can enable HPKP headers with Java Configuration:
public class WebSecurityConfig extends public class WebSecurityConfig extends
WebSecurityConfigurerAdapter { WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http
// ... // ...
.headers() .headers()
.httpPublicKeyPinning() .httpPublicKeyPinning()
.includeSubdomains(true) .includeSubdomains(true)
.reportUri("http://example.net/pkp-report") .reportUri("http://example.net/pkp-report")
.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
} }
} }
---- ----

View File

@ -199,9 +199,11 @@ public final class HpkpHeaderWriter implements HeaderWriter {
* *
* Use * Use
* *
* Map<String, String> pins = new HashMap<String, String>(); * <code>
* Map&lt;String, String&gt; pins = new HashMap&lt;String, String&gt;();
* pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"); * pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256");
* pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256"); * pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256");
* </code>
* </p> * </p>
* *
* @param pins the map of base64-encoded SPKI fingerprint &amp; cryptographic hash algorithm pairs. * @param pins the map of base64-encoded SPKI fingerprint &amp; cryptographic hash algorithm pairs.