# Encryption using the Signal Protocol <style> .ui-infobar, #doc.markdown-body { max-width: 2000px; } </style> ###### tags: `Kizuna-architecture` ## Summary of Meeting Notes * Encryption should be done both in sync and async so that holoport owners don't get to see messages * Goal should be not sending plaintext messsages between client and holoport * Key storage * DHT: more resilient, less secure * Source Chain: less resilient, more secure * resiliencey means being able to handle operations when holoports are offline * Key request (from DHT) * problem of key draining * problem of simultaneous key pulls * clearly a problem even with randomization * would be fixed if we ask agents for keys (sync) * deliver key via call_remote * Cryptographic calculations are not safe in wasm * holoport owners may get access to private keys * implement as close to the APIs * Relevant resources * maybe libsodium api can offer more than hdk (e.g. lair function that does DHE in the background) * https://libsodium.gitbook.io/doc/public-key_cryptography/authenticated_encryption * https://github.com/holochain/holochain/blob/develop/crates/hdk3/src/x_salsa20_poly1305/x_25519_x_salsa20_poly1305_encrypt.rs #### 2/09/2021 Questions and Comments - Plain text should never be sent from client to HoloPort - We dont want to store private keys in source chain - not compliant w/ CAL, also a bit risky - There has to be at least 1 key stored persistently in client that can decrypt all encrypted entries in source chain. (And this is not the private counterpart of AgentPubKey because that is for signing) - If every private entry is encrypted with this key, then this key is weakest point of the encryption scheme we will have. - If we will store this in one local device of the agent, how do we ensure that multi-session is possible? - OR, we could derive a private/public key from AgentPrivateKey which means that you don't have to store that key persistently because as long as you have the right seed, you will always generate the same private key. - Art has said that Chaperone needs to also derive some sort of encryption key so that it can be used for encrypting entries for an agent in the wormhole (what is wormhole?? does this mean the private key leaves local device?) - **We may need some form of persistent private key storage in the client when using Holo** - How do we reconcile the difference between crypto implementation for native Holochain user and Holo hosted user? - Holochain has Lair, but Holo does not. So, I think we have to match the native holochain user with Holo use case? - Where should we generate dynamic keys that can work both for Holo and Holochain - Lair - Will not work with Holo (since lair is replaced by Chaperone in Holo) - Client (on our own) - The burden of implementing key infrastructure will be upon us - Each project will have different implementation (may introduce lots of security vulnerabilities) - We can do basically anything we want to if we do this. - Chaperone - No idea how this will work yet. Maybe chaperone will expose APIs that we can use and get the private key exposed outside of the iframe? - Will chaperone have key generation APIs and will we be able to access those dynamically generated keys so that we can persitently store them? - Do we have access to secrets on the browser or will it be completely contained in chaperone? - A no to this answer would mean that we have to introduce something like tweetnacl into Kizuna. ### External libraries - https://github.com/signalapp/libsignal-protocol-javascript - https://www.npmjs.com/package/tweetnacl ## Entry Relationship Diagram ```mermaid graph LR subgraph encryption zome subgraph agents alice end subgraph public keys in DHT and source chain X3DHKeys onetime_key_1 onetime_key_2 onetime_key_... alice-->X3DHKeys alice-->onetime_key_1 alice-->onetime_key_2 alice-->onetime_key_... end subgraph private keys in LAIR X3DHKeys_pr onetime_key_1_pr onetime_key_2_pr onetime_key_..._pr X3DHKeys.->|can fetch|X3DHKeys_pr onetime_key_1.->|can fetch|onetime_key_1_pr onetime_key_2.->|can fetch|onetime_key_2_pr onetime_key_... .->|can fetch|onetime_key_..._pr end end ``` ## P2P Encryption using the x_salsa20_poly1305 ```mermaid sequenceDiagram participant ACD as Alice_Conversation_CELL participant DHT as DHT participant BCD as Bobby_Conversation_CELL ACD-->>ACD: create an x25519 Identity Keypair using ``` ## Functions (using x_salsa_poly_1305) ### `init` ```rust= fn ``` ## Functions ### `encrypt_message` - Alice sends a message to offline Bobby ```rust= fn encrypt_message(message: MessageParameter) -> ExternResult<EncryptedMessage> pub struct AssociatedData { sender_identity_key: X25519PubKey, receiver_identity_key: X25519PubKey, sender_ephemeral_key: X25519PubKey, receiver_onetime_key: X25519PubKey } #[hdk_entry(id = "encryptedmessage", visibility = "public")] pub struct EncryptedMessage { id: Option<String>, encrypted_data: Vec<Bytes>, associated_data: AssociatedData } ``` ```mermaid sequenceDiagram participant ACD as Alice_Conversation_CELL participant BCD as Bobby_Conversation_CELL participant DHT as DHT par init rect rgba(0,0,255,.1) ACD-->>ACD: call publish_prekeys end and rect rgba(0,0,255,.1) BCD-->>BCD: call publish_prekeys end end rect rgba(255, 0, 0, .5) ACD-->>ACD: call `get_prekey_bundle` end ACD-->>ACD: call `agree_on_secret` -> shared_master Note over ACD: use shared secret to derive two symmetric keys : root key and sending chain key # ratchet step ACD-->>ACD: call `create_x25519_keypair` for initial ratchet keypair ACD-->>ACD: run ratchet_chain <br> (generated_public_key, bob_onetime_key) -> ratchet_key ACD-->>ACD: run root_chain (shared_master, ratchet_key) -> (root_key, sending_key) ACD-->>ACD: run sending_chain <br> (sending_key) -> (chain_key, message_key) ACD-->>ACD: encrypt ``` ### `publish_prekeys` ```rust= fn publish_prekeys() -> ExternResult<bool> ``` ```rust= // two entry types = two validation rules // downside: getting two entries every time a session is being established // what data type for keys is native to rsm/rust #[hdk_entry(id = "x3dhkeys", visibility = "public")] pub struct X3DHKeys = { identity_key: X25519PubKey, // once signed_prekey: X25519PubKey, // replaced weekly/monthly together with prekey sig prekey_signature: X25519PubKey, // replaced weekly/monthly together with prekey } #[hdk_entry(id = "x3dhonetimekeys", visibility = "public")] pub struct X3DHOnetimeKeys = { onetime_prekeys: Vec<X25519PubKey> // replaced once available keys are depleted/low } ``` ```rust= // `hdk3::x_salsa20_poly1305::create_x25519_keypair::create_x25519_keypair` pub fn create_x25519_keypair() -> HdkResult<X25519PubKey> // `hdk3::host_fn::sign::sign` fn sign(key: AgentPubKey, data: SerializedBytes) -> HdkResult<Signature> // `hdk3::prelude::prelude::signature::Signature` pub struct Signature(pub Vec<u8>); ``` ```mermaid sequenceDiagram participant BCD as Bobby_Conversation_CELL participant DHT as DHT BCD-->>BCD: call `create_x25519_keypair` for identity key Note over BCD: can we use the AgentPubKey for the identity key? BCD-->>BCD: call `create_x25519_keypair` for prekey BCD-->>BCD: sign prekey BCD-->>BCD: create X3DHKeys entry BCD-->>DHT: commit X3DHKeys entry in DHT DHT-->>BCD: return header address BCD-->>DHT: link Agent to entry with tag 'x3dhkeys' loop until required number of onetime keys is met BCD-->>BCD: call`create_x25519_keypair`to generate onetime keys end BCD-->>BCD: create X3DHOnetimeKeys entry BCD-->>DHT: commit X3DHOnetimeKeys entry in DHT DHT-->>BCD: return header address BCD-->>DHT: link Agent to entry with tag 'x3dhonetimekeys' ``` ### `replace_prekey` ```rust= fn replace_prekey() -> ExternResult<bool> ``` ```mermaid sequenceDiagram participant BCD as Bobby_Conversation_CELL participant DHT as DHT BCD-->>BCD: construct hash address of X3DHKeys entry in DHT BCD-->>DHT: get X3DHKeys entry from DHT DHT-->>BCD: return X3DHKeys entry BCD-->>BCD: call`create_x25519_keypair`to generate prekey BCD-->>DHT: update X3DHKeys entry in DHT with newly generated prekey ``` ### `replenish_onetime_keys` ```rust= fn replenish_onetime_keys() -> ExternResult<bool> ``` ```mermaid sequenceDiagram participant BCD as Bobby_Conversation_CELL participant DHT as DHT BCD-->>BCD: construct hash address of X3DHOnetimeKeys entry in DHT BCD-->>DHT: get X3DHOnetimeKeys entry from DHT DHT-->>BCD: return X3DHOnetimeKeys entry loop until required number of onetime keys is met BCD-->>BCD: call`create_x25519_keypair`to generate onetime keys end BCD-->>DHT: update X3DHOnetimeKeys entry in DHT with newly generated keys ``` ### `get_prekey_bundle` ```rust= pub struct PrekeyBundle { X3DHKeys: X3DHKeys, OnetimeKey: String } fn get_prekey_bundle() -> ExternResult<PrekeyBundle> ``` ```mermaid sequenceDiagram participant ACD as Alice_Conversation_CELL participant BCD as Bobby_Conversation_CELL participant DHT as DHT ACD-->>DHT: get_links from receiver Agent with tag 'x3dhkeys' DHT-->>ACD: return links ACD-->>DHT: get link target DHT-->>ACD: return X3DHKeys entry ACD-->>DHT: get_links from receiver Agent with tag 'x3dhonetimekeys' DHT-->>ACD: return links ACD-->>DHT: get link target DHT-->>ACD: return X3DHOnetimeKeys entry ACD-->>ACD: select a onetime prekey at random Note over ACD: what selection mechanism would be good? ACD-->>DHT: update_entry to remove selected onetime prekey Note over DHT: exposes social graph ACD-->>ACD: return PrekeyBundle ``` ### `agree_on_secret` ```mermaid sequenceDiagram participant ACD as Alice_Conversation_CELL participant DHT as DHT ACD-->>ACD: verify prekey_signature[bobby] opt verification failed ACD-->>ACD: return error end ACD-->>ACD: DH(identity_key[alice], signed_prekey[bobby]) -> DH1 ACD-->>ACD: call`create_x25519_keypair`to generate an ephemeral key ACD-->>ACD: DH(ephemeral_key[alice], identity_key[bobby]) -> DH2 ACD-->>ACD: DH(ephemeral_key[alice], signed_prekey[bobby]) -> DH3 ACD-->>ACD: DH(ephemeral_key[alice], onetime_key[bobby]) -> DH4 ACD-->>ACD: concatenate all DH outputs (DH1||DH2||DH3||DH4) -> shared key ACD-->>ACD: encode both identity keys as byte strings ACD-->>ACD: contatenate encoded byte strings -> AD ACD-->>ACD: return shared key ``` ### `ratchet_chain` ```rust= pub struct RatchetInput { public_key_receiver: String //from receiver public_key_sender: String //own, used to retrieve private key from lair } fn ratchet(input: RatchetInput) -> ExternResult<Key> { // Diffie-Hellman } ``` ### `root_chain` ```rust= pub struct RootInput { private_key_shared: String dh_output: String } pub struct RootPair { chain_key: String, root_key: String } fn ratchet(input: RootInput) -> ExternResult<RootPair> { // KDF } ``` ### `sending_chain` ```rust= pub struct SendingPair{ chain_key: String, message_key: String } fn sending_chain(chain_key: Key) -> ExternResult<SendingPair> { // KDF } ``` ### `receiving_chain` ### `AEAD` ### `KDF` ### `Encryption` ```mermaid sequenceDiagram participant SecMem as Client_Secure_Memory participant Client as Alice_Client participant Chap as Alice_Chaperone participant ACC as Alice_Conversation_Cell participant DHT as DHT rect rgba(255, 0, 0, 0.3) Note over Client: Key Generation Note over SecMem: implement storage to secure memory Note over Chap: Will the chaperone have any role in key generation and storage? Client-->>Client: generate registrationID <br> (libsignal) Client-->>SecMem: store registrationID Client-->>Client: generate identityKeyPair <br> (libsignal) Client-->>SecMem: store identityKeyPair Client-->>Client: generate prekey and signed prekey <br> (libsignal) Client-->>DHT: register prekeys and signed prekey Note over Client: implement a Store for storing and fetching keys end rect rgba(0, 255, 0, 0.2) Note over Client: Session Building Client-->>Client: Build a session <br> (libsignal) Client-->>DHT: fetch prekey bundle DHT-->>Client: return prekey bundle Client-->>Client: process prekeys <br> (libsignal) end rect rgba(0, 0, 255, 0.2) Note over Client: encrypt a message Client-->>Client: encrypt message payload using Session <br> (libsignal) Client-->>ACC: send_message containing the ciphertext as payload Note over Client: we can't encrypt other data that the backend needs to process (e.g. receiver) -> attack vector during transmission to backend? ACC-->>Client: return message bundle end rect rgba(255, 0, 255, 0.2) Note over Client: Decryption DHT-->>Client: receive message via signal or getters Client-->>Client: find or build matching session with sender Client-->>Client: decrypt message payload <br> (libsignal) end ```