opensecret 0.0.9925 → 0.0.9949

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +656 -40
  3. data/lib/configs/README.md +58 -0
  4. data/lib/extension/file.rb +67 -0
  5. data/lib/extension/string.rb +10 -0
  6. data/lib/factbase/facts.opensecret.io.ini +1 -0
  7. data/lib/interprete.rb +334 -61
  8. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  9. data/lib/keytools/kdf.api.rb +9 -15
  10. data/lib/keytools/kdf.bcrypt.rb +69 -19
  11. data/lib/keytools/kdf.pbkdf2.rb +112 -23
  12. data/lib/keytools/key.api.rb +146 -36
  13. data/lib/keytools/key.db.rb +94 -29
  14. data/lib/keytools/key.id.rb +1 -1
  15. data/lib/keytools/key.ident.rb +243 -0
  16. data/lib/keytools/key.local.rb +62 -68
  17. data/lib/keytools/key.pass.rb +2 -2
  18. data/lib/keytools/key.rb +2 -28
  19. data/lib/modules/{cryptology.md → README.md} +0 -0
  20. data/lib/session/fact.finder.rb +65 -428
  21. data/lib/session/time.stamp.rb +1 -28
  22. data/lib/usecase/cmd.rb +127 -54
  23. data/lib/usecase/config/README.md +57 -0
  24. data/lib/usecase/docker/README.md +146 -0
  25. data/lib/usecase/docker/docker.rb +49 -0
  26. data/lib/usecase/edit/README.md +43 -0
  27. data/lib/usecase/edit/delete.rb +46 -0
  28. data/lib/usecase/export.rb +40 -0
  29. data/lib/usecase/files/README.md +37 -0
  30. data/lib/usecase/files/eject.rb +56 -0
  31. data/lib/usecase/files/file_me.rb +78 -0
  32. data/lib/usecase/files/read.rb +169 -0
  33. data/lib/usecase/files/write.rb +89 -0
  34. data/lib/usecase/goto.rb +57 -0
  35. data/lib/usecase/id.rb +1 -1
  36. data/lib/usecase/import.rb +13 -30
  37. data/lib/usecase/init.rb +2 -17
  38. data/lib/usecase/jenkins/README.md +146 -0
  39. data/lib/usecase/jenkins/crazy_ruby_post_attempt.OLD +234 -0
  40. data/lib/usecase/jenkins/jenkins.rb +208 -0
  41. data/lib/usecase/login.rb +6 -5
  42. data/lib/usecase/logout.rb +1 -3
  43. data/lib/usecase/open.rb +11 -66
  44. data/lib/usecase/print.rb +40 -0
  45. data/lib/usecase/put.rb +34 -156
  46. data/lib/usecase/set.rb +2 -4
  47. data/lib/usecase/show.rb +138 -0
  48. data/lib/usecase/terraform/README.md +91 -0
  49. data/lib/usecase/terraform/terraform.rb +121 -0
  50. data/lib/usecase/token.rb +4 -80
  51. data/lib/usecase/update/README.md +55 -0
  52. data/lib/usecase/update/rename.rb +180 -0
  53. data/lib/usecase/use.rb +1 -3
  54. data/lib/usecase/verse.rb +20 -0
  55. data/lib/usecase/view.rb +71 -0
  56. data/lib/usecase/vpn/README.md +150 -0
  57. data/lib/usecase/vpn/vpn.ini +31 -0
  58. data/lib/usecase/vpn/vpn.rb +54 -0
  59. data/lib/version.rb +1 -1
  60. data/opensecret.gemspec +3 -4
  61. metadata +34 -35
  62. data/.travis.yml +0 -5
  63. data/CODE_OF_CONDUCT.md +0 -74
  64. data/LICENSE.txt +0 -21
  65. data/bin/ops +0 -20
  66. data/lib/keytools/binary.map.rb +0 -294
  67. data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +0 -179
  68. data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +0 -190
  69. data/lib/keytools/doc.star.schema.strategy.txt +0 -77
  70. data/lib/keytools/doc.using.pbkdf2.kdf.ruby +0 -95
  71. data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +0 -266
  72. data/lib/keytools/key.mach.rb +0 -248
  73. data/lib/keytools/keydebug.txt +0 -295
  74. data/lib/modules/cryptology/open.bcrypt.rb +0 -170
  75. data/lib/usecase/read.rb +0 -89
  76. data/lib/usecase/safe.rb +0 -92
@@ -28,9 +28,8 @@ module OpenKey
28
28
  # == How to Create the Encryption Key
29
29
  #
30
30
  # To create a high entropy encryption key this method takes the first
31
- # 168 bits from the 186 bit BCrypt key produced by {KdfBCrypt} and
32
- # the first 96 bits from the 132 bit PBKDF2 key produced inside the
33
- # {KeyPbkdf2} class and amalgamates them to produce a 264 bit key.
31
+ # 168 bits from the 186 bit BCrypt key and the first 96 bits from the
32
+ # 132 bit PBKDF2 key and amalgamates them to produce a 264 bit key.
34
33
  #
35
34
  # The 264 bit key is then digested to produce a 256bit encryption key.
36
35
  class KdfApi
@@ -51,7 +50,7 @@ module OpenKey
51
50
 
