slosilo 2.0.1 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2f977e53f906393dcac0918f5fb5b2b2b41563b
4
- data.tar.gz: 967b35f4ed8f91c24dfab09245ff0cb798b585ed
3
+ metadata.gz: 1b7ca2d8d2bbd323237e1ff513bd8d00af684da0
4
+ data.tar.gz: 152b2735c0d053e414baeebd40ce229761514a2a
5
5
  SHA512:
6
- metadata.gz: fd31b09380e3f9411aca6a7ee9244d727e0962954da29c18adffa68b81b58265607205b5a519de8d211da07f412600788c1b5579fc0a604e06328e5bb86e62a8
7
- data.tar.gz: 3454255c91a0a62c59bedf5b425e5b5a9fdb1ba29283d85ae0e5be7ca9b5cd2b1fa44b4fa280d65d37441d802e634309a928373848ad4cf199c9e5ac58d074cd
6
+ metadata.gz: a09dd7a7607809467e9f77c0400c85e3c8336722058de27e00f5aad6cda1577db3f747f3b30b2ecdf677ef679a2a78061f0dbd292de253f02d3f6c14a3ee2495
7
+ data.tar.gz: 56e9ee6c12c3419d32b6e6260a6889cad2d38e9822cd66788950b17bf3f559e1a8c660cd9519d1c05c070dc4873fc7446e3cda58809d535886ce62835032db34
@@ -0,0 +1,2 @@
1
+ /Gemfile.lock
2
+ /spec/reports
@@ -1,4 +1,7 @@
1
+ # v2.1.1
2
+
3
+ * Add support for JWT-formatted tokens, with arbitrary expiration.
4
+
1
5
  # v2.0.1
2
6
 
3
7
  * Fixes a bug that occurs when signing tokens containing Unicode data
4
-
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env groovy
2
+
3
+ pipeline {
4
+ agent { label 'executor-v2' }
5
+
6
+ options {
7
+ timestamps()
8
+ buildDiscarder(logRotator(daysToKeepStr: '30'))
9
+ }
10
+
11
+ stages {
12
+ stage('Test') {
13
+ steps {
14
+ sh './test.sh'
15
+
16
+ junit 'spec/reports/*.xml'
17
+ }
18
+ }
19
+
20
+ stage('Publish to RubyGems') {
21
+ agent { label 'releaser-v2' }
22
+ when {
23
+ branch 'master'
24
+ }
25
+
26
+ steps {
27
+ checkout scm
28
+ sh './publish-rubygem.sh'
29
+ deleteDir()
30
+ }
31
+ }
32
+ }
33
+
34
+ post {
35
+ always {
36
+ cleanupAndNotify(currentBuild.currentResult)
37
+ }
38
+ }
39
+ }
@@ -1,3 +1,4 @@
1
+ require "slosilo/jwt"
1
2
  require "slosilo/version"
2
3
  require "slosilo/keystore"
3
4
  require "slosilo/symmetric"
@@ -8,5 +8,8 @@ module Slosilo
8
8
  super
9
9
  end
10
10
  end
11
+
12
+ class TokenValidationError < Error
13
+ end
11
14
  end
