safedb 0.01.0001
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +793 -0
- data/Rakefile +16 -0
- data/bin/safe +5 -0
- data/lib/configs/README.md +58 -0
- data/lib/extension/array.rb +162 -0
- data/lib/extension/dir.rb +35 -0
- data/lib/extension/file.rb +123 -0
- data/lib/extension/hash.rb +33 -0
- data/lib/extension/string.rb +572 -0
- data/lib/factbase/facts.safedb.net.ini +38 -0
- data/lib/interprete.rb +462 -0
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +243 -0
- data/lib/keytools/kdf.bcrypt.rb +265 -0
- data/lib/keytools/kdf.pbkdf2.rb +262 -0
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1391 -0
- data/lib/keytools/key.db.rb +330 -0
- data/lib/keytools/key.docs.rb +195 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +259 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +585 -0
- data/lib/logging/gem.logging.rb +132 -0
- data/lib/modules/README.md +43 -0
- data/lib/modules/cryptology/aes-256.rb +154 -0
- data/lib/modules/cryptology/amalgam.rb +70 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +138 -0
- data/lib/modules/cryptology/crypt.io.rb +225 -0
- data/lib/modules/cryptology/engineer.rb +99 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/modules/storage/git.store.rb +399 -0
- data/lib/session/fact.finder.rb +334 -0
- data/lib/session/require.gem.rb +112 -0
- data/lib/session/time.stamp.rb +340 -0
- data/lib/session/user.home.rb +49 -0
- data/lib/usecase/cmd.rb +487 -0
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +157 -0
- data/lib/usecase/init.rb +63 -0
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +71 -0
- data/lib/usecase/logout.rb +28 -0
- data/lib/usecase/open.rb +71 -0
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +81 -0
- data/lib/usecase/set.rb +44 -0
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +35 -0
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +41 -0
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +3 -0
- data/safedb.gemspec +34 -0
- metadata +193 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
# A Key/Value database knows how to manipulate a JSON backed data structure
|
9
|
+
# (put, add etc) <b>after reading and then decrypting it</b> from a
|
10
|
+
# file and <b>before encrypting and then writing it</b> to a file.
|
11
|
+
#
|
12
|
+
# It provides behaviour to which we can create, append (add), update
|
13
|
+
# (change), read parts and delete essentially two structures
|
14
|
+
#
|
15
|
+
# - a collection of name/value pairs
|
16
|
+
# - an ordered list of values
|
17
|
+
#
|
18
|
+
# == JSON is Not Exposed in the Interface
|
19
|
+
#
|
20
|
+
# A key/value database doesn't expose the data format used in the implementation
|
21
|
+
# allowing this to be changed seamlessly to YAMl or other formats.
|
22
|
+
#
|
23
|
+
# == Symmetric Encryption and Decryption
|
24
|
+
#
|
25
|
+
# A key/value database supports operations to <b>read from</b> and <b>write to</b>
|
26
|
+
# a known filepath and with a symmetric key it can
|
27
|
+
#
|
28
|
+
# - decrypt <b>after reading from</b> a file and
|
29
|
+
# - encrypt <b>before writing to</b> a (the same) file
|
30
|
+
#
|
31
|
+
# == Hashes as the Primary Data Structure
|
32
|
+
#
|
33
|
+
# The key/value database openly extends {Hash} as the data structure for holding
|
34
|
+
#
|
35
|
+
# - strings
|
36
|
+
# - arrays
|
37
|
+
# - other hashes
|
38
|
+
# - booleans
|
39
|
+
# - integers and floats
|
40
|
+
class KeyDb < Hash
|
41
|
+
|
42
|
+
# Return a key database data structure that is instantiated from
|
43
|
+
# the parameter JSON string.
|
44
|
+
#
|
45
|
+
# @param db_json_string [String]
|
46
|
+
# this json formatted data structure will be converted into a
|
47
|
+
# a Ruby hash (map) data structure and returned.
|
48
|
+
#
|
49
|
+
# @return [KeyDb]
|
50
|
+
# a hash data structure that has been instantiated as per the
|
51
|
+
# parameter json string content.
|
52
|
+
def self.from_json( db_json_string )
|
53
|
+
|
54
|
+
data_db = KeyDb.new()
|
55
|
+
data_db.merge!( JSON.parse( db_json_string ) )
|
56
|
+
return data_db
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
# Create a new key value entry inside a dictionary with the specified
|
63
|
+
# name at the root of this database. Successful completion means the
|
64
|
+
# named dictionary will contain one more entry than it need even if it
|
65
|
+
# did not previously exist.
|
66
|
+
#
|
67
|
+
# @param dictionary_name [String]
|
68
|
+
#
|
69
|
+
# if a dictionary with this name exists at the root of the
|
70
|
+
# database add the parameter key value pair into it.
|
71
|
+
#
|
72
|
+
# if no dictionary exists then create one first before adding
|
73
|
+
# the key value pair as the first entry into it.
|
74
|
+
#
|
75
|
+
# @param key_name [String]
|
76
|
+
#
|
77
|
+
# the key part of the key value pair that will be added into the
|
78
|
+
# dictionary whose name was provided in the first parameter.
|
79
|
+
#
|
80
|
+
# @param value [String]
|
81
|
+
#
|
82
|
+
# the value part of the key value pair that will be added into the
|
83
|
+
# dictionary whose name was provided in the first parameter.
|
84
|
+
def create_entry( dictionary_name, key_name, value )
|
85
|
+
|
86
|
+
KeyError.not_new( dictionary_name, self )
|
87
|
+
KeyError.not_new( key_name, self )
|
88
|
+
KeyError.not_new( value, self )
|
89
|
+
|
90
|
+
self[ dictionary_name ] = {} unless self.has_key?( dictionary_name )
|
91
|
+
self[ dictionary_name ][ key_name ] = value
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Create a new secondary tier map key value entry inside a primary tier
|
97
|
+
# map at the map_key_name location.
|
98
|
+
#
|
99
|
+
# A failure will occur if either the outer or inner keys already exist
|
100
|
+
# without their values being map objects.
|
101
|
+
#
|
102
|
+
# If this method is called against a new empty map, the resulting map
|
103
|
+
# structure will look like the below.
|
104
|
+
#
|
105
|
+
# { outer_keyname ~> { inner_keyname ~> { entry_keyname, entry_value } } }
|
106
|
+
#
|
107
|
+
# @param outer_keyname [String]
|
108
|
+
#
|
109
|
+
# if a dictionary with this name exists at the root of the
|
110
|
+
# database add the parameter key value pair into it.
|
111
|
+
#
|
112
|
+
# if no dictionary exists then create one first before adding
|
113
|
+
# the key value pair as the first entry into it.
|
114
|
+
#
|
115
|
+
# @param inner_keyname [String]
|
116
|
+
#
|
117
|
+
# if a map exists at this key name then an entry comprising of
|
118
|
+
# a map_entry_key and a entry_value may either be added
|
119
|
+
# (if the map_entry_key does not already exist), or updated if
|
120
|
+
# it does.
|
121
|
+
#
|
122
|
+
# if the map does not exist it will be created and its first and
|
123
|
+
# only entry will be a key with inner_keyname along with a new
|
124
|
+
# single entry map consisting of the entry_keyname and the
|
125
|
+
# entry_value.
|
126
|
+
#
|
127
|
+
# @param entry_keyname [String]
|
128
|
+
#
|
129
|
+
# this key will exist in the second tier map after this operation.
|
130
|
+
#
|
131
|
+
# @param entry_value [String]
|
132
|
+
#
|
133
|
+
# this value will exist in the second tier map after this operation
|
134
|
+
# and if the entry_keyname already existed its value is overwritten
|
135
|
+
# with this one.
|
136
|
+
#
|
137
|
+
def create_map_entry( outer_keyname, inner_keyname, entry_keyname, entry_value )
|
138
|
+
|
139
|
+
KeyError.not_new( outer_keyname, self )
|
140
|
+
KeyError.not_new( inner_keyname, self )
|
141
|
+
KeyError.not_new( entry_keyname, self )
|
142
|
+
KeyError.not_new( entry_value, self )
|
143
|
+
|
144
|
+
self[ outer_keyname ] = {} unless self.has_key?( outer_keyname )
|
145
|
+
self[ outer_keyname ][ inner_keyname ] = {} unless self[ outer_keyname ].has_key?( inner_keyname )
|
146
|
+
self[ outer_keyname ][ inner_keyname ][ entry_keyname ] = entry_value
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# Does this database have an entry in the root dictionary named with
|
152
|
+
# the key_name parameter?
|
153
|
+
#
|
154
|
+
# @param dictionary_name [String]
|
155
|
+
#
|
156
|
+
# immediately return false if a dictionary with this name does
|
157
|
+
# <b>not exist</b> at the root of this database.
|
158
|
+
#
|
159
|
+
# @param key_name [String]
|
160
|
+
#
|
161
|
+
# test whether a key/value pair answering to this name exists inside
|
162
|
+
# the specified dictionary at the root of this database.
|
163
|
+
#
|
164
|
+
def has_entry?( dictionary_name, key_name )
|
165
|
+
|
166
|
+
KeyError.not_new( dictionary_name, self )
|
167
|
+
KeyError.not_new( key_name, self )
|
168
|
+
|
169
|
+
return false unless self.has_key?( dictionary_name )
|
170
|
+
return self[ dictionary_name ].has_key?( key_name )
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# Get the entry with the key name in a dictionary that is itself
|
176
|
+
# inside another dictionary (named in the first parameter) which
|
177
|
+
# thankfully is at the root of this database.
|
178
|
+
#
|
179
|
+
# Only call this method if {has_entry?} returns true for the same
|
180
|
+
# dictionary and key name parameters.
|
181
|
+
#
|
182
|
+
# @param dictionary_name [String]
|
183
|
+
#
|
184
|
+
# get the entry inside a dictionary which is itself inside a
|
185
|
+
# dictionary (with this dictionary name) which is itself at the
|
186
|
+
# root of this database.
|
187
|
+
#
|
188
|
+
# @param key_name [String]
|
189
|
+
#
|
190
|
+
# get the value part of the key value pair that is inside a
|
191
|
+
# dictionary (with the above dictionary name) which is itself
|
192
|
+
# at the root of this database.
|
193
|
+
#
|
194
|
+
def get_entry( dictionary_name, key_name )
|
195
|
+
|
196
|
+
return self[ dictionary_name ][ key_name ]
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# Delete an existing key value entry inside the dictionary with the specified
|
202
|
+
# name at the root of this database. Successful completion means the
|
203
|
+
# named dictionary will contain one less entry if that key existed.
|
204
|
+
#
|
205
|
+
# @param dictionary_name [String]
|
206
|
+
#
|
207
|
+
# if a dictionary with this name exists at the root of the
|
208
|
+
# database add the parameter key value pair into it.
|
209
|
+
#
|
210
|
+
# if no dictionary exists throw an error
|
211
|
+
#
|
212
|
+
# @param key_name [String]
|
213
|
+
#
|
214
|
+
# the key part of the key value pair that will be deleted in the
|
215
|
+
# dictionary whose name was provided in the first parameter.
|
216
|
+
def delete_entry( dictionary_name, key_name )
|
217
|
+
|
218
|
+
KeyError.not_new( dictionary_name, self )
|
219
|
+
KeyError.not_new( key_name, self )
|
220
|
+
|
221
|
+
self[ dictionary_name ].delete( key_name )
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
# Read and inject into this envelope, the data structure found in a
|
227
|
+
# file at the path specified in the first parameter.
|
228
|
+
#
|
229
|
+
# Symmetric cryptography is mandatory for the envelope so we must
|
230
|
+
# <b>encrypt before writing</b> and <b>decrypt after reading</b>.
|
231
|
+
#
|
232
|
+
# An argument error will result if a suitable key is not provided.
|
233
|
+
#
|
234
|
+
# If the file does not exist (denoting the first read) all this method
|
235
|
+
# does is to stash the filepath as an instance variable and igore the
|
236
|
+
# decryption key which can be nil (or ommitted).
|
237
|
+
#
|
238
|
+
# @param the_filepath [String]
|
239
|
+
# absolute path to the file which acts as the persistent mirror to
|
240
|
+
# this data structure envelope.
|
241
|
+
#
|
242
|
+
# @param decryption_key [String]
|
243
|
+
# encryption at rest is a given so this mandatory parameter must
|
244
|
+
# contain a robust symmetric decryption key. The key will be used
|
245
|
+
# for decryption after the read and it will not linger (ie not cached
|
246
|
+
# as an instance variable).
|
247
|
+
#
|
248
|
+
# @raise [ArgumentError] if the decryption key is not robust enough.
|
249
|
+
def read the_filepath, decryption_key = nil
|
250
|
+
|
251
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
252
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
253
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
254
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
255
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
256
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
257
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
258
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
259
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
260
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
261
|
+
|
262
|
+
raise RuntimeError, "This KeyDb.read() software is never called so how can I be here?"
|
263
|
+
|
264
|
+
@filepath = the_filepath
|
265
|
+
return unless File.exists? @filepath
|
266
|
+
|
267
|
+
cipher_text = Base64.decode64( File.read( @filepath ).strip )
|
268
|
+
plain_text = ToolBelt::Blowfish.decryptor( cipher_text, decryption_key )
|
269
|
+
|
270
|
+
data_structure = JSON.parse plain_text
|
271
|
+
self.merge! data_structure
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
# Write the data in this envelope hash map into a file-system
|
277
|
+
# backed mirror whose path was specified in the {self.read} method.
|
278
|
+
#
|
279
|
+
# Technology for encryption at rest is supported by this dictionary
|
280
|
+
# and to this aim, please endeavour to post a robust symmetric
|
281
|
+
# encryption key.
|
282
|
+
#
|
283
|
+
# Calling this {self.write} method when the file at the prescribed path
|
284
|
+
# does not exist results in the directory structure being created
|
285
|
+
# (if necessary) and then the encrypted file being written.
|
286
|
+
#
|
287
|
+
# @param encryption_key [String]
|
288
|
+
# encryption at rest is a given so this mandatory parameter must
|
289
|
+
# contain a robust symmetric encryption key. The symmetric key will
|
290
|
+
# be used for the decryption after the read. Note that the decryption
|
291
|
+
# key does not linger meaning it isn't cached in an instance variable.
|
292
|
+
def write encryption_key
|
293
|
+
|
294
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
295
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
296
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
297
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
298
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
299
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
300
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
301
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
302
|
+
# @todo -> this is confused - it uses INI but above methods use JSON
|
303
|
+
|
304
|
+
raise RuntimeError, "This KeyDb.write( key ) software is never called so how can I be here?"
|
305
|
+
|
306
|
+
FileUtils.mkdir_p(File.dirname(@filepath))
|
307
|
+
cipher_text = Base64.encode64 ToolBelt::Blowfish.encryptor( self.to_json, encryption_key )
|
308
|
+
File.write @filepath, cipher_text
|
309
|
+
|
310
|
+
puts ""
|
311
|
+
puts "=== ============================"
|
312
|
+
puts "=== Envelope State ============="
|
313
|
+
puts "=== ============================"
|
314
|
+
|
315
|
+
a_ini_file = IniFile.new
|
316
|
+
self.each_key do |section_name|
|
317
|
+
a_ini_file[section_name] = self[section_name]
|
318
|
+
end
|
319
|
+
puts a_ini_file.to_s
|
320
|
+
|
321
|
+
puts "=== ============================"
|
322
|
+
puts ""
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# The open key library generates keys, it stores their salts, it produces differing
|
4
|
+
# representations of the keys (like base64 for storage and binary for encrypting).
|
5
|
+
#
|
6
|
+
# == Key Class Names their and Responsibility
|
7
|
+
#
|
8
|
+
# The 5 core key classes in the open key library are
|
9
|
+
#
|
10
|
+
# - {Key} represents keys in bits, binary and base 64 formats
|
11
|
+
# - {Key64} for converting from to and between base 64 characters
|
12
|
+
# - {Key256} uses key derivation functions to produce high entropy keys
|
13
|
+
# - {KeyIO} reads and writes key metadata (like salts) from/to persistent storage
|
14
|
+
# - {KeyCycle} for creating and locking the keys that underpin the security
|
15
|
+
#
|
16
|
+
# == The 5 Core Key Classes
|
17
|
+
#
|
18
|
+
# Key To initialize with a 264 binary bit string. To hold the
|
19
|
+
# key and represent it when requested
|
20
|
+
# - as a 264 bit binary bit string
|
21
|
+
# - as a 256 bit binary bit string
|
22
|
+
# - as a 256 bit raw bytes encryption key
|
23
|
+
# - as a YACHT64 formatted string
|
24
|
+
#
|
25
|
+
# Key64 To map in and out of the Yacht64 character set - from and to
|
26
|
+
# - a binary bit string sequence
|
27
|
+
# - a Base64 character encoding
|
28
|
+
# - a UrlSafe Base64 character encoding
|
29
|
+
# - a Radix64 character encoding
|
30
|
+
#
|
31
|
+
# Key256 It generates a key in 3 different and important ways. It can
|
32
|
+
# generate
|
33
|
+
#
|
34
|
+
# (a) from_password
|
35
|
+
# (b) from_random (or it can)
|
36
|
+
# (c) regenerate
|
37
|
+
#
|
38
|
+
# When generating from a password it takes a dictionary with
|
39
|
+
# a pre-tailored "section" and writes BCrypt and Pbkdf2 salts
|
40
|
+
# into it.
|
41
|
+
#
|
42
|
+
# When generating random it kicks of by creating a 55 byte
|
43
|
+
# random key fo BCrypt and a 64 byte random key for Pbkdf2.
|
44
|
+
# It then calls upon generate_from_password.
|
45
|
+
#
|
46
|
+
# When regenerating it queries the dictionary provided at the
|
47
|
+
# pre-tailored "section" for the BCrypt and Pbkdf2 salts and
|
48
|
+
# then uses input passwords (be they human randomly sourced)
|
49
|
+
# and regenerates the keys it produced at an earlier sitting.
|
50
|
+
#
|
51
|
+
# KeyIO KeyIO is instantiated with a folder path and a "key reference".
|
52
|
+
# KeyIO will then manage writing to and rereading from the structure
|
53
|
+
# hel inside th efile. The file is named (primarily) by the
|
54
|
+
# reference string.
|
55
|
+
#
|
56
|
+
# KeyCycle KeyLifeCycle implements the merry go round that palms off
|
57
|
+
# responsibility to the intra-session cycle and then back again
|
58
|
+
# to ever rotary inter-session(ary) cycle.
|
59
|
+
########### Maybe think of a method where we pass in
|
60
|
+
########### 2 secrets - 1 human and 1 55 random bytes (session)
|
61
|
+
###########
|
62
|
+
########### 1 another 55 random key is created (the actual encryption key)
|
63
|
+
########### 2 then the above key is encrypted TWICE (2 diff salts and keys)
|
64
|
+
########### 3 Once by key from human password
|
65
|
+
########### 4 Once by key from machine password
|
66
|
+
########### 5 then the key from 1 is returned
|
67
|
+
########### 6 caller encrypts file .................... (go 4 it)
|
68
|
+
|
69
|
+
|
70
|
+
# Generates a 256 bit symmetric encryption key derived from a random
|
71
|
+
# seed sequence of 55 bytes. These 55 bytes are then fed into the
|
72
|
+
# {from_password} key derivation function and processed in a similar
|
73
|
+
# way to if a human had generated the string.
|
74
|
+
#
|
75
|
+
|
76
|
+
|
77
|
+
# <b>Key derivation functions</b> exist to convert <b>low entropy</b> human
|
78
|
+
# created passwords into a high entropy key that is computationally difficult
|
79
|
+
# to acquire through brute force.
|
80
|
+
#
|
81
|
+
# == SafeDb's Underlying Security Strategy
|
82
|
+
#
|
83
|
+
# <b>Randomly generate a 256 bit encryption key and encrypt it</b> with a key
|
84
|
+
# derived from a human password and generated by at least two cryptographic
|
85
|
+
# workhorses known as <b>key derivation functions</b>.
|
86
|
+
#
|
87
|
+
# The encryption key (encrypted by the one derived from a human password) sits
|
88
|
+
# at the beginning of a long chain of keys and encryption - so much so that the
|
89
|
+
# crypt material being outputted for storage is all but worthless to anyone but
|
90
|
+
# its rightful owner.
|
91
|
+
#
|
92
|
+
# == Key Size vs Crack Time
|
93
|
+
#
|
94
|
+
# Cracking a 256 bit key would need roughly 2^255 iterations (half the space)
|
95
|
+
# and this is akin to the number of atoms in the known universe.
|
96
|
+
#
|
97
|
+
# <b>The human key can put security at risk.</b>
|
98
|
+
#
|
99
|
+
# The rule of thumb is that a 40 character password with a good spread of the
|
100
|
+
# roughly 90 typable characters, would produce security equivalent to that of
|
101
|
+
# an AES 256bit key. As the password size and entropy drop, so does the security,
|
102
|
+
# exponentially.
|
103
|
+
#
|
104
|
+
# As human generated passwords have a relatively small key space, key derivation
|
105
|
+
# functions must be slow to compute with any implementation.
|
106
|
+
#
|
107
|
+
# == Key Derivation Functions for Command Line Apps
|
108
|
+
#
|
109
|
+
# A command line app (with no recourse to a central server) uses a Key
|
110
|
+
# Derivation Function (like BCrypt, Aaron2 or PBKD2) in a manner different
|
111
|
+
# to that employed by server side software.
|
112
|
+
#
|
113
|
+
# - server side passwords are hashed then both salt and hash are persisted
|
114
|
+
# - command line apps do not store the key - they only store the salt
|
115
|
+
# - both throw away the original password
|
116
|
+
#
|
117
|
+
# == One Key | One Session | One Crypt
|
118
|
+
#
|
119
|
+
# Command line apps use the derived key to <b>symmetrically encrypt and decrypt</b>
|
120
|
+
# one and only one 48 character key and a new key is derived at the beginning
|
121
|
+
# of every session.
|
122
|
+
#
|
123
|
+
# At the end of the session <b>all material encrypted by the outgoing key</b>
|
124
|
+
# is removed. This aggressive key rotation strategy leaves no stone unturned in
|
125
|
+
# the quest for ultimate security.
|
126
|
+
#
|
127
|
+
# == SafeDb's CLI Key Derivation Architecture
|
128
|
+
#
|
129
|
+
# SafeDb never accesses another server and giving its users total control
|
130
|
+
# of their secret crypted materials. It strengthens the key derivation process
|
131
|
+
# in three important ways.
|
132
|
+
#
|
133
|
+
# - [1] it does not store the key nor does it store the password
|
134
|
+
#
|
135
|
+
# - [2] a new master key is generated for every session only to hold the master index file
|
136
|
+
#
|
137
|
+
# - [3] it uses both <b>BCrypt</b> (Blowfish Crypt) and the indefatigable <b>PBKD2</b>
|
138
|
+
|
139
|
+
|
140
|
+
# After a successful initialization, the application instance is linked to a keystore
|
141
|
+
# whose contents are responsible for securing the application instance database.
|
142
|
+
#
|
143
|
+
# To ascertain what needs to be done to bridge the gap to full initialization the
|
144
|
+
# app needs to know 3 things from the KeyApi. These things are
|
145
|
+
#
|
146
|
+
# - the ID of this app instance on the machine
|
147
|
+
# - if a keystore been associated with this ID
|
148
|
+
# - whether the keystore secures the app database
|
149
|
+
#
|
150
|
+
# The answers dictate the steps that need to be undertaken to bring the database of
|
151
|
+
# the application instance under the secure wing of the KeyApi.
|
152
|
+
#
|
153
|
+
#
|
154
|
+
# == 1. What is the App Instance ID on this Machine?
|
155
|
+
#
|
156
|
+
# The KeyApi uses the "just given" application reference and the machine environment to
|
157
|
+
# respond with a <b>digested identifier</b> binding the application instance to the
|
158
|
+
# present machine (workstation).
|
159
|
+
#
|
160
|
+
#
|
161
|
+
# == 2. Has a Keystore been associated with this ID?
|
162
|
+
#
|
163
|
+
# The application's configuration manager is asked to find an associated KeyStore ID
|
164
|
+
# mapped against the app/machine id garnered by question 1.
|
165
|
+
#
|
166
|
+
# <b>No it has not!</b>
|
167
|
+
#
|
168
|
+
# If <b>NO</b> then a KeyStore ID is acquired <b>either from the init command's parameter</b>,
|
169
|
+
# or a <b>suitable default</b>. This new association between the app/machine ID and the
|
170
|
+
# KeyStore ID is then stored so the answer next time will be <b>YES</b>.
|
171
|
+
#
|
172
|
+
# <b>Yes it has!</b>
|
173
|
+
#
|
174
|
+
# Great - we now submit the KeyStore ID to the KeyApi so that it may answer question 3.
|
175
|
+
#
|
176
|
+
#
|
177
|
+
# == 3. Does the keystore secure the app instance database?
|
178
|
+
#
|
179
|
+
# For the KeyApi to answer, it needs the App's Instance ID and the KeyStore ID.
|
180
|
+
#
|
181
|
+
# <b>Not Yet!</b> Now <b>NO</b> means this application instance's database has not been
|
182
|
+
# brought under the protection of the KeyApi's multi-layered security net. For this it
|
183
|
+
# needs
|
184
|
+
#
|
185
|
+
# - the KeyStore ID
|
186
|
+
# - the application instance reference
|
187
|
+
# - the plaintext secret from which nothing of the host survives
|
188
|
+
# - the current application database plaintext
|
189
|
+
#
|
190
|
+
# <b>Yes it does!</b> If the app db keys <b>have been instantiated</b> and the client app is
|
191
|
+
# <b>sitting pretty</b> in possession of the database ciphertext, no more needs doing.
|
192
|
+
|
193
|
+
module SafeDb
|
194
|
+
|
195
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
|
6
|
+
# This class is the parent to all opensession errors
|
7
|
+
# that originate from the command line.
|
8
|
+
#
|
9
|
+
# All opensession cli originating errors are about
|
10
|
+
#
|
11
|
+
# - a problem with the input or
|
12
|
+
# - a problem with the current state or
|
13
|
+
# - a predictable future problem
|
14
|
+
class KeyError < StandardError
|
15
|
+
|
16
|
+
|
17
|
+
# Initialize the error and provide a culprit
|
18
|
+
# object which will be to-stringed and given
|
19
|
+
# out as evidence (look at this)!
|
20
|
+
#
|
21
|
+
# This method will take care of loggin the error.
|
22
|
+
#
|
23
|
+
# @param message [String] human readable error message
|
24
|
+
# @param culprit [Object] object that is either pertinent, a culprit or culpable
|
25
|
+
def initialize message, culprit
|
26
|
+
|
27
|
+
super(message)
|
28
|
+
|
29
|
+
@the_culprit = culprit
|
30
|
+
|
31
|
+
log.info(x) { "An [Error] Occured => #{message}" }
|
32
|
+
log.info(x) { "Object of Interest => #{culprit.to_s}" } unless culprit.nil?
|
33
|
+
log.info(x) { "Class Name Culprit => #{culprit.class.name}" }
|
34
|
+
log.info(x) { "Error Message From => #{self.class.name}" }
|
35
|
+
|
36
|
+
thread_backtrace = Thread.current.backtrace.join("\n")
|
37
|
+
thread_backtrace.to_s.log_lines
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# This method gives interested parties the object that
|
43
|
+
# is at the centre of the exception. This object is either
|
44
|
+
# very pertinent, culpable or at the very least, interesting.
|
45
|
+
#
|
46
|
+
# @return [String] string representation of culpable object
|
47
|
+
def culprit
|
48
|
+
return "No culprit identified." if @the_culprit.nil?
|
49
|
+
return @the_culprit.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Assert that the parameter string attribute is <b>not new</b> which
|
54
|
+
# means neither nil, nor empty nor consists solely of whitespace.
|
55
|
+
#
|
56
|
+
# The <b>NEW</b> acronym tells us that a bearer worthy of the name is
|
57
|
+
#
|
58
|
+
# - neither <b>N</b>il
|
59
|
+
# - nor <b>E</b>mpty
|
60
|
+
# - nor consists solely of <b>W</b>hitespace
|
61
|
+
#
|
62
|
+
# @param the_attribute [String]
|
63
|
+
# raise a {KeyError} if the attribute is not new.
|
64
|
+
#
|
65
|
+
# @param the_desc [String]
|
66
|
+
# a description of th attribute
|
67
|
+
#
|
68
|
+
# @raise [KeyError]
|
69
|
+
#
|
70
|
+
# The attribute cannot be <b>NEW</b>. The <b>NEW acronym</b> asserts
|
71
|
+
# that the attribute is
|
72
|
+
#
|
73
|
+
# - neither <b>N</b>il
|
74
|
+
# - nor <b>E</b>mpty
|
75
|
+
# - nor <b>W</b>hitespace only
|
76
|
+
#
|
77
|
+
def self.not_new the_attribute, the_desc
|
78
|
+
|
79
|
+
attribute_new = the_attribute.nil? || the_attribute.chomp.strip.empty?
|
80
|
+
return unless attribute_new
|
81
|
+
|
82
|
+
msg = "[the_desc] is either nil, empty or consists solely of whitespace."
|
83
|
+
raise KeyError.new( msg, the_desc )
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
=begin
|
91
|
+
# Throw this error if the configured safe directory points to a file.
|
92
|
+
class SafeDirectoryIsFile < OpenError::CliError; end;
|
93
|
+
|
94
|
+
# Throw this error if safe directory path is either nil or empty.
|
95
|
+
class SafeDirNotConfigured < OpenError::CliError; end;
|
96
|
+
|
97
|
+
# Throw this error if the email address is nil, empty or less than 5 characters.
|
98
|
+
class EmailAddrNotConfigured < OpenError::CliError; end;
|
99
|
+
|
100
|
+
# Throw this error if the store url is either nil or empty.
|
101
|
+
class StoreUrlNotConfigured < OpenError::CliError; end;
|
102
|
+
|
103
|
+
# Throw if "prime folder" name occurs 2 or more times in the path.
|
104
|
+
class SafePrimeNameRepeated < OpenError::CliError; end;
|
105
|
+
|
106
|
+
# Throw if "prime folder" name occurs 2 or more times in the path.
|
107
|
+
class SafePrimeNameNotAtEnd < OpenError::CliError; end;
|
108
|
+
=end
|
109
|
+
|
110
|
+
end
|