52
51
 
53
52
  # To create a high entropy encryption key we use the full 180 bits
54
- # from the returned 180 bit BCrypt key produced by {KdfBCrypt}.
53
+ # from the returned 180 bit BCrypt key.
55
54
  #
56
55
  # When amalgamated with the <b>332 bits from the PBKDF2 Key</b> we
57
56
  # achieve a powerful <b>union key length</b> of 512 bits.
@@ -67,8 +66,8 @@ module OpenKey
67
66
 
68
67
 
69
68
  # To create a high entropy encryption key we use the full 180 bits
70
- # from the returned 180 bit BCrypt key produced by {KdfBCrypt} and
71
- # the first 332 bits from the 384 bit key returned by PBKDF2.
69
+ # from the returned 180 bit BCrypt key and the first 332 bits from
70
+ # the 384 bit PBKDF2 key.
72
71
  #
73
72
  # On amalgamation, the outcome is a quality <b>union key length</b>
74
73
  # of <b>512 bits</b>.
@@ -107,19 +106,14 @@ module OpenKey
107
106
  # that exists to convert <b>low entropy</b> human generated passwords into a high
108
107
  # entropy key that is computationally infeasible to acquire through brute force.
109
108
  #
110
- # To create a high entropy encryption key we use the full 180 bits
111
- # from the returned 180 bit BCrypt key produced by {KdfBCrypt} and
112
- # the first 332 bits from the 384 bit key returned by PBKDF2.
113
- #
114
109
  # On amalgamation, the outcome is a quality <b>union key length</b>
115
110
  # of <b>512 bits</b>.
116
111
  #
117
112
  # == Creating a High Entropy Encryption Key
118
113
  #
119
114
  # To create a high entropy encryption key this method takes the first
120
- # 168 bits from the 186 bit BCrypt key produced by {KdfBCrypt} and
121
- # the first 96 bits from the 132 bit PBKDF2 key produced inside the
122
- # {KeyPbkdf2} class and amalgamates them to produce a 264 bit key.
115
+ # 168 bits from the 186 bit BCrypt and the first 96 bits from the 132
116
+ # bit PBKDF2 key and amalgamates them to produce a 264 bit key.
123
117
  #
124
118
  # Note that all four of the above numbers are divisable by six (6), for
125
119
  # representation with a 64 character set, and eight (8), for transport
@@ -206,8 +200,8 @@ module OpenKey
206
200
 
207
201
  def self.derive_and_amalgamate( human_secret, bcrypt_salt, pbkdf2_salt )
208
202
 
209
- bcrypt_key = OpenKey::KdfBCrypt.generate_key( human_secret, bcrypt_salt )
210
- pbkdf2_key = OpenKey::KeyPbkdf2.generate_key( human_secret.reverse, pbkdf2_salt )
203
+ bcrypt_key = KdfBCrypt.generate_key( human_secret, bcrypt_salt )
204
+ pbkdf2_key = KeyPbkdf2.generate_key( human_secret.reverse, pbkdf2_salt )
211
205
 
212
206
  assert_bcrypt_key_bit_length bcrypt_key
213
207
  assert_pbkdf2_key_bit_length pbkdf2_key
@@ -3,7 +3,6 @@
3
3
 
4
4
  module OpenKey
5
5
 
6
-
7
6
  # BCrypt is a <b>Blowfish based Key Derivation Function (KDF)</b> that exists to
8
7
  # convert <b>low entropy</b> human created passwords into a high entropy key that
9
8
  # is computationally infeasible to acquire through brute force.
@@ -24,6 +23,25 @@ module OpenKey
24
23
  # <b>A cost of 16 will result in 2^16 = 65,536 iterations</b> and will slow the
25
24
  # derivation time to about a second on a powerful 2020 laptop.
26
25
  #
26
+ # == BCrypt Cost Iteration Timings on an Intel i-5 Laptop
27
+ #
28
+ # The benchmark timings were incredibly consistent and
29
+ # took almost exactly twice as long for every step.
30
+ #
31
+ # An IBM ThinkPad was used to generate the timings.
32
+ #
33
+ # Memory RAM ~> 15GiB
34
+ # Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
35
+ #
36
+ # The timing results (for 2 steps) multiplied by four (4).
37
+ #
38
+ # 3.84 seconds for 2^16 (65,536) iterations
39
+ # 0.96 seconds for 2^14 (16,384) iterations
40
+ # 0.24 seconds for 2^12 ( 4,096) iterations
41
+ # 0.06 seconds for 2^10 ( 1,024) iterations
42
+ #
43
+ # A double digit iteration cost must be provided to avoid
44
+ # an in-built failure trap. The default cost is now 10.
27
45
  class KdfBCrypt
28
46
 
29
47
  require "bcrypt"
@@ -32,13 +50,31 @@ module OpenKey
32
50
  # two so if the iteration integer is 12 there will be two
33
51
  # to the power of 12 ( 2^12 ) giving 4096 iterations.
34
52
  # The minimum number is 4 (16 iterations) and the max is 31.
53
+ #
35
54
  # @example
36
55
  # Configuring 16 into this directive results in
37
56
  # 2^16 = 65,536 iterations
38
57
  #
