opensecret 0.0.962 → 0.0.988
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 +4 -4
- data/README.md +16 -10
- data/bin/opensecret +3 -4
- data/bin/ops +5 -0
- data/lib/extension/string.rb +114 -0
- data/lib/factbase/facts.opensecret.io.ini +9 -21
- data/lib/interprete/begin.rb +232 -0
- data/lib/interprete/cmd.rb +621 -0
- data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
- data/lib/interprete/init.rb +205 -0
- data/lib/interprete/key.rb +119 -0
- data/lib/interprete/open.rb +148 -0
- data/lib/{plugins/usecases → interprete}/put.rb +19 -6
- data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
- data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
- data/lib/interprete/set.rb +46 -0
- data/lib/interprete/use.rb +43 -0
- data/lib/interpreter.rb +165 -0
- data/lib/keytools/binary.map.rb +245 -0
- data/lib/keytools/digester.rb +245 -0
- data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
- data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
- data/lib/keytools/doc.star.schema.strategy.txt +77 -0
- data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
- data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
- data/lib/keytools/kdf.bcrypt.rb +180 -0
- data/lib/keytools/kdf.pbkdf2.rb +164 -0
- data/lib/keytools/key.data.rb +227 -0
- data/lib/keytools/key.derivation.rb +341 -0
- data/lib/keytools/key.module.rb +140 -0
- data/lib/keytools/key.rb +481 -0
- data/lib/logging/gem.logging.rb +1 -2
- data/lib/modules/cryptology.md +43 -0
- data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
- data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +118 -0
- data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
- data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
- data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
- data/lib/modules/mappers/collateral.rb +282 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/mappers/envelope.rb +127 -0
- data/lib/modules/mappers/settings.rb +170 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
- data/lib/notepad/scratch.pad.rb +17 -0
- data/lib/session/fact.finder.rb +13 -0
- data/lib/session/require.gem.rb +5 -0
- data/lib/store-commands.txt +180 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +5 -6
- metadata +74 -29
- data/lib/crypto/blowfish.rb +0 -85
- data/lib/crypto/collect.rb +0 -140
- data/lib/crypto/verify.rb +0 -33
- data/lib/opensecret.rb +0 -236
- data/lib/plugins/cipher.rb +0 -203
- data/lib/plugins/ciphers/blowfish.rb +0 -126
- data/lib/plugins/coldstore.rb +0 -181
- data/lib/plugins/envelope.rb +0 -116
- data/lib/plugins/secrets.uc.rb +0 -94
- data/lib/plugins/usecase.rb +0 -239
- data/lib/plugins/usecases/init.rb +0 -145
- data/lib/plugins/usecases/open.rb +0 -108
- data/lib/session/attributes.rb +0 -279
- data/lib/session/dictionary.rb +0 -191
- data/lib/session/file.path.rb +0 -53
- data/lib/session/session.rb +0 -80
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
module OpenSecret
|
5
5
|
|
6
|
+
module ToolBelt
|
7
|
+
|
6
8
|
# CryptIO concentrates on injecting and ingesting crypt properties into and
|
7
9
|
# out of a key/value dictionary as well as injecting and ingesting cryptographic
|
8
10
|
# materials into and out of text files.
|
@@ -217,4 +219,7 @@ module OpenSecret
|
|
217
219
|
end
|
218
220
|
|
219
221
|
|
222
|
+
end
|
223
|
+
|
224
|
+
|
220
225
|
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
module OpenSecret
|
4
4
|
|
5
|
+
module ToolBelt
|
6
|
+
|
7
|
+
|
5
8
|
require 'securerandom'
|
6
9
|
|
7
10
|
# This class will be refactored into an interface implemented by a set
|
@@ -12,6 +15,7 @@ module OpenSecret
|
|
12
15
|
# information in the most secure (but simple) manner.
|
13
16
|
class Engineer
|
14
17
|
|
18
|
+
|
15
19
|
# --
|
16
20
|
# -- Get a viable machine password taking into account the human
|
17
21
|
# -- password length and the specified mix_ratio.
|
@@ -38,7 +42,6 @@ module OpenSecret
|
|
38
42
|
end
|
39
43
|
|
40
44
|
|
41
|
-
|
42
45
|
# Amalgamate the parameter passwords using a specific mix ratio. This method
|
43
46
|
# produces cryptographically stronger secrets than algorithms that simply
|
44
47
|
# concatenate two string keys together. If knowledge of one key were gained, this
|
@@ -90,4 +93,7 @@ module OpenSecret
|
|
90
93
|
end
|
91
94
|
|
92
95
|
|
96
|
+
end
|
97
|
+
|
98
|
+
|
93
99
|
end
|
File without changes
|
@@ -0,0 +1,282 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module OpenSecret
|
5
|
+
|
6
|
+
module Mapper
|
7
|
+
|
8
|
+
# <b>Collateral</b> fingers (locates/points to) resource paths and objects that
|
9
|
+
# are accessible on the filesystem of either the workstation or its associated
|
10
|
+
# (external) drives.
|
11
|
+
#
|
12
|
+
# Its ubiquitous language distinguishes four (4) base paths for the
|
13
|
+
#
|
14
|
+
# - <b>frontend store</b> - usually a path to USB connected devices like phones
|
15
|
+
# - <b>mid-end store</b> - location is on workstation off the user's home directory
|
16
|
+
# - <b>(local) backend store</b> - departure lounge located off the home directory
|
17
|
+
# - <b>(remote) backend store</b> - arrival gate on S3, Google Drive or SSH drive
|
18
|
+
class Collateral
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
|
22
|
+
# After constructing the <b>{Collateral}</b> object the building
|
23
|
+
# blocks that all paths are fabricated from must be injected.
|
24
|
+
attr_accessor :domain_name, :frontend_path
|
25
|
+
|
26
|
+
ENV_OPS_KEY_NAME = "OPS_KEY"
|
27
|
+
|
28
|
+
# The context name is used in various places (filesystem especially)
|
29
|
+
# in order to disambiguate the question of which software creates, reads,
|
30
|
+
# and updates the containing collateral.
|
31
|
+
CONTEXT_NAME = "opensecret.io"
|
32
|
+
|
33
|
+
# The name of the frontend directory in which encrypted
|
34
|
+
# session envelopes are stored in a tree-like structure.
|
35
|
+
SESSION_ENVELOPES_NAME = "session.envelopes"
|
36
|
+
|
37
|
+
# The name of the frontend directory (library) in which crypt
|
38
|
+
# keys are stored in a tree-like structure.
|
39
|
+
KEY_STORE_LIBRARY_NAME = "keystore.library"
|
40
|
+
|
41
|
+
# The name of the backend directory (library) in which crypt
|
42
|
+
# material is stored in a tree-like structure.
|
43
|
+
CRYPT_STORE_LIBRARY_NAME = "crypt.library"
|
44
|
+
|
45
|
+
# The name of the frontend directory (library) in which the
|
46
|
+
# index files are harboured in a flat structure.
|
47
|
+
CONFIG_LIBRARY_NAME = "ops.configuration"
|
48
|
+
|
49
|
+
# The name of the frontend directory in which the master keys
|
50
|
+
# are (or will be) stored.
|
51
|
+
MASTER_KEYS_DIR_NAME = "known.master.keys"
|
52
|
+
|
53
|
+
# The public key filename will end with this constant suffix.
|
54
|
+
PUBLIC_KEY_NAME_SUFFIX = "public.key.txt"
|
55
|
+
|
56
|
+
# The private key filename will end with this constant suffix.
|
57
|
+
PRIVATE_KEY_NAME_SUFFIX = "private.key.txt"
|
58
|
+
|
59
|
+
|
60
|
+
# Write the public key text into a file within the master keys
|
61
|
+
# folder. The directory path is created if necessary.
|
62
|
+
#
|
63
|
+
# @param public_key_text [String]
|
64
|
+
# the text totality that makes up the domain's public key
|
65
|
+
def write_public_key public_key_text
|
66
|
+
FileUtils.mkdir_p master_keys_path
|
67
|
+
File.write public_key_path, public_key_text
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Read the public key text from a file within the master keys
|
72
|
+
# folder. This method will fail if the public key isn't found.
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
# return the text totality making up the domain's public key
|
76
|
+
def read_public_key
|
77
|
+
return File.read( public_key_path )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Write the private key text into a file within the master keys
|
82
|
+
# folder. The directory path is created if necessary.
|
83
|
+
#
|
84
|
+
# @param private_key_text [String]
|
85
|
+
# the text totality that makes up the domain's private key
|
86
|
+
def write_private_key private_key_text
|
87
|
+
FileUtils.mkdir_p master_keys_path
|
88
|
+
File.write private_key_path, private_key_text
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Read the private key text from a file within the master keys
|
93
|
+
# folder. This method will fail if the private key isn't found.
|
94
|
+
#
|
95
|
+
# @return [String]
|
96
|
+
# return the text totality making up the domain's private key
|
97
|
+
def read_private_key
|
98
|
+
return File.read( private_key_path )
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Return true if the private key exists within a file under the
|
103
|
+
# auspices of the current domain.
|
104
|
+
#
|
105
|
+
# @param private_key_text [String]
|
106
|
+
# the text totality that makes up the domain's private key
|
107
|
+
def private_key_exists?
|
108
|
+
return File.file? private_key_path
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# The path to the initial configuration file below the user's home
|
113
|
+
# directory. The directory name the configuration file sits in is
|
114
|
+
# a dot prefixed context name derived from the value inside the
|
115
|
+
# {Mapper::Collateral::CONTEXT_NAME} constant.
|
116
|
+
#
|
117
|
+
# ~/.<<context-name>>/<<context-name>>-configuration.ini
|
118
|
+
#
|
119
|
+
# You can see the filename too is derived from the context with a
|
120
|
+
# trailing string ending in <b>.ini</b>.
|
121
|
+
#
|
122
|
+
# @return [String] full path to the context configuration file
|
123
|
+
def config_file
|
124
|
+
return File.join config_directory, "#{CONTEXT_NAME}.configuration.ini"
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# This method returns the absolute path to the directory that the
|
129
|
+
# configuration file sits in. It is basically just the dot prefixed
|
130
|
+
# context name (the {Mapper::Collateral::CONTEXT_NAME} constant).
|
131
|
+
#
|
132
|
+
# ~/.<<context-name>>
|
133
|
+
#
|
134
|
+
# @return [String] path to directory holding context configuration file
|
135
|
+
def config_directory
|
136
|
+
return File.join home_directory, ".#{CONTEXT_NAME}"
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
# The configuration file governing the files and behaviour
|
141
|
+
# of the specified domain.
|
142
|
+
def domain_config_file
|
143
|
+
return File.join config_library_path, "ops.domain.config.ini"
|
144
|
+
end
|
145
|
+
|
146
|
+
# The path within the frontend directory in which the
|
147
|
+
# data index and the index configuration files are stored.
|
148
|
+
# @return [String]
|
149
|
+
# path to the base frontend index library directory
|
150
|
+
def config_library_path
|
151
|
+
return File.join frontend_base, CONFIG_LIBRARY_NAME
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
|
158
|
+
# The path to the session's folder that will be situated
|
159
|
+
# within the frontend drive location so that any session
|
160
|
+
# materials can be carried away with the external drive.
|
161
|
+
def session_folder
|
162
|
+
return File.join frontend_base, SESSION_ENVELOPES_NAME
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
# The path to the session's index file is an amalgam of the
|
167
|
+
# session folder {session_folder} and a <b>lowercased</b>
|
168
|
+
# session id stamp that should be the first n characters of
|
169
|
+
# the session id.
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
#
|
173
|
+
# session id stamp = M4Tywsv3BgNJQSn2MrA3484T
|
174
|
+
# session filename = session.m4tywsv3bgnjqsn2mra3484t.txt
|
175
|
+
#
|
176
|
+
# @param id_stamp [String]
|
177
|
+
#
|
178
|
+
# the first n characters of the session id that will
|
179
|
+
# be lowercased and sandwiched between the words
|
180
|
+
# <tt>session.</tt> and <tt>.txt</tt>
|
181
|
+
#
|
182
|
+
# @return [String]
|
183
|
+
# path to the session file that sits inside the frontend
|
184
|
+
# drive in folder called <b>session.envelopes</b>
|
185
|
+
def session_index_file id_stamp
|
186
|
+
return File.join( session_folder, "session.#{id_stamp.downcase}.txt" )
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
# The path within the frontend directory in which the encrypted
|
193
|
+
# keystore envelopes live in a tree-like structure.
|
194
|
+
# @return [String]
|
195
|
+
# path to the base frontend keystore data directory
|
196
|
+
def frontend_keystore_path
|
197
|
+
return File.join frontend_base, KEY_STORE_LIBRARY_NAME
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# The path within the backend directory in which the encrypted
|
202
|
+
# crypt envelopes live in a tree-like structure.
|
203
|
+
# @return [String]
|
204
|
+
# path to the backend crypt store data directory
|
205
|
+
def backend_cryptstore_path
|
206
|
+
return File.join storage_url, "#{@domain_name}.#{CRYPT_STORE_LIBRARY_NAME}"
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# On non-windows systems the home directory is defined
|
211
|
+
# perfectly by Ruby's Dir object.
|
212
|
+
#
|
213
|
+
# On Windows we sometimes get /AppData/Roaming appended
|
214
|
+
# onto the actual home directory. In these cases this
|
215
|
+
# method removes it.
|
216
|
+
#
|
217
|
+
# @return [String] the path to the machine user's home directory
|
218
|
+
def home_directory
|
219
|
+
|
220
|
+
return Dir.home unless Gem.win_platform?
|
221
|
+
|
222
|
+
extraneous_path = "/AppData/Roaming"
|
223
|
+
if Dir.home.end_with? extraneous_path then
|
224
|
+
return Dir.home.gsub( extraneous_path, "" )
|
225
|
+
end
|
226
|
+
|
227
|
+
return Dir.home
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Log the configuration file to two places
|
233
|
+
#
|
234
|
+
# - the console
|
235
|
+
# - the log file below the user's home directory.
|
236
|
+
#
|
237
|
+
# The logging is done at the INFO level.
|
238
|
+
def log_config
|
239
|
+
|
240
|
+
log.info(x) { File.read(config_file).log_lines }
|
241
|
+
puts ""
|
242
|
+
puts File.read(config_file)
|
243
|
+
puts ""
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
|
251
|
+
def frontend_base
|
252
|
+
return File.join @frontend_path, "ops.#{@domain_name}"
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
def storage_url
|
257
|
+
return File.join config_directory, "#{CONTEXT_NAME}.storage"
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
def master_keys_path
|
262
|
+
return File.join frontend_base, MASTER_KEYS_DIR_NAME
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
def public_key_path
|
267
|
+
return File.join master_keys_path, "#{@domain_name}.#{PUBLIC_KEY_NAME_SUFFIX}"
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
def private_key_path
|
272
|
+
return File.join master_keys_path, "#{@domain_name}.#{PRIVATE_KEY_NAME_SUFFIX}"
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module OpenKey
|
5
|
+
|
6
|
+
require 'inifile'
|
7
|
+
|
8
|
+
# An OpenSession dictionary is a <b>2D (two dimensional) hash</b> data
|
9
|
+
# structure backed by an encrypted file.
|
10
|
+
#
|
11
|
+
# It supports operations to <b>read from</b> and <b>write to</b> a known
|
12
|
+
# filepath and given the correct symmetric encryption key it will
|
13
|
+
#
|
14
|
+
# - decrypt <b>after reading from</b> the file and
|
15
|
+
# - encrypt <b>before writing to</b> the file
|
16
|
+
#
|
17
|
+
# This dictionary extends {Hash} in order to deliver on its core key value
|
18
|
+
# storage and retrieve use cases. Extend this dictionary and provide
|
19
|
+
# context specific methods through constants to read and write context
|
20
|
+
# specific data.
|
21
|
+
#
|
22
|
+
# == The <em>Current</em> Dictionary Section
|
23
|
+
#
|
24
|
+
# This Dictionary is <b>two-dimensional</b> so all key-value pairs are stored
|
25
|
+
# under the auspices of a section.
|
26
|
+
#
|
27
|
+
# The Dictionary can track the <b>current section</b> for you and all data
|
28
|
+
# exchanges can occur in lieu of a single section if you so wish by using
|
29
|
+
# the provided {put} and {get} methods.
|
30
|
+
#
|
31
|
+
# To employ section management functionality you should pass in a current
|
32
|
+
# <b>section id</b> when creating the dictionary.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# To use the dictionary in the raw (unextended) format you create
|
36
|
+
# write and read it like this.
|
37
|
+
#
|
38
|
+
# ----------------------------------------------------------------------
|
39
|
+
#
|
40
|
+
# my_dictionary = Dictionary.create( "/path/to/backing/file" )
|
41
|
+
#
|
42
|
+
# my_dictionary["user23"] = {}
|
43
|
+
# my_dictionary["user23"]["Name"] = "Joe Bloggs"
|
44
|
+
# my_dictionary["user23"]["Email"] = "joebloggs@example.com"
|
45
|
+
# my_dictionary["user23"]["Phone"] = "+44 07342 800080"
|
46
|
+
#
|
47
|
+
# my_dictionary.write( "crypt-key-1234-wxyz" )
|
48
|
+
#
|
49
|
+
# ----------------------------------------------------------------------
|
50
|
+
#
|
51
|
+
# my_dictionary = Dictionary.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
|
52
|
+
# puts my_dictionary.has_key? "user23" # => true
|
53
|
+
# puts my_dictionary["user23"].length # => 3
|
54
|
+
# puts my_dictionary["user23"]["Email"] # => "joebloggs@example.com"
|
55
|
+
#
|
56
|
+
# ----------------------------------------------------------------------
|
57
|
+
class Dictionary < Hash
|
58
|
+
|
59
|
+
attr_accessor :backing_filepath, :section_id
|
60
|
+
|
61
|
+
|
62
|
+
# Create either a new empty dictionary or unmarshal (deserialize) the
|
63
|
+
# dictionary from an encrypted file depending on whether a file exists
|
64
|
+
# at the backing_file parameter location.
|
65
|
+
#
|
66
|
+
# If the backing file indeed exists, the crypt key will be employed to
|
67
|
+
# decode and then decrypt the contents before the unmarshal operation.
|
68
|
+
#
|
69
|
+
# The filepath is stored as an instance variable hence the {write}
|
70
|
+
# operation does not need to be told <b>where to?</b>
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# # Create Dictionary the first time
|
74
|
+
# my_dictionary = Dictionary.create( "/path/to/backing/file" )
|
75
|
+
#
|
76
|
+
# # Create Dictionary from an Encrypted Backing File
|
77
|
+
# my_dictionary = Dictionary.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
|
78
|
+
#
|
79
|
+
# @param backing_file [String]
|
80
|
+
# the backing file is the filepath to this Dictionary's encrypted
|
81
|
+
# backing file when serialized. If no file exists at this path the
|
82
|
+
# operation will instantiate and return a new empty {Hash} object.
|
83
|
+
#
|
84
|
+
# @param crypt_key [String]
|
85
|
+
# if the backing file exists then this parameter must contain a
|
86
|
+
# robust symmetric decryption key. The symmetric key will be used
|
87
|
+
# for decryption after the base64 encoded file is read.
|
88
|
+
#
|
89
|
+
# Note that the decryption key is never part of the dictionary object.
|
90
|
+
# This class method knows it but the new Dictionary has no crypt key
|
91
|
+
# instance variable. Another crypt key must then be introduced when
|
92
|
+
# serializing (writing) the dictionary back into a file.
|
93
|
+
#
|
94
|
+
# @return [Dictionary]
|
95
|
+
# return a new Dictionary that knows where to go if it needs
|
96
|
+
# to read (deserialize) or write (serialize) itself.
|
97
|
+
#
|
98
|
+
# If no file exists at the path a new empty {Hash} object is
|
99
|
+
# returned.
|
100
|
+
#
|
101
|
+
# If a file exists, then the crypt_key parameter is expected
|
102
|
+
# to be the decryption and key and the dictionary will be based
|
103
|
+
# on the decrypted contents of the file.
|
104
|
+
#
|
105
|
+
# @raise [ArgumentError]
|
106
|
+
# An {ArgumentError} is raised if either no decryption key is provided
|
107
|
+
# or one that is unsuitable (ie was not used within the encryption).
|
108
|
+
# Errors can also arise if the block coding and decoding has not been
|
109
|
+
# done satisfactorily.
|
110
|
+
def self.create backing_file, crypt_key = nil
|
111
|
+
|
112
|
+
key_missing = File.file?( backing_file ) && crypt_key.nil?
|
113
|
+
raise ArgumentError, "No crypt key provided for file #{backing_file}" if key_missing
|
114
|
+
|
115
|
+
dictionary = Dictionary.new
|
116
|
+
dictionary.backing_filepath = backing_file
|
117
|
+
|
118
|
+
return dictionary unless File.file? backing_file
|
119
|
+
|
120
|
+
file_contents = File.read( backing_file ).strip
|
121
|
+
plaintext_str = file_contents.block_decode_decrypt( crypt_key )
|
122
|
+
dictionary.ingest_contents( plaintext_str )
|
123
|
+
|
124
|
+
return dictionary
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Create either a new dictionary containing the specified section or unmarshal
|
130
|
+
# (deserialize) the dictionary from an encrypted file depending on whether a
|
131
|
+
# file exists at the backing_file parameter location and then <b>create</b> the
|
132
|
+
# section <b>only if it does not exist</b>.
|
133
|
+
#
|
134
|
+
# If the backing file indeed exists, the crypt key will be employed to
|
135
|
+
# decode and then decrypt the contents before the unmarshal operation.
|
136
|
+
#
|
137
|
+
# The filepath is stored as an instance variable hence the {write}
|
138
|
+
# operation does not need to be told <b>where to?</b>
|
139
|
+
#
|
140
|
+
# This dictionary will also know which <b>"section"</b> should be used to
|
141
|
+
# put, add, update and delete key/value data. You can employ this dictionary
|
142
|
+
# such that <b>each instance only creates, updates, removes and/or reads</b>
|
143
|
+
# from a single section.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# # Create Dictionary the first time with a section.
|
147
|
+
# my_dictionary = Dictionary.create( "/path/to/file", "Europe" )
|
148
|
+
#
|
149
|
+
# # Create Dictionary from an Encrypted Backing File
|
150
|
+
# my_dictionary = Dictionary.create( "/path/to/file", "Europe", "1234-wxyz" )
|
151
|
+
#
|
152
|
+
# @param backing_file [String]
|
153
|
+
# the backing file is the filepath to this Dictionary's encrypted
|
154
|
+
# backing file when serialized.
|
155
|
+
#
|
156
|
+
# @param section_id [String]
|
157
|
+
# the created dictionary know which <b>section</b> should be used to
|
158
|
+
# put, add, update and delete key/value data. If the backing file
|
159
|
+
# does not exist a new section is created in the empty dictionary.
|
160
|
+
#
|
161
|
+
# If the file exists a new section is created only if it is not
|
162
|
+
# already present inside the dictionary.
|
163
|
+
#
|
164
|
+
# @param crypt_key [String]
|
165
|
+
# if the backing file exists then this parameter must contain a
|
166
|
+
# robust symmetric decryption key. The symmetric key will be used
|
167
|
+
# for decryption after the base64 encoded file is read.
|
168
|
+
#
|
169
|
+
# Note that the decryption key is never part of the dictionary object.
|
170
|
+
# This class method knows it but the new Dictionary has no crypt key
|
171
|
+
# instance variable. Another crypt key must then be introduced when
|
172
|
+
# serializing (writing) the dictionary back into a file.
|
173
|
+
#
|
174
|
+
# @return [Dictionary]
|
175
|
+
# return a new Dictionary that knows where to go if it needs
|
176
|
+
# to read (deserialize) or write (serialize) itself.
|
177
|
+
#
|
178
|
+
# If no file exists at the path a new empty {Hash} object is
|
179
|
+
# returned.
|
180
|
+
#
|
181
|
+
# If a file exists, then the crypt_key parameter is expected
|
182
|
+
# to be the decryption and key and the dictionary will be based
|
183
|
+
# on the decrypted contents of the file.
|
184
|
+
#
|
185
|
+
# @raise [ArgumentError]
|
186
|
+
# An {ArgumentError} is raised if either no decryption key is provided
|
187
|
+
# or one that is unsuitable (ie was not used within the encryption).
|
188
|
+
# Errors can also arise if the block coding and decoding has not been
|
189
|
+
# done satisfactorily.
|
190
|
+
def self.create_with_section backing_file, section_id, crypt_key = nil
|
191
|
+
|
192
|
+
dictionary = create( backing_file, crypt_key = nil )
|
193
|
+
dictionary.section_id = section_id
|
194
|
+
dictionary[section_id] = {} unless dictionary.has_key?( section_id )
|
195
|
+
|
196
|
+
return dictionary
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# Write the data in this dictionary hash map into a file-system
|
202
|
+
# backend mirror whose path was specified in the {Dictionary.create}
|
203
|
+
# factory method.
|
204
|
+
#
|
205
|
+
# Technology for encryption at rest is mandatory when using this
|
206
|
+
# Dictionary to write and read files from the filesystem.
|
207
|
+
#
|
208
|
+
# Calling this {self.write} method when the file at the prescribed path
|
209
|
+
# does not exist results in the directory structure being created
|
210
|
+
# (if necessary) and then the (possibly encrypted) file being written.
|
211
|
+
#
|
212
|
+
# @param crypt_key [String]
|
213
|
+
# this parameter must contain a robust symmetric crypt key to use for
|
214
|
+
# the encryption before writing to the filesystem.
|
215
|
+
#
|
216
|
+
# Note that the decryption key is never part of the dictionary object.
|
217
|
+
# For uncrackable security this key must be changed every time the
|
218
|
+
# file is written.
|
219
|
+
def write crypt_key
|
220
|
+
|
221
|
+
ini_file = IniFile.new
|
222
|
+
self.each_key do |section_name|
|
223
|
+
ini_file[section_name] = self[section_name]
|
224
|
+
end
|
225
|
+
|
226
|
+
crypted_text = ini_file.to_s.encrypt_block_encode( crypt_key )
|
227
|
+
|
228
|
+
FileUtils.mkdir_p File.dirname(@backing_filepath)
|
229
|
+
File.write @backing_filepath, crypted_text
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
def get key_name
|
236
|
+
return self[@section_id][key_name]
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
def put key_name, key_value
|
242
|
+
self[@section_id][key_name] = key_value
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
# Ingest the contents of the INI string and merge it into a
|
249
|
+
# this object which is a {Hash}.
|
250
|
+
#
|
251
|
+
# @param the_contents [String]
|
252
|
+
# the INI string that will be ingested and morphed into
|
253
|
+
# this dictionary.
|
254
|
+
#
|
255
|
+
# @raise [ArgumentError]
|
256
|
+
# if the content contains any nil section name, key name
|
257
|
+
# or key value.
|
258
|
+
def ingest_contents the_contents
|
259
|
+
|
260
|
+
ini_file = IniFile.new( :content => the_contents )
|
261
|
+
ini_file.each do | data_group, data_key, data_value |
|
262
|
+
ingest_entry data_group, data_key, data_value
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
private
|
269
|
+
|
270
|
+
|
271
|
+
def ingest_entry section_name, key_name, value
|
272
|
+
|
273
|
+
msg = "A NIL object detected during ingestion of file [#{@filepath}]."
|
274
|
+
raise ArgumentError.new msg if section_name.nil? || key_name.nil? || value.nil?
|
275
|
+
|
276
|
+
if self.has_key? section_name then
|
277
|
+
self[section_name][key_name] = value
|
278
|
+
else
|
279
|
+
self.store section_name, { key_name => value }
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
end
|