opensecret 0.0.988 → 0.0.9925

Sign up to get free protection for your applications and to get access to all the features.
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