secure_headers 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16e634b2502cd5ee9d87fac9a9d2c6af9bc48fda
4
+ data.tar.gz: 1416e09703db75cdf25379a1273477f346080c72
5
+ SHA512:
6
+ metadata.gz: d1483f86c255f59593766bf3312d0085df5b59281c2daf426c16bf930c92c06ae17c17e07984be696b1719336e89ea7000e19948713b310354c749200e2debd6
7
+ data.tar.gz: eaf23b06c98757048b1516d87e429a23b5c70606db476f90eb042250bbd863bd1fddfdbcf12a6fce51e077a0ee3e6332cedcc041514de6a0ce55295d77a4bd65
@@ -1 +1 @@
1
- ruby-1.9.3-p484
1
+ 2.1.6
data/README.md CHANGED
@@ -8,6 +8,7 @@ The gem will automatically apply several headers that are related to security.
8
8
  - X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
9
9
  - X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
10
10
  - X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
11
+ - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorites. [Public Key Pinnning Specification](https://tools.ietf.org/html/draft-ietf-websec-key-pinning-21)
11
12
 
12
13
  ## Usage
13
14
 
@@ -21,6 +22,7 @@ The following methods are going to be called, unless they are provided in a `ski
21
22
 
22
23
  * `:set_csp_header`
23
24
  * `:set_hsts_header`
25
+ * `:set_hpkp_header`
24
26
  * `:set_x_frame_options_header`
25
27
  * `:set_x_xss_protection_header`
26
28
  * `:set_x_content_type_options_header`
@@ -51,15 +53,24 @@ This gem makes a few assumptions about how you will use some features. For exam
51
53
  :img_src => "https:",
52
54
  :report_uri => '//example.com/uri-directive'
53
55
  }
56
+ config.hpkp = {
57
+ :max_age => 60.days.to_i,
58
+ :include_subdomains => true,
59
+ :report_uri => '//example.com/uri-directive',
60
+ :pins => [
61
+ {:sha256 => 'abc'},
62
+ {:sha256 => '123'}
63
+ ]
64
+ }
54
65
  end
55
66
 
56
- # and then simply include this in application_controller.rb
67
+ # and then include this in application_controller.rb
57
68
  class ApplicationController < ActionController::Base
58
69
  ensure_security_headers
59
70
  end
60
71
  ```
61
72
 
62
- Or simply add it to application controller
73
+ Or do the config as a parameter to `ensure_security_headers`
63
74
 
64
75
  ```ruby
65
76
  ensure_security_headers(
@@ -298,6 +309,26 @@ console.log("will raise an exception if not in script_hashes.yml!")
298
309
  <% end %>
299
310
  ```
300
311
 
312
+ ### Public Key Pins
313
+
314
+ Be aware that pinning error reporting is governed by the same rules as everything else. If you have a pinning failure that tries to report back to the same origin, by definition this will not work.
315
+
316
+ ```
317
+ config.hpkp = {
318
+ max_age: 60.days.to_i, # max_age is a required parameter
319
+ include_subdomains: true, # whether or not to apply pins to subdomains
320
+ # Per the spec, SHA256 hashes are the only currently supported format.
321
+ pins: [
322
+ {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
323
+ {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
324
+ ],
325
+ enforce: true, # defaults to false (report-only mode)
326
+ report_uri: '//example.com/uri-directive',
327
+ app_name: 'example',
328
+ tag_report_uri: true
329
+ }
330
+ ```
331
+
301
332
  ### Using with Sinatra
302
333
 
303
334
  Here's an example using SecureHeaders for Sinatra applications:
@@ -321,6 +352,7 @@ require 'secure_headers'
321
352
  :img_src => "https: data:",
322
353
  :frame_src => "https: http:.twimg.com http://itunes.apple.com"
323
354
  }
355
+ config.hpkp = false
324
356
  end
325
357
 
326
358
  class Donkey < Sinatra::Application
@@ -6,7 +6,7 @@ module SecureHeaders
6
6
  class << self
7
7
  attr_accessor :hsts, :x_frame_options, :x_content_type_options,
8
8
  :x_xss_protection, :csp, :x_download_options, :script_hashes,
9
- :x_permitted_cross_domain_policies
9
+ :x_permitted_cross_domain_policies, :hpkp
10
10
 
11
11
  def configure &block
12
12
  instance_eval &block
@@ -42,6 +42,7 @@ module SecureHeaders
42
42
  self.secure_headers_options = options
43
43
  before_filter :prep_script_hash
44
44
  before_filter :set_hsts_header
45
+ before_filter :set_hpkp_header
45
46
  before_filter :set_x_frame_options_header
