rack-protection 3.1.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -7
- data/README.md +1 -1
- data/lib/rack/protection/authenticity_token.rb +1 -0
- data/lib/rack/protection/base.rb +1 -1
- data/lib/rack/protection/content_security_policy.rb +2 -2
- data/lib/rack/protection/cookie_tossing.rb +1 -1
- data/lib/rack/protection/frame_options.rb +1 -1
- data/lib/rack/protection/json_csrf.rb +1 -1
- data/lib/rack/protection/referrer_policy.rb +1 -1
- data/lib/rack/protection/strict_transport.rb +1 -1
- data/lib/rack/protection/version.rb +1 -1
- data/lib/rack/protection/xss_header.rb +2 -2
- data/lib/rack/protection.rb +2 -5
- data/rack-protection.gemspec +3 -4
- metadata +21 -37
- data/lib/rack/protection/encrypted_cookie.rb +0 -273
- data/lib/rack/protection/encryptor.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8762f8b7bc260c94e353c6c9eb9a24d3a0efe695cb5904f526a5a84094f567db
|
4
|
+
data.tar.gz: d3c7e05bf8bfea7c6b077025f330429f386b416bccce5ebfab2d91d0513e437b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48b9fffade45349249d1122c3acc8618f2cb8ca05cde4ce1959b063899faad7c5c433bf6481518beedd0a0dd2ff28c878b1f5898fc3f21629ea39233fa747dc6
|
7
|
+
data.tar.gz: ec3176d39b37dfdd97b4d230f98251681a93c6dddcb17db4659c998d8f296dc9fc97808a9b732bbf055d0d2b0504f9696f541b6d6864983b214df94ceff636a5
|
data/Gemfile
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
source 'https://rubygems.org'
|
4
|
-
|
4
|
+
gemspec
|
5
5
|
|
6
6
|
gem 'rake'
|
7
|
+
gem 'rspec', '~> 3'
|
8
|
+
gem 'rack-test'
|
7
9
|
|
8
10
|
rack_version = ENV['rack'].to_s
|
9
11
|
rack_version = nil if rack_version.empty? || (rack_version == 'stable')
|
10
12
|
rack_version = { github: 'rack/rack' } if rack_version == 'head'
|
11
13
|
gem 'rack', rack_version
|
12
|
-
|
13
|
-
gem 'sinatra', path: '..'
|
14
|
-
|
15
|
-
gemspec
|
16
|
-
|
17
|
-
gem 'rack-test', github: 'rack/rack-test'
|
data/README.md
CHANGED
data/lib/rack/protection/base.rb
CHANGED
@@ -74,7 +74,7 @@ module Rack
|
|
74
74
|
|
75
75
|
def deny(env)
|
76
76
|
warn env, "attack prevented by #{self.class}"
|
77
|
-
[options[:status], { '
|
77
|
+
[options[:status], { 'content-type' => 'text/plain' }, [options[:message]]]
|
78
78
|
end
|
79
79
|
|
80
80
|
def report(env)
|
@@ -26,7 +26,7 @@ module Rack
|
|
26
26
|
# https://scotthelme.co.uk/csp-cheat-sheet/
|
27
27
|
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
28
28
|
#
|
29
|
-
# Sets the '
|
29
|
+
# Sets the 'content-security-policy[-report-only]' header.
|
30
30
|
#
|
31
31
|
# Options: ContentSecurityPolicy configuration is a complex topic with
|
32
32
|
# several levels of support that has evolved over time.
|
@@ -71,7 +71,7 @@ module Rack
|
|
71
71
|
|
72
72
|
def call(env)
|
73
73
|
status, headers, body = @app.call(env)
|
74
|
-
header = options[:report_only] ? '
|
74
|
+
header = options[:report_only] ? 'content-security-policy-report-only' : 'content-security-policy'
|
75
75
|
headers[header] ||= csp_policy if html? headers
|
76
76
|
[status, headers, body]
|
77
77
|
end
|
@@ -51,7 +51,7 @@ module Rack
|
|
51
51
|
def redirect(env)
|
52
52
|
request = Request.new(env)
|
53
53
|
warn env, "attack prevented by #{self.class}"
|
54
|
-
[302, { '
|
54
|
+
[302, { 'content-type' => 'text/html', 'location' => request.path }, []]
|
55
55
|
end
|
56
56
|
|
57
57
|
def bad_cookies
|
@@ -39,7 +39,7 @@ module Rack
|
|
39
39
|
def has_vector?(request, headers)
|
40
40
|
return false if request.xhr?
|
41
41
|
return false if options[:allow_if]&.call(request.env)
|
42
|
-
return false unless headers['
|
42
|
+
return false unless headers['content-type'].to_s.split(';', 2).first =~ %r{^\s*application/json\s*$}
|
43
43
|
|
44
44
|
origin(request.env).nil? and referrer(request.env) != request.host
|
45
45
|
end
|
@@ -18,8 +18,8 @@ module Rack
|
|
18
18
|
|
19
19
|
def call(env)
|
20
20
|
status, headers, body = @app.call(env)
|
21
|
-
headers['
|
22
|
-
headers['
|
21
|
+
headers['x-xss-protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers
|
22
|
+
headers['x-content-type-options'] ||= 'nosniff' if options[:nosniff]
|
23
23
|
[status, headers, body]
|
24
24
|
end
|
25
25
|
end
|
data/lib/rack/protection.rb
CHANGED
@@ -9,8 +9,6 @@ module Rack
|
|
9
9
|
autoload :Base, 'rack/protection/base'
|
10
10
|
autoload :CookieTossing, 'rack/protection/cookie_tossing'
|
11
11
|
autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy'
|
12
|
-
autoload :Encryptor, 'rack/protection/encryptor'
|
13
|
-
autoload :EncryptedCookie, 'rack/protection/encrypted_cookie'
|
14
12
|
autoload :EscapedParams, 'rack/protection/escaped_params'
|
15
13
|
autoload :FormToken, 'rack/protection/form_token'
|
16
14
|
autoload :FrameOptions, 'rack/protection/frame_options'
|
@@ -26,12 +24,11 @@ module Rack
|
|
26
24
|
autoload :XSSHeader, 'rack/protection/xss_header'
|
27
25
|
|
28
26
|
def self.new(app, options = {})
|
29
|
-
# does not include: RemoteReferrer, AuthenticityToken and FormToken
|
30
27
|
except = Array options[:except]
|
31
28
|
use_these = Array options[:use]
|
32
29
|
|
33
30
|
if options.fetch(:without_session, false)
|
34
|
-
except += %i[
|
31
|
+
except += %i[remote_token]
|
35
32
|
end
|
36
33
|
|
37
34
|
Rack::Builder.new do
|
@@ -43,6 +40,7 @@ module Rack
|
|
43
40
|
use ::Rack::Protection::FormToken, options if use_these.include? :form_token
|
44
41
|
use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy
|
45
42
|
use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
|
43
|
+
use ::Rack::Protection::SessionHijacking, options if use_these.include? :session_hijacking
|
46
44
|
use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport
|
47
45
|
|
48
46
|
# On by default, unless skipped
|
@@ -52,7 +50,6 @@ module Rack
|
|
52
50
|
use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf
|
53
51
|
use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal
|
54
52
|
use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token
|
55
|
-
use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking
|
56
53
|
use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header
|
57
54
|
run app
|
58
55
|
end.to_app
|
data/rack-protection.gemspec
CHANGED
@@ -36,10 +36,9 @@ RubyGems 2.0 or newer is required to protect against public gem pushes. You can
|
|
36
36
|
'rubygems_mfa_required' => 'true'
|
37
37
|
}
|
38
38
|
|
39
|
-
s.required_ruby_version = '>= 2.
|
39
|
+
s.required_ruby_version = '>= 2.7.8'
|
40
40
|
|
41
41
|
# dependencies
|
42
|
-
s.add_dependency '
|
43
|
-
s.
|
44
|
-
s.add_development_dependency 'rspec', '~> 3'
|
42
|
+
s.add_dependency 'base64', '>= 0.1.0'
|
43
|
+
s.add_dependency 'rack', '>= 3.0.0', '< 4'
|
45
44
|
end
|
metadata
CHANGED
@@ -1,63 +1,49 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-protection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/sinatra/sinatra/graphs/contributors
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: base64
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.2'
|
20
17
|
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
19
|
+
version: 0.1.0
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '2.2'
|
30
24
|
- - ">="
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
26
|
+
version: 0.1.0
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
|
-
name: rack
|
28
|
+
name: rack
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- - "
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '2'
|
40
|
-
type: :development
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
31
|
+
- - ">="
|
45
32
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
47
|
-
-
|
48
|
-
name: rspec
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
33
|
+
version: 3.0.0
|
34
|
+
- - "<"
|
52
35
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
54
|
-
type: :
|
36
|
+
version: '4'
|
37
|
+
type: :runtime
|
55
38
|
prerelease: false
|
56
39
|
version_requirements: !ruby/object:Gem::Requirement
|
57
40
|
requirements:
|
58
|
-
- - "
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.0.0
|
44
|
+
- - "<"
|
59
45
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
46
|
+
version: '4'
|
61
47
|
description: Protect against typical web attacks, works with all Rack apps, including
|
62
48
|
Rails
|
63
49
|
email: sinatrarb@googlegroups.com
|
@@ -75,8 +61,6 @@ files:
|
|
75
61
|
- lib/rack/protection/base.rb
|
76
62
|
- lib/rack/protection/content_security_policy.rb
|
77
63
|
- lib/rack/protection/cookie_tossing.rb
|
78
|
-
- lib/rack/protection/encrypted_cookie.rb
|
79
|
-
- lib/rack/protection/encryptor.rb
|
80
64
|
- lib/rack/protection/escaped_params.rb
|
81
65
|
- lib/rack/protection/form_token.rb
|
82
66
|
- lib/rack/protection/frame_options.rb
|
@@ -101,7 +85,7 @@ metadata:
|
|
101
85
|
homepage_uri: http://sinatrarb.com/protection/
|
102
86
|
documentation_uri: https://www.rubydoc.info/gems/rack-protection
|
103
87
|
rubygems_mfa_required: 'true'
|
104
|
-
post_install_message:
|
88
|
+
post_install_message:
|
105
89
|
rdoc_options: []
|
106
90
|
require_paths:
|
107
91
|
- lib
|
@@ -109,15 +93,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
93
|
requirements:
|
110
94
|
- - ">="
|
111
95
|
- !ruby/object:Gem::Version
|
112
|
-
version: 2.
|
96
|
+
version: 2.7.8
|
113
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
98
|
requirements:
|
115
99
|
- - ">="
|
116
100
|
- !ruby/object:Gem::Version
|
117
101
|
version: '0'
|
118
102
|
requirements: []
|
119
|
-
rubygems_version: 3.
|
120
|
-
signing_key:
|
103
|
+
rubygems_version: 3.5.3
|
104
|
+
signing_key:
|
121
105
|
specification_version: 4
|
122
106
|
summary: Protect against typical web attacks, works with all Rack apps, including
|
123
107
|
Rails.
|
@@ -1,273 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'openssl'
|
4
|
-
require 'zlib'
|
5
|
-
require 'json'
|
6
|
-
require 'rack/request'
|
7
|
-
require 'rack/response'
|
8
|
-
require 'rack/session/abstract/id'
|
9
|
-
|
10
|
-
module Rack
|
11
|
-
module Protection
|
12
|
-
# Rack::Protection::EncryptedCookie provides simple cookie based session management.
|
13
|
-
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
|
14
|
-
# data set to :key (default: rack.session). The object that encodes the
|
15
|
-
# session data is configurable and must respond to +encode+ and +decode+.
|
16
|
-
# Both methods must take a string and return a string.
|
17
|
-
#
|
18
|
-
# When the secret key is set, cookie data is checked for data integrity.
|
19
|
-
# The old_secret key is also accepted and allows graceful secret rotation.
|
20
|
-
# A legacy_hmac_secret is also accepted and is used to upgrade existing
|
21
|
-
# sessions to the new encryption scheme.
|
22
|
-
#
|
23
|
-
# There is also a legacy_hmac_coder option which can be set if a non-default
|
24
|
-
# coder was used for legacy session cookies.
|
25
|
-
#
|
26
|
-
# Example:
|
27
|
-
#
|
28
|
-
# use Rack::Protection::EncryptedCookie,
|
29
|
-
# :key => 'rack.session',
|
30
|
-
# :domain => 'foo.com',
|
31
|
-
# :path => '/',
|
32
|
-
# :expire_after => 2592000,
|
33
|
-
# :secret => 'change_me',
|
34
|
-
# :old_secret => 'old_secret'
|
35
|
-
#
|
36
|
-
# All parameters are optional.
|
37
|
-
#
|
38
|
-
# Example using legacy HMAC options
|
39
|
-
#
|
40
|
-
# Rack::Protection:EncryptedCookie.new(application, {
|
41
|
-
# # The secret used for legacy HMAC cookies
|
42
|
-
# legacy_hmac_secret: 'legacy secret',
|
43
|
-
# # legacy_hmac_coder will default to Rack::Protection::EncryptedCookie::Base64::Marshal
|
44
|
-
# legacy_hmac_coder: Rack::Protection::EncryptedCookie::Identity.new,
|
45
|
-
# # legacy_hmac will default to OpenSSL::Digest::SHA1
|
46
|
-
# legacy_hmac: OpenSSL::Digest::SHA256
|
47
|
-
# })
|
48
|
-
#
|
49
|
-
# Example of a cookie with no encoding:
|
50
|
-
#
|
51
|
-
# Rack::Protection::EncryptedCookie.new(application, {
|
52
|
-
# :coder => Rack::Protection::EncryptedCookie::Identity.new
|
53
|
-
# })
|
54
|
-
#
|
55
|
-
# Example of a cookie with custom encoding:
|
56
|
-
#
|
57
|
-
# Rack::Protection::EncryptedCookie.new(application, {
|
58
|
-
# :coder => Class.new {
|
59
|
-
# def encode(str); str.reverse; end
|
60
|
-
# def decode(str); str.reverse; end
|
61
|
-
# }.new
|
62
|
-
# })
|
63
|
-
#
|
64
|
-
class EncryptedCookie < Rack::Session::Abstract::Persisted
|
65
|
-
# Encode session cookies as Base64
|
66
|
-
class Base64
|
67
|
-
def encode(str)
|
68
|
-
[str].pack('m0')
|
69
|
-
end
|
70
|
-
|
71
|
-
def decode(str)
|
72
|
-
str.unpack1('m')
|
73
|
-
end
|
74
|
-
|
75
|
-
# Encode session cookies as Marshaled Base64 data
|
76
|
-
class Marshal < Base64
|
77
|
-
def encode(str)
|
78
|
-
super(::Marshal.dump(str))
|
79
|
-
end
|
80
|
-
|
81
|
-
def decode(str)
|
82
|
-
return unless str
|
83
|
-
|
84
|
-
begin
|
85
|
-
::Marshal.load(super(str))
|
86
|
-
rescue StandardError
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# N.B. Unlike other encoding methods, the contained objects must be a
|
93
|
-
# valid JSON composite type, either a Hash or an Array.
|
94
|
-
class JSON < Base64
|
95
|
-
def encode(obj)
|
96
|
-
super(::JSON.dump(obj))
|
97
|
-
end
|
98
|
-
|
99
|
-
def decode(str)
|
100
|
-
return unless str
|
101
|
-
|
102
|
-
begin
|
103
|
-
::JSON.parse(super(str))
|
104
|
-
rescue StandardError
|
105
|
-
nil
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
class ZipJSON < Base64
|
111
|
-
def encode(obj)
|
112
|
-
super(Zlib::Deflate.deflate(::JSON.dump(obj)))
|
113
|
-
end
|
114
|
-
|
115
|
-
def decode(str)
|
116
|
-
return unless str
|
117
|
-
|
118
|
-
::JSON.parse(Zlib::Inflate.inflate(super(str)))
|
119
|
-
rescue StandardError
|
120
|
-
nil
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# Use no encoding for session cookies
|
126
|
-
class Identity
|
127
|
-
def encode(str); str; end
|
128
|
-
def decode(str); str; end
|
129
|
-
end
|
130
|
-
|
131
|
-
class Marshal
|
132
|
-
def encode(str)
|
133
|
-
::Marshal.dump(str)
|
134
|
-
end
|
135
|
-
|
136
|
-
def decode(str)
|
137
|
-
::Marshal.load(str) if str
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
attr_reader :coder
|
142
|
-
|
143
|
-
def initialize(app, options = {})
|
144
|
-
# Assume keys are hex strings and convert them to raw byte strings for
|
145
|
-
# actual key material
|
146
|
-
@secrets = options.values_at(:secret, :old_secret).compact.map do |secret|
|
147
|
-
[secret].pack('H*')
|
148
|
-
end
|
149
|
-
|
150
|
-
warn <<-MSG unless secure?(options)
|
151
|
-
SECURITY WARNING: No secret option provided to Rack::Protection::EncryptedCookie.
|
152
|
-
This poses a security threat. It is strongly recommended that you
|
153
|
-
provide a secret to prevent exploits that may be possible from crafted
|
154
|
-
cookies. This will not be supported in future versions of Rack, and
|
155
|
-
future versions will even invalidate your existing user cookies.
|
156
|
-
|
157
|
-
Called from: #{caller[0]}.
|
158
|
-
MSG
|
159
|
-
|
160
|
-
warn <<-MSG if @secrets.first && @secrets.first.length < 32
|
161
|
-
SECURITY WARNING: Your secret is not long enough. It must be at least
|
162
|
-
32 bytes long and securely random. To generate such a key for use
|
163
|
-
you can run the following command:
|
164
|
-
|
165
|
-
ruby -rsecurerandom -e 'p SecureRandom.hex(32)'
|
166
|
-
|
167
|
-
Called from: #{caller[0]}.
|
168
|
-
MSG
|
169
|
-
|
170
|
-
if options.key?(:legacy_hmac_secret)
|
171
|
-
@legacy_hmac = options.fetch(:legacy_hmac, OpenSSL::Digest::SHA1)
|
172
|
-
|
173
|
-
# Multiply the :digest_length: by 2 because this value is the length of
|
174
|
-
# the digest in bytes but session digest strings are encoded as hex
|
175
|
-
# strings
|
176
|
-
@legacy_hmac_length = @legacy_hmac.new.digest_length * 2
|
177
|
-
@legacy_hmac_secret = options[:legacy_hmac_secret]
|
178
|
-
@legacy_hmac_coder = (options[:legacy_hmac_coder] ||= Base64::Marshal.new)
|
179
|
-
else
|
180
|
-
@legacy_hmac = false
|
181
|
-
end
|
182
|
-
|
183
|
-
# If encryption is used we can just use a default Marshal encoder
|
184
|
-
# without Base64 encoding the results.
|
185
|
-
#
|
186
|
-
# If no encryption is used, rely on the previous default (Base64::Marshal)
|
187
|
-
@coder = (options[:coder] ||= (@secrets.any? ? Marshal.new : Base64::Marshal.new))
|
188
|
-
|
189
|
-
super(app, options.merge!(cookie_only: true))
|
190
|
-
end
|
191
|
-
|
192
|
-
private
|
193
|
-
|
194
|
-
def find_session(req, _sid)
|
195
|
-
data = unpacked_cookie_data(req)
|
196
|
-
data = persistent_session_id!(data)
|
197
|
-
[data['session_id'], data]
|
198
|
-
end
|
199
|
-
|
200
|
-
def extract_session_id(request)
|
201
|
-
unpacked_cookie_data(request)['session_id']
|
202
|
-
end
|
203
|
-
|
204
|
-
def unpacked_cookie_data(request)
|
205
|
-
request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
|
206
|
-
session_data = cookie_data = request.cookies[@key]
|
207
|
-
|
208
|
-
# Try to decrypt with the first secret, if that returns nil, try
|
209
|
-
# with old_secret
|
210
|
-
unless @secrets.empty?
|
211
|
-
session_data = Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets.first)
|
212
|
-
session_data ||= Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets[1]) if @secrets.size > 1
|
213
|
-
end
|
214
|
-
|
215
|
-
# If session_data is still nil, are there is a legacy HMAC
|
216
|
-
# configured, try verify and parse the cookie that way
|
217
|
-
if !session_data && @legacy_hmac
|
218
|
-
digest = cookie_data.slice!(-@legacy_hmac_length..-1)
|
219
|
-
cookie_data.slice!(-2..-1) # remove double dash
|
220
|
-
session_data = cookie_data if digest_match?(cookie_data, digest)
|
221
|
-
|
222
|
-
# Decode using legacy HMAC decoder
|
223
|
-
request.set_header(k, @legacy_hmac_coder.decode(session_data) || {})
|
224
|
-
else
|
225
|
-
request.set_header(k, coder.decode(session_data) || {})
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def persistent_session_id!(data, sid = nil)
|
231
|
-
data ||= {}
|
232
|
-
data['session_id'] ||= sid || generate_sid
|
233
|
-
data
|
234
|
-
end
|
235
|
-
|
236
|
-
def write_session(req, session_id, session, _options)
|
237
|
-
session = session.merge('session_id' => session_id)
|
238
|
-
session_data = coder.encode(session)
|
239
|
-
|
240
|
-
unless @secrets.empty?
|
241
|
-
session_data = Rack::Protection::Encryptor.encrypt_message(session_data, @secrets.first)
|
242
|
-
end
|
243
|
-
|
244
|
-
if session_data.size > (4096 - @key.size)
|
245
|
-
req.get_header(RACK_ERRORS).puts('Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.')
|
246
|
-
nil
|
247
|
-
else
|
248
|
-
session_data
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
def delete_session(_req, _session_id, options)
|
253
|
-
# Nothing to do here, data is in the client
|
254
|
-
generate_sid unless options[:drop]
|
255
|
-
end
|
256
|
-
|
257
|
-
def digest_match?(data, digest)
|
258
|
-
return false unless data && digest
|
259
|
-
|
260
|
-
Rack::Utils.secure_compare(digest, generate_hmac(data))
|
261
|
-
end
|
262
|
-
|
263
|
-
def generate_hmac(data)
|
264
|
-
OpenSSL::HMAC.hexdigest(@legacy_hmac.new, @legacy_hmac_secret, data)
|
265
|
-
end
|
266
|
-
|
267
|
-
def secure?(options)
|
268
|
-
@secrets.size >= 1 ||
|
269
|
-
(options[:coder] && options[:let_coder_handle_secure_encoding])
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'openssl'
|
4
|
-
|
5
|
-
module Rack
|
6
|
-
module Protection
|
7
|
-
module Encryptor
|
8
|
-
CIPHER = 'aes-256-gcm'
|
9
|
-
DELIMITER = '--'
|
10
|
-
|
11
|
-
def self.base64_encode(str)
|
12
|
-
[str].pack('m0')
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.base64_decode(str)
|
16
|
-
str.unpack1('m0')
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.encrypt_message(data, secret, auth_data = '')
|
20
|
-
raise ArgumentError, 'data cannot be nil' if data.nil?
|
21
|
-
|
22
|
-
cipher = OpenSSL::Cipher.new(CIPHER)
|
23
|
-
cipher.encrypt
|
24
|
-
cipher.key = secret[0, cipher.key_len]
|
25
|
-
|
26
|
-
# Rely on OpenSSL for the initialization vector
|
27
|
-
iv = cipher.random_iv
|
28
|
-
|
29
|
-
# This must be set to properly use AES GCM for the OpenSSL module
|
30
|
-
cipher.auth_data = auth_data
|
31
|
-
|
32
|
-
cipher_text = cipher.update(data)
|
33
|
-
cipher_text << cipher.final
|
34
|
-
|
35
|
-
"#{base64_encode cipher_text}#{DELIMITER}#{base64_encode iv}#{DELIMITER}#{base64_encode cipher.auth_tag}"
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.decrypt_message(data, secret)
|
39
|
-
return unless data
|
40
|
-
|
41
|
-
cipher = OpenSSL::Cipher.new(CIPHER)
|
42
|
-
cipher_text, iv, auth_tag = data.split(DELIMITER, 3).map! { |v| base64_decode(v) }
|
43
|
-
|
44
|
-
# This check is from ActiveSupport::MessageEncryptor
|
45
|
-
# see: https://github.com/ruby/openssl/issues/63
|
46
|
-
return if auth_tag.nil? || auth_tag.bytes.length != 16
|
47
|
-
|
48
|
-
cipher.decrypt
|
49
|
-
cipher.key = secret[0, cipher.key_len]
|
50
|
-
cipher.iv = iv
|
51
|
-
cipher.auth_tag = auth_tag
|
52
|
-
cipher.auth_data = ''
|
53
|
-
|
54
|
-
decrypted_data = cipher.update(cipher_text)
|
55
|
-
decrypted_data << cipher.final
|
56
|
-
decrypted_data
|
57
|
-
rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|