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
@@ -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