opensecret 0.0.988 → 0.0.9925
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|