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