slosilo 2.0.1 → 3.0.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.
- checksums.yaml +5 -5
- data/.dockerignore +2 -0
- data/.github/CODEOWNERS +10 -0
- data/.gitleaks.toml +221 -0
- data/CHANGELOG.md +17 -1
- data/CONTRIBUTING.md +16 -0
- data/Jenkinsfile +75 -0
- data/LICENSE +2 -2
- data/README.md +6 -5
- data/SECURITY.md +42 -0
- data/lib/slosilo/adapters/sequel_adapter.rb +12 -3
- data/lib/slosilo/attr_encrypted.rb +4 -1
- data/lib/slosilo/errors.rb +3 -0
- data/lib/slosilo/jwt.rb +122 -0
- data/lib/slosilo/key.rb +74 -2
- data/lib/slosilo/keystore.rb +13 -2
- data/lib/slosilo/version.rb +1 -1
- data/lib/slosilo.rb +1 -0
- data/lib/tasks/slosilo.rake +5 -0
- data/publish-rubygem.sh +11 -0
- data/slosilo.gemspec +12 -3
- data/spec/file_adapter_spec.rb +1 -1
- data/spec/jwt_spec.rb +102 -0
- data/spec/key_spec.rb +48 -2
- data/spec/sequel_adapter_spec.rb +3 -3
- data/spec/slosilo_spec.rb +32 -0
- data/spec/spec_helper.rb +5 -2
- data/spec/symmetric_spec.rb +4 -4
- data/test.sh +27 -0
- metadata +61 -24
data/lib/slosilo/key.rb
CHANGED
@@ -3,6 +3,8 @@ require 'json'
|
|
3
3
|
require 'base64'
|
4
4
|
require 'time'
|
5
5
|
|
6
|
+
require 'slosilo/errors'
|
7
|
+
|
6
8
|
module Slosilo
|
7
9
|
class Key
|
8
10
|
def initialize raw_key = nil
|
@@ -13,6 +15,10 @@ module Slosilo
|
|
13
15
|
else
|
14
16
|
OpenSSL::PKey::RSA.new 2048
|
15
17
|
end
|
18
|
+
rescue OpenSSL::PKey::PKeyError => e
|
19
|
+
# old openssl versions used to report ArgumentError
|
20
|
+
# which arguably makes more sense here, so reraise as that
|
21
|
+
raise ArgumentError, e, e.backtrace
|
16
22
|
end
|
17
23
|
|
18
24
|
attr_reader :key
|
@@ -71,14 +77,80 @@ module Slosilo
|
|
71
77
|
token["key"] = fingerprint
|
72
78
|
token
|
73
79
|
end
|
80
|
+
|
81
|
+
JWT_ALGORITHM = 'conjur.org/slosilo/v2'.freeze
|
82
|
+
|
83
|
+
# Issue a JWT with the given claims.
|
84
|
+
# `iat` (issued at) claim is automatically added.
|
85
|
+
# Other interesting claims you can give are:
|
86
|
+
# - `sub` - token subject, for example a user name;
|
87
|
+
# - `exp` - expiration time (absolute);
|
88
|
+
# - `cidr` (Conjur extension) - array of CIDR masks that are accepted to
|
89
|
+
# make requests that bear this token
|
90
|
+
def issue_jwt claims
|
91
|
+
token = Slosilo::JWT.new claims
|
92
|
+
token.add_signature \
|
93
|
+
alg: JWT_ALGORITHM,
|
94
|
+
kid: fingerprint,
|
95
|
+
&method(:sign)
|
96
|
+
token.freeze
|
97
|
+
end
|
98
|
+
|
99
|
+
DEFAULT_EXPIRATION = 8 * 60
|
74
100
|
|
75
|
-
def token_valid? token, expiry =
|
101
|
+
def token_valid? token, expiry = DEFAULT_EXPIRATION
|
102
|
+
return jwt_valid? token if token.respond_to? :header
|
76
103
|
token = token.clone
|
77
104
|
expected_key = token.delete "key"
|
78
105
|
return false if (expected_key and (expected_key != fingerprint))
|
79
106
|
signature = Base64::urlsafe_decode64(token.delete "signature")
|
80
107
|
(Time.parse(token["timestamp"]) + expiry > Time.now) && verify_signature(token, signature)
|
81
108
|
end
|
109
|
+
|
110
|
+
# Validate a JWT.
|
111
|
+
#
|
112
|
+
# Convenience method calling #validate_jwt and returning false if an
|
113
|
+
# exception is raised.
|
114
|
+
#
|
115
|
+
# @param token [JWT] pre-parsed token to verify
|
116
|
+
# @return [Boolean]
|
117
|
+
def jwt_valid? token
|
118
|
+
validate_jwt token
|
119
|
+
true
|
120
|
+
rescue
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
# Validate a JWT.
|
125
|
+
#
|
126
|
+
# First checks whether algorithm is 'conjur.org/slosilo/v2' and the key id
|
127
|
+
# matches this key's fingerprint. Then verifies if the token is not expired,
|
128
|
+
# as indicated by the `exp` claim; in its absence tokens are assumed to
|
129
|
+
# expire in `iat` + 8 minutes.
|
130
|
+
#
|
131
|
+
# If those checks pass, finally the signature is verified.
|
132
|
+
#
|
133
|
+
# @raises TokenValidationError if any of the checks fail.
|
134
|
+
#
|
135
|
+
# @note It's the responsibility of the caller to examine other claims
|
136
|
+
# included in the token; consideration needs to be given to handling
|
137
|
+
# unrecognized claims.
|
138
|
+
#
|
139
|
+
# @param token [JWT] pre-parsed token to verify
|
140
|
+
def validate_jwt token
|
141
|
+
def err msg
|
142
|
+
raise Error::TokenValidationError, msg, caller
|
143
|
+
end
|
144
|
+
|
145
|
+
header = token.header
|
146
|
+
err 'unrecognized algorithm' unless header['alg'] == JWT_ALGORITHM
|
147
|
+
err 'mismatched key' if (kid = header['kid']) && kid != fingerprint
|
148
|
+
iat = Time.at token.claims['iat'] || err('unknown issuing time')
|
149
|
+
exp = Time.at token.claims['exp'] || (iat + DEFAULT_EXPIRATION)
|
150
|
+
err 'token expired' if exp <= Time.now
|
151
|
+
err 'invalid signature' unless verify_signature token.string_to_sign, token.signature
|
152
|
+
true
|
153
|
+
end
|
82
154
|
|
83
155
|
def sign_string value
|
84
156
|
salt = shake_salt
|
@@ -86,7 +158,7 @@ module Slosilo
|
|
86
158
|
end
|
87
159
|
|
88
160
|
def fingerprint
|
89
|
-
@fingerprint ||= OpenSSL::Digest::
|
161
|
+
@fingerprint ||= OpenSSL::Digest::SHA256.hexdigest key.public_key.to_der
|
90
162
|
end
|
91
163
|
|
92
164
|
def == other
|
data/lib/slosilo/keystore.rb
CHANGED
@@ -59,15 +59,26 @@ module Slosilo
|
|
59
59
|
keystore.any? { |k| k.token_valid? token }
|
60
60
|
end
|
61
61
|
|
62
|
+
# Looks up the signer by public key fingerprint and checks the validity
|
63
|
+
# of the signature. If the token is JWT, exp and/or iat claims are also
|
64
|
+
# verified; the caller is responsible for validating any other claims.
|
62
65
|
def token_signer token
|
63
|
-
|
66
|
+
begin
|
67
|
+
# see if maybe it's a JWT
|
68
|
+
token = JWT token
|
69
|
+
fingerprint = token.header['kid']
|
70
|
+
rescue ArgumentError
|
71
|
+
fingerprint = token['key']
|
72
|
+
end
|
73
|
+
|
74
|
+
key, id = keystore.get_by_fingerprint fingerprint
|
64
75
|
if key && key.token_valid?(token)
|
65
76
|
return id
|
66
77
|
else
|
67
78
|
return nil
|
68
79
|
end
|
69
80
|
end
|
70
|
-
|
81
|
+
|
71
82
|
attr_accessor :adapter
|
72
83
|
|
73
84
|
private
|
data/lib/slosilo/version.rb
CHANGED
data/lib/slosilo.rb
CHANGED
data/lib/tasks/slosilo.rake
CHANGED
@@ -24,4 +24,9 @@ namespace :slosilo do
|
|
24
24
|
task :migrate => :environment do |t|
|
25
25
|
Slosilo.adapter.migrate!
|
26
26
|
end
|
27
|
+
|
28
|
+
desc "Recalculate fingerprints in keystore"
|
29
|
+
task :recalculate_fingerprints => :environment do |t|
|
30
|
+
Slosilo.adapter.recalculate_fingerprints
|
31
|
+
end
|
27
32
|
end
|
data/publish-rubygem.sh
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/bin/bash -e
|
2
|
+
|
3
|
+
docker pull registry.tld/conjurinc/publish-rubygem
|
4
|
+
|
5
|
+
docker run -i --rm -v $PWD:/src -w /src alpine/git clean -fxd
|
6
|
+
|
7
|
+
summon --yaml "RUBYGEMS_API_KEY: !var rubygems/api-key" \
|
8
|
+
docker run --rm --env-file @SUMMONENVFILE -v "$(pwd)":/opt/src \
|
9
|
+
registry.tld/conjurinc/publish-rubygem slosilo
|
10
|
+
|
11
|
+
docker run -i --rm -v $PWD:/src -w /src alpine/git clean -fxd
|
data/slosilo.gemspec
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
2
|
+
begin
|
3
|
+
require File.expand_path('../lib/slosilo/version', __FILE__)
|
4
|
+
rescue LoadError
|
5
|
+
# so that bundle can be run without the app code
|
6
|
+
module Slosilo
|
7
|
+
VERSION = '0.0.0'
|
8
|
+
end
|
9
|
+
end
|
3
10
|
|
4
11
|
Gem::Specification.new do |gem|
|
5
12
|
gem.authors = ["Rafa\305\202 Rzepecki"]
|
@@ -15,13 +22,15 @@ Gem::Specification.new do |gem|
|
|
15
22
|
gem.name = "slosilo"
|
16
23
|
gem.require_paths = ["lib"]
|
17
24
|
gem.version = Slosilo::VERSION
|
18
|
-
gem.required_ruby_version = '>=
|
19
|
-
|
25
|
+
gem.required_ruby_version = '>= 3.0.0'
|
26
|
+
|
20
27
|
gem.add_development_dependency 'rake'
|
21
28
|
gem.add_development_dependency 'rspec', '~> 3.0'
|
22
29
|
gem.add_development_dependency 'ci_reporter_rspec'
|
23
30
|
gem.add_development_dependency 'simplecov'
|
31
|
+
gem.add_development_dependency 'simplecov-cobertura'
|
24
32
|
gem.add_development_dependency 'io-grab', '~> 0.0.1'
|
25
33
|
gem.add_development_dependency 'sequel' # for sequel tests
|
26
34
|
gem.add_development_dependency 'sqlite3' # for sequel tests
|
35
|
+
gem.add_development_dependency 'activesupport' # for convenience in specs
|
27
36
|
end
|
data/spec/file_adapter_spec.rb
CHANGED
@@ -22,7 +22,7 @@ describe Slosilo::Adapters::FileAdapter do
|
|
22
22
|
context "unacceptable id" do
|
23
23
|
let(:id) { "foo.bar" }
|
24
24
|
it "isn't accepted" do
|
25
|
-
expect { subject.put_key id, key }.to raise_error
|
25
|
+
expect { subject.put_key id, key }.to raise_error /id should not contain a period/
|
26
26
|
end
|
27
27
|
end
|
28
28
|
context "acceptable id" do
|
data/spec/jwt_spec.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# (Mostly) integration tests for JWT token format
|
4
|
+
describe Slosilo::Key do
|
5
|
+
include_context "with example key"
|
6
|
+
|
7
|
+
describe '#issue_jwt' do
|
8
|
+
it 'issues an JWT token with given claims' do
|
9
|
+
allow(Time).to receive(:now) { DateTime.parse('2014-06-04 23:22:32 -0400').to_time }
|
10
|
+
|
11
|
+
tok = key.issue_jwt sub: 'host/example', cidr: %w(fec0::/64)
|
12
|
+
|
13
|
+
expect(tok).to be_frozen
|
14
|
+
|
15
|
+
expect(tok.header).to eq \
|
16
|
+
alg: 'conjur.org/slosilo/v2',
|
17
|
+
kid: key_fingerprint
|
18
|
+
expect(tok.claims).to eq \
|
19
|
+
iat: 1401938552,
|
20
|
+
sub: 'host/example',
|
21
|
+
cidr: ['fec0::/64']
|
22
|
+
|
23
|
+
expect(key.verify_signature tok.string_to_sign, tok.signature).to be_truthy
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Slosilo::JWT do
|
29
|
+
context "with a signed token" do
|
30
|
+
let(:signature) { 'very signed, such alg' }
|
31
|
+
subject(:token) { Slosilo::JWT.new test: "token" }
|
32
|
+
before do
|
33
|
+
allow(Time).to receive(:now) { DateTime.parse('2014-06-04 23:22:32 -0400').to_time }
|
34
|
+
token.add_signature(alg: 'test-sig') { signature }
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows conversion to JSON representation with #to_json' do
|
38
|
+
json = JSON.load token.to_json
|
39
|
+
expect(JSON.load Base64.urlsafe_decode64 json['protected']).to eq \
|
40
|
+
'alg' => 'test-sig'
|
41
|
+
expect(JSON.load Base64.urlsafe_decode64 json['payload']).to eq \
|
42
|
+
'iat' => 1401938552, 'test' => 'token'
|
43
|
+
expect(Base64.urlsafe_decode64 json['signature']).to eq signature
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'allows conversion to compact representation with #to_s' do
|
47
|
+
h, c, s = token.to_s.split '.'
|
48
|
+
expect(JSON.load Base64.urlsafe_decode64 h).to eq \
|
49
|
+
'alg' => 'test-sig'
|
50
|
+
expect(JSON.load Base64.urlsafe_decode64 c).to eq \
|
51
|
+
'iat' => 1401938552, 'test' => 'token'
|
52
|
+
expect(Base64.urlsafe_decode64 s).to eq signature
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#to_json' do
|
57
|
+
it "passes any parameters" do
|
58
|
+
token = Slosilo::JWT.new
|
59
|
+
allow(token).to receive_messages \
|
60
|
+
header: :header,
|
61
|
+
claims: :claims,
|
62
|
+
signature: :signature
|
63
|
+
expect_any_instance_of(Hash).to receive(:to_json).with :testing
|
64
|
+
expect(token.to_json :testing)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '()' do
|
69
|
+
include_context "with example key"
|
70
|
+
|
71
|
+
it 'understands both serializations' do
|
72
|
+
[COMPACT_TOKEN, JSON_TOKEN].each do |token|
|
73
|
+
token = Slosilo::JWT token
|
74
|
+
expect(token.header).to eq \
|
75
|
+
'typ' => 'JWT',
|
76
|
+
'alg' => 'conjur.org/slosilo/v2',
|
77
|
+
'kid' => key_fingerprint
|
78
|
+
expect(token.claims).to eq \
|
79
|
+
'sub' => 'host/example',
|
80
|
+
'iat' => 1401938552,
|
81
|
+
'exp' => 1401938552 + 60*60,
|
82
|
+
'cidr' => ['fec0::/64']
|
83
|
+
expect(key.verify_signature token.string_to_sign, token.signature).to be_truthy
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'is a noop if already parsed' do
|
88
|
+
token = Slosilo::JWT COMPACT_TOKEN
|
89
|
+
expect(Slosilo::JWT token).to eq token
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises ArgumentError on failure to convert' do
|
93
|
+
expect { Slosilo::JWT "foo bar" }.to raise_error ArgumentError
|
94
|
+
expect { Slosilo::JWT elite: 31337 }.to raise_error ArgumentError
|
95
|
+
expect { Slosilo::JWT "foo.bar.xyzzy" }.to raise_error ArgumentError
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
COMPACT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=.eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJjaWRyIjpbImZlYzA6Oi82NCJdLCJleHAiOjE0MDE5NDIxNTIsImlhdCI6MTQwMTkzODU1Mn0=.qSxy6gx0DbiIc-Wz_vZhBsYi1SCkHhzxfMGPnnG6MTqjlzy7ntmlU2H92GKGoqCRo6AaNLA_C3hA42PeEarV5nMoTj8XJO_kwhrt2Db2OX4u83VS0_enoztWEZG5s45V0Lv71lVR530j4LD-hpqhm_f4VuISkeH84u0zX7s1zKOlniuZP-abCAHh0htTnrVz9wKG0VywkCUmWYyNNqC2h8PRf64SvCWcQ6VleHpjO-ms8OeTw4ZzRbzKMi0mL6eTmQlbT3PeBArUaS0pNJPg9zdDQaL2XDOofvQmj6Yy_8RA4eCt9HEfTYEdriVqK-_9QCspbGzFVn9GTWf51MRi5dngV9ItsDoG9ktDtqFuMttv7TcqjftsIHZXZsAZ175E".freeze
|
100
|
+
|
101
|
+
JSON_TOKEN = "{\"protected\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=\",\"payload\":\"eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJjaWRyIjpbImZlYzA6Oi82NCJdLCJleHAiOjE0MDE5NDIxNTIsImlhdCI6MTQwMTkzODU1Mn0=\",\"signature\":\"qSxy6gx0DbiIc-Wz_vZhBsYi1SCkHhzxfMGPnnG6MTqjlzy7ntmlU2H92GKGoqCRo6AaNLA_C3hA42PeEarV5nMoTj8XJO_kwhrt2Db2OX4u83VS0_enoztWEZG5s45V0Lv71lVR530j4LD-hpqhm_f4VuISkeH84u0zX7s1zKOlniuZP-abCAHh0htTnrVz9wKG0VywkCUmWYyNNqC2h8PRf64SvCWcQ6VleHpjO-ms8OeTw4ZzRbzKMi0mL6eTmQlbT3PeBArUaS0pNJPg9zdDQaL2XDOofvQmj6Yy_8RA4eCt9HEfTYEdriVqK-_9QCspbGzFVn9GTWf51MRi5dngV9ItsDoG9ktDtqFuMttv7TcqjftsIHZXZsAZ175E\"}".freeze
|
102
|
+
end
|
data/spec/key_spec.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
|
3
6
|
describe Slosilo::Key do
|
4
7
|
include_context "with example key"
|
5
8
|
|
@@ -120,7 +123,7 @@ describe Slosilo::Key do
|
|
120
123
|
context "when given something else" do
|
121
124
|
subject { Slosilo::Key.new "foo" }
|
122
125
|
it "fails early" do
|
123
|
-
expect { subject }.to raise_error
|
126
|
+
expect { subject }.to raise_error ArgumentError
|
124
127
|
end
|
125
128
|
end
|
126
129
|
end
|
@@ -175,7 +178,50 @@ describe Slosilo::Key do
|
|
175
178
|
subject { key.signed_token data }
|
176
179
|
it { is_expected.to eq(expected_token) }
|
177
180
|
end
|
178
|
-
|
181
|
+
|
182
|
+
describe "#validate_jwt" do
|
183
|
+
let(:token) do
|
184
|
+
instance_double Slosilo::JWT,
|
185
|
+
header: { 'alg' => 'conjur.org/slosilo/v2' },
|
186
|
+
claims: { 'iat' => Time.now.to_i },
|
187
|
+
string_to_sign: double("string to sign"),
|
188
|
+
signature: double("signature")
|
189
|
+
end
|
190
|
+
|
191
|
+
before do
|
192
|
+
allow(key).to receive(:verify_signature).with(token.string_to_sign, token.signature) { true }
|
193
|
+
end
|
194
|
+
|
195
|
+
it "verifies the signature" do
|
196
|
+
expect { key.validate_jwt token }.not_to raise_error
|
197
|
+
end
|
198
|
+
|
199
|
+
it "rejects unknown algorithm" do
|
200
|
+
token.header['alg'] = 'HS256' # we're not supporting standard algorithms
|
201
|
+
expect { key.validate_jwt token }.to raise_error /algorithm/
|
202
|
+
end
|
203
|
+
|
204
|
+
it "rejects bad signature" do
|
205
|
+
allow(key).to receive(:verify_signature).with(token.string_to_sign, token.signature) { false }
|
206
|
+
expect { key.validate_jwt token }.to raise_error /signature/
|
207
|
+
end
|
208
|
+
|
209
|
+
it "rejects expired token" do
|
210
|
+
token.claims['exp'] = 1.hour.ago.to_i
|
211
|
+
expect { key.validate_jwt token }.to raise_error /expired/
|
212
|
+
end
|
213
|
+
|
214
|
+
it "accepts unexpired token with implicit expiration" do
|
215
|
+
token.claims['iat'] = 5.minutes.ago
|
216
|
+
expect { key.validate_jwt token }.to_not raise_error
|
217
|
+
end
|
218
|
+
|
219
|
+
it "rejects token expired with implicit expiration" do
|
220
|
+
token.claims['iat'] = 10.minutes.ago.to_i
|
221
|
+
expect { key.validate_jwt token }.to raise_error /expired/
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
179
225
|
describe "#token_valid?" do
|
180
226
|
let(:data) { { "foo" => :bar } }
|
181
227
|
let(:signature) { Base64::urlsafe_encode64 "\xB0\xCE{\x9FP\xEDV\x9C\xE7b\x8B[\xFAil\x87^\x96\x17Z\x97\x1D\xC2?B\x96\x9C\x8Ep-\xDF_\x8F\xC21\xD9^\xBC\n\x16\x04\x8DJ\xF6\xAF-\xEC\xAD\x03\xF9\xEE:\xDF\xB5\x8F\xF9\xF6\x81m\xAB\x9C\xAB1\x1E\x837\x8C\xFB\xA8P\xA8<\xEA\x1Dx\xCEd\xED\x84f\xA7\xB5t`\x96\xCC\x0F\xA9t\x8B\x9Fo\xBF\x92K\xFA\xFD\xC5?\x8F\xC68t\xBC\x9F\xDE\n$\xCA\xD2\x8F\x96\x0EtX2\x8Cl\x1E\x8Aa\r\x8D\xCAi\x86\x1A\xBD\x1D\xF7\xBC\x8561j\x91YlO\xFA(\x98\x10iq\xCC\xAF\x9BV\xC6\v\xBC\x10Xm\xCD\xFE\xAD=\xAA\x95,\xB4\xF7\xE8W\xB8\x83;\x81\x88\xE6\x01\xBA\xA5F\x91\x17\f\xCE\x80\x8E\v\x83\x9D<\x0E\x83\xF6\x8D\x03\xC0\xE8A\xD7\x90i\x1D\x030VA\x906D\x10\xA0\xDE\x12\xEF\x06M\xD8\x8B\xA9W\xC8\x9DTc\x8AJ\xA4\xC0\xD3!\xFA\x14\x89\xD1p\xB4J7\xA5\x04\xC2l\xDC8<\x04Y\xD8\xA4\xFB[\x89\xB1\xEC\xDA\xB8\xD7\xEA\x03Ja pinch of salt".force_encoding("ASCII-8BIT") }
|
data/spec/sequel_adapter_spec.rb
CHANGED
@@ -30,13 +30,13 @@ describe Slosilo::Adapters::SequelAdapter do
|
|
30
30
|
describe "#put_key" do
|
31
31
|
let(:id) { "id" }
|
32
32
|
it "creates the key" do
|
33
|
-
expect(model).to receive(:create).with
|
33
|
+
expect(model).to receive(:create).with(hash_including(:id => id, :key => key.to_der))
|
34
34
|
allow(model).to receive_messages columns: [:id, :key]
|
35
35
|
subject.put_key id, key
|
36
36
|
end
|
37
37
|
|
38
38
|
it "adds the fingerprint if feasible" do
|
39
|
-
expect(model).to receive(:create).with
|
39
|
+
expect(model).to receive(:create).with(hash_including(:id => id, :key => key.to_der, :fingerprint => key.fingerprint))
|
40
40
|
allow(model).to receive_messages columns: [:id, :key, :fingerprint]
|
41
41
|
subject.put_key id, key
|
42
42
|
end
|
@@ -60,7 +60,7 @@ describe Slosilo::Adapters::SequelAdapter do
|
|
60
60
|
let(:db) { Sequel.sqlite }
|
61
61
|
before do
|
62
62
|
allow(subject).to receive(:create_model).and_call_original
|
63
|
-
Sequel.cache_anonymous_models = false
|
63
|
+
Sequel::Model.cache_anonymous_models = false
|
64
64
|
Sequel::Model.db = db
|
65
65
|
end
|
66
66
|
end
|
data/spec/slosilo_spec.rb
CHANGED
@@ -88,5 +88,37 @@ describe Slosilo do
|
|
88
88
|
expect(subject.token_signer(token)).not_to be
|
89
89
|
end
|
90
90
|
end
|
91
|
+
|
92
|
+
context "with JWT token" do
|
93
|
+
before do
|
94
|
+
expect(key).to receive(:validate_jwt) do |jwt|
|
95
|
+
expect(jwt.header).to eq 'kid' => key.fingerprint
|
96
|
+
expect(jwt.claims).to eq({})
|
97
|
+
expect(jwt.signature).to eq 'sig'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "accepts pre-parsed JSON serialization" do
|
102
|
+
expect(Slosilo.token_signer(
|
103
|
+
'protected' => 'eyJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=',
|
104
|
+
'payload' => 'e30=',
|
105
|
+
'signature' => 'c2ln'
|
106
|
+
)).to eq 'test'
|
107
|
+
end
|
108
|
+
|
109
|
+
it "accepts pre-parsed JWT token" do
|
110
|
+
expect(Slosilo.token_signer(Slosilo::JWT(
|
111
|
+
'protected' => 'eyJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=',
|
112
|
+
'payload' => 'e30=',
|
113
|
+
'signature' => 'c2ln'
|
114
|
+
))).to eq 'test'
|
115
|
+
end
|
116
|
+
|
117
|
+
it "accepts compact serialization" do
|
118
|
+
expect(Slosilo.token_signer(
|
119
|
+
'eyJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=.e30=.c2ln'
|
120
|
+
)).to eq 'test'
|
121
|
+
end
|
122
|
+
end
|
91
123
|
end
|
92
124
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require "simplecov"
|
2
|
+
require "simplecov-cobertura"
|
3
|
+
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
2
5
|
SimpleCov.start
|
3
6
|
|
4
7
|
require 'slosilo'
|
@@ -41,7 +44,7 @@ Dg1ikwi8GUF4HPZe9DyhXgDhg19wM/qcpjX8bSypsUWHWP+FanhjdWU=
|
|
41
44
|
-----END RSA PRIVATE KEY-----
|
42
45
|
""" }
|
43
46
|
let (:key) { Slosilo::Key.new rsa.to_der }
|
44
|
-
let (:key_fingerprint) { "
|
47
|
+
let (:key_fingerprint) { "107bdb8501c419fad2fdb20b467d4d0a62a16a98c35f2da0eb3b1ff929795ad9" }
|
45
48
|
|
46
49
|
let (:another_rsa) do
|
47
50
|
OpenSSL::PKey::RSA.new """
|
@@ -74,7 +77,7 @@ ooQ2FuL0K6ukQfHPjuMswqi41lmVH8gIVqVC+QnImUCrGxH9WXWy
|
|
74
77
|
-----END RSA PRIVATE KEY-----
|
75
78
|
"""
|
76
79
|
end
|
77
|
-
|
80
|
+
|
78
81
|
def self.mock_own_key
|
79
82
|
before { allow(Slosilo).to receive(:[]).with(:own).and_return key }
|
80
83
|
end
|
data/spec/symmetric_spec.rb
CHANGED
@@ -24,12 +24,12 @@ describe Slosilo::Symmetric do
|
|
24
24
|
context "when the ciphertext has been messed with" do
|
25
25
|
let(:ciphertext) { "pwnd!" } # maybe we should do something more realistic like add some padding?
|
26
26
|
it "raises an exception" do
|
27
|
-
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception
|
27
|
+
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception /Invalid version/
|
28
28
|
end
|
29
29
|
context "by adding a trailing 0" do
|
30
30
|
let(:new_ciphertext){ ciphertext + '\0' }
|
31
31
|
it "raises an exception" do
|
32
|
-
expect{ subject.decrypt(new_ciphertext, key: key, aad: auth_data) }.to raise_exception
|
32
|
+
expect{ subject.decrypt(new_ciphertext, key: key, aad: auth_data) }.to raise_exception /Invalid version/
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -44,7 +44,7 @@ describe Slosilo::Symmetric do
|
|
44
44
|
|
45
45
|
context "and the ciphertext has been messed with" do
|
46
46
|
it "raises an exception" do
|
47
|
-
expect{ subject.decrypt(ciphertext + "\0\0\0", key: key, aad: auth_data)}.to raise_exception
|
47
|
+
expect{ subject.decrypt(ciphertext + "\0\0\0", key: key, aad: auth_data)}.to raise_exception OpenSSL::Cipher::CipherError
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -52,7 +52,7 @@ describe Slosilo::Symmetric do
|
|
52
52
|
context "when the auth data doesn't match" do
|
53
53
|
let(:auth_data){ "asdf" }
|
54
54
|
it "raises an exception" do
|
55
|
-
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception
|
55
|
+
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception OpenSSL::Cipher::CipherError
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/test.sh
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/bin/bash -xe
|
2
|
+
|
3
|
+
iid=slosilo-test-$(date +%s)
|
4
|
+
|
5
|
+
docker build -t $iid -f - . << EOF
|
6
|
+
FROM ruby:3.0
|
7
|
+
WORKDIR /app
|
8
|
+
COPY Gemfile slosilo.gemspec ./
|
9
|
+
RUN bundle
|
10
|
+
COPY . ./
|
11
|
+
RUN bundle
|
12
|
+
EOF
|
13
|
+
|
14
|
+
cidfile=$(mktemp -u)
|
15
|
+
docker run --cidfile $cidfile -v /app/spec/reports $iid bundle exec rake jenkins || :
|
16
|
+
|
17
|
+
cid=$(cat $cidfile)
|
18
|
+
|
19
|
+
docker cp $cid:/app/spec/reports spec/
|
20
|
+
docker cp $cid:/app/coverage spec
|
21
|
+
|
22
|
+
docker rm $cid
|
23
|
+
|
24
|
+
# untag, will use cache next time if available but no junk will be left
|
25
|
+
docker rmi $iid
|
26
|
+
|
27
|
+
rm $cidfile
|