39
- # This is a safe default and will slow the derivation time
40
- # to about a second on a powerful 2020 laptop.
41
- BCRYPT_ITERATION_INTEGER = 16
58
+ # == BCrypt Cost Iteration Timings on an Intel i-5 Laptop
59
+ #
60
+ # The benchmark timings were incredibly consistent and
61
+ # took almost exactly twice as long for every step.
62
+ #
63
+ # An IBM ThinkPad was used to generate the timings.
64
+ #
65
+ # Memory RAM ~> 15GiB
66
+ # Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
67
+ #
68
+ # The timing results (for 2 steps) multiplied by four (4).
69
+ #
70
+ # 3.84 seconds for 2^16 (65,536) iterations
71
+ # 0.96 seconds for 2^14 (16,384) iterations
72
+ # 0.24 seconds for 2^12 ( 4,096) iterations
73
+ # 0.06 seconds for 2^10 ( 1,024) iterations
74
+ #
75
+ # A double digit iteration cost must be provided to avoid
76
+ # an in-built failure trap. The default cost is now 10.
77
+ BCRYPT_ITERATION_INTEGER = 10
42
78
 
43
79
  # The bcrypt algorithm produces a key that is 181 bits in
44
80
  # length. The algorithm then converts the binary 181 bits
@@ -46,13 +82,11 @@ module OpenKey
46
82
  #
47
83
  # 181 / 6 = 30 remainder 1 (so 31 characters are needed).
48
84
  BCRYPT_KEY_LENGTH = 31
49
-
50
85
 
51
86
  # BCrypt key derivation (from text) implementations truncate
52
87
  # the first 55 characters of the incoming text.
53
88
  BCRYPT_MAX_IN_TEXT_LENGTH = 55
54
89
 
55
-
56
90
  # The BCrypt algorithm produces 181 raw binary bits which is just
57
91
  # one bit more than a 30 character base64 string. Hence the algorithm
58
92
  # puts out 31 characters.
@@ -60,7 +94,6 @@ module OpenKey
60
94
  # We discard the 31st character because 5 of its 6 bits are 100%
61
95
  # predictable. Thus the returned key will contribute 180 bits.
62
96
  BCRYPT_KEY_EXPORT_BIT_LENGTH = 180
63
-
64
97
 
65
98
  # The BCrypt algorithm salt string should be 22 characters
66
99
  # and may include forward slashes and periods.
@@ -134,15 +167,15 @@ module OpenKey
134
167
  # same form to the {generate_key} method.
135
168
  def self.generate_bcrypt_salt
136
169
 
137
- the_salt_str = BCrypt::Engine.generate_salt( BCRYPT_ITERATION_INTEGER )
138
- bcrypt_salt = the_salt_str[ BCRYPT_OUTPUT_TEXT_PREFIX.length .. -1 ]
139
- assert_bcrypt_salt bcrypt_salt
140
- return bcrypt_salt
170
+ full_bcrypt_salt = BCrypt::Engine.generate_salt( BCRYPT_ITERATION_INTEGER )
171
+ main_bcrypt_salt = full_bcrypt_salt[ BCRYPT_OUTPUT_TEXT_PREFIX.length .. -1 ]
172
+ keep_bcrypt_salt = "#{BCRYPT_ITERATION_INTEGER}#{main_bcrypt_salt}"
173
+ assert_bcrypt_salt( keep_bcrypt_salt )
174
+ return keep_bcrypt_salt
141
175
 
142
176
  end
143
177
 
144
178
 
145
-
146
179
  # Key generators should first use the {generate_salt} method to create
147
180
  # a BCrypt salt string and then submit it to this method together with
148
181
  # a human generated password in order to derive a key.
@@ -181,16 +214,18 @@ module OpenKey
181
214
  # predictable. Thus the returned key will contribute 180 bits.
182
215
  def self.generate_key human_secret, bcrypt_salt
183
216
 
217
+ iteration_int = bcrypt_salt[ 0 .. 1 ]
218
+ bcrypt_prefix = "$2x$#{iteration_int}$"
219
+ full_salt_str = bcrypt_prefix + bcrypt_salt[ 2 .. -1 ]
220
+
184
221
  assert_bcrypt_salt( bcrypt_salt )
185
- full_bcrypt_salt = BCRYPT_OUTPUT_TEXT_PREFIX + bcrypt_salt
186
- hashed_secret = BCrypt::Engine.hash_secret( human_secret, full_bcrypt_salt )
187
- encoded64_key = BCrypt::Password.new( hashed_secret ).to_s
188
222
 
223
+ hashed_secret = BCrypt::Engine.hash_secret( human_secret, full_salt_str )
224
+ encoded64_key = BCrypt::Password.new( hashed_secret ).to_s
189
225
  key_begin_index = BCRYPT_OUTPUT_TEXT_PREFIX.length + BCRYPT_SALT_LENGTH
190
226
  radix64_key_str = encoded64_key[ key_begin_index .. -1 ]
191
227
  key_length_mesg = "The BCrypt key length should have #{BCRYPT_KEY_LENGTH} characters."
192
228
  raise RuntimeError, key_length_mesg unless radix64_key_str.length == BCRYPT_KEY_LENGTH
193
-
194
229
  chopped_radix64_key = radix64_key_str.chop()
