safedb 0.01.0001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +793 -0
  7. data/Rakefile +16 -0
  8. data/bin/safe +5 -0
  9. data/lib/configs/README.md +58 -0
  10. data/lib/extension/array.rb +162 -0
  11. data/lib/extension/dir.rb +35 -0
  12. data/lib/extension/file.rb +123 -0
  13. data/lib/extension/hash.rb +33 -0
  14. data/lib/extension/string.rb +572 -0
  15. data/lib/factbase/facts.safedb.net.ini +38 -0
  16. data/lib/interprete.rb +462 -0
  17. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  18. data/lib/keytools/kdf.api.rb +243 -0
  19. data/lib/keytools/kdf.bcrypt.rb +265 -0
  20. data/lib/keytools/kdf.pbkdf2.rb +262 -0
  21. data/lib/keytools/kdf.scrypt.rb +190 -0
  22. data/lib/keytools/key.64.rb +326 -0
  23. data/lib/keytools/key.algo.rb +109 -0
  24. data/lib/keytools/key.api.rb +1391 -0
  25. data/lib/keytools/key.db.rb +330 -0
  26. data/lib/keytools/key.docs.rb +195 -0
  27. data/lib/keytools/key.error.rb +110 -0
  28. data/lib/keytools/key.id.rb +271 -0
  29. data/lib/keytools/key.ident.rb +243 -0
  30. data/lib/keytools/key.iv.rb +107 -0
  31. data/lib/keytools/key.local.rb +259 -0
  32. data/lib/keytools/key.now.rb +402 -0
  33. data/lib/keytools/key.pair.rb +259 -0
  34. data/lib/keytools/key.pass.rb +120 -0
  35. data/lib/keytools/key.rb +585 -0
  36. data/lib/logging/gem.logging.rb +132 -0
  37. data/lib/modules/README.md +43 -0
  38. data/lib/modules/cryptology/aes-256.rb +154 -0
  39. data/lib/modules/cryptology/amalgam.rb +70 -0
  40. data/lib/modules/cryptology/blowfish.rb +130 -0
  41. data/lib/modules/cryptology/cipher.rb +207 -0
  42. data/lib/modules/cryptology/collect.rb +138 -0
  43. data/lib/modules/cryptology/crypt.io.rb +225 -0
  44. data/lib/modules/cryptology/engineer.rb +99 -0
  45. data/lib/modules/mappers/dictionary.rb +288 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/modules/storage/git.store.rb +399 -0
  48. data/lib/session/fact.finder.rb +334 -0
  49. data/lib/session/require.gem.rb +112 -0
  50. data/lib/session/time.stamp.rb +340 -0
  51. data/lib/session/user.home.rb +49 -0
  52. data/lib/usecase/cmd.rb +487 -0
  53. data/lib/usecase/config/README.md +57 -0
  54. data/lib/usecase/docker/README.md +146 -0
  55. data/lib/usecase/docker/docker.rb +49 -0
  56. data/lib/usecase/edit/README.md +43 -0
  57. data/lib/usecase/edit/delete.rb +46 -0
  58. data/lib/usecase/export.rb +40 -0
  59. data/lib/usecase/files/README.md +37 -0
  60. data/lib/usecase/files/eject.rb +56 -0
  61. data/lib/usecase/files/file_me.rb +78 -0
  62. data/lib/usecase/files/read.rb +169 -0
  63. data/lib/usecase/files/write.rb +89 -0
  64. data/lib/usecase/goto.rb +57 -0
  65. data/lib/usecase/id.rb +36 -0
  66. data/lib/usecase/import.rb +157 -0
  67. data/lib/usecase/init.rb +63 -0
  68. data/lib/usecase/jenkins/README.md +146 -0
  69. data/lib/usecase/jenkins/jenkins.rb +208 -0
  70. data/lib/usecase/login.rb +71 -0
  71. data/lib/usecase/logout.rb +28 -0
  72. data/lib/usecase/open.rb +71 -0
  73. data/lib/usecase/print.rb +40 -0
  74. data/lib/usecase/put.rb +81 -0
  75. data/lib/usecase/set.rb +44 -0
  76. data/lib/usecase/show.rb +138 -0
  77. data/lib/usecase/terraform/README.md +91 -0
  78. data/lib/usecase/terraform/terraform.rb +121 -0
  79. data/lib/usecase/token.rb +35 -0
  80. data/lib/usecase/update/README.md +55 -0
  81. data/lib/usecase/update/rename.rb +180 -0
  82. data/lib/usecase/use.rb +41 -0
  83. data/lib/usecase/verse.rb +20 -0
  84. data/lib/usecase/view.rb +71 -0
  85. data/lib/usecase/vpn/README.md +150 -0
  86. data/lib/usecase/vpn/vpn.ini +31 -0
  87. data/lib/usecase/vpn/vpn.rb +54 -0
  88. data/lib/version.rb +3 -0
  89. data/safedb.gemspec +34 -0
  90. metadata +193 -0
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+
5
+ module SafeDb
6
+
7
+
8
+ # This class derives <b>non secret but unique identifiers</b> based on different
9
+ # combinations of the <b>application, shell and machine (compute element)</b>
10
+ # references.
11
+ #
12
+ # == Identifier Are Not Secrets
13
+ #
14
+ # <b>And their starting values are retrievable</b>
15
+ #
16
+ # Note that the principle and practise of <b>identifiers is not about keeping secrets</b>.
17
+ # An identifier can easily give up its starting value/s if and when brute force is
18
+ # applied. The properties of a good iidentifier (ID) are
19
+ #
20
+ # - non repeatability (also known as uniqueness)
21
+ # - non predictability (of the next identifier)
22
+ # - containing alphanumerics (for file/folder/url names)
23
+ # - human readable (hence hyphens and separators)
24
+ # - non offensive (no swear words popping out)
25
+ #
26
+ # == Story | Identifiers Speak Volumes
27
+ #
28
+ # I told a friend what the turnover of his company was and how many clients he had.
29
+ # He was shocked and wanted to know how I had gleened this information.
30
+ #
31
+ # The invoices he sent me (a year apart). Both his invoice IDs (identifiers) and his
32
+ # user IDs where integers that counted up. So I could determine how many new clients
33
+ # he had in the past year, how many clients he had when I got the invoice, and I
34
+ # determined the turnover by guesstimating the average invoice amount.
35
+ #
36
+ # Many successful website attacks are owed to a predictable customer ID or a counter
37
+ # type session ID within the cookies.
38
+ #
39
+ # == Good Identifiers Need Volumes
40
+ #
41
+ # IDs are not secrets - but even so, a large number of properties are required
42
+ # to produce a high quality ID.
43
+ #
44
+ class KeyId
45
+
46
+
47
+ # The identity chunk length is set at four (4) which means each of the
48
+ # fabricated identifiers comprises of four character segments divided by
49
+ # hyphens. Only the <b>62 alpha-numerics ( a-z, A-Z and 0-9 )</b> will
50
+ # appear within identifiers - which maintains simplicity and provides an
51
+ # opportunity to re-iterate that <b>identifiers</b> are designed to be
52
+ # <b>unpredictable</b>, but <b>not secret</b>.
53
+ IDENTITY_CHUNK_LENGTH = 4
54
+
55
+
56
+ # A hyphen is the chosen character for dividing the identifier strings
57
+ # into chunks of four (4) as per the {IDENTITY_CHUNK_LENGTH} constant.
58
+ SEGMENT_CHAR = "-"
59
+
60
+
61
+ # Get an identifier that is <b>always the same</b> for the parameter
62
+ # application reference <b>regardless of the machine or shell</b> or
63
+ # even the machine user, coming together to make the request.
64
+ #
65
+ # The returned identifier will consist only of alphanumeric characters
66
+ # and one hyphen, plus it always starts and ends with an alphanumeric.
67
+ #
68
+ # @param app_instance_ref [String]
69
+ # the string reference of the application instance (or shard) that
70
+ # is in play and needs to be digested into a unique but not-a-secret
71
+ # identifier.
72
+ #
73
+ # @return [String]
74
+ # An identifier that is guaranteed to be the same whenever the
75
+ # same application reference is provided on any machine, using any
76
+ # user through any shell interface or command prompt.
77
+ #
78
+ # It must be different for any other application reference.
79
+ def self.derive_app_instance_identifier( app_instance_ref )
80
+ return derive_identifier( app_instance_ref )
81
+ end
82
+
83
+
84
+ # Get an identifier that is <b>always the same</b> for the application
85
+ # instance (with reference given in parameter) on <b>this machine</b>
86
+ # and is always different when either/or or both the application ref
87
+ # and machine are different.
88
+ #
89
+ # The returned identifier will consist of only alphanumeric characters
90
+ # and hyphens - it will always start and end with an alphanumeric.
91
+ #
92
+ # This behaviour draws a fine line around the concept of machine, virtual
93
+ # machine, <b>workstation</b> and/or <b>compute element</b>.
94
+ #
95
+ # <b>(aka) The AIM ID</b>
96
+ #
97
+ # Returned ID is aka the <b>Application Instance Machine (AIM)</b> Id.
98
+ #
99
+ # @param app_ref [String]
100
+ # the string reference of the application instance (or shard) that
101
+ # is being used.
102
+ #
103
+ # @return [String]
104
+ # an identifier that is guaranteed to be the same whenever the
105
+ # same application reference is provided on this machine.
106
+ #
107
+ # it must be different on another machine even when the same
108
+ # application reference is provided.
109
+ #
110
+ # It will also be different on this workstation if the application
111
+ # instance identifier provided is different.
112
+ def self.derive_app_instance_machine_id( app_ref )
113
+ return derive_identifier( app_ref + KeyIdent.derive_machine_identifier() )
114
+ end
115
+
116
+
117
+ # The <b>32 character</b> <b>universal identifier</b> bonds a digested
118
+ # <b>application state identifier</b> with the <b>shell identifier</b>.
119
+ # This method gives <b>dual double guarantees</b> to the effect that
120
+ #
121
+ # - a change in one, or in the other, or in both returns a different universal id
122
+ # - the same app state identifier in the same shell produces the same universal id
123
+ #
124
+ # <b>The 32 Character Universal Identifier</b>
125
+ #
126
+ # The universal identifier is an amalgam of two digests which can be individually
127
+ # retrieved from other methods in this class. An example is
128
+ #
129
+ # universal id => hg2x0-g3uslf-pa2bl5-09xvbd-n4wcq
130
+ # the shell id => g3uslf-pa2bl5-09xvbd
131
+ # app state id => hg2x0-n4wcq
132
+ #
133
+ # The 32 character universal identifier comprises of 18 session identifier
134
+ # characters (see {derive_session_id}) <b>sandwiched between</b>
135
+ # ten (10) digested application identifier characters, five (5) in front and
136
+ # five (5) at the back - all segmented by four (4) hyphens.
137
+ #
138
+ # @param app_reference [String]
139
+ # the chosen plaintext application reference identifier that
140
+ # is the input to the digesting (hashing) algorithm.
141
+ #
142
+ # @param session_token [String]
143
+ # a triply segmented (and one liner) text token instantiated by
144
+ # {KeyLocal.generate_shell_key_and_token} and provided
145
+ # here ad verbatim.
146
+ #
147
+ # @return [String]
148
+ # a 32 character string that cannot feasibly be repeated due to the use
149
+ # of one way functions within its derivation. The returned identifier bonds
150
+ # the application state reference with the present session.
151
+ def self.derive_universal_id( app_reference, session_token )
152
+
153
+ shellid = derive_session_id( session_token )
154
+ app_ref = derive_identifier( app_reference + shellid )
155
+ chunk_1 = app_ref[ 0 .. IDENTITY_CHUNK_LENGTH ]
156
+ chunk_3 = app_ref[ ( IDENTITY_CHUNK_LENGTH + 1 ) .. -1 ]
157
+
158
+ return "#{chunk_1}#{shellid}#{SEGMENT_CHAR}#{chunk_3}".downcase
159
+
160
+ end
161
+
162
+
163
+ # The session ID generated here is a derivative of the 150 character
164
+ # session token instantiated by {KeyLocal.generate_shell_key_and_token}
165
+ # and provided here <b>ad verbatim</b>.
166
+ #
167
+ # The algorithm for deriving the session ID is as follows.
168
+ #
169
+ # - convert the 150 characters to an alphanumeric string
170
+ # - convert the result to a bit string and then to a key
171
+ # - put the key's binary form through a 384 bit digest
172
+ # - convert the digest's output to 64 YACHT64 characters
173
+ # - remove the (on average 2) non-alphanumeric characters
174
+ # - cherry pick a spread out 12 characters from the pool
175
+ # - hiphenate the character positions five (5) and ten (10)
176
+ # - ensure the length of the resultant ID is fourteen (14)
177
+ #
178
+ # The resulting session id will look something like this
179
+ #
180
+ # g3sf-pab5-9xvd
181
+ #
182
+ # @param session_token [String]
183
+ # a triply segmented (and one liner) text token instantiated by
184
+ # {KeyLocal.generate_shell_key_and_token} and provided here ad
185
+ # verbatim.
186
+ #
187
+ # @return [String]
188
+ # a 14 character string that cannot feasibly be repeated
189
+ # within the keyspace of even a gigantic organisation.
190
+ #
191
+ # This method guarantees that the session id will always be the same when
192
+ # called by commands within the same shell in the same machine.
193
+ def self.derive_session_id( session_token )
194
+
195
+ assert_session_token_size( session_token )
196
+ random_length_id_key = Key.from_char64( session_token.to_alphanumeric )
197
+ a_384_bit_key = random_length_id_key.to_384_bit_key()
198
+ a_64_char_str = a_384_bit_key.to_char64()
199
+ base_64_chars = a_64_char_str.to_alphanumeric
200
+
201
+ id_chars_pool = KeyAlgo.cherry_picker( ID_TRI_CHUNK_LEN, base_64_chars )
202
+ id_hyphen_one = id_chars_pool.insert( IDENTITY_CHUNK_LENGTH, SEGMENT_CHAR )
203
+ id_characters = id_hyphen_one.insert( ( IDENTITY_CHUNK_LENGTH * 2 + 1 ), SEGMENT_CHAR )
204
+
205
+ err_msg = "Shell ID needs #{ID_TRI_TOTAL_LEN} not #{id_characters.length} characters."
206
+ raise RuntimeError, err_msg unless id_characters.length == ID_TRI_TOTAL_LEN
207
+
208
+ return id_characters.downcase
209
+
210
+ end
211
+
212
+
213
+ # This method returns a <b>10 character</b> digest of the parameter
214
+ # <b>reference</b> string.
215
+ #
216
+ # <b>How to Derive the 10 Character Identifier</b>
217
+ #
218
+ # So how are the 10 characters derived from the reference provided in
219
+ # the first parameter. The algorithm is this.
220
+ #
221
+ # - reverse the reference and feed it to a 256 bit digest
222
+ # - chop away the rightmost digits so that 252 bits are left
223
+ # - convert the one-zero bit str to 42 (YACHT64) characters
224
+ # - remove the (on average 1.5) non-alphanumeric characters
225
+ # - cherry pick and return <b>spread out 8 characters</b>
226
+ #
227
+ # @param reference [String]
228
+ # the plaintext reference input to the digest algorithm
229
+ #
230
+ # @return [String]
231
+ # a 10 character string that is a digest of the reference string
232
+ # provided in the parameter.
233
+ def self.derive_identifier( reference )
234
+
235
+ bitstr_256 = Key.from_binary( Digest::SHA256.digest( reference.reverse ) ).to_s
236
+ bitstr_252 = bitstr_256[ 0 .. ( BIT_LENGTH_252 - 1 ) ]
237
+ id_err_msg = "The ID digest needs #{BIT_LENGTH_252} not #{bitstr_252.length} chars."
238
+ raise RuntimeError, id_err_msg unless bitstr_252.length == BIT_LENGTH_252
239
+
240
+ id_chars_pool = Key64.from_bits( bitstr_252 ).to_alphanumeric
241
+ undivided_str = KeyAlgo.cherry_picker( ID_TWO_CHUNK_LEN, id_chars_pool )
242
+ id_characters = undivided_str.insert( IDENTITY_CHUNK_LENGTH, SEGMENT_CHAR )
243
+
244
+ min_size_msg = "Id length #{id_characters.length} is not #{(ID_TWO_CHUNK_LEN + 1)} chars."
245
+ raise RuntimeError, min_size_msg unless id_characters.length == ( ID_TWO_CHUNK_LEN + 1 )
246
+
247
+ return id_characters.downcase
248
+
249
+ end
250
+
251
+
252
+ private
253
+
254
+
255
+ ID_TWO_CHUNK_LEN = IDENTITY_CHUNK_LENGTH * 2
256
+ ID_TRI_CHUNK_LEN = IDENTITY_CHUNK_LENGTH * 3
257
+ ID_TRI_TOTAL_LEN = ID_TRI_CHUNK_LEN + 2
258
+
259
+ BIT_LENGTH_252 = 252
260
+
261
+
262
+ def self.assert_session_token_size session_token
263
+ err_msg = "Session token has #{session_token.length} and not #{KeyLocal::SESSION_TOKEN_SIZE} chars."
264
+ raise RuntimeError, err_msg unless session_token.length == KeyLocal::SESSION_TOKEN_SIZE
265
+ end
266
+
267
+
268
+ end
269
+
270
+
271
+ end
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module SafeDb
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
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/ruby
2
+
3
+ module SafeDb
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