46
47
  before_filter :set_csp_header
47
48
  before_filter :set_x_xss_protection_header
@@ -61,6 +62,7 @@ module SecureHeaders
61
62
  def set_security_headers(options = self.class.secure_headers_options)
62
63
  set_csp_header(request, options[:csp])
63
64
  set_hsts_header(options[:hsts])
65
+ set_hpkp_header(options[:hpkp])
64
66
  set_x_frame_options_header(options[:x_frame_options])
65
67
  set_x_xss_protection_header(options[:x_xss_protection])
66
68
  set_x_content_type_options_header(options[:x_content_type_options])
@@ -136,6 +138,16 @@ module SecureHeaders
136
138
  set_a_header(:hsts, StrictTransportSecurity, options)
137
139
  end
138
140
 
141
+ def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
142
+ return unless request.ssl?
143
+ config = self.class.options_for :hpkp, options
144
+
145
+ return if config == false || config.nil?
146
+
147
+ hpkp_header = PublicKeyPins.new(config)
148
+ set_header(hpkp_header)
149
+ end
150
+
139
151
  def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
140
152
  set_a_header(:x_download_options, XDownloadOptions, options)
141
153
  end
@@ -168,6 +180,7 @@ end
168
180
 
169
181
  require "secure_headers/version"
170
182
  require "secure_headers/header"
183
+ require "secure_headers/headers/public_key_pins"
171
184
  require "secure_headers/headers/content_security_policy"
172
185
  require "secure_headers/headers/x_frame_options"
173
186
  require "secure_headers/headers/strict_transport_security"
