opensecret 0.0.988 → 0.0.9925
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +56 -159
- data/bin/opensecret +2 -2
- data/bin/ops +17 -2
- data/lib/extension/string.rb +14 -16
- data/lib/{interpreter.rb → interprete.rb} +53 -29
- data/lib/keytools/binary.map.rb +49 -0
- data/lib/keytools/kdf.api.rb +249 -0
- data/lib/keytools/kdf.bcrypt.rb +64 -29
- data/lib/keytools/kdf.pbkdf2.rb +92 -83
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1281 -0
- data/lib/keytools/key.db.rb +265 -0
- data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +265 -0
- data/lib/keytools/key.mach.rb +248 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +428 -298
- data/lib/keytools/keydebug.txt +295 -0
- data/lib/logging/gem.logging.rb +3 -3
- data/lib/modules/cryptology/collect.rb +20 -0
- data/lib/session/require.gem.rb +1 -1
- data/lib/usecase/cmd.rb +417 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +174 -0
- data/lib/usecase/init.rb +78 -0
- data/lib/usecase/login.rb +70 -0
- data/lib/usecase/logout.rb +30 -0
- data/lib/usecase/open.rb +126 -0
- data/lib/{interprete → usecase}/put.rb +100 -47
- data/lib/usecase/read.rb +89 -0
- data/lib/{interprete → usecase}/safe.rb +0 -0
- data/lib/{interprete → usecase}/set.rb +0 -0
- data/lib/usecase/token.rb +111 -0
- data/lib/{interprete → usecase}/use.rb +0 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +4 -3
- metadata +39 -33
- data/lib/exception/cli.error.rb +0 -53
- data/lib/exception/errors/cli.errors.rb +0 -31
- data/lib/interprete/begin.rb +0 -232
- data/lib/interprete/cmd.rb +0 -621
- data/lib/interprete/export.rb +0 -163
- data/lib/interprete/init.rb +0 -205
- data/lib/interprete/key.rb +0 -119
- data/lib/interprete/open.rb +0 -148
- data/lib/interprete/seal.rb +0 -129
- data/lib/keytools/digester.rb +0 -245
- data/lib/keytools/key.data.rb +0 -227
- data/lib/keytools/key.derivation.rb +0 -341
- data/lib/modules/mappers/collateral.rb +0 -282
- data/lib/modules/mappers/envelope.rb +0 -127
- data/lib/modules/mappers/settings.rb +0 -170
- data/lib/notepad/scratch.pad.rb +0 -224
- data/lib/store-commands.txt +0 -180
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module OpenKey
|
5
|
+
|
6
|
+
# Algorithms that are quality catalysts in the derivation and entropy spread
|
7
|
+
# of keys, identifiers and base64 character numbers.
|
8
|
+
class KeyAlgo
|
9
|
+
|
10
|
+
|
11
|
+
# Cherry pick a given number of characters from the character pool
|
12
|
+
# so that a good spread is achieved. This picker is the anti-pattern
|
13
|
+
# of just axing the first 5 characters from a 100 character string
|
14
|
+
# essentially wasting over 90% of the available entropy.
|
15
|
+
#
|
16
|
+
# This is the <b>algorithem to cherry pick</b> a spread of characters
|
17
|
+
# from the pool in the second parameter.
|
18
|
+
#
|
19
|
+
# - if the character pool length is a multiple of num_chars all is good otherwise
|
20
|
+
# - constrict to the <b>highest multiple of the pick size below</b> the pool length
|
21
|
+
# - divide that number by num_chars to get the first offset and character spacing
|
22
|
+
# - if spacing is 3, the first character is the 3rd, the second the 6th and so on
|
23
|
+
# - then return the cherry picked characters
|
24
|
+
#
|
25
|
+
# @param pick_size [FixNum] the number of characters to cherry pick
|
26
|
+
# @param char_pool [String] a pool of characters to cherry pick from
|
27
|
+
# @return [String]
|
28
|
+
# a string whose length is the one indicated by the first parameter
|
29
|
+
# and whose characters contain a predictable, repeatable spread from
|
30
|
+
# the character pool parameter
|
31
|
+
def self.cherry_picker( pick_size, char_pool )
|
32
|
+
|
33
|
+
hmb_limit = highest_multiple_below( pick_size, char_pool.length )
|
34
|
+
jump_size = hmb_limit / pick_size
|
35
|
+
read_point = jump_size
|
36
|
+
picked_chars = ""
|
37
|
+
loop do
|
38
|
+
picked_chars += char_pool[ read_point - 1 ]
|
39
|
+
read_point += jump_size
|
40
|
+
break if read_point > hmb_limit
|
41
|
+
end
|
42
|
+
|
43
|
+
err_msg = "Expected cherry pick size to be #{pick_size} but it was #{picked_chars.length}."
|
44
|
+
raise RuntimeError, err_msg unless picked_chars.length == pick_size
|
45
|
+
|
46
|
+
return picked_chars
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Affectionately known as <b>a hmb</b>, this method returns the
|
52
|
+
# <b>highest multiple</b> of the first parameter that is below
|
53
|
+
# <b>(either less than or equal to)</b> the second parameter.
|
54
|
+
#
|
55
|
+
# - -------- - ------- - ----------------- -
|
56
|
+
# | Small | Big | Highest Multiple |
|
57
|
+
# | Number | Number | Below Big Num |
|
58
|
+
# | -------- - ------- - ----------------- |
|
59
|
+
# | 5 | 25 | 25 |
|
60
|
+
# | 3 | 20 | 18 |
|
61
|
+
# | 8 | 63 | 56 |
|
62
|
+
# | 1 | 1 | 1 |
|
63
|
+
# | 26 | 28 | 26 |
|
64
|
+
# | 1 | 7 | 7 |
|
65
|
+
# | 16 | 16 | 16 |
|
66
|
+
# | -------- - ------- - ----------------- |
|
67
|
+
# | 10 | 8 | ERROR |
|
68
|
+
# | -4 | 17 | ERROR |
|
69
|
+
# | 4 | -17 | ERROR |
|
70
|
+
# | 0 | 32 | ERROR |
|
71
|
+
# | 29 | 0 | ERROR |
|
72
|
+
# | -4 | 0 | ERROR |
|
73
|
+
# | -------- - ------- - ----------------- |
|
74
|
+
# - -------- - ------- - ----------------- -
|
75
|
+
#
|
76
|
+
# Zeroes and negative numbers cannot be entertained, nor can the
|
77
|
+
# small number be larger than the big one.
|
78
|
+
#
|
79
|
+
# @param small_num [FixNum]
|
80
|
+
# the highest multiple of this number below the one in the
|
81
|
+
# next parameter is what will be returned.
|
82
|
+
#
|
83
|
+
# @param big_num [FixNum]
|
84
|
+
# returns either this number or the nearest below it that is
|
85
|
+
# a multiple of the number in the first parameter.
|
86
|
+
#
|
87
|
+
# @raise [ArgumentError]
|
88
|
+
# if the first parameter is greater than the second
|
89
|
+
# if either or both parameters are zero or negative
|
90
|
+
def self.highest_multiple_below small_num, big_num
|
91
|
+
|
92
|
+
arg_issue = (small_num > big_num) || small_num < 1 || big_num < 1
|
93
|
+
err_msg = "Invalid args #{small_num} and #{big_num} to HMB function."
|
94
|
+
raise ArgumentError, err_msg if arg_issue
|
95
|
+
|
96
|
+
for index in 0 .. ( big_num - 1 )
|
97
|
+
invex = big_num - index # an [invex] is an inverted index
|
98
|
+
return invex if invex % small_num == 0
|
99
|
+
end
|
100
|
+
|
101
|
+
raise ArgumentError, "Could not find a multiple of #{small_num} lower than #{big_num}"
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,1281 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module OpenKey
|
4
|
+
|
5
|
+
# Use the key applications programming interface to transition the
|
6
|
+
# state of three (3) core keys in accordance with the needs of the
|
7
|
+
# executing use case.
|
8
|
+
#
|
9
|
+
# == KeyApi | The 3 Keys
|
10
|
+
#
|
11
|
+
# The three keys service the needs of a <b>command line application</b>
|
12
|
+
# that executes within a <b>shell environment in a unix envirronment</b>
|
13
|
+
# or a <b>command prompt in windows</b>.
|
14
|
+
#
|
15
|
+
# So what are the 3 keys and what is their purpose.
|
16
|
+
#
|
17
|
+
# - shell key | exists to lock the index key created at login
|
18
|
+
# - human key | exists to lock the index key created at login
|
19
|
+
# - index key | exists to lock the application's index file
|
20
|
+
#
|
21
|
+
# So why do two keys (the shell key and human key) exist to lock the
|
22
|
+
# same index key?
|
23
|
+
#
|
24
|
+
# == KeyApi | Why Lock the Index Key Twice?
|
25
|
+
#
|
26
|
+
# On this login, the <b>previous login's human key is regenerated</b> from
|
27
|
+
# the <b>human password and the saved salts</b>. This <em>old human key</em>
|
28
|
+
# decrypts and reveals the <b><em>old index key</em></b> which in turn
|
29
|
+
# decrypts and reveals the index string.
|
30
|
+
#
|
31
|
+
# Both the old human key and the old index key are discarded.
|
32
|
+
#
|
33
|
+
# Then 48 bytes of randomness are sourced to generate the new index key. This
|
34
|
+
# key encrypts the now decrypted index string and is thrown away. The password
|
35
|
+
# sources a new human key (the salts are saved), and this new key locks the
|
36
|
+
# index key's source bytes.
|
37
|
+
#
|
38
|
+
# The shell key again locks the index key's source bytes. <b><em>Why twice?</em></b>
|
39
|
+
#
|
40
|
+
# - during subsequent shell command calls the human key is unavailable however
|
41
|
+
# the index key can be accessed via the shell key.
|
42
|
+
#
|
43
|
+
# - when the shell dies (or logout is issued) the shell key dies. Now the index
|
44
|
+
# key can only be accessed by a login when the password is made available.
|
45
|
+
#
|
46
|
+
# That is why the index key is locked twice. The shell key opens it mid-session
|
47
|
+
# and the regenerated human key opens it during the login of the next session.
|
48
|
+
#
|
49
|
+
# == The LifeCycle of each Key
|
50
|
+
#
|
51
|
+
# It seems odd that the human key is born during this login then dies
|
52
|
+
# at the very next one (as stated below). This is because the human key
|
53
|
+
# isn't the password, <b>the human key is sourced from the password</b>.
|
54
|
+
#
|
55
|
+
# So when are the 3 keys <b>born</b> and when do they <b>cease being</b>.
|
56
|
+
#
|
57
|
+
# - shell key | is born when the shell is created and dies when the shell dies
|
58
|
+
# - human key | is born when the user logs in this time and dies at the next login
|
59
|
+
# - index key | the life of the index key exactly mirrors that of the human key
|
60
|
+
#
|
61
|
+
# == The 7 Key API Calls
|
62
|
+
#
|
63
|
+
# | - | -------- | ------------ | ------------------------------- |
|
64
|
+
# | # | Rationale | Use Case | Goals | Tasks |
|
65
|
+
# | - | ------------------------------- | ------------ | ------------------------------- |
|
66
|
+
# | 1 | Create and Obfuscate Shell Key | key | x | y |
|
67
|
+
# | 2 | New App Instance on Workstation | init | x | y |
|
68
|
+
# | 3 | Login to App Instance in Shell | login | x | y |
|
69
|
+
#
|
70
|
+
class KeyApi
|
71
|
+
|
72
|
+
|
73
|
+
# This method should only be called once for each application instance
|
74
|
+
# resident on a workstation (machine) and it derives and writes the identifiers
|
75
|
+
# into the openkey configuration file.
|
76
|
+
#
|
77
|
+
# <b>The Identifiers to Configure</b>
|
78
|
+
#
|
79
|
+
# The principal identifiers to derive and configure are the
|
80
|
+
#
|
81
|
+
# - identifier for the application instance on this machine
|
82
|
+
# - global identifier derived for the application instance
|
83
|
+
# - keystore url location for this app on this machine
|
84
|
+
# - time the above two identifiers were burned to disk
|
85
|
+
#
|
86
|
+
# <b>Set(App) Configuration File</b>
|
87
|
+
#
|
88
|
+
# Neither the file nor its parent folder need to exist. We attempt to create
|
89
|
+
# the directory path and then the file. After this method has executed the
|
90
|
+
# below directives will be added to the openkey application coniguration.
|
91
|
+
#
|
92
|
+
# Config filepath is $HOME/.config/openkey/openkey.app.config.ini
|
93
|
+
#
|
94
|
+
# [srn1-apzd]
|
95
|
+
# app.instance.id = crnl-d3my
|
96
|
+
# keystore.url.id = /home/apollo/abcd/ab-motors-inc
|
97
|
+
# initialize.time = Fri May 25 11:59:46 2018 ( 18145.1159.462 )
|
98
|
+
#
|
99
|
+
# @param domain_name [String]
|
100
|
+
# the string reference that points to the application instance
|
101
|
+
# that is being initialized on this machine.
|
102
|
+
#
|
103
|
+
# @param keystore_url [String]
|
104
|
+
# The keystore url points to where the key metadata protecting
|
105
|
+
# this application instance lives. The simplest keystores are
|
106
|
+
# based on files and for them this url is just a folder path.
|
107
|
+
#
|
108
|
+
# The keystore URL cannot be <b>N.E.W</b> (nil, empty, whitespace only).
|
109
|
+
def self.init_app_domain( domain_name, keystore_url )
|
110
|
+
|
111
|
+
KeyError.not_new( domain_name, self )
|
112
|
+
KeyError.not_new( keystore_url, self )
|
113
|
+
|
114
|
+
aim_id = KeyId.derive_app_instance_machine_id( domain_name )
|
115
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
116
|
+
|
117
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
118
|
+
keypairs.use( aim_id )
|
119
|
+
keypairs.set( APP_INSTANCE_ID_KEY, app_id )
|
120
|
+
keypairs.set( KEYSTORE_IDENTIFIER_KEY, keystore_url )
|
121
|
+
keypairs.set( APP_INITIALIZE_TIME, KeyNow.fetch() )
|
122
|
+
|
123
|
+
# --
|
124
|
+
# -- Switch the dominant application domain being used to
|
125
|
+
# -- the domain that is being initialized right here.
|
126
|
+
# --
|
127
|
+
use_application_domain( domain_name )
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# Has the inter-sessionary key ( derived from a human secret ) been setup
|
133
|
+
# for the application shard referenced in the parameter?
|
134
|
+
#
|
135
|
+
# This method returns yes (true) if and only if
|
136
|
+
#
|
137
|
+
# - the application's keystore file exists
|
138
|
+
# - the file contains a breadcrumbs section
|
139
|
+
# - crumbs exist for human key rederivation
|
140
|
+
#
|
141
|
+
# If false return gives the go-ahead to
|
142
|
+
#
|
143
|
+
# - collect the human secret (in one of a number of ways)
|
144
|
+
# - pass it through key derivation functions
|
145
|
+
# - generate a high entropy power key and lock some initial content with it
|
146
|
+
# - use the key sourced from the human secret to lock the power key
|
147
|
+
# - throw away the secret, power key and human sourced key
|
148
|
+
# - save crumbs (ciphertext, salts, ivs) for content retrieval given secret
|
149
|
+
#
|
150
|
+
# Note that the {init_app_domain} method must have been called on this machine
|
151
|
+
# with the name of this application instance and the keystore url. An error results
|
152
|
+
# if no file is found at the {MACHINE_CONFIG_FILE} path.
|
153
|
+
#
|
154
|
+
# @param domain_name [String]
|
155
|
+
# a string reference for the <b>in-focus</b> shard of the application
|
156
|
+
#
|
157
|
+
# @return [Boolean]
|
158
|
+
# return true if the human secret for the parameter application name
|
159
|
+
# has been collected, transformed into a key, that key used to lock the
|
160
|
+
# power key, then secret and keys deleted, plus a trail of breadcrumbs
|
161
|
+
# sprinkled to allow the <b>inter-sessionary key to be regenerated</b>
|
162
|
+
# at the <b>next login</b>.
|
163
|
+
#
|
164
|
+
# <b>Lest we forget</b> - buried within this ensemble of activities, is
|
165
|
+
# <b>generating the high entropy power key</b>, using it to lock the
|
166
|
+
# application database before discarding it.
|
167
|
+
def self.is_domain_keys_setup?( domain_name )
|
168
|
+
|
169
|
+
KeyError.not_new( domain_name, self )
|
170
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
171
|
+
aim_id = KeyId.derive_app_instance_machine_id( domain_name )
|
172
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
173
|
+
keypairs.use( aim_id )
|
174
|
+
|
175
|
+
keystore_file = get_keystore_file_from_domain_name( domain_name )
|
176
|
+
return false unless File.exists?( keystore_file )
|
177
|
+
|
178
|
+
crumbs_db = KeyPair.new( keystore_file )
|
179
|
+
return false unless crumbs_db.has_section?( APP_KEY_DB_BREAD_CRUMBS )
|
180
|
+
|
181
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
182
|
+
return crumbs_db.contains?( INTER_KEY_CIPHERTEXT )
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Transform the domain secret into a key, use that key to lock the
|
188
|
+
# power key, delete the secret and keys and leave behind a trail of
|
189
|
+
# <b>breadcrumbs sprinkled</b> to allow the <b>inter-sessionary key</b>
|
190
|
+
# to be <b>regenerated</b> at the <b>next login</b>.
|
191
|
+
#
|
192
|
+
# <b>Lest we forget</b> - buried within this ensemble of activities, is
|
193
|
+
# <b>generating the high entropy power key</b>, using it to lock the
|
194
|
+
# application database before discarding it.
|
195
|
+
#
|
196
|
+
# The use case steps once the human secret is acquired is to
|
197
|
+
#
|
198
|
+
# - pass it through key derivation functions
|
199
|
+
# - generate a high entropy power key and lock some initial content with it
|
200
|
+
# - use the key sourced from the human secret to lock the power key
|
201
|
+
# - throw away the secret, power key and human sourced key
|
202
|
+
# - save crumbs (ciphertext, salts, ivs) for content retrieval given secret
|
203
|
+
#
|
204
|
+
# Note that the {init_app_domain} method must have been called on this machine
|
205
|
+
# with the name of this application instance and the keystore url. An error results
|
206
|
+
# if no file is found at the {MACHINE_CONFIG_FILE} path.
|
207
|
+
#
|
208
|
+
# @param domain_name [String]
|
209
|
+
# the string reference that points to the application instance
|
210
|
+
# that is being initialized on this machine.
|
211
|
+
#
|
212
|
+
# @param domain_secret [String]
|
213
|
+
# the secret text that can potentially be cryptographically weak (low entropy).
|
214
|
+
# This text is severely strengthened and morphed into a key using multiple key
|
215
|
+
# derivation functions like <b>PBKDF2, BCrypt</b> and <b>SCrypt</b>.
|
216
|
+
#
|
217
|
+
# The secret text is discarded and the <b>derived inter-session key</b> is used
|
218
|
+
# only to encrypt the <em>randomly generated super strong <b>index key</b></em>,
|
219
|
+
# <b>before being itself discarded</b>.
|
220
|
+
#
|
221
|
+
# @param content_header [String]
|
222
|
+
# the content header tops the ciphertext storage file with details of how where
|
223
|
+
# and why the file came to be.
|
224
|
+
def self.setup_domain_keys( domain_name, domain_secret, content_header )
|
225
|
+
|
226
|
+
# --
|
227
|
+
# -- Get the breadcrumbs trail and
|
228
|
+
# -- timestamp the moment.
|
229
|
+
# --
|
230
|
+
crumbs_db = get_crumbs_db_from_domain_name( domain_name )
|
231
|
+
crumbs_db.set( APP_INSTANCE_SETUP_TIME, KeyNow.fetch() )
|
232
|
+
|
233
|
+
# --
|
234
|
+
# -- Create a new power key and lock the content with it.
|
235
|
+
# -- Create a new inter key and lock the power key with it.
|
236
|
+
# -- Leave the necessary breadcrumbs for regeneration.
|
237
|
+
# --
|
238
|
+
recycle_keys( domain_name, domain_secret, crumbs_db, content_header, get_virgin_content( domain_name ) )
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# Recycle the inter-sessionary key (based on the secret) and create a new
|
244
|
+
# content encryption (power) key and lock the parameter content with it
|
245
|
+
# before returning the new content encryption key.
|
246
|
+
#
|
247
|
+
# The {content_ciphertxt_file_from_domain_name} method is used to produce the path at which
|
248
|
+
# the ciphertext (resulting from locking the parameter content), is stored.
|
249
|
+
#
|
250
|
+
# @param domain_name [String]
|
251
|
+
#
|
252
|
+
# the (application instance) domain name chosen by the user or the
|
253
|
+
# machine that is interacting with the OpenKey software.
|
254
|
+
#
|
255
|
+
# @param domain_secret [String]
|
256
|
+
#
|
257
|
+
# the domain secret that is put through key derivation functions in order
|
258
|
+
# to attain the strongest possible inter-sessionary key which is used only
|
259
|
+
# to encrypt and decrypt the high-entropy content encryption key.
|
260
|
+
#
|
261
|
+
# @param crumbs_db [KeyPair]
|
262
|
+
#
|
263
|
+
# The crumbs database is expected to be initialized with a section
|
264
|
+
# ready to receive breadcrumb data. The crumbs data injected are
|
265
|
+
#
|
266
|
+
# - a random iv for future AES decryption of the parameter content
|
267
|
+
# - cryptographic salts for future rederivation of the inter-sessionary key
|
268
|
+
# - the resultant ciphertext from the inter key locking the content key
|
269
|
+
#
|
270
|
+
# @param the_content [String]
|
271
|
+
#
|
272
|
+
# the app database content whose ciphertext is to be recycled using the
|
273
|
+
# recycled (newly derived) high entropy random content encryption key.
|
274
|
+
def self.recycle_keys( domain_name, domain_secret, crumbs_db, content_header, the_content )
|
275
|
+
|
276
|
+
KeyError.not_new( domain_name, self )
|
277
|
+
KeyError.not_new( domain_secret, self )
|
278
|
+
KeyError.not_new( the_content, self )
|
279
|
+
|
280
|
+
# --
|
281
|
+
# -- Create a random initialization vector (iv)
|
282
|
+
# -- used for AES encryption of virgin content
|
283
|
+
# --
|
284
|
+
iv_base64_chars = KeyIV.new().for_storage()
|
285
|
+
crumbs_db.set( INDEX_DB_CRYPT_IV_KEY, iv_base64_chars )
|
286
|
+
random_iv = KeyIV.in_binary( iv_base64_chars )
|
287
|
+
|
288
|
+
# --
|
289
|
+
# -- Create a new high entropy power key
|
290
|
+
# -- for encrypting the virgin content.
|
291
|
+
# --
|
292
|
+
power_key = Key.from_random
|
293
|
+
|
294
|
+
# --
|
295
|
+
# -- Encrypt the virgin content using the
|
296
|
+
# -- power key and the random iv and write
|
297
|
+
# -- the Base64 encoded ciphertext into a
|
298
|
+
# -- neighbouring file.
|
299
|
+
# --
|
300
|
+
to_filepath = content_ciphertxt_file_from_domain_name( domain_name )
|
301
|
+
binary_ciphertext = power_key.do_encrypt_text( random_iv, the_content )
|
302
|
+
binary_to_write( to_filepath, content_header, binary_ciphertext )
|
303
|
+
|
304
|
+
# --
|
305
|
+
# -- Derive new inter-sessionary key.
|
306
|
+
# -- Use it to encrypt the power key.
|
307
|
+
# -- Set the reretrieval breadcrumbs.
|
308
|
+
# --
|
309
|
+
inter_key = KdfApi.generate_from_password( domain_secret, crumbs_db )
|
310
|
+
inter_txt = inter_key.do_encrypt_key( power_key )
|
311
|
+
crumbs_db.set( INTER_KEY_CIPHERTEXT, inter_txt )
|
312
|
+
|
313
|
+
# --
|
314
|
+
# -- Return the just createdC high entropy
|
315
|
+
# -- content encryption (power) key.
|
316
|
+
# --
|
317
|
+
return power_key
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
|
323
|
+
# At <b>the end</b> of a successful login the <b>old content crypt key</b> will
|
324
|
+
# have been <b>re-acquired and discarded,</b> with a <b>fresh one created</b>and
|
325
|
+
# put to work <b>protecting</b> the application's content.
|
326
|
+
#
|
327
|
+
# After reacquisitioning (but before discarding) the old crypt key, the app's
|
328
|
+
# key-value database is <b>silently decrypted with it then immediately re-encrypted</b>
|
329
|
+
# with the newly created (and locked down) crypt key.
|
330
|
+
#
|
331
|
+
# <b>Login Recycles 3 things</b>
|
332
|
+
#
|
333
|
+
# The three (3) things recycled by this login are
|
334
|
+
#
|
335
|
+
# - the human key (sourced by putting the secret text through two key derivation functions)
|
336
|
+
# - the content crypt key (sourced from a random 48 byte sequence)
|
337
|
+
# - the content ciphertext (sourced by decrypting with the old and re-encrypting with the new)
|
338
|
+
#
|
339
|
+
# Remember that the content crypt key is itself encrypted by two key entities.
|
340
|
+
#
|
341
|
+
# <b>The Inter and Intra Session Crypt Keys</b>
|
342
|
+
#
|
343
|
+
# This <b>login use case</b> is the <b>only time</b> in the session that the
|
344
|
+
# <b>human provided secret</b> is made available - hence the inter-session name.
|
345
|
+
#
|
346
|
+
# The intra session key is employed by use case calls on within (intra) the
|
347
|
+
# session it was created within.
|
348
|
+
#
|
349
|
+
# <b>The Weakness of the Human Inter Sessionary Key</b>
|
350
|
+
#
|
351
|
+
# The weakest link in the human-sourced key is clearly the human. Yes it is
|
352
|
+
# strengthened by key derivation functions with cost parameters as high is
|
353
|
+
# tolerable, but despite and in spite of these efforts, poorly chosen short
|
354
|
+
# passwords are not infeasible to acquire through brute force.
|
355
|
+
#
|
356
|
+
# The fallability is countered by invalidating and recycling the (inter session)
|
357
|
+
# key on every login, thus reducing the time frame available to an attacker.
|
358
|
+
#
|
359
|
+
# <b>The Weakness of the Shell Intra Sessionary Key</b>
|
360
|
+
#
|
361
|
+
# The shell key hails from a super random (infeasible to crack) source of
|
362
|
+
# 48 binary bytes. So what is its achilles heel?
|
363
|
+
#
|
364
|
+
# The means of protecting the shell key is the weakness. The source of its
|
365
|
+
# protection key is a motley crue of data unique not just to the workstation,
|
366
|
+
# but the parent shell. This is also passed through key derivation functions
|
367
|
+
# to strengthen it.
|
368
|
+
#
|
369
|
+
# <em><b>Temporary Environment Variables</b></em>
|
370
|
+
#
|
371
|
+
# The shell key's ciphertext lives as a short term environment variable so
|
372
|
+
# <b>when the shell dies the ciphertext dies</b> and any opportunity to resurrect
|
373
|
+
# the shell key <b>dies with it</b>.
|
374
|
+
#
|
375
|
+
# A <b>logout</b> command <b>removes the random iv and ciphertext</b> forged
|
376
|
+
# when the shell acted to encrypt the content key. Even mid shell session, a
|
377
|
+
# logout renders the shell key worthless.
|
378
|
+
#
|
379
|
+
# <b>Which (BreadCrumbs) endure?</b>
|
380
|
+
#
|
381
|
+
# Only <b>4 things endure</b> post the <b>login (recycle)</b> activities.
|
382
|
+
# These are the
|
383
|
+
#
|
384
|
+
# - salts and iteration counts used to generate the inter-session key
|
385
|
+
# - index key ciphertext after encryption using the inter-session key
|
386
|
+
# - index key ciphertext after encryption using the intra-session key
|
387
|
+
# - <b>content ciphertext</b> after the decrypt re-encrypt activities
|
388
|
+
#
|
389
|
+
#
|
390
|
+
# @param domain_name [String]
|
391
|
+
# the string reference that points to the application instance
|
392
|
+
# that is being initialized on this machine.
|
393
|
+
#
|
394
|
+
# @param domain_secret [String]
|
395
|
+
# the secret text that can potentially be cryptographically weak (low entropy).
|
396
|
+
# This text is severely strengthened and morphed into a key using multiple key
|
397
|
+
# derivation functions like <b>PBKDF2, BCrypt</b> and <b>SCrypt</b>.
|
398
|
+
#
|
399
|
+
# The secret text is discarded and the <b>derived inter-session key</b> is used
|
400
|
+
# only to encrypt the <em>randomly generated super strong <b>index key</b></em>,
|
401
|
+
# <b>before being itself discarded</b>.
|
402
|
+
#
|
403
|
+
# The key ring only stores the salts. This means the secret text based key can
|
404
|
+
# only be regenerated at the next login, which explains the inter-session label.
|
405
|
+
#
|
406
|
+
# <b>Note on Password Key Derivation</b>
|
407
|
+
# For each guess, a brute force attacker would need to perform
|
408
|
+
# <b>one million PBKDF2</b> and <b>65,536 BCrypt</b> algorithm
|
409
|
+
# iterations.
|
410
|
+
#
|
411
|
+
# Even so, a password of 6 characters or less can be successfully
|
412
|
+
# attacked. With all earth's computing resources working exclusively
|
413
|
+
# and in concert on attacking one password, it would take over
|
414
|
+
# <b>one million years to access the key</b> derived from a well spread
|
415
|
+
# 24 character password. And the key becomes obsolete the next time
|
416
|
+
# you login.
|
417
|
+
#
|
418
|
+
# Use the above information to decide on secrets with sufficient
|
419
|
+
# entropy and spread with at least 12 characters.
|
420
|
+
#
|
421
|
+
# @param content_header [String]
|
422
|
+
# the content header tops the ciphertext storage file with details of how where
|
423
|
+
# and why the file came to be.
|
424
|
+
def self.do_login( domain_name, domain_secret, content_header )
|
425
|
+
|
426
|
+
# --
|
427
|
+
# -- Get the breadcrumbs trail.
|
428
|
+
# --
|
429
|
+
crumbs_db = get_crumbs_db_from_domain_name( domain_name )
|
430
|
+
|
431
|
+
# --
|
432
|
+
# -- Get the old inter-sessionary key (created during the previous login)
|
433
|
+
# -- Get the old content encryption (power) key (again created during the previous login)
|
434
|
+
# -- Get the old random initialization vector (created during the previous login)
|
435
|
+
# --
|
436
|
+
old_inter_key = KdfApi.regenerate_from_salts( domain_secret, crumbs_db )
|
437
|
+
old_power_key = old_inter_key.do_decrypt_key( crumbs_db.get( INTER_KEY_CIPHERTEXT ) )
|
438
|
+
old_random_iv = KeyIV.in_binary( crumbs_db.get( INDEX_DB_CRYPT_IV_KEY ) )
|
439
|
+
|
440
|
+
# --
|
441
|
+
# -- Read the binary text representing the encrypted content
|
442
|
+
# -- that was last written by any use case capable of changing
|
443
|
+
# -- the application database content.
|
444
|
+
# --
|
445
|
+
from_filepath = content_ciphertxt_file_from_domain_name( domain_name )
|
446
|
+
old_crypt_txt = binary_from_read( from_filepath )
|
447
|
+
|
448
|
+
# --
|
449
|
+
# -- Decrypt the binary ciphertext that was last written by a use case
|
450
|
+
# -- capable of changing the application database.
|
451
|
+
# --
|
452
|
+
plain_content = old_power_key.do_decrypt_text( old_random_iv, old_crypt_txt )
|
453
|
+
|
454
|
+
# --
|
455
|
+
# -- Create a new power key and lock the content with it.
|
456
|
+
# -- Create a new inter key and lock the power key with it.
|
457
|
+
# -- Leave the necessary breadcrumbs for regeneration.
|
458
|
+
# -- Return the new power key that re-locked the content.
|
459
|
+
# --
|
460
|
+
power_key = recycle_keys( domain_name, domain_secret, crumbs_db, content_header, plain_content )
|
461
|
+
|
462
|
+
# --
|
463
|
+
# -- Regenerate intra-session key from the session token.
|
464
|
+
# -- Encrypt power key for intra (in) session retrieval.
|
465
|
+
# --
|
466
|
+
intra_key = KeyLocal.regenerate_shell_key( to_token() )
|
467
|
+
intra_txt = intra_key.do_encrypt_key( power_key )
|
468
|
+
|
469
|
+
# --
|
470
|
+
# -- Set the (ciphertext) breadcrumbs for re-acquiring the
|
471
|
+
# -- content encryption (power) key during (inside) this
|
472
|
+
# -- shell session.
|
473
|
+
# --
|
474
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
475
|
+
unique_id = KeyId.derive_universal_id( app_id, to_token() )
|
476
|
+
crumbs_db.use( unique_id )
|
477
|
+
crumbs_db.set( INTRA_KEY_CIPHERTEXT, intra_txt )
|
478
|
+
crumbs_db.set( SESSION_LOGIN_DATETIME, KeyNow.fetch() )
|
479
|
+
|
480
|
+
# --
|
481
|
+
# -- Switch the dominant application domain being used to
|
482
|
+
# -- the domain that has just logged in.
|
483
|
+
# --
|
484
|
+
use_application_domain( domain_name )
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
# Switch the application instance that the current shell session is using.
|
490
|
+
# Trigger this method either during the login use case or when the user
|
491
|
+
# issues an intent to use a different application instance.
|
492
|
+
#
|
493
|
+
# The machine configuration file at path {MACHINE_CONFIG_FILE} is changed
|
494
|
+
# in the following way
|
495
|
+
#
|
496
|
+
# - a {SESSION_APP_DOMAINS} section is added if one does not exist
|
497
|
+
# - the shell session ID key is added (or updated if it exists)
|
498
|
+
# - with a value corresponding to the app instance ID (on this machine)
|
499
|
+
#
|
500
|
+
# Subsequent use cases can now access the application ID by going first to
|
501
|
+
# the {SESSION_APP_DOMAINS} section, reading the ID of the app instance on
|
502
|
+
# this machine and then using that in turn to read the {APP_INSTANCE_ID_KEY}
|
503
|
+
# value.
|
504
|
+
#
|
505
|
+
# The {APP_INSTANCE_ID_KEY} value is the global ID of the app instance no
|
506
|
+
# matter which machine or shell is being used.
|
507
|
+
#
|
508
|
+
# @param domain_name [String]
|
509
|
+
# the string reference that points to the global application identifier
|
510
|
+
# no matter the machine being used.
|
511
|
+
def self.use_application_domain( domain_name )
|
512
|
+
|
513
|
+
KeyError.not_new( domain_name, self )
|
514
|
+
|
515
|
+
aim_id = KeyId.derive_app_instance_machine_id( domain_name )
|
516
|
+
sid_id = KeyId.derive_session_id( to_token() )
|
517
|
+
|
518
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
519
|
+
keypairs.use( SESSION_APP_DOMAINS )
|
520
|
+
keypairs.set( sid_id, aim_id )
|
521
|
+
|
522
|
+
end
|
523
|
+
|
524
|
+
|
525
|
+
# <b>Logout of the shell key session</b> by making the high entropy content
|
526
|
+
# encryption key <b>irretrievable for all intents and purposes</b> to anyone
|
527
|
+
# who does not possess the domain secret.
|
528
|
+
#
|
529
|
+
# The key logout action is deleting the ciphertext originally produced when
|
530
|
+
# the intra-sessionary (shell) key encrypted the content encryption key.
|
531
|
+
#
|
532
|
+
# <b>Why Isn't the Session Token Deleted?</b>
|
533
|
+
#
|
534
|
+
# The session token is left to <b>die by natural causes</b> so that we don't
|
535
|
+
# interfere with other domain interactions that may be in progress within
|
536
|
+
# this shell session.
|
537
|
+
#
|
538
|
+
# @param domain_name [String]
|
539
|
+
# the string reference that points to the application instance that we
|
540
|
+
# are logging out of from the shell on this machine.
|
541
|
+
def self.do_logout( domain_name )
|
542
|
+
|
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
|
548
|
+
|
549
|
+
|
550
|
+
# --> ######################
|
551
|
+
# --> Login / Logout Time
|
552
|
+
# --> ######################
|
553
|
+
# -->
|
554
|
+
# --> During login you create a section heading same as the session ID
|
555
|
+
# --> You then put the intra-key ciphertext there (from locking power key)
|
556
|
+
# --> To check if a login has occurred we ensure this session's ID exists as a header in crumbs DB
|
557
|
+
# --> On logout we remove the session ID and all the subsection crumbs (intra key ciphertext)
|
558
|
+
# --> Logout makes it impossible to access the power key (now only by seret delivery and the inter key ciphertext)
|
559
|
+
# -->
|
560
|
+
|
561
|
+
|
562
|
+
# --
|
563
|
+
# -- Get the breadcrumbs trail.
|
564
|
+
# --
|
565
|
+
crumbs_db = get_crumbs_db_from_domain_name( domain_name )
|
566
|
+
|
567
|
+
|
568
|
+
# --
|
569
|
+
# -- Set the (ciphertext) breadcrumbs for re-acquiring the
|
570
|
+
# -- content encryption (power) key during (inside) this
|
571
|
+
# -- shell session.
|
572
|
+
# --
|
573
|
+
unique_id = KeyId.derive_universal_id( domain_name )
|
574
|
+
crumbs_db.use( unique_id )
|
575
|
+
crumbs_db.set( INTRA_KEY_CIPHERTEXT, intra_txt )
|
576
|
+
crumbs_db.set( SESSION_LOGOUT_DATETIME, KeyNow.fetch() )
|
577
|
+
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
# Has the user orchestrating this shell session logged in? Yes or no?
|
582
|
+
# If yes then they appear to have supplied the correct secret
|
583
|
+
#
|
584
|
+
# - in this shell session
|
585
|
+
# - on this machine and
|
586
|
+
# - for this application instance
|
587
|
+
#
|
588
|
+
# Use the crumbs found underneath the universal (session) ID within the
|
589
|
+
# main breadcrumbs file for this application instance.
|
590
|
+
#
|
591
|
+
# Note that the system does not rely on this value for its security, it
|
592
|
+
# exists only to give a pleasant error message.
|
593
|
+
#
|
594
|
+
# @return [Boolean]
|
595
|
+
# return true if a marker denoting that this shell session with this
|
596
|
+
# application instance on this machine has logged in. Subverting this
|
597
|
+
# return value only serves to evoke disgraceful degradation.
|
598
|
+
def self.is_logged_in?( domain_name )
|
599
|
+
############## Write this code.
|
600
|
+
############## Write this code.
|
601
|
+
############## Write this code.
|
602
|
+
############## Write this code.
|
603
|
+
############## Write this code.
|
604
|
+
############## Write this code.
|
605
|
+
############## Write this code.
|
606
|
+
return false unless File.exists?( frontend_keystore_file() )
|
607
|
+
|
608
|
+
crumbs_db = KeyPair.new( frontend_keystore_file() )
|
609
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
610
|
+
return false unless crumbs_db.contains?( LOGGED_IN_APP_SESSION_ID )
|
611
|
+
|
612
|
+
recorded_id = crumbs_db.get( LOGGED_IN_APP_SESSION_ID )
|
613
|
+
return recorded_id.eql?( @uni_id )
|
614
|
+
|
615
|
+
end
|
616
|
+
|
617
|
+
|
618
|
+
# 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.
|
622
|
+
#
|
623
|
+
# The plaintext is deserialized into a data structure and returned.
|
624
|
+
#
|
625
|
+
# <b>Steps Taken To Read the Content</b>
|
626
|
+
#
|
627
|
+
# Reading the content requires a rostra of actions namely
|
628
|
+
#
|
629
|
+
# - reading the path to the <b>keystore breadcrumbs file</b>
|
630
|
+
# - using the session token to derive the (unique to the) shell key
|
631
|
+
# - using the shell key and ciphertext to unlock the index key
|
632
|
+
# - reading the encrypted and encoded content, decoding and decrypting it
|
633
|
+
# - employing index key, ciphertext and random iv to reveal the content
|
634
|
+
#
|
635
|
+
# @return [String]
|
636
|
+
# decode, decrypt and hen return the plain text content that was written
|
637
|
+
# to a file by the {write_content} method.
|
638
|
+
def self.read_app_content()
|
639
|
+
|
640
|
+
# --
|
641
|
+
# -- Get the filepath to the breadcrumbs file using the trail in
|
642
|
+
# -- the global configuration left by {use_application_domain}.
|
643
|
+
# --
|
644
|
+
crumbs_db = get_crumbs_db_from_session_token()
|
645
|
+
|
646
|
+
# --
|
647
|
+
# -- Get the path to the file holding the ciphertext of the application
|
648
|
+
# -- database content locked by the content encryption key.
|
649
|
+
# --
|
650
|
+
crypt_filepath = content_ciphertxt_file_from_session_token()
|
651
|
+
|
652
|
+
# --
|
653
|
+
# -- Regenerate intra-session key from the session token.
|
654
|
+
# --
|
655
|
+
intra_key = KeyLocal.regenerate_shell_key( to_token() )
|
656
|
+
|
657
|
+
# --
|
658
|
+
# -- Decrypt and acquire the content enryption key that was created
|
659
|
+
# -- during the login use case and encrypted using the intra sessionary
|
660
|
+
# -- key.
|
661
|
+
# --
|
662
|
+
unique_id = KeyId.derive_universal_id( read_app_id(), to_token() )
|
663
|
+
crumbs_db.use( unique_id )
|
664
|
+
power_key = intra_key.do_decrypt_key( crumbs_db.get( INTRA_KEY_CIPHERTEXT ) )
|
665
|
+
|
666
|
+
# --
|
667
|
+
# -- Set the (ciphertext) breadcrumbs for re-acquiring the
|
668
|
+
# -- content encryption (power) key during (inside) this
|
669
|
+
# -- shell session.
|
670
|
+
# --
|
671
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
672
|
+
random_iv = KeyIV.in_binary( crumbs_db.get( INDEX_DB_CRYPT_IV_KEY ) )
|
673
|
+
|
674
|
+
# --
|
675
|
+
# -- Get the full ciphertext file (warts and all) and then top and
|
676
|
+
# -- tail until just the valuable ciphertext is at hand. Decode then
|
677
|
+
# -- decrypt the ciphertext and instantiate a key database from it.
|
678
|
+
# --
|
679
|
+
crypt_txt = binary_from_read( crypt_filepath )
|
680
|
+
|
681
|
+
json_content = power_key.do_decrypt_text( random_iv, crypt_txt )
|
682
|
+
|
683
|
+
return KeyDb.from_json( json_content )
|
684
|
+
|
685
|
+
end
|
686
|
+
|
687
|
+
|
688
|
+
# This write content behaviour takes the parameter content, encyrpts and
|
689
|
+
# encodes it using the index key, which is itself derived from the shell
|
690
|
+
# key unlocking the intra session ciphertext. The crypted content is
|
691
|
+
# written to a file whose path is derviced by {content_ciphertxt_file_from_domain_name}.
|
692
|
+
#
|
693
|
+
# <b>Steps Taken To Write the Content</b>
|
694
|
+
#
|
695
|
+
# Writing the content requires a rostra of actions namely
|
696
|
+
#
|
697
|
+
# - deriving filepaths to both the breadcrumb and ciphertext files
|
698
|
+
# - creating a random iv and adding its base64 form to the breadcrumbs
|
699
|
+
# - using the session token to derive the (unique to the) shell key
|
700
|
+
# - using the shell key and (intra) ciphertext to acquire the index key
|
701
|
+
# - using the index key and random iv to encrypt and encode the content
|
702
|
+
# - writing the resulting ciphertext to a file at the designated path
|
703
|
+
#
|
704
|
+
# @param content_header [String]
|
705
|
+
# the string that will top the ciphertext content when it is written
|
706
|
+
#
|
707
|
+
# @param app_database [KeyDb]
|
708
|
+
# this key database class will be streamed using its {Hash.to_json}
|
709
|
+
# method and the resulting content will be encrypted and written to
|
710
|
+
# the file at path {content_ciphertxt_file_from_session_token}.
|
711
|
+
#
|
712
|
+
# This method's mirror is {read_app_content}.
|
713
|
+
def self.write_app_content( content_header, app_database )
|
714
|
+
|
715
|
+
# --
|
716
|
+
# -- Get the filepath to the breadcrumbs file using the trail in
|
717
|
+
# -- the global configuration left by {use_application_domain}.
|
718
|
+
# --
|
719
|
+
crumbs_db = get_crumbs_db_from_session_token()
|
720
|
+
|
721
|
+
# --
|
722
|
+
# -- Get the path to the file holding the ciphertext of the application
|
723
|
+
# -- database content locked by the content encryption key.
|
724
|
+
# --
|
725
|
+
crypt_filepath = content_ciphertxt_file_from_session_token()
|
726
|
+
|
727
|
+
# --
|
728
|
+
# -- Regenerate intra-session key from the session token.
|
729
|
+
# --
|
730
|
+
intra_key = KeyLocal.regenerate_shell_key( to_token() )
|
731
|
+
|
732
|
+
# --
|
733
|
+
# -- Decrypt and acquire the content enryption key that was created
|
734
|
+
# -- during the login use case and encrypted using the intra sessionary
|
735
|
+
# -- key.
|
736
|
+
# --
|
737
|
+
unique_id = KeyId.derive_universal_id( read_app_id(), to_token() )
|
738
|
+
crumbs_db.use( unique_id )
|
739
|
+
power_key = intra_key.do_decrypt_key( crumbs_db.get( INTRA_KEY_CIPHERTEXT ) )
|
740
|
+
|
741
|
+
# --
|
742
|
+
# -- Create a new random initialization vector (iv) to use when
|
743
|
+
# -- encrypting the incoming database content before writing it
|
744
|
+
# -- out to the file at the crypt filepath.
|
745
|
+
# --
|
746
|
+
iv_base64_chars = KeyIV.new().for_storage()
|
747
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
748
|
+
crumbs_db.set( INDEX_DB_CRYPT_IV_KEY, iv_base64_chars )
|
749
|
+
random_iv = KeyIV.in_binary( iv_base64_chars )
|
750
|
+
|
751
|
+
# --
|
752
|
+
# -- Now we use the content encryption (power) key and the random initialization
|
753
|
+
# -- vector (iv) to first encrypt the incoming content and then to Base64 encode
|
754
|
+
# -- the result. This is then written into the crypt filepath derived earlier.
|
755
|
+
# --
|
756
|
+
binary_ciphertext = power_key.do_encrypt_text( random_iv, app_database.to_json )
|
757
|
+
binary_to_write( crypt_filepath, content_header, binary_ciphertext )
|
758
|
+
|
759
|
+
end
|
760
|
+
|
761
|
+
|
762
|
+
# Register the URL to the <b>frontend keystore</b> that is tied to
|
763
|
+
# this application instance on this workstation (and user). The default
|
764
|
+
# keystore sits on an accessible filesystem that is preferably a
|
765
|
+
# removable drive (like a USB key or phone) which allows the keys to
|
766
|
+
# your secrets to travel with you in your pocket.
|
767
|
+
#
|
768
|
+
# <b>Changing the Keystore Url</b>
|
769
|
+
#
|
770
|
+
# If the keystore url has already been configured this method will overwrite
|
771
|
+
# (thereby updating) it.
|
772
|
+
#
|
773
|
+
# <b>Changing the Keystore Url</b>
|
774
|
+
#
|
775
|
+
# The keystore directives in the global configuration file looks like this.
|
776
|
+
#
|
777
|
+
# [keystore.ids]
|
778
|
+
# dxEy-v2w3-x7y8 = /media/usb_key/family.creds
|
779
|
+
# 47S3-Nv0w-8SYf = /media/usb_key/friend.creds
|
780
|
+
# 3Dds-8Tts-Jy2G = /media/usb_key/office.creds
|
781
|
+
#
|
782
|
+
# <b>Which Use Case Sets the Keystore Url?</b>
|
783
|
+
#
|
784
|
+
# The keystore url must be provided <b>the very first time</b> init
|
785
|
+
# is called for an app instance on a machine. If the configuration
|
786
|
+
# is wiped, the next initialize use case must again provide it.
|
787
|
+
#
|
788
|
+
# <b>How to Add (Extend) Storage Services</b>
|
789
|
+
#
|
790
|
+
# We could use Redis, PostgreSQL, even a Rest API to provide storage
|
791
|
+
# services. To extend it - make a keystore ID boss its own section and
|
792
|
+
# then add keypairs like
|
793
|
+
#
|
794
|
+
# - the keystore URL
|
795
|
+
# - the keystore Type (or interface class)
|
796
|
+
# - keystore create destroy markers
|
797
|
+
#
|
798
|
+
# @param keystore_url [String]
|
799
|
+
# The keystore url points to where the key metadata protecting
|
800
|
+
# this application instance lives. The simplest keystores are
|
801
|
+
# based on files and for them this url is just a folder path.
|
802
|
+
#
|
803
|
+
# @raise [KeyError]
|
804
|
+
#
|
805
|
+
# The keystore URL cannot be <b>NEW</b>. The <b>NEW acronym</b> asserts
|
806
|
+
# that the attribute is
|
807
|
+
#
|
808
|
+
# - neither <b>N</b>il
|
809
|
+
# - nor <b>E</b>mpty
|
810
|
+
# - nor <b>W</b>hitespace only
|
811
|
+
#
|
812
|
+
def register_keystore keystore_url
|
813
|
+
KeyError.not_new( keystore_url, self )
|
814
|
+
@keymap.write( @aim_id, KEYSTORE_IDENTIFIER_KEY, keystore_url )
|
815
|
+
end
|
816
|
+
|
817
|
+
|
818
|
+
# Generate a new set of envelope breadcrumbs, derive the new envelope
|
819
|
+
# filepath, then <b>encrypt</b> the raw envelope content, and write the
|
820
|
+
# resulting ciphertext out into the new file.
|
821
|
+
#
|
822
|
+
# The important parameters in play are the
|
823
|
+
#
|
824
|
+
# - session token used to find the storage folder
|
825
|
+
# - random envelope external ID used to name the ciphertext file
|
826
|
+
# - generated random key for encrypting and decrypting the content
|
827
|
+
# - generated random initialization vector (IV) for crypting
|
828
|
+
# - name of the file in which the locked content is placed
|
829
|
+
# - header and footer content that tops and tails the ciphertext
|
830
|
+
#
|
831
|
+
# @param crumbs_map [Hash]
|
832
|
+
#
|
833
|
+
# nothing is read from this crumbs map but 3 things are written to
|
834
|
+
# it with these corresponding key names
|
835
|
+
#
|
836
|
+
# - random content external ID {CONTENT_EXTERNAL_ID}
|
837
|
+
# - high entropy crypt key {CONTENT_ENCRYPT_KEY}
|
838
|
+
# - and initialization vector {CONTENT_RANDOM_IV}
|
839
|
+
#
|
840
|
+
# @param content_body [String]
|
841
|
+
#
|
842
|
+
# this is the envelope's latest and greatest content that will
|
843
|
+
# be encrypted, encoded, topped, tailed and then pushed out to
|
844
|
+
# the domain's storage folder.
|
845
|
+
#
|
846
|
+
# @param content_header [String]
|
847
|
+
#
|
848
|
+
# the string that will top the ciphertext content when it is written
|
849
|
+
#
|
850
|
+
def self.content_lock( crumbs_map, content_body, content_header )
|
851
|
+
|
852
|
+
# --
|
853
|
+
# -- Create the external content ID and place
|
854
|
+
# -- it within the crumbs map.
|
855
|
+
# --
|
856
|
+
content_exid = get_random_reference()
|
857
|
+
crumbs_map[ CONTENT_EXTERNAL_ID ] = content_exid
|
858
|
+
|
859
|
+
# --
|
860
|
+
# -- Create a random initialization vector (iv)
|
861
|
+
# -- for AES encryption and store it within the
|
862
|
+
# -- breadcrumbs map.
|
863
|
+
# --
|
864
|
+
iv_base64 = KeyIV.new().for_storage()
|
865
|
+
random_iv = KeyIV.in_binary( iv_base64 )
|
866
|
+
crumbs_map[ CONTENT_RANDOM_IV ] = iv_base64
|
867
|
+
|
868
|
+
# --
|
869
|
+
# -- Create a new high entropy random key for
|
870
|
+
# -- locking the content with AES. Place the key
|
871
|
+
# -- within the breadcrumbs map.
|
872
|
+
# --
|
873
|
+
crypt_key = Key.from_random()
|
874
|
+
crumbs_map[ CONTENT_ENCRYPT_KEY ] = crypt_key.to_char64()
|
875
|
+
|
876
|
+
# --
|
877
|
+
# -- Now use AES to lock the content body and write
|
878
|
+
# -- the encoded ciphertext out to a file that is
|
879
|
+
# -- topped with the parameter content header.
|
880
|
+
# --
|
881
|
+
binary_ctext = crypt_key.do_encrypt_text( random_iv, content_body )
|
882
|
+
content_path = content_filepath( content_exid )
|
883
|
+
binary_to_write( content_path, content_header, binary_ctext )
|
884
|
+
|
885
|
+
end
|
886
|
+
|
887
|
+
|
888
|
+
# Use the content's external id expected in the breadcrumbs together with
|
889
|
+
# the session token to derive the content's filepath and then unlock and
|
890
|
+
# the content as a {KeyDb} structure.
|
891
|
+
#
|
892
|
+
# Unlocking the content means reading it, decoding and then decrypting it using
|
893
|
+
# the initialization vector (iv) and decryption key whose values are expected
|
894
|
+
# within the breadcrumbs map.
|
895
|
+
#
|
896
|
+
# @param crumbs_map [Hash]
|
897
|
+
#
|
898
|
+
# the three (3) data points expected within the breadcrumbs map are the
|
899
|
+
#
|
900
|
+
# - content's external ID {CONTENT_EXTERNAL_ID}
|
901
|
+
# - AES encryption key {CONTENT_ENCRYPT_KEY}
|
902
|
+
# - initialization vector {CONTENT_RANDOM_IV}
|
903
|
+
#
|
904
|
+
def self.content_unlock( crumbs_map )
|
905
|
+
|
906
|
+
# --
|
907
|
+
# -- Get the external ID of the content then use
|
908
|
+
# -- that plus the session context to derive the
|
909
|
+
# -- content's ciphertext filepath.
|
910
|
+
# --
|
911
|
+
content_path = content_filepath( crumbs_map[ CONTENT_EXTERNAL_ID ] )
|
912
|
+
|
913
|
+
# --
|
914
|
+
# -- Read the binary ciphertext of the content
|
915
|
+
# -- from the file. Then decrypt it using the
|
916
|
+
# -- AES crypt key and intialization vector.
|
917
|
+
# --
|
918
|
+
crypt_txt = binary_from_read( content_path )
|
919
|
+
random_iv = KeyIV.in_binary( crumbs_map[ CONTENT_RANDOM_IV ] )
|
920
|
+
crypt_key = Key.from_char64( crumbs_map[ CONTENT_ENCRYPT_KEY ] )
|
921
|
+
json_text = crypt_key.do_decrypt_text( random_iv, crypt_txt )
|
922
|
+
|
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 )
|
928
|
+
|
929
|
+
end
|
930
|
+
|
931
|
+
|
932
|
+
# This method returns the <b>content filepath</b> which (at its core)
|
933
|
+
# is an amalgam of the application's (domain) identifier and the content's
|
934
|
+
# external identifier (XID).
|
935
|
+
#
|
936
|
+
# The filename is prefixed by {CONTENT_FILE_PREFIX}.
|
937
|
+
#
|
938
|
+
# @param external_id [String]
|
939
|
+
#
|
940
|
+
# nothing is read from this crumbs map but 3 things are written to
|
941
|
+
# it with these corresponding key names
|
942
|
+
#
|
943
|
+
# - random content external ID {CONTENT_EXTERNAL_ID}
|
944
|
+
# - high entropy crypt key {CONTENT_ENCRYPT_KEY}
|
945
|
+
# - and initialization vector {CONTENT_RANDOM_IV}
|
946
|
+
def self.content_filepath( external_id )
|
947
|
+
|
948
|
+
app_identity = read_app_id()
|
949
|
+
store_folder = get_store_folder()
|
950
|
+
env_filename = "#{CONTENT_FILE_PREFIX}.#{external_id}.#{app_identity}.txt"
|
951
|
+
env_filepath = File.join( store_folder, env_filename )
|
952
|
+
return env_filepath
|
953
|
+
|
954
|
+
end
|
955
|
+
|
956
|
+
|
957
|
+
# If the <b>content dictionary is not nil</b> and contains a key named
|
958
|
+
# {CONTENT_EXTERNAL_ID} then we return true as we expect the content
|
959
|
+
# ciphertext and its corresponding file to exist.
|
960
|
+
#
|
961
|
+
# This method throws an exception if they key exists but there is no
|
962
|
+
# file at the expected location.
|
963
|
+
#
|
964
|
+
# @param crumbs_map [Hash]
|
965
|
+
#
|
966
|
+
# we test for the existence of the constant {CONTENT_EXTERNAL_ID}
|
967
|
+
# and if it exists we assert that the content filepath should also
|
968
|
+
# be present.
|
969
|
+
#
|
970
|
+
def self.content_exists?( crumbs_map )
|
971
|
+
|
972
|
+
return false if crumbs_map.nil?
|
973
|
+
return false unless crumbs_map.has_key?( CONTENT_EXTERNAL_ID )
|
974
|
+
|
975
|
+
external_id = crumbs_map[ CONTENT_EXTERNAL_ID ]
|
976
|
+
the_filepath = content_filepath( external_id )
|
977
|
+
error_string = "External ID #{external_id} found but no file at #{the_filepath}"
|
978
|
+
raise RuntimeException, error_string unless File.file?( the_filepath )
|
979
|
+
|
980
|
+
return true
|
981
|
+
|
982
|
+
end
|
983
|
+
|
984
|
+
|
985
|
+
# Construct the header for the ciphertext content files written out
|
986
|
+
# onto the filesystem.
|
987
|
+
#
|
988
|
+
# @param gem_version [String] the current version number of the calling gem
|
989
|
+
# @param gem_name [String] the current name of the calling gem
|
990
|
+
# @param gem_site [String] the current website of the calling gem
|
991
|
+
#
|
992
|
+
# @param the_domain_name [String]
|
993
|
+
#
|
994
|
+
# This method uses one of the two (2) ways to gain the application id.
|
995
|
+
#
|
996
|
+
# If not logged in callers will have the domain name and should pass it
|
997
|
+
# in so that this method can use {KeyId.derive_app_instance_identifier}
|
998
|
+
# to gain the application id.
|
999
|
+
#
|
1000
|
+
# If logged in then method {KeyApi.use_application_domain} will have
|
1001
|
+
# executed and the application ID will be written inside the
|
1002
|
+
# <b>machine configuration file</b> under the application instance on
|
1003
|
+
# machine id and referenced in turn from the {SESSION_APP_DOMAINS} map.
|
1004
|
+
#
|
1005
|
+
# In the above case post a NIL domain name and this method will now
|
1006
|
+
# turn to {KeyApi.read_app_id} for the application id.
|
1007
|
+
def self.format_header( gem_version, gem_name, gem_site, the_domain_name = nil )
|
1008
|
+
|
1009
|
+
application_id = KeyId.derive_app_instance_identifier(the_domain_name) unless the_domain_name.nil?
|
1010
|
+
application_id = read_app_id() if the_domain_name.nil?
|
1011
|
+
universal_id = KeyId.derive_universal_id( application_id, to_token() )
|
1012
|
+
|
1013
|
+
line1 = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
|
1014
|
+
line2 = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
|
1015
|
+
line3 = "#{gem_name} ciphertext block\n"
|
1016
|
+
line4 = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
|
1017
|
+
line5 = "App Ref Num := #{application_id}\n" # application domain reference
|
1018
|
+
line6 = "Access Time := #{KeyNow.grab()}\n" # timestamp of the last write
|
1019
|
+
line7 = "App Version := #{gem_version}\n" # this application semantic version
|
1020
|
+
line8 = "Website Url := #{gem_site}\n" # app website or github url
|
1021
|
+
line9 = "Session Ref := #{universal_id}\n" # application domain reference
|
1022
|
+
|
1023
|
+
return line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8 + line9
|
1024
|
+
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
|
1028
|
+
private
|
1029
|
+
|
1030
|
+
|
1031
|
+
|
1032
|
+
MACHINE_CONFIG_FILE = File.join( Dir.home, ".config/openkey/openkey.app.config.ini" )
|
1033
|
+
|
1034
|
+
SESSION_APP_DOMAINS = "session.app.domains"
|
1035
|
+
TOKEN_VARIABLE_NAME = "OPEN_SESSION_TOKEN"
|
1036
|
+
TOKEN_VARIABLE_SIZE = 150
|
1037
|
+
|
1038
|
+
SESSION_IDENTIFIER_KEY = "session.identifiers"
|
1039
|
+
KEYSTORE_IDENTIFIER_KEY = "keystore.url.id"
|
1040
|
+
APP_INSTANCE_ID_KEY = "app.instance.id"
|
1041
|
+
AIM_IDENTITY_REF_KEY = "aim.identity.ref"
|
1042
|
+
LOGIN_TIMESTAMP_KEY = "login.timestamp"
|
1043
|
+
LOGOUT_TIMESTAMP_KEY = "logout.timestamp"
|
1044
|
+
MACHINE_CONFIGURATION = "machine.configuration"
|
1045
|
+
APP_INITIALIZE_TIME = "initialize.time"
|
1046
|
+
|
1047
|
+
APP_INSTANCE_SETUP_TIME = "app.instance.setup.time"
|
1048
|
+
|
1049
|
+
APP_KEY_DB_NAME_PREFIX = "openkey.breadcrumbs"
|
1050
|
+
FILE_CIPHERTEXT_PREFIX = "openkey.cipher.file"
|
1051
|
+
OK_BASE_FOLDER_PREFIX = "openkey.store"
|
1052
|
+
OK_BACKEND_CRYPT_PREFIX = "backend.crypt"
|
1053
|
+
|
1054
|
+
APP_KEY_DB_DIRECTIVES = "key.db.directives"
|
1055
|
+
APP_KEY_DB_CREATE_TIME_KEY = "initialize.time"
|
1056
|
+
APP_KEY_DB_BREAD_CRUMBS = "openkey.bread.crumbs"
|
1057
|
+
|
1058
|
+
LOGGED_IN_APP_SESSION_ID = "logged.in.app.session.id"
|
1059
|
+
SESSION_LOGIN_DATETIME = "session.login.datetime"
|
1060
|
+
SESSION_LOGOUT_DATETIME = "session.logout.datetime"
|
1061
|
+
|
1062
|
+
INTER_KEY_CIPHERTEXT = "inter.key.ciphertext"
|
1063
|
+
INTRA_KEY_CIPHERTEXT = "intra.key.ciphertext"
|
1064
|
+
INDEX_DB_CRYPT_IV_KEY = "index.db.cipher.iv"
|
1065
|
+
|
1066
|
+
BLOCK_64_START_STRING = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab\n"
|
1067
|
+
BLOCK_64_END_STRING = "ba9876543210fedcba9876543210fedcba9876543210fedcba9876543210\n"
|
1068
|
+
BLOCK_64_DELIMITER = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
|
1069
|
+
|
1070
|
+
XID_SOURCE_APPROX_LEN = 11
|
1071
|
+
|
1072
|
+
CONTENT_FILE_PREFIX = "tree.db"
|
1073
|
+
CONTENT_EXTERNAL_ID = "content.xid"
|
1074
|
+
CONTENT_ENCRYPT_KEY = "content.key"
|
1075
|
+
CONTENT_RANDOM_IV = "content.iv"
|
1076
|
+
|
1077
|
+
|
1078
|
+
def self.binary_to_write( to_filepath, content_header, binary_ciphertext )
|
1079
|
+
|
1080
|
+
base64_ciphertext = Base64.encode64( binary_ciphertext )
|
1081
|
+
|
1082
|
+
content_to_write =
|
1083
|
+
content_header +
|
1084
|
+
BLOCK_64_DELIMITER +
|
1085
|
+
BLOCK_64_START_STRING +
|
1086
|
+
base64_ciphertext +
|
1087
|
+
BLOCK_64_END_STRING +
|
1088
|
+
BLOCK_64_DELIMITER
|
1089
|
+
|
1090
|
+
File.write( to_filepath, content_to_write )
|
1091
|
+
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
|
1095
|
+
def self.binary_from_read( from_filepath )
|
1096
|
+
|
1097
|
+
file_text = File.read( from_filepath )
|
1098
|
+
core_data = file_text.in_between( BLOCK_64_START_STRING, BLOCK_64_END_STRING ).strip
|
1099
|
+
return Base64.decode64( core_data )
|
1100
|
+
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
|
1104
|
+
def self.get_random_reference
|
1105
|
+
|
1106
|
+
# Do not forget that you can pass this through
|
1107
|
+
# the derive identifier method if uniformity is
|
1108
|
+
# what you seek.
|
1109
|
+
#
|
1110
|
+
# [ KeyId.derive_identifier( reference ) ]
|
1111
|
+
#
|
1112
|
+
random_ref = SecureRandom.urlsafe_base64( XID_SOURCE_APPROX_LEN ).delete("-_").downcase
|
1113
|
+
return random_ref[ 0 .. ( XID_SOURCE_APPROX_LEN - 1 ) ]
|
1114
|
+
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
|
1118
|
+
def self.get_virgin_content( domain_name )
|
1119
|
+
|
1120
|
+
KeyError.not_new( domain_name, self )
|
1121
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
1122
|
+
|
1123
|
+
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 )
|
1127
|
+
return initial_db.to_json
|
1128
|
+
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
|
1132
|
+
# This method depends on {use_application_domain} which sets
|
1133
|
+
# the application ID against the session identity so only call
|
1134
|
+
# it if we are in a logged in state.
|
1135
|
+
#
|
1136
|
+
# NOTE this will NOT be set until the session is logged in so
|
1137
|
+
# the call fails before that. For this reason do not call this
|
1138
|
+
# method from outside this class. If the domain name is
|
1139
|
+
# available use {KeyId.derive_app_instance_identifier} instead.
|
1140
|
+
def self.read_app_id()
|
1141
|
+
|
1142
|
+
aim_id = read_aim_id()
|
1143
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
1144
|
+
keypairs.use( aim_id )
|
1145
|
+
return keypairs.get( APP_INSTANCE_ID_KEY )
|
1146
|
+
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
|
1150
|
+
def self.read_aim_id()
|
1151
|
+
|
1152
|
+
session_identifier = KeyId.derive_session_id( to_token() )
|
1153
|
+
|
1154
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
1155
|
+
keypairs.use( SESSION_APP_DOMAINS )
|
1156
|
+
return keypairs.get( session_identifier )
|
1157
|
+
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
|
1161
|
+
def self.get_crumbs_db_from_domain_name( domain_name )
|
1162
|
+
|
1163
|
+
KeyError.not_new( domain_name, self )
|
1164
|
+
keystore_file = get_keystore_file_from_domain_name( domain_name )
|
1165
|
+
crumbs_db = KeyPair.new( keystore_file )
|
1166
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
1167
|
+
return crumbs_db
|
1168
|
+
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
|
1172
|
+
def self.get_crumbs_db_from_session_token()
|
1173
|
+
|
1174
|
+
keystore_file = get_keystore_file_from_session_token()
|
1175
|
+
crumbs_db = KeyPair.new( keystore_file )
|
1176
|
+
crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS )
|
1177
|
+
return crumbs_db
|
1178
|
+
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
|
1182
|
+
def self.get_store_folder()
|
1183
|
+
|
1184
|
+
aim_id = read_aim_id()
|
1185
|
+
app_id = read_app_id()
|
1186
|
+
return get_app_keystore_folder( aim_id, app_id )
|
1187
|
+
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
|
1191
|
+
def self.get_app_keystore_folder( aim_id, app_id )
|
1192
|
+
|
1193
|
+
keypairs = KeyPair.new( MACHINE_CONFIG_FILE )
|
1194
|
+
keypairs.use( aim_id )
|
1195
|
+
keystore_url = keypairs.get( KEYSTORE_IDENTIFIER_KEY )
|
1196
|
+
basedir_name = "#{OK_BASE_FOLDER_PREFIX}.#{app_id}"
|
1197
|
+
return File.join( keystore_url, basedir_name )
|
1198
|
+
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
|
1202
|
+
def self.get_keystore_file_from_domain_name( domain_name )
|
1203
|
+
|
1204
|
+
aim_id = KeyId.derive_app_instance_machine_id( domain_name )
|
1205
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
1206
|
+
|
1207
|
+
app_key_db_file = "#{APP_KEY_DB_NAME_PREFIX}.#{app_id}.ini"
|
1208
|
+
return File.join( get_app_keystore_folder( aim_id, app_id ), app_key_db_file )
|
1209
|
+
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
|
1213
|
+
def self.get_keystore_file_from_session_token()
|
1214
|
+
|
1215
|
+
aim_id = read_aim_id()
|
1216
|
+
app_id = read_app_id()
|
1217
|
+
|
1218
|
+
app_key_db_file = "#{APP_KEY_DB_NAME_PREFIX}.#{app_id}.ini"
|
1219
|
+
return File.join( get_app_keystore_folder( aim_id, app_id ), app_key_db_file )
|
1220
|
+
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
|
1224
|
+
def self.content_ciphertxt_file_from_domain_name( domain_name )
|
1225
|
+
|
1226
|
+
aim_id = KeyId.derive_app_instance_machine_id( domain_name )
|
1227
|
+
app_id = KeyId.derive_app_instance_identifier( domain_name )
|
1228
|
+
|
1229
|
+
appdb_cipher_file = "#{FILE_CIPHERTEXT_PREFIX}.#{app_id}.txt"
|
1230
|
+
return File.join( get_app_keystore_folder( aim_id, app_id ), appdb_cipher_file )
|
1231
|
+
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
|
1235
|
+
def self.content_ciphertxt_file_from_session_token()
|
1236
|
+
|
1237
|
+
aim_id = read_aim_id()
|
1238
|
+
app_id = read_app_id()
|
1239
|
+
|
1240
|
+
appdb_cipher_file = "#{FILE_CIPHERTEXT_PREFIX}.#{app_id}.txt"
|
1241
|
+
return File.join( get_app_keystore_folder( aim_id, app_id ), appdb_cipher_file )
|
1242
|
+
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
|
1246
|
+
def self.to_token()
|
1247
|
+
|
1248
|
+
raw_env_var_value = ENV[TOKEN_VARIABLE_NAME]
|
1249
|
+
raise_token_error( TOKEN_VARIABLE_NAME, "not present") unless raw_env_var_value
|
1250
|
+
|
1251
|
+
env_var_value = raw_env_var_value.strip
|
1252
|
+
raise_token_error( TOKEN_VARIABLE_NAME, "consists only of whitespace") if raw_env_var_value.empty?
|
1253
|
+
|
1254
|
+
size_msg = "length should contain exactly #{TOKEN_VARIABLE_SIZE} characters"
|
1255
|
+
raise_token_error( TOKEN_VARIABLE_NAME, size_msg ) unless env_var_value.length == TOKEN_VARIABLE_SIZE
|
1256
|
+
|
1257
|
+
return env_var_value
|
1258
|
+
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
|
1262
|
+
def raise_token_error env_var_name, message
|
1263
|
+
|
1264
|
+
puts ""
|
1265
|
+
puts "#{TOKEN_VARIABLE_NAME} environment variable #{message}."
|
1266
|
+
puts "To instantiate it you can use the below command."
|
1267
|
+
puts ""
|
1268
|
+
puts "$ export OPEN_SESSION_TOKEN=`ops token`"
|
1269
|
+
puts ""
|
1270
|
+
puts "ps => those are backticks around `ops token` (not apostrophes)."
|
1271
|
+
puts ""
|
1272
|
+
|
1273
|
+
raise RuntimeError, "#{TOKEN_VARIABLE_NAME} environment variable #{message}."
|
1274
|
+
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
|
1281
|
+
end
|