layout: true class: typo, typo-selection, nord-dark --- count: false class: center, middle # End to End Encryption Basics What are the layers of the onion and how do I deal with them. --- # Goal .pv-xxl[ Send messages, which are only readable by the person you intended to read the messages. ] --- # Goal .rect.blue[Identity of users .rect.yellow[Verify identity] .rect.yellow[Sign messages (attach identity)] ] .rect.indigo[Encryption of messages .rect.grape[Encrypt messages in a matrix room] .rect.grape[Share decryption keys] .rect.grape[Backup decryption keys] .rect.grape[Attach identity to messages] .rect.grape[Verify identity on messages] ] --- # Layers of encryption .font-md[ .rect.red[(Olm) Account .rect.orange[Cryptographic identity keys] .rect.orange[Sign messages] .rect.orange[Generate one time keys] .rect.orange[Generate olm sessions (in/out)] .rect.yellow[Verify signatures] ] .rect.indigo[Olm (device messaging) .rect.cyan[Send encrypted messages between devices] .rect.cyan[Share Megolm keys] .rect.cyan[Double ratchet] ] .rect.green[Megolm (room messaging) .rect.lime[Send encrypted messages in a room] .rect.lime[Backup] .rect.lime[Export inbound sessions] .rect.lime[Ratchet index] ] .rect.violet[Verification .rect.pink[Verification of identity keys] .rect.pink[Crosssigning] .rect.pink[Key reuse protections] .rect.pink[Warnings to the user] ] .rect.teal[Backup .rect.grape[Secret storage] .rect.grape[Secret sharing] .rect.grape[Megolm key backup] .rect.grape[Dehydrated devices] ] ] --- count: false class: center, middle # Intermezzo --- # Some terms - ciphertext: The encrypted message - plaintext: The message before encryption - cleartext: The message after encryption including unencrypted metadata .card.noborder[ .card.width-43[ .section[**Plaintext**] .section.font-sm[ ```json { "content": { "body": "abc", "msgtype": "m.text" } } ``` ] ] .ri-arrow-right-line.icon-inline.nord15[] .card.width-43[ .section[**Cleartext**] .section.font-sm[ ```json { "content": { "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "AwgAEnBr/9Ejj6j0dueWODP5kHw8e7lJeia/18nqdGpM/29tuXBOlSMFqrk5bLUV0SfbgBtsarxQ6UmYda5XYkbOk/MHiGDzI6HKl9u6/jywUXp5GoABF1be8KiwxUUsf7pyRWe0wVrkTvbtSq4QmNyHkRyL8I5CXi9iKNPKGZrz2Djt1SllrahLh7dwKtM0fx24+xoSk0RtI/tlVtVJ+H9kNs8nKO5RJ7RJ3/BVzlFq7rzLEELbh7GVdloJ", "device_id": "FNIHZKGIZE", "sender_key": "wxgMPMGH8HsPn2e4mANQV3qDc5aA7ReH6xr/msB5kBc", "session_id": "5HlGI5xwhZN2cwmd3XpxurxDHkFlKr/wu+TVd1Yp7D8" } } ``` ] ] ] --- # Some more terms - Signing: Similar to a hash, but requires a private key you have and a public key on the other end. So only you can add the signature and others can verify it. This makes the content trusted. --- count: false class: center, middle # (Olm) Account --- # Account - This is your identity. - Currently an ed25519 and a curve25519 key. - Not to be confused with your Matrix account. - Generated for every login. - Can be sent messages via the device id. - Sometimes called a session or a device. .rect.red[DO NOT RELY ON THE DEVICE ID. ONLY THE ED25519 KEY IS SAFE! TRUST IS BUILT USING THAT KEY AND THE DEVICE ID JUST USED FOR SENDING TO IT.] --- ```dart final account = await Account.create(); await account.generateFallbackKey(); await account.generateOneTimeKeys(account.maxNumberOfOneTimeKeys()/2); Future<Map<String, Object>> signKey(key, fallback) { final signedKey = {'key': key, if (fallback) 'fallback': true}; signedKey['signatures'][client.userid]['ed25519:${client.deviceid}'] = await account.sign(signedKey.serializeJson()); return signedKey; } final signedOTKs = await account.oneTimeKey().map((keyid, key) async => MapEntry('signed_curve25519:$key', signKey(key, false)); final signedFallbackKeys = await account.fallbackKey().map((keyid, key) async => MapEntry('signed_curve25519:$key', signKey(key, true)); ``` --- ```dart await client.uploadKeys( deviceKeys: { 'user_id': client.userid, 'device_id': deviceid, 'algorithms': [Algorithm.olmV1Curve25519AesSha2, Algorithm.megolmV1AesSha2], 'keys': { 'ed25519:${client.deviceid}': signKey(await account.ed25519Key()), 'curve25519:${client.deviceid}': signKey(await account.curve25519Key()), } }, otks: signedOTKs, fallbackKey: signedFallbackKeys); ``` --- count: false class: center, middle # To Device Messaging --- # Olm layer - Olm is a double ratchet derived from 4 curve25519 keys - Your identity key - Their identity key - Your one time key (generated automatically) - Their one time key (uploaded before) - Message is encrypted for and sent to each device/account - Message can only be decrypted via the correct identity key and private parts of your keys. - The sessions between 2 devices/accounts are reused until they break. - Start a new one by sending a dummy message --- class: center, middle ![](./olm.svg) --- # Olm layer - Olm messages can be pre-key or normal messages - pre-key: No message was sent back. Contains more data to create an inbound session from it - normal: Remove redundant state that is only needed for the first message - After that a ratchet is moved forward on every exchange --- # Outbound messaging ```dart final theirIdentityKeys = client.queryKeys({otherUser: [otherUserDeviceId]}); // verify the users device key have signed themselves // possibly also verify that the user is verified final claimedKey = client.claimKeys({otherUser: [otherUserDeviceId]}).first; await theirIdentityKeys.ed25519.verify(claimedKey.withSignaturesStripped, theirKey.signatures[otherUser]['ed25519:$otherUserDeviceId']) final session = account.createOutboundSession( identityKey: theirIdentityKeys.curve25519, oneTimeKey: claimedKey.key, ); final (messageType, ciphertext) = session.encrypt(plaintext); ``` --- ```dart // plaintext includes: // 'type': type, // 'content': payload, // 'sender': client.userID, // 'keys': {'ed25519': fingerprintKey}, // 'recipient': device.userId, // 'recipient_keys': {'ed25519': device.ed25519Key}, client.sendToDevice({otherUser: {otherUserDeviceId: { 'type': 'm.room.encrypted', 'content': { 'algorithm': 'm.olm.v1.curve25519-aes-sha2', 'sender_key': account.curve25519, 'ciphertext': { theirIdentityKeys.curve25519: { 'type': messageType, 'body': ciphertext, } } }}}); ``` --- # Inbound messaging ```dart final theirIdentityKeys = client.queryKeys({otherUser: []}); // verify the users device key have signed themselves // possibly also verify that the user is verified final theirDeviceKeys = theirIdentityKeys.firstWhere( (e) => e.keys.contains('curve25519:${msg.content.sender_key}' ); // try exisiting olm sessions first for (final session in sessionsForUser(otherUser, msg.content.sender_key)) { final decrypted = session.decrypt(msg.content.ciphertext[account.curve25519].msgType, msg.content.ciphertext[account.curve25519].body); // if success, persist session update return decrypted; } ``` --- ```dart if(msg.content.ciphertext[account.curve25519].msgType != 0) { throw OlmChannelCorrupt; } final session = account.createInboundSession( theirIdentityKey: msg.content.sender_key, message: msg.content.ciphertext[account.curve25519].body, ); final decrypted = session.decrypt(msg.content.ciphertext[account.curve25519].msgType, msg.content.ciphertext[account.curve25519].body); // if success, persist session update return decrypted; ``` --- count: false class: center, middle # Room Messaging --- # Room Messaging - This is for messages sent in a room. - There is no way to verify the sender on this layer. - You only know who encrypted something because they sent you the key. --- # Room Messaging - Uses Megolm - Megolm is a single ratchet - Every message gets a new key by ratcheting forward - Key is shared with every user in the room (or the verified subset) - When a user leaves -> new key - When a new user joins -> share key with the next message - In a way symmetric encryption with a private signature - Distinguation between inbound and outbound session (because of the signature) --- class: center, middle ![](./megolm.svg) --- # Outbound Messaging - Create outbound session. - Send it to everyone over to_device messaging (olm encrypted of course). - Possibly backup the inbound session. - Encrypt the message. --- ```dart final groupSession = await GroupSession.create(); // share this with room members final inbound = groupSession.toInbound(); final payloadContent = { 'content': payload, 'type': type, 'room_id': roomId, }; final ciphertext = await groupSession.encrypt(payloadContent.dumpJson()); final messageToSend = <String, Object?>{ 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'ciphertext': ciphertext, 'session_id': groupSession.sessionId(), if (mRelatesTo != null) 'm.relates_to': mRelatesTo, }; } ``` --- # Inbound Messaging - Decrypt with the inbound session - Show verification status based on how you got the key. - Possibly check online key backup or send key request for missing inbound sessions. - Verify that index wasn't used before. --- ```dart // if the message has the correct algorithm: final inbound = await InboundGroupSession.import(db.findSession(roomId, content.session_id)); final (plaintext, messageIndex) = await inbound.decrypt(content.ciphertext); final existingEventId = await db.eventIdForMessageIndex(messageIndex); if (existingEventId != null) assert(existingEventId == event.id); await db.storeEventIdForMessageIndex(messageIndex, event.id); assert(plaintext.roomId == roomId) ``` --- count: false class: center, middle # Verification --- # Identities - Identity keys for each device are cumbersome - NxM verification - Cross-signing keys to sign yourself and others - device signing key to sign your own devices - user signing key to sign other users - master key to sign your device and user signing keys and to be signed by others --- # Crosssigning Chain ![](static/matrix-cross-signing-key-diagram.svg) --- # Verification Basics - Establish if a channel is secure by comparing the identity keys seen by each other - Either by deriving a key from the identity keys and comparing that encoded as numbers or emoji - Or sharing the encoded visible key directly (via QR code for example) - Negotiate the method of verification ahead of time - Can be done over to_device messages or room messages. - to_device verification relates using a txnid, room messages use event relations. --- # Usual verification sequence .mermaid.center[ <pre> sequenceDiagram participant A as Alice participant B as Bob A->>B: m.key.verification.request B->>A: m.key.verification.ready A->>B: m.key.verification.start Note over A,B: Execute verification method here A->>B: m.key.verification.done B->>A: m.key.verification.done </pre> ] --- .card.noborder[ .card.width-27[ .section[`verification.request`] .section.font-sm[ Includes supported verification methods and what user you are trying to verify (in case of room messages). Also includes what device sent this request. ] ] .card.width-27[ .section[`verification.ready`] .section.font-sm[ Reply including supported verification methods. Also includes what device sent this request. ] ] .card.width-27[ .section[`verification.start`] .section.font-sm[ Actually starts the verification. Includes the method selected. QR code verification includes the scanned QR secret here. SAS verification includes the supported key agreement protocols and SAS method. Also includes what device sent this request. ] ] .card.width-27[ .section[`verification.done`] .section.font-sm[ Communicates that side has finished the verification. ] ] .card.width-27[ .section[`verification.cancel`] .section.font-sm[ Cancel the verification and explain why the verification failed. ] ] ] --- # SAS verification .mermaid.center[ <pre> sequenceDiagram participant A as Alice participant B as Bob A->>B: m.key.verification.start B->>A: m.key.verification.accept A->>B: m.key.verification.key B->>A: m.key.verification.key Note over A,B: Users verify the SAS string A->>B: m.key.verification.mac B->>A: m.key.verification.mac A->>B: m.key.verification.done B->>A: m.key.verification.done </pre> ] --- .card.noborder[ .card.width-27[ .section[`verification.start`] .section.font-sm[ Actually starts the verification. Includes the method selected. SAS verification includes the supported key agreement protocols and SAS method. Also includes what device sent this request. ] ] .card.width-27[ .section[`verification.accept`] .section.font-sm[ Contains a hash of the `start` message as well as the choses key agreement protocol and MAC method. ] ] .card.width-27[ .section[`verification.key`] .section.font-sm[ Exchanges an ephemeral public key from both sides. ] ] .card.width-27[ .section[`verification.mac`] .section.font-sm[ Calculates a mac of all the keys to verify and a "super mac". Client uses that to mark all those keys as verified. ] ] ] --- # QR verification .mermaid.center[ <pre> sequenceDiagram participant A as Alice participant B as Bob Note over A: Scans QR code A->>B: m.key.verification.start Note over B: Verifies Alice scanned A->>B: m.key.verification.done B->>A: m.key.verification.done </pre> ] --- .card.noborder[ .card.width-27[ .section[`verification.start`] .section.font-sm[ Actually starts the verification. Includes the method selected. QR code verification includes the scanned QR secret here. Also includes what device sent this request. ] ] ] --- # Verification Summary - SAS verification is done by deriving a shared secret and comparing that, then using that to authenticate the identity keys. - QR code shares one key directly and says it is trusted. The other side then receives that secret back and can mark that user as trusted, since it received the secret securely. - QR code verification requires either both sides to have trusted master keys (for user verification) or one side to have a trusted master key (for device verification) --- count: false class: center, middle # Backup --- # Online Key Backup - Currently an assymmetric encryption of your megolm inbound sessions. - Anybody can basically upload keys, so there is no way to trust them. - You need to verify the key backup key is signed by one of your devices, otherwise it could be your server has the secret key. --- # Symmetric Secure Secret Sharing and Storage - A way to share the private parts of the key backup as well as your signing keys - Share either via to device messages or via account data - Account date can be accessed by a passphrase or the encoded encryption key (recovery key). - You can use different phrases for each secret or one for multiple. --- count: false class: center, middle # FIN --- --- <https://about.gitlab.com/blog/2020/09/08/efficient-code-review-tips/> <https://about.gitlab.com/blog/2021/03/09/tips-for-better-code-review/> <https://stackoverflow.blog/2019/09/30/how-to-make-good-code-reviews-better/> <https://docs.gitlab.com/ee/development/code_review.html> <https://docs.gitlab.com/ee/development/code_review.html#reviewer-roulette>