opensecret 0.0.960 → 0.0.962
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.
- checksums.yaml +4 -4
- data/lib/notepad/{blow.rb → scratch.pad.rb} +1 -1
- data/lib/opensecret.rb +36 -5
- data/lib/plugins/cipher.rb +100 -201
- data/lib/plugins/ciphers/aes-256.rb +85 -101
- data/lib/plugins/ciphers/blowfish.rb +2 -2
- data/lib/plugins/coldstore.rb +38 -2
- data/lib/plugins/crypt.io.rb +220 -0
- data/lib/plugins/secrets.uc.rb +44 -0
- data/lib/plugins/usecases/init.rb +9 -1
- data/lib/plugins/usecases/lock.rb +3 -3
- data/lib/plugins/usecases/open.rb +4 -4
- data/lib/plugins/usecases/put.rb +1 -1
- data/lib/plugins/usecases/unlock.rb +208 -0
- data/lib/version.rb +1 -1
- metadata +5 -10
- data/lib/opensecret/executors/crypt.keys/crypt.keys.ini +0 -26
- data/lib/opensecret/executors/crypt.keys/crypt.keys.rb +0 -68
- data/lib/opensecret/executors/decrypt/decrypt.ini +0 -64
- data/lib/opensecret/executors/decrypt/decrypt.rb +0 -49
- data/lib/opensecret/executors/encrypt/encrypt.ini +0 -55
- data/lib/opensecret/executors/encrypt/encrypt.rb +0 -82
- data/lib/using.txt +0 -247
@@ -61,7 +61,7 @@ module OpenSecret
|
|
61
61
|
log.info(x) { "os blowfish request to encrypt plain text with provided key." }
|
62
62
|
|
63
63
|
block_txt = plain_text
|
64
|
-
block_txt +=
|
64
|
+
block_txt += CryptIO::TEXT_PADDER until block_txt.bytesize % OpenSecret::Blowfish::BLOWFISH_BLOCK_LEN == 0
|
65
65
|
raw_stretched_key = Digest::SHA256.digest(encryption_key)
|
66
66
|
|
67
67
|
blowfish_encryptor = OpenSSL::Cipher.new(OpenSecret::Blowfish::BLOWFISH_CIPHER_ID).encrypt
|
@@ -113,7 +113,7 @@ module OpenSecret
|
|
113
113
|
decrypt_tool.key = digested_key
|
114
114
|
|
115
115
|
padded_plaintxt = decrypt_tool.update(cipher_text) << decrypt_tool.final
|
116
|
-
pad_begin_index = padded_plaintxt.index
|
116
|
+
pad_begin_index = padded_plaintxt.index CryptIO::TEXT_PADDER
|
117
117
|
return padded_plaintxt if pad_begin_index.nil?
|
118
118
|
return padded_plaintxt[ 0 .. (pad_begin_index-1) ]
|
119
119
|
|
data/lib/plugins/coldstore.rb
CHANGED
@@ -82,11 +82,47 @@ module OpenSecret
|
|
82
82
|
#
|
83
83
|
# nil is reurned if no file can be found in the local mirror
|
84
84
|
# at the configured path
|
85
|
+
#
|
86
|
+
# @raise [RuntimeError]
|
87
|
+
# unless the path exists in this coldstore and that path is
|
88
|
+
# a directory (as opposed to a file).
|
89
|
+
#
|
90
|
+
# @raise [ArgumentError]
|
91
|
+
# if more than one file match is made at the path specified.
|
85
92
|
def read from_path
|
86
93
|
|
87
94
|
frozen_filepath = File.join @store_path, from_path
|
88
|
-
|
89
|
-
|
95
|
+
frozen_dir_path = File.dirname(frozen_filepath)
|
96
|
+
|
97
|
+
log.info(x) { "Coldstore will search in folder [#{frozen_dir_path.hr_path}]" }
|
98
|
+
|
99
|
+
exists_msg = "Directory #{frozen_dir_path} does not exist in store."
|
100
|
+
is_dir_msg = "Path #{frozen_dir_path} should be a directory (not a file)."
|
101
|
+
raise RuntimeError, exists_msg unless File.exists? frozen_dir_path
|
102
|
+
raise RuntimeError, is_dir_msg unless File.directory? frozen_dir_path
|
103
|
+
|
104
|
+
full_filepath = ""
|
105
|
+
file_matched = false
|
106
|
+
|
107
|
+
Dir.glob("#{frozen_dir_path}/**/*.os.txt").each do |matched_path|
|
108
|
+
|
109
|
+
log.info(x) { "Coldstore search with [#{from_path}] has matched [#{matched_path.hr_path}]" }
|
110
|
+
log.info(x) { "Ignore directory at [#{matched_path.hr_path}]." } if File.directory? matched_path
|
111
|
+
next if File.directory? matched_path
|
112
|
+
|
113
|
+
two_match_msg = "More than one file matched. The second is #{matched_path}."
|
114
|
+
raise ArgumentError, two_match_msg if file_matched
|
115
|
+
file_matched = true
|
116
|
+
|
117
|
+
full_filepath = matched_path
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
no_file_msg = "Coldstore could not find path [#{from_path}] from [#{@store_path}]."
|
122
|
+
raise RuntimeError, no_file_msg unless file_matched
|
123
|
+
|
124
|
+
log.info(x) { "Coldstore matched exactly one envelope at [#{full_filepath.hr_path}]." }
|
125
|
+
return File.read full_filepath
|
90
126
|
|
91
127
|
end
|
92
128
|
|
@@ -0,0 +1,220 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module OpenSecret
|
5
|
+
|
6
|
+
# CryptIO concentrates on injecting and ingesting crypt properties into and
|
7
|
+
# out of a key/value dictionary as well as injecting and ingesting cryptographic
|
8
|
+
# materials into and out of text files.
|
9
|
+
#
|
10
|
+
# == Cryptographic Properties
|
11
|
+
#
|
12
|
+
# A crypt properties dictionary acts as <b>output from every encryption event</b>
|
13
|
+
# and <b>input to every decryption event</b>. The most common properties include
|
14
|
+
#
|
15
|
+
# - the symmetric key used for the encryption and decryption
|
16
|
+
# - the iv (initialization vector) that adds another dimension of strength
|
17
|
+
# - authorization data that thwarts switch attacks by tying context to content
|
18
|
+
# - the cipher algorithm, its implementation and its encryption strength
|
19
|
+
# - the various glue strings that allow related ciphertext to occupy a file
|
20
|
+
#
|
21
|
+
# == Why Pad?
|
22
|
+
#
|
23
|
+
# Many ciphers (like Blowfish) constrains plain text lengths to multiples
|
24
|
+
# of 8 (or 16) and a common <b>right pad with spaces</b> strategy is employed
|
25
|
+
# as a workaround. opensecret does it diferently.
|
26
|
+
#
|
27
|
+
# == Why isn't Space Padding Used?
|
28
|
+
#
|
29
|
+
# If opensecret padded plaintext (ending in one or more spaces) with
|
30
|
+
# spaces, the decrypt phase (after right stripping spaces) would return
|
31
|
+
# plain text string <b>shorter than the original</b>.
|
32
|
+
#
|
33
|
+
# == Why Unusual Padding and Separators
|
34
|
+
#
|
35
|
+
# Why does opensecret employ unusual strings for padding and separation.
|
36
|
+
#
|
37
|
+
# The separator string must be unusual to make it unlikely for it to occur in any
|
38
|
+
# of the map's key value pairs nor indeed the chunk of text being glued. Were
|
39
|
+
# this to happen, the separate and reconstitute phase may not accurately return
|
40
|
+
# the same two entities we are employed to unite.
|
41
|
+
#
|
42
|
+
# == So How is Padding Done?
|
43
|
+
#
|
44
|
+
# Instead of single space padding - opensecret uses an unlikely 7 character
|
45
|
+
# padder which is repeated until the multiple is reached.
|
46
|
+
#
|
47
|
+
# <tt><-|@|-></tt>
|
48
|
+
#
|
49
|
+
# == So How is Padding Done?
|
50
|
+
#
|
51
|
+
# The <b>padder length must be a prime number</b> or infinite loops could occur.
|
52
|
+
#
|
53
|
+
# If the padder string is likely to occur in the plain text, another
|
54
|
+
# padder (or strategy) should and could be employed.
|
55
|
+
#
|
56
|
+
class CryptIO
|
57
|
+
|
58
|
+
|
59
|
+
# The opensecret text padder. See the class description for an analysis
|
60
|
+
# of the use of this type of padder.
|
61
|
+
TEXT_PADDER = "<-|@|->"
|
62
|
+
|
63
|
+
# An unusual string that glues together an encryption dictionary and
|
64
|
+
# a chunk of base64 encoded and encrypted ciphertext.
|
65
|
+
# The string must be unusual enough to ensure it does not occur within
|
66
|
+
# the dictionary metadata keys or values.
|
67
|
+
INNER_GLUE_STRING = "\n<-|@| < || opensecret inner crypt material axis || > |@|->\n\n"
|
68
|
+
|
69
|
+
# An unusual string that glues together the asymmetrically encrypted outer
|
70
|
+
# encryption key with the outer crypted text.
|
71
|
+
OUTER_GLUE_STRING = "\n<-|@| < || opensecret outer crypt material axis || > |@|->\n\n"
|
72
|
+
|
73
|
+
# Text header for key-value pairs hash map that will be serialized.
|
74
|
+
DICT_HEADER_NAME = "crypt.properties"
|
75
|
+
|
76
|
+
# Name for the class of cipher employed.
|
77
|
+
DICT_CIPHER_NAME = "cipher.class"
|
78
|
+
|
79
|
+
# Name for the {Base64} encoded symmetric (lock/unlock) crypt key.
|
80
|
+
DICT_CRYPT_KEY = "encryption.key"
|
81
|
+
|
82
|
+
# Dictionary name for the encryption iv (initialization vector)
|
83
|
+
DICT_CRYPT_IV = "encryption.iv"
|
84
|
+
|
85
|
+
# Dictionary name for the Base64 (urlsafe) encoded plaintext digest.
|
86
|
+
DICT_TEXT_DIGEST = "plaintext.digest"
|
87
|
+
|
88
|
+
|
89
|
+
# Serialize and then unify a hash map and a textual chunk using
|
90
|
+
# a known but unusual separator string in a manner that protects
|
91
|
+
# content integrity during the serialize / deserialize process.
|
92
|
+
#
|
93
|
+
# This crypt serialization uses a specific "inner glue" as the
|
94
|
+
# string that separates the serialized key/value dictionary and
|
95
|
+
# the encoded textual block.
|
96
|
+
#
|
97
|
+
# @param hash_map [String]
|
98
|
+
# this hash (dictionary) will be serialized into INI formatted text
|
99
|
+
# using behaviour from {Hash} and {IniFile}.
|
100
|
+
#
|
101
|
+
# @param text_chunk [String]
|
102
|
+
# the usually Base64 encrypted textual block to be glued at the
|
103
|
+
# bottom of the returned block.
|
104
|
+
#
|
105
|
+
# @return [String] serialized and glued together result of map plus text
|
106
|
+
#
|
107
|
+
# @raise [ArgumentError]
|
108
|
+
# if the dictionary hash_map is either nil or empty.
|
109
|
+
def self.inner_crypt_serialize hash_map, text_chunk
|
110
|
+
|
111
|
+
nil_or_empty_hash = hash_map.nil? || hash_map.empty?
|
112
|
+
raise ArgumentError, "Cannot serialize nil or empty properties." if nil_or_empty_hash
|
113
|
+
ini_map = IniFile.new
|
114
|
+
ini_map[ DICT_HEADER_NAME ] = hash_map
|
115
|
+
return ini_map.to_s + INNER_GLUE_STRING + text_chunk
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Deserialize an opensecret formatted text which contains an
|
121
|
+
# encryption properties dictionary (serialized in INI format)
|
122
|
+
# and a Base64 encoded crypt block which is the subject of the
|
123
|
+
# encryption dictionary.
|
124
|
+
#
|
125
|
+
# The crypt serialization used a specific "inner glue" as the
|
126
|
+
# string that separates the serialized key/value dictionary and
|
127
|
+
# the encoded textual block. We now employ this glue to split
|
128
|
+
# the serialized dictionary from the textual block.
|
129
|
+
#
|
130
|
+
# @param hash_map [String]
|
131
|
+
# send an instantiated hash (dictionary) which will be populated
|
132
|
+
# by this deserialize operation. The dictionary propeties can
|
133
|
+
# then be used to decrypt the returned ciphertext.
|
134
|
+
#
|
135
|
+
# @param text_block [String]
|
136
|
+
# the first of a two-part amalgamation is a hash (dictionary) in
|
137
|
+
# INI serialized form and the second part is a Base64 encrypted
|
138
|
+
# textual block.
|
139
|
+
#
|
140
|
+
# The deserialized key/value pairs will be stuffed into the
|
141
|
+
# non nil (usually empty) hash map in the first parameter and
|
142
|
+
# the block (in the 2nd part) will be Base64 decoded and
|
143
|
+
# returned by this method.
|
144
|
+
#
|
145
|
+
# @return [String]
|
146
|
+
# The encoded block in the 2nd part of the 2nd parameter will be
|
147
|
+
# Base64 decoded and returned.
|
148
|
+
#
|
149
|
+
# @raise [ArgumentError]
|
150
|
+
# if the dictionary hash_map is either nil or empty. Also if
|
151
|
+
# the inner glue tying the two parts together is missing an
|
152
|
+
# {ArgumentError} will be thrown.
|
153
|
+
def self.inner_crypt_deserialize hash_map, text_block
|
154
|
+
|
155
|
+
raise ArgumentError, "Cannot populate a nil hash map." if hash_map.nil?
|
156
|
+
assert_contains_glue text_block, INNER_GLUE_STRING
|
157
|
+
|
158
|
+
serialized_map = text_block.split(INNER_GLUE_STRING).first.strip
|
159
|
+
encoded64_text = text_block.split(INNER_GLUE_STRING).last.strip
|
160
|
+
ini_props_hash = IniFile.new( :content => serialized_map )
|
161
|
+
encrypt_values = ini_props_hash[DICT_HEADER_NAME]
|
162
|
+
|
163
|
+
hash_map.merge!( encrypt_values )
|
164
|
+
return Base64.decode64( encoded64_text )
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Using an outer divider (glue) - attach the asymmetrically encrypted outer
|
170
|
+
# encryption key with the outer encrypted text.
|
171
|
+
#
|
172
|
+
# @param crypt_material_x [String] asymmetrically encrypted (encoded) outer encryption key
|
173
|
+
# @param crypt_material_y [String] symmetrically encrypted inner metadata and payload crypt
|
174
|
+
#
|
175
|
+
# @return [String] concatenated result of the two crypt materials and divider string
|
176
|
+
def self.outer_crypt_serialize crypt_material_x, crypt_material_y
|
177
|
+
return crypt_material_x + OUTER_GLUE_STRING + crypt_material_y
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# Given two blocks of text that were bounded together by the
|
182
|
+
# {self.outer_crypt_serialize} method we must return either the
|
183
|
+
# first block (true) or the second (false).
|
184
|
+
#
|
185
|
+
# @param crypt_material [String]
|
186
|
+
# large block of text in two parts that is divided by the
|
187
|
+
# outer glue string.
|
188
|
+
#
|
189
|
+
# @param top_block [Boolean]
|
190
|
+
# if true the top (of the two) blocks will be returned
|
191
|
+
# otherwise the bottom block is returned.
|
192
|
+
#
|
193
|
+
# @return [String] either the first or second block of text
|
194
|
+
#
|
195
|
+
# @raise [ArgumentError]
|
196
|
+
# If the outer glue string tying the two parts together is
|
197
|
+
# missing an {ArgumentError} will be thrown.
|
198
|
+
def self.outer_crypt_deserialize os_material, top_block
|
199
|
+
|
200
|
+
assert_contains_glue os_material, OUTER_GLUE_STRING
|
201
|
+
return os_material.split(OUTER_GLUE_STRING).first.strip if top_block
|
202
|
+
return os_material.split(OUTER_GLUE_STRING).last.strip
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def self.assert_contains_glue os_crypted_block, glue_string
|
210
|
+
|
211
|
+
no_glue_msg = "\nGlue string not in opensecret cipher block.\n#{glue_string}\n"
|
212
|
+
raise ArgumentError, no_glue_msg unless os_crypted_block.include? glue_string
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
end
|
data/lib/plugins/secrets.uc.rb
CHANGED
@@ -44,6 +44,50 @@ module OpenSecret
|
|
44
44
|
end
|
45
45
|
|
46
46
|
|
47
|
+
# Unlock the {OpenSSL::PKey::RSA} private key and return the cipher object
|
48
|
+
# usable for asymmetric encryption, decryption, signing and signature
|
49
|
+
# verification use cases.
|
50
|
+
#
|
51
|
+
# The returned private key can be used to generate its twin public key
|
52
|
+
# and should be used to verify the same public key as (if) and when the
|
53
|
+
# need arises.
|
54
|
+
#
|
55
|
+
# @param locked_private_key [String]
|
56
|
+
# the locked up private key ciphertext
|
57
|
+
#
|
58
|
+
# @param unlock_key [String]
|
59
|
+
# the symmetric encryption key that can be used to unlock the private
|
60
|
+
# key ciphertext in the first parameter.
|
61
|
+
#
|
62
|
+
# @return [OpenSSL::PKey::RSA]
|
63
|
+
# return the {OpenSSL::PKey::RSA} private key that will be
|
64
|
+
# usable for asymmetric encryption, decryption, signing and signature
|
65
|
+
# verification.
|
66
|
+
def unlock_private_key locked_private_key, unlock_key
|
67
|
+
return OpenSSL::PKey::RSA.new locked_private_key, unlock_key
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Return the {OpenSSL::PKey::RSA} private key which is a cipher object
|
72
|
+
# usable for asymmetric encryption, decryption, signing and signature
|
73
|
+
# verification use cases.
|
74
|
+
#
|
75
|
+
# The returned private key can be used to generate its twin public key
|
76
|
+
# and should be used to verify the same public key as (if) and when the
|
77
|
+
# need arises.
|
78
|
+
#
|
79
|
+
# @param private_key_text [String]
|
80
|
+
# the private key plain text
|
81
|
+
#
|
82
|
+
# @return [OpenSSL::PKey::RSA]
|
83
|
+
# return the {OpenSSL::PKey::RSA} private key that will be
|
84
|
+
# usable for asymmetric encryption, decryption, signing and signature
|
85
|
+
# verification.
|
86
|
+
def to_private_key private_key_text
|
87
|
+
return OpenSSL::PKey::RSA.new private_key_text
|
88
|
+
end
|
89
|
+
|
90
|
+
|
47
91
|
end
|
48
92
|
|
49
93
|
|
@@ -88,12 +88,20 @@ module OpenSecret
|
|
88
88
|
machine_key_x = Base64.urlsafe_encode64(
|
89
89
|
Blowfish.new.encryptor(machine_key, machine_key_crypt_key)
|
90
90
|
)
|
91
|
-
public_key_64 = Base64.urlsafe_encode64 asymmetric_keys.public_key.to_pem
|
92
91
|
|
93
92
|
OpenSession::Attributes.stash @c[:global][:name], @c[:global][:name], @c[:global][:machine_key_x], machine_key_x
|
94
93
|
OpenSession::Attributes.stash @c[:global][:name], @c[:global][:name], @c[:global][:stamp_key], @c[:global][:stamp_23]
|
94
|
+
|
95
|
+
|
96
|
+
## Change and write the public key to own file (see paper notes for naming direction)
|
97
|
+
## Change and write the public key to own file (see paper notes for naming direction)
|
98
|
+
## Change and write the public key to own file (see paper notes for naming direction)
|
99
|
+
public_key_64 = Base64.urlsafe_encode64 asymmetric_keys.public_key.to_pem
|
95
100
|
OpenSession::Attributes.stash @c[:global][:name], @c[:global][:name], @c[:global][:publickey_id], public_key_64
|
96
101
|
|
102
|
+
## not needed - produce public key from private key then validate with key in configuration.
|
103
|
+
## not needed - produce public key from private key then validate with key in configuration.
|
104
|
+
## not needed - produce public key from private key then validate with key in configuration.
|
97
105
|
to_sign_segments = [ secured_keytext, public_key_64, @email_addr, @c[:global][:stamp_23] ]
|
98
106
|
to_sign_packet = to_sign_segments.alphanumeric_union.concat_length
|
99
107
|
signature_string = Base64.urlsafe_encode64( asymmetric_keys.sign( OpenSSL::Digest::SHA256.new, to_sign_packet ) )
|
@@ -68,15 +68,15 @@ module OpenSecret
|
|
68
68
|
def execute
|
69
69
|
|
70
70
|
rel_filepath = OpenSession::Attributes.instance.get_value @@context_name, @c[:open][:open_name], @c[:open][:open_pathname]
|
71
|
-
master_public_key = Base64.urlsafe_decode64( OpenSession::Attributes.instance.get_value @c[:global][:name], @c[:global][:name], "public.key" )
|
71
|
+
master_public_key = OpenSSL::PKey::RSA.new ( Base64.urlsafe_decode64( OpenSession::Attributes.instance.get_value @c[:global][:name], @c[:global][:name], "public.key" ) )
|
72
72
|
|
73
73
|
main_store = ColdStore.new @c[:global][:store_mainpath]
|
74
74
|
keys_store = ColdStore.new @c[:global][:store_keyspath]
|
75
75
|
|
76
76
|
envelope = get_envelope
|
77
77
|
asym_key = OpenSSL::PKey::RSA.new @c[:global][:bit_key_size]
|
78
|
-
lockdown =
|
79
|
-
lockedup =
|
78
|
+
lockdown = Cipher.encrypt_it( asym_key.public_key, envelope.to_json )
|
79
|
+
lockedup = Cipher.encrypt_it( master_public_key, asym_key.export )
|
80
80
|
|
81
81
|
##### ###################################### #####
|
82
82
|
##### ###################################### #####
|
@@ -27,7 +27,7 @@ module OpenSecret
|
|
27
27
|
#
|
28
28
|
class Open < SecretsUseCase
|
29
29
|
|
30
|
-
attr_writer :
|
30
|
+
attr_writer :outer_path
|
31
31
|
@@context_name = "opensecret"
|
32
32
|
|
33
33
|
# Execute the <tt>open use case</tt> activities which precedes the ability to
|
@@ -69,9 +69,9 @@ module OpenSecret
|
|
69
69
|
#
|
70
70
|
def execute
|
71
71
|
|
72
|
-
last_fwdslash_index = @
|
73
|
-
folder_path = @
|
74
|
-
file_word = @
|
72
|
+
last_fwdslash_index = @outer_path.rindex "/"
|
73
|
+
folder_path = @outer_path[0 .. last_fwdslash_index]
|
74
|
+
file_word = @outer_path[last_fwdslash_index .. -1]
|
75
75
|
|
76
76
|
session_folder_path = File.join @p[:open_dirpath], folder_path
|
77
77
|
|
data/lib/plugins/usecases/put.rb
CHANGED
@@ -77,7 +77,7 @@ module OpenSecret
|
|
77
77
|
@@context_name = "opensecret"
|
78
78
|
|
79
79
|
# The <b>put use case</b> follows <b>open</b> and it adds secrets into an
|
80
|
-
# <em>(encrypted at rest)</em>
|
80
|
+
# <em>(encrypted at rest)</em> envelope. Put can be called many times to
|
81
81
|
# add secrets. Finally the <b>lock use case</b> commits all opened secrets
|
82
82
|
# into the configured storage engines.
|
83
83
|
#
|
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module OpenSecret
|
4
|
+
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
# The <tt>unlock use case</tt> moves doubly encrypted material <b>from the frozen</b>
|
8
|
+
# (key and crypt) stores, <b>into an (encrypted) session envelope</b>.
|
9
|
+
#
|
10
|
+
# Material within the auspices of the session envelope can be manipulated by commands
|
11
|
+
# like {OpenSecret::Put} and remove and also read by commands like peep, look and update.
|
12
|
+
#
|
13
|
+
# <b>Unlock | Pre-Conditions</b>
|
14
|
+
#
|
15
|
+
# To deliver on its core promise of moving the encrypted (key and crypt) stores
|
16
|
+
# to an encrypted session envelope, <b>unlock</b> expects
|
17
|
+
#
|
18
|
+
# - the local key and crypt stores to be pulled (refreshed) from remote
|
19
|
+
# - the destination session path to either be empty (or)
|
20
|
+
# - a populated session path containing equivalent data (ie cannot be overwritten)
|
21
|
+
# - both the human (agent) password and workstation key to be correct
|
22
|
+
#
|
23
|
+
# <b>Unlock | Alternate Flows</b>
|
24
|
+
#
|
25
|
+
# This use case requires the human (agent) password unless the <tt>--no-human-password</tt>
|
26
|
+
# flag was posted along with the <tt>init</tt> command. In this instance an agent password
|
27
|
+
# will have been generated and encrypted with the user's home tree.
|
28
|
+
#
|
29
|
+
# <b>Unlock | Observable Value</b>
|
30
|
+
#
|
31
|
+
# The observable value proffered by unlock is
|
32
|
+
#
|
33
|
+
# - validating the existence of the requested secret path
|
34
|
+
# - validation of the human password and workstation key
|
35
|
+
# - the keys and crypt at the paths are decrypted
|
36
|
+
# - the session envelope is stuffed with the crypted material
|
37
|
+
# - an encrypted session envelope (post-stuffing)
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
#
|
41
|
+
# These use case steps are implemented and ready to go.
|
42
|
+
#
|
43
|
+
# $ opensecret open geo/countries
|
44
|
+
# $ opensecret put uganda/capital kampala
|
45
|
+
# $ opensecret put kenya/capital nairobi
|
46
|
+
# $ opensecret put france/capital paris
|
47
|
+
# $ opensecret put france/population 23000000
|
48
|
+
# $ opensecret lock
|
49
|
+
# $ opensecret unlock geo/countries --with=asdfasdf
|
50
|
+
#
|
51
|
+
# Below are future examples with use cases like list and print
|
52
|
+
# which curently have not been implemented.
|
53
|
+
#
|
54
|
+
# $ opensecret init bobby@example.com --with="hUM4n-0pen$3cr3t"
|
55
|
+
#
|
56
|
+
# $ opensecret open my/gadgets
|
57
|
+
#
|
58
|
+
# $ opensecret put laptop/login/username bob
|
59
|
+
# $ opensecret put laptop/login/password bob$_p455w0rd
|
60
|
+
# $ opensecret put iphone/pin 8529
|
61
|
+
#
|
62
|
+
# $ opensecret lock
|
63
|
+
# $ opensecret list
|
64
|
+
#
|
65
|
+
# $ opensecret unlock my/gadgets --with="hUM4n-0pen$3cr3t"
|
66
|
+
# $ opensecret print
|
67
|
+
#
|
68
|
+
# $ opensecret put wifi/ssid Virgin-HGPAS
|
69
|
+
# $ opensecret put wifi/password SDBLJHGPASDFA1234ZNF
|
70
|
+
#
|
71
|
+
# $ opensecret print
|
72
|
+
# $ opensecret lock
|
73
|
+
#
|
74
|
+
class Unlock < SecretsUseCase
|
75
|
+
|
76
|
+
attr_writer :outer_path, :master_p4ss
|
77
|
+
@@context_name = "opensecret"
|
78
|
+
|
79
|
+
|
80
|
+
# The <tt>lock use case</tt> is called after {OpenSecret::Open} and {OpenSecret::Put}
|
81
|
+
# and its effect is to dispatch the doubly encrypted materrial to the configured storage
|
82
|
+
# platform, be it Git, S3, SSH or just an accessible file-system.
|
83
|
+
#
|
84
|
+
# <b>Main Flow of Events</b>
|
85
|
+
#
|
86
|
+
# To seal the envelope built up by put, file and add amongst others requires
|
87
|
+
# that we
|
88
|
+
#
|
89
|
+
# - get the encrypted envelope
|
90
|
+
# - create a strong asymmetric key
|
91
|
+
# - lock the envelope using opensecret's double encrypt modus operandi
|
92
|
+
# - place the doubly encrypted result into the crypt store
|
93
|
+
# - lock the asymmetric key again with opensecret's double encrypt modus operandi
|
94
|
+
# - place the doubly encrypted result into the key store
|
95
|
+
#
|
96
|
+
# The second double lock of the crypted envelope is done with the public key
|
97
|
+
# aspect of an asymmetric key created here.
|
98
|
+
#
|
99
|
+
# The second double lock of the crypted private key (created here) is done with
|
100
|
+
# the master public key created in the {Init.execute} use case main flow.
|
101
|
+
#
|
102
|
+
# <b>Lock | Observable Value</b>
|
103
|
+
#
|
104
|
+
# The observable value after the lock use case has completed entails
|
105
|
+
#
|
106
|
+
# - a doubly encrypted data envelope placed in the backend store
|
107
|
+
# - a doubly encrypted private key placed in the frontend store
|
108
|
+
# - both stores sync'd with off-machine Git (S3, ...) mirrors
|
109
|
+
# - deletion of the (encrypted) envelope {Open}ed and stuffed with {Put}
|
110
|
+
# - deletion of {Open}ed session data to locate and decrypt envelope
|
111
|
+
def execute
|
112
|
+
|
113
|
+
main_store = ColdStore.new @c[:global][:store_mainpath]
|
114
|
+
keys_store = ColdStore.new @c[:global][:store_keyspath]
|
115
|
+
|
116
|
+
keys_crypt = keys_store.read @outer_path
|
117
|
+
main_crypt = main_store.read @outer_path
|
118
|
+
|
119
|
+
unless @master_p4ss
|
120
|
+
@master_p4ss = Collect.secret_text(
|
121
|
+
@c[:global][:min_passwd_len],
|
122
|
+
false,
|
123
|
+
"Enter Master Password "
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
workstation_rawkey = OpenSession::Attributes.instance.get_value @c[:global][:name], @c[:global][:name], @c[:global][:machine_key_x]
|
128
|
+
original_timestamp = OpenSession::Attributes.instance.get_value @c[:global][:name], @c[:global][:name], @c[:global][:stamp_key]
|
129
|
+
workstation_cipher = Base64.urlsafe_decode64( workstation_rawkey )
|
130
|
+
crypt_key_segments = [ @master_p4ss, @c[:global][:separator_a], @email_addr, @c[:global][:separator_a], original_timestamp ]
|
131
|
+
workstation_lock_x = crypt_key_segments.alphanumeric_union.concat_length
|
132
|
+
|
133
|
+
workstation_string = Blowfish.new.decryptor workstation_cipher, workstation_lock_x
|
134
|
+
private_key_lock_x = Amalgam.passwords @master_p4ss, workstation_string, @c[:global][:ratio]
|
135
|
+
private_key_cipher = File.read @c[:global][:master_prv_key]
|
136
|
+
private_key_object = unlock_private_key( private_key_cipher, private_key_lock_x )
|
137
|
+
|
138
|
+
string_private_key = Cipher.decrypt_it( private_key_object, keys_crypt )
|
139
|
+
plain_message_text = Cipher.decrypt_it( to_private_key(string_private_key), main_crypt )
|
140
|
+
|
141
|
+
puts ""
|
142
|
+
puts "==== ======================================= ===="
|
143
|
+
puts "==== The Exported Plain Text Structured Data ===="
|
144
|
+
puts "==== ======================================= ===="
|
145
|
+
puts ""
|
146
|
+
puts plain_message_text
|
147
|
+
puts ""
|
148
|
+
puts ""
|
149
|
+
puts JSON.pretty_generate( JSON.parse( plain_message_text ) )
|
150
|
+
puts ""
|
151
|
+
|
152
|
+
exit
|
153
|
+
|
154
|
+
#############################################################################################
|
155
|
+
#############################################################################################
|
156
|
+
|
157
|
+
rel_filepath = OpenSession::Attributes.instance.get_value @@context_name, @c[:open][:open_name], @c[:open][:open_pathname]
|
158
|
+
master_public_key = Base64.urlsafe_decode64( OpenSession::Attributes.instance.get_value @c[:global][:name], @c[:global][:name], "public.key" )
|
159
|
+
|
160
|
+
|
161
|
+
#############################################################################################
|
162
|
+
#############################################################################################
|
163
|
+
|
164
|
+
=begin
|
165
|
+
Crypto.print_secret_env_var @p[:env_var_name], machine_key
|
166
|
+
GitFlow.do_clone_repo @p[:public_gitrepo], @p[:local_gitrepo]
|
167
|
+
FileUtils.mkdir_p @p[:public_keydir]
|
168
|
+
File.write @p[:public_keypath], public_key_text
|
169
|
+
GitFlow.push @p[:local_gitrepo], @p[:public_keyname], @c[:time][:stamp]
|
170
|
+
=end
|
171
|
+
|
172
|
+
|
173
|
+
# key4_pem = File.read 'private.secure.pem'
|
174
|
+
# pass_phrase = 'superduperpasswordistoBeENTEREDRIGHT1234HereandRightNOW'
|
175
|
+
# key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase
|
176
|
+
# decrypted_text = key4.private_decrypt(Base64.urlsafe_decode64(encrypted_string))
|
177
|
+
|
178
|
+
# print "\nHey we have done the decryption.\n", "\n"
|
179
|
+
# print decrypted_text, "\n"
|
180
|
+
|
181
|
+
#############################################################################################
|
182
|
+
#############################################################################################
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Perform pre-conditional validations in preparation to executing the main flow
|
188
|
+
# of events for this use case. This method may throw the below exceptions.
|
189
|
+
#
|
190
|
+
# @raise [SafeDirNotConfigured] if the safe's url has not been configured
|
191
|
+
# @raise [EmailAddrNotConfigured] if the email address has not been configured
|
192
|
+
# @raise [StoreUrlNotConfigured] if the crypt store url is not configured
|
193
|
+
def pre_validation
|
194
|
+
|
195
|
+
@email_addr = OpenSession::Attributes.instance.get_value @@context_name, @@context_name, "email"
|
196
|
+
email_configured = !@email_addr.nil? && !@email_addr.empty? && @email_addr.length > 4
|
197
|
+
@err_msg = "viable [email address] not configured. Try =>] opensecret email joe@example.com"
|
198
|
+
raise EmailAddrNotConfigured.new @err_msg, @email_addr unless email_configured
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|