syde 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
data/LICENSE.mit ADDED
@@ -0,0 +1,21 @@
1
+ (MIT License)
2
+
3
+ Copyright (c) 2010 Adam Prescott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ Syde
2
+ ====
3
+
4
+ Syde is a *sy*mmetric *d*ata *e*ncryption library written in Ruby, licensed under the MIT license. It provides a saved encrypted data storage under a single password.
5
+
6
+ # How do I use this thing?
7
+
8
+ To install from RubyGems:
9
+
10
+ gem install syde
11
+
12
+ To get the source:
13
+
14
+ git clone https://github.com/aprescott/syde.git
15
+
16
+ To run the tests with the source:
17
+
18
+ rake test
19
+
20
+ To contribute:
21
+
22
+ * Fork it
23
+ * Make a new feature branch: `git checkout -b some-new-thing master`
24
+ * Pull request
25
+
26
+ Basic usage
27
+ -----------
28
+
29
+ require "syde"
30
+
31
+ vault = Syde::Vault.create("password")
32
+ vault #=> #<Vault (locked)>
33
+ vault.unlock!("password")
34
+ vault #=> #<Vault (unlocked)>
35
+ vault.contents #=> []
36
+ vault << "something important"
37
+ vault.contents #=> ["something important"]
38
+ vault.lock
39
+ vault #=> #<Vault (locked)>
40
+
41
+ ### Reopening the vault
42
+
43
+ When you call Vault.create your vault is saved to disk, in a default location. To reopen the vault using the default location, you can use `Vault.open`. Adding contents to the vault will cause it to be saved automatically, without needing to `Vault#lock`.
44
+
45
+ ### Auto-locking
46
+
47
+ By default, there is a 5-minute auto-lock timeout period. After 5 minutes has passed since unlocking the vault, the vault will automatically lock itself. It is possible to set the time manually:
48
+
49
+ vault.unlock("password", 2 * 60)
50
+
51
+ will set the timeout period to 2 minutes. Using a non-positive length of time will not unlock the vault. To unlock the vault indefinitely, use `unlock!`.
52
+
53
+ ### Deleting contents of the vault
54
+
55
+ To delete something in the vault, use `delete`:
56
+
57
+ vault << "foo"
58
+ vault.contents
59
+ vault.delete("foo")
60
+ vault.contents
61
+
62
+ ### Modifying contents in-place
63
+
64
+ vault.contents #=> ["foo"]
65
+ string = vault.contents.first #=> "foo"
66
+ string.replace("bar")
67
+ vault.contents #=> ["bar"]
68
+
69
+ Objects in the vault are serialised and then deserialised and as such are not modifiable.
70
+
71
+ ### Available data
72
+
73
+ To see the data being stored in the vault file, use `data`. When the vault is unlocked, the contents are visible via `data`; otherwise, only the encrypted contents are visible.
74
+
75
+ ### Specifying a different file
76
+
77
+ By default the file used for vault storage is ~/.syde/storage_file.vault. To change this, when calling `create`, pass a `String` as the second argument, for the filepath to be used.
78
+
79
+ Some details
80
+ ------------
81
+
82
+ The password you give is used to encrypt, using the OpenSSL standard library, a random 4096-bit secret key generated from /dev/urandom. This secret key is then used for the encryption and decryption of the vault contents. When a vault is created it will by default have its information stored in ~/.syde/storage_file.vault. Vault contents, and the secret 4096-bit key are kept in this file in encrypted form only, and never written to the file as plaintext; when the vault is unlocked the plaintext is available to the running application.
83
+
84
+ Modifying the contents of the .vault file is not recommended, as it will cause Syde::Vault to be unable to open it.
85
+
86
+ ### Vault contents
87
+
88
+ The contents of a vault are currently a simple Array, and are serialised with YAML before being encrypted. When the vault is opened and unlocked, the ciphertext is decrypted and then deserialised back to Ruby objects.
89
+
90
+ ### Forgotten passwords
91
+
92
+ If your password is forgotten it's unlikely that you'll be able to retrieve any of the data stored in encrypted form as plaintext.
93
+
94
+ Tests
95
+ -----
96
+
97
+ `rake test` will run the set of unit tests.
98
+
99
+ Ruby versions
100
+ -------------
101
+
102
+ Should work without incident on 1.8.7.
103
+
104
+ TODO/issues
105
+ -----------
106
+
107
+ Fix encoding problems to get 1.9 support.
108
+
109
+ The intention is to have future versions support storing data associated to keys, allowing you to use
110
+
111
+ vault.add :password => "important"
112
+
113
+ This can be done with the current design by setting the contents of the vault to be just a hash.
@@ -0,0 +1,38 @@
1
+ module Syde
2
+ class Vault
3
+ module Crypto
4
+ def self.random_bytes(length)
5
+ File.open("/dev/urandom") { |f| f.read(length) }
6
+ end
7
+
8
+ def self.new_iv
9
+ cipher.random_iv
10
+ end
11
+
12
+ def self.aes(mode, key, iv, text)
13
+ c = cipher.send(mode)
14
+ c.key = digest(key)
15
+ c.iv = iv
16
+ c.update(text) << c.final
17
+ end
18
+
19
+ def self.encrypt(key, iv, plaintext)
20
+ aes(:encrypt, key, iv, plaintext)
21
+ end
22
+
23
+ def self.decrypt(key, iv, ciphertext)
24
+ aes(:decrypt, key, iv, ciphertext)
25
+ end
26
+
27
+ def self.digest(input)
28
+ OpenSSL::Digest::SHA256.digest(input)
29
+ end
30
+
31
+ private
32
+
33
+ def self.cipher
34
+ OpenSSL::Cipher::Cipher.new("aes-256-cbc")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Syde
2
+ module Errors
3
+ class PasswordIncorrectError < StandardError; end
4
+ class MissingPasswordError < StandardError; end
5
+ class AccessError < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ module Syde
2
+ class Vault
3
+ module Storage
4
+ DefaultStorageFile = File.expand_path("~/.syde/storage_file.vault")
5
+ FileUtils.touch(DefaultStorageFile) unless File.exist?(DefaultStorageFile)
6
+
7
+ def self.valid_format?(data)
8
+ data.is_a?(Hash) &&
9
+ data.keys.include?(:encrypted) &&
10
+ data.keys.include?(:plaintext) &&
11
+ data[:plaintext].keys.include?(:iv) &&
12
+ data[:plaintext].keys.include?(:secret_key_hash) &&
13
+ data[:encrypted].keys.include?(:secret_key) &&
14
+ data[:encrypted].keys.include?(:contents) &&
15
+ data[:encrypted][:contents].is_a?(String)
16
+ end
17
+
18
+ def self.read(content)
19
+ file { |f| f.read }
20
+ end
21
+
22
+ def self.write(content, file = nil)
23
+ if file
24
+ File.open(file, "w") do |f|
25
+ f << content
26
+ end
27
+ else
28
+ file("w") { |f| f << content }
29
+ end
30
+ end
31
+
32
+ def self.file(mode = "r", &block)
33
+ File.open(DefaultStorageFile, mode, &block)
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/syde/vault.rb ADDED
@@ -0,0 +1,214 @@
1
+ module Syde
2
+ class Vault
3
+ include Errors
4
+
5
+ attr_accessor :plaintext_secret_key
6
+ attr_reader :file
7
+
8
+ def self.open(file = Storage::DefaultStorageFile)
9
+ file = File.expand_path(file)
10
+ FileUtils.touch(file) unless File.exist?(file)
11
+
12
+ Vault.new(YAML.load_file(file) || "", file)
13
+ end
14
+
15
+ def self.create(password, file = Storage::DefaultStorageFile)
16
+ file = File.expand_path(file)
17
+
18
+ raise "#{file} contains content -- refusing to override." if File.exist?(file) && File.size(file) > 0
19
+
20
+ FileUtils.touch(file) unless File.exist?(file)
21
+
22
+ h = {}
23
+ [:plaintext, :encrypted].each { |e| h[e] = {} }
24
+
25
+ h[:plaintext][:iv] = Crypto.new_iv
26
+ new_secret_key = Vault.new_secret_key(password, h[:plaintext][:iv])
27
+ encrypted_key = new_secret_key[:encrypted_key]
28
+ hash = new_secret_key[:plaintext_key_hash]
29
+ plaintext_key = new_secret_key[:plaintext_key]
30
+
31
+ h[:encrypted][:secret_key] = encrypted_key
32
+ h[:plaintext][:secret_key_hash] = hash
33
+
34
+ h[:encrypted][:contents] = Crypto.encrypt(plaintext_key, h[:plaintext][:iv], YAML.dump([]))
35
+ h[:plaintext][:contents] = []
36
+
37
+ File.open(file, "w") do |f|
38
+ f << YAML.dump(h)
39
+ end
40
+
41
+ Vault.new(h, file)
42
+ end
43
+
44
+ def initialize(data, file)
45
+ @data = data
46
+ @file = file.freeze
47
+
48
+ raise ArgumentError, "unable to find any stored data." if @data.empty?
49
+ raise ArgumentError, "data is not valid." unless Storage.valid_format?(@data)
50
+ end
51
+
52
+ def data
53
+ if locked?
54
+ public_data
55
+ else
56
+ internal_data
57
+ end
58
+ end
59
+
60
+ def public_data
61
+ public_data = YAML.load(YAML.dump(internal_data))
62
+ public_data[:plaintext].delete(:contents)
63
+ public_data
64
+ end
65
+
66
+ private
67
+
68
+ def internal_data
69
+ @data
70
+ end
71
+
72
+ public
73
+
74
+ def iv
75
+ internal_data[:plaintext][:iv]
76
+ end
77
+
78
+ def secret_key_hash
79
+ internal_data[:plaintext][:secret_key_hash]
80
+ end
81
+
82
+ def decrypt_secret_key(password)
83
+ Crypto.aes(:decrypt, password, iv, internal_data[:encrypted][:secret_key])
84
+ end
85
+
86
+ def lock
87
+ internal_data[:encrypted][:contents] = Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump(internal_data[:plaintext][:contents]))
88
+ internal_data[:plaintext][:contents] = nil
89
+ @plaintext_secret_key = nil
90
+ end
91
+
92
+ def unlock!(password = nil)
93
+ raise MissingPasswordError, "no password given." unless password
94
+
95
+ plaintext_secret_key = decrypt_secret_key(password)
96
+ if Crypto.digest(plaintext_secret_key) != secret_key_hash
97
+ raise PasswordIncorrectError
98
+ else
99
+ @plaintext_secret_key = plaintext_secret_key
100
+ internal_data[:encrypted][:contents] ||= Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump([]))
101
+ internal_data[:plaintext][:contents] = YAML.load(Crypto.decrypt(@plaintext_secret_key, iv, internal_data[:encrypted][:contents]))
102
+ end
103
+ end
104
+
105
+ def unlock(password = nil, timeout = 5 * 60)
106
+ return false unless timeout > 0
107
+ unlock!(password)
108
+ start_locking_timer(timeout)
109
+ true
110
+ end
111
+
112
+ def start_locking_timer(seconds)
113
+ Thread.new do
114
+ sleep seconds
115
+ self.lock
116
+ end
117
+ end
118
+
119
+ def locked?
120
+ if plaintext_secret_key
121
+ false
122
+ else
123
+ true
124
+ end
125
+ end
126
+
127
+ def plaintext_contents
128
+ raise AccessError, "vault is locked; unable to access vault contents." if locked?
129
+
130
+ YAML.load(YAML.dump(internal_contents))
131
+ end
132
+
133
+ private
134
+
135
+ def internal_contents
136
+ internal_data[:plaintext][:contents]
137
+ end
138
+
139
+ public
140
+
141
+ def contents
142
+ if locked?
143
+ public_contents
144
+ else
145
+ plaintext_contents
146
+ end
147
+ end
148
+
149
+ def public_contents
150
+ raise AccessError, "vault is locked; unable to access vault contents." if locked?
151
+
152
+ public_data[:plaintext][:contents]
153
+ end
154
+
155
+ private
156
+
157
+ def update_contents(new_content)
158
+ internal_data[:encrypted][:contents] = Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump(internal_contents))
159
+
160
+ Storage.write(YAML.dump(public_data), file)
161
+
162
+ plaintext_contents
163
+ end
164
+
165
+ public
166
+
167
+ def contents=(new_content)
168
+ raise AccessError, "vault is locked; unable to modify vault contents." if locked?
169
+
170
+ internal_contents.replace(new_content)
171
+
172
+ update_contents(new_content)
173
+ end
174
+
175
+ def add(*contents)
176
+ raise AccessError, "vault is locked; unable to add content to vault." if locked?
177
+
178
+ contents.each do |content|
179
+ internal_contents << content
180
+ end
181
+
182
+ update_contents(internal_contents)
183
+ end
184
+ alias_method :<<, :add
185
+
186
+ def remove(*contents)
187
+ raise AccessError, "vault is locked; unable to remove content from vault." if locked?
188
+
189
+ contents.each do |content|
190
+ internal_contents.delete(content)
191
+ end
192
+
193
+ update_contents(internal_contents)
194
+ end
195
+
196
+ def status
197
+ locked? ? "locked" : "unlocked"
198
+ end
199
+
200
+ def inspect
201
+ %Q{#<Vault (#{status})>}
202
+ end
203
+
204
+ def self.new_secret_key(password, iv)
205
+ plaintext = Crypto.digest(Crypto.random_bytes(4096))
206
+ new_key = Crypto.aes(:encrypt, password, iv, plaintext)
207
+ #? plaintext = nil
208
+ #? GC.start
209
+ { :encrypted_key => new_key,
210
+ :plaintext_key_hash => Crypto.digest(plaintext),
211
+ :plaintext_key => plaintext }
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,214 @@
1
+ module Syde
2
+ class Vault
3
+ include Errors
4
+
5
+ attr_accessor :plaintext_secret_key
6
+ attr_reader :file
7
+
8
+ def self.open(file = Storage::DefaultStorageFile)
9
+ file = File.expand_path(file)
10
+ FileUtils.touch(file) unless File.exist?(file)
11
+
12
+ Vault.new(YAML.load_file(file) || "", file)
13
+ end
14
+
15
+ def self.create(password, file = Storage::DefaultStorageFile)
16
+ file = File.expand_path(file)
17
+
18
+ raise "#{file} contains content -- refusing to override." if File.exist?(file) && File.size(file) > 0
19
+
20
+ FileUtils.touch(file) unless File.exist?(file)
21
+
22
+ h = {}
23
+ [:plaintext, :encrypted].each { |e| h[e] = {} }
24
+
25
+ h[:plaintext][:iv] = Crypto.new_iv
26
+ new_secret_key = Vault.new_secret_key(password, h[:plaintext][:iv])
27
+ encrypted_key = new_secret_key[:encrypted_key]
28
+ hash = new_secret_key[:plaintext_key_hash]
29
+ plaintext_key = new_secret_key[:plaintext_key]
30
+
31
+ h[:encrypted][:secret_key] = encrypted_key
32
+ h[:plaintext][:secret_key_hash] = hash
33
+
34
+ h[:encrypted][:contents] = Crypto.encrypt(plaintext_key, h[:plaintext][:iv], YAML.dump([]))
35
+ h[:plaintext][:contents] = []
36
+
37
+ File.open(file, "w") do |f|
38
+ f << YAML.dump(h)
39
+ end
40
+
41
+ Vault.new(h, file)
42
+ end
43
+
44
+ def initialize(data, file)
45
+ @data = data
46
+ @file = file.freeze
47
+
48
+ raise ArgumentError, "unable to find any stored data." if @data.empty?
49
+ raise ArgumentError, "data is not valid." unless Storage.valid_format?(@data)
50
+ end
51
+
52
+ def data
53
+ if locked?
54
+ public_data
55
+ else
56
+ internal_data
57
+ end
58
+ end
59
+
60
+ def public_data
61
+ public_data = YAML.load(YAML.dump(internal_data))
62
+ public_data[:plaintext].delete(:contents)
63
+ public_data
64
+ end
65
+
66
+ private
67
+
68
+ def internal_data
69
+ @data
70
+ end
71
+
72
+ public
73
+
74
+ def iv
75
+ internal_data[:plaintext][:iv]
76
+ end
77
+
78
+ def secret_key_hash
79
+ internal_data[:plaintext][:secret_key_hash]
80
+ end
81
+
82
+ def decrypt_secret_key(password)
83
+ Crypto.aes(:decrypt, password, iv, internal_data[:encrypted][:secret_key])
84
+ end
85
+
86
+ def lock
87
+ internal_data[:encrypted][:contents] = Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump(internal_data[:plaintext][:contents]))
88
+ internal_data[:plaintext][:contents] = nil
89
+ @plaintext_secret_key = nil
90
+ end
91
+
92
+ def unlock!(password = nil)
93
+ raise MissingPasswordError, "no password given." unless password
94
+
95
+ plaintext_secret_key = decrypt_secret_key(password)
96
+ if Crypto.digest(plaintext_secret_key) != secret_key_hash
97
+ raise PasswordIncorrectError
98
+ else
99
+ @plaintext_secret_key = plaintext_secret_key
100
+ internal_data[:encrypted][:contents] ||= Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump([]))
101
+ internal_data[:plaintext][:contents] = YAML.load(Crypto.decrypt(@plaintext_secret_key, iv, internal_data[:encrypted][:contents]))
102
+ end
103
+ end
104
+
105
+ def unlock(password = nil, timeout = 5 * 60)
106
+ return false unless timeout > 0
107
+ unlock!(password)
108
+ start_locking_timer(timeout)
109
+ true
110
+ end
111
+
112
+ def start_locking_timer(seconds)
113
+ Thread.new do
114
+ sleep seconds
115
+ self.lock
116
+ end
117
+ end
118
+
119
+ def locked?
120
+ if plaintext_secret_key
121
+ false
122
+ else
123
+ true
124
+ end
125
+ end
126
+
127
+ def plaintext_contents
128
+ raise AccessError, "vault is locked; unable to access vault contents." if locked?
129
+
130
+ YAML.load(YAML.dump(internal_contents))
131
+ end
132
+
133
+ private
134
+
135
+ def internal_contents
136
+ internal_data[:plaintext][:contents]
137
+ end
138
+
139
+ public
140
+
141
+ def contents
142
+ if locked?
143
+ public_contents
144
+ else
145
+ plaintext_contents
146
+ end
147
+ end
148
+
149
+ def public_contents
150
+ raise AccessError, "vault is locked; unable to access vault contents." if locked?
151
+
152
+ public_data[:plaintext][:contents]
153
+ end
154
+
155
+ private
156
+
157
+ def update_contents(new_content)
158
+ internal_data[:encrypted][:contents] = Crypto.encrypt(@plaintext_secret_key, iv, YAML.dump(internal_contents))
159
+
160
+ Storage.write(YAML.dump(public_data), file)
161
+
162
+ plaintext_contents
163
+ end
164
+
165
+ public
166
+
167
+ def contents=(new_content)
168
+ raise AccessError, "vault is locked; unable to modify vault contents." if locked?
169
+
170
+ internal_contents.replace(new_content)
171
+
172
+ update_contents(new_content)
173
+ end
174
+
175
+ def add(*contents)
176
+ raise AccessError, "vault is locked; unable to add content to vault." if locked?
177
+
178
+ contents.each do |content|
179
+ internal_contents << content
180
+ end
181
+
182
+ update_contents(internal_contents)
183
+ end
184
+ alias_method :<<, :add
185
+
186
+ def remove(*contents)
187
+ raise AccessError, "vault is locked; unable to remove content from vault." if locked?
188
+
189
+ contents.each do |content|
190
+ internal_contents.delete(content)
191
+ end
192
+
193
+ update_contents(internal_contents)
194
+ end
195
+
196
+ def status
197
+ locked? ? "locked" : "unlocked"
198
+ end
199
+
200
+ def inspect
201
+ %Q{#<Vault (#{status})>}
202
+ end
203
+
204
+ def self.new_secret_key(password, iv)
205
+ plaintext = Crypto.digest(Crypto.random_bytes(4096))
206
+ new_key = Crypto.aes(:encrypt, password, iv, plaintext)
207
+ #? plaintext = nil
208
+ #? GC.start
209
+ { :encrypted_key => new_key,
210
+ :plaintext_key_hash => Crypto.digest(plaintext),
211
+ :plaintext_key => plaintext }
212
+ end
213
+ end
214
+ end
data/lib/syde.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "openssl"
2
+ require "yaml"
3
+ require "fileutils"
4
+
5
+ # http://snippets.dzone.com/posts/show/576
6
+ # http://snippets.dzone.com/posts/show/4975
7
+
8
+ FileUtils.mkdir(File.expand_path("~/.syde")) unless File.exist?(File.expand_path("~/.syde"))
9
+
10
+ module Syde
11
+ SYDE_VERSION_MAJOR = "0"
12
+ SYDE_VERSION_MINOR = "0"
13
+ SYDE_VERSION_TINY = "1"
14
+
15
+ SYDE_VERSION = [SYDE_VERSION_MAJOR, SYDE_VERSION_MINOR, SYDE_VERSION_TINY].join(".")
16
+
17
+ SYDE_VERSION_DATE = nil
18
+ end
19
+
20
+ require "syde/crypto"
21
+ require "syde/errors"
22
+ require "syde/storage"
23
+ require "syde/vault"
data/lib/syde.rb~ ADDED
@@ -0,0 +1,23 @@
1
+ require "openssl"
2
+ require "yaml"
3
+ require "fileutils"
4
+
5
+ # http://snippets.dzone.com/posts/show/576
6
+ # http://snippets.dzone.com/posts/show/4975
7
+
8
+ FileUtils.mkdir(File.expand_path("~/.syde")) unless File.exist?(File.expand_path("~/.syde"))
9
+
10
+ module Syde
11
+ SYDE_VERSION_MAJOR = "0"
12
+ SYDE_VERSION_MINOR = "0"
13
+ SYDE_VERSION_TINY = "1"
14
+
15
+ SYDE_VERSION = [SYDE_VERSION_MAJOR, SYDE_VERSION_MINOR, SYDE_VERSION_TINY].join(".")
16
+
17
+ SYDE_VERSION_DATE = nil
18
+ end
19
+
20
+ require "syde/crypto"
21
+ require "syde/errors"
22
+ require "syde/storage"
23
+ require "syde/vault"
data/rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "rake"
2
+
3
+ task :test do
4
+ system("ruby -I lib -I test test/_test.rb")
5
+ end
6
+
7
+ task :default => :test
data/syde.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "syde"
3
+ s.version = "0.0.1"
4
+ s.authors = ["Adam Prescott"]
5
+ s.email = ["adam@aprescott.com"]
6
+ s.homepage = "https://github.com/aprescott/syde"
7
+ s.summary = "Symmetric data encryption library."
8
+ s.description = "Syde is a symmetric data encryption library written in Ruby, licensed under the MIT license. It provides a saved encrypted data storage under a single password."
9
+ s.files = Dir["{lib/**/*,test/**/*}"] + %w[LICENSE.mit Gemfile rakefile README.md syde.gemspec .gemtest]
10
+ s.require_path = "lib"
11
+ s.test_files = Dir["test/*"]
12
+ s.has_rdoc = false
13
+ s.add_development_dependency "rake"
14
+ s.required_ruby_version = "~> 1.8.7"
15
+ s.requirements << "Ruby 1.8.7, does not work with 1.9 (yet) due to encodings"
16
+ end
data/test/_test.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+
3
+ TEST_STORAGE_FILE = File.expand_path(File.join("", *%w[tmp test_vault]))
4
+
5
+ require "syde"
6
+ require "test_vault"
7
+ require "test_storage"
8
+ require "test_crypto"
@@ -0,0 +1,30 @@
1
+ class TC_Syde_Crypto < Test::Unit::TestCase
2
+ def setup
3
+ FileUtils.rm(TEST_STORAGE_FILE)
4
+ @vault = Syde::Vault.create("test_password", TEST_STORAGE_FILE)
5
+ end
6
+
7
+ def tear_down
8
+ FileUtils.rm(TEST_STORAGE_FILE)
9
+ end
10
+
11
+ def iv
12
+ @vault.iv
13
+ end
14
+
15
+ def encrypt(key, plaintext)
16
+ Syde::Vault::Crypto.encrypt(key, iv, plaintext)
17
+ end
18
+
19
+ def decrypt(key, ciphertext)
20
+ Syde::Vault::Crypto.decrypt(key, iv, ciphertext)
21
+ end
22
+
23
+ def test_invertible
24
+ assert_equal "test_plaintext", decrypt("test_key", encrypt("test_key", "test_plaintext"))
25
+ end
26
+
27
+ def test_secret_key_length
28
+ Syde::Vault::Crypto.random_bytes(1024).size == 1024
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ class TC_Syde_Storage < Test::Unit::TestCase
2
+ TEST_DATA = { :plaintext => { :iv => "test iv", :secret_key_hash => "test secret key hash" },
3
+ :encrypted => { :secret_key => "test secret key",
4
+ :contents => "" } }
5
+
6
+ def setup
7
+ FileUtils.rm(TEST_STORAGE_FILE) if File.exist?(TEST_STORAGE_FILE)
8
+ @vault = Syde::Vault.create("test_password", TEST_STORAGE_FILE)
9
+ end
10
+
11
+ def valid_format?(d)
12
+ Syde::Vault::Storage.valid_format?(d)
13
+ end
14
+
15
+ def test_valid_format
16
+ assert valid_format?(TEST_DATA), "data intended to be in correct format was found invalid"
17
+ end
18
+
19
+ def test_internal_valid_format
20
+ @vault.unlock!("test_password")
21
+ assert valid_format?(@vault.data), "internal inconsistency"
22
+ end
23
+
24
+ def test_data_copy
25
+ Marshal.load(Marshal.dump(TEST_DATA))
26
+ end
27
+
28
+ def test_invalid_format
29
+ invalid_formats = {
30
+ [] => "annot be an array",
31
+ nil => "cannot be nil",
32
+ {} => "contains nothing",
33
+ }
34
+
35
+ invalid_formats.each do |d, result|
36
+ assert !valid_format?(d), result
37
+ end
38
+ end
39
+
40
+ def test_missing_plaintext
41
+ d = test_data_copy
42
+ d.delete(:plaintext)
43
+ assert !valid_format?(d), "data is incorrect format: :plaintext key is missing"
44
+ end
45
+
46
+ def test_missing_encrypted
47
+ d = test_data_copy
48
+ d.delete(:encrypted)
49
+ assert !valid_format?(d), "data is incorrect format: :encrypted key is missing"
50
+ end
51
+ def test_missing_encrypted_contents
52
+ d = test_data_copy
53
+ d[:encrypted].delete(:contents)
54
+ assert !valid_format?(d), "data is incorrect format: missing encrypted :contents"
55
+ end
56
+ def test_encrypted_contents_is_string
57
+ d = test_data_copy
58
+ d[:encrypted][:contents] = {}
59
+ assert !valid_format?(d), "data is incorrect format: encrypted contents must be a string"
60
+
61
+ d = test_data_copy
62
+ d[:encrypted][:contents] = []
63
+ assert !valid_format?(d) => "data is incorrect format: encrypted contents must be a string"
64
+ end
65
+ def test_missing_encrypted_secret_key
66
+ d = test_data_copy
67
+ d[:encrypted].delete(:secret_key)
68
+ assert !valid_format?(d), "data is incorrect format: missing encrypted :secret_key"
69
+ end
70
+ def test_missing_plaintext_iv
71
+ d = test_data_copy
72
+ d[:plaintext].delete(:iv)
73
+ assert !valid_format?(d), "data is incorrect format: missing plaintext :iv"
74
+ end
75
+ def test_missing_secret_key_hash
76
+ d = test_data_copy
77
+ d[:plaintext].delete(:secret_key_hash)
78
+ assert !valid_format?(d), "data is incorrect format: missing secret key hash"
79
+ end
80
+
81
+ def test_open_storage_file
82
+ assert Syde::Vault.open(TEST_STORAGE_FILE)
83
+ end
84
+
85
+ def test_empty_data_failure
86
+ assert_raise ArgumentError do
87
+ Syde::Vault.new("")
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,68 @@
1
+ class TC_Syde_Vault < Test::Unit::TestCase
2
+ def setup
3
+ FileUtils.rm(TEST_STORAGE_FILE) if File.exist?(TEST_STORAGE_FILE)
4
+ @vault = Syde::Vault.create("test_password", TEST_STORAGE_FILE)
5
+ end
6
+
7
+ def tear_down
8
+ FileUtils.rm(TEST_STORAGE_FILE) if File.exist?(TEST_STORAGE_FILE)
9
+ end
10
+
11
+ def test_initial_contents_empty
12
+ @vault.unlock!("test_password")
13
+ assert @vault.contents.empty?
14
+ end
15
+
16
+ def test_vault_add
17
+ @vault.unlock!("test_password")
18
+ assert @vault.add("1")
19
+ assert @vault.contents == ["1"]
20
+ end
21
+
22
+ def test_vault_multiple_add
23
+ @vault.unlock!("test_password")
24
+ assert @vault.add("1", 3, /regex/)
25
+ @vault.contents.each do |e|
26
+ assert [/regex/, 3, "1"].include?(e)
27
+ end
28
+ end
29
+
30
+ def test_begins_locked
31
+ assert @vault.locked?
32
+ end
33
+
34
+ def test_non_override_of_storage_file
35
+ File.open(TEST_STORAGE_FILE, "w") do |f|
36
+ f.write "test"
37
+ end
38
+ assert_raise RuntimeError do
39
+ Syde::Vault.create("test_password", TEST_STORAGE_FILE)
40
+ end
41
+ end
42
+
43
+ def test_unlock_incorrect_password
44
+ assert_raise OpenSSL::Cipher::CipherError, Syde::Errors::PasswordIncorrectError do
45
+ @vault.unlock!("incorrect_password")
46
+ end
47
+ assert @vault.locked?
48
+ end
49
+
50
+ def test_unlock_correct_password
51
+ @vault.unlock!("test_password")
52
+ assert !@vault.locked?
53
+ end
54
+
55
+ def test_auto_locks
56
+ assert @vault.locked?
57
+ @vault.unlock("test_password", 1)
58
+ assert !@vault.locked?
59
+ sleep 2
60
+ assert @vault.locked?
61
+ end
62
+
63
+ def test_instant_lock
64
+ assert @vault.locked?
65
+ @vault.unlock("test_password", 0)
66
+ assert @vault.locked?
67
+ end
68
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syde
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Adam Prescott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-17 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rake
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ description: Syde is a symmetric data encryption library written in Ruby, licensed under the MIT license. It provides a saved encrypted data storage under a single password.
28
+ email:
29
+ - adam@aprescott.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - lib/syde.rb
38
+ - lib/syde.rb~
39
+ - lib/syde/vault.rb
40
+ - lib/syde/vault.rb~
41
+ - lib/syde/errors.rb
42
+ - lib/syde/storage.rb
43
+ - lib/syde/crypto.rb
44
+ - test/test_storage.rb
45
+ - test/test_vault.rb
46
+ - test/_test.rb
47
+ - test/test_crypto.rb
48
+ - LICENSE.mit
49
+ - Gemfile
50
+ - rakefile
51
+ - README.md
52
+ - syde.gemspec
53
+ - .gemtest
54
+ has_rdoc: true
55
+ homepage: https://github.com/aprescott/syde
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 1.8.7
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ requirements:
76
+ - Ruby 1.8.7, does not work with 1.9 (yet) due to encodings
77
+ rubyforge_project:
78
+ rubygems_version: 1.6.2
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Symmetric data encryption library.
82
+ test_files:
83
+ - test/test_storage.rb
84
+ - test/test_vault.rb
85
+ - test/_test.rb
86
+ - test/test_crypto.rb