195
230
 
196
231
  return Key.from_radix64( chopped_radix64_key )
@@ -198,14 +233,29 @@ module OpenKey
198
233
  end
199
234
 
200
235
 
201
-
202
236
  private
203
237
 
204
238
 
239
+ # ---
240
+ # --- Timings Code
241
+ # ---
242
+ # --- chopped_radix64_key = NIL
243
+ # --- require 'benchmark'
244
+ # --- timings = Benchmark.measure {
245
+ # ---
246
+ # --- -- wrapped up code block
247
+ # ---
248
+ # --- }
249
+ # ---
250
+ # --- log.info(x) { "BCrypt key generation timings ~> #{timings}" }
251
+ # ---
252
+
253
+
205
254
  def self.assert_bcrypt_salt the_salt
206
255
  raise RuntimeError, "bcrypt salt not expected to be nil." if the_salt.nil?
207
- salt_length_msg = "A bcrypt salt is expected to contain #{BCRYPT_SALT_LENGTH} characters."
208
- raise RuntimeError, salt_length_msg unless the_salt.length == BCRYPT_SALT_LENGTH
256
+ bcrypt_total_length = 2 + BCRYPT_SALT_LENGTH
257
+ salt_length_msg = "BCrypt salt #{the_salt} is #{the_salt.length} and not #{bcrypt_total_length} characters."
258
+ raise RuntimeError, salt_length_msg unless the_salt.length == bcrypt_total_length
209
259
  end
210
260
 
211
261
 
@@ -25,6 +25,32 @@ module OpenKey
25
25
  # The difficulty is in detecting the operating system's C libraries that are directly
26
26
  # accessed for OpenSSL functionality. If the distinction can be made accurately, those
27
27
  # with newer libraries can reap the benefits immediately.
28
+ #
29
+ # == PBKDF2 Cost Iteration Timings on an Intel i-5 Laptop
30
+ #
31
+ # An IBM ThinkPad was used to generate the timings.
32
+ #
33
+ # Memory RAM ~> 15GiB
34
+ # Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
35
+ #
36
+ # The timing results show that a prudent value is somewhere
37
+ # between one hundred thousand and ten million iterations.
38
+ #
39
+ # 9.6 seconds for 10,000,000 ten million iterations
40
+ # 0.96 seconds for 1,000,000 one million iterations
41
+ # 0.096 seconds for 100,000 one hundred thousand iterations
42
+ #
43
+ # Open key sets iteration counts for PBKDF2 in hexadecimal and
44
+ # a valid range starts at 1 and counts up in chunks of a hundred
45
+ # thousand (100,000).
46
+ #
47
+ # 1 ~> 100,000
48
+ # 5 ~> 500,000
49
+ # 10 ~> 1,000,000
50
+ # 16 ~> 16,000,000
51
+ # 256 ~> 256,000,000
52
+ #
53
+ # The maximum iteration multiplier allowed is 16,384.
28
54
  class KeyPbkdf2
29
55
 
30
56
 
@@ -32,7 +58,31 @@ module OpenKey
32
58
  # growth of <b>GPU driven cloud based computing</b> power
33
59
  # that is curently being honed by mining BitCoin and training
34
60
  # neural networks.
35
- PBKDF2_ITERATION_COUNT = 1000000
61
+ #
62
+ # == PBKDF2 Cost Iteration Timings on an Intel i-5 Laptop
63
+ #
64
+ # An IBM ThinkPad was used to generate the timings.
65
+ #
66
+ # Memory RAM ~> 15GiB
67
+ # Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
68
+ #
69
+ # The timing results show that a prudent value is somewhere
70
+ # between one hundred thousand and ten million iterations.
71
+ #
72
+ # Open key sets iteration counts for PBKDF2 in hexadecimal and
73
+ # a valid range starts at 1 and counts up in chunks of a hundred
74
+ # thousand (100,000).
75
+ #
76
+ # 1 ~> 100,000
77
+ # 5 ~> 500,000
78
+ # 10 ~> 1,000,000
79
+ # 16 ~> 16,000,000
80
+ # 256 ~> 256,000,000
81
+ PBKDF2_ITERATION_MULTIPLIER = 1
82
+
83
+ # The quantity used to multiply the iteration multiplier by to
84
+ # gain the iteration count.
85
+ ONE_HUNDRED_THOUSAND = 100000
36
86
 
37
87
 
38
88
  # Documentation for this algorithm says this about the key length.
@@ -56,7 +106,6 @@ module OpenKey
56
106
  PBKDF2_EXPORT_BIT_LENGTH = PBKDF2_EXPORT_KEY_LENGTH * 8
57
107
 
58
108
 
59
-
60
109
  # The documented recommended salt length in bytes for the PBKDF2
61
110
  # algorithm is between <b>16 and 24 bytes</b>. The setting here is
62
111
  # at the upper bound of that range.
@@ -65,26 +114,38 @@ module OpenKey
65
114
 
66
115
  # Return a random cryptographic salt generated from twenty-four
67
116
  # random bytes produced by a secure random number generator. The
