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.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +56 -159
  3. data/bin/opensecret +2 -2
  4. data/bin/ops +17 -2
  5. data/lib/extension/string.rb +14 -16
  6. data/lib/{interpreter.rb → interprete.rb} +53 -29
  7. data/lib/keytools/binary.map.rb +49 -0
  8. data/lib/keytools/kdf.api.rb +249 -0
  9. data/lib/keytools/kdf.bcrypt.rb +64 -29
  10. data/lib/keytools/kdf.pbkdf2.rb +92 -83
  11. data/lib/keytools/kdf.scrypt.rb +190 -0
  12. data/lib/keytools/key.64.rb +326 -0
  13. data/lib/keytools/key.algo.rb +109 -0
  14. data/lib/keytools/key.api.rb +1281 -0
  15. data/lib/keytools/key.db.rb +265 -0
  16. data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
  17. data/lib/keytools/key.error.rb +110 -0
  18. data/lib/keytools/key.id.rb +271 -0
  19. data/lib/keytools/key.iv.rb +107 -0
  20. data/lib/keytools/key.local.rb +265 -0
  21. data/lib/keytools/key.mach.rb +248 -0
  22. data/lib/keytools/key.now.rb +402 -0
  23. data/lib/keytools/key.pair.rb +259 -0
  24. data/lib/keytools/key.pass.rb +120 -0
  25. data/lib/keytools/key.rb +428 -298
  26. data/lib/keytools/keydebug.txt +295 -0
  27. data/lib/logging/gem.logging.rb +3 -3
  28. data/lib/modules/cryptology/collect.rb +20 -0
  29. data/lib/session/require.gem.rb +1 -1
  30. data/lib/usecase/cmd.rb +417 -0
  31. data/lib/usecase/id.rb +36 -0
  32. data/lib/usecase/import.rb +174 -0
  33. data/lib/usecase/init.rb +78 -0
  34. data/lib/usecase/login.rb +70 -0
  35. data/lib/usecase/logout.rb +30 -0
  36. data/lib/usecase/open.rb +126 -0
  37. data/lib/{interprete → usecase}/put.rb +100 -47
  38. data/lib/usecase/read.rb +89 -0
  39. data/lib/{interprete → usecase}/safe.rb +0 -0
  40. data/lib/{interprete → usecase}/set.rb +0 -0
  41. data/lib/usecase/token.rb +111 -0
  42. data/lib/{interprete → usecase}/use.rb +0 -0
  43. data/lib/version.rb +1 -1
  44. data/opensecret.gemspec +4 -3
  45. metadata +39 -33
  46. data/lib/exception/cli.error.rb +0 -53
  47. data/lib/exception/errors/cli.errors.rb +0 -31
  48. data/lib/interprete/begin.rb +0 -232
  49. data/lib/interprete/cmd.rb +0 -621
  50. data/lib/interprete/export.rb +0 -163
  51. data/lib/interprete/init.rb +0 -205
  52. data/lib/interprete/key.rb +0 -119
  53. data/lib/interprete/open.rb +0 -148
  54. data/lib/interprete/seal.rb +0 -129
  55. data/lib/keytools/digester.rb +0 -245
  56. data/lib/keytools/key.data.rb +0 -227
  57. data/lib/keytools/key.derivation.rb +0 -341
  58. data/lib/modules/mappers/collateral.rb +0 -282
  59. data/lib/modules/mappers/envelope.rb +0 -127
  60. data/lib/modules/mappers/settings.rb +0 -170
  61. data/lib/notepad/scratch.pad.rb +0 -224
  62. data/lib/store-commands.txt +0 -180
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/ruby
2
+
3
+ module OpenKey
4
+
5
+ # Create and deliver representations of a random initialization vector
6
+ # suitable for the AES symmetric encryption algorithm which demands a
7
+ # 18 byte binary string.
8
+ #
9
+ # The initialization vector is sourced from {SecureRandom} which provides
10
+ # a highly random (and secure) byte sequence usually sourced from udev-random.
11
+ #
12
+ # + ------------------ + -------- + ------------ + ------------------- +
13
+ # | Random IV Format | Bits | Bytes | Base64 |
14
+ # | ------------------ | -------- | ------------ | ------------------- |
15
+ # | Random IV Stored | 192 Bits | 24 bytes | 32 characters |
16
+ # | Random IV Binary | 128 Bits | 16 bytes | (not stored) |
17
+ # + ------------------ + -------- + ------------ + ------------------- +
18
+ #
19
+ # This table shows that the initialization vector can be represented by
20
+ # both a <b>32 character base64 string</b> suitable for storage and a
21
+ # <b>18 byte binary</b> for feeding the algorithm.
22
+ class KeyIV
23
+
24
+
25
+ # The 24 random bytes is equivalent to 192 bits which when sliced into 6 bit
26
+ # blocks (one for each base64 character) results in 32 base64 characters.
27
+ NO_OF_BASE64_CHARS = 32
28
+
29
+ # We ask for 24 secure random bytes that are individually created to ensure
30
+ # we get exactly the right number.
31
+ NO_OF_SOURCE_BYTES = 24
32
+
33
+ # We truncate the source random bytes so that 16 bytes are returned for the
34
+ # random initialization vector.
35
+ NO_OF_BINARY_BYTES = 16
36
+
37
+
38
+ # Initialize an initialization vector from a source of random bytes
39
+ # which can then be presented in both a <b>(base64) storage</b> format
40
+ # and a <b>binary string</b> format.
41
+ #
42
+ # + ------------------ + -------- + ------------ + ------------------- +
43
+ # | Random IV Format | Bits | Bytes | Base64 |
44
+ # | ------------------ | -------- | ------------ | ------------------- |
45
+ # | Random IV Stored | 192 Bits | 24 bytes | 32 characters |
46
+ # | Random IV Binary | 128 Bits | 16 bytes | (not stored) |
47
+ # + ------------------ + -------- + ------------ + ------------------- +
48
+ #
49
+ # We ask for 24 secure random bytes that are individually created to ensure
50
+ # we get exactly the right number.
51
+ #
52
+ # If the storage format is requested a <b>32 character base64 string</b> is
53
+ # returned but if the binary form is requested the <b>first 16 bytes</b> are
54
+ # issued.
55
+ def initialize
56
+ @bit_string = Key.to_random_bits( NO_OF_SOURCE_BYTES )
57
+ end
58
+
59
+
60
+ # When the storage format is requested a <b>32 character base64 string</b> is
61
+ # returned - created from the initialized 24 secure random bytes.
62
+ #
63
+ # + ---------------- + -------- + ------------ + ------------------- +
64
+ # | Random IV Stored | 192 Bits | 24 bytes | 32 characters |
65
+ # + ---------------- + -------- + ------------ + ------------------- +
66
+ #
67
+ # @return [String]
68
+ # a <b>32 character base64 formatted string</b> is returned.
69
+ def for_storage
70
+ return Key64.from_bits( @bit_string )
71
+ end
72
+
73
+
74
+ #
75
+ # + ---------------- + -------- + ------------ + ------------------- +
76
+ # | Random IV Binary | 128 Bits | 16 bytes | (not stored) |
77
+ # + ---------------- + -------- + ------------ + ------------------- +
78
+ #
79
+ # @param iv_base64_chars [String]
80
+ # the 32 characters in base64 format that will be converted into a binary
81
+ # string (24 byte) representation and then truncated to 16 bytes and outputted
82
+ # in binary form.
83
+ #
84
+ # @return [String]
85
+ # a <b>16 byte binary string</b> is returned.
86
+ #
87
+ # @raise [ArgumentError]
88
+ # if a <b>32 base64 characters</b> are not presented in the parameter.
89
+ def self.in_binary iv_base64_chars
90
+
91
+ b64_msg = "Expected #{NO_OF_BASE64_CHARS} base64 chars not #{iv_base64_chars.length}."
92
+ raise ArgumentError, b64_msg unless iv_base64_chars.length == NO_OF_BASE64_CHARS
93
+
94
+ binary_string = Key.to_binary_from_bit_string( Key64.to_bits( iv_base64_chars ) )
95
+
96
+ bin_msg = "Expected #{NO_OF_SOURCE_BYTES} binary bytes not #{binary_string.length}."
97
+ raise RuntimeError, bin_msg unless binary_string.length == NO_OF_SOURCE_BYTES
98
+
99
+ return binary_string[ 0 .. ( NO_OF_BINARY_BYTES - 1 ) ]
100
+
101
+ end
102
+
103
+
104
+ end
105
+
106
+
107
+ end
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module OpenKey
5
+
6
+ # The command line interface has a high entropy randomly generated
7
+ # key whose purpose is to <b>lock the application's data key</b> for
8
+ # the duration of the session which is between a login and a logout.
9
+ #
10
+ # These keys are unique to only one shell session on one workstation
11
+ # and they live lives that are no longer (and mostly shorter) than
12
+ # the life of the parent shell.
13
+ #
14
+ # == The 4 CLI Shell Entities
15
+ #
16
+ # The four (4) important entities within the shell session are
17
+ #
18
+ # - an obfuscator key for locking the shell key during a session
19
+ # - a high entropy randomly generated shell key for locking the app data key
20
+ # - one environment variable whose value embodies three (3) data segments
21
+ # - a session id derived by pushing the env var through a one-way function
22
+ class KeyLocal
23
+
24
+
25
+ # The number of Radix64 characters that make up a valid BCrypt salt.
26
+ # To create a BCrypt salt use
27
+ BCRYPT_SALT_LENGTH = 22
28
+
29
+
30
+ # The session token comprises of 3 segments with fixed lengths.
31
+ # This triply segmented text token that can be used to decrypt
32
+ # and deliver the shell key.
33
+ SESSION_TOKEN_SIZE = 128 + 22
34
+
35
+
36
+ # Given a 150 character session token, what is the index that pinpoints
37
+ # the beginning of the 22 character BCrypt salt? The answer is given
38
+ # by this BCRYPT_SALT_START_INDEX constant.
39
+ BCRYPT_SALT_START_INDEX = SESSION_TOKEN_SIZE - BCRYPT_SALT_LENGTH
40
+
41
+
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
44
+ # key and return a triply segmented text token that can be used to decrypt
45
+ # and deliver the shell key as long as the same shell on the same machine
46
+ # is employed to make the call.
47
+ #
48
+ # <b>The 3 Session Token Segments</b>
49
+ #
50
+ # The session token is divided up into 3 segments with a total of 150
51
+ # characters.
52
+ #
53
+ # | -------- | ------------ | ------------------------------------- |
54
+ # | Segment | Length | Purpose |
55
+ # | -------- | ------------ | ------------------------------------- |
56
+ # | 1 | 16 bytes | AES Encrypt Initialization Vector(IV) |
57
+ # | 2 | 80 bytes | Cipher text from Random Key AES crypt |
58
+ # | 3 | 22 chars | Salt for obfuscator key derivation |
59
+ # | -------- | ------------ | ------------------------------------- |
60
+ # | Total | 150 chars | Session Token in Environment Variable |
61
+ # | -------- | ------------ | ------------------------------------- |
62
+ #
63
+ # Why is the <b>16 byte salt and the 80 byte BCrypt ciphertext</b> represented
64
+ # by <b>128 base64 characters</b>?
65
+ #
66
+ # 16 bytes + 80 bytes = 96 bytes
67
+ # 96 bytes x 8 bits = 768 bits
68
+ # 768 bits / 6 bits = 128 base64 characters
69
+ #
70
+ # @return [String]
71
+ # return a triply segmented text token that can be used to decrypt
72
+ # and redeliver the high entropy session shell key on the same machine
73
+ # and within the same shell on the same machine.
74
+ def self.generate_shell_key_and_token
75
+
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
+ bcrypt_salt_key = KdfBCrypt.generate_bcrypt_salt
87
+ obfuscator_key = derive_session_crypt_key( bcrypt_salt_key )
88
+ random_key_ciphertext = obfuscator_key.do_encrypt_key( Key.from_random() )
89
+ session_token = random_key_ciphertext + bcrypt_salt_key.reverse
90
+ assert_session_token_size( session_token )
91
+
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
+ return session_token
98
+
99
+ end
100
+
101
+
102
+ # Regenerate the random shell key that was instantiated and locked
103
+ # during the {instantiate_shell_key_and_generate_token} method.
104
+ #
105
+ # To successfully reacquire the randomly generated (and then locked)
106
+ # shell key we must be provided with four (4) data points, three (3)
107
+ # of which are embalmed within the 150 character session token
108
+ # parameter.
109
+ #
110
+ # <b>What we need to Regenerate the Shell Key</b>
111
+ #
112
+ # Regenerating the shell key is done in two steps when given the
113
+ # three (3) <b>session token segments</b> described below, and the
114
+ # shell identity key described in the {OpenKey::Identifier} class.
115
+ #
116
+ # The session token is divided up into 3 segments with a total of 150
117
+ # characters.
118
+ #
119
+ # | -------- | ------------ | ------------------------------------- |
120
+ # | Segment | Length | Purpose |
121
+ # | -------- | ------------ | ------------------------------------- |
122
+ # | 1 | 16 bytes | AES Encrypt Initialization Vector(IV) |
123
+ # | 2 | 80 bytes | Cipher text from Random Key AES crypt |
124
+ # | 3 | 22 chars | Salt 4 shell identity key derivation |
125
+ # | -------- | ------------ | ------------------------------------- |
126
+ # | Total | 150 chars | Session Token in Environment Variable |
127
+ # | -------- | ------------ | ------------------------------------- |
128
+ #
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
+ # @param session_token [String]
143
+ # a triply segmented (and one liner) text token instantiated by
144
+ # {self.instantiate_shell_key_and_generate_token} and provided
145
+ # here ad verbatim.
146
+ #
147
+ # @return [OpenKey::Key]
148
+ # an extremely high entropy 256 bit key derived (digested) from 48
149
+ # 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
+
161
+
162
+ assert_session_token_size( session_token )
163
+ bcrypt_salt = session_token[ BCRYPT_SALT_START_INDEX .. -1 ].reverse
164
+ assert_bcrypt_salt_size( bcrypt_salt )
165
+
166
+ 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
+
174
+ regenerated_key = obfuscator_key.do_decrypt_key( key_ciphertext )
175
+
176
+ return regenerated_key
177
+
178
+ end
179
+
180
+
181
+ # Derive a <b>short term (session scoped) encryption key</b> from the
182
+ # surrounding shell and workstation (machine) environment with an
183
+ # important same/different guarantee.
184
+ #
185
+ # The <b>same / different guarantee promises</b> us that the derived
186
+ # key will be
187
+ #
188
+ # - <b>the same</b> whenever called from within this executing shell
189
+ # - <b>different</b> when the shell and/or workstation are different
190
+ #
191
+ # This method uses a one-way function to return a combinatorial digested
192
+ # session identification string using a number of distinct parameters that
193
+ # deliver the important behaviours of changing in certain circumstances
194
+ # and remaining unchanged in others.
195
+ #
196
+ # <b>Change | When Should the key Change?</b>
197
+ #
198
+ # What is really important is that the <b>key changes when</b>
199
+ #
200
+ # - the <b>command shell</b> changes
201
+ # - the workstation <b>shell user is switched</b>
202
+ # - the host machine <b>workstation</b> is changed
203
+ # - the user <b>SSH's</b> into another shell
204
+ #
205
+ # A distinct workstation is identified by the first MAC address and the
206
+ # hostname of the machine.
207
+ #
208
+ # <b>Unchanged | When Should the Key Remain Unchanged?</b>
209
+ #
210
+ # Remaining <b>unchanged</b> in certain scenarios is a feature that is
211
+ # just as important as changing in others. The key must remain
212
+ # <b>unchanged</b> when
213
+ #
214
+ # - the <b>user returns to a command shell</b>
215
+ # - the user exits their <b>remote SSH session</b>
216
+ # - <b>sudo is used</b> to execute the commands
217
+ # - the user comes back to their <b>workstation</b>
218
+ # - the clock ticks into another day, month, year ...
219
+ #
220
+ # @param bcrypt_salt_key [OpenKey::Key]
221
+ #
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.
225
+ #
226
+ # @return [OpenKey::Key]
227
+ # a digested key suitable for short term (session scoped) use with the
228
+ # guarantee that the same key will be returned whenever called from within
229
+ # the same executing shell environment and a different key when not.
230
+ def self.derive_session_crypt_key bcrypt_salt_key
231
+
232
+ shell_id_text = KeyMach.derive_shell_identity_string()
233
+ truncate_text = shell_id_text.length > KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH
234
+ shell_id_trim = shell_id_text unless truncate_text
235
+ shell_id_trim = shell_id_text[ 0 .. ( KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH - 1 ) ] if truncate_text
236
+
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
+ return KdfBCrypt.generate_key( shell_id_trim, bcrypt_salt_key )
243
+
244
+ end
245
+
246
+
247
+ private
248
+
249
+
250
+ def self.assert_session_token_size session_token
251
+ err_msg = "Session token has #{session_token.length} and not #{SESSION_TOKEN_SIZE} chars."
252
+ raise RuntimeError, err_msg unless session_token.length == SESSION_TOKEN_SIZE
253
+ end
254
+
255
+
256
+ 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
259
+ end
260
+
261
+
262
+ end
263
+
264
+
265
+ end
@@ -0,0 +1,248 @@
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 KeyMach
30
+
31
+ # This method uses a one-way function to return a combinatorial digested
32
+ # session identification string using a number of distinct parameters that
33
+ # deliver the important behaviours of changing in certain circumstances
34
+ # and remaining unchanged in others.
35
+ #
36
+ # <b>Change | When Should the Session ID Change?</b>
37
+ #
38
+ # The session id is not a secret but it has to be unique due to its role
39
+ # in indexing the session envelopes and their changes.
40
+ #
41
+ # What is really important is that the <b>session id changes</b> when
42
+ #
43
+ # - the <b>domain</b> being used changes
44
+ # - the <b>command shell</b> changes
45
+ # - the user <b>switches to another workstation user</b>
46
+ # - the <b>workstation host</b> is changed
47
+ # - the <b>session token</b> environment variable changes
48
+ # - the user <b>SSH's</b> into another shell
49
+ #
50
+ # A distinct workstation is identified by the first MAC address and the
51
+ # hostname of the machine.
52
+ #
53
+ # <b>Unchanged | When Should it Remain Unchanged?</b>
54
+ #
55
+ # Remaining <b>unchanged</b> in certain scenarious is a session ID feature
56
+ # that is just as important as it changing in others.
57
+ #
58
+ # The session ID <b>must remain unchanged</b> when
59
+ #
60
+ # - the <b>user returns to a command shell</b>
61
+ # - the user <b>switches back to using a domain</b>
62
+ # - the user exits their <b>remote SSH session</b>
63
+ # - <b>sudo is used</b> to execute the commands
64
+ # - the user comes back to their <b>workstation</b>
65
+ # - the clock ticks into another day, month, year ...
66
+ #
67
+ # The pre-hash inputs are an amalgam of the below example data.
68
+ #
69
+ # Mac Address => 20cf3067dec3
70
+ # Parent PID => 5817
71
+ # Machine Host => data-cruncher
72
+ # Session Time => 18083.0310.41.796577366
73
+ #
74
+ # @return [String]
75
+ # a one line textual shell identity string
76
+ def self.derive_shell_identity_string
77
+
78
+ require 'socket'
79
+
80
+ calling_module = File.basename caller_locations(1,1).first.absolute_path, ".rb"
81
+ calling_method = caller_locations(1,1).first.base_label
82
+ calling_lineno = caller_locations(1,1).first.lineno
83
+ caller_details = "#{calling_module} | #{calling_method} | (line #{calling_lineno})"
84
+
85
+ log.info(x) { "### #####################################################################" }
86
+ log.info(x) { "### Caller Details =>> =>> #{caller_details}" }
87
+ log.info(x) { "### #####################################################################" }
88
+
89
+
90
+ log.info(x) { "Etc.getlogin() => #{Etc.getlogin()}" }
91
+ log.info(x) { "Socket.gethostname() => #{Socket.gethostname()}" }
92
+ log.info(x) { "get_net_address_digits() => #{get_net_address_digits()}" }
93
+ log.info(x) { "derive_network_identity() => #{derive_network_identity()}" }
94
+ log.info(x) { "Process.ppid.to_s() => #{Process.ppid.to_s()}" }
95
+
96
+
97
+ # Do not change the order of this data because it is reversed and
98
+ # the parent's shell ID hotly followed by the network identifier are
99
+ # the most significant data points.
100
+ identity_text = [
101
+ Etc.getlogin(),
102
+ Socket.gethostname(),
103
+ get_net_address_digits(),
104
+ derive_network_identity(),
105
+ Process.ppid.to_s()
106
+ ].join.reverse
107
+
108
+ return identity_text
109
+
110
+ end
111
+
112
+
113
+ # This method uses a one-way function to return a combinatorial digested
114
+ # machine identification string using a number of distinct input parameters
115
+ # to deliver the characteristic of producing the same identifier for the
116
+ # same machine, virtual machine, workstation and/or compute element, and
117
+ # reciprocally, a different one on a different machine.
118
+ #
119
+ # The userspace is also a key machine identifier so a different machine user
120
+ # generates a different identifier when all other things remain equal.
121
+ #
122
+ # Mac Address => 20cf3067dec3
123
+ # Machine User => joebloggs
124
+ # Machine Host => data-cruncher
125
+ # IP Addresses => w.x.y.z + a.b.c.d
126
+ #
127
+ # @return [String]
128
+ # a one line textual machine workstation or compute element identifier
129
+ # that is (surprisingly) different when the machine user changes.
130
+ def self.derive_machine_identity_string
131
+
132
+ require 'socket'
133
+
134
+ identity_text = [
135
+ Etc.getlogin,
136
+ Socket.gethostname(),
137
+ get_net_address_digits(),
138
+ derive_network_identity()
139
+ ].join.reverse
140
+
141
+ return identity_text
142
+
143
+ end
144
+
145
+
146
+ # If the system was rebooted on April 23rd, 2018 at 22:00:16 we
147
+ # expect this method not to return <b>2018-04-23 22:00:16</b>, but
148
+ # to return the <b>8 least significant digits</b> bootup time
149
+ # digits which in this case are <b>23220016</b>.
150
+ #
151
+ # Investigate all Linux flavours to understand whether this command
152
+ # works (or is it just Ubuntu). Also does Docker return a sensible
153
+ # value here?
154
+ #
155
+ # This method is not production ready. Not only is the time within
156
+ # a small range, also the most significant digit can fluctuate up
157
+ # or down randomly (in a non-deterministic manner.
158
+ #
159
+ # @return [String] the time when the system was booted.
160
+ def self.get_bootup_time_digits
161
+
162
+ boot_time_cmd = "uptime -s"
163
+ uptime_string = %x[ #{boot_time_cmd} ]
164
+ return uptime_string.to_alphanumeric[ 6 .. -1 ]
165
+
166
+ end
167
+
168
+
169
+ # This method will return the first readable non loopback (127.0.0.1)
170
+ # IP address if the bolean {ip_address_readable?} returns true.
171
+ #
172
+ # @return [String]
173
+ # the first sensible non-loopback IP address which we know to exist.
174
+ #
175
+ # @raise RuntimeError if the IP address that we have on good authority exists, does not.
176
+ def self.get_net_address_digits
177
+
178
+ return "mT4Lq8DsG-x=@/y(_9A:]r" unless ip_address_readable?()
179
+
180
+ ip_addresses = get_address_list()
181
+ ip_addresses.each do |candidate_address|
182
+ return candidate_address.to_alphanumeric unless candidate_address.eql?( "127.0.0.1" )
183
+ end
184
+ raise RuntimeError, "Was led to expect at least one readable IP address."
185
+
186
+ end
187
+
188
+
189
+ # Return a string that is the same if the logical underlying machine the
190
+ # software is running on is the same - and different if it is different.
191
+ #
192
+ # The current implementation ciphens off the last 12 characters of the gnerated
193
+ # UUID and this is guaranteed to remain constant in a manner required by
194
+ # <b>license generating tools</b>.
195
+ #
196
+ # Within a Docker container this behaviour will have to be observed
197
+ # carefully - especially when Docker Compose type tools are used
198
+ # and the container persists across the host machine reboots.
199
+ def self.derive_network_identity
200
+
201
+ require 'uuid'
202
+
203
+ the_uuid = UUID.new.generate
204
+
205
+ too_short = the_uuid.length <= NETWORK_ID_LENGTH
206
+ raise RuntimeError, "Unexpected UUID format [#{the_uuid}]" if too_short
207
+
208
+ net_id_chunk = the_uuid[ (the_uuid.length - NETWORK_ID_LENGTH) .. -1 ]
209
+ perfect_size = net_id_chunk.length == NETWORK_ID_LENGTH
210
+ size_err_msg = "Expected [ #{net_id_chunk} ] net ID length of #{NETWORK_ID_LENGTH}."
211
+ raise RuntimeError,size_err_msg unless perfect_size
212
+
213
+ return net_id_chunk
214
+
215
+ end
216
+
217
+
218
+
219
+ private
220
+
221
+
222
+
223
+ NETWORK_ID_LENGTH = 12
224
+
225
+
226
+ def self.ip_address_readable?
227
+ ip_addresses = get_address_list()
228
+ no_addresses = ip_addresses.length == 0 || ( ip_addresses.length == 1 && ip_addresses[0] == "127.0.0.1" )
229
+ return false if no_addresses
230
+ ip_addresses.each do |candidate_address|
231
+ return true unless candidate_address.eql?( "127.0.0.1" )
232
+ end
233
+ return false
234
+ end
235
+
236
+
237
+ def self.get_address_list
238
+ multipleAddresses = Socket.ip_address_list
239
+ stringAddressText = multipleAddresses.to_s
240
+ ipAddressRegEx = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
241
+ return stringAddressText.scan( ipAddressRegEx );
242
+ end
243
+
244
+
245
+ end
246
+
247
+
248
+ end