opensecret 0.0.9925 → 0.0.9949

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +656 -40
  3. data/lib/configs/README.md +58 -0
  4. data/lib/extension/file.rb +67 -0
  5. data/lib/extension/string.rb +10 -0
  6. data/lib/factbase/facts.opensecret.io.ini +1 -0
  7. data/lib/interprete.rb +334 -61
  8. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  9. data/lib/keytools/kdf.api.rb +9 -15
  10. data/lib/keytools/kdf.bcrypt.rb +69 -19
  11. data/lib/keytools/kdf.pbkdf2.rb +112 -23
  12. data/lib/keytools/key.api.rb +146 -36
  13. data/lib/keytools/key.db.rb +94 -29
  14. data/lib/keytools/key.id.rb +1 -1
  15. data/lib/keytools/key.ident.rb +243 -0
  16. data/lib/keytools/key.local.rb +62 -68
  17. data/lib/keytools/key.pass.rb +2 -2
  18. data/lib/keytools/key.rb +2 -28
  19. data/lib/modules/{cryptology.md → README.md} +0 -0
  20. data/lib/session/fact.finder.rb +65 -428
  21. data/lib/session/time.stamp.rb +1 -28
  22. data/lib/usecase/cmd.rb +127 -54
  23. data/lib/usecase/config/README.md +57 -0
  24. data/lib/usecase/docker/README.md +146 -0
  25. data/lib/usecase/docker/docker.rb +49 -0
  26. data/lib/usecase/edit/README.md +43 -0
  27. data/lib/usecase/edit/delete.rb +46 -0
  28. data/lib/usecase/export.rb +40 -0
  29. data/lib/usecase/files/README.md +37 -0
  30. data/lib/usecase/files/eject.rb +56 -0
  31. data/lib/usecase/files/file_me.rb +78 -0
  32. data/lib/usecase/files/read.rb +169 -0
  33. data/lib/usecase/files/write.rb +89 -0
  34. data/lib/usecase/goto.rb +57 -0
  35. data/lib/usecase/id.rb +1 -1
  36. data/lib/usecase/import.rb +13 -30
  37. data/lib/usecase/init.rb +2 -17
  38. data/lib/usecase/jenkins/README.md +146 -0
  39. data/lib/usecase/jenkins/crazy_ruby_post_attempt.OLD +234 -0
  40. data/lib/usecase/jenkins/jenkins.rb +208 -0
  41. data/lib/usecase/login.rb +6 -5
  42. data/lib/usecase/logout.rb +1 -3
  43. data/lib/usecase/open.rb +11 -66
  44. data/lib/usecase/print.rb +40 -0
  45. data/lib/usecase/put.rb +34 -156
  46. data/lib/usecase/set.rb +2 -4
  47. data/lib/usecase/show.rb +138 -0
  48. data/lib/usecase/terraform/README.md +91 -0
  49. data/lib/usecase/terraform/terraform.rb +121 -0
  50. data/lib/usecase/token.rb +4 -80
  51. data/lib/usecase/update/README.md +55 -0
  52. data/lib/usecase/update/rename.rb +180 -0
  53. data/lib/usecase/use.rb +1 -3
  54. data/lib/usecase/verse.rb +20 -0
  55. data/lib/usecase/view.rb +71 -0
  56. data/lib/usecase/vpn/README.md +150 -0
  57. data/lib/usecase/vpn/vpn.ini +31 -0
  58. data/lib/usecase/vpn/vpn.rb +54 -0
  59. data/lib/version.rb +1 -1
  60. data/opensecret.gemspec +3 -4
  61. metadata +34 -35
  62. data/.travis.yml +0 -5
  63. data/CODE_OF_CONDUCT.md +0 -74
  64. data/LICENSE.txt +0 -21
  65. data/bin/ops +0 -20
  66. data/lib/keytools/binary.map.rb +0 -294
  67. data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +0 -179
  68. data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +0 -190
  69. data/lib/keytools/doc.star.schema.strategy.txt +0 -77
  70. data/lib/keytools/doc.using.pbkdf2.kdf.ruby +0 -95
  71. data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +0 -266
  72. data/lib/keytools/key.mach.rb +0 -248
  73. data/lib/keytools/keydebug.txt +0 -295
  74. data/lib/modules/cryptology/open.bcrypt.rb +0 -170
  75. data/lib/usecase/read.rb +0 -89
  76. data/lib/usecase/safe.rb +0 -92
@@ -5,7 +5,7 @@ module OpenKey
5
5
 
6
6
  require 'json'
7
7
 