@@ -0,0 +1,95 @@
1
+ module SecureHeaders
2
+ class PublicKeyPinsBuildError < StandardError; end
3
+ class PublicKeyPins < Header
4
+ module Constants
5
+ HPKP_HEADER_NAME = "Public-Key-Pins"
6
+ ENV_KEY = 'secure_headers.public_key_pins'
7
+ HASH_ALGORITHMS = [:sha256]
8
+ DIRECTIVES = [:max_age]
9
+ end
10
+ class << self
11
+ def symbol_to_hyphen_case sym
12
+ sym.to_s.gsub('_', '-')
13
+ end
14
+ end
15
+ include Constants
16
+
17
+ def initialize(config=nil)
18
+ @config = validate_config(config)
19
+
20
+ @pins = @config.fetch(:pins, nil)
21
+ @report_uri = @config.fetch(:report_uri, nil)
22
+ @app_name = @config.fetch(:app_name, nil)
23
+ @enforce = !!@config.fetch(:enforce, nil)
24
+ @include_subdomains = !!@config.fetch(:include_subdomains, nil)
25
+ @tag_report_uri = !!@config.fetch(:tag_report_uri, nil)
26
+ end
27
+
28
+ def name
29
+ base = HPKP_HEADER_NAME
30
+ if !@enforce
31
+ base += "-Report-Only"
32
+ end
33
+ base
34
+ end
35
+
36
+ def value
37
+ header_value = [
38
+ generic_directives,
39
+ pin_directives,
40
+ report_uri_directive,
41
+ subdomain_directive
42
+ ].compact.join('; ').strip
43
+ end
44
+
45
+ def validate_config(config)
46
+ raise PublicKeyPinsBuildError.new("config must be a hash.") unless config.is_a? Hash
47
+
48
+ if !config[:max_age]
49
+ raise PublicKeyPinsBuildError.new("max-age is a required directive.")
50
+ elsif config[:max_age].to_s !~ /\A\d+\z/
51
+ raise PublicKeyPinsBuildError.new("max-age must be a number.
52
+ #{config[:max_age]} was supplied.")
53
+ elsif config[:pins] && config[:pins].length < 2
54
+ raise PublicKeyPinsBuildError.new("A minimum of 2 pins are required.")
55
+ end
56
+
57
+ config
58
+ end
59
+
60
+ def pin_directives
61
+ return nil if @pins.nil?
62
+ @pins.collect do |pin|
63
+ pin.map do |token, hash|
64
+ "pin-#{token}=\"#{hash}\"" if HASH_ALGORITHMS.include?(token)
65
+ end
66
+ end.join('; ')
67
+ end
68
+
69
+ def generic_directives
70
+ DIRECTIVES.collect do |directive_name|
71
+ build_directive(directive_name) if @config[directive_name]
72
+ end.join('; ')
73
+ end
74
+
75
+ def build_directive(key)
76
+ "#{self.class.symbol_to_hyphen_case(key)}=#{@config[key]}"
77
+ end
78
+
79
+ def report_uri_directive
80
+ return nil if @report_uri.nil?
81
+
82
+ if @tag_report_uri
83
+ @report_uri = "#{@report_uri}?enforce=#{@enforce}"
84
+ @report_uri += "&app_name=#{@app_name}" if @app_name
85
+ end
86
+
87
+ "report-uri=\"#{@report_uri}\""
88
+ end
89
+
90
+
91
+ def subdomain_directive
92
+ @include_subdomains ? 'includeSubDomains' : nil
93
+ end
94
+ end
95
+ end
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "2.0.2"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe PublicKeyPins do
5
+ specify{ expect(PublicKeyPins.new(:max_age => 1234).name).to eq("Public-Key-Pins-Report-Only") }
6
+ specify{ expect(PublicKeyPins.new(:max_age => 1234, :enforce => true).name).to eq("Public-Key-Pins") }
7
+
8
+ specify { expect(PublicKeyPins.new({:max_age => 1234}).value).to eq("max-age=1234")}
9
+ specify { expect(PublicKeyPins.new(:max_age => 1234).value).to eq("max-age=1234")}
10
+ specify {
11
+ config = {:max_age => 1234, :pins => [{:sha256 => 'base64encodedpin1'}, {:sha256 => 'base64encodedpin2'}]}
12
+ header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
13
+ expect(PublicKeyPins.new(config).value).to eq(header_value)
14
+ }
15
+
16
+ context "with an invalid configuration" do
17
+ it "raises an exception when max-age is not provided" do
18
+ expect {
19
+ PublicKeyPins.new(:foo => 'bar')
20
+ }.to raise_error(PublicKeyPinsBuildError)
21
+ end
22
+
23
+ it "raises an exception with an invalid max-age" do
24
+ expect {
25
+ PublicKeyPins.new(:max_age => 'abc123')
26
+ }.to raise_error(PublicKeyPinsBuildError)
27
+ end
28
+
29
+ it 'raises an exception with less than 2 pins' do
30
+ expect {
31
+ config = {:max_age => 1234, :pins => [{:sha256 => 'base64encodedpin'}]}
32
+ PublicKeyPins.new(config)
33
+ }.to raise_error(PublicKeyPinsBuildError)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -24,6 +24,7 @@ describe SecureHeaders do
24
24
 
25
25
  def reset_config
26
26
  ::SecureHeaders::Configuration.configure do |config|
27
+ config.hpkp = nil
27
28
  config.hsts = nil
28
29
  config.x_frame_options = nil
29
30
  config.x_content_type_options = nil
@@ -36,6 +37,7 @@ describe SecureHeaders do
36
37
 
37
38
  def set_security_headers(subject)
38
39
  subject.set_csp_header
40
+ subject.set_hpkp_header
39
41
  subject.set_hsts_header
40
42
  subject.set_x_frame_options_header
41
43
  subject.set_x_content_type_options_header
@@ -65,6 +67,7 @@ describe SecureHeaders do
65
67
  subject.set_csp_header
66
68
  subject.set_x_frame_options_header
67
69
  subject.set_hsts_header
70
+ subject.set_hpkp_header
68
71
  subject.set_x_xss_protection_header
69
72
  subject.set_x_content_type_options_header
70
73
  subject.set_x_download_options_header
@@ -109,6 +112,17 @@ describe SecureHeaders do
109
112
  subject.set_hsts_header({:include_subdomains => true})
110
113
  end
111
114
 
115
+ it "does not set the HPKP header if disabled" do
116
+ should_not_assign_header(HPKP_HEADER_NAME)
117
+ subject.set_hpkp_header
118
+ end
119
+
120
+ it "does not set the HPKP header if request is over HTTP" do
121
+ allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
122
+ should_not_assign_header(HPKP_HEADER_NAME)
123
+ subject.set_hpkp_header(:max_age => 1234)
124
+ end
125
+
112
126
  it "does not set the CSP header if disabled" do
113
127
  stub_user_agent(USER_AGENTS[:chrome])
114
128
  should_not_assign_header(HEADER_NAME)
@@ -130,6 +144,7 @@ describe SecureHeaders do
130
144
  it "does not set any headers when disabled" do
131
145
  ::SecureHeaders::Configuration.configure do |config|
132
146
  config.hsts = false
147
+ config.hpkp = false
133
148
  config.x_frame_options = false
134
149
  config.x_content_type_options = false
135
150
  config.x_xss_protection = false
@@ -190,6 +205,38 @@ describe SecureHeaders do
190
205
  end
191
206
  end
192
207
 
208
+ describe "#set_public_key_pins" do
209
+ it "sets the Public-Key-Pins header" do
210
+ should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
211
+ subject.set_hpkp_header(:max_age => 1234)
212
+ end
213
+
214
+ it "allows you to enforce public key pinning" do
215
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
216
+ subject.set_hpkp_header(:max_age => 1234, :enforce => true)
217
+ end
218
+
219
+ it "allows you to specific a custom max-age value" do
220
+ should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
221
+ subject.set_hpkp_header(:max_age => 1234)
222
+ end
223
+
224
+ it "allows you to specify includeSubdomains" do
225
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
226
+ subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
227
+ end
228
+
229
+ it "allows you to specify a report-uri" do
230
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
231
+ subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
232
+ end
233
+
234
+ it "allows you to specify a report-uri with app_name" do
235
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
236
+ subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
237
+ end
238
+ end
239
+
193
240
  describe "#set_x_xss_protection" do
194
241
  it "sets the X-XSS-Protection header" do
195
242
  should_assign_header(X_XSS_PROTECTION_HEADER_NAME, SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
@@ -7,6 +7,7 @@ if defined?(Coveralls)
7
7
  Coveralls.wear!
8
8
  end
9
9
 
10
+ include ::SecureHeaders::PublicKeyPins::Constants
10
11
  include ::SecureHeaders::StrictTransportSecurity::Constants
11
12
  include ::SecureHeaders::ContentSecurityPolicy::Constants
12
13
  include ::SecureHeaders::XFrameOptions::Constants
metadata CHANGED
@@ -1,30 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
5
- prerelease:
4
+ version: 2.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Neil Matatall
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-05-05 00:00:00.000000000 Z
11
+ date: 2015-05-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  description: Security related headers all in one gem.
@@ -34,10 +31,10 @@ executables: []
34
31
  extensions: []
35
32
  extra_rdoc_files: []
36
33
  files:
37
- - .gitignore
38
- - .ruby-gemset
39
- - .ruby-version
40
- - .travis.yml
34
+ - ".gitignore"
35
+ - ".ruby-gemset"
36
+ - ".ruby-version"
37
+ - ".travis.yml"
41
38
  - Gemfile
42
39
  - LICENSE
43
40
  - README.md
@@ -130,6 +127,7 @@ files:
130
127
  - lib/secure_headers/header.rb
131
128
  - lib/secure_headers/headers/content_security_policy.rb
132
129
  - lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb
130
+ - lib/secure_headers/headers/public_key_pins.rb
133
131
  - lib/secure_headers/headers/strict_transport_security.rb
134
132
  - lib/secure_headers/headers/x_content_type_options.rb
135
133
  - lib/secure_headers/headers/x_download_options.rb
@@ -144,6 +142,7 @@ files:
144
142
  - secure_headers.gemspec
145
143
  - spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb
146
144
  - spec/lib/secure_headers/headers/content_security_policy_spec.rb
145
+ - spec/lib/secure_headers/headers/public_key_pins_spec.rb
147
146
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
148
147
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
149
148
  - spec/lib/secure_headers/headers/x_download_options_spec.rb
@@ -156,32 +155,32 @@ files:
156
155
  homepage: https://github.com/twitter/secureheaders
157
156
  licenses:
158
157
  - Apache Public License 2.0
158
+ metadata: {}
159
159
  post_install_message:
160
160
  rdoc_options: []
161
161
  require_paths:
162
162
  - lib
163
163
  required_ruby_version: !ruby/object:Gem::Requirement
164
- none: false
165
164
  requirements:
166
- - - ! '>='
165
+ - - ">="
167
166
  - !ruby/object:Gem::Version
168
167
  version: '0'
169
168
  required_rubygems_version: !ruby/object:Gem::Requirement
170
- none: false
171
169
  requirements:
172
- - - ! '>='
170
+ - - ">="
173
171
  - !ruby/object:Gem::Version
174
172
  version: '0'
175
173
  requirements: []
176
174
  rubyforge_project:
177
- rubygems_version: 1.8.23
175
+ rubygems_version: 2.2.3
178
176
  signing_key:
179
- specification_version: 3
177
+ specification_version: 4
180
178
  summary: Add easily configured security headers to responses including content-security-policy,
181
179
  x-frame-options, strict-transport-security, etc.
182
180
  test_files:
183
181
  - spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb
184
182
  - spec/lib/secure_headers/headers/content_security_policy_spec.rb
183
+ - spec/lib/secure_headers/headers/public_key_pins_spec.rb
185
184
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
186
185
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
187
186
  - spec/lib/secure_headers/headers/x_download_options_spec.rb