Module: SshTresor::Tresor
- Defined in:
- lib/ssh_tresor/tresor.rb
Class Method Summary collapse
- .add_all_keys(blob) ⇒ Object
- .add_all_keys_with_agent(agent, blob) ⇒ Object
- .add_key(blob, fingerprint) ⇒ Object
- .add_key_with_agent(agent, blob, fingerprint) ⇒ Object
- .create_slot(agent, key, master_key) ⇒ Object
- .decrypt(blob) ⇒ Object
- .decrypt_with_agent(agent, blob) ⇒ Object
- .decrypt_with_slot(agent, key, slot, blob) ⇒ Object
- .encrypt(plaintext, fingerprints: []) ⇒ Object
- .encrypt_with_agent(agent, plaintext, fingerprints: []) ⇒ Object
- .encrypt_with_keys(agent, keys, plaintext) ⇒ Object
- .list_keys ⇒ Object
- .list_slots(blob) ⇒ Object
- .recover_master_key(agent, blob) ⇒ Object
- .remove_key(blob, fingerprint) ⇒ Object
- .resolve_slot_fingerprint(blob, fingerprint) ⇒ Object
Class Method Details
.add_all_keys(blob) ⇒ Object
64 65 66 |
# File 'lib/ssh_tresor/tresor.rb', line 64 def add_all_keys(blob) add_all_keys_with_agent(Agent.connect, blob) end |
.add_all_keys_with_agent(agent, blob) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ssh_tresor/tresor.rb', line 68 def add_all_keys_with_agent(agent, blob) master_key = recover_master_key(agent, blob) new_slots = blob.slots.dup added = 0 agent.list_keys.each do |key| next if blob.find_slot(key.fingerprint_bytes) begin new_slots << create_slot(agent, key, master_key) added += 1 rescue Error next end end [TresorBlob.new(slots: new_slots, data_nonce: blob.data_nonce, ciphertext: blob.ciphertext), added] end |
.add_key(blob, fingerprint) ⇒ Object
47 48 49 |
# File 'lib/ssh_tresor/tresor.rb', line 47 def add_key(blob, fingerprint) add_key_with_agent(Agent.connect, blob, fingerprint) end |
.add_key_with_agent(agent, blob, fingerprint) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/ssh_tresor/tresor.rb', line 51 def add_key_with_agent(agent, blob, fingerprint) master_key = recover_master_key(agent, blob) new_key = agent.find_key(fingerprint) raise Error, "Invalid tresor format: key already exists in tresor" if blob.find_slot(new_key.fingerprint_bytes) TresorBlob.new( slots: blob.slots + [create_slot(agent, new_key, master_key)], data_nonce: blob.data_nonce, ciphertext: blob.ciphertext ) end |
.create_slot(agent, key, master_key) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/ssh_tresor/tresor.rb', line 115 def create_slot(agent, key, master_key) challenge = Crypto.random_challenge signature = agent.sign(key, challenge) slot_key = Crypto.derive_key(signature) nonce = Crypto.random_nonce encrypted_key = Crypto.encrypt(slot_key, nonce, master_key) Slot.new( fingerprint: key.fingerprint_bytes, challenge: challenge, nonce: nonce, encrypted_key: encrypted_key ) end |
.decrypt(blob) ⇒ Object
26 27 28 |
# File 'lib/ssh_tresor/tresor.rb', line 26 def decrypt(blob) decrypt_with_agent(Agent.connect, blob) end |
.decrypt_with_agent(agent, blob) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/ssh_tresor/tresor.rb', line 30 def decrypt_with_agent(agent, blob) keys = agent.list_keys.sort_by(&:security_key?) keys.each do |key| slot = blob.find_slot(key.fingerprint_bytes) next if slot.nil? begin return decrypt_with_slot(agent, key, slot, blob) rescue DecryptionError next end end raise NoMatchingSlot end |
.decrypt_with_slot(agent, key, slot, blob) ⇒ Object
130 131 132 133 134 135 |
# File 'lib/ssh_tresor/tresor.rb', line 130 def decrypt_with_slot(agent, key, slot, blob) signature = agent.sign(key, slot.challenge) slot_key = Crypto.derive_key(signature) master_key = Crypto.decrypt(slot_key, slot.nonce, slot.encrypted_key) Crypto.decrypt(master_key, blob.data_nonce, blob.ciphertext) end |
.encrypt(plaintext, fingerprints: []) ⇒ Object
12 13 14 |
# File 'lib/ssh_tresor/tresor.rb', line 12 def encrypt(plaintext, fingerprints: []) encrypt_with_agent(Agent.connect, plaintext, fingerprints: fingerprints) end |
.encrypt_with_agent(agent, plaintext, fingerprints: []) ⇒ Object
16 17 18 19 20 21 22 23 24 |
# File 'lib/ssh_tresor/tresor.rb', line 16 def encrypt_with_agent(agent, plaintext, fingerprints: []) keys = if fingerprints.empty? [agent.first_key] else fingerprints.map { |fingerprint| agent.find_key(fingerprint) } end encrypt_with_keys(agent, keys, plaintext) end |
.encrypt_with_keys(agent, keys, plaintext) ⇒ Object
106 107 108 109 110 111 112 113 |
# File 'lib/ssh_tresor/tresor.rb', line 106 def encrypt_with_keys(agent, keys, plaintext) master_key = Crypto.random_master_key slots = keys.map { |key| create_slot(agent, key, master_key) } data_nonce = Crypto.random_nonce ciphertext = Crypto.encrypt(master_key, data_nonce, plaintext) TresorBlob.new(slots: slots, data_nonce: data_nonce, ciphertext: ciphertext) end |
.list_keys ⇒ Object
98 99 100 |
# File 'lib/ssh_tresor/tresor.rb', line 98 def list_keys Agent.connect.list_keys end |
.list_slots(blob) ⇒ Object
102 103 104 |
# File 'lib/ssh_tresor/tresor.rb', line 102 def list_slots(blob) blob.slot_fingerprints end |
.recover_master_key(agent, blob) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/ssh_tresor/tresor.rb', line 137 def recover_master_key(agent, blob) agent.list_keys.each do |key| slot = blob.find_slot(key.fingerprint_bytes) next if slot.nil? begin signature = agent.sign(key, slot.challenge) slot_key = Crypto.derive_key(signature) return Crypto.decrypt(slot_key, slot.nonce, slot.encrypted_key) rescue DecryptionError next end end raise NoMatchingSlot end |
.remove_key(blob, fingerprint) ⇒ Object
87 88 89 90 91 92 93 94 95 96 |
# File 'lib/ssh_tresor/tresor.rb', line 87 def remove_key(blob, fingerprint) raise Error, "Invalid tresor format: cannot remove the last key from tresor" if blob.slots.length == 1 fingerprint_bytes = resolve_slot_fingerprint(blob, fingerprint) new_slots = blob.slots.reject { |slot| slot.fingerprint == fingerprint_bytes } raise KeyNotFound, "Key not found: #{fingerprint}" if new_slots.length == blob.slots.length TresorBlob.new(slots: new_slots, data_nonce: blob.data_nonce, ciphertext: blob.ciphertext) end |
.resolve_slot_fingerprint(blob, fingerprint) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/ssh_tresor/tresor.rb', line 154 def resolve_slot_fingerprint(blob, fingerprint) normalized = fingerprint.delete_prefix("SHA256:") matches = blob.slot_fingerprints.select do |slot_fingerprint| Base64.strict_encode64(slot_fingerprint).delete("=").start_with?(normalized) end case matches.length when 0 raise KeyNotFound, "Key not found: #{fingerprint}" when 1 matches.first else raise KeyNotFound, "Key not found: #{fingerprint} (ambiguous: #{matches.length} slots match this prefix, please be more specific)" end end |