68
- # returned salt is a Base64 encoded string that can be stored
69
- # and given straight into the {KeyPbkdf2.generate_key} method.
117
+ # returned salt is primarily a Base64 encoded string that can be
118
+ # stored and then passed to the {KeyPbkdf2.generate_key} method.
119
+ #
120
+ # + ------------ + -------- + ------------ + ------------- +
121
+ # | | Bits | Bytes | Base64 |
122
+ # | ------------ | -------- | ------------ | ------------- |
123
+ # | PBKDF2 Salt | 192 Bits | 24 bytes | 32 characters |
124
+ # + ------------ + -------- + ------------ + ------------- +
125
+ #
126
+ # The leading part of the character sequence indicates the length
127
+ # of the salt in chunks of 100,000 and is plus sign separated.
70
128
  #
71
- # + ------------ + -------- + ------------ + ------------------- +
72
- # | | Bits | Bytes | Base64 |
73
- # | ------------ | -------- | ------------ | ------------------- |
74
- # | PBKDF2 Salt | 192 Bits | 24 bytes | 32 characters |
75
- # + ------------ + -------- + ------------ + ------------------- +
129
+ # 42+12345678abcdefgh12345678ABCDEFGH ~> 4,200,000 iterations
130
+ # 9+12345678abcdefgh12345678ABCDEFGH ~> 900,000 iterations
131
+ # 100+12345678abcdefgh12345678ABCDEFGH ~> 10,000,000 iterations
76
132
  #
77
- # The returned salt consists of 32 base64 characters which can be
78
- # stored and fed into generate key. Before being used, the generate
79
- # key method will convert the 32 base64 characters back into a
80
- # <b>24 byte binary</b> formatted string.
133
+ # Note that the generate key method will convert the trailing 32
134
+ # base64 characters back into a <b>24 byte binary</b> string.
81
135
  #
82
136
  # @return [String]
83
- # The returned salt consists of 32 base64 characters which can be
84
- # stored and fed into the {KeyPbkdf2.generate_key}. These characters
85
- # represent twenty-four (24) randomly and securely generated bytes.
137
+ # a relatively small iteration count multiplier separated from the
138
+ # main salt characters by a plus sign. The salt characters will
139
+ # consist of 32 base64 characters which can be stored and fed into
140
+ # the {generate_key}.
141
+ #
142
+ # These 32 characters are a representation of the twenty-four (24)
143
+ # randomly and securely generated bytes.
86
144
  def self.generate_pbkdf2_salt
87
- return Key64.from_bits( Key.to_random_bits( PBKDF2_SALT_LENGTH_BYTES ) )
145
+
146
+ pbkdf2_salt = Key64.from_bits( Key.to_random_bits( PBKDF2_SALT_LENGTH_BYTES ) )
147
+ return "#{PBKDF2_ITERATION_MULTIPLIER}+#{pbkdf2_salt}"
148
+
88
149
  end
89
150
 
90
151
 
@@ -136,9 +197,12 @@ module OpenKey
136
197
  # dictionary word or name is the way to generate a powerful key
137
198
  # that has embedded a near 100% entropy rating.
138
199
  #
139
- # @param pbkdf2_salt [String]
140
- # this parameter salt must consist of 32 base64 characters which
141
- # will be converted into a <b>24 byte binary</b> formatted string.
200
+ # @param pbkdf2_string [String]
201
+ # this is a relatively small iteration count multiplier separated
202
+ # from the main salt characters by a plus sign. The salt characters
203
+ # will consist of 32 base64 characters which can be stored and fed
204
+ # into the {generate_key}.
205
+ #
142
206
  # The salt string presented here must have either been recently
143
207
  # generated by {generate_pbkdf2salt} or read from a persistence
144
208
  # store and resubmitted here in order to regenerate the same key.
@@ -147,9 +211,16 @@ module OpenKey
147
211
  # a key holder containing the key which can then be accessed via
148
212
  # many different formats. The {Key} returned by this method
149
213
  # encapsulates the derived key with the specified byte count.
150
- def self.generate_key human_secret, pbkdf2_salt
214
+ def self.generate_key human_secret, pbkdf2_string
215
+
216
+ KeyError.not_new pbkdf2_string, "PBKDF2 Algorithm Salt"
217
+ multiplier = pbkdf2_string.split("+")[0].to_i
218
+ pbkdf2_salt = pbkdf2_string.split("+")[1]
219
+
220
+ mult_msg = "Iteration multiplier is an integer from 1 to 16,384 not [#{multiplier}]."
221
+ raise ArgumentError, mult_msg_msg unless( multiplier > 0 && multiplier < 16385 )
222
+ iteration_count = multiplier * ONE_HUNDRED_THOUSAND
151
223
 
152
- KeyError.not_new pbkdf2_salt, "PBKDF2 Algorithm Salt"
153
224
  binary_salt = Key.to_binary_from_bit_string( Key64.to_bits( pbkdf2_salt ) )
154
225
  err_msg = "Expected salt of #{PBKDF2_SALT_LENGTH_BYTES} bytes not #{binary_salt.length}."
155
226
  raise ArgumentError, err_msg unless binary_salt.length == PBKDF2_SALT_LENGTH_BYTES
