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.
- checksums.yaml +4 -4
- data/README.md +656 -40
- data/lib/configs/README.md +58 -0
- data/lib/extension/file.rb +67 -0
- data/lib/extension/string.rb +10 -0
- data/lib/factbase/facts.opensecret.io.ini +1 -0
- data/lib/interprete.rb +334 -61
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +9 -15
- data/lib/keytools/kdf.bcrypt.rb +69 -19
- data/lib/keytools/kdf.pbkdf2.rb +112 -23
- data/lib/keytools/key.api.rb +146 -36
- data/lib/keytools/key.db.rb +94 -29
- data/lib/keytools/key.id.rb +1 -1
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.local.rb +62 -68
- data/lib/keytools/key.pass.rb +2 -2
- data/lib/keytools/key.rb +2 -28
- data/lib/modules/{cryptology.md → README.md} +0 -0
- data/lib/session/fact.finder.rb +65 -428
- data/lib/session/time.stamp.rb +1 -28
- data/lib/usecase/cmd.rb +127 -54
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +1 -1
- data/lib/usecase/import.rb +13 -30
- data/lib/usecase/init.rb +2 -17
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/crazy_ruby_post_attempt.OLD +234 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +6 -5
- data/lib/usecase/logout.rb +1 -3
- data/lib/usecase/open.rb +11 -66
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +34 -156
- data/lib/usecase/set.rb +2 -4
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +4 -80
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +1 -3
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +3 -4
- metadata +34 -35
- data/.travis.yml +0 -5
- data/CODE_OF_CONDUCT.md +0 -74
- data/LICENSE.txt +0 -21
- data/bin/ops +0 -20
- data/lib/keytools/binary.map.rb +0 -294
- data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +0 -179
- data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +0 -190
- data/lib/keytools/doc.star.schema.strategy.txt +0 -77
- data/lib/keytools/doc.using.pbkdf2.kdf.ruby +0 -95
- data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +0 -266
- data/lib/keytools/key.mach.rb +0 -248
- data/lib/keytools/keydebug.txt +0 -295
- data/lib/modules/cryptology/open.bcrypt.rb +0 -170
- data/lib/usecase/read.rb +0 -89
- data/lib/usecase/safe.rb +0 -92
data/lib/keytools/key.db.rb
CHANGED
@@ -5,7 +5,7 @@ module OpenKey
|
|
5
5
|
|
6
6
|
require 'json'
|
7
7
|
|
8
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
97
|
-
#
|
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
|
-
#
|
100
|
-
#
|
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
|
-
#
|
102
|
+
# If this method is called against a new empty map, the resulting map
|
103
|
+
# structure will look like the below.
|
104
104
|
#
|
105
|
-
#
|
106
|
-
# perfectly with a path down this tree database.
|
105
|
+
# { outer_keyname ~> { inner_keyname ~> { entry_keyname, entry_value } } }
|
107
106
|
#
|
108
|
-
#
|
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
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
125
|
-
|
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
|
-
|
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))
|
data/lib/keytools/key.id.rb
CHANGED
@@ -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 +
|
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
|
data/lib/keytools/key.local.rb
CHANGED
@@ -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
|
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
|
-
#
|
43
|
-
# and then
|
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
|
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
|
-
#
|
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
|
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 |
|
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 ..
|
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
|
223
|
-
#
|
224
|
-
#
|
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 =
|
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
|
-
|
258
|
-
|
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
|
|