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.
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
@@ -0,0 +1,127 @@
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
@@ -0,0 +1,170 @@
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
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module OpenSecret
5
+
6
+ module Store
7
+
8
+ # Cold storage can sync repositories with a <b>bias during conflicts</b>
9
+ # either to the <em>remote repository</em> <b>when pulling</b>, and then
10
+ # conversely to the <em>local reposiory</em> <b>when pushing</b>.
11
+ #
12
+ # In between the sync operations a ColdStore can create, read, update and
13
+ # delete to and from the local mirror.
14
+ #
15
+ # == ColdStore | Use Cases
16
+ #
17
+ # Any <b>self-respecting coldstore</b> must, after initialization, provide
18
+ # some basic (and mandatory) behaviour.
19
+ #
20
+ # These include
21
+ #
22
+ # - <b>read</b> - reading text from a (possibly unavailable) frozen path
23
+ # - <b>write</b> - writing text (effectively freezing it) to a path
24
+ # - <b>pull</b> - sync with a <b>collision bias</b> that favours the remote mirror
25
+ # - <b>push</b> - sync with a <b>collision bias</b> that favours the local mirror
26
+ #
27
+ # <b>Cold Storage</b> is borrowed from BitCoin and represents offline storage
28
+ # for keys and crypts. opensecret separates keys and crypts so that you can
29
+ # transfer and share secrets by moving keys (not the crypts).
30
+ #
31
+ # == Houses and Gold Bullion
32
+ #
33
+ # You don't carry houses or gold bullion around to rent, share or transfer
34
+ # their ownership.
35
+ #
36
+ # You copy keys to rent secrets and when the tenure is up (or you change your
37
+ # mind) you revoke access with a metaphorical lock change.
38
+ #
39
+ # opensecret embodies concepts like an owner who rents as opposed to a change
40
+ # in ownership.
41
+ #
42
+ # == trade secrets | commoditizing secrets
43
+ #
44
+ # opensecret is a conduit through which secrets can be bought and sold.
45
+ #
46
+ # It commoditizes secrets so that they can be owned, traded, leased and
47
+ # auctioned. Options to acquire or relinquish them at set prices can easily
48
+ # be taken out.
49
+ class ColdStore
50
+
51
+ # @param base_path [String]
52
+ # path to the store's (mirror) base directory.
53
+ # If the denoted directory does not exist an attempt will be made to
54
+ # create it. If a file exists at this path an error will be thrown.
55
+ #
56
+ # @param domain [String]
57
+ # the domain is an identifier (and namespace) denoting which opensecret
58
+ # "account" is being accessed. opensecret allows the creation and use of
59
+ # multiple domains.
60
+ def initialize local_path
61
+
62
+ @store_path = local_path
63
+ FileUtils.mkdir_p @store_path
64
+
65
+ end
66
+
67
+
68
+ # Read the file frozen (in this store mirror) at this path and
69
+ # return its contents.
70
+ #
71
+ # Coldstores are usually frozen offline (offmachine) so for this
72
+ # to work the {ColdStore.pull} behaviour must have executed to
73
+ # create a local store mirror. This method reads from that mirror.
74
+ #
75
+ # @param from_path [String]
76
+ # read the file frozen at this path and return its contents
77
+ # so that the defreeze process can begin.
78
+ #
79
+ # This path is relative to the base of the store defined in
80
+ # the constructor.
81
+ #
82
+ # @return [String]
83
+ # return the text frozen in a file at the denoted local path
84
+ #
85
+ # nil is reurned if no file can be found in the local mirror
86
+ # at the configured path
87
+ #
88
+ # @raise [RuntimeError]
89
+ # unless the path exists in this coldstore and that path is
90
+ # a directory (as opposed to a file).
91
+ #
92
+ # @raise [ArgumentError]
93
+ # if more than one file match is made at the path specified.
94
+ def read from_path
95
+
96
+ frozen_filepath = File.join @store_path, from_path
97
+ frozen_dir_path = File.dirname(frozen_filepath)
98
+
99
+ log.info(x) { "Coldstore will search in folder [#{frozen_dir_path.hr_path}]" }
100
+
101
+ exists_msg = "Directory #{frozen_dir_path} does not exist in store."
102
+ is_dir_msg = "Path #{frozen_dir_path} should be a directory (not a file)."
103
+ raise RuntimeError, exists_msg unless File.exists? frozen_dir_path
104
+ raise RuntimeError, is_dir_msg unless File.directory? frozen_dir_path
105
+
106
+ full_filepath = ""
107
+ file_matched = false
108
+
109
+ Dir.glob("#{frozen_dir_path}/**/*.os.txt").each do |matched_path|
110
+
111
+ log.info(x) { "Coldstore search with [#{from_path}] has matched [#{matched_path.hr_path}]" }
112
+ log.info(x) { "Ignore directory at [#{matched_path.hr_path}]." } if File.directory? matched_path
113
+ next if File.directory? matched_path
114
+
115
+ two_match_msg = "More than one file matched. The second is #{matched_path}."
116
+ raise ArgumentError, two_match_msg if file_matched
117
+ file_matched = true
118
+
119
+ full_filepath = matched_path
120
+
121
+ end
122
+
123
+ no_file_msg = "Coldstore could not find path [#{from_path}] from [#{@store_path}]."
124
+ raise RuntimeError, no_file_msg unless file_matched
125
+
126
+ log.info(x) { "Coldstore matched exactly one envelope at [#{full_filepath.hr_path}]." }
127
+ return File.read full_filepath
128
+
129
+ end
130
+
131
+
132
+ # Write (freeze) the text into a file at the denoted path. The
133
+ # folder path will be created if need be.
134
+ #
135
+ # Coldstores are usually frozen offline (offmachine) so after
136
+ # this method completes the {ColdStore.push} behaviour must be
137
+ # executed to synchronize the local coldstore freezer with the
138
+ # remote mirror.
139
+ #
140
+ # @param this_text [String]
141
+ # this is the text that needs to be frozen into the local and
142
+ # subsequently the remote coldstore freezer.
143
+ #
144
+ # @param to_path [String]
145
+ # write the text (effectively freezing it) into the file at
146
+ # this path. An attempt will be made to put down the necessary
147
+ # directory structure.
148
+ #
149
+ # This path is relative to the base of the store defined in
150
+ # the constructor.
151
+ def write this_text, to_path
152
+
153
+ freeze_filepath = File.join @store_path, to_path
154
+
155
+ log.info(x) { "ColdStore freezing #{this_text.length} characters of worthless text."}
156
+ log.info(x) { "ColdStore freeze file path => #{freeze_filepath.hr_path}"}
157
+
158
+ FileUtils.mkdir_p(File.dirname(freeze_filepath))
159
+ File.write freeze_filepath, this_text
160
+
161
+ end
162
+
163
+
164
+ private
165
+
166
+ # @todo - write sync (with a local bias during conflicts)
167
+ # The open up to the public (published) api.
168
+ def push
169
+
170
+
171
+ end
172
+
173
+ # @todo - write sync (with a rmote bias during conflicts)
174
+ # The open up to the public (published) api.
175
+ def pull
176
+
177
+ end
178
+
179
+
180
+ end
181
+
182
+
183
+ end
184
+
185
+
186
+ end