opensecret 0.0.962 → 0.0.988

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -10
  3. data/bin/opensecret +3 -4
  4. data/bin/ops +5 -0
  5. data/lib/extension/string.rb +114 -0
  6. data/lib/factbase/facts.opensecret.io.ini +9 -21
  7. data/lib/interprete/begin.rb +232 -0
  8. data/lib/interprete/cmd.rb +621 -0
  9. data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
  10. data/lib/interprete/init.rb +205 -0
  11. data/lib/interprete/key.rb +119 -0
  12. data/lib/interprete/open.rb +148 -0
  13. data/lib/{plugins/usecases → interprete}/put.rb +19 -6
  14. data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
  15. data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
  16. data/lib/interprete/set.rb +46 -0
  17. data/lib/interprete/use.rb +43 -0
  18. data/lib/interpreter.rb +165 -0
  19. data/lib/keytools/binary.map.rb +245 -0
  20. data/lib/keytools/digester.rb +245 -0
  21. data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
  22. data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
  23. data/lib/keytools/doc.star.schema.strategy.txt +77 -0
  24. data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
  25. data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
  26. data/lib/keytools/kdf.bcrypt.rb +180 -0
  27. data/lib/keytools/kdf.pbkdf2.rb +164 -0
  28. data/lib/keytools/key.data.rb +227 -0
  29. data/lib/keytools/key.derivation.rb +341 -0
  30. data/lib/keytools/key.module.rb +140 -0
  31. data/lib/keytools/key.rb +481 -0
  32. data/lib/logging/gem.logging.rb +1 -2
  33. data/lib/modules/cryptology.md +43 -0
  34. data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
  35. data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
  36. data/lib/modules/cryptology/blowfish.rb +130 -0
  37. data/lib/modules/cryptology/cipher.rb +207 -0
  38. data/lib/modules/cryptology/collect.rb +118 -0
  39. data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
  40. data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
  41. data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
  42. data/lib/modules/mappers/collateral.rb +282 -0
  43. data/lib/modules/mappers/dictionary.rb +288 -0
  44. data/lib/modules/mappers/envelope.rb +127 -0
  45. data/lib/modules/mappers/settings.rb +170 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
  48. data/lib/notepad/scratch.pad.rb +17 -0
  49. data/lib/session/fact.finder.rb +13 -0
  50. data/lib/session/require.gem.rb +5 -0
  51. data/lib/store-commands.txt +180 -0
  52. data/lib/version.rb +1 -1
  53. data/opensecret.gemspec +5 -6
  54. metadata +74 -29
  55. data/lib/crypto/blowfish.rb +0 -85
  56. data/lib/crypto/collect.rb +0 -140
  57. data/lib/crypto/verify.rb +0 -33
  58. data/lib/opensecret.rb +0 -236
  59. data/lib/plugins/cipher.rb +0 -203
  60. data/lib/plugins/ciphers/blowfish.rb +0 -126
  61. data/lib/plugins/coldstore.rb +0 -181
  62. data/lib/plugins/envelope.rb +0 -116
  63. data/lib/plugins/secrets.uc.rb +0 -94
  64. data/lib/plugins/usecase.rb +0 -239
  65. data/lib/plugins/usecases/init.rb +0 -145
  66. data/lib/plugins/usecases/open.rb +0 -108
  67. data/lib/session/attributes.rb +0 -279
  68. data/lib/session/dictionary.rb +0 -191
  69. data/lib/session/file.path.rb +0 -53
  70. 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
@@ -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