Class: SafeDb::KeyApi
- Inherits:
-
Object
- Object
- SafeDb::KeyApi
- Defined in:
- lib/keytools/key.api.rb
Overview
Use RubyMine to understand the correlations and dependencies on this now monolithic class that must be broken up before meaningful effective and efficient progress can be made.
REFACTOR KEY API TO DRAW OUT POSSIBLY THESE FIVE CONCEPTS.
- 1
-
the safe tty token
- 2
-
the machine configurations in ~/.config/openkey/openkey.app.config.ini
- 3
-
the login / logout session crumbs database
- 4
-
the master content database holding local config, chapters and verses
- 5
-
the safe databases that unmarshal into either JSON or file content
Use the key applications programming interface to transition the state of three (3) core keys in accordance with the needs of the executing use case.
KeyApi | The 3 Keys
The three keys service the needs of a command line application that executes within a shell environment in a unix envirronment or a command prompt in windows.
So what are the 3 keys and what is their purpose.
-
shell key | exists to lock the index key created at login
-
human key | exists to lock the index key created at login
-
index key | exists to lock the application’s index file
So why do two keys (the shell key and human key) exist to lock the same index key?
KeyApi | Why Lock the Index Key Twice?
On this login, the previous login’s human key is regenerated from the human password and the saved salts. This old human key decrypts and reveals the old index key which in turn decrypts and reveals the index string.
Both the old human key and the old index key are discarded.
Then 48 bytes of randomness are sourced to generate the new index key. This key encrypts the now decrypted index string and is thrown away. The password sources a new human key (the salts are saved), and this new key locks the index key’s source bytes.
The shell key again locks the index key’s source bytes. Why twice?
-
during subsequent shell command calls the human key is unavailable however the index key can be accessed via the shell key.
-
when the shell dies (or logout is issued) the shell key dies. Now the index key can only be accessed by a login when the password is made available.
That is why the index key is locked twice. The shell key opens it mid-session and the regenerated human key opens it during the login of the next session.
The LifeCycle of each Key
It seems odd that the human key is born during this login then dies at the very next one (as stated below). This is because the human key isn’t the password, the human key is sourced from the password.
So when are the 3 keys born and when do they cease being.
-
shell key | is born when the shell is created and dies when the shell dies
-
human key | is born when the user logs in this time and dies at the next login
-
index key | the life of the index key exactly mirrors that of the human key
The 7 Key API Calls
| - | -------- | ------------ | ------------------------------- |
| # | Rationale | Use Case | Goals | Tasks |
| - | ------------------------------- | ------------ | ------------------------------- |
| 1 | Create and Obfuscate Shell Key | key | x | y |
| 2 | New App Instance on Workstation | init | x | y |
| 3 | Login to App Instance in Shell | login | x | y |
Class Method Summary collapse
-
.content_filepath(external_id) ⇒ Object
This method returns the content filepath which (at its core) is an amalgam of the application’s (domain) identifier and the content’s external identifier (XID).
-
.content_lock(crumbs_map, content_body, content_header) ⇒ Object
Generate a new set of envelope breadcrumbs, derive the new envelope filepath, then encrypt the raw envelope content, and write the resulting ciphertext out into the new file.
-
.content_unlock(crumbs_map) ⇒ Object
Use the content’s external id expected in the breadcrumbs together with the session token to derive the content’s filepath and then unlock and the content as a KeyDb structure.
-
.db_envelope_exists?(crumbs_map) ⇒ Boolean
If the content dictionary is not nil and contains a key named CONTENT_EXTERNAL_ID then we return true as we expect the content ciphertext and its corresponding file to exist.
-
.do_login(domain_name, domain_secret, content_header) ⇒ Object
At the end of a successful login the old content crypt key will have been re-acquired and discarded, with a fresh one createdand put to work protecting the application’s content.
-
.do_logout(domain_name) ⇒ Object
Logout of the shell key session by making the high entropy content encryption key irretrievable for all intents and purposes to anyone who does not possess the domain secret.
-
.format_header(gem_version, gem_name, gem_site, the_domain_name = nil) ⇒ Object
Construct the header for the ciphertext content files written out onto the filesystem.
-
.init_app_domain(domain_name, keystore_url) ⇒ Object
This method should only be called once for each application instance resident on a workstation (machine) and it derives and writes the identifiers into the openkey configuration file.
-
.is_domain_keys_setup?(domain_name) ⇒ Boolean
Has the inter-sessionary key ( derived from a human secret ) been setup for the application shard referenced in the parameter?.
-
.is_logged_in?(domain_name) ⇒ Boolean
Has the user orchestrating this shell session logged in? Yes or no? If yes then they appear to have supplied the correct secret.
-
.read_master_db(use_grandparent_pid = false) ⇒ String
To read the content we first find the appropriate shell key and the appropriate database ciphertext, one decrypts the other to produce the master database decryption key which in turn reveals the JSON representation of the master database.
-
.recycle_keys(domain_name, domain_secret, crumbs_db, content_header, the_content) ⇒ Object
Recycle the inter-sessionary key (based on the secret) and create a new content encryption (power) key and lock the parameter content with it before returning the new content encryption key.
-
.setup_domain_keys(domain_name, domain_secret, content_header) ⇒ Object
Transform the domain secret into a key, use that key to lock the power key, delete the secret and keys and leave behind a trail of breadcrumbs sprinkled to allow the inter-sessionary key to be regenerated at the next login.
-
.to_db_create_date(the_master_db) ⇒ String
Return a date/time string detailing when the master database was first created.
-
.to_db_domain_id(the_master_db) ⇒ String
Return the domain ID of the master database.
-
.to_db_domain_name(the_master_db) ⇒ String
Return the domain name of the master database.
-
.to_matching_dictionary(the_master_db, start_string) ⇒ Hash
Return a dictionary containing a string key and the corresponding master database value whenever the master database key starts with the parameter string.
-
.use_application_domain(domain_name) ⇒ Object
Switch the application instance that the current shell session is using.
-
.write_master_db(content_header, app_database) ⇒ Object
This write content behaviour takes the parameter content, encyrpts and encodes it using the index key, which is itself derived from the shell key unlocking the intra session ciphertext.
Instance Method Summary collapse
-
#register_keystore(keystore_url) ⇒ Object
Register the URL to the frontend keystore that is tied to this application instance on this workstation (and user).
Class Method Details
.content_filepath(external_id) ⇒ Object
This method returns the content filepath which (at its core) is an amalgam of the application’s (domain) identifier and the content’s external identifier (XID).
The filename is prefixed by CONTENT_FILE_PREFIX.
1049 1050 1051 1052 1053 1054 1055 1056 1057 |
# File 'lib/keytools/key.api.rb', line 1049 def self.content_filepath( external_id ) app_identity = read_app_id() store_folder = get_store_folder() env_filename = "#{CONTENT_FILE_PREFIX}.#{external_id}.#{app_identity}.txt" env_filepath = File.join( store_folder, env_filename ) return env_filepath end |
.content_lock(crumbs_map, content_body, content_header) ⇒ Object
Generate a new set of envelope breadcrumbs, derive the new envelope filepath, then encrypt the raw envelope content, and write the resulting ciphertext out into the new file.
The important parameters in play are the
-
session token used to find the storage folder
-
random envelope external ID used to name the ciphertext file
-
generated random key for encrypting and decrypting the content
-
generated random initialization vector (IV) for crypting
-
name of the file in which the locked content is placed
-
header and footer content that tops and tails the ciphertext
957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 |
# File 'lib/keytools/key.api.rb', line 957 def self.content_lock( crumbs_map, content_body, content_header ) # -- # -- Create the external content ID and place # -- it within the crumbs map. # -- content_exid = get_random_reference() crumbs_map[ CONTENT_EXTERNAL_ID ] = content_exid # -- # -- Create a random initialization vector (iv) # -- for AES encryption and store it within the # -- breadcrumbs map. # -- iv_base64 = KeyIV.new().for_storage() random_iv = KeyIV.in_binary( iv_base64 ) crumbs_map[ CONTENT_RANDOM_IV ] = iv_base64 # -- # -- Create a new high entropy random key for # -- locking the content with AES. Place the key # -- within the breadcrumbs map. # -- crypt_key = Key.from_random() crumbs_map[ CONTENT_ENCRYPT_KEY ] = crypt_key.to_char64() # -- # -- Now use AES to lock the content body and write # -- the encoded ciphertext out to a file that is # -- topped with the parameter content header. # -- binary_ctext = crypt_key.do_encrypt_text( random_iv, content_body ) content_path = content_filepath( content_exid ) binary_to_write( content_path, content_header, binary_ctext ) end |
.content_unlock(crumbs_map) ⇒ Object
Use the content’s external id expected in the breadcrumbs together with the session token to derive the content’s filepath and then unlock and the content as a SafeDb::KeyDb structure.
Unlocking the content means reading it, decoding and then decrypting it using the initialization vector (iv) and decryption key whose values are expected within the breadcrumbs map.
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 |
# File 'lib/keytools/key.api.rb', line 1011 def self.content_unlock( crumbs_map ) # -- # -- Get the external ID of the content then use # -- that plus the session context to derive the # -- content's ciphertext filepath. # -- content_path = content_filepath( crumbs_map[ CONTENT_EXTERNAL_ID ] ) # -- # -- Read the binary ciphertext of the content # -- from the file. Then decrypt it using the # -- AES crypt key and intialization vector. # -- crypt_txt = binary_from_read( content_path ) random_iv = KeyIV.in_binary( crumbs_map[ CONTENT_RANDOM_IV ] ) crypt_key = Key.from_char64( crumbs_map[ CONTENT_ENCRYPT_KEY ] ) text_data = crypt_key.do_decrypt_text( random_iv, crypt_txt ) return text_data end |
.db_envelope_exists?(crumbs_map) ⇒ Boolean
If the content dictionary is not nil and contains a key named CONTENT_EXTERNAL_ID then we return true as we expect the content ciphertext and its corresponding file to exist.
This method throws an exception if they key exists but there is no file at the expected location.
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 |
# File 'lib/keytools/key.api.rb', line 1073 def self.db_envelope_exists?( crumbs_map ) return false if crumbs_map.nil? return false unless crumbs_map.has_key?( CONTENT_EXTERNAL_ID ) external_id = crumbs_map[ CONTENT_EXTERNAL_ID ] the_filepath = content_filepath( external_id ) error_string = "External ID #{external_id} found but no file at #{the_filepath}" raise RuntimeException, error_string unless File.file?( the_filepath ) return true end |
.do_login(domain_name, domain_secret, content_header) ⇒ Object
At the end of a successful login the old content crypt key will have been re-acquired and discarded, with a fresh one createdand put to work protecting the application’s content.
After reacquisitioning (but before discarding) the old crypt key, the app’s key-value database is silently decrypted with it then immediately re-encrypted with the newly created (and locked down) crypt key.
Login Recycles 3 things
The three (3) things recycled by this login are
-
the human key (sourced by putting the secret text through two key derivation functions)
-
the content crypt key (sourced from a random 48 byte sequence)
-
the content ciphertext (sourced by decrypting with the old and re-encrypting with the new)
Remember that the content crypt key is itself encrypted by two key entities.
The Inter and Intra Session Crypt Keys
This login use case is the only time in the session that the human provided secret is made available - hence the inter-session name.
The intra session key is employed by use case calls on within (intra) the session it was created within.
The Weakness of the Human Inter Sessionary Key
The weakest link in the human-sourced key is clearly the human. Yes it is strengthened by key derivation functions with cost parameters as high is tolerable, but despite and in spite of these efforts, poorly chosen short passwords are not infeasible to acquire through brute force.
The fallability is countered by invalidating and recycling the (inter session) key on every login, thus reducing the time frame available to an attacker.
The Weakness of the Shell Intra Sessionary Key
The shell key hails from a super random (infeasible to crack) source of 48 binary bytes. So what is its achilles heel?
The means of protecting the shell key is the weakness. The source of its protection key is a motley crue of data unique not just to the workstation, but the parent shell. This is also passed through key derivation functions to strengthen it.
Temporary Environment Variables
The shell key’s ciphertext lives as a short term environment variable so when the shell dies the ciphertext dies and any opportunity to resurrect the shell key dies with it.
A logout command removes the random iv and ciphertext forged when the shell acted to encrypt the content key. Even mid shell session, a logout renders the shell key worthless.
Which (BreadCrumbs) endure?
Only 4 things endure post the login (recycle) activities. These are the
-
salts and iteration counts used to generate the inter-session key
-
index key ciphertext after encryption using the inter-session key
-
index key ciphertext after encryption using the intra-session key
-
content ciphertext after the decrypt re-encrypt activities
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/keytools/key.api.rb', line 440 def self.do_login( domain_name, domain_secret, content_header ) # -- # -- Get the breadcrumbs trail. # -- crumbs_db = get_crumbs_db_from_domain_name( domain_name ) # -- # -- Get the old inter-sessionary key (created during the previous login) # -- Get the old content encryption (power) key (again created during the previous login) # -- Get the old random initialization vector (created during the previous login) # -- old_inter_key = KdfApi.regenerate_from_salts( domain_secret, crumbs_db ) old_power_key = old_inter_key.do_decrypt_key( crumbs_db.get( INTER_KEY_CIPHERTEXT ) ) old_random_iv = KeyIV.in_binary( crumbs_db.get( INDEX_DB_CRYPT_IV_KEY ) ) # -- # -- Read the binary text representing the encrypted content # -- that was last written by any use case capable of changing # -- the application database content. # -- from_filepath = content_ciphertxt_file_from_domain_name( domain_name ) old_crypt_txt = binary_from_read( from_filepath ) # -- # -- Decrypt the binary ciphertext that was last written by a use case # -- capable of changing the application database. # -- plain_content = old_power_key.do_decrypt_text( old_random_iv, old_crypt_txt ) # -- # -- Create a new power key and lock the content with it. # -- Create a new inter key and lock the power key with it. # -- Leave the necessary breadcrumbs for regeneration. # -- Return the new power key that re-locked the content. # -- power_key = recycle_keys( domain_name, domain_secret, crumbs_db, content_header, plain_content ) # -- # -- Regenerate intra-session key from the session token. # -- Encrypt power key for intra (in) session retrieval. # -- intra_key = KeyLocal.regenerate_shell_key( to_token(), true ) intra_txt = intra_key.do_encrypt_key( power_key ) # -- # -- Set the (ciphertext) breadcrumbs for re-acquiring the # -- content encryption (power) key during (inside) this # -- shell session. # -- app_id = KeyId.derive_app_instance_identifier( domain_name ) unique_id = KeyId.derive_universal_id( app_id, to_token() ) crumbs_db.use( unique_id ) crumbs_db.set( INTRA_KEY_CIPHERTEXT, intra_txt ) crumbs_db.set( SESSION_LOGIN_DATETIME, KeyNow.fetch() ) # -- # -- Switch the dominant application domain being used to # -- the domain that has just logged in. # -- use_application_domain( domain_name ) end |
.do_logout(domain_name) ⇒ Object
Logout of the shell key session by making the high entropy content encryption key irretrievable for all intents and purposes to anyone who does not possess the domain secret.
The key logout action is deleting the ciphertext originally produced when the intra-sessionary (shell) key encrypted the content encryption key.
Why Isn’t the Session Token Deleted?
The session token is left to die by natural causes so that we don’t interfere with other domain interactions that may be in progress within this shell session.
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 |
# File 'lib/keytools/key.api.rb', line 557 def self.do_logout( domain_name ) # --> @todo - user should ONLY type in logout | without domain name # --> @todo - user should ONLY type in logout | without domain name # --> @todo - user should ONLY type in logout | without domain name # --> @todo - user should ONLY type in logout | without domain name # --> @todo - user should ONLY type in logout | without domain name # --> ###################### # --> Login / Logout Time # --> ###################### # --> # --> During login you create a section heading same as the session ID # --> You then put the intra-key ciphertext there (from locking power key) # --> To check if a login has occurred we ensure this session's ID exists as a header in crumbs DB # --> On logout we remove the session ID and all the subsection crumbs (intra key ciphertext) # --> Logout makes it impossible to access the power key (now only by seret delivery and the inter key ciphertext) # --> # -- # -- Get the breadcrumbs trail. # -- crumbs_db = get_crumbs_db_from_domain_name( domain_name ) # -- # -- Set the (ciphertext) breadcrumbs for re-acquiring the # -- content encryption (power) key during (inside) this # -- shell session. # -- unique_id = KeyId.derive_universal_id( domain_name ) crumbs_db.use( unique_id ) crumbs_db.set( INTRA_KEY_CIPHERTEXT, intra_txt ) crumbs_db.set( SESSION_LOGOUT_DATETIME, KeyNow.fetch() ) end |
.format_header(gem_version, gem_name, gem_site, the_domain_name = nil) ⇒ Object
Construct the header for the ciphertext content files written out onto the filesystem.
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 |
# File 'lib/keytools/key.api.rb', line 1110 def self.format_header( gem_version, gem_name, gem_site, the_domain_name = nil ) application_id = KeyId.derive_app_instance_identifier(the_domain_name) unless the_domain_name.nil? application_id = read_app_id() if the_domain_name.nil? universal_id = KeyId.derive_universal_id( application_id, to_token() ) line1 = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" line2 = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" line3 = "#{gem_name} ciphertext block\n" line4 = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" line5 = "App Ref Num := #{application_id}\n" # application domain reference line6 = "Access Time := #{KeyNow.grab()}\n" # timestamp of the last write line7 = "App Version := #{gem_version}\n" # this application semantic version line8 = "Website Url := #{gem_site}\n" # app website or github url line9 = "Session Ref := #{universal_id}\n" # application domain reference return line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8 + line9 end |
.init_app_domain(domain_name, keystore_url) ⇒ Object
This method should only be called once for each application instance resident on a workstation (machine) and it derives and writes the identifiers into the openkey configuration file.
The Identifiers to Configure
The principal identifiers to derive and configure are the
-
identifier for the application instance on this machine
-
global identifier derived for the application instance
-
keystore url location for this app on this machine
-
time the above two identifiers were burned to disk
Set(App) Configuration File
Neither the file nor its parent folder need to exist. We attempt to create the directory path and then the file. After this method has executed the below directives will be added to the openkey application coniguration.
Config filepath is $HOME/.config/openkey/openkey.app.config.ini
[srn1-apzd]
app.instance.id = crnl-d3my
keystore.url.id = /home/joe/credentials/repo
initialize.time = Fri May 25 11:59:46 2018 ( 18145.1159.462 )
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/keytools/key.api.rb', line 125 def self.init_app_domain( domain_name, keystore_url ) KeyError.not_new( domain_name, self ) KeyError.not_new( keystore_url, self ) aim_id = KeyId.derive_app_instance_machine_id( domain_name ) app_id = KeyId.derive_app_instance_identifier( domain_name ) keypairs = KeyPair.new( MACHINE_CONFIG_FILE ) keypairs.use( aim_id ) keypairs.set( APP_INSTANCE_ID_KEY, app_id ) keypairs.set( KEYSTORE_IDENTIFIER_KEY, keystore_url ) keypairs.set( APP_INITIALIZE_TIME, KeyNow.fetch() ) # -- # -- Switch the dominant application domain being used to # -- the domain that is being initialized right here. # -- use_application_domain( domain_name ) end |
.is_domain_keys_setup?(domain_name) ⇒ Boolean
Has the inter-sessionary key ( derived from a human secret ) been setup for the application shard referenced in the parameter?
This method returns yes (true) if and only if
-
the application’s keystore file exists
-
the file contains a breadcrumbs section
-
crumbs exist for human key rederivation
If false return gives the go-ahead to
-
collect the human secret (in one of a number of ways)
-
pass it through key derivation functions
-
generate a high entropy power key and lock some initial content with it
-
use the key sourced from the human secret to lock the power key
-
throw away the secret, power key and human sourced key
-
save crumbs (ciphertext, salts, ivs) for content retrieval given secret
Note that the init_app_domain method must have been called on this machine with the name of this application instance and the keystore url. An error results if no file is found at the MACHINE_CONFIG_FILE path.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/keytools/key.api.rb', line 183 def self.is_domain_keys_setup?( domain_name ) KeyError.not_new( domain_name, self ) keypairs = KeyPair.new( MACHINE_CONFIG_FILE ) aim_id = KeyId.derive_app_instance_machine_id( domain_name ) app_id = KeyId.derive_app_instance_identifier( domain_name ) keypairs.use( aim_id ) keystore_file = get_keystore_file_from_domain_name( domain_name ) return false unless File.exists?( keystore_file ) crumbs_db = KeyPair.new( keystore_file ) return false unless crumbs_db.has_section?( APP_KEY_DB_BREAD_CRUMBS ) crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS ) return crumbs_db.contains?( INTER_KEY_CIPHERTEXT ) end |
.is_logged_in?(domain_name) ⇒ Boolean
Has the user orchestrating this shell session logged in? Yes or no? If yes then they appear to have supplied the correct secret
-
in this shell session
-
on this machine and
-
for this application instance
Use the crumbs found underneath the universal (session) ID within the main breadcrumbs file for this application instance.
Note that the system does not rely on this value for its security, it exists only to give a pleasant error message.
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 |
# File 'lib/keytools/key.api.rb', line 614 def self.is_logged_in?( domain_name ) ############## Write this code. ############## Write this code. ############## Write this code. ############## Write this code. ############## Write this code. ############## Write this code. ############## Write this code. return false unless File.exists?( frontend_keystore_file() ) crumbs_db = KeyPair.new( frontend_keystore_file() ) crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS ) return false unless crumbs_db.contains?( LOGGED_IN_APP_SESSION_ID ) recorded_id = crumbs_db.get( LOGGED_IN_APP_SESSION_ID ) return recorded_id.eql?( @uni_id ) end |
.read_master_db(use_grandparent_pid = false) ⇒ String
To read the content we first find the appropriate shell key and the appropriate database ciphertext, one decrypts the other to produce the master database decryption key which in turn reveals the JSON representation of the master database.
The master database JSON is deserialized as a Hash and returned.
Steps Taken To Read the Master Database
Reading the master database requires a rostra of actions namely
-
reading the path to the keystore breadcrumbs file
-
using the session token to derive the (unique to the) shell key
-
using the shell key and ciphertext to unlock the index key
-
reading the encrypted and encoded content, decoding and decrypting it
-
employing index key, ciphertext and random iv to reveal the content
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
# File 'lib/keytools/key.api.rb', line 744 def self.read_master_db( use_grandparent_pid = false ) # -- # -- Get the filepath to the breadcrumbs file using the trail in # -- the global configuration left by {use_application_domain}. # -- crumbs_db = get_crumbs_db_from_session_token() # -- # -- Get the path to the file holding the ciphertext of the application # -- database content locked by the content encryption key. # -- crypt_filepath = content_ciphertxt_file_from_session_token() # -- # -- Regenerate intra-session key from the session token. # -- ############ intra_key = KeyLocal.regenerate_shell_key( to_token(), use_grandparent_pid ) intra_key = KeyLocal.regenerate_shell_key( to_token(), true ) # -- # -- Decrypt and acquire the content enryption key that was created # -- during the login use case and encrypted using the intra sessionary # -- key. # -- unique_id = KeyId.derive_universal_id( read_app_id(), to_token() ) crumbs_db.use( unique_id ) power_key = intra_key.do_decrypt_key( crumbs_db.get( INTRA_KEY_CIPHERTEXT ) ) # -- # -- Set the (ciphertext) breadcrumbs for re-acquiring the # -- content encryption (power) key during (inside) this # -- shell session. # -- crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS ) random_iv = KeyIV.in_binary( crumbs_db.get( INDEX_DB_CRYPT_IV_KEY ) ) # -- # -- Get the full ciphertext file (warts and all) and then top and # -- tail until just the valuable ciphertext is at hand. Decode then # -- decrypt the ciphertext and instantiate a key database from the # -- resulting JSON string. # -- crypt_txt = binary_from_read( crypt_filepath ) json_content = power_key.do_decrypt_text( random_iv, crypt_txt ) return KeyDb.from_json( json_content ) end |
.recycle_keys(domain_name, domain_secret, crumbs_db, content_header, the_content) ⇒ Object
Recycle the inter-sessionary key (based on the secret) and create a new content encryption (power) key and lock the parameter content with it before returning the new content encryption key.
The content_ciphertxt_file_from_domain_name method is used to produce the path at which the ciphertext (resulting from locking the parameter content), is stored.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/keytools/key.api.rb', line 290 def self.recycle_keys( domain_name, domain_secret, crumbs_db, content_header, the_content ) KeyError.not_new( domain_name, self ) KeyError.not_new( domain_secret, self ) KeyError.not_new( the_content, self ) # -- # -- Create a random initialization vector (iv) # -- used for AES encryption of virgin content # -- iv_base64_chars = KeyIV.new().for_storage() crumbs_db.set( INDEX_DB_CRYPT_IV_KEY, iv_base64_chars ) random_iv = KeyIV.in_binary( iv_base64_chars ) # -- # -- Create a new high entropy power key # -- for encrypting the virgin content. # -- power_key = Key.from_random # -- # -- Encrypt the virgin content using the # -- power key and the random iv and write # -- the Base64 encoded ciphertext into a # -- neighbouring file. # -- to_filepath = content_ciphertxt_file_from_domain_name( domain_name ) binary_ciphertext = power_key.do_encrypt_text( random_iv, the_content ) binary_to_write( to_filepath, content_header, binary_ciphertext ) # -- # -- Derive new inter-sessionary key. # -- Use it to encrypt the power key. # -- Set the reretrieval breadcrumbs. # -- inter_key = KdfApi.generate_from_password( domain_secret, crumbs_db ) inter_txt = inter_key.do_encrypt_key( power_key ) crumbs_db.set( INTER_KEY_CIPHERTEXT, inter_txt ) # -- # -- Return the just createdC high entropy # -- content encryption (power) key. # -- return power_key end |
.setup_domain_keys(domain_name, domain_secret, content_header) ⇒ Object
Transform the domain secret into a key, use that key to lock the power key, delete the secret and keys and leave behind a trail of breadcrumbs sprinkled to allow the inter-sessionary key to be regenerated at the next login.
Lest we forget - buried within this ensemble of activities, is generating the high entropy power key, using it to lock the application database before discarding it.
The use case steps once the human secret is acquired is to
-
pass it through key derivation functions
-
generate a high entropy power key and lock some initial content with it
-
use the key sourced from the human secret to lock the power key
-
throw away the secret, power key and human sourced key
-
save crumbs (ciphertext, salts, ivs) for content retrieval given secret
Note that the init_app_domain method must have been called on this machine with the name of this application instance and the keystore url. An error results if no file is found at the MACHINE_CONFIG_FILE path.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/keytools/key.api.rb', line 240 def self.setup_domain_keys( domain_name, domain_secret, content_header ) # -- # -- Get the breadcrumbs trail and # -- timestamp the moment. # -- crumbs_db = get_crumbs_db_from_domain_name( domain_name ) crumbs_db.set( APP_INSTANCE_SETUP_TIME, KeyNow.fetch() ) # -- # -- Create a new power key and lock the content with it. # -- Create a new inter key and lock the power key with it. # -- Leave the necessary breadcrumbs for regeneration. # -- recycle_keys( domain_name, domain_secret, crumbs_db, content_header, get_virgin_content( domain_name ) ) end |
.to_db_create_date(the_master_db) ⇒ String
Return a date/time string detailing when the master database was first created.
642 643 644 |
# File 'lib/keytools/key.api.rb', line 642 def self.to_db_create_date( the_master_db ) return the_master_db[ DB_CREATE_DATE ] end |
.to_db_domain_id(the_master_db) ⇒ String
Return the domain ID of the master database.
666 667 668 |
# File 'lib/keytools/key.api.rb', line 666 def self.to_db_domain_id( the_master_db ) return the_master_db[ DB_DOMAIN_ID ] end |
.to_db_domain_name(the_master_db) ⇒ String
Return the domain name of the master database.
654 655 656 |
# File 'lib/keytools/key.api.rb', line 654 def self.to_db_domain_name( the_master_db ) return the_master_db[ DB_DOMAIN_NAME ] end |
.to_matching_dictionary(the_master_db, start_string) ⇒ Hash
Return a dictionary containing a string key and the corresponding master database value whenever the master database key starts with the parameter string.
For example if the master database contains a dictionary like this.
envelope@earth => { radius => 24034km, sun_distance_light_minutes => 8 }
textfile@kepler => { filepath => $HOME/keplers_laws.txt, filekey => Nsf8F34dhDT34jLKsLf52 }
envelope@jupiter => { radius => 852837km, sun_distance_light_minutes => 6 }
envelope@pluto => { radius => 2601km, sun_distance_light_minutes => 52 }
textfile@newton => { filepath => $HOME/newtons_laws.txt, filekey => sdDFRTTYu4567fghFG5Jl }
with “envelope@” as the start string to match. The returned dictionary would have 3 elements whose keys are the unique portion of the string.
earth => { radius => 24034km, sun_distance_light_minutes => 8 }
jupiter => { radius => 852837km, sun_distance_light_minutes => 6 }
pluto => { radius => 2601km, sun_distance_light_minutes => 52 }
If no matches are found an empty dictionary is returned.
702 703 704 705 706 707 708 709 710 711 712 |
# File 'lib/keytools/key.api.rb', line 702 def self.to_matching_dictionary( the_master_db, start_string ) matching_dictionary = {} the_master_db.each_key do | db_key | next unless db_key.start_with?( start_string ) dictionary_key = db_key.gsub( start_string, "" ) matching_dictionary.store( dictionary_key, the_master_db[db_key] ) end return matching_dictionary end |
.use_application_domain(domain_name) ⇒ Object
Switch the application instance that the current shell session is using. Trigger this method either during the login use case or when the user issues an intent to use a different application instance.
The machine configuration file at path MACHINE_CONFIG_FILE is changed in the following way
-
a SESSION_APP_DOMAINS section is added if one does not exist
-
the shell session ID key is added (or updated if it exists)
-
with a value corresponding to the app instance ID (on this machine)
Subsequent use cases can now access the application ID by going first to the SESSION_APP_DOMAINS section, reading the ID of the app instance on this machine and then using that in turn to read the APP_INSTANCE_ID_KEY value.
The APP_INSTANCE_ID_KEY value is the global ID of the app instance no matter which machine or shell is being used.
527 528 529 530 531 532 533 534 535 536 537 538 |
# File 'lib/keytools/key.api.rb', line 527 def self.use_application_domain( domain_name ) KeyError.not_new( domain_name, self ) aim_id = KeyId.derive_app_instance_machine_id( domain_name ) sid_id = KeyId.derive_session_id( to_token() ) keypairs = KeyPair.new( MACHINE_CONFIG_FILE ) keypairs.use( SESSION_APP_DOMAINS ) keypairs.set( sid_id, aim_id ) end |
.write_master_db(content_header, app_database) ⇒ Object
This write content behaviour takes the parameter content, encyrpts and encodes it using the index key, which is itself derived from the shell key unlocking the intra session ciphertext. The crypted content is written to a file whose path is derviced by content_ciphertxt_file_from_domain_name.
Steps Taken To Write the Content
Writing the content requires a rostra of actions namely
-
deriving filepaths to both the breadcrumb and ciphertext files
-
creating a random iv and adding its base64 form to the breadcrumbs
-
using the session token to derive the (unique to the) shell key
-
using the shell key and (intra) ciphertext to acquire the index key
-
using the index key and random iv to encrypt and encode the content
-
writing the resulting ciphertext to a file at the designated path
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 |
# File 'lib/keytools/key.api.rb', line 820 def self.write_master_db( content_header, app_database ) # -- # -- Get the filepath to the breadcrumbs file using the trail in # -- the global configuration left by {use_application_domain}. # -- crumbs_db = get_crumbs_db_from_session_token() # -- # -- Get the path to the file holding the ciphertext of the application # -- database content locked by the content encryption key. # -- crypt_filepath = content_ciphertxt_file_from_session_token() # -- # -- Regenerate intra-session key from the session token. # -- intra_key = KeyLocal.regenerate_shell_key( to_token(), true ) # -- # -- Decrypt and acquire the content enryption key that was created # -- during the login use case and encrypted using the intra sessionary # -- key. # -- unique_id = KeyId.derive_universal_id( read_app_id(), to_token() ) crumbs_db.use( unique_id ) power_key = intra_key.do_decrypt_key( crumbs_db.get( INTRA_KEY_CIPHERTEXT ) ) # -- # -- Create a new random initialization vector (iv) to use when # -- encrypting the incoming database content before writing it # -- out to the file at the crypt filepath. # -- iv_base64_chars = KeyIV.new().for_storage() crumbs_db.use( APP_KEY_DB_BREAD_CRUMBS ) crumbs_db.set( INDEX_DB_CRYPT_IV_KEY, iv_base64_chars ) random_iv = KeyIV.in_binary( iv_base64_chars ) # -- # -- Now we use the content encryption (power) key and the random initialization # -- vector (iv) to first encrypt the incoming content and then to Base64 encode # -- the result. This is then written into the crypt filepath derived earlier. # -- binary_ciphertext = power_key.do_encrypt_text( random_iv, app_database.to_json ) binary_to_write( crypt_filepath, content_header, binary_ciphertext ) end |
Instance Method Details
#register_keystore(keystore_url) ⇒ Object
Register the URL to the frontend keystore that is tied to this application instance on this workstation (and user). The default keystore sits on an accessible filesystem that is preferably a removable drive (like a USB key or phone) which allows the keys to your secrets to travel with you in your pocket.
Changing the Keystore Url
If the keystore url has already been configured this method will overwrite (thereby updating) it.
Changing the Keystore Url
The keystore directives in the global configuration file looks like this.
[keystore.ids]
dxEy-v2w3-x7y8 = /media/usb_key/family.creds
47S3-Nv0w-8SYf = /media/usb_key/friend.creds
3Dds-8Tts-Jy2G = /media/usb_key/office.creds
Which Use Case Sets the Keystore Url?
The keystore url must be provided the very first time init is called for an app instance on a machine. If the configuration is wiped, the next initialize use case must again provide it.
How to Add (Extend) Storage Services
We could use Redis, PostgreSQL, even a Rest API to provide storage services. To extend it - make a keystore ID boss its own section and then add keypairs like
-
the keystore URL
-
the keystore Type (or interface class)
-
keystore create destroy markers
The keystore URL cannot be NEW. The NEW acronym asserts that the attribute is
-
neither Nil
-
nor Empty
-
nor Whitespace only
919 920 921 922 |
# File 'lib/keytools/key.api.rb', line 919 def register_keystore keystore_url KeyError.not_new( keystore_url, self ) @keymap.write( @aim_id, KEYSTORE_IDENTIFIER_KEY, keystore_url ) end |