8
- # An envelope knows how to manipulate a JSON backed data structure
8
+ # A Key/Value database knows how to manipulate a JSON backed data structure
9
9
  # (put, add etc) <b>after reading and then decrypting it</b> from a
10
10
  # file and <b>before encrypting and then writing it</b> to a file.
11
11
  #
@@ -17,12 +17,12 @@ module OpenKey
17
17
  #
18
18
  # == JSON is Not Exposed in the Interface
19
19
  #
20
- # An envelope doesn't expose the data format used in the implementation
20
+ # A key/value database doesn't expose the data format used in the implementation
21
21
  # allowing this to be changed seamlessly to YAMl or other formats.
22
22
  #
23
23
  # == Symmetric Encryption and Decryption
24
24
  #
25
- # An envelope supports operations to <b>read from</b> and <b>write to</b>
25
+ # A key/value database supports operations to <b>read from</b> and <b>write to</b>
26
26
  # a known filepath and with a symmetric key it can
27
27
  #
28
28
  # - decrypt <b>after reading from</b> a file and
@@ -30,7 +30,7 @@ module OpenKey
30
30
  #
31
31
  # == Hashes as the Primary Data Structure
32
32
  #
33
- # Envelope extends {Hash} as the core data structure for holding
33
+ # The key/value database openly extends {Hash} as the data structure for holding
34
34
  #
35
35
  # - strings
36
36
  # - arrays
@@ -93,38 +93,57 @@ module OpenKey
93
93
  end
94
94
 
95
95
 
96
- # Fast forward tries to walk down this database tree as far as it can
97
- # led by the <b>forward-slash separated</b> tree_path parameter.
96
+ # Create a new secondary tier map key value entry inside a primary tier
97
+ # map at the map_key_name location.
98
98
  #
99
- # It stops the first time it encounters a key within the tree_path that
100
- # is not next in line from the currently walked position and returns
101
- # the sub tree at its point.
99
+ # A failure will occur if either the outer or inner keys already exist
100
+ # without their values being map objects.
102
101
  #
103
- # @param tree_path [String]
102
+ # If this method is called against a new empty map, the resulting map
103
+ # structure will look like the below.
104
104
  #
105
- # this is a forward slash separated path that is expected to align
106
- # perfectly with a path down this tree database.
105
+ # { outer_keyname ~> { inner_keyname ~> { entry_keyname, entry_value } } }
107
106
  #
108
- # The child tree is returned as soon as a dead end is reached where
109
- # the next branch in the tree_path does not correspond to a key at
110
- # the walked position.
107
+ # @param outer_keyname [String]
111
108
  #
112
- # The remaining subtree is returned if the tree_path end is reached.
113
- def fast_forward( tree_path )
114
-
115
- KeyError.not_new( tree_path, self )
116
-
117
- ## @todo put a loop here
118
- ## @todo put a loop here
119
- ## @todo put a loop here
120
- ## @todo put a loop here
121
- ## @todo put a loop here
122
- sub_tree = self[ tree_path ] if self.has_key?( tree_path )
109
+ # if a dictionary with this name exists at the root of the
110
+ # database add the parameter key value pair into it.
111
+ #
112
+ # if no dictionary exists then create one first before adding
113
+ # the key value pair as the first entry into it.
114
+ #
115
+ # @param inner_keyname [String]
116
+ #
117
+ # if a map exists at this key name then an entry comprising of
118
+ # a map_entry_key and a entry_value may either be added
119
+ # (if the map_entry_key does not already exist), or updated if
120
+ # it does.
121
+ #
122
+ # if the map does not exist it will be created and its first and
123
+ # only entry will be a key with inner_keyname along with a new
124
+ # single entry map consisting of the entry_keyname and the
125
+ # entry_value.
126
+ #
127
+ # @param entry_keyname [String]
128
+ #
129
+ # this key will exist in the second tier map after this operation.
130
+ #
131
+ # @param entry_value [String]
132
+ #
133
+ # this value will exist in the second tier map after this operation
134
+ # and if the entry_keyname already existed its value is overwritten
135
+ # with this one.
136
+ #
137
+ def create_map_entry( outer_keyname, inner_keyname, entry_keyname, entry_value )
123
138
 
124
- sub_database = KeyDb.new()
125
- sub_database.merge!( sub_tree )
139
+ KeyError.not_new( outer_keyname, self )
140
+ KeyError.not_new( inner_keyname, self )
141
+ KeyError.not_new( entry_keyname, self )
142
+ KeyError.not_new( entry_value, self )
126
143
 