@@ -157,7 +228,7 @@ module OpenKey
157
228
  pbkdf2_key = OpenSSL::PKCS5.pbkdf2_hmac(
158
229
  human_secret,
159
230
  binary_salt,
160
- PBKDF2_ITERATION_COUNT,
231
+ iteration_count,
161
232
  PBKDF2_EXPORT_KEY_LENGTH,
162
233
  OpenSSL::Digest::SHA384.new
163
234
  )
@@ -167,6 +238,24 @@ module OpenKey
167
238
  end
168
239
 
169
240
 
241
+ private
242
+
243
+
244
+ # ---
245
+ # --- Timings Code
246
+ # ---
247
+ # --- chopped_radix64_key = NIL
248
+ # --- require 'benchmark'
249
+ # --- timings = Benchmark.measure {
250
+ # ---
251
+ # --- -- wrapped up code block
252
+ # ---
253
+ # --- }
254
+ # ---
255
+ # --- log.info(x) { "PBKDF2 key generation timings ~> #{timings}" }
256
+ # ---
257
+
258
+
170
259
  end
171
260
 
172
261
 
@@ -2,6 +2,22 @@
2
2
 
3
3
  module OpenKey
4
4
 
5
+ # Use RubyMine to understand the correlations and dependencies on
6
+ # this now monolithic class that must be broken up before meaningful
7
+ # effective and efficient progress can be made.
8
+ #
9
+ # ---
10
+ #
11
+ # == REFACTOR KEY API TO DRAW OUT POSSIBLY THESE FIVE CONCEPTS.
12
+ #
13
+ # - [1] the safe tty token
14
+ # - [2] the machine configurations in ~/.config/openkey/openkey.app.config.ini
15
+ # - [3] the login / logout session crumbs database
16
+ # - [4] the master content database holding local config, chapters and verses
17
+ # - [5] the safe databases that unmarshal into either JSON or file content
18
+ #
19
+ # ---
20
+ #
5
21
  # Use the key applications programming interface to transition the
6
22
  # state of three (3) core keys in accordance with the needs of the
7
23
  # executing use case.
@@ -93,7 +109,7 @@ module OpenKey
93
109
  #
94
110
  # [srn1-apzd]
95
111
  # app.instance.id = crnl-d3my
96
- # keystore.url.id = /home/apollo/abcd/ab-motors-inc
112
+ # keystore.url.id = /home/joe/credentials/repo
97
113
  # initialize.time = Fri May 25 11:59:46 2018 ( 18145.1159.462 )
98
114
  #
99
115
  # @param domain_name [String]
@@ -540,11 +556,11 @@ module OpenKey
540
556
  # are logging out of from the shell on this machine.
541
557
  def self.do_logout( domain_name )
542
558
 
543
- # --> @todo - user should ONLY type in ops logout | without domain name
544
- # --> @todo - user should ONLY type in ops logout | without domain name
545
- # --> @todo - user should ONLY type in ops logout | without domain name
546
- # --> @todo - user should ONLY type in ops logout | without domain name
547
- # --> @todo - user should ONLY type in ops logout | without domain name
559
+ # --> @todo - user should ONLY type in logout | without domain name
560
+ # --> @todo - user should ONLY type in logout | without domain name
561
+ # --> @todo - user should ONLY type in logout | without domain name
562
+ # --> @todo - user should ONLY type in logout | without domain name
563
+ # --> @todo - user should ONLY type in logout | without domain name
548
564
 
549
565
 
550
566
  # --> ######################
@@ -615,16 +631,97 @@ module OpenKey
615
631
  end
616
632
 
617
633
 
634
+ # Return a date/time string detailing when the master database was first created.
635
+ #
636
+ # @param the_master_db [Hash]
637
+ # the master database to inspect = REFACTOR convert methods into a class instance
638
+ #
639
+ # @return [String]
640
+ # return a date/time string representation denoting when the master database
641
+ # was first created.
642
+ def self.to_db_create_date( the_master_db )
643
+ return the_master_db[ DB_CREATE_DATE ]
644
+ end
645
+
646
+
647
+ # Return the domain name of the master database.
648
+ #
649
+ # @param the_master_db [Hash]
650
+ # the master database to inspect = REFACTOR convert methods into a class instance
651
+ #
652
+ # @return [String]
653
+ # return the domain name of the master database.
654
+ def self.to_db_domain_name( the_master_db )
655
+ return the_master_db[ DB_DOMAIN_NAME ]
656
+ end
657
+
658
+
659
+ # Return the domain ID of the master database.
660
+ #
661
+ # @param the_master_db [Hash]
662
+ # the master database to inspect = REFACTOR convert methods into a class instance
663
+ #
664
+ # @return [String]
665
+ # return the domain ID of the master database.
666
+ def self.to_db_domain_id( the_master_db )
667
+ return the_master_db[ DB_DOMAIN_ID ]
668
+ end
669
+
670
+
671
+ # Return a dictionary containing a string key and the corresponding master database
672
+ # value whenever the master database key starts with the parameter string.
673
+ #
674
+ # For example if the master database contains a dictionary like this.
675
+ #
676
+ # envelope@earth => { radius => 24034km, sun_distance_light_minutes => 8 }
677
+ # textfile@kepler => { filepath => $HOME/keplers_laws.txt, filekey => Nsf8F34dhDT34jLKsLf52 }
678
+ # envelope@jupiter => { radius => 852837km, sun_distance_light_minutes => 6 }
679
+ # envelope@pluto => { radius => 2601km, sun_distance_light_minutes => 52 }
680
+ # textfile@newton => { filepath => $HOME/newtons_laws.txt, filekey => sdDFRTTYu4567fghFG5Jl }
681
+ #
682
+ # with "envelope@" as the start string to match.
683
+ # The returned dictionary would have 3 elements whose keys are the unique portion of the string.
684
+ #
685
+ # earth => { radius => 24034km, sun_distance_light_minutes => 8 }
686
+ # jupiter => { radius => 852837km, sun_distance_light_minutes => 6 }
687
+ # pluto => { radius => 2601km, sun_distance_light_minutes => 52 }
688
+ #
689
+ # If no matches are found an empty dictionary is returned.
690
+ #
691
+ # @param the_master_db [Hash]
692
+ # the master database to inspect = REFACTOR convert methods into a class instance
693
+ #
694
+ # @param start_string [String]
695
+ # the start string to match. Every key in the master database that
696
+ # starts with this string is considered a match. The corresponding value
697
+ # of each matching key is appended onto the end of an array.
698
+ #
699
+ # @return [Hash]
700
+ # a dictionary whose keys are the unique (2nd) portion of the string with corresponding
701
+ # values and in no particular order.
702
+ def self.to_matching_dictionary( the_master_db, start_string )
703
+
704
+ matching_dictionary = {}
705
+ the_master_db.each_key do | db_key |
706
+ next unless db_key.start_with?( start_string )
707
+ dictionary_key = db_key.gsub( start_string, "" )
708
+ matching_dictionary.store( dictionary_key, the_master_db[db_key] )
709
+ end
710
+ return matching_dictionary
711
+
712
+ end
713
+
714
+
618
715
  # To read the content we first find the appropriate shell key and the
