mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
finalize multi-device
This commit is contained in:
16
WASignalGroup/ciphertext_message.js
Normal file
16
WASignalGroup/ciphertext_message.js
Normal file
@@ -0,0 +1,16 @@
|
||||
class CiphertextMessage {
|
||||
UNSUPPORTED_VERSION = 1;
|
||||
|
||||
CURRENT_VERSION = 3;
|
||||
|
||||
WHISPER_TYPE = 2;
|
||||
|
||||
PREKEY_TYPE = 3;
|
||||
|
||||
SENDERKEY_TYPE = 4;
|
||||
|
||||
SENDERKEY_DISTRIBUTION_TYPE = 5;
|
||||
|
||||
ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
}
|
||||
module.exports = CiphertextMessage;
|
||||
41
WASignalGroup/group.proto
Normal file
41
WASignalGroup/group.proto
Normal file
@@ -0,0 +1,41 @@
|
||||
package groupproto;
|
||||
|
||||
message SenderKeyMessage {
|
||||
optional uint32 id = 1;
|
||||
optional uint32 iteration = 2;
|
||||
optional bytes ciphertext = 3;
|
||||
}
|
||||
|
||||
message SenderKeyDistributionMessage {
|
||||
optional uint32 id = 1;
|
||||
optional uint32 iteration = 2;
|
||||
optional bytes chainKey = 3;
|
||||
optional bytes signingKey = 4;
|
||||
}
|
||||
|
||||
|
||||
message SenderKeyStateStructure {
|
||||
message SenderChainKey {
|
||||
optional uint32 iteration = 1;
|
||||
optional bytes seed = 2;
|
||||
}
|
||||
|
||||
message SenderMessageKey {
|
||||
optional uint32 iteration = 1;
|
||||
optional bytes seed = 2;
|
||||
}
|
||||
|
||||
message SenderSigningKey {
|
||||
optional bytes public = 1;
|
||||
optional bytes private = 2;
|
||||
}
|
||||
|
||||
optional uint32 senderKeyId = 1;
|
||||
optional SenderChainKey senderChainKey = 2;
|
||||
optional SenderSigningKey senderSigningKey = 3;
|
||||
repeated SenderMessageKey senderMessageKeys = 4;
|
||||
}
|
||||
|
||||
message SenderKeyRecordStructure {
|
||||
repeated SenderKeyStateStructure senderKeyStates = 1;
|
||||
}
|
||||
106
WASignalGroup/group_cipher.js
Normal file
106
WASignalGroup/group_cipher.js
Normal file
@@ -0,0 +1,106 @@
|
||||
const SenderKeyMessage = require('./sender_key_message');
|
||||
const crypto = require('libsignal/src/crypto');
|
||||
|
||||
class GroupCipher {
|
||||
constructor(senderKeyStore, senderKeyName) {
|
||||
this.senderKeyStore = senderKeyStore;
|
||||
this.senderKeyName = senderKeyName;
|
||||
}
|
||||
|
||||
async encrypt(paddedPlaintext) {
|
||||
try {
|
||||
const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
|
||||
const senderKeyState = record.getSenderKeyState();
|
||||
const senderKey = senderKeyState.getSenderChainKey().getSenderMessageKey();
|
||||
|
||||
const ciphertext = await this.getCipherText(
|
||||
senderKey.getIv(),
|
||||
senderKey.getCipherKey(),
|
||||
paddedPlaintext
|
||||
);
|
||||
|
||||
const senderKeyMessage = new SenderKeyMessage(
|
||||
senderKeyState.getKeyId(),
|
||||
senderKey.getIteration(),
|
||||
ciphertext,
|
||||
senderKeyState.getSigningKeyPrivate()
|
||||
);
|
||||
senderKeyState.setSenderChainKey(senderKeyState.getSenderChainKey().getNext());
|
||||
await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
|
||||
return senderKeyMessage.serialize();
|
||||
} catch (e) {
|
||||
//console.log(e.stack);
|
||||
throw new Error('NoSessionException');
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(senderKeyMessageBytes) {
|
||||
const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
|
||||
if (!record) throw new Error(`No sender key for: ${this.senderKeyName}`);
|
||||
|
||||
const senderKeyMessage = new SenderKeyMessage(null, null, null, null, senderKeyMessageBytes);
|
||||
|
||||
const senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
|
||||
//senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
|
||||
const senderKey = this.getSenderKey(senderKeyState, senderKeyMessage.getIteration());
|
||||
// senderKeyState.senderKeyStateStructure.senderSigningKey.private =
|
||||
|
||||
const plaintext = await this.getPlainText(
|
||||
senderKey.getIv(),
|
||||
senderKey.getCipherKey(),
|
||||
senderKeyMessage.getCipherText()
|
||||
);
|
||||
|
||||
await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
getSenderKey(senderKeyState, iteration) {
|
||||
let senderChainKey = senderKeyState.getSenderChainKey();
|
||||
if (senderChainKey.getIteration() > iteration) {
|
||||
if (senderKeyState.hasSenderMessageKey(iteration)) {
|
||||
return senderKeyState.removeSenderMessageKey(iteration);
|
||||
}
|
||||
throw new Error(
|
||||
`Received message with old counter: ${senderChainKey.getIteration()}, ${iteration}`
|
||||
);
|
||||
}
|
||||
|
||||
if (senderChainKey.getIteration() - iteration > 2000) {
|
||||
throw new Error('Over 2000 messages into the future!');
|
||||
}
|
||||
|
||||
while (senderChainKey.getIteration() < iteration) {
|
||||
senderKeyState.addSenderMessageKey(senderChainKey.getSenderMessageKey());
|
||||
senderChainKey = senderChainKey.getNext();
|
||||
}
|
||||
|
||||
senderKeyState.setSenderChainKey(senderChainKey.getNext());
|
||||
return senderChainKey.getSenderMessageKey();
|
||||
}
|
||||
|
||||
getPlainText(iv, key, ciphertext) {
|
||||
try {
|
||||
const plaintext = crypto.decrypt(key, ciphertext, iv);
|
||||
return plaintext;
|
||||
} catch (e) {
|
||||
//console.log(e.stack);
|
||||
throw new Error('InvalidMessageException');
|
||||
}
|
||||
}
|
||||
|
||||
getCipherText(iv, key, plaintext) {
|
||||
try {
|
||||
iv = typeof iv === 'string' ? Buffer.from(iv, 'base64') : iv;
|
||||
key = typeof key === 'string' ? Buffer.from(key, 'base64') : key;
|
||||
const crypted = crypto.encrypt(key, Buffer.from(plaintext), iv);
|
||||
return crypted;
|
||||
} catch (e) {
|
||||
//console.log(e.stack);
|
||||
throw new Error('InvalidMessageException');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GroupCipher;
|
||||
51
WASignalGroup/group_session_builder.js
Normal file
51
WASignalGroup/group_session_builder.js
Normal file
@@ -0,0 +1,51 @@
|
||||
//const utils = require('../../common/utils');
|
||||
const SenderKeyDistributionMessage = require('./sender_key_distribution_message');
|
||||
|
||||
const keyhelper = require("libsignal/src/keyhelper");
|
||||
class GroupSessionBuilder {
|
||||
constructor(senderKeyStore) {
|
||||
this.senderKeyStore = senderKeyStore;
|
||||
}
|
||||
|
||||
async process(senderKeyName, senderKeyDistributionMessage) {
|
||||
//console.log('GroupSessionBuilder process', senderKeyName, senderKeyDistributionMessage);
|
||||
const senderKeyRecord = await this.senderKeyStore.loadSenderKey(senderKeyName);
|
||||
senderKeyRecord.addSenderKeyState(
|
||||
senderKeyDistributionMessage.getId(),
|
||||
senderKeyDistributionMessage.getIteration(),
|
||||
senderKeyDistributionMessage.getChainKey(),
|
||||
senderKeyDistributionMessage.getSignatureKey()
|
||||
);
|
||||
await this.senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
||||
}
|
||||
|
||||
// [{"senderKeyId":1742199468,"senderChainKey":{"iteration":0,"seed":"yxMY9VFQcXEP34olRAcGCtsgx1XoKsHfDIh+1ea4HAQ="},"senderSigningKey":{"public":""}}]
|
||||
async create(senderKeyName) {
|
||||
try {
|
||||
const senderKeyRecord = await this.senderKeyStore.loadSenderKey(senderKeyName);
|
||||
//console.log('GroupSessionBuilder create session', senderKeyName, senderKeyRecord);
|
||||
|
||||
if (senderKeyRecord.isEmpty()) {
|
||||
const keyId = keyhelper.generateSenderKeyId();
|
||||
const senderKey = keyhelper.generateSenderKey();
|
||||
const signingKey = keyhelper.generateSenderSigningKey();
|
||||
|
||||
senderKeyRecord.setSenderKeyState(keyId, 0, senderKey, signingKey);
|
||||
await this.senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
||||
}
|
||||
|
||||
const state = senderKeyRecord.getSenderKeyState();
|
||||
|
||||
return new SenderKeyDistributionMessage(
|
||||
state.getKeyId(),
|
||||
state.getSenderChainKey().getIteration(),
|
||||
state.getSenderChainKey().getSeed(),
|
||||
state.getSigningKeyPublic()
|
||||
);
|
||||
} catch (e) {
|
||||
//console.log(e.stack);
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = GroupSessionBuilder;
|
||||
5
WASignalGroup/index.js
Normal file
5
WASignalGroup/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports.GroupSessionBuilder = require('./group_session_builder')
|
||||
module.exports.SenderKeyDistributionMessage = require('./sender_key_distribution_message')
|
||||
module.exports.SenderKeyRecord = require('./sender_key_record')
|
||||
module.exports.SenderKeyName = require('./sender_key_name')
|
||||
module.exports.GroupCipher = require('./group_cipher')
|
||||
13
WASignalGroup/protobufs.js
Normal file
13
WASignalGroup/protobufs.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const path = require('path');
|
||||
const protobuf = require('protobufjs');
|
||||
|
||||
const protodir = path.resolve(__dirname);
|
||||
const group = protobuf.loadSync(path.join(protodir, 'group.proto')).lookup('groupproto');
|
||||
|
||||
module.exports = {
|
||||
SenderKeyDistributionMessage: group.lookup('SenderKeyDistributionMessage'),
|
||||
SenderKeyMessage: group.lookup('SenderKeyMessage'),
|
||||
SenderKeyStateStructure: group.lookup('SenderKeyStateStructure'),
|
||||
SenderChainKey: group.lookup('SenderChainKey'),
|
||||
SenderSigningKey: group.lookup('SenderSigningKey'),
|
||||
};
|
||||
6
WASignalGroup/readme.md
Normal file
6
WASignalGroup/readme.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Signal-Group
|
||||
|
||||
This contains the code to decrypt/encrypt WA group messages.
|
||||
Originally from [pokearaujo/libsignal-node](https://github.com/pokearaujo/libsignal-node)
|
||||
|
||||
The code has been moved outside the signal package as I felt it didn't belong in ths signal package, as it isn't inherently a part of signal but of WA.
|
||||
50
WASignalGroup/sender_chain_key.js
Normal file
50
WASignalGroup/sender_chain_key.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const SenderMessageKey = require('./sender_message_key');
|
||||
//const HKDF = require('./hkdf');
|
||||
const crypto = require('libsignal/src/crypto');
|
||||
|
||||
class SenderChainKey {
|
||||
MESSAGE_KEY_SEED = Buffer.from([0x01]);
|
||||
|
||||
CHAIN_KEY_SEED = Buffer.from([0x02]);
|
||||
|
||||
iteration = 0;
|
||||
|
||||
chainKey = Buffer.alloc(0);
|
||||
|
||||
constructor(iteration, chainKey) {
|
||||
this.iteration = iteration;
|
||||
this.chainKey = chainKey;
|
||||
}
|
||||
|
||||
getIteration() {
|
||||
return this.iteration;
|
||||
}
|
||||
|
||||
getSenderMessageKey() {
|
||||
return new SenderMessageKey(
|
||||
this.iteration,
|
||||
this.getDerivative(this.MESSAGE_KEY_SEED, this.chainKey)
|
||||
);
|
||||
}
|
||||
|
||||
getNext() {
|
||||
return new SenderChainKey(
|
||||
this.iteration + 1,
|
||||
this.getDerivative(this.CHAIN_KEY_SEED, this.chainKey)
|
||||
);
|
||||
}
|
||||
|
||||
getSeed() {
|
||||
return typeof this.chainKey === 'string' ? Buffer.from(this.chainKey, 'base64') : this.chainKey;
|
||||
}
|
||||
|
||||
getDerivative(seed, key) {
|
||||
key = typeof key === 'string' ? Buffer.from(key, 'base64') : key;
|
||||
const hash = crypto.calculateMAC(key, seed);
|
||||
//const hash = new Hash().hmac_hash(key, seed, 'sha256', '');
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderChainKey;
|
||||
78
WASignalGroup/sender_key_distribution_message.js
Normal file
78
WASignalGroup/sender_key_distribution_message.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const CiphertextMessage = require('./ciphertext_message');
|
||||
const protobufs = require('./protobufs');
|
||||
|
||||
class SenderKeyDistributionMessage extends CiphertextMessage {
|
||||
constructor(
|
||||
id = null,
|
||||
iteration = null,
|
||||
chainKey = null,
|
||||
signatureKey = null,
|
||||
serialized = null
|
||||
) {
|
||||
super();
|
||||
if (serialized) {
|
||||
try {
|
||||
const version = serialized[0];
|
||||
const message = serialized.slice(1);
|
||||
|
||||
const distributionMessage = protobufs.SenderKeyDistributionMessage.decode(
|
||||
message
|
||||
).toJSON();
|
||||
this.serialized = serialized;
|
||||
this.id = distributionMessage.id;
|
||||
this.iteration = distributionMessage.iteration;
|
||||
this.chainKey = distributionMessage.chainKey;
|
||||
this.signatureKey = distributionMessage.signingKey;
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
} else {
|
||||
const version = this.intsToByteHighAndLow(this.CURRENT_VERSION, this.CURRENT_VERSION);
|
||||
this.id = id;
|
||||
this.iteration = iteration;
|
||||
this.chainKey = chainKey;
|
||||
this.signatureKey = signatureKey;
|
||||
const message = protobufs.SenderKeyDistributionMessage.encode(
|
||||
protobufs.SenderKeyDistributionMessage.create({
|
||||
id,
|
||||
iteration,
|
||||
chainKey,
|
||||
signingKey: this.signatureKey,
|
||||
})
|
||||
).finish();
|
||||
this.serialized = Buffer.concat([Buffer.from([version]), message]);
|
||||
}
|
||||
}
|
||||
|
||||
intsToByteHighAndLow(highValue, lowValue) {
|
||||
return (((highValue << 4) | lowValue) & 0xff) % 256;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.serialized;
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this.SENDERKEY_DISTRIBUTION_TYPE;
|
||||
}
|
||||
|
||||
getIteration() {
|
||||
return this.iteration;
|
||||
}
|
||||
|
||||
getChainKey() {
|
||||
return typeof this.chainKey === 'string' ? Buffer.from(this.chainKey, 'base64') : this.chainKey;
|
||||
}
|
||||
|
||||
getSignatureKey() {
|
||||
return typeof this.signatureKey === 'string'
|
||||
? Buffer.from(this.signatureKey, 'base64')
|
||||
: this.signatureKey;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderKeyDistributionMessage;
|
||||
92
WASignalGroup/sender_key_message.js
Normal file
92
WASignalGroup/sender_key_message.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const CiphertextMessage = require('./ciphertext_message');
|
||||
const curve = require('libsignal/src/curve');
|
||||
const protobufs = require('./protobufs');
|
||||
|
||||
class SenderKeyMessage extends CiphertextMessage {
|
||||
SIGNATURE_LENGTH = 64;
|
||||
|
||||
constructor(
|
||||
keyId = null,
|
||||
iteration = null,
|
||||
ciphertext = null,
|
||||
signatureKey = null,
|
||||
serialized = null
|
||||
) {
|
||||
super();
|
||||
if (serialized) {
|
||||
const version = serialized[0];
|
||||
const message = serialized.slice(1, serialized.length - this.SIGNATURE_LENGTH);
|
||||
const signature = serialized.slice(-1 * this.SIGNATURE_LENGTH);
|
||||
const senderKeyMessage = protobufs.SenderKeyMessage.decode(message).toJSON();
|
||||
senderKeyMessage.ciphertext = Buffer.from(senderKeyMessage.ciphertext, 'base64');
|
||||
|
||||
this.serialized = serialized;
|
||||
this.messageVersion = (version & 0xff) >> 4;
|
||||
|
||||
this.keyId = senderKeyMessage.id;
|
||||
this.iteration = senderKeyMessage.iteration;
|
||||
this.ciphertext = senderKeyMessage.ciphertext;
|
||||
this.signature = signature;
|
||||
} else {
|
||||
const version = (((this.CURRENT_VERSION << 4) | this.CURRENT_VERSION) & 0xff) % 256;
|
||||
ciphertext = Buffer.from(ciphertext); // .toString('base64');
|
||||
const message = protobufs.SenderKeyMessage.encode(
|
||||
protobufs.SenderKeyMessage.create({
|
||||
id: keyId,
|
||||
iteration,
|
||||
ciphertext,
|
||||
})
|
||||
).finish();
|
||||
|
||||
const signature = this.getSignature(
|
||||
signatureKey,
|
||||
Buffer.concat([Buffer.from([version]), message])
|
||||
);
|
||||
this.serialized = Buffer.concat([Buffer.from([version]), message, Buffer.from(signature)]);
|
||||
this.messageVersion = this.CURRENT_VERSION;
|
||||
this.keyId = keyId;
|
||||
this.iteration = iteration;
|
||||
this.ciphertext = ciphertext;
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
getKeyId() {
|
||||
return this.keyId;
|
||||
}
|
||||
|
||||
getIteration() {
|
||||
return this.iteration;
|
||||
}
|
||||
|
||||
getCipherText() {
|
||||
return this.ciphertext;
|
||||
}
|
||||
|
||||
verifySignature(signatureKey) {
|
||||
const part1 = this.serialized.slice(0, this.serialized.length - this.SIGNATURE_LENGTH + 1);
|
||||
const part2 = this.serialized.slice(-1 * this.SIGNATURE_LENGTH);
|
||||
const res = curve.verifySignature(signatureKey, part1, part2);
|
||||
if (!res) throw new Error('Invalid signature!');
|
||||
}
|
||||
|
||||
getSignature(signatureKey, serialized) {
|
||||
const signature = Buffer.from(
|
||||
curve.calculateSignature(
|
||||
signatureKey,
|
||||
serialized
|
||||
)
|
||||
);
|
||||
return signature;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.serialized;
|
||||
}
|
||||
|
||||
getType() {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderKeyMessage;
|
||||
70
WASignalGroup/sender_key_name.js
Normal file
70
WASignalGroup/sender_key_name.js
Normal file
@@ -0,0 +1,70 @@
|
||||
function isNull(str) {
|
||||
return str === null || str.value === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* java String hashCode 的实现
|
||||
* @param strKey
|
||||
* @return intValue
|
||||
*/
|
||||
function intValue(num) {
|
||||
const MAX_VALUE = 0x7fffffff;
|
||||
const MIN_VALUE = -0x80000000;
|
||||
if (num > MAX_VALUE || num < MIN_VALUE) {
|
||||
// eslint-disable-next-line
|
||||
return (num &= 0xffffffff);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function hashCode(strKey) {
|
||||
let hash = 0;
|
||||
if (!isNull(strKey)) {
|
||||
for (let i = 0; i < strKey.length; i++) {
|
||||
hash = hash * 31 + strKey.charCodeAt(i);
|
||||
hash = intValue(hash);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将js页面的number类型转换为java的int类型
|
||||
* @param num
|
||||
* @return intValue
|
||||
*/
|
||||
|
||||
class SenderKeyName {
|
||||
constructor(groupId, sender) {
|
||||
this.groupId = groupId;
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
getSender() {
|
||||
return this.sender;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return `${this.groupId}::${this.sender.id}::${this.sender.deviceId}`;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.serialize();
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
if (other === null) return false;
|
||||
if (!(other instanceof SenderKeyName)) return false;
|
||||
return this.groupId === other.groupId && this.sender.toString() === other.sender.toString();
|
||||
}
|
||||
|
||||
hashCode() {
|
||||
return hashCode(this.groupId) ^ hashCode(this.sender.toString());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderKeyName;
|
||||
54
WASignalGroup/sender_key_record.js
Normal file
54
WASignalGroup/sender_key_record.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const SenderKeyState = require('./sender_key_state');
|
||||
|
||||
class SenderKeyRecord {
|
||||
MAX_STATES = 5;
|
||||
|
||||
constructor(serialized) {
|
||||
this.senderKeyStates = [];
|
||||
|
||||
if (serialized) {
|
||||
const list = serialized;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const structure = list[i];
|
||||
this.senderKeyStates.push(
|
||||
new SenderKeyState(null, null, null, null, null, null, structure)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.senderKeyStates.length === 0;
|
||||
}
|
||||
|
||||
getSenderKeyState(keyId) {
|
||||
if (!keyId && this.senderKeyStates.length) return this.senderKeyStates[0];
|
||||
for (let i = 0; i < this.senderKeyStates.length; i++) {
|
||||
const state = this.senderKeyStates[i];
|
||||
if (state.getKeyId() === keyId) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
throw new Error(`No keys for: ${keyId}`);
|
||||
}
|
||||
|
||||
addSenderKeyState(id, iteration, chainKey, signatureKey) {
|
||||
this.senderKeyStates.push(new SenderKeyState(id, iteration, chainKey, null, signatureKey));
|
||||
}
|
||||
|
||||
setSenderKeyState(id, iteration, chainKey, keyPair) {
|
||||
this.senderKeyStates.length = 0;
|
||||
this.senderKeyStates.push(new SenderKeyState(id, iteration, chainKey, keyPair));
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const recordStructure = [];
|
||||
for (let i = 0; i < this.senderKeyStates.length; i++) {
|
||||
const senderKeyState = this.senderKeyStates[i];
|
||||
recordStructure.push(senderKeyState.getStructure());
|
||||
}
|
||||
return recordStructure;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderKeyRecord;
|
||||
129
WASignalGroup/sender_key_state.js
Normal file
129
WASignalGroup/sender_key_state.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const SenderChainKey = require('./sender_chain_key');
|
||||
const SenderMessageKey = require('./sender_message_key');
|
||||
|
||||
const protobufs = require('./protobufs');
|
||||
|
||||
class SenderKeyState {
|
||||
MAX_MESSAGE_KEYS = 2000;
|
||||
|
||||
constructor(
|
||||
id = null,
|
||||
iteration = null,
|
||||
chainKey = null,
|
||||
signatureKeyPair = null,
|
||||
signatureKeyPublic = null,
|
||||
signatureKeyPrivate = null,
|
||||
senderKeyStateStructure = null
|
||||
) {
|
||||
if (senderKeyStateStructure) {
|
||||
this.senderKeyStateStructure = senderKeyStateStructure;
|
||||
} else {
|
||||
if (signatureKeyPair) {
|
||||
signatureKeyPublic = signatureKeyPair.public;
|
||||
signatureKeyPrivate = signatureKeyPair.private;
|
||||
}
|
||||
|
||||
chainKey = typeof chainKey === 'string' ? Buffer.from(chainKey, 'base64') : chainKey;
|
||||
this.senderKeyStateStructure = protobufs.SenderKeyStateStructure.create();
|
||||
const senderChainKeyStructure = protobufs.SenderChainKey.create();
|
||||
senderChainKeyStructure.iteration = iteration;
|
||||
senderChainKeyStructure.seed = chainKey;
|
||||
this.senderKeyStateStructure.senderChainKey = senderChainKeyStructure;
|
||||
|
||||
const signingKeyStructure = protobufs.SenderSigningKey.create();
|
||||
signingKeyStructure.public =
|
||||
typeof signatureKeyPublic === 'string' ?
|
||||
Buffer.from(signatureKeyPublic, 'base64') :
|
||||
signatureKeyPublic;
|
||||
if (signatureKeyPrivate) {
|
||||
signingKeyStructure.private =
|
||||
typeof signatureKeyPrivate === 'string' ?
|
||||
Buffer.from(signatureKeyPrivate, 'base64') :
|
||||
signatureKeyPrivate;
|
||||
}
|
||||
this.senderKeyStateStructure.senderKeyId = id;
|
||||
this.senderChainKey = senderChainKeyStructure;
|
||||
this.senderKeyStateStructure.senderSigningKey = signingKeyStructure;
|
||||
}
|
||||
this.senderKeyStateStructure.senderMessageKeys =
|
||||
this.senderKeyStateStructure.senderMessageKeys || [];
|
||||
}
|
||||
|
||||
SenderKeyState(senderKeyStateStructure) {
|
||||
this.senderKeyStateStructure = senderKeyStateStructure;
|
||||
}
|
||||
|
||||
getKeyId() {
|
||||
return this.senderKeyStateStructure.senderKeyId;
|
||||
}
|
||||
|
||||
getSenderChainKey() {
|
||||
return new SenderChainKey(
|
||||
this.senderKeyStateStructure.senderChainKey.iteration,
|
||||
this.senderKeyStateStructure.senderChainKey.seed
|
||||
);
|
||||
}
|
||||
|
||||
setSenderChainKey(chainKey) {
|
||||
const senderChainKeyStructure = protobufs.SenderChainKey.create({
|
||||
iteration: chainKey.getIteration(),
|
||||
seed: chainKey.getSeed(),
|
||||
});
|
||||
this.senderKeyStateStructure.senderChainKey = senderChainKeyStructure;
|
||||
}
|
||||
|
||||
getSigningKeyPublic() {
|
||||
return typeof this.senderKeyStateStructure.senderSigningKey.public === 'string' ?
|
||||
Buffer.from(this.senderKeyStateStructure.senderSigningKey.public, 'base64') :
|
||||
this.senderKeyStateStructure.senderSigningKey.public;
|
||||
}
|
||||
|
||||
getSigningKeyPrivate() {
|
||||
return typeof this.senderKeyStateStructure.senderSigningKey.private === 'string' ?
|
||||
Buffer.from(this.senderKeyStateStructure.senderSigningKey.private, 'base64') :
|
||||
this.senderKeyStateStructure.senderSigningKey.private;
|
||||
}
|
||||
|
||||
hasSenderMessageKey(iteration) {
|
||||
const list = this.senderKeyStateStructure.senderMessageKeys;
|
||||
for (let o = 0; o < list.length; o++) {
|
||||
const senderMessageKey = list[o];
|
||||
if (senderMessageKey.iteration === iteration) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addSenderMessageKey(senderMessageKey) {
|
||||
const senderMessageKeyStructure = protobufs.SenderKeyStateStructure.create({
|
||||
iteration: senderMessageKey.getIteration(),
|
||||
seed: senderMessageKey.getSeed(),
|
||||
});
|
||||
this.senderKeyStateStructure.senderMessageKeys.push(senderMessageKeyStructure);
|
||||
|
||||
if (this.senderKeyStateStructure.senderMessageKeys.length > this.MAX_MESSAGE_KEYS) {
|
||||
this.senderKeyStateStructure.senderMessageKeys.shift();
|
||||
}
|
||||
}
|
||||
|
||||
removeSenderMessageKey(iteration) {
|
||||
let result = null;
|
||||
|
||||
this.senderKeyStateStructure.senderMessageKeys = this.senderKeyStateStructure.senderMessageKeys.filter(
|
||||
senderMessageKey => {
|
||||
if (senderMessageKey.iteration === iteration) result = senderMessageKey;
|
||||
return senderMessageKey.iteration !== iteration;
|
||||
}
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
return new SenderMessageKey(result.iteration, result.seed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getStructure() {
|
||||
return this.senderKeyStateStructure;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SenderKeyState;
|
||||
39
WASignalGroup/sender_message_key.js
Normal file
39
WASignalGroup/sender_message_key.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { deriveSecrets } = require('libsignal/src/crypto');
|
||||
class SenderMessageKey {
|
||||
iteration = 0;
|
||||
|
||||
iv = Buffer.alloc(0);
|
||||
|
||||
cipherKey = Buffer.alloc(0);
|
||||
|
||||
seed = Buffer.alloc(0);
|
||||
|
||||
constructor(iteration, seed) {
|
||||
const derivative = deriveSecrets(seed, Buffer.alloc(32), Buffer.from('WhisperGroup'));
|
||||
const keys = new Uint8Array(32);
|
||||
keys.set(new Uint8Array(derivative[0].slice(16)));
|
||||
keys.set(new Uint8Array(derivative[1].slice(0, 16)), 16);
|
||||
this.iv = Buffer.from(derivative[0].slice(0, 16));
|
||||
this.cipherKey = Buffer.from(keys.buffer);
|
||||
|
||||
this.iteration = iteration;
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
getIteration() {
|
||||
return this.iteration;
|
||||
}
|
||||
|
||||
getIv() {
|
||||
return this.iv;
|
||||
}
|
||||
|
||||
getCipherKey() {
|
||||
return this.cipherKey;
|
||||
}
|
||||
|
||||
getSeed() {
|
||||
return this.seed;
|
||||
}
|
||||
}
|
||||
module.exports = SenderMessageKey;
|
||||
Reference in New Issue
Block a user