safedb 0.01.0001
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|