opensecret 0.0.988 → 0.0.9925

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.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +56 -159
  3. data/bin/opensecret +2 -2
  4. data/bin/ops +17 -2
  5. data/lib/extension/string.rb +14 -16
  6. data/lib/{interpreter.rb → interprete.rb} +53 -29
  7. data/lib/keytools/binary.map.rb +49 -0
  8. data/lib/keytools/kdf.api.rb +249 -0
  9. data/lib/keytools/kdf.bcrypt.rb +64 -29
  10. data/lib/keytools/kdf.pbkdf2.rb +92 -83
  11. data/lib/keytools/kdf.scrypt.rb +190 -0
  12. data/lib/keytools/key.64.rb +326 -0
  13. data/lib/keytools/key.algo.rb +109 -0
  14. data/lib/keytools/key.api.rb +1281 -0
  15. data/lib/keytools/key.db.rb +265 -0
  16. data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
  17. data/lib/keytools/key.error.rb +110 -0
  18. data/lib/keytools/key.id.rb +271 -0
  19. data/lib/keytools/key.iv.rb +107 -0
  20. data/lib/keytools/key.local.rb +265 -0
  21. data/lib/keytools/key.mach.rb +248 -0
  22. data/lib/keytools/key.now.rb +402 -0
  23. data/lib/keytools/key.pair.rb +259 -0
  24. data/lib/keytools/key.pass.rb +120 -0
  25. data/lib/keytools/key.rb +428 -298
  26. data/lib/keytools/keydebug.txt +295 -0
  27. data/lib/logging/gem.logging.rb +3 -3
  28. data/lib/modules/cryptology/collect.rb +20 -0
  29. data/lib/session/require.gem.rb +1 -1
  30. data/lib/usecase/cmd.rb +417 -0
  31. data/lib/usecase/id.rb +36 -0
  32. data/lib/usecase/import.rb +174 -0
  33. data/lib/usecase/init.rb +78 -0
  34. data/lib/usecase/login.rb +70 -0
  35. data/lib/usecase/logout.rb +30 -0
  36. data/lib/usecase/open.rb +126 -0
  37. data/lib/{interprete → usecase}/put.rb +100 -47
  38. data/lib/usecase/read.rb +89 -0
  39. data/lib/{interprete → usecase}/safe.rb +0 -0
  40. data/lib/{interprete → usecase}/set.rb +0 -0
  41. data/lib/usecase/token.rb +111 -0
  42. data/lib/{interprete → usecase}/use.rb +0 -0
  43. data/lib/version.rb +1 -1
  44. data/opensecret.gemspec +4 -3
  45. metadata +39 -33
  46. data/lib/exception/cli.error.rb +0 -53
  47. data/lib/exception/errors/cli.errors.rb +0 -31
  48. data/lib/interprete/begin.rb +0 -232
  49. data/lib/interprete/cmd.rb +0 -621
  50. data/lib/interprete/export.rb +0 -163
  51. data/lib/interprete/init.rb +0 -205
  52. data/lib/interprete/key.rb +0 -119
  53. data/lib/interprete/open.rb +0 -148
  54. data/lib/interprete/seal.rb +0 -129
  55. data/lib/keytools/digester.rb +0 -245
  56. data/lib/keytools/key.data.rb +0 -227
  57. data/lib/keytools/key.derivation.rb +0 -341
  58. data/lib/modules/mappers/collateral.rb +0 -282
  59. data/lib/modules/mappers/envelope.rb +0 -127
  60. data/lib/modules/mappers/settings.rb +0 -170
  61. data/lib/notepad/scratch.pad.rb +0 -224
  62. data/lib/store-commands.txt +0 -180