619
- # appropriate database ciphertext, one decrypts the other to produce the index
620
- # database crypt key and that decrypts the current database's ciphertext to
621
- # reveal the database in plaintext.
716
+ # appropriate database ciphertext, one decrypts the other to produce the master
717
+ # database decryption key which in turn reveals the JSON representation of the
718
+ # master database.
622
719
  #
623
- # The plaintext is deserialized into a data structure and returned.
720
+ # The master database JSON is deserialized as a {Hash} and returned.
624
721
  #
625
- # <b>Steps Taken To Read the Content</b>
722
+ # <b>Steps Taken To Read the Master Database</b>
626
723
  #
627
- # Reading the content requires a rostra of actions namely
724
+ # Reading the master database requires a rostra of actions namely
628
725
  #
629
726
  # - reading the path to the <b>keystore breadcrumbs file</b>
630
727
  # - using the session token to derive the (unique to the) shell key
@@ -632,10 +729,19 @@ module OpenKey
632
729
  # - reading the encrypted and encoded content, decoding and decrypting it
633
730
  # - employing index key, ciphertext and random iv to reveal the content
634
731
  #
732
+ # @param use_grandparent_pid [Boolean]
733
+ #
734
+ # Optional boolean parameter. If set to true the PID (process ID) used
735
+ # as part of an obfuscator key and normally acquired from the parent
736
+ # process should now be acquired from the grandparent's process.
737
+ #
738
+ # Set to true when accessing the safe's credentials from a sub process
739
+ # rather than directly through the logged in shell.
740
+ #
635
741
  # @return [String]
636
742
  # decode, decrypt and hen return the plain text content that was written
637
743
  # to a file by the {write_content} method.
638
- def self.read_app_content()
744
+ def self.read_master_db( use_grandparent_pid = false )
639
745
 
640
746
  # --
641
747
  # -- Get the filepath to the breadcrumbs file using the trail in
@@ -652,7 +758,7 @@ module OpenKey
652
758
  # --
653
759
  # -- Regenerate intra-session key from the session token.
654
760
  # --
655
- intra_key = KeyLocal.regenerate_shell_key( to_token() )
761
+ intra_key = KeyLocal.regenerate_shell_key( to_token(), use_grandparent_pid )
656
762
 
657
763
  # --
658
764
  # -- Decrypt and acquire the content enryption key that was created
@@ -674,10 +780,10 @@ module OpenKey
674
780
  # --
675
781
  # -- Get the full ciphertext file (warts and all) and then top and
676
782
  # -- tail until just the valuable ciphertext is at hand. Decode then
677
- # -- decrypt the ciphertext and instantiate a key database from it.
783
+ # -- decrypt the ciphertext and instantiate a key database from the
784
+ # -- resulting JSON string.
678
785
  # --
679
786
  crypt_txt = binary_from_read( crypt_filepath )
680
-
681
787
  json_content = power_key.do_decrypt_text( random_iv, crypt_txt )
682
788
 
683
789
  return KeyDb.from_json( json_content )
@@ -709,8 +815,8 @@ module OpenKey
709
815
  # method and the resulting content will be encrypted and written to
710
816
  # the file at path {content_ciphertxt_file_from_session_token}.
711
817
  #
712
- # This method's mirror is {read_app_content}.
713
- def self.write_app_content( content_header, app_database )
818
+ # This method's mirror is {read_master_db}.
819
+ def self.write_master_db( content_header, app_database )
714
820
 
