Module: LocalVault::KeySlot
- Defined in:
- lib/localvault/key_slot.rb
Overview
Encrypts/decrypts a vault’s master key for a specific user’s X25519 public key.
Key slots enable multi-user vault access via sync. Each authorized user has a slot containing the vault’s master key encrypted to their public key. Uses an ephemeral sender keypair (X25519 Box) — same construction as ShareCrypto.
Defined Under Namespace
Classes: DecryptionError
Class Method Summary collapse
-
.create(master_key, recipient_pub_key_b64) ⇒ String
Encrypt a master key for a recipient’s X25519 public key.
-
.decrypt(slot_b64, my_private_key_bytes) ⇒ String
Decrypt a key slot using the recipient’s private key.
Class Method Details
.create(master_key, recipient_pub_key_b64) ⇒ String
Encrypt a master key for a recipient’s X25519 public key.
Uses an ephemeral sender keypair so the recipient can decrypt without knowing who sent it.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/localvault/key_slot.rb', line 27 def self.create(master_key, recipient_pub_key_b64) recipient_pub = RbNaCl::PublicKey.new(Base64.strict_decode64(recipient_pub_key_b64)) ephemeral_sk = RbNaCl::PrivateKey.generate box = RbNaCl::Box.new(recipient_pub, ephemeral_sk) nonce = RbNaCl::Random.random_bytes(RbNaCl::Box.nonce_bytes) ciphertext = box.box(nonce, master_key) payload = { "v" => 1, "sender_pub" => Base64.strict_encode64(ephemeral_sk.public_key.to_bytes), "nonce" => Base64.strict_encode64(nonce), "ciphertext" => Base64.strict_encode64(ciphertext) } Base64.strict_encode64(JSON.generate(payload)) end |
.decrypt(slot_b64, my_private_key_bytes) ⇒ String
Decrypt a key slot using the recipient’s private key.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/localvault/key_slot.rb', line 49 def self.decrypt(slot_b64, my_private_key_bytes) raw = Base64.strict_decode64(slot_b64) payload = JSON.parse(raw) sender_pub = RbNaCl::PublicKey.new(Base64.strict_decode64(payload.fetch("sender_pub"))) my_sk = RbNaCl::PrivateKey.new(my_private_key_bytes) box = RbNaCl::Box.new(sender_pub, my_sk) nonce = Base64.strict_decode64(payload.fetch("nonce")) ciphertext = Base64.strict_decode64(payload.fetch("ciphertext")) box.open(nonce, ciphertext) rescue RbNaCl::CryptoError => e raise DecryptionError, "Failed to decrypt key slot: #{e.}" rescue JSON::ParserError, KeyError => e raise DecryptionError, "Invalid key slot format: #{e.}" rescue ArgumentError => e raise DecryptionError, "Invalid key slot encoding: #{e.}" end |