slosilo 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +24 -11
- data/lib/slosilo/adapters/sequel_adapter.rb +1 -1
- data/lib/slosilo/attr_encrypted.rb +29 -6
- data/lib/slosilo/symmetric.rb +30 -9
- data/lib/slosilo/version.rb +1 -1
- data/spec/encrypted_attributes_spec.rb +114 -0
- data/spec/key_spec.rb +2 -2
- data/spec/symmetric_spec.rb +39 -17
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 761d6e24aaa95427bbc3d7449bc980b29b602bca
|
4
|
+
data.tar.gz: 79950d2ab62c71a6f9ebdb1f68afdfd13c53894e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de1ab282bd0067317a21e2d40dbb0668c737e487bc2e29e25120f460191d2c8a550b25ec98719a4d99d72176a73f0b47007703733ad5a0e77077a13c1fc54d6d
|
7
|
+
data.tar.gz: e7444706c7f1af27f3a7eebf350252b37a033ca4b225a5d8ea6d24afbf49f40d42e6613187f8f0fe2322c9bf73dc1b54d5e36796ba7ef10aa8c95ff54f5aa8b3
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Slosilo is providing a ruby interface to some cryptographic primitives:
|
4
4
|
- symmetric encryption,
|
5
|
-
- a mixin for easy encryption of object attributes
|
5
|
+
- a mixin for easy encryption of object attributes,
|
6
6
|
- asymmetric encryption and signing,
|
7
7
|
- a keystore in a postgres sequel db -- it allows easy storage and retrieval of keys,
|
8
8
|
- a keystore in files.
|
@@ -17,6 +17,20 @@ And then execute:
|
|
17
17
|
|
18
18
|
$ bundle
|
19
19
|
|
20
|
+
## Compatibility
|
21
|
+
|
22
|
+
Version 2.0 introduced new symmetric encryption scheme using AES-256-GCM
|
23
|
+
for authenticated encryption. It allows you to provide AAD on all symmetric
|
24
|
+
encryption primitives. It's also **NOT COMPATIBLE** with CBC used in version <2.
|
25
|
+
|
26
|
+
This means you'll have to migrate all your existing data. There's no easy way to
|
27
|
+
do this currently provided; it's recommended to create a database migration and
|
28
|
+
put relevant code fragments in it directly. (This will also have the benefit of making
|
29
|
+
the migration self-contained.)
|
30
|
+
|
31
|
+
Since symmetric encryption is used in processing asymetrically encrypted messages,
|
32
|
+
this incompatibility extends to those too.
|
33
|
+
|
20
34
|
## Usage
|
21
35
|
|
22
36
|
### Symmetric encryption
|
@@ -24,12 +38,14 @@ And then execute:
|
|
24
38
|
```ruby
|
25
39
|
sym = Slosilo::Symmetric.new
|
26
40
|
key = sym.random_key
|
27
|
-
|
41
|
+
# additional authenticated data
|
42
|
+
message_id = "message 001"
|
43
|
+
ciphertext = sym.encrypt "secret message", key: key, aad: message_id
|
28
44
|
```
|
29
45
|
|
30
46
|
```ruby
|
31
47
|
sym = Slosilo::Symmetric.new
|
32
|
-
message = sym.decrypt ciphertext, key: key
|
48
|
+
message = sym.decrypt ciphertext, key: key, aad: message_id
|
33
49
|
```
|
34
50
|
|
35
51
|
### Encryption mixin
|
@@ -39,11 +55,15 @@ require 'slosilo'
|
|
39
55
|
|
40
56
|
class Foo
|
41
57
|
attr_accessor :foo
|
42
|
-
attr_encrypted :foo
|
58
|
+
attr_encrypted :foo, aad: :id
|
43
59
|
|
44
60
|
def raw_foo
|
45
61
|
@foo
|
46
62
|
end
|
63
|
+
|
64
|
+
def id
|
65
|
+
"unique record id"
|
66
|
+
end
|
47
67
|
end
|
48
68
|
|
49
69
|
Slosilo::encryption_key = Slosilo::Symmetric.new.random_key
|
@@ -56,13 +76,6 @@ obj.foo # => "bar"
|
|
56
76
|
|
57
77
|
You can safely use it in ie. ActiveRecord::Base or Sequel::Model subclasses.
|
58
78
|
|
59
|
-
#### Warning
|
60
|
-
|
61
|
-
The encrypted data is not authenticated; it's intended to prevent
|
62
|
-
opportunistic access to secrets by a third party which gets hold of a database
|
63
|
-
dump. *IT DOES NOT prevent tampering.* If your threat model includes an attacker
|
64
|
-
which can modify the database, `attr_encrypted` by itself IS NOT SECURE.
|
65
|
-
|
66
79
|
### Asymmetric encryption and signing
|
67
80
|
|
68
81
|
```ruby
|
@@ -5,21 +5,44 @@ module Slosilo
|
|
5
5
|
# so we encrypt sensitive attributes before storing them
|
6
6
|
module EncryptedAttributes
|
7
7
|
module ClassMethods
|
8
|
+
|
9
|
+
# @param options [Hash]
|
10
|
+
# @option :aad [#to_proc, #to_s] Provide additional authenticated data for
|
11
|
+
# encryption. This should be something unique to the instance having
|
12
|
+
# this attribute, such as a primary key; this will ensure that an attacker can't swap
|
13
|
+
# values around -- trying to decrypt value with a different auth data will fail.
|
14
|
+
# This means you have to be able to recover it in order to decrypt attributes.
|
15
|
+
# The following values are accepted:
|
16
|
+
#
|
17
|
+
# * Something proc-ish: will be called with self each time auth data is needed.
|
18
|
+
# * Something stringish: will be to_s-d and used for all instances as auth data.
|
19
|
+
# Note that this will only prevent swapping in data using another string.
|
20
|
+
#
|
21
|
+
# The recommended way to use this option is to pass a proc-ish that identifies the record.
|
22
|
+
# Note the proc-ish can be a simple method name; for example in case of a Sequel::Model:
|
23
|
+
# attr_encrypted :secret, aad: :pk
|
8
24
|
def attr_encrypted *a
|
25
|
+
options = a.last.is_a?(Hash) ? a.pop : {}
|
26
|
+
aad = options[:aad]
|
27
|
+
# note nil.to_s is "", which is exactly the right thing
|
28
|
+
auth_data = aad.respond_to?(:to_proc) ? aad.to_proc : proc{ |_| aad.to_s }
|
29
|
+
raise ":aad proc must take one argument" unless auth_data.arity.abs == 1 # take abs to allow *args arity, -1
|
30
|
+
|
9
31
|
# push a module onto the inheritance hierarchy
|
10
32
|
# this allows calling super in classes
|
11
33
|
include(accessors = Module.new)
|
12
34
|
accessors.module_eval do
|
13
35
|
a.each do |attr|
|
14
36
|
define_method "#{attr}=" do |value|
|
15
|
-
super(EncryptedAttributes.encrypt
|
37
|
+
super(EncryptedAttributes.encrypt(value, aad: auth_data[self]))
|
16
38
|
end
|
17
39
|
define_method attr do
|
18
|
-
EncryptedAttributes.decrypt(super())
|
40
|
+
EncryptedAttributes.decrypt(super(), aad: auth_data[self])
|
19
41
|
end
|
20
42
|
end
|
21
43
|
end
|
22
44
|
end
|
45
|
+
|
23
46
|
end
|
24
47
|
|
25
48
|
def self.included base
|
@@ -27,14 +50,14 @@ module Slosilo
|
|
27
50
|
end
|
28
51
|
|
29
52
|
class << self
|
30
|
-
def encrypt value
|
53
|
+
def encrypt value, opts={}
|
31
54
|
return nil unless value
|
32
|
-
cipher.encrypt value, key: key
|
55
|
+
cipher.encrypt value, key: key, aad: opts[:aad]
|
33
56
|
end
|
34
57
|
|
35
|
-
def decrypt ctxt
|
58
|
+
def decrypt ctxt, opts={}
|
36
59
|
return nil unless ctxt
|
37
|
-
cipher.decrypt ctxt, key: key
|
60
|
+
cipher.decrypt ctxt, key: key, aad: opts[:aad]
|
38
61
|
end
|
39
62
|
|
40
63
|
def key
|
data/lib/slosilo/symmetric.rb
CHANGED
@@ -1,25 +1,40 @@
|
|
1
1
|
module Slosilo
|
2
2
|
class Symmetric
|
3
|
+
VERSION_MAGIC = 'G'
|
4
|
+
TAG_LENGTH = 16
|
5
|
+
|
3
6
|
def initialize
|
4
|
-
@cipher = OpenSSL::Cipher.new '
|
7
|
+
@cipher = OpenSSL::Cipher.new 'aes-256-gcm' # NB: has to be lower case for whatever reason.
|
5
8
|
end
|
6
|
-
|
9
|
+
|
10
|
+
# This lets us do a final sanity check in migrations from older encryption versions
|
11
|
+
def cipher_name
|
12
|
+
@cipher.name
|
13
|
+
end
|
14
|
+
|
7
15
|
def encrypt plaintext, opts = {}
|
8
16
|
@cipher.reset
|
9
17
|
@cipher.encrypt
|
10
|
-
@cipher.key = opts[:key]
|
18
|
+
@cipher.key = (opts[:key] or raise("missing :key option"))
|
11
19
|
@cipher.iv = iv = random_iv
|
12
|
-
|
13
|
-
|
20
|
+
@cipher.auth_data = opts[:aad] || "" # Nothing good happens if you set this to nil, or don't set it at all
|
21
|
+
ctext = @cipher.update(plaintext) + @cipher.final
|
22
|
+
tag = @cipher.auth_tag(TAG_LENGTH)
|
23
|
+
"#{VERSION_MAGIC}#{tag}#{iv}#{ctext}"
|
14
24
|
end
|
15
|
-
|
25
|
+
|
16
26
|
def decrypt ciphertext, opts = {}
|
27
|
+
version, tag, iv, ctext = unpack ciphertext
|
28
|
+
|
29
|
+
raise "Invalid version magic: expected #{VERSION_MAGIC} but was #{version}" unless version == VERSION_MAGIC
|
30
|
+
|
17
31
|
@cipher.reset
|
18
32
|
@cipher.decrypt
|
19
33
|
@cipher.key = opts[:key]
|
20
|
-
@cipher.iv
|
21
|
-
|
22
|
-
|
34
|
+
@cipher.iv = iv
|
35
|
+
@cipher.auth_tag = tag
|
36
|
+
@cipher.auth_data = opts[:aad] || ""
|
37
|
+
@cipher.update(ctext) + @cipher.final
|
23
38
|
end
|
24
39
|
|
25
40
|
def random_iv
|
@@ -29,5 +44,11 @@ module Slosilo
|
|
29
44
|
def random_key
|
30
45
|
@cipher.random_key
|
31
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# return tag, iv, ctext
|
50
|
+
def unpack msg
|
51
|
+
msg.unpack "aa#{TAG_LENGTH}a#{@cipher.iv_len}a*"
|
52
|
+
end
|
32
53
|
end
|
33
54
|
end
|
data/lib/slosilo/version.rb
CHANGED
@@ -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
|
data/spec/key_spec.rb
CHANGED
@@ -77,8 +77,8 @@ describe Slosilo::Key do
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
let(:ciphertext)
|
81
|
-
let(:skey)
|
80
|
+
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') }
|
81
|
+
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') }
|
82
82
|
describe '#decrypt' do
|
83
83
|
it "decrypts the symmetric key and then uses it to decrypt the ciphertext" do
|
84
84
|
expect(subject.decrypt(ciphertext, skey)).to eq(plaintext)
|
data/spec/symmetric_spec.rb
CHANGED
@@ -3,34 +3,56 @@ require 'spec_helper'
|
|
3
3
|
describe Slosilo::Symmetric do
|
4
4
|
# TODO transform it to class methods only?
|
5
5
|
let(:plaintext) { "quick brown fox jumped over the lazy dog" }
|
6
|
+
let(:auth_data) { "some record id" }
|
6
7
|
let(:key) { "^\xBAIv\xDB1\x0Fi\x04\x11\xFD\x14\xA7\xCD\xDFf\x93\xFE\x93}\v\x01\x11\x98\x14\xE0;\xC1\xE2 v\xA5".force_encoding("ASCII-8BIT") }
|
7
|
-
let(:iv) { "\
|
8
|
-
let(:ciphertext) { "\
|
8
|
+
let(:iv) { "\xD9\xABn\x01b\xFA\xBD\xC2\xE5\xEA\x01\xAC".force_encoding("ASCII-8BIT") }
|
9
|
+
let(:ciphertext) { "G^W1\x9C\xD4\xCC\x87\xD3\xFF\x86[\x0E3\xC0\xC8^\xD9\xABn\x01b\xFA\xBD\xC2\xE5\xEA\x01\xAC\x9E\xB9:\xF7\xD4ebeq\xDC \xC0sG\xA4\xAE,\xB8A|\x97\xBC\xFD\x85\xE1\xB93\x95>\xBD\n\x05\xFB\x15\x1F\x06#3M9".force_encoding('ASCII-8BIT') }
|
10
|
+
|
9
11
|
describe '#encrypt' do
|
10
|
-
it "encrypts with AES-256-
|
12
|
+
it "encrypts with AES-256-GCM" do
|
11
13
|
allow(subject).to receive_messages random_iv: iv
|
12
|
-
expect(subject.encrypt(plaintext, key: key)).to eq(ciphertext)
|
14
|
+
expect(subject.encrypt(plaintext, key: key, aad: auth_data)).to eq(ciphertext)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
18
|
describe '#decrypt' do
|
17
|
-
it "decrypts with AES-256-
|
18
|
-
expect(subject.decrypt(ciphertext, key: key)).to eq(plaintext)
|
19
|
+
it "decrypts with AES-256-GCM" do
|
20
|
+
expect(subject.decrypt(ciphertext, key: key, aad: auth_data)).to eq(plaintext)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
context "when the ciphertext has been messed with" do
|
25
|
+
let(:ciphertext) { "pwnd!" } # maybe we should do something more realistic like add some padding?
|
26
|
+
it "raises an exception" do
|
27
|
+
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception
|
28
|
+
end
|
29
|
+
context "by adding a trailing 0" do
|
30
|
+
let(:new_ciphertext){ ciphertext + '\0' }
|
31
|
+
it "raises an exception" do
|
32
|
+
expect{ subject.decrypt(new_ciphertext, key: key, aad: auth_data) }.to raise_exception
|
33
|
+
end
|
34
|
+
end
|
19
35
|
end
|
20
|
-
|
21
|
-
context "when
|
22
|
-
let(:
|
23
|
-
let(:
|
24
|
-
|
25
|
-
|
36
|
+
|
37
|
+
context "when no auth_data is given" do
|
38
|
+
let(:auth_data){""}
|
39
|
+
let(:ciphertext){ "Gm\xDAT\xE8I\x9F\xB7\xDC\xBB\x84\xD3Q#\x1F\xF4\x8C\aV\x93\x8F_\xC7\xBC87\xC9U\xF1\xAF\x8A\xD62\x1C5H\x86\x17\x19=B~Y*\xBC\x9D\eJeTx\x1F\x02l\t\t\xD3e\xA4\x11\x13y*\x95\x9F\xCD\xC4@\x9C"}
|
40
|
+
|
41
|
+
it "decrypts the message" do
|
42
|
+
expect(subject.decrypt(ciphertext, key: key, aad: auth_data)).to eq(plaintext)
|
43
|
+
end
|
44
|
+
|
45
|
+
context "and the ciphertext has been messed with" do
|
46
|
+
it "raises an exception" do
|
47
|
+
expect{ subject.decrypt(ciphertext + "\0\0\0", key: key, aad: auth_data)}.to raise_exception
|
48
|
+
end
|
26
49
|
end
|
27
50
|
end
|
28
51
|
|
29
|
-
context "when the
|
30
|
-
let(:
|
31
|
-
|
32
|
-
|
33
|
-
expect(subject.decrypt(ciphertext, key: key)).to eq("zGptmL3vd4obi1vqSiWHt/Ias2k+6qDtuq9vdow8jNA=")
|
52
|
+
context "when the auth data doesn't match" do
|
53
|
+
let(:auth_data){ "asdf" }
|
54
|
+
it "raises an exception" do
|
55
|
+
expect{ subject.decrypt(ciphertext, key: key, aad: auth_data)}.to raise_exception
|
34
56
|
end
|
35
57
|
end
|
36
58
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slosilo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rafał Rzepecki
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- lib/slosilo/version.rb
|
139
139
|
- lib/tasks/slosilo.rake
|
140
140
|
- slosilo.gemspec
|
141
|
+
- spec/encrypted_attributes_spec.rb
|
141
142
|
- spec/file_adapter_spec.rb
|
142
143
|
- spec/key_spec.rb
|
143
144
|
- spec/keystore_spec.rb
|
@@ -166,11 +167,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
167
|
version: '0'
|
167
168
|
requirements: []
|
168
169
|
rubyforge_project:
|
169
|
-
rubygems_version: 2.
|
170
|
+
rubygems_version: 2.0.14
|
170
171
|
signing_key:
|
171
172
|
specification_version: 4
|
172
173
|
summary: Store SSL keys in a database
|
173
174
|
test_files:
|
175
|
+
- spec/encrypted_attributes_spec.rb
|
174
176
|
- spec/file_adapter_spec.rb
|
175
177
|
- spec/key_spec.rb
|
176
178
|
- spec/keystore_spec.rb
|