127
- return sub_database
144
+ self[ outer_keyname ] = {} unless self.has_key?( outer_keyname )
145
+ self[ outer_keyname ][ inner_keyname ] = {} unless self[ outer_keyname ].has_key?( inner_keyname )
146
+ self[ outer_keyname ][ inner_keyname ][ entry_keyname ] = entry_value
128
147
 
129
148
  end
130
149
 
@@ -179,6 +198,31 @@ module OpenKey
179
198
  end
180
199
 
181
200
 
201
+ # Delete an existing key value entry inside the dictionary with the specified
202
+ # name at the root of this database. Successful completion means the
203
+ # named dictionary will contain one less entry if that key existed.
204
+ #
205
+ # @param dictionary_name [String]
206
+ #
207
+ # if a dictionary with this name exists at the root of the
208
+ # database add the parameter key value pair into it.
209
+ #
210
+ # if no dictionary exists throw an error
211
+ #
212
+ # @param key_name [String]
213
+ #
214
+ # the key part of the key value pair that will be deleted in the
215
+ # dictionary whose name was provided in the first parameter.
216
+ def delete_entry( dictionary_name, key_name )
217
+
218
+ KeyError.not_new( dictionary_name, self )
219
+ KeyError.not_new( key_name, self )
220
+
221
+ self[ dictionary_name ].delete( key_name )
222
+
223
+ end
224
+
225
+
182
226
  # Read and inject into this envelope, the data structure found in a
183
227
  # file at the path specified in the first parameter.
184
228
  #
@@ -204,6 +248,17 @@ module OpenKey
204
248
  # @raise [ArgumentError] if the decryption key is not robust enough.
205
249
  def read the_filepath, decryption_key = nil
206
250
 
251
+ # @todo -> this is confused - it uses INI but above methods use JSON
252
+ # @todo -> this is confused - it uses INI but above methods use JSON
253
+ # @todo -> this is confused - it uses INI but above methods use JSON
254
+ # @todo -> this is confused - it uses INI but above methods use JSON
255
+ # @todo -> this is confused - it uses INI but above methods use JSON
256
+ # @todo -> this is confused - it uses INI but above methods use JSON
257
+ # @todo -> this is confused - it uses INI but above methods use JSON
258
+ # @todo -> this is confused - it uses INI but above methods use JSON
259
+ # @todo -> this is confused - it uses INI but above methods use JSON
260
+ # @todo -> this is confused - it uses INI but above methods use JSON
261
+
207
262
  raise RuntimeError, "This KeyDb.read() software is never called so how can I be here?"
208
263
 
209
264
  @filepath = the_filepath
@@ -236,6 +291,16 @@ module OpenKey
236
291
  # key does not linger meaning it isn't cached in an instance variable.
237
292
  def write encryption_key
238
293
 
294
+ # @todo -> this is confused - it uses INI but above methods use JSON
295
+ # @todo -> this is confused - it uses INI but above methods use JSON
296
+ # @todo -> this is confused - it uses INI but above methods use JSON
297
+ # @todo -> this is confused - it uses INI but above methods use JSON
298
+ # @todo -> this is confused - it uses INI but above methods use JSON
299
+ # @todo -> this is confused - it uses INI but above methods use JSON
300
+ # @todo -> this is confused - it uses INI but above methods use JSON
301
+ # @todo -> this is confused - it uses INI but above methods use JSON
302
+ # @todo -> this is confused - it uses INI but above methods use JSON
303
+
239
304
  raise RuntimeError, "This KeyDb.write( key ) software is never called so how can I be here?"
240
305
 
241
306
  FileUtils.mkdir_p(File.dirname(@filepath))
@@ -110,7 +110,7 @@ module OpenKey
110
110
  # It will also be different on this workstation if the application
111
111
  # instance identifier provided is different.
112
112
  def self.derive_app_instance_machine_id( app_ref )
113
- return derive_identifier( app_ref + KeyMach.derive_machine_identity_string() )
113
+ return derive_identifier( app_ref + KeyIdent.derive_machine_identifier() )
114
114
  end
115
115
 