@@ -1,282 +0,0 @@
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
@@ -1,127 +0,0 @@
1
- #!/usr/bin/ruby
2
- # coding: utf-8
3
-
4
- module OpenSecret
5
-
6
- module Mapper
7
-
8
- require 'json'
9
-
10
- # An envelope knows how to manipulate a JSON backed data structure
11
- # (put, add etc) <b>after reading and then decrypting it</b> from a
12
- # file and <b>before encrypting and then writing it</b> to a file.
13
- #
14
- # It provides behaviour to which we can create, append (add), update
15
- # (change), read parts and delete essentially two structures
16
- #
17
- # - a collection of name/value pairs
18
- # - an ordered list of values
19
- #
20
- # == JSON is Not Exposed in the Interface
21
- #
22
- # An envelope doesn't expose the data format used in the implementation
23
- # allowing this to be changed seamlessly to YAMl or other formats.
24
- #
25
- # == Symmetric Encryption and Decryption
26
- #
27
- # An envelope supports operations to <b>read from</b> and <b>write to</b>
28
- # a known filepath and with a symmetric key it can
29
- #
30
- # - decrypt <b>after reading from</b> a file and
31
- # - encrypt <b>before writing to</b> a (the same) file
32
- #
33
- # == Hashes as the Primary Data Structure
34
- #
35
- # Envelope extends {Hash} as the core data structure for holding
36
- #
37
- # - strings
38
- # - arrays
39
- # - other hashes
40
- # - booleans
41
- # - integers and floats
42
- class Envelope < Hash
43
-
44
-
45
- # Read and inject into this envelope, the data structure found in a
46
- # file at the path specified in the first parameter.
47
- #
48
- # Symmetric cryptography is mandatory for the envelope so we must
49
- # <b>encrypt before writing</b> and <b>decrypt after reading</b>.
50
- #
51
- # An argument error will result if a suitable key is not provided.
52
- #
53
- # If the file does not exist (denoting the first read) all this method
54
- # does is to stash the filepath as an instance variable and igore the
55
- # decryption key which can be nil (or ommitted).
56
- #
57
- # @param the_filepath [String]
58
- # absolute path to the file which acts as the persistent mirror to
59
- # this data structure envelope.
60
- #
61
- # @param decryption_key [String]
62
- # encryption at rest is a given so this mandatory parameter must
63
- # contain a robust symmetric decryption key. The key will be used
64
- # for decryption after the read and it will not linger (ie not cached
65
- # as an instance variable).
66
- #
67
- # @raise [ArgumentError] if the decryption key is not robust enough.
68
- def read the_filepath, decryption_key = nil
69
-
70
- @filepath = the_filepath
71
- return unless File.exists? @filepath
72
-
73
- cipher_text = Base64.decode64( File.read( @filepath ).strip )
74
- plain_text = ToolBelt::Blowfish.decryptor( cipher_text, decryption_key )
75
-
76
- data_structure = JSON.parse plain_text
77
- self.merge! data_structure
78
-
79
- end
80
-
81
-
82
- # Write the data in this envelope hash map into a file-system
83
- # backed mirror whose path was specified in the {self.read} method.
84
- #
85
- # Technology for encryption at rest is supported by this dictionary
86
- # and to this aim, please endeavour to post a robust symmetric
87
- # encryption key.
88
- #
89
- # Calling this {self.write} method when the file at the prescribed path
90
- # does not exist results in the directory structure being created
91
- # (if necessary) and then the encrypted file being written.
92
- #
93
- # @param encryption_key [String]
94
- # encryption at rest is a given so this mandatory parameter must
95
- # contain a robust symmetric encryption key. The symmetric key will
96
- # be used for the decryption after the read. Note that the decryption
97
- # key does not linger meaning it isn't cached in an instance variable.
98
- def write encryption_key
99
-
100
- FileUtils.mkdir_p(File.dirname(@filepath))
101
- cipher_text = Base64.encode64 ToolBelt::Blowfish.encryptor( self.to_json, encryption_key )
102
- File.write @filepath, cipher_text
103
-
104
- puts ""
105
- puts "=== ============================"
106
- puts "=== Envelope State ============="
107
- puts "=== ============================"
108
-
109
- a_ini_file = IniFile.new
110
- self.each_key do |section_name|
111
- a_ini_file[section_name] = self[section_name]
112
- end
113
- puts a_ini_file.to_s
114
-
115
- puts "=== ============================"
116
- puts ""
117
-
118
- end
119
-
120
-
121
- end
122
-
123
-
124
- end
125
-
126
-
127
- end
@@ -1,170 +0,0 @@
1
- #!/usr/bin/ruby
2
- # coding: utf-8
3
-
4
- module OpenSecret
5
-
6
- module Mapper
7
-
8
- require 'inifile'
9
- require 'singleton'
10
-
11
- # This class contains basic behaviour for managing a client only
12
- # (serverless) session. Configuration directives are read and written
13
- # from an INI off the home directory that is created when the session
14
- # is first initiated.
15
- class Settings
16
-
17
- # Stash the setting directive and its value into the configuration file
18
- # using the default settings group.
19
- #
20
- # The default settings group is resolved via {Collateral::CONTEXT_NAME}
21
- #
22
- # @param key_name [String] the name of the key whose value is to be written
23
- # @param key_value [String] the data item value of the key specified
24
- def self.stash key_name, key_value
25
- write Collateral::CONTEXT_NAME, key_name, key_value
26
- end
27
-
28
-
29
- # Stash the setting directive and its value into the configuration file
30
- # using the default settings group.
31
- #
32
- # The default settings group is resolved via {Collateral::CONTEXT_NAME}
33
- #
34
- # @param key_name [String] the name of the key whose value is to be written
35
- # @return [String]
36
- # return the value of the configuration directive in the default group
37
- def self.grab key_name
38
- read Collateral::CONTEXT_NAME, key_name
39
- end
40
-
41
-
42
- # Write the key/value pair in the parameter into the session's
43
- # configuration INI file that lives in a context-named folder
44
- # off the home directory.
45
- #
46
- # The session file will be in a folder whose name is simply
47
- # the dot prefixed context_name. The session file itself will
48
- # be named using context_name + @@filename_tail
49
- #
50
- # @example ~/.openbox/openbox-session.ini is the filepath for context "openbox"
51
- #
52
- # If neither the folder nor file exist, both are created.
53
- # If the file did not exist a new one will with the contents
54
- # (if the key is length and the value is 2m).
55
- #
56
- # [openbox]
57
- # length = 2m
58
- #
59
- # If the file does already exist, an appropriate merge will be
60
- # performed to create or update the section name, key name and
61
- # value. The file may end up looking like
62
- #
63
- # [closedbox]
64
- # shape = cuboid
65
- # color = blue
66
- #
67
- # [openbox]
68
- # length = 2m
69
- # width = 3m
70
- #
71
- # @param section_name [String] name grouping the section of config values
72
- # @param key [String] the key name of config directive to be written into the file
73
- # @param value [String] value of the config directive to be written into the file
74
- #
75
- def self.write section_name, key, value
76
-
77
- config_filepath = Collateral.instance.config_file
78
- config_directory = Collateral.instance.config_directory
79
-
80
- FileUtils.mkdir_p config_directory unless File.exists? config_directory
81
-
82
- config_map = IniFile.new( :filename => config_filepath, :encoding => 'UTF-8' )
83
- config_map = IniFile.load( config_filepath ) if File.exists? config_filepath
84
-
85
- config_map[section_name][key] = value
86
- config_map.write
87
-
88
- end
89
-
90
-
91
- # Given the configuration key name and the context name, get the
92
- # corresponding key value from the configuration file whose path
93
- # is acquired using the {self#get_filepath} method.
94
- #
95
- # @param key_name [String] the key whose value is to be retrieved
96
- #
97
- # @return [String] the value configured for the parameter key
98
- #
99
- # @raise ArgumentError for any one of a long list of reasons that
100
- # cause the key value to not be retrieved. This can range from
101
- # non-existent directories and files, non readable files, incorrect
102
- # configurations right down to missing keys or even missing values.
103
- def self.read section_name, key_name
104
-
105
- the_data = get_inifile_data
106
-
107
- section_exists = the_data.has_section?( section_name )
108
- raise ArgumentError.new "Section [#{section_name}] not found in INI file => #{the_data.to_s}" unless section_exists
109
-
110
- key_exists = the_data[ section_name ].has_key?( key_name )
111
- raise ArgumentError.new "Key [#{key_name}] not found in section [#{section_name}] => #{the_data.to_s}" unless key_exists
112
-
113
- rawvalue = the_data[section_name][key_name]
114
- raise ArgumentError.new "Empty value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if rawvalue.empty?
115
-
116
- keyvalue = rawvalue.chomp.strip
117
- raise ArgumentError.new "Whitespace value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if keyvalue.empty?
118
-
119
- return keyvalue
120
-
121
- end
122
-
123
-
124
- # Return true if the settings configuration file contains the specified
125
- # key (in the 2nd parameter) within the section name specified in the
126
- # first parameter.
127
- #
128
- # This method does not check the contents (value) of the key. Even if it
129
- # is an empty string, this method returns true so long as the section
130
- # exists and the key exists within that.
131
- #
132
- # @param section_name [String] the name of the section to search under
133
- # @param key_name [String] the name of the key to test for existence
134
- # @return [Boolean]
135
- # return true if both the section and the key exists within it.
136
- # return false if the section specified does not exist.
137
- #
138
- # raise [ArgumentError]
139
- # if the INI file does not exist
140
- def self.contains_key? section_name, key_name
141
-
142
- the_data = get_inifile_data
143
-
144
- return false unless the_data.has_section?( section_name )
145
- return the_data[ section_name ].has_key?( key_name )
146
-
147
- end
148
-
149
-
150
- private
151
-
152
-
153
- def self.get_inifile_data
154
-
155
- the_file = Collateral.instance.config_file
156
- raise ArgumentError.new "No configuration file found => [ #{the_file} ]" unless File.exists? the_file
157
-
158
- the_text = File.read the_file
159
- raise ArgumentError.new "Configuration file is empty => [ #{the_file} ]" if the_text.empty?
160
-
161
- return IniFile.load the_file
162
-
163
- end
164
-
165
- end
166
-
167
- end
168
-
169
-
170
- end