12
15
  end
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+
3
+ module Slosilo
4
+ # A JWT-formatted Slosilo token.
5
+ # @note This is not intended to be a general-purpose JWT implementation.
6
+ class JWT
7
+ # Create a new unsigned token with the given claims.
8
+ # @param claims [#to_h] claims to embed in this token.
9
+ def initialize claims = {}
10
+ @claims = JSONHash[claims]
11
+ end
12
+
13
+ # Parse a token in compact representation
14
+ def self.parse_compact raw
15
+ load *raw.split('.', 3).map(&Base64.method(:urlsafe_decode64))
16
+ end
17
+
18
+ # Parse a token in JSON representation.
19
+ # @note only single signature is currently supported.
20
+ def self.parse_json raw
21
+ raw = JSON.load raw unless raw.respond_to? :to_h
22
+ parts = raw.to_h.values_at(*%w(protected payload signature))
23
+ fail ArgumentError, "input not a complete JWT" unless parts.all?
24
+ load *parts.map(&Base64.method(:urlsafe_decode64))
25
+ end
26
+
27
+ # Add a signature.
28
+ # @note currently only a single signature is handled;
29
+ # the token will be frozen after this operation.
30
+ def add_signature header, &sign
31
+ @claims = canonicalize_claims.freeze
32
+ @header = JSONHash[header].freeze
33
+ @signature = sign[string_to_sign].freeze
34
+ freeze
35
+ end
36
+
37
+ def string_to_sign
38
+ [header, claims].map(&method(:encode)).join '.'
39
+ end
40
+
41
+ # Returns the JSON serialization of this JWT.
42
+ def to_json *a
43
+ {
44
+ protected: encode(header),
45
+ payload: encode(claims),
46
+ signature: encode(signature)
47
+ }.to_json *a
48
+ end
49
+
50
+ # Returns the compact serialization of this JWT.
51
+ def to_s
52
+ [header, claims, signature].map(&method(:encode)).join('.')
53
+ end
54
+
55
+ attr_accessor :claims, :header, :signature
56
+
57
+ private
58
+
59
+ # Create a JWT token object from existing header, payload, and signature strings.
60
+ # @param header [#to_s] URLbase64-encoded representation of the protected header
61
+ # @param payload [#to_s] URLbase64-encoded representation of the token payload
62
+ # @param signature [#to_s] URLbase64-encoded representation of the signature
63
+ def self.load header, payload, signature
64
+ self.new(JSONHash.load payload).tap do |token|
65
+ token.header = JSONHash.load header
66
+ token.signature = signature.to_s.freeze
67
+ token.freeze
68
+ end
69
+ end
70
+
71
+ def canonicalize_claims
72
+ claims[:iat] = Time.now unless claims.include? :iat
73
+ claims[:iat] = claims[:iat].to_time.to_i
74
+ claims[:exp] = claims[:exp].to_time.to_i if claims.include? :exp
75
+ JSONHash[claims.to_a]
76
+ end
77
+
78
+ # Convenience method to make the above code clearer.
79
+ # Converts to string and urlbase64-encodes.
80
+ def encode s
81
+ Base64.urlsafe_encode64 s.to_s
82
+ end
83
+
84
+ # a hash with a possibly frozen JSON stringification
85
+ class JSONHash < Hash
86
+ def to_s
87
+ @repr || to_json
88
+ end
89
+
90
+ def freeze
91
+ @repr = to_json.freeze
92
+ super
93
+ end
94
+
95
+ def self.load raw
96
+ self[JSON.load raw.to_s].tap do |h|
97
+ h.send :repr=, raw
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def repr= raw
104
+ @repr = raw.freeze
105
+ freeze
106
+ end
107
+ end
108
+ end
109
+
110
+ # Try to convert by detecting token representation and parsing
111
+ def self.JWT raw
112
+ if raw.is_a? JWT
113
+ raw
114
+ elsif raw.respond_to?(:to_h) || raw =~ /\A\s*\{/
115
+ JWT.parse_json raw
116
+ else
117
+ JWT.parse_compact raw
118
+ end
119
+ rescue
120
+ raise ArgumentError, "invalid value for JWT(): #{raw.inspect}"
121
+ end
122
+ end
@@ -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 = 8 * 60
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
@@ -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
- key, id = keystore.get_by_fingerprint token['key']
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
@@ -1,3 +1,3 @@
1
1
  module Slosilo
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.1"
3
3
  end
@@ -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
@@ -1,5 +1,12 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/slosilo/version', __FILE__)
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"]
@@ -24,4 +31,5 @@ Gem::Specification.new do |gem|
24
31
  gem.add_development_dependency 'io-grab', '~> 0.0.1'
25
32
  gem.add_development_dependency 'sequel' # for sequel tests
26
33
  gem.add_development_dependency 'sqlite3' # for sequel tests
34
+ gem.add_development_dependency 'activesupport' # for convenience in specs
27
35
  end
@@ -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
@@ -0,0 +1,103 @@
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
+
84
+ expect(key.verify_signature token.string_to_sign, token.signature).to be_truthy
85
+ end
86
+ end
87
+
88
+ it 'is a noop if already parsed' do
89
+ token = Slosilo::JWT COMPACT_TOKEN
90
+ expect(Slosilo::JWT token).to eq token
91
+ end
92
+
93
+ it 'raises ArgumentError on failure to convert' do
94
+ expect { Slosilo::JWT "foo bar" }.to raise_error ArgumentError
95
+ expect { Slosilo::JWT elite: 31337 }.to raise_error ArgumentError
96
+ expect { Slosilo::JWT "foo.bar.xyzzy" }.to raise_error ArgumentError
97
+ end
98
+ end
99
+
100
+ COMPACT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiJkMjhlM2EzNDdlMzY4NDE2YjMxMjlhNDBjMWI4ODdmZSJ9.eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJpYXQiOjE0MDE5Mzg1NTIsImV4cCI6MTQwMTk0MjE1MiwiY2lkciI6WyJmZWMwOjovNjQiXX0=.cw9S8Oxu8BmvDgEotBlNiZoJNkAGDpvGIuhCCnG-nMq80dy0ECjC4xERYXHx3bcadEJ8jWfqDB90d7CGvJyepbMhC1hEdsb8xNWGkkqTvQOh33cnZiEJjjfbORpKnOpcc8QySmB9Eb_zKl5WaM6Sjm-uINJri3djuVo_n_S7I43YvKw7gbd5u2gVttgaWnqlnJoeXZnnmXSYH8_66Lr__BqO4tCedShQIf4gA0R_dljrzVSZtJsFTKvwuNOuCvBqO8dQkhp8vplOkKynDkdip-H2nDBQb9Y3bQ8K0NVtSJatBy-d1HvPHSwFZrH4K7P_J2OgJtw9GtckT43QasliXoibdk__Hyvy4HJIIM44rSm7JUuyZWl8e8svRqLujBP7".freeze
101
+
102
+ JSON_TOKEN = "{\"protected\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiJkMjhlM2EzNDdlMzY4NDE2YjMxMjlhNDBjMWI4ODdmZSJ9\",\"payload\":\"eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJpYXQiOjE0MDE5Mzg1NTIsImV4cCI6MTQwMTk0MjE1MiwiY2lkciI6WyJmZWMwOjovNjQiXX0=\",\"signature\":\"cw9S8Oxu8BmvDgEotBlNiZoJNkAGDpvGIuhCCnG-nMq80dy0ECjC4xERYXHx3bcadEJ8jWfqDB90d7CGvJyepbMhC1hEdsb8xNWGkkqTvQOh33cnZiEJjjfbORpKnOpcc8QySmB9Eb_zKl5WaM6Sjm-uINJri3djuVo_n_S7I43YvKw7gbd5u2gVttgaWnqlnJoeXZnnmXSYH8_66Lr__BqO4tCedShQIf4gA0R_dljrzVSZtJsFTKvwuNOuCvBqO8dQkhp8vplOkKynDkdip-H2nDBQb9Y3bQ8K0NVtSJatBy-d1HvPHSwFZrH4K7P_J2OgJtw9GtckT43QasliXoibdk__Hyvy4HJIIM44rSm7JUuyZWl8e8svRqLujBP7\"}".freeze
103
+ end
@@ -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") }
@@ -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
@@ -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' => 'eyJraWQiOiJkMjhlM2EzNDdlMzY4NDE2YjMxMjlhNDBjMWI4ODdmZSJ9',
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' => 'eyJraWQiOiJkMjhlM2EzNDdlMzY4NDE2YjMxMjlhNDBjMWI4ODdmZSJ9',
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
+ 'eyJraWQiOiJkMjhlM2EzNDdlMzY4NDE2YjMxMjlhNDBjMWI4ODdmZSJ9.e30=.c2ln'
120
+ )).to eq 'test'
121
+ end
122
+ end
91
123
  end
92
124
  end
@@ -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,25 @@
1
+ #!/bin/bash -xe
2
+
3
+ iid=slosilo-test-$(date +%s)
4
+
5
+ docker build -t $iid -f - . << EOF
6
+ FROM ruby
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 rm $cid
21
+
22
+ # untag, will use cache next time if available but no junk will be left
23
+ docker rmi $iid
24
+
25
+ rm $cidfile
metadata CHANGED
@@ -1,111 +1,125 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slosilo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafał Rzepecki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-14 00:00:00.000000000 Z
11
+ date: 2017-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: ci_reporter_rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: io-grab
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: 0.0.1
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.0.1
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: sequel
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: sqlite3
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  description: This gem provides an easy way of storing and retrieving encryption keys
@@ -116,10 +130,12 @@ executables: []
116
130
  extensions: []
117
131
  extra_rdoc_files: []
118
132
  files:
119
- - .gitignore
120
- - .kateproject
133
+ - ".dockerignore"
134
+ - ".gitignore"
135
+ - ".kateproject"
121
136
  - CHANGELOG.md
122
137
  - Gemfile
138
+ - Jenkinsfile
123
139
  - LICENSE
124
140
  - README.md
125
141
  - Rakefile
@@ -132,15 +148,18 @@ files:
132
148
  - lib/slosilo/adapters/sequel_adapter/migration.rb