715
821
  # --
716
822
  # -- Get the filepath to the breadcrumbs file using the trail in
@@ -918,13 +1024,9 @@ module OpenKey
918
1024
  crypt_txt = binary_from_read( content_path )
919
1025
  random_iv = KeyIV.in_binary( crumbs_map[ CONTENT_RANDOM_IV ] )
920
1026
  crypt_key = Key.from_char64( crumbs_map[ CONTENT_ENCRYPT_KEY ] )
921
- json_text = crypt_key.do_decrypt_text( random_iv, crypt_txt )
1027
+ text_data = crypt_key.do_decrypt_text( random_iv, crypt_txt )
922
1028
 
923
- # --
924
- # -- The decrypted content is expected to be a tree data structure
925
- # -- streamed into JSON before encryption.
926
- # --
927
- return KeyDb.from_json( json_text )
1029
+ return text_data
928
1030
 
929
1031
  end
930
1032
 
@@ -967,7 +1069,7 @@ module OpenKey
967
1069
  # and if it exists we assert that the content filepath should also
968
1070
  # be present.
969
1071
  #
970
- def self.content_exists?( crumbs_map )
1072
+ def self.db_envelope_exists?( crumbs_map )
971
1073
 
972
1074
  return false if crumbs_map.nil?
973
1075
  return false unless crumbs_map.has_key?( CONTENT_EXTERNAL_ID )
@@ -1028,13 +1130,17 @@ module OpenKey
1028
1130
  private
1029
1131
 
1030
1132
 
1133
+ # --------------------------------------------------------
1134
+ # In order to separate keys into a new gem we must
1135
+ # break knowledge of this variable name and have it
1136
+ # instead passed in by clients.
1137
+ TOKEN_VARIABLE_NAME = "SAFE_TTY_TOKEN"
1138
+ TOKEN_VARIABLE_SIZE = 152
1139
+ # --------------------------------------------------------
1031
1140
 
1032
- MACHINE_CONFIG_FILE = File.join( Dir.home, ".config/openkey/openkey.app.config.ini" )
1033
1141
 
1142
+ MACHINE_CONFIG_FILE = File.join( Dir.home, ".config/openkey/openkey.app.config.ini" )
1034
1143
  SESSION_APP_DOMAINS = "session.app.domains"
1035
- TOKEN_VARIABLE_NAME = "OPEN_SESSION_TOKEN"
1036
- TOKEN_VARIABLE_SIZE = 150
1037
-
1038
1144
  SESSION_IDENTIFIER_KEY = "session.identifiers"
1039
1145
  KEYSTORE_IDENTIFIER_KEY = "keystore.url.id"
1040
1146
  APP_INSTANCE_ID_KEY = "app.instance.id"
@@ -1072,7 +1178,11 @@ module OpenKey
1072
1178
  CONTENT_FILE_PREFIX = "tree.db"
1073
1179
  CONTENT_EXTERNAL_ID = "content.xid"
1074
1180
  CONTENT_ENCRYPT_KEY = "content.key"
1075
- CONTENT_RANDOM_IV = "content.iv"
1181
+ CONTENT_RANDOM_IV = "content.iv"
1182
+
1183
+ DB_CREATE_DATE = "db.create.date"
1184
+ DB_DOMAIN_NAME = "db.domain.name"
1185
+ DB_DOMAIN_ID = "db.domain.id"
1076
1186
 
1077
1187
 
1078
1188
  def self.binary_to_write( to_filepath, content_header, binary_ciphertext )
@@ -1121,9 +1231,9 @@ module OpenKey
1121
1231
  app_id = KeyId.derive_app_instance_identifier( domain_name )
1122
1232
 
1123
1233
  initial_db = KeyDb.new()
1124
- initial_db.store( "Database Birthday", KeyNow.fetch() )
1125
- initial_db.store( "App Domain Name", domain_name )
1126
- initial_db.store( "Application Id", app_id )
1234
+ initial_db.store( DB_CREATE_DATE, KeyNow.fetch() )
1235
+ initial_db.store( DB_DOMAIN_NAME, domain_name )
1236
+ initial_db.store( DB_DOMAIN_ID, app_id )
1127
1237
  return initial_db.to_json
1128
1238
 
1129
1239
  end
@@ -1259,15 +1369,15 @@ module OpenKey
1259
1369
  end
1260
1370
 
1261
1371
 
1262
- def raise_token_error env_var_name, message
1372
+ def self.raise_token_error env_var_name, message
1263
1373
 
1264
1374
  puts ""
1265
1375
  puts "#{TOKEN_VARIABLE_NAME} environment variable #{message}."
1266
1376
  puts "To instantiate it you can use the below command."
1267
1377
  puts ""
1268
- puts "$ export OPEN_SESSION_TOKEN=`ops token`"
1378
+ puts "$ export #{TOKEN_VARIABLE_NAME}=`safe token`"
1269
1379
  puts ""
1270
- puts "ps => those are backticks around `ops token` (not apostrophes)."
1380
+ puts "ps => those are backticks around `safe token` (not apostrophes)."
1271
1381
  puts ""
1272
1382
 
1273
1383
  raise RuntimeError, "#{TOKEN_VARIABLE_NAME} environment variable #{message}."