Encrypt source for medium groups
This commit is contained in:
parent
dd6b91bb36
commit
61d4c7c349
|
@ -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(
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue