slosilo 1.0.0 → 2.2.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.
@@ -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"]
@@ -18,10 +25,11 @@ Gem::Specification.new do |gem|
18
25
  gem.required_ruby_version = '>= 1.9.3'
19
26
 
20
27
  gem.add_development_dependency 'rake'
21
- gem.add_development_dependency 'rspec', '~> 2.14'
22
- gem.add_development_dependency 'ci_reporter', '~> 1.9'
28
+ gem.add_development_dependency 'rspec', '~> 3.0'
29
+ gem.add_development_dependency 'ci_reporter_rspec'
23
30
  gem.add_development_dependency 'simplecov'
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
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'slosilo/attr_encrypted'
3
+
4
+ describe Slosilo::EncryptedAttributes do
5
+ before(:all) do
6
+ Slosilo::encryption_key = OpenSSL::Cipher.new("aes-256-gcm").random_key
7
+ end
8
+
9
+ let(:aad) { proc{ |_| "hithere" } }
10
+
11
+ let(:base){
12
+ Class.new do
13
+ attr_accessor :normal_ivar,:with_aad
14
+ def stupid_ivar
15
+ side_effect!
16
+ @_explicit
17
+ end
18
+ def stupid_ivar= e
19
+ side_effect!
20
+ @_explicit = e
21
+ end
22
+ def side_effect!
23
+
24
+ end
25
+ end
26
+ }
27
+
28
+ let(:sub){
29
+ Class.new(base) do
30
+ attr_encrypted :normal_ivar, :stupid_ivar
31
+ end
32
+ }
33
+
34
+ subject{ sub.new }
35
+
36
+ context "when setting a normal ivar" do
37
+ let(:value){ "some value" }
38
+ it "stores an encrypted value in the ivar" do
39
+ subject.normal_ivar = value
40
+ expect(subject.instance_variable_get(:"@normal_ivar")).to_not eq(value)
41
+ end
42
+
43
+ it "recovers the value set" do
44
+ subject.normal_ivar = value
45
+ expect(subject.normal_ivar).to eq(value)
46
+ end
47
+ end
48
+
49
+ context "when setting an attribute with an implementation" do
50
+ it "calls the base class method" do
51
+ expect(subject).to receive_messages(:side_effect! => nil)
52
+ subject.stupid_ivar = "hi"
53
+ expect(subject.stupid_ivar).to eq("hi")
54
+ end
55
+ end
56
+
57
+ context "when given an :aad option" do
58
+
59
+ let(:cipher){ Slosilo::EncryptedAttributes.cipher }
60
+ let(:key){ Slosilo::EncryptedAttributes.key}
61
+ context "that is a string" do
62
+ let(:aad){ "hello there" }
63
+ before{ sub.attr_encrypted :with_aad, aad: aad }
64
+ it "encrypts the value with the given string for auth data" do
65
+ expect(cipher).to receive(:encrypt).with("hello", key: key, aad: aad)
66
+ subject.with_aad = "hello"
67
+ end
68
+
69
+ it "decrypts the encrypted value" do
70
+ subject.with_aad = "foo"
71
+ expect(subject.with_aad).to eq("foo")
72
+ end
73
+ end
74
+
75
+ context "that is nil" do
76
+ let(:aad){ nil }
77
+ before{ sub.attr_encrypted :with_aad, aad: aad }
78
+ it "encrypts the value with an empty string for auth data" do
79
+ expect(cipher).to receive(:encrypt).with("hello",key: key, aad: "").and_call_original
80
+ subject.with_aad = "hello"
81
+ end
82
+
83
+ it "decrypts the encrypted value" do
84
+ subject.with_aad = "hello"
85
+ expect(subject.with_aad).to eq("hello")
86
+ end
87
+ end
88
+
89
+ context "that is a proc" do
90
+ let(:aad){
91
+ proc{ |o| "x" }
92
+ }
93
+
94
+ before{ sub.attr_encrypted :with_aad, aad: aad }
95
+
96
+ it "calls the proc with the object being encrypted" do
97
+ expect(aad).to receive(:[]).with(subject).and_call_original
98
+ subject.with_aad = "hi"
99
+ end
100
+
101
+ it "encrypts the value with the string returned for auth data" do
102
+ expect(cipher).to receive(:encrypt).with("hello", key: key, aad: aad[subject]).and_call_original
103
+ subject.with_aad = "hello"
104
+ end
105
+ it "decrypts the encrypted value" do
106
+ subject.with_aad = "hello"
107
+ expect(subject.with_aad).to eq("hello")
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+
114
+ end
@@ -13,7 +13,7 @@ describe Slosilo::Adapters::FileAdapter do
13
13
  describe "#get_key" do
14
14
  context "when given key does not exist" do
15
15
  it "returns nil" do
16
- subject.get_key(:whatever).should_not be
16
+ expect(subject.get_key(:whatever)).not_to be
17
17
  end
18
18
  end
19
19
  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
- lambda { subject.put_key id, key }.should 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
@@ -30,11 +30,11 @@ describe Slosilo::Adapters::FileAdapter do
30
30
  let(:key_encrypted) { "encrypted key" }
31
31
  let(:fname) { "#{dir}/#{id}.key" }
32
32
  it "creates the key" do
33
- Slosilo::EncryptedAttributes.should_receive(:encrypt).with(key.to_der).and_return key_encrypted
34
- File.should_receive(:write).with(fname, key_encrypted)
35
- File.should_receive(:chmod).with(0400, fname)
33
+ expect(Slosilo::EncryptedAttributes).to receive(:encrypt).with(key.to_der).and_return key_encrypted
34
+ expect(File).to receive(:write).with(fname, key_encrypted)
35
+ expect(File).to receive(:chmod).with(0400, fname)
36
36
  subject.put_key id, key
37
- subject.instance_variable_get("@keys")[id].should == key
37
+ expect(subject.instance_variable_get("@keys")[id]).to eq(key)
38
38
  end
39
39
  end
40
40
  end
@@ -45,7 +45,7 @@ describe Slosilo::Adapters::FileAdapter do
45
45
  it "iterates over each key" do
46
46
  results = []
47
47
  adapter.each { |id,k| results << { id => k } }
48
- results.should == [ { one: :onek}, {two: :twok } ]
48
+ expect(results).to eq([ { one: :onek}, {two: :twok } ])
49
49
  end
50
50
  end
51
51
 
@@ -60,13 +60,13 @@ describe Slosilo::Adapters::FileAdapter do
60
60
 
61
61
  describe '#get_key' do
62
62
  it "loads and decrypts the key" do
63
- adapter.get_key(id).should == key
63
+ expect(adapter.get_key(id)).to eq(key)
64
64
  end
65
65
  end
66
66
 
67
67
  describe '#get_by_fingerprint' do
68
68
  it "can look up a key by a fingerprint" do
69
- adapter.get_by_fingerprint(key_fingerprint).should == [key, id]
69
+ expect(adapter.get_by_fingerprint(key_fingerprint)).to eq([key, id])
70
70
  end
71
71
  end
72
72
 
@@ -74,7 +74,7 @@ describe Slosilo::Adapters::FileAdapter do
74
74
  it "enumerates the keys" do
75
75
  results = []
76
76
  adapter.each { |id,k| results << { id => k } }
77
- results.should == [ { id => key } ]
77
+ expect(results).to eq([ { id => key } ])
78
78
  end
79
79
  end
80
80
  end
@@ -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
@@ -1,41 +1,56 @@
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
 
6
9
  subject { key }
7
- its(:to_der) { should == rsa.to_der }
8
- its(:to_s) { should == rsa.public_key.to_pem }
9
- its(:fingerprint) { should == key_fingerprint }
10
- it { should be_private }
10
+
11
+ describe '#to_der' do
12
+ subject { super().to_der }
13
+ it { is_expected.to eq(rsa.to_der) }
14
+ end
15
+
16
+ describe '#to_s' do
17
+ subject { super().to_s }
18
+ it { is_expected.to eq(rsa.public_key.to_pem) }
19
+ end
20
+
21
+ describe '#fingerprint' do
22
+ subject { super().fingerprint }
23
+ it { is_expected.to eq(key_fingerprint) }
24
+ end
25
+ it { is_expected.to be_private }
11
26
 
12
27
  context "with identical key" do
13
28
  let(:other) { Slosilo::Key.new rsa.to_der }
14
29
  it "is equal" do
15
- subject.should == other
30
+ expect(subject).to eq(other)
16
31
  end
17
32
 
18
33
  it "is eql?" do
19
- subject.eql?(other).should be_true
34
+ expect(subject.eql?(other)).to be_truthy
20
35
  end
21
36
 
22
37
  it "has equal hash" do
23
- subject.hash.should == other.hash
38
+ expect(subject.hash).to eq(other.hash)
24
39
  end
25
40
  end
26
41
 
27
42
  context "with a different key" do
28
43
  let(:other) { Slosilo::Key.new another_rsa }
29
44
  it "is not equal" do
30
- subject.should_not == other
45
+ expect(subject).not_to eq(other)
31
46
  end
32
47
 
33
48
  it "is not eql?" do
34
- subject.eql?(other).should_not be_true
49
+ expect(subject.eql?(other)).not_to be_truthy
35
50
  end
36
51
 
37
52
  it "has different hash" do
38
- subject.hash.should_not == other.hash
53
+ expect(subject.hash).not_to eq(other.hash)
39
54
  end
40
55
  end
41
56
 
@@ -54,29 +69,29 @@ describe Slosilo::Key do
54
69
  it "generates a symmetric encryption key and encrypts the plaintext with the public key" do
55
70
  ctxt, skey = subject.encrypt plaintext
56
71
  pskey = rsa.private_decrypt skey
57
- Slosilo::Symmetric.new.decrypt(ctxt, key: pskey).should == plaintext
72
+ expect(Slosilo::Symmetric.new.decrypt(ctxt, key: pskey)).to eq(plaintext)
58
73
  end
59
74
  end
60
75
 
61
76
  describe '#encrypt_message' do
62
77
  it "#encrypts a message and then returns the result as a single string" do
63
- subject.should_receive(:encrypt).with(plaintext).and_return ['fake ciphertext', 'fake key']
64
- subject.encrypt_message(plaintext).should == 'fake keyfake ciphertext'
78
+ expect(subject).to receive(:encrypt).with(plaintext).and_return ['fake ciphertext', 'fake key']
79
+ expect(subject.encrypt_message(plaintext)).to eq('fake keyfake ciphertext')
65
80
  end
66
81
  end
67
82
 
68
- let(:ciphertext) { "\x8B\xE6\xEC\x8C\xAB\xA4\xC0\x8EF\"\x0F\xD5Yh\xA1\aq\x00\xF5\xAC\xAB\v\a\xEC\xF6G\xA6\e\x14N\xFF\x11\x98\xDA\x19\xB5\x8994_:\xA0\xF8\x06l\xDC\x9B\xB1\xE8z\x83\xCC\x9A\x02E\x02tOhu\x92F]h".force_encoding("ASCII-8BIT") }
69
- let(:skey) { "\x15\xFF\xD4>\x9C\xF4\x0F\xB6\x04C\x18\xC1\x96\xC3\xE0T\xA3\xF5\xE8\x17\xA6\xE0\x86~rrw\xC3\xDF\x11)$\x9B\r@\x0E\xE4Zv\rw-\xFC\x1C\x84\x17\xBD\xDB\x12u\xCD\r\xFEo\xD5\xC8[\x8FEA\\\xA9\xA2F#\x8BH -\xFA\xF7\xA9\xEBf\x97\xAAT}\x8E*\xC0r\x944p\xD0\x9A\xE7\xBD\a;O\f\xEF\xE4B.y\xFA\xB4e@O\xBB\x15>y\xC9\t=\x01\xE1J\xF3X\xA9\x9E3\x04^H\x1F\xFF\x19C\x93ve)@\xF7\t_\xCF\xDE\xF1\xB7]\x83lL\xB8%A\x93p{\xE9Y\xBAu\xCE\x99T\xDC\xDF\xE7\x0FD%\xB9AXb\x1CW\x94$P\xBB\xE1g\xDEE\t\xC4\x92\x9E\xFEt\xDF\xD0\xEA\x03\xC4\x12\xA9\x02~u\xF4\x92 ;\xA0\xCE.\b+)\x05\xEDo\xA5cF\xF8\x12\xD7F\x97\xE44\xBF\xF1@\xA5\xC5\xA8\xE5\a\xE8ra<\x04\xB5\xA2\f\t\xF7T\x97\e\xF5(^\xAB\xA5%84\x10\xD1\x13e<\xCA/\xBF}%\xF6\xB1%\xB2".force_encoding("ASCII-8BIT") }
83
+ let(:ciphertext){ "G\xAD^\x17\x11\xBBQ9-b\x14\xF6\x92#Q0x\xF4\xAD\x1A\x92\xC3VZW\x89\x8E\x8Fg\x93\x05B\xF8\xD6O\xCFGCTp\b~\x916\xA3\x9AN\x8D\x961\x1F\xA3mSf&\xAD\xA77/]z\xA89\x01\xA7\xA9\x92\f".force_encoding('ASCII-8BIT') }
84
+ let(:skey){ "\x82\x93\xFAA\xA6wQA\xE1\xB5\xA6b\x8C.\xCF#I\x86I\x83u\x99\rTA\xEF\xC4\x91\xC5)-\xEBQ\xB1\xC0\xC6\xFF\x90L\xFE\x1E\x15\x81\x12\x16\xDD:A\xC5d\xE1B\xD2f@\xB8o\xB7+N\xB7\n\x92\xDC\x9E\xE3\x83\xB8>h\a\xC7\xCC\xCF\xD0t\x06\x8B\xA8\xBF\xEFe\xA4{\x88\f\xDD\roF\xEB.\xDA\xBF\x9D_0>\xF03c'\x1F!)*-\x19\x97\xAC\xD2\x1F(,6h\a\x93\xDB\x8E\x97\xF9\x1A\x11\x84\x11t\xD9\xB2\x85\xB0\x12\x7F\x03\x00O\x8F\xBE#\xFFb\xA5w\xF3g\xCF\xB4\xF2\xB7\xDBiA=\xA8\xFD1\xEC\xBF\xD7\x8E\xB6W>\x03\xACNBa\xBF\xFD\xC6\xB32\x8C\xE2\xF1\x87\x9C\xAE6\xD1\x12\vkl\xBB\xA0\xED\x9A\xEE6\xF2\xD9\xB4LL\xE2h/u_\xA1i=\x11x\x8DGha\x8EG\b+\x84[\x87\x8E\x01\x0E\xA5\xB0\x9F\xE9vSl\x18\xF3\xEA\xF4NH\xA8\xF1\x81\xBB\x98\x01\xE8p]\x18\x11f\xA3K\xA87c\xBB\x13X~K\xA2".force_encoding('ASCII-8BIT') }
70
85
  describe '#decrypt' do
71
86
  it "decrypts the symmetric key and then uses it to decrypt the ciphertext" do
72
- subject.decrypt(ciphertext, skey).should == plaintext
87
+ expect(subject.decrypt(ciphertext, skey)).to eq(plaintext)
73
88
  end
74
89
  end
75
90
 
76
91
  describe '#decrypt_message' do
77
92
  it "splits the message into key and rest, then #decrypts it" do
78
- subject.should_receive(:decrypt).with(ciphertext, skey).and_return plaintext
79
- subject.decrypt_message(skey + ciphertext).should == plaintext
93
+ expect(subject).to receive(:decrypt).with(ciphertext, skey).and_return plaintext
94
+ expect(subject.decrypt_message(skey + ciphertext)).to eq(plaintext)
80
95
  end
81
96
  end
82
97
 
@@ -85,22 +100,30 @@ describe Slosilo::Key do
85
100
  subject { Slosilo::Key.new }
86
101
  let (:rsa) { double "key" }
87
102
  it "generates a new key pair" do
88
- OpenSSL::PKey::RSA.should_receive(:new).with(2048).and_return(rsa)
89
- subject.key.should == rsa
103
+ expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa)
104
+ expect(subject.key).to eq(rsa)
90
105
  end
91
106
  end
92
107
  context "when given an armored key" do
93
108
  subject { Slosilo::Key.new rsa.to_der }
94
- its(:to_der) { should == rsa.to_der }
109
+
110
+ describe '#to_der' do
111
+ subject { super().to_der }
112
+ it { is_expected.to eq(rsa.to_der) }
113
+ end
95
114
  end
96
115
  context "when given a key instance" do
97
116
  subject { Slosilo::Key.new rsa }
98
- its(:to_der) { should == rsa.to_der }
117
+
118
+ describe '#to_der' do
119
+ subject { super().to_der }
120
+ it { is_expected.to eq(rsa.to_der) }
121
+ end
99
122
  end
100
123
  context "when given something else" do
101
124
  subject { Slosilo::Key.new "foo" }
102
125
  it "fails early" do
103
- expect { subject }.to raise_error
126
+ expect { subject }.to raise_error ArgumentError
104
127
  end
105
128
  end
106
129
  end
@@ -108,21 +131,34 @@ describe Slosilo::Key do
108
131
  describe "#sign" do
109
132
  context "when given a hash" do
110
133
  it "converts to a sorted array and signs that" do
111
- key.should_receive(:sign_string).with '[["a",3],["b",42]]'
134
+ expect(key).to receive(:sign_string).with '[["a",3],["b",42]]'
112
135
  key.sign b: 42, a: 3
113
136
  end
114
137
  end
115
138
  context "when given an array" do
116
139
  it "signs a JSON representation instead" do
117
- key.should_receive(:sign_string).with '[2,[42,2]]'
140
+ expect(key).to receive(:sign_string).with '[2,[42,2]]'
118
141
  key.sign [2, [42, 2]]
119
142
  end
120
143
  end
121
144
  context "when given a string" do
122
145
  let(:expected_signature) { "d[\xA4\x00\x02\xC5\x17\xF5P\x1AD\x91\xF9\xC1\x00P\x0EG\x14,IN\xDE\x17\xE1\xA2a\xCC\xABR\x99'\xB0A\xF5~\x93M/\x95-B\xB1\xB6\x92!\x1E\xEA\x9C\v\xC2O\xA8\x91\x1C\xF9\x11\x92a\xBFxm-\x93\x9C\xBBoM\x92%\xA9\xD06$\xC1\xBC.`\xF8\x03J\x16\xE1\xB0c\xDD\xBF\xB0\xAA\xD7\xD4\xF4\xFC\e*\xAB\x13A%-\xD3\t\xA5R\x18\x01let6\xC8\xE9\"\x7F6O\xC7p\x82\xAB\x04J(IY\xAA]b\xA4'\xD6\x873`\xAB\x13\x95g\x9C\x17\xCAB\xF8\xB9\x85B:^\xC5XY^\x03\xEA\xB6V\x17b2\xCA\xF5\xD6\xD4\xD2\xE3u\x11\xECQ\x0Fb\x14\xE2\x04\xE1<a\xC5\x01eW-\x15\x01X\x81K\x1A\xE5A\vVj\xBF\xFC\xFE#\xD5\x93y\x16\xDC\xB4\x8C\xF0\x02Y\xA8\x87i\x01qC\xA7#\xE8\f\xA5\xF0c\xDEJ\xB0\xDB BJ\x87\xA4\xB0\x92\x80\x03\x95\xEE\xE9\xB8K\xC0\xE3JbE-\xD4\xCBP\\\x13S\"\eZ\xE1\x93\xFDa pinch of salt".force_encoding("ASCII-8BIT") }
123
146
  it "signs it" do
124
- key.stub shake_salt: 'a pinch of salt'
125
- key.sign("this sentence is not this sentence").should == expected_signature
147
+ allow(key).to receive_messages shake_salt: 'a pinch of salt'
148
+ expect(key.sign("this sentence is not this sentence")).to eq(expected_signature)
149
+ end
150
+ end
151
+
152
+ context "when given a Hash containing non-ascii characters" do
153
+ let(:unicode){ "adèle.dupuis" }
154
+ let(:encoded){
155
+ unicode.dup.tap{|s| s.force_encoding Encoding::ASCII_8BIT}
156
+ }
157
+ let(:hash){ {"data" => unicode} }
158
+
159
+ it "converts the value to raw bytes before signing it" do
160
+ expect(key).to receive(:sign_string).with("[[\"data\",\"#{encoded}\"]]").and_call_original
161
+ key.sign hash
126
162
  end
127
163
  end
128
164
  end
@@ -136,44 +172,87 @@ describe Slosilo::Key do
136
172
  let(:expected_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") }
137
173
  let(:expected_token) { token_to_sign.merge "signature" => expected_signature, "key" => key_fingerprint }
138
174
  before do
139
- key.stub shake_salt: salt
140
- Time.stub new: time
175
+ allow(key).to receive_messages shake_salt: salt
176
+ allow(Time).to receive_messages new: time
141
177
  end
142
178
  subject { key.signed_token data }
143
- it { should == expected_token }
179
+ it { is_expected.to eq(expected_token) }
144
180
  end
145
-
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
+
146
225
  describe "#token_valid?" do
147
226
  let(:data) { { "foo" => :bar } }
148
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") }
149
228
  let(:token) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC", "signature" => signature } }
150
- before { Time.stub now: Time.new(2012,1,1,1,2,1,0) }
229
+ before { allow(Time).to receive_messages now: Time.new(2012,1,1,1,2,1,0) }
151
230
  subject { key.token_valid? token }
152
- it { should be_true }
231
+ it { is_expected.to be_truthy }
153
232
 
154
233
  it "doesn't check signature on the advisory key field" do
155
- key.token_valid?(token.merge "key" => key_fingerprint).should be_true
234
+ expect(key.token_valid?(token.merge "key" => key_fingerprint)).to be_truthy
156
235
  end
157
236
 
158
237
  it "rejects the token if the key field is present and doesn't match" do
159
- key.token_valid?(token.merge "key" => "this is not the key you are looking for").should_not be_true
238
+ expect(key.token_valid?(token.merge "key" => "this is not the key you are looking for")).not_to be_truthy
160
239
  end
161
240
 
162
241
  context "when token is 1 hour old" do
163
- before { Time.stub now: Time.new(2012,1,1,2,1,1,0) }
164
- it { should be_false }
242
+ before { allow(Time).to receive_messages now: Time.new(2012,1,1,2,1,1,0) }
243
+ it { is_expected.to be_falsey }
165
244
  context "when timestamp in the token is changed accordingly" do
166
245
  let(:token) { { "data" => data, "timestamp" => "2012-01-01 02:00:01 UTC", "signature" => signature } }
167
- it { should be_false }
246
+ it { is_expected.to be_falsey }
168
247
  end
169
248
  end
170
249
  context "when the data is changed" do
171
250
  let(:data) { { "foo" => :baz } }
172
- it { should be_false }
251
+ it { is_expected.to be_falsey }
173
252
  end
174
253
  context "when RSA decrypt raises an error" do
175
- before { OpenSSL::PKey::RSA.any_instance.should_receive(:public_decrypt).and_raise(OpenSSL::PKey::RSAError) }
176
- it { should be_false }
254
+ before { expect_any_instance_of(OpenSSL::PKey::RSA).to receive(:public_decrypt).and_raise(OpenSSL::PKey::RSAError) }
255
+ it { is_expected.to be_falsey }
177
256
  end
178
257
  end
179
258
  end