Encrypt source for medium groups

This commit is contained in:
Maxim Shishmarev 2020-05-05 17:03:21 +10:00
parent dd6b91bb36
commit 61d4c7c349
7 changed files with 119 additions and 58 deletions

View File

@ -742,15 +742,17 @@
ourIdentity
);
const groupSecretKeyHex = StringView.arrayBufferToHex(
identityKeys.privKey
);
// Constructing a "create group" message
const proto = new textsecure.protobuf.DataMessage();
const groupUpdate = new textsecure.protobuf.MediumGroupUpdate();
groupUpdate.groupId = groupId;
groupUpdate.groupSecretKey = StringView.arrayBufferToHex(
identityKeys.privKey
);
groupUpdate.groupSecretKey = groupSecretKeyHex;
groupUpdate.senderKey = senderKey;
groupUpdate.members = [ourIdentity, ...members];
groupUpdate.groupName = groupName;
@ -758,7 +760,7 @@
await window.Signal.Data.createOrUpdateIdentityKey({
id: groupId,
secretKey: identityKeys.privKey,
secretKey: groupSecretKeyHex,
});
const convo = await window.ConversationController.getOrCreateAndWait(

View File

@ -1,5 +1,5 @@
/* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView,
libsignal, window, TextDecoder, TextEncoder, dcodeIO, process, crypto */
libsignal, window, TextDecoder, TextEncoder, dcodeIO, process */
const nodeFetch = require('node-fetch');
const https = require('https');
@ -14,40 +14,11 @@ const endpointBase = '/storage_rpc/v1';
// Request index for debugging
let onionReqIdx = 0;
const encryptForNode = async (node, payload) => {
const encryptForNode = async (node, payloadStr) => {
const textEncoder = new TextEncoder();
const plaintext = textEncoder.encode(payload);
const plaintext = textEncoder.encode(payloadStr);
const ephemeral = await libloki.crypto.generateEphemeralKeyPair();
const snPubkey = StringView.hexToArrayBuffer(node.pubkey_x25519);
const ephemeralSecret = await libsignal.Curve.async.calculateAgreement(
snPubkey,
ephemeral.privKey
);
const salt = window.Signal.Crypto.bytesFromString('LOKI');
const key = await crypto.subtle.importKey(
'raw',
salt,
{ name: 'HMAC', hash: { name: 'SHA-256' } },
false,
['sign']
);
const symmetricKey = await crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
key,
ephemeralSecret
);
const ciphertext = await window.libloki.crypto.EncryptGCM(
symmetricKey,
plaintext
);
return { ciphertext, symmetricKey, ephemeral_key: ephemeral.pubKey };
return libloki.crypto.encryptForPubkey(node.pubkey_x25519, plaintext);
};
// Returns the actual ciphertext, symmetric key that will be used
@ -65,7 +36,7 @@ const encryptForRelay = async (node, nextNode, ctx) => {
const reqJson = {
ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'),
ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeral_key),
ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeralKey),
destination: nextNode.pubkey_ed25519,
};
@ -101,7 +72,7 @@ const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => {
const payload = {
ciphertext: ciphertextBase64,
ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key),
ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeralKey),
};
const fetchOptions = {

View File

@ -268,10 +268,7 @@ async function decryptWithSenderKeyInner(
const messageKey = await advanceRatchet(groupId, senderIdentity, curKeyIdx);
// TODO: this might fail, handle this
const plaintext = await libloki.crypto.DecryptGCM(
messageKey,
ciphertext.toArrayBuffer()
);
const plaintext = await libloki.crypto.DecryptGCM(messageKey, ciphertext);
return plaintext;
}

View File

@ -7,7 +7,8 @@
TextEncoder,
TextDecoder,
crypto,
dcodeIO
dcodeIO,
libloki
*/
// eslint-disable-next-line func-names
@ -34,6 +35,50 @@
return ivAndCiphertext;
}
async function deriveSymmetricKey(pubkey, seckey) {
const ephemeralSecret = await libsignal.Curve.async.calculateAgreement(
pubkey,
seckey
);
const salt = window.Signal.Crypto.bytesFromString('LOKI');
const key = await crypto.subtle.importKey(
'raw',
salt,
{ name: 'HMAC', hash: { name: 'SHA-256' } },
false,
['sign']
);
const symmetricKey = await crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
key,
ephemeralSecret
);
return symmetricKey;
}
async function encryptForPubkey(pubkeyX25519, payloadBytes) {
const ephemeral = await libloki.crypto.generateEphemeralKeyPair();
const snPubkey = StringView.hexToArrayBuffer(pubkeyX25519);
const symmetricKey = await deriveSymmetricKey(snPubkey, ephemeral.privKey);
const ciphertext = await EncryptGCM(symmetricKey, payloadBytes);
return { ciphertext, symmetricKey, ephemeralKey: ephemeral.pubKey };
}
async function decryptForPubkey(seckeyX25519, ephemKey, ciphertext) {
const symmetricKey = await deriveSymmetricKey(ephemKey, seckeyX25519);
const plaintext = await DecryptGCM(symmetricKey, ciphertext);
return plaintext;
}
async function EncryptGCM(symmetricKey, plaintext) {
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
@ -471,6 +516,8 @@
PairingType,
LokiSessionCipher,
generateEphemeralKeyPair,
encryptForPubkey,
decryptForPubkey,
_decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey,
sha512,
};

