secure_headers 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

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