opensecret 0.0.988 → 0.0.9925
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 +5 -5
- data/README.md +56 -159
- data/bin/opensecret +2 -2
- data/bin/ops +17 -2
- data/lib/extension/string.rb +14 -16
- data/lib/{interpreter.rb → interprete.rb} +53 -29
- data/lib/keytools/binary.map.rb +49 -0
- data/lib/keytools/kdf.api.rb +249 -0
- data/lib/keytools/kdf.bcrypt.rb +64 -29
- data/lib/keytools/kdf.pbkdf2.rb +92 -83
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1281 -0
- data/lib/keytools/key.db.rb +265 -0
- data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +265 -0
- data/lib/keytools/key.mach.rb +248 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +428 -298
- data/lib/keytools/keydebug.txt +295 -0
- data/lib/logging/gem.logging.rb +3 -3
- data/lib/modules/cryptology/collect.rb +20 -0
- data/lib/session/require.gem.rb +1 -1
- data/lib/usecase/cmd.rb +417 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +174 -0
- data/lib/usecase/init.rb +78 -0
- data/lib/usecase/login.rb +70 -0
- data/lib/usecase/logout.rb +30 -0
- data/lib/usecase/open.rb +126 -0
- data/lib/{interprete → usecase}/put.rb +100 -47
- data/lib/usecase/read.rb +89 -0
- data/lib/{interprete → usecase}/safe.rb +0 -0
- data/lib/{interprete → usecase}/set.rb +0 -0
- data/lib/usecase/token.rb +111 -0
- data/lib/{interprete → usecase}/use.rb +0 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +4 -3
- metadata +39 -33
- data/lib/exception/cli.error.rb +0 -53
- data/lib/exception/errors/cli.errors.rb +0 -31
- data/lib/interprete/begin.rb +0 -232
- data/lib/interprete/cmd.rb +0 -621
- data/lib/interprete/export.rb +0 -163
- data/lib/interprete/init.rb +0 -205
- data/lib/interprete/key.rb +0 -119
- data/lib/interprete/open.rb +0 -148
- data/lib/interprete/seal.rb +0 -129
- data/lib/keytools/digester.rb +0 -245
- data/lib/keytools/key.data.rb +0 -227
- data/lib/keytools/key.derivation.rb +0 -341
- data/lib/modules/mappers/collateral.rb +0 -282
- data/lib/modules/mappers/envelope.rb +0 -127
- data/lib/modules/mappers/settings.rb +0 -170
- data/lib/notepad/scratch.pad.rb +0 -224
- data/lib/store-commands.txt +0 -180
data/lib/keytools/key.data.rb
DELETED
@@ -1,227 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
# coding: utf-8
|
3
|
-
|
4
|
-
module OpenKey
|
5
|
-
|
6
|
-
require 'inifile'
|
7
|
-
|
8
|
-
# This is a key-value store backed by unencrypted (plain-text) permanent
|
9
|
-
# file-system storage in INI format.
|
10
|
-
#
|
11
|
-
# == Key-Value Pair Groupings
|
12
|
-
#
|
13
|
-
# The key-value pairs can be collated into a
|
14
|
-
#
|
15
|
-
# - custom group with a name specified to methods {read} and {write}
|
16
|
-
# - default group that is accessible via the methods {get} and {put}
|
17
|
-
#
|
18
|
-
# The name given to the default group can be specified to the constructor.
|
19
|
-
# If none is provided the aptly named "default" is used.
|
20
|
-
|
21
|
-
|
22
|
-
# An OpenSession dictionary is a <b>2D (two dimensional) hash</b> data
|
23
|
-
# structure backed by an encrypted file.
|
24
|
-
#
|
25
|
-
# It supports operations to <b>read from</b> and <b>write to</b> a known
|
26
|
-
# filepath and given the correct symmetric encryption key it will
|
27
|
-
#
|
28
|
-
# - decrypt <b>after reading from</b> the file and
|
29
|
-
# - encrypt <b>before writing to</b> the file
|
30
|
-
#
|
31
|
-
# This dictionary extends {Hash} in order to deliver on its core key value
|
32
|
-
# storage and retrieve use cases. Extend this dictionary and provide
|
33
|
-
# context specific methods through constants to read and write context
|
34
|
-
# specific data.
|
35
|
-
#
|
36
|
-
# == The <em>Current</em> Dictionary Section
|
37
|
-
#
|
38
|
-
# This KeyData is <b>two-dimensional</b> so all key-value pairs are stored
|
39
|
-
# under the auspices of a section.
|
40
|
-
#
|
41
|
-
# The KeyData can track the <b>current section</b> for you and all data
|
42
|
-
# exchanges can occur in lieu of a single section if you so wish by using
|
43
|
-
# the provided {put} and {get} methods.
|
44
|
-
#
|
45
|
-
# To employ section management functionality you should pass in a current
|
46
|
-
# <b>section id</b> when creating the dictionary.
|
47
|
-
#
|
48
|
-
# @example
|
49
|
-
# To use the dictionary in the raw (unextended) format you create
|
50
|
-
# write and read it like this.
|
51
|
-
#
|
52
|
-
# ----------------------------------------------------------------------
|
53
|
-
#
|
54
|
-
# my_dictionary = KeyData.create( "/path/to/backing/file" )
|
55
|
-
#
|
56
|
-
# my_dictionary["user23"] = {}
|
57
|
-
# my_dictionary["user23"]["Name"] = "Joe Bloggs"
|
58
|
-
# my_dictionary["user23"]["Email"] = "joebloggs@example.com"
|
59
|
-
# my_dictionary["user23"]["Phone"] = "+44 07342 800080"
|
60
|
-
#
|
61
|
-
# my_dictionary.write( "crypt-key-1234-wxyz" )
|
62
|
-
#
|
63
|
-
# ----------------------------------------------------------------------
|
64
|
-
#
|
65
|
-
# my_dictionary = KeyData.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
|
66
|
-
# puts my_dictionary.has_key? "user23" # => true
|
67
|
-
# puts my_dictionary["user23"].length # => 3
|
68
|
-
# puts my_dictionary["user23"]["Email"] # => "joebloggs@example.com"
|
69
|
-
#
|
70
|
-
# ----------------------------------------------------------------------
|
71
|
-
class KeyData
|
72
|
-
|
73
|
-
|
74
|
-
# Initialize the key value store and auto write a time stamp that
|
75
|
-
# has nano-second accuracy with a key whose name is gleened from
|
76
|
-
# the constant {KeyData::INIT_TIME_STAMP_NAME}.
|
77
|
-
#
|
78
|
-
# The path to the backing INI file is gleened from the first
|
79
|
-
# backing file path parameter.
|
80
|
-
#
|
81
|
-
# @param backing_file_path [String]
|
82
|
-
# the expected location of the file-backed key-value store.
|
83
|
-
# If the folder and/or file do not exist the folder is created
|
84
|
-
# and then the file is created along with the time stamps.
|
85
|
-
#
|
86
|
-
# @param the_default_group [String]
|
87
|
-
# the name of the default group. If none is presented this value
|
88
|
-
# will default to the aptly named "default".
|
89
|
-
def initialize backing_file_path, the_reference
|
90
|
-
|
91
|
-
@file_path = backing_file_path
|
92
|
-
@reference = the_reference
|
93
|
-
|
94
|
-
create_dir_if_necessary
|
95
|
-
put_stamps_if_necessary
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
# Stash the setting directive and its value into the configuration file
|
101
|
-
# using the default settings group.
|
102
|
-
#
|
103
|
-
# The default settings group is resolved via {Collateral::CONTEXT_NAME}
|
104
|
-
#
|
105
|
-
# @param key_name [String] the name of the key whose value is to be written
|
106
|
-
# @param key_value [String] the data item value of the key specified
|
107
|
-
def put key_name, key_value
|
108
|
-
write @reference, key_name, key_value
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
# Stash the setting directive and its value into the configuration file
|
113
|
-
# using the default settings group.
|
114
|
-
#
|
115
|
-
# The default settings group is resolved via {Collateral::CONTEXT_NAME}
|
116
|
-
#
|
117
|
-
# @param key_name [String] the name of the key whose value is to be written
|
118
|
-
# @return [String]
|
119
|
-
# return the value of the configuration directive in the default group
|
120
|
-
def get key_name
|
121
|
-
read @reference, key_name
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
# Write the key/value pair in the parameter into this key/value store's
|
126
|
-
# base file-system backing INI file.
|
127
|
-
#
|
128
|
-
# This method assumes the existence of the backing configuration file at
|
129
|
-
# the @file_path instance variable that was set during initialization.
|
130
|
-
#
|
131
|
-
# Observable value is the written key/value pair within the specified
|
132
|
-
# section. The alternate flows are
|
133
|
-
#
|
134
|
-
# - if the section does not exist it is created
|
135
|
-
# - if the section and key exist the value is inserted or overwritten
|
136
|
-
#
|
137
|
-
# @param section_name [String] name grouping the section of config values
|
138
|
-
# @param key [String] the key name of config directive to be written into the file
|
139
|
-
# @param value [String] value of the config directive to be written into the file
|
140
|
-
#
|
141
|
-
def write section_name, key, value
|
142
|
-
|
143
|
-
config_map = IniFile.new( :filename => @file_path, :encoding => 'UTF-8' )
|
144
|
-
config_map = IniFile.load( @file_path ) if File.file? @file_path
|
145
|
-
config_map[section_name][key] = value
|
146
|
-
config_map.write
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
|
151
|
-
# Given the configuration key name and the context name, get the
|
152
|
-
# corresponding key value from the configuration file whose path
|
153
|
-
# is acquired using the {self#get_filepath} method.
|
154
|
-
#
|
155
|
-
# @param key_name [String] the key whose value is to be retrieved
|
156
|
-
#
|
157
|
-
# @return [String] the value configured for the parameter key
|
158
|
-
#
|
159
|
-
# @raise ArgumentError for any one of a long list of reasons that
|
160
|
-
# cause the key value to not be retrieved. This can range from
|
161
|
-
# non-existent directories and files, non readable files, incorrect
|
162
|
-
# configurations right down to missing keys or even missing values.
|
163
|
-
def read section_name, key_name
|
164
|
-
|
165
|
-
raise ArgumentError.new "No configuration file found => [ #{@file_path} ]" unless File.exists? @file_path
|
166
|
-
|
167
|
-
the_text = File.read @file_path
|
168
|
-
raise ArgumentError.new "Configuration file is empty => [ #{@file_path} ]" if the_text.empty?
|
169
|
-
|
170
|
-
the_data = IniFile.load @file_path
|
171
|
-
key_exists = the_data[ section_name ].has_key?( key_name )
|
172
|
-
raise ArgumentError.new "Key [#{key_name}] not found in section [#{section_name}] => #{the_data.to_s}" unless key_exists
|
173
|
-
|
174
|
-
rawvalue = the_data[section_name][key_name]
|
175
|
-
raise ArgumentError.new "Empty value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if rawvalue.empty?
|
176
|
-
|
177
|
-
keyvalue = rawvalue.chomp.strip
|
178
|
-
raise ArgumentError.new "Whitespace value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if keyvalue.empty?
|
179
|
-
|
180
|
-
return keyvalue
|
181
|
-
|
182
|
-
end
|
183
|
-
|
184
|
-
|
185
|
-
# Get the time stamp that was written to the key-value store at
|
186
|
-
# the point it was first initialized and then subsequently written
|
187
|
-
# out (serialized) onto the file-system.
|
188
|
-
#
|
189
|
-
# The time stamp returned marks the first time this key-value store
|
190
|
-
# was conceived by a use case actor and subsequently serialized.
|
191
|
-
#
|
192
|
-
# @return [String]
|
193
|
-
# the string time stamp denoting the first time this key-value
|
194
|
-
# store was first initialized and then subsequently written out
|
195
|
-
# (serialized) onto the file-system.
|
196
|
-
def time_stamp
|
197
|
-
return get INIT_TIME_STAMP_NAME
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
private
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
def create_dir_if_necessary
|
207
|
-
|
208
|
-
config_directory = File.dirname @file_path
|
209
|
-
return if (File.exist? config_directory) && (File.directory? config_directory)
|
210
|
-
FileUtils.mkdir_p config_directory
|
211
|
-
|
212
|
-
end
|
213
|
-
|
214
|
-
|
215
|
-
def put_stamps_if_necessary
|
216
|
-
|
217
|
-
return if File.file? @file_path
|
218
|
-
|
219
|
-
put INIT_TIME_STAMP_NAME, OpenSession::Stamp.yyjjj_hhmm_ss_nanosec
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
end
|
@@ -1,341 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
# coding: utf-8
|
3
|
-
|
4
|
-
module OpenKey
|
5
|
-
|
6
|
-
|
7
|
-
# The OpenKey underlying security strategy is to lock a master index file
|
8
|
-
# with a <b>symmetric encryption key</b> that is based on two randomly generated
|
9
|
-
# and amalgamated <b>55 and 45 character keys</b> and then to lock that key
|
10
|
-
# <b>(and only that key)</b> with a 256 bit symmetric encryption key derived from
|
11
|
-
# a human password and generated by at least two cryptographic workhorses known
|
12
|
-
# as <b>key derivation functions</b>.
|
13
|
-
#
|
14
|
-
# Random powerful keys are derived are seeded with 55 random bytes and
|
15
|
-
# then fed through the master key generator and its two key derivation
|
16
|
-
# functions (BCrypt and PBKDF2).
|
17
|
-
#
|
18
|
-
# == What Does the Master Encryption Key Generator Do?
|
19
|
-
#
|
20
|
-
# This class sits at the core of implementing that strategy and works to produce
|
21
|
-
# 256 bit encryption key derived from a human password which is then minced by
|
22
|
-
# two best of breed key derivation functions (BCrypt and PBKDF2).
|
23
|
-
#
|
24
|
-
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
25
|
-
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
26
|
-
# into a high entropy key that is computationally infeasible to acquire via brute
|
27
|
-
# force.
|
28
|
-
#
|
29
|
-
# == How to Create the Encryption Key
|
30
|
-
#
|
31
|
-
# To create a high entropy encryption key this method takes the first
|
32
|
-
# 168 bits from the 186 bit BCrypt key produced by {BCryptKeyGen} and
|
33
|
-
# the first 96 bits from the 132 bit PBKDF2 key produced inside the
|
34
|
-
# {Pbkdf2KeyGen} class and amalgamates them to produce a 264 bit key.
|
35
|
-
#
|
36
|
-
# The 264 bit key is then digested to produce a 256bit encryption key.
|
37
|
-
class KeyDerivation
|
38
|
-
|
39
|
-
|
40
|
-
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
41
|
-
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
42
|
-
# into a high entropy key that is computationally infeasible to acquire via brute
|
43
|
-
# force.
|
44
|
-
BCRYPT_SALT_KEY_NAME = "bcrypt.salt"
|
45
|
-
|
46
|
-
|
47
|
-
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
48
|
-
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
49
|
-
# into a high entropy key that is computationally infeasible to acquire via brute
|
50
|
-
# force.
|
51
|
-
PBKDF2_SALT_KEY_NAME = "pbkdf2.salt"
|
52
|
-
|
53
|
-
|
54
|
-
# To create a high entropy encryption key the first 168 bits from the
|
55
|
-
# 186 bit BCrypt key produced by {BCryptKeyGen} is sliced off and used
|
56
|
-
# as the lead part of the generated key.
|
57
|
-
BCRYPT_KEY_CONTRIBUTION_SIZE = 168
|
58
|
-
|
59
|
-
|
60
|
-
# The first 96 bits from the 132 bit PBKDF2 key produced inside the
|
61
|
-
# {Pbkdf2KeyGen} class is amalgamated to the BCrypt 168 bit key to produce
|
62
|
-
# a 264 bit key.
|
63
|
-
PBKDF2_KEY_CONTRIBUTION_SIZE = 96
|
64
|
-
|
65
|
-
|
66
|
-
AMALGAM_KEY_RAW_BIT_SIZE = BCRYPT_KEY_CONTRIBUTION_SIZE + PBKDF2_KEY_CONTRIBUTION_SIZE
|
67
|
-
|
68
|
-
AMALGAM_KEY_SIX_BIT_COUNT = AMALGAM_KEY_RAW_BIT_SIZE / 6
|
69
|
-
|
70
|
-
AMALGAM_KEY_EIGHT_BYTE_COUNT = AMALGAM_KEY_RAW_BIT_SIZE / 8
|
71
|
-
|
72
|
-
|
73
|
-
# To acquire a <b>machine generated symmetric encryption key</b> pass
|
74
|
-
# in a {Key} initialized with {Key.from_random_bytes} and this method
|
75
|
-
# will digest it for extra security and produce a gold standard 256 bit
|
76
|
-
# encryption key ready to use with the AES256 algorithm.
|
77
|
-
#
|
78
|
-
# Do not use the {Key.from_random_bytes} as an encryption key, instead
|
79
|
-
# <b>encrypt and then persist</b> the key if you will need to decrypt the
|
80
|
-
# cipher text at some future date.
|
81
|
-
#
|
82
|
-
# <b>The 48 Bytes map to 64 Base64 Characters</b>
|
83
|
-
#
|
84
|
-
# To re-acquire the key for decryption, <b>read and unencrypt</b> the
|
85
|
-
# <b>64 base64 characters</b> with <b>Key.from_base64</b> and then pass
|
86
|
-
# it again to this method to <b>re-acquire</b> the original symmetric
|
87
|
-
# encryption/decryption key.
|
88
|
-
#
|
89
|
-
# | -------- | ------------ | -------------------------------- |
|
90
|
-
# | Bits | Bytes | Base64 |
|
91
|
-
# | -------- | ------------ | -------------------------------- |
|
92
|
-
# | 384 Bits | is 48 bytes | and 64 characters |
|
93
|
-
# | 256 Bits | 32 precisely | 43 Chars (42 + 4 remainder bits) |
|
94
|
-
# | -------- | ------------ | -------------------------------- |
|
95
|
-
#
|
96
|
-
# For <b>simplicity's sake</b>, try to employ a <b>bit length</b> with
|
97
|
-
# a bit count that is a <b>multiple of both 6 and 8</b>. This method mashes
|
98
|
-
# up the raw key and provides you with a powerful 256 bit key.
|
99
|
-
#
|
100
|
-
# @param the_key [OpenKey::Key]
|
101
|
-
# use <b>Key.from_random_bytes</b> to create a seed whose bit length
|
102
|
-
# is a <b>multiple of <em>both 6 and 8</em></b>. This method will mash
|
103
|
-
# up the raw key, thus provisioning a powerful 256 bit encryption key.
|
104
|
-
#
|
105
|
-
# @return [OpenKey::Key]
|
106
|
-
# the raw key will be mashed up and this method will faithfully return
|
107
|
-
# a powerful 256 bit encryption key.
|
108
|
-
def self.from_key the_key
|
109
|
-
return Digest::SHA256.digest( Digest::SHA384.digest( the_key.to_binary_bytes ) )
|
110
|
-
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# This method generates a 256 bit symmetric encryption key derived from
|
115
|
-
# a human password and passed through two cryptographic workhorses
|
116
|
-
# (BCrypt and PBKDF2), the best of breed <b>key derivation functions</b>.
|
117
|
-
#
|
118
|
-
# == BCrypt and the PBKDF2 Cryptographic Algorithms
|
119
|
-
#
|
120
|
-
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
121
|
-
# that exists to convert <b>low entropy</b> human generated passwords into a high
|
122
|
-
# entropy key that is computationally infeasible to acquire through brute force.
|
123
|
-
#
|
124
|
-
# == Creating a High Entropy Encryption Key
|
125
|
-
#
|
126
|
-
# To create a high entropy encryption key this method takes the first
|
127
|
-
# 168 bits from the 186 bit BCrypt key produced by {BCryptKeyGen} and
|
128
|
-
# the first 96 bits from the 132 bit PBKDF2 key produced inside the
|
129
|
-
# {Pbkdf2KeyGen} class and amalgamates them to produce a 264 bit key.
|
130
|
-
#
|
131
|
-
# Note that all four of the above numbers are divisable by six (6), for
|
132
|
-
# representation with a 64 character set, and eight (8), for transport
|
133
|
-
# via the byte (8 bit) protocols.
|
134
|
-
#
|
135
|
-
# <b>Size of BCrypt and PBKDF2 Derived Keys</b>
|
136
|
-
#
|
137
|
-
# ----------- | --------- | ----------------- | ----------- |
|
138
|
-
# ----------- | --------- | ----------------- | ----------- |
|
139
|
-
# | Algorithm | Bit Count | Base64 Chars | 8 Bit Bytes |
|
140
|
-
# ----------- | --------- | ----------------- | ----------- |
|
141
|
-
# | BCrypt | 168 Bits | 28 characters | 21 bytes |
|
142
|
-
# | Pbkdf2 | 96 Bits | 16 characters | 12 bytes |
|
143
|
-
# ----------- | --------- | ----------------- | ----------- |
|
144
|
-
# | Total | 264 Bits | 44 characters | 33 bytes |
|
145
|
-
# ----------- | --------- | ----------------- | ----------- |
|
146
|
-
#
|
147
|
-
# <b>256 Bit Encryption Key | Remove 8 Bits</b>
|
148
|
-
#
|
149
|
-
# The manufactured encryption key, an amalgam of the above now has
|
150
|
-
# 264 bits carried by 44 Base64 characters.
|
151
|
-
#
|
152
|
-
# Just before it is used to encrypt vital keys, eight (8) bits are
|
153
|
-
# removed from the end of the key. The key is then converted into a
|
154
|
-
# powerful 32 byte (256 bit) encryption agent and is hashed by the
|
155
|
-
# SHA256 digest and delivered.
|
156
|
-
#
|
157
|
-
# @param human_secret [String]
|
158
|
-
# a robust human generated password with as much entropy as can
|
159
|
-
# be mustered. Remember that 40 characters spread randomly over
|
160
|
-
# the key space of about 90 characters and not relating to any
|
161
|
-
# dictionary word or name is the way to generate a powerful key
|
162
|
-
# that has embedded a near 100% entropy rating.
|
163
|
-
#
|
164
|
-
# @param dictionary [Hash]
|
165
|
-
# an instantiated hash object in which we will write the salts to
|
166
|
-
# be persisted and regurgitated during the regenerate process.
|
167
|
-
#
|
168
|
-
# @return [Key]
|
169
|
-
# the 256 bit symmetric encryption key derived from a human password
|
170
|
-
# and passed through two cryptographic workhorses.
|
171
|
-
def self.from_password human_secret, dictionary
|
172
|
-
|
173
|
-
bcrypt_salt = BCryptKeyGen.generate_salt
|
174
|
-
pbkdf2_salt = Pbkdf2KeyGen.generate_salt
|
175
|
-
|
176
|
-
dictionary.put( BCRYPT_SALT_KEY_NAME, bcrypt_salt )
|
177
|
-
dictionary.put( PBKDF2_SALT_KEY_NAME, pbkdf2_salt )
|
178
|
-
|
179
|
-
return generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
|
184
|
-
# Regenerate the viciously unretrievable nor reversable key that was
|
185
|
-
# generated in the past and with the same salts that were used during
|
186
|
-
# the original key derivation process.
|
187
|
-
#
|
188
|
-
# @param dictionary [Hash]
|
189
|
-
# an instantiated and populated hash object containing the salts
|
190
|
-
# which were created in the past during the generation. These are
|
191
|
-
# now vital for a successful regeneration.
|
192
|
-
#
|
193
|
-
# @return [Key]
|
194
|
-
# the 256 bit symmetric encryption key that was previously generated
|
195
|
-
# from the secret and the cryptographic salts within the dictionary.
|
196
|
-
def self.regenerate human_secret, dictionary
|
197
|
-
|
198
|
-
bcrypt_salt = dictionary.get( BCRYPT_SALT_KEY_NAME )
|
199
|
-
pbkdf2_salt = dictionary.get( PBKDF2_SALT_KEY_NAME )
|
200
|
-
return generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
|
201
|
-
|
202
|
-
end
|
203
|
-
|
204
|
-
|
205
|
-
# Derive a <b>short term (session scoped) encryption key</b> from the
|
206
|
-
# surrounding shell execution environment whilst giving two (2) important
|
207
|
-
# guarantees.
|
208
|
-
#
|
209
|
-
# The two guarantees governing the returned key are that it is
|
210
|
-
#
|
211
|
-
# - <b>the same</b> whenever called within this executing shell
|
212
|
-
# - <b>different</b> different when another shell is employed
|
213
|
-
#
|
214
|
-
# The much higher collision rate is tolerable because the key's lifetime
|
215
|
-
# is only <b>as long as commands are being typed into a given shell</b>
|
216
|
-
# or command prompt in the case of Windows.
|
217
|
-
#
|
218
|
-
# This method uses a one-way function to return a combinatorial digested
|
219
|
-
# session identification string using a number of distinct parameters that
|
220
|
-
# deliver the important behaviours of changing in certain circumstances
|
221
|
-
# and remaining unchanged in others.
|
222
|
-
#
|
223
|
-
# <b>Change | When Should the key Change?</b>
|
224
|
-
#
|
225
|
-
# What is really important is that the <b>key changes when</b>
|
226
|
-
#
|
227
|
-
# - the <b>command shell</b> changes
|
228
|
-
# - the workstation <b>shell user is switched</b>
|
229
|
-
# - the host machine <b>workstation</b> is changed
|
230
|
-
# - the user <b>SSH's</b> into another shell
|
231
|
-
#
|
232
|
-
# A distinct workstation is identified by the first MAC address and the
|
233
|
-
# hostname of the machine.
|
234
|
-
#
|
235
|
-
# <b>Unchanged | When Should the Key Remain Unchanged?</b>
|
236
|
-
#
|
237
|
-
# Remaining <b>unchanged</b> in certain scenarios is a feature that is
|
238
|
-
# just as important as changing in others. The key must remain
|
239
|
-
# <b>unchanged</b> when
|
240
|
-
#
|
241
|
-
# - the <b>user returns to a command shell</b>
|
242
|
-
# - the user exits their <b>remote SSH session</b>
|
243
|
-
# - <b>sudo is used</b> to execute the commands
|
244
|
-
# - the user comes back to their <b>workstation</b>
|
245
|
-
# - the clock ticks into another day, month, year ...
|
246
|
-
#
|
247
|
-
# @return [OpenKey::Key]
|
248
|
-
# a digested key suitable for short term (session scoped) use with the
|
249
|
-
# guarantee that the same key will be returned whenever called from within
|
250
|
-
# the same executing shell environment and a different key when not.
|
251
|
-
def self.from_session
|
252
|
-
|
253
|
-
require 'macaddr'
|
254
|
-
|
255
|
-
# Do not change the order of this data because it is reversed and
|
256
|
-
# the parent's shell ID hotly followed by the MAC address are the
|
257
|
-
# most significant data points.
|
258
|
-
|
259
|
-
raw_data_string = [
|
260
|
-
Socket.gethostname,
|
261
|
-
OpenSession::Home.instance.username,
|
262
|
-
Mac.addr.to_alphanumeric,
|
263
|
-
Process.ppid.to_s
|
264
|
-
].join.reverse
|
265
|
-
|
266
|
-
return Digest::SHA256.digest( Digest::SHA512.digest( raw_data_string ) )
|
267
|
-
|
268
|
-
end
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
private
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
def self.generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
|
277
|
-
bcrypt_key = OpenKey::BCryptKeyGen.generate_key( human_secret, bcrypt_salt )
|
278
|
-
pbkdf2_key = OpenKey::Pbkdf2KeyGen.generate_key( human_secret.reverse, pbkdf2_salt )
|
279
|
-
return merge_then_digest( bcrypt_key, pbkdf2_key )
|
280
|
-
|
281
|
-
end
|
282
|
-
|
283
|
-
|
284
|
-
def self.merge_then_digest bcrypt_key, pbkdf2_key
|
285
|
-
|
286
|
-
assert_bcrypt_key_bit_length bcrypt_key
|
287
|
-
assert_pbkdf2_key_bit_length pbkdf2_key
|
288
|
-
|
289
|
-
raw_key = bcrypt_key.to_s[ 0 .. (BCRYPT_KEY_CONTRIBUTION_SIZE-1) ] + pbkdf2_key.to_s[ 0 .. (PBKDF2_KEY_CONTRIBUTION_SIZE-1) ]
|
290
|
-
|
291
|
-
assert_amalgam_key_bit_length raw_key
|
292
|
-
assert_amalgam_key_six_bit_count raw_key
|
293
|
-
assert_amalgam_key_eight_bit_count raw_key
|
294
|
-
|
295
|
-
rawbytes_key = [ raw_key.to_s ].pack("B*")
|
296
|
-
digested_key = OpenSSL::Digest::SHA256.new.digest( rawbytes_key )
|
297
|
-
return Key.new ( Base64.urlsafe_encode64( digested_key ) )
|
298
|
-
|
299
|
-
end
|
300
|
-
|
301
|
-
|
302
|
-
def self.assert_bcrypt_key_bit_length bcrypt_key
|
303
|
-
bcrypt_key_bit_length = bcrypt_key.to_s.bytesize
|
304
|
-
bcrypt_keysize_msg = "Expecting #{BCryptKeyGen::BCRYPT_KEY_TRANSPORT_LENGTH} not #{bcrypt_key_bit_length} bits in bcrypt key."
|
305
|
-
raise RuntimeError, bcrypt_keysize_msg unless bcrypt_key_bit_length == BCryptKeyGen::BCRYPT_KEY_TRANSPORT_LENGTH
|
306
|
-
end
|
307
|
-
|
308
|
-
|
309
|
-
def self.assert_pbkdf2_key_bit_length pbkdf2_key
|
310
|
-
pbkdf2_key_bit_length = pbkdf2_key.to_s.bytesize
|
311
|
-
pbkdf2_keysize_msg = "Expecting #{Pbkdf2KeyGen::PBKDF2_KEY_TRANSPORT_LENGTH} not #{pbkdf2_key_bit_length} bits in pbkdf2 key."
|
312
|
-
raise RuntimeError, pbkdf2_keysize_msg unless pbkdf2_key_bit_length == Pbkdf2KeyGen::PBKDF2_KEY_TRANSPORT_LENGTH
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
|
-
def self.assert_amalgam_key_bit_length amalgam_key
|
317
|
-
|
318
|
-
amalgam_key_bit_length = amalgam_key.to_s.bytesize
|
319
|
-
amalgam_keysize_msg = "Expecting #{AMALGAM_KEY_RAW_BIT_SIZE} not #{amalgam_key_bit_length} bits in amalgam key."
|
320
|
-
raise RuntimeError, amalgam_keysize_msg unless amalgam_key_bit_length == AMALGAM_KEY_RAW_BIT_SIZE
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
|
-
def self.assert_amalgam_key_six_bit_count amalgam_key
|
325
|
-
amalgam_key_six_bit_length = amalgam_key.to_s.bytesize / 6
|
326
|
-
amalgam_key_six_bit_msg = "Expecting #{AMALGAM_KEY_SIX_BIT_COUNT} six bit blocks not #{amalgam_key_six_bit_length}."
|
327
|
-
raise RuntimeError, amalgam_key_six_bit_msg unless amalgam_key_six_bit_length == AMALGAM_KEY_SIX_BIT_COUNT
|
328
|
-
end
|
329
|
-
|
330
|
-
|
331
|
-
def self.assert_amalgam_key_eight_bit_count amalgam_key
|
332
|
-
amalgam_key_eight_bit_length = amalgam_key.to_s.bytesize / 8
|
333
|
-
amalgam_key_eight_bit_msg = "Expecting #{AMALGAM_KEY_EIGHT_BYTE_COUNT} eight bit blocks not #{amalgam_key_eight_bit_length}."
|
334
|
-
raise RuntimeError, amalgam_key_eight_bit_msg unless amalgam_key_eight_bit_length == AMALGAM_KEY_EIGHT_BYTE_COUNT
|
335
|
-
end
|
336
|
-
|
337
|
-
|
338
|
-
end
|
339
|
-
|
340
|
-
|
341
|
-
end
|