View File

@ -679,18 +679,39 @@ MessageReceiver.prototype.extend({
async decryptForMediumGroup(envelope, ciphertextObj) {
const groupId = envelope.source;
// const identity = await window.Signal.Data.getIdentityKeyById(groupId);
// const secretKey = identity.secretKey; // TODO: use this for decryption!
const identity = await window.Signal.Data.getIdentityKeyById(groupId);
const secretKeyHex = identity.secretKey;
if (!secretKeyHex) {
throw new Error(`Secret key is empty for group ${groupId}!`);
}
const { senderIdentity } = envelope;
const {
ciphertext: ciphertext2,
ephemeralKey,
} = textsecure.protobuf.MediumGroupContent.decode(ciphertextObj);
const ephemKey = ephemeralKey.toArrayBuffer();
const secretKey = dcodeIO.ByteBuffer.wrap(
secretKeyHex,
'hex'
).toArrayBuffer();
const res = await libloki.crypto.decryptForPubkey(
secretKey,
ephemKey,
ciphertext2.toArrayBuffer()
);
const {
ciphertext,
keyIdx,
} = textsecure.protobuf.MediumGroupCiphertext.decode(ciphertextObj);
} = textsecure.protobuf.MediumGroupCiphertext.decode(res);
const plaintext = await window.SenderKeyAPI.decryptWithSenderKey(
ciphertext,
ciphertext.toArrayBuffer(),
keyIdx,
groupId,
senderIdentity

View File

@ -492,15 +492,15 @@ OutgoingMessage.prototype = {
},
// Send a message to a public group
async sendPublicMessage(number) {
await this.transmitMessage(
number,
this.message.dataMessage,
this.timestamp,
0 // ttl
);
await this.transmitMessage(
number,
this.message.dataMessage,
this.timestamp,
0 // ttl
);
this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted();
this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted();
},
async sendMediumGroupMessage(groupId) {
@ -524,12 +524,29 @@ OutgoingMessage.prototype = {
return;
}
const source = ourIdentity;
// We should include ciphertext idx in the message
const content = new textsecure.protobuf.MediumGroupCiphertext({
ciphertext,
source,
keyIdx,
});
// Encrypt for the group's identity key to hide source and key idx:
const {
ciphertext: ciphertextOuter,
ephemeralKey,
} = await libloki.crypto.encryptForPubkey(
groupId,
content.encode().toArrayBuffer()
);
const contentOuter = new textsecure.protobuf.MediumGroupContent({
ciphertext: ciphertextOuter,
ephemeralKey,
});
log.debug(
'Group ciphertext: ',
window.Signal.Crypto.arrayBufferToBase64(ciphertext)
@ -540,7 +557,7 @@ OutgoingMessage.prototype = {
ttl,
ourKey: ourIdentity,
sourceDevice: 1,
content: content.encode().toArrayBuffer(),
content: contentOuter.encode().toArrayBuffer(),
isFriendRequest: false,
isSessionRequest: false,
};

View File

@ -42,7 +42,13 @@ message Content {
message MediumGroupCiphertext {
optional bytes ciphertext = 1;
optional uint32 keyIdx = 2;
optional string source = 2;
optional uint32 keyIdx = 3;
}
message MediumGroupContent {
optional bytes ciphertext = 1;
optional bytes ephemeralKey = 2;
}
message MediumGroupUpdate {