116
116
 
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module OpenKey
5
+
6
+ # This class knows how to derive information from the machine environment to aide
7
+ # in producing identifiers unique to the machine and/or workstation, with functionality
8
+ # similar to that required by licensing software.
9
+ #
10
+ # == Identity is Similar to Licensing Software | Except Deeper
11
+ #
12
+ # Deriving the identity string follows similar principles to licensing
13
+ # software that attempts to determine whether the operating environment
14
+ # is the same or different. But it goes deeper than licensing software
15
+ # as it is not only concerned about the <b>same workstation</b> - it is
16
+ # also concerned about <b>the same shell or command line interface</b>.
17
+ #
18
+ # == Known Issues
19
+ #
20
+ # The dependent macaddr gem is known to fail in scenarios where a
21
+ # VPN tunnel is active and a tell tale sign is the ifconfig command
22
+ # returning the tun0 interface rather than "eth0" or something that
23
+ # resembles "ensp21".
24
+ #
25
+ # This is one of the error messages resulting from such a case.
26
+ #
27
+ # macaddr.rb:86 from_getifaddrs undefined method pfamily (NoMethodError)
28
+ #
29
+ class KeyIdent
30
+
31
+ # This method returns a plaintext string hat is guaranteed to be the same
32
+ # whenever called within the same shell for the same user on the same
33
+ # workstation, virtual machine, container or SSH session and different whenever
34
+ # a new shell is acquired.
35
+ #
36
+ # What is really important is that the <b>shell identity string changes</b> when
37
+ #
38
+ # - the <b>command shell</b> changes
39
+ # - the user <b>switches to another workstation user</b>
40
+ # - the <b>workstation or machine host</b> is changed
41
+ # - the user <b>SSH's</b> into another shell
42
+ #
43
+ # <b>Unchanged | When Should it Remain Unchanged?</b>
44
+ #
45
+ # Remaining <b>unchanged</b> is a feature that is as important and this must
46
+ # be so when and/or after
47
+ #
48
+ # - the <b>user returns to a command shell</b>
49
+ # - the user <b>switches back to using a domain</b>
50
+ # - the user exits their <b>remote SSH session</b>
51
+ # - <b>sudo is used</b> to execute the commands
52
+ # - the user comes back to their <b>workstation</b>
53
+ # - the clock ticks into another day, month, year ...
54
+ #
55
+ # @param use_grandparent_pid [Boolean]
56
+ #
57
+ # Optional boolean parameter. If set to true the PID (process ID) used
58
+ # as part of an obfuscator key and normally acquired from the parent
59
+ # process should now be acquired from the grandparent's process.
60
+ #
61
+ # Set to true when accessing the safe's credentials from a sub process
62
+ # rather than directly through the logged in shell.
63
+ #
64
+ # @return [String]
65
+ # Return a one line textual shell identity string.
66
+ #
67
+ # As key derivation algorithms enforcing a maximum length may be length may
68
+ # be applied, each character must add value so non-alphanumerics (mostly hyphens)
69
+ # are cleansed out before returning.
70
+ def self.derive_shell_identifier( use_grandparent_pid = false )
71
+
72
+ require 'socket'
73
+
74
+ # -- Ensure that the most significant data points
75
+ # -- come first just like with numbers.
76
+
77
+ identity_text =
78
+ [
79
+ get_ancestor_pid( use_grandparent_pid ),
80
+ get_bootup_id(),
81
+ Etc.getlogin(),
82
+ Socket.gethostname()
83
+ ].join
84
+
85
+ return identity_text.to_alphanumeric
86
+
87
+ end
88
+
89
+
90
+ # Return an ancestor process ID meaning return either the parent process
91
+ # ID or the grandparent process ID. The one returned depends on the paremeter
92
+ # boolean value.
93
+ #
94
+ # == Command Used to find the grandparent process ID.
95
+ #
96
+ # $ ps -fp 31870 | awk "/tty/"' { print $3 } '
97
+ # $ ps -fp 31870 | awk "/31870/"' { print $3 } '
98
+ #
99
+ # The one liner finds the parental process ID of the process with the given
100
+ # parameter process ID.
101
+ #
102
+ # $ ps -fp 31870
103
+ #
104
+ # UID PID PPID C STIME TTY TIME CMD
105
+ # joe 31870 2618 0 12:55 tty2 00:01:03 /usr/bin/emacs25
106
+ #
107
+ # The ps command outputs two (2) lines and **awk** is employed to select the
108
+ # line containing the already known ID. We then print the 3rd string in the
109
+ # line which we expect to be the parent PID of the PID.
110
+ #
111
+ # == Warning | Do Not Use $PPID
112
+ #
113
+ # Using $PPID is fools gold because the PS command itself runs as another
114
+ # process so $PPID is this (calling) process ID and the number returned is
115
+ # exactly the same as the parent ID of this process - which is actually the
116
+ # grandparent of the invoked ps process.
117
+ #
118
+ # @param use_grandparent_pid [Boolean]
119
+ # Set to true if the grandparent process ID is required and false if
120
+ # only the parent process ID should be returned.
121
+ #
122
+ # @return [String]
123
+ # Return ancestor process ID that belongs to either the parent process
124
+ # or the grandparent process.
125
+ def self.get_ancestor_pid( use_grandparent_pid )
126
+
127
+ parental_process_id = Process.ppid.to_s()
128
+ grandparent_pid_cmd = "ps -fp #{parental_process_id} | awk \"/#{parental_process_id}/\"' { print $3 } '"
129
+ raw_grandparent_pid = %x[#{grandparent_pid_cmd}]
130
+ the_grandparent_pid = raw_grandparent_pid.chomp
131
+
132
+ log.debug(x) { "QQQQQ ~> QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" }
133
+ log.debug(x) { "QQQQQ ~> Request Bool Use GPPID is ~> [[ #{use_grandparent_pid} ]]" }
134
+ log.debug(x) { "QQQQQ ~> Main Parent Process ID is ~> [[ #{parental_process_id} ]]" }
135
+ log.debug(x) { "QQQQQ ~> GrandParent Process ID is ~> [[ #{the_grandparent_pid} ]]" }
136
+ log.debug(x) { "QQQQQ ~> QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" }
137
+
138
+ return ( use_grandparent_pid ? the_grandparent_pid : parental_process_id )
139
+
140
+ end
141
+
142
+
143
+ # This method uses a one-way function to return a combinatorial digested
144
+ # machine identification string using a number of distinct input parameters
145
+ # to deliver the characteristic of producing the same identifier for the
146
+ # same machine, virtual machine, workstation and/or compute element, and
147
+ # reciprocally, a different one on a different machine.
148
+ #
149
+ # The userspace is also a key machine identifier so a different machine user
150
+ # generates a different identifier when all other things remain equal.
151
+ #
152
+ # @return [String]
153
+ # a one line textual machine workstation or compute element identifier
154
+ # that is (surprisingly) different when the machine user changes.
155
+ def self.derive_machine_identifier
156
+
157
+ require 'socket'
158
+
159
+ identity_text = [
160
+ Etc.getlogin,
161
+ get_machine_id(),
162
+ Socket.gethostname()
163
+ ].join.reverse
164
+
165
+ return identity_text
166
+
167
+ end
168
+
169
+
170
+ # If you need to know whether a Linux computer has been rebooted or
171
+ # you need an identifier that stays the same until the computer reboots,
172
+ # look no further than the read only (non sudoer accessible) **boot id**.
173
+ #
174
+ # In the modern era of virtualization you should always check the behaviour
175
+ # of the above identifiers when used inside
176
+ #
177
+ # - docker containers
178
+ # - Amazon EC2 servers (or Azure or GCE)
179
+ # - vagrant (VirtualBox/VMWare)
180
+ # - Windows MSGYWIN (Ubuntu) environments
181
+ # - Kubernetes pods
182
+ #
183
+ # @return [String] the bootup ID hash value
184
+ def self.get_bootup_id
185
+
186
+ bootup_id_cmd = "cat /proc/sys/kernel/random/boot_id"
187
+ bootup_id_str = %x[ #{bootup_id_cmd} ]
188
+ return bootup_id_str.chomp
189
+
190
+ end
191
+
192
+
193
+ # The machine identifier is a UUID based hash value that is tied to the
194
+ # CPU and motherboard of the machine. This read-only identifier can be
195
+ # accessed without sudoer permissions so is perfect for license generators
196
+ # and environment sensitive software.
197
+ #
198
+ # In the modern era of virtualization you should always check the behaviour
199
+ # of the above identifiers when used inside
200
+ #
201
+ # - docker containers
202
+ # - Amazon EC2 servers (or Azure or GCE)
203
+ # - vagrant (VirtualBox/VMWare)
204
+ # - Windows MSGYWIN (Ubuntu) environments
205
+ # - Kubernetes pods
206
+ #
207
+ # @return [String] the machine ID hash value
208
+ def self.get_machine_id
209
+
210
+ machine_id_cmd = "cat /etc/machine-id"
211
+ machine_id_str = %x[ #{machine_id_cmd} ]
212
+ return machine_id_str.chomp
213
+
214
+ end
215
+
216
+
217
+ # If the system was rebooted on April 23rd, 2018 at 22:00:16 we
218
+ # expect this method not to return <b>2018-04-23 22:00:16</b>, but
219
+ # to return the <b>8 least significant digits</b> bootup time
220
+ # digits which in this case are <b>23220016</b>.
221
+ #
222
+ # Investigate all Linux flavours to understand whether this command
223
+ # works (or is it just Ubuntu). Also does Docker return a sensible
224
+ # value here?
225
+ #
226
+ # This method is not production ready. Not only is the time within
227
+ # a small range, also the most significant digit can fluctuate up
228
+ # or down randomly (in a non-deterministic manner.
229
+ #
230
+ # @return [String] the time when the system was booted.
231
+ def self.get_bootup_time_digits
232
+
233
+ boot_time_cmd = "uptime -s"
234
+ uptime_string = %x[ #{boot_time_cmd} ]
235
+ return uptime_string.chomp.to_alphanumeric[ 6 .. -1 ]
236
+
237
+ end
238
+
239
+
240
+ end
241
+
242
+
243
+ end
@@ -27,20 +27,31 @@ module OpenKey
27
27
  BCRYPT_SALT_LENGTH = 22
28
28
 
29
29
 
30
+ # There are two digits representing the BCrypt iteration count.
31
+ # The minimum is 10 and the maximum is 16.
32
+ BCRYPT_ITER_COUNT_SIZE = 2
33
+
34
+
30
35
  # The session token comprises of 3 segments with fixed lengths.
31
36
  # This triply segmented text token that can be used to decrypt
32
37
  # and deliver the shell key.
33
- SESSION_TOKEN_SIZE = 128 + 22
38
+ SESSION_TOKEN_SIZE = 128 + 22 + BCRYPT_ITER_COUNT_SIZE
34
39
 
35
40
 
36
- # Given a 150 character session token, what is the index that pinpoints
41
+ # Given a 152 character session token, what is the index that pinpoints
37
42
  # the beginning of the 22 character BCrypt salt? The answer is given
38
43
  # by this BCRYPT_SALT_START_INDEX constant.
39
- BCRYPT_SALT_START_INDEX = SESSION_TOKEN_SIZE - BCRYPT_SALT_LENGTH
44
+ BCRYPT_SALT_START_INDEX = SESSION_TOKEN_SIZE - BCRYPT_SALT_LENGTH - BCRYPT_ITER_COUNT_SIZE
45
+
46
+
47
+ # What index pinpoints the end of the BCrypt salt itself.
48
+ # This is easy as the final 2 characters are the iteration count
49
+ # so the end index is the length subtract 1 subtract 2.
50
+ BCRYPT_SALT_END_INDEX = SESSION_TOKEN_SIZE - 1
40
51
 
41
52
 
42
- # Instantiate the session by generating a random high entropy shell key
43
- # and then generating an obfuscator key which we use to lock the shell
53
+ # Initialize the session by generating a random high entropy shell token
54
+ # and then generate an obfuscator key which we use to lock the shell
44
55
  # key and return a triply segmented text token that can be used to decrypt
45
56
  # and deliver the shell key as long as the same shell on the same machine
46
57
  # is employed to make the call.
@@ -73,27 +84,12 @@ module OpenKey
73
84
  # and within the same shell on the same machine.
74
85
  def self.generate_shell_key_and_token
75
86
 
76
- calling_module = File.basename caller_locations(1,1).first.absolute_path, ".rb"
77
- calling_method = caller_locations(1,1).first.base_label
78
- calling_lineno = caller_locations(1,1).first.lineno
79
- caller_details = "#{calling_module} | #{calling_method} | (line #{calling_lineno})"
80
-
81
- log.info(x) { "### #####################################################################" }
82
- log.info(x) { "### Caller Details =>> =>> #{caller_details}" }
83
- log.info(x) { "### #####################################################################" }
84
-
85
-
86
87
  bcrypt_salt_key = KdfBCrypt.generate_bcrypt_salt
87
88
  obfuscator_key = derive_session_crypt_key( bcrypt_salt_key )
88
89
  random_key_ciphertext = obfuscator_key.do_encrypt_key( Key.from_random() )
89
90
  session_token = random_key_ciphertext + bcrypt_salt_key.reverse
90
91
  assert_session_token_size( session_token )
91
92
 
92
- log.info(x) { "BCrypt Salt Create => #{bcrypt_salt_key}" }
93
- log.info(x) { "Obfuscate ShellKey => #{obfuscator_key.to_s()}" }
94
- log.info(x) { "EncryptedKey Crypt => #{random_key_ciphertext}" }
95
- log.info(x) { "Session Token Unit => #{session_token}" }
96
-
97
93
  return session_token
98
94
 
99
95
  end
@@ -103,17 +99,17 @@ module OpenKey
103
99
  # during the {instantiate_shell_key_and_generate_token} method.
104
100
  #
105
101
  # To successfully reacquire the randomly generated (and then locked)
106
- # shell key we must be provided with four (4) data points, three (3)
102
+ # shell key we must be provided with five (5) data points, four (4)
107
103
  # of which are embalmed within the 150 character session token
108
104
  # parameter.
109
105
  #
110
106
  # <b>What we need to Regenerate the Shell Key</b>
111
107
  #
112
108
  # Regenerating the shell key is done in two steps when given the
113
- # three (3) <b>session token segments</b> described below, and the
109
+ # four (4) <b>session token segments</b> described below, and the
114
110
  # shell identity key described in the {OpenKey::Identifier} class.
115
111
  #
116
- # The session token is divided up into 3 segments with a total of 150
112
+ # The session token is divided up into 4 segments with a total of 152
117
113
  # characters.
118
114
  #
119
115
  # | -------- | ------------ | ------------------------------------- |
@@ -122,55 +118,36 @@ module OpenKey
122
118
  # | 1 | 16 bytes | AES Encrypt Initialization Vector(IV) |
123
119
  # | 2 | 80 bytes | Cipher text from Random Key AES crypt |
124
120
  # | 3 | 22 chars | Salt 4 shell identity key derivation |
121
+ # | 4 | 2 chars | BCrypt iteration parameter (10 to 16) |
125
122
  # | -------- | ------------ | ------------------------------------- |
126
- # | Total | 150 chars | Session Token in Environment Variable |
123
+ # | Total | 152 chars | Session Token in Environment Variable |
127
124
  # | -------- | ------------ | ------------------------------------- |
128
125
  #
129
- # <b>How to Regenerate the Shell Key</b>
130
- #
131
- # The two steps for regenerating the shell key are
132
- #
133
- # - use the shell identity string and the BCrypt key derivation salt
134
- # in the third segment of the token to regenerate the shell identity
135
- # key.
136
- #
137
- # - put 3 items through AES 256 decryption to derive the 256 bit shell
138
- # crypt key. The 3 items are the <b>shell identity key</b> derived
139
- # in step 1, the AES IV (initialization vector, in the first segment
140
- # of the token, and the <b>ciphertext in the middle segment</b>.
141
- #
142
126
  # @param session_token [String]
143
127
  # a triply segmented (and one liner) text token instantiated by
144
128
  # {self.instantiate_shell_key_and_generate_token} and provided
145
129
  # here ad verbatim.
146
130
  #
131
+ # @param use_grandparent_pid [Boolean]
132
+ #
133
+ # Optional boolean parameter. If set to true the PID (process ID) used
134
+ # as part of an obfuscator key and normally acquired from the parent
135
+ # process should now be acquired from the grandparent's process.
136
+ #
137
+ # Set to true when accessing the safe's credentials from a sub process
138
+ # rather than directly through the logged in shell.
139
+ #
147
140
  # @return [OpenKey::Key]
148
141
  # an extremely high entropy 256 bit key derived (digested) from 48
149
142
  # random bytes at the beginning of the shell (cli) session.
150
- def self.regenerate_shell_key( session_token )
151
-
152
- calling_module = File.basename caller_locations(1,1).first.absolute_path, ".rb"
153
- calling_method = caller_locations(1,1).first.base_label
154
- calling_lineno = caller_locations(1,1).first.lineno
155
- caller_details = "#{calling_module} | #{calling_method} | (line #{calling_lineno})"
156
-
157
- log.info(x) { "### #####################################################################" }
158
- log.info(x) { "### Caller Details =>> =>> #{caller_details}" }
159
- log.info(x) { "### #####################################################################" }
160
-
143
+ def self.regenerate_shell_key( session_token, use_grandparent_pid = false )
161
144
 
162
145
  assert_session_token_size( session_token )
163
- bcrypt_salt = session_token[ BCRYPT_SALT_START_INDEX .. -1 ].reverse
146
+ bcrypt_salt = session_token[ BCRYPT_SALT_START_INDEX .. BCRYPT_SALT_END_INDEX ].reverse
164
147
  assert_bcrypt_salt_size( bcrypt_salt )
165
148
 
166
149
  key_ciphertext = session_token[ 0 .. ( BCRYPT_SALT_START_INDEX - 1 ) ]
167
- obfuscator_key = derive_session_crypt_key( bcrypt_salt )
168
-
169
- log.info(x) { "BCrypt Salt REGEND => #{bcrypt_salt}" }
170
- log.info(x) { "SessionToken REGEN => #{session_token}" }
171
- log.info(x) { "Chopped Ciphertext => #{key_ciphertext}" }
172
- log.info(x) { "Obfuscate ShellKey => #{obfuscator_key.to_s()}" }
173
-
150
+ obfuscator_key = derive_session_crypt_key( bcrypt_salt, use_grandparent_pid )
174
151
  regenerated_key = obfuscator_key.do_decrypt_key( key_ciphertext )
175
152
 
176
153
  return regenerated_key
@@ -219,26 +196,29 @@ module OpenKey
219
196
  #
220
197
  # @param bcrypt_salt_key [OpenKey::Key]
221
198
  #
222
- # Either use the {KdfBCrypt.generate_bcrypt_salt} method to generate
223
- # the salt or retrieve and post in a previously generated salt which
224
- # must hold 22 printable characters.
199
+ # Either use BCrypt to generate the salt or retrieve and post in a
200
+ # previously generated salt which must hold 22 printable characters.
201
+ #
202
+ # @param use_grandparent_pid [Boolean]
203
+ #
204
+ # Optional boolean parameter. If set to true the PID (process ID) used
205
+ # as part of an obfuscator key and normally acquired from the parent
206
+ # process should now be acquired from the grandparent's process.
207
+ #
208
+ # Set to true when accessing the safe's credentials from a sub process
209
+ # rather than directly through the logged in shell.
225
210
  #
226
211
  # @return [OpenKey::Key]
227
212
  # a digested key suitable for short term (session scoped) use with the
228
213
  # guarantee that the same key will be returned whenever called from within
229
214
  # the same executing shell environment and a different key when not.
230
- def self.derive_session_crypt_key bcrypt_salt_key
215
+ def self.derive_session_crypt_key bcrypt_salt_key, use_grandparent_pid = false
231
216
 
232
- shell_id_text = KeyMach.derive_shell_identity_string()
217
+ shell_id_text = KeyIdent.derive_shell_identifier( use_grandparent_pid )
233
218
  truncate_text = shell_id_text.length > KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH
234
219
  shell_id_trim = shell_id_text unless truncate_text
235
220
  shell_id_trim = shell_id_text[ 0 .. ( KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH - 1 ) ] if truncate_text
236
221
 
237
- log.info(x) { "Shell Identity Str => #{shell_id_text}" }
238
- log.info(x) { "Shell Id TxtLength => #{shell_id_text.length()}" }
239
- log.info(x) { "Truncate Shell Str => #{truncate_text}" }
240
- log.info(x) { "Resulting IDString => #{shell_id_trim}" }
241
-
242
222
  return KdfBCrypt.generate_key( shell_id_trim, bcrypt_salt_key )
243
223
 
244
224
  end
@@ -247,6 +227,19 @@ module OpenKey
247
227
  private
248
228
 
249
229
 
230
+ # 000000000000000000000000000000000000000000000000000000000000000
231
+ # How to determine the caller.
232
+ # Better strategy would be just to print the stack trace
233
+ # That gives you much more bang for the one line buck.
234
+ # 000000000000000000000000000000000000000000000000000000000000000
235
+ # calling_module = File.basename caller_locations(1,1).first.absolute_path, ".rb"
236
+ # calling_method = caller_locations(1,1).first.base_label
237
+ # calling_lineno = caller_locations(1,1).first.lineno
238
+ # caller_details = "#{calling_module} | #{calling_method} | (line #{calling_lineno})"
239
+ # log.info(x) { "### Caller Details =>> =>> #{caller_details}" }
240
+ # 000000000000000000000000000000000000000000000000000000000000000
241
+
242
+
250
243
  def self.assert_session_token_size session_token
251
244
  err_msg = "Session token has #{session_token.length} and not #{SESSION_TOKEN_SIZE} chars."
252
245
  raise RuntimeError, err_msg unless session_token.length == SESSION_TOKEN_SIZE
@@ -254,8 +247,9 @@ module OpenKey
254
247
 
255
248
 
256
249
  def self.assert_bcrypt_salt_size bcrypt_salt
257
- err_msg = "Expected BCrypt salt length of #{BCRYPT_SALT_LENGTH} not #{bcrypt_salt.length}."
258
- raise RuntimeError, err_msg unless bcrypt_salt.length == BCRYPT_SALT_LENGTH
250
+ amalgam_length = BCRYPT_SALT_LENGTH + BCRYPT_ITER_COUNT_SIZE
251
+ err_msg = "Expected BCrypt salt length of #{amalgam_length} not #{bcrypt_salt.length}."
252
+ raise RuntimeError, err_msg unless bcrypt_salt.length == amalgam_length
259
253
  end
260
254
 
261
255