linzer 0.6.5 → 0.7.0.beta1
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.
- checksums.yaml +4 -4
- data/.standard.yml +1 -0
- data/CHANGELOG.md +4 -0
- data/README.md +27 -0
- data/examples/sinatra/Gemfile +9 -0
- data/examples/sinatra/config.ru +11 -0
- data/examples/sinatra/http-signatures.yml +26 -0
- data/examples/sinatra/myapp.rb +10 -0
- data/lib/linzer/version.rb +1 -1
- data/lib/linzer.rb +1 -0
- data/lib/rack/auth/signature.rb +206 -0
- metadata +28 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a19b05f41aa933779f504056b56b00fc1a8a6cda19bb26065dce56177caf0c2
|
4
|
+
data.tar.gz: 25d7d8b4afbd60a3e6e872064d92f9be22670466292e23a6ba8010ca546cdf23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 527fd9a73a1605b706fc7ca597ce14f4f5c95be099907bf61832aa698b11fe51dbf25a5eaf70d48d7c6715c91c27bfa391482a88a67f0e932c5f60ff8f3f16df
|
7
|
+
data.tar.gz: 2b30454801734973711afe81559ad18f09f6d08f473796241d56e1b13d5619971a697945703b85012e18ae969059ca5a3718e677fc71716151bd46e795eac8a7
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -21,6 +21,33 @@ Or just `gem install linzer`.
|
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
|
+
### TL;DR: I just want to protect my application!!
|
25
|
+
|
26
|
+
Add the following middleware to you run Rack application, e.g.:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# config.ru
|
30
|
+
use Rack::Auth::Signature, except: "/login",
|
31
|
+
default_key: {"key" => IO.read("app/config/pubkey.pem"), "alg" => "ed25519"}
|
32
|
+
```
|
33
|
+
|
34
|
+
or on more complex scenarios:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# config.ru
|
38
|
+
use Rack::Auth::Signature, except: "/login",
|
39
|
+
config_path: "app/configuration/http-signatures.yml"
|
40
|
+
```
|
41
|
+
|
42
|
+
And that's it, all routes in the example app (except `/login`) above will
|
43
|
+
require a valid signature created with the respective private key held by a
|
44
|
+
client. For more details on what configuration options are available, take a
|
45
|
+
look at
|
46
|
+
[examples/sinatra/http-signatures.yml](https://github.com/nomadium/linzer/tree/master/examples/sinatra/http-signatures.yml) to get started and/or
|
47
|
+
[lib/rack/auth/signature.rb](https://github.com/nomadium/linzer/tree/master/lib/rack/auth/signature.rb) for full implementation details.
|
48
|
+
|
49
|
+
To learn about more specific scenarios or use cases, keep reading on below.
|
50
|
+
|
24
51
|
### To sign a HTTP message:
|
25
52
|
|
26
53
|
```ruby
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
no_older_than: 6000
|
3
|
+
created_required: true
|
4
|
+
keyid_required: true
|
5
|
+
# nonce_required: false
|
6
|
+
# alg_required: false
|
7
|
+
# tag_required: false
|
8
|
+
# expires_required: false
|
9
|
+
covered_components:
|
10
|
+
- "@method"
|
11
|
+
- "@request-target"
|
12
|
+
- date
|
13
|
+
known_keys:
|
14
|
+
foo:
|
15
|
+
alg: ed25519
|
16
|
+
key: |
|
17
|
+
-----BEGIN PUBLIC KEY-----
|
18
|
+
MCowBQYDK2VwAyEAMEH9bSanwgAWE5qxUEaXjK6qei8z2hiHT0nlr7ljG0Y=
|
19
|
+
-----END PUBLIC KEY-----
|
20
|
+
bar:
|
21
|
+
alg: hmac-sha256
|
22
|
+
key: !binary |-
|
23
|
+
KuOR/Q8U1Crgp0WsFqW+5ZuC+KbN41dIlXWp71UhPEeJcRfuxZEy6XAUE9nf0yl4jt55yRASBnD0kQKucO6SfA==
|
24
|
+
baz:
|
25
|
+
alg: rsa-pss-sha512
|
26
|
+
path: rsa.pem
|
data/lib/linzer/version.rb
CHANGED
data/lib/linzer.rb
CHANGED
@@ -0,0 +1,206 @@
|
|
1
|
+
require "linzer"
|
2
|
+
require "logger"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
# Rack::Auth::Signature implements HTTP Message Signatures, as per RFC 9421.
|
6
|
+
#
|
7
|
+
# Initialize with the Rack application that you want protecting.
|
8
|
+
# A hash with options and a block can be passed to customize, enhance
|
9
|
+
# or disable security checks applied to incoming requests.
|
10
|
+
#
|
11
|
+
module Rack
|
12
|
+
module Auth
|
13
|
+
class Signature
|
14
|
+
def initialize(app, options = {}, &block)
|
15
|
+
@app = app
|
16
|
+
@options = load_options(options)
|
17
|
+
instance_eval(&block) if block
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
@request = Rack::Request.new(env)
|
22
|
+
|
23
|
+
if excluded? || allowed?
|
24
|
+
@app.call(env)
|
25
|
+
else
|
26
|
+
response = options[:error_response].values
|
27
|
+
Rack::Response.new(*response).finish
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def excluded?
|
34
|
+
return false if !request
|
35
|
+
Array(options[:except]).include?(request.path_info)
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed?
|
39
|
+
has_signature? && acceptable? && verifiable?
|
40
|
+
end
|
41
|
+
|
42
|
+
DEFAULT_OPTIONS = {
|
43
|
+
no_older_than: 900,
|
44
|
+
created_required: true,
|
45
|
+
nonce_required: false,
|
46
|
+
alg_required: false,
|
47
|
+
tag_required: false,
|
48
|
+
expires_required: false,
|
49
|
+
keyid_required: false,
|
50
|
+
known_keys: {},
|
51
|
+
covered_components: %w[@method @request-target @authority date],
|
52
|
+
error_response: {body: [], status: 401, headers: {}}
|
53
|
+
}
|
54
|
+
|
55
|
+
private_constant :DEFAULT_OPTIONS
|
56
|
+
|
57
|
+
attr_reader :request, :options
|
58
|
+
|
59
|
+
def params
|
60
|
+
@signature.parameters || {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def logger
|
64
|
+
@logger ||= request.logger || ::Logger.new($stderr)
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_signature?
|
68
|
+
@message = Linzer::Message.new(request)
|
69
|
+
@signature = Linzer::Signature.build(@message.headers)
|
70
|
+
request.env["rack.signature"] = @signature
|
71
|
+
(@signature.to_h.keys & %w[signature signature-input]).size == 2
|
72
|
+
rescue => ex
|
73
|
+
logger.error ex.message
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def created?
|
78
|
+
required = options[:created_required]
|
79
|
+
if required
|
80
|
+
params.key?("created") && Integer(params["created"])
|
81
|
+
else
|
82
|
+
!required
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def expires?
|
87
|
+
required = options[:expires_required]
|
88
|
+
if required
|
89
|
+
params.key?("expires") && Integer(params["expires"]) > Time.now.to_i
|
90
|
+
else
|
91
|
+
!required
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def keyid?
|
96
|
+
required = options[:keyid_required]
|
97
|
+
required ? params.key?("keyid") : !required
|
98
|
+
end
|
99
|
+
|
100
|
+
def nonce?
|
101
|
+
required = options[:nonce_required]
|
102
|
+
required ? params.key?("nonce") : !required
|
103
|
+
end
|
104
|
+
|
105
|
+
def alg?
|
106
|
+
required = options[:alg_required]
|
107
|
+
required ? params.key?("alg") : !required
|
108
|
+
end
|
109
|
+
|
110
|
+
def tag?
|
111
|
+
required = options[:tag_required]
|
112
|
+
required ? params.key?("tag") : !required
|
113
|
+
end
|
114
|
+
|
115
|
+
def has_required_params?
|
116
|
+
created? && expires? && keyid? && nonce? && alg? && tag?
|
117
|
+
rescue => ex
|
118
|
+
logger.error ex.message
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
def has_required_components?
|
123
|
+
components = @signature.components || []
|
124
|
+
covered_components = options[:covered_components]
|
125
|
+
warning = "Insufficient coverage by signature. Consult S 7.2.1. in RFC"
|
126
|
+
logger.warn warning if covered_components.empty?
|
127
|
+
(covered_components || []).all? { |c| components.include?(c) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def acceptable?
|
131
|
+
has_required_params? && has_required_components?
|
132
|
+
end
|
133
|
+
|
134
|
+
def verifiable?
|
135
|
+
verify_opts = {}
|
136
|
+
|
137
|
+
warning = "Risk of signature replay! (Consult S 7.2.2. in RFC)"
|
138
|
+
logger.warn warning unless options[:no_older_than]
|
139
|
+
|
140
|
+
if options[:no_older_than]
|
141
|
+
age = Integer(options[:no_older_than])
|
142
|
+
verify_opts[:no_older_than] = age
|
143
|
+
end
|
144
|
+
|
145
|
+
Linzer.verify(key, @message, @signature, **verify_opts)
|
146
|
+
rescue => ex
|
147
|
+
logger.error ex.message
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
def key
|
152
|
+
build_key(params["keyid"] || :default)
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_key(keyid)
|
156
|
+
material = if keyid == :default
|
157
|
+
options[:default_key]
|
158
|
+
else
|
159
|
+
key_data = options[:known_keys][keyid] || {}
|
160
|
+
if !key_data.key?("key") && key_data.key?("path")
|
161
|
+
key_data["key"] = IO.read(key_data["path"]) rescue nil
|
162
|
+
end
|
163
|
+
key_data
|
164
|
+
end
|
165
|
+
|
166
|
+
key_not_found = "Key not found. Signature cannot be verified."
|
167
|
+
raise Linzer::Error.new key_not_found if !material || !material["key"]
|
168
|
+
|
169
|
+
alg = @signature.parameters["alg"] || material["alg"] || :unknown
|
170
|
+
instantiate_key(keyid, alg, material)
|
171
|
+
end
|
172
|
+
|
173
|
+
def instantiate_key(keyid, alg, material)
|
174
|
+
key_methods = {
|
175
|
+
"rsa-pss-sha512" => :new_rsa_pss_sha512_key,
|
176
|
+
"rsa-v1_5-sha256" => :new_rsa_v1_5_sha256_key,
|
177
|
+
"hmac-sha256" => :new_hmac_sha256_key,
|
178
|
+
"ecdsa-p256-sha256" => :new_ecdsa_p256_sha256_key,
|
179
|
+
"ecdsa-p384-sha384" => :new_ecdsa_p384_sha384_key,
|
180
|
+
"ed25519" => :new_ed25519_public_key
|
181
|
+
}
|
182
|
+
method = key_methods[alg]
|
183
|
+
|
184
|
+
alg_error = "Unsupported or unknown signature algorithm"
|
185
|
+
raise Linzer::Error.new alg_error unless method
|
186
|
+
|
187
|
+
Linzer.public_send(method, material["key"], keyid.to_s)
|
188
|
+
end
|
189
|
+
|
190
|
+
def load_options(options)
|
191
|
+
DEFAULT_OPTIONS
|
192
|
+
.merge(load_options_from_config_file(options))
|
193
|
+
.merge(Hash(options)) || {}
|
194
|
+
end
|
195
|
+
|
196
|
+
def load_options_from_config_file(options)
|
197
|
+
config_path = options[:config_path]
|
198
|
+
YAML
|
199
|
+
.safe_load_file(config_path)
|
200
|
+
.transform_keys(&:to_sym)
|
201
|
+
rescue
|
202
|
+
{}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Landaeta
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: openssl
|
@@ -103,6 +103,26 @@ dependencies:
|
|
103
103
|
- - ">="
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: 3.1.2
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: logger
|
108
|
+
requirement: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '1.7'
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 1.7.0
|
116
|
+
type: :runtime
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '1.7'
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.7.0
|
106
126
|
email:
|
107
127
|
- miguel@miguel.cc
|
108
128
|
executables: []
|
@@ -116,6 +136,10 @@ files:
|
|
116
136
|
- LICENSE.txt
|
117
137
|
- README.md
|
118
138
|
- Rakefile
|
139
|
+
- examples/sinatra/Gemfile
|
140
|
+
- examples/sinatra/config.ru
|
141
|
+
- examples/sinatra/http-signatures.yml
|
142
|
+
- examples/sinatra/myapp.rb
|
119
143
|
- lib/linzer.rb
|
120
144
|
- lib/linzer/common.rb
|
121
145
|
- lib/linzer/ecdsa.rb
|
@@ -132,6 +156,7 @@ files:
|
|
132
156
|
- lib/linzer/signer.rb
|
133
157
|
- lib/linzer/verifier.rb
|
134
158
|
- lib/linzer/version.rb
|
159
|
+
- lib/rack/auth/signature.rb
|
135
160
|
homepage: https://github.com/nomadium/linzer
|
136
161
|
licenses:
|
137
162
|
- MIT
|
@@ -153,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
178
|
- !ruby/object:Gem::Version
|
154
179
|
version: '0'
|
155
180
|
requirements: []
|
156
|
-
rubygems_version: 3.6.
|
181
|
+
rubygems_version: 3.6.7
|
157
182
|
specification_version: 4
|
158
183
|
summary: An implementation of HTTP Messages Signatures (RFC9421)
|
159
184
|
test_files: []
|