safedb 0.01.0001

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 (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