133
149
  - lib/slosilo/attr_encrypted.rb
134
150
  - lib/slosilo/errors.rb
151
+ - lib/slosilo/jwt.rb
135
152
  - lib/slosilo/key.rb
136
153
  - lib/slosilo/keystore.rb
137
154
  - lib/slosilo/random.rb
138
155
  - lib/slosilo/symmetric.rb
139
156
  - lib/slosilo/version.rb
140
157
  - lib/tasks/slosilo.rake
158
+ - publish-rubygem.sh
141
159
  - slosilo.gemspec
142
160
  - spec/encrypted_attributes_spec.rb
143
161
  - spec/file_adapter_spec.rb
162
+ - spec/jwt_spec.rb
144
163
  - spec/key_spec.rb
145
164
  - spec/keystore_spec.rb
146
165
  - spec/random_spec.rb
@@ -148,6 +167,7 @@ files:
148
167
  - spec/slosilo_spec.rb
149
168
  - spec/spec_helper.rb
150
169
  - spec/symmetric_spec.rb
170
+ - test.sh
151
171
  homepage: ''
152
172
  licenses:
153
173
  - MIT
@@ -158,23 +178,24 @@ require_paths:
158
178
  - lib
159
179
  required_ruby_version: !ruby/object:Gem::Requirement
160
180
  requirements:
161
- - - '>='
181
+ - - ">="
162
182
  - !ruby/object:Gem::Version
163
183
  version: 1.9.3
164
184
  required_rubygems_version: !ruby/object:Gem::Requirement
165
185
  requirements:
166
- - - '>='
186
+ - - ">="
167
187
  - !ruby/object:Gem::Version
168
188
  version: '0'
169
189
  requirements: []
170
190
  rubyforge_project:
171
- rubygems_version: 2.0.14.1
191
+ rubygems_version: 2.6.14
172
192
  signing_key:
173
193
  specification_version: 4
174
194
  summary: Store SSL keys in a database
175
195
  test_files:
176
196
  - spec/encrypted_attributes_spec.rb
177
197
  - spec/file_adapter_spec.rb
198
+ - spec/jwt_spec.rb
178
199
  - spec/key_spec.rb
179
200
  - spec/keystore_spec.rb
180
201
  - spec/random_spec.rb
@@ -182,4 +203,3 @@ test_files:
182
203
  - spec/slosilo_spec.rb
183
204
  - spec/spec_helper.rb
184
205
  - spec/symmetric_spec.rb
185
- has_rdoc: