diff --git a/README.md b/README.md index c57b4f4..4ec82d5 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Content-Security-Policy: script-src 'nonce-a2ZobGFoZg==' ~~~ ### Set the `IsDevelopment` option to `true` when developing! -When `IsDevelopment` is true, the AllowedHosts, SSLRedirect, STS header, and HPKP header will not be in effect. This allows you to work in development/test mode and not have any annoying redirects to HTTPS (ie. development can happen on HTTP), or block `localhost` has a bad host. +When `IsDevelopment` is true, the AllowedHosts, SSLRedirect, and STS header will not be in effect. This allows you to work in development/test mode and not have any annoying redirects to HTTPS (ie. development can happen on HTTP), or block `localhost` has a bad host. ### Available options Secure comes with a variety of configuration options (Note: these are not the default option values. See the defaults below.): @@ -80,12 +80,10 @@ s := secure.New(secure.Options{ BrowserXssFilter: true, // If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false. CustomBrowserXssValue: "1; report=https://example.com/xss-report", // CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "". ContentSecurityPolicy: "default-src 'self'", // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "". Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be later retrieved using the Nonce function. - PublicKey: `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`, // Deprecated: This feature is no longer recommended. PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "". ReferrerPolicy: "same-origin", // ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "". FeaturePolicy: "vibrate 'none';", // Deprecated: this header has been renamed to PermissionsPolicy. FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "". PermissionsPolicy: "fullscreen=(), geolocation=()", // PermissionsPolicy allows the Permissions-Policy header with the value to be set with a custom value. Default is "". CrossOriginOpenerPolicy: "same-origin", // CrossOriginOpenerPolicy allows the Cross-Origin-Opener-Policy header with the value to be set with a custom value. Default is "". - ExpectCTHeader: `enforce, max-age=30, report-uri="https://www.example.com/ct-report"`, IsDevelopment: true, // This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false. }) @@ -123,7 +121,6 @@ l := secure.New(secure.Options{ FeaturePolicy: "", PermissionsPolicy: "", CrossOriginOpenerPolicy: "", - ExpectCTHeader: "", IsDevelopment: false, }) ~~~ diff --git a/secure.go b/secure.go index 2c45f25..0efcc61 100644 --- a/secure.go +++ b/secure.go @@ -26,7 +26,6 @@ const ( referrerPolicyHeader = "Referrer-Policy" featurePolicyHeader = "Feature-Policy" permissionsPolicyHeader = "Permissions-Policy" - expectCTHeader = "Expect-CT" coopHeader = "Cross-Origin-Opener-Policy" ctxDefaultSecureHeaderKey = secureCtxKey("SecureResponseHeader") @@ -82,9 +81,6 @@ type Options struct { // Eg: script-src $NONCE -> script-src 'nonce-a2ZobGFoZg==' // CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option. Default is "". CustomFrameOptionsValue string - // PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "". - // Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible. - PublicKey string // ReferrerPolicy allows sites to control when browsers will pass the Referer header to other sites. Default is "". ReferrerPolicy string // FeaturePolicy allows to selectively enable and disable use of various browser features and APIs. Default is "". @@ -112,8 +108,6 @@ type Options struct { SSLProxyHeaders map[string]string // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header. STSSeconds int64 - // ExpectCTHeader allows the Expect-CT header value to be set with a custom value. Default is "". - ExpectCTHeader string // SecureContextKey allows a custom key to be specified for context storage. SecureContextKey string } @@ -434,11 +428,6 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He responseHeader.Set(xssProtectionHeader, xssProtectionValue) } - // HPKP header. - if len(s.opt.PublicKey) > 0 && ssl && !s.opt.IsDevelopment { - responseHeader.Set(hpkpHeader, s.opt.PublicKey) - } - // Content Security Policy header. if len(s.opt.ContentSecurityPolicy) > 0 { if s.opt.nonceEnabled { @@ -477,11 +466,6 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He responseHeader.Set(coopHeader, s.opt.CrossOriginOpenerPolicy) } - // Expect-CT header. - if len(s.opt.ExpectCTHeader) > 0 { - responseHeader.Set(expectCTHeader, s.opt.ExpectCTHeader) - } - return responseHeader, r, nil } diff --git a/secure_test.go b/secure_test.go index c3a4fc0..107ce71 100644 --- a/secure_test.go +++ b/secure_test.go @@ -969,121 +969,6 @@ func TestInlineSecureForRequestOnly(t *testing.T) { expect(t, res.Header().Get("X-Frame-Options"), "") } -// https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning -const hpkp = `pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="; max-age=5184000; includeSubdomains; report-uri="https://www.example.net/hpkp-report"` - -func TestHPKP(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - req.URL.Scheme = "https" - - s.Handler(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), hpkp) -} - -func TestHPKPForRequestOnly(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - req.URL.Scheme = "https" - - s.HandlerForRequestOnly(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPNotSet(t *testing.T) { - s := New() - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.Handler(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPNotSetForRequestOnly(t *testing.T) { - s := New() - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.HandlerForRequestOnly(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPInDevMode(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - IsDevelopment: true, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.Handler(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPInDevModeForRequestOnly(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - IsDevelopment: true, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.HandlerForRequestOnly(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPNonSSL(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.Handler(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - -func TestHPKPNonSSLForRequestOnly(t *testing.T) { - s := New(Options{ - PublicKey: hpkp, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - - s.HandlerForRequestOnly(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Public-Key-Pins"), "") -} - func TestReferrer(t *testing.T) { s := New(Options{ ReferrerPolicy: "same-origin", @@ -1154,21 +1039,6 @@ func TestCrossOriginOpenerPolicy(t *testing.T) { expect(t, res.Header().Get("Cross-Origin-Opener-Policy"), "same-origin") } -func TestExpectCT(t *testing.T) { - s := New(Options{ - ExpectCTHeader: `enforce, max-age=30, report-uri="https://www.example.com/ct-report"`, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/foo", nil) - req.Host = "www.example.com" - - s.Handler(myHandler).ServeHTTP(res, req) - - expect(t, res.Code, http.StatusOK) - expect(t, res.Header().Get("Expect-CT"), `enforce, max-age=30, report-uri="https://www.example.com/ct-report"`) -} - func TestIsSSL(t *testing.T) { s := New(Options{ SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},