Verified Commit f7754221 authored by Samuel Elliott's avatar Samuel Elliott
Browse files

Verify the sender's public key if it's passed to encryption/signcryption functions/streams

parent b6066dd5
......@@ -40,6 +40,13 @@ test('encrypt stream', async () => {
});
test('decrypt', async () => {
const data = await decrypt(ENCRYPTED, KEYPAIR_BOB, KEYPAIR_ALICE.publicKey);
expect(data.toString()).toBe(INPUT_STRING);
expect(data.sender_public_key).toStrictEqual(KEYPAIR_ALICE.publicKey);
});
test('decrypt doesn\'t require sender public key', async () => {
const data = await decrypt(ENCRYPTED, KEYPAIR_BOB);
expect(data.toString()).toBe(INPUT_STRING);
......@@ -47,6 +54,22 @@ test('decrypt', async () => {
});
test('decrypt stream', async () => {
const stream = new DecryptStream(KEYPAIR_BOB, KEYPAIR_ALICE.publicKey);
const result: Buffer[] = [];
await new Promise((rs, rj) => {
stream.on('error', rj);
stream.on('end', rs);
stream.on('data', chunk => result.push(chunk));
stream.end(ENCRYPTED);
});
expect(result.toString()).toBe(INPUT_STRING);
expect(stream.sender_public_key).toStrictEqual(KEYPAIR_ALICE.publicKey);
});
test('decrypt stream doesn\'t require sender public key', async () => {
const stream = new DecryptStream(KEYPAIR_BOB);
const result: Buffer[] = [];
......
......@@ -43,6 +43,13 @@ test('encrypt stream', async () => {
});
test('decrypt', async () => {
const data = await designcrypt(SIGNCRYPTED, KEYPAIR_BOB, KEYPAIR_ALICE.publicKey);
expect(data.toString()).toBe(INPUT_STRING);
expect(data.sender_public_key).toStrictEqual(KEYPAIR_ALICE.publicKey);
});
test('decrypt doesn\'t require sender public key', async () => {
const data = await designcrypt(SIGNCRYPTED, KEYPAIR_BOB);
expect(data.toString()).toBe(INPUT_STRING);
......@@ -50,6 +57,22 @@ test('decrypt', async () => {
});
test('decrypt stream', async () => {
const stream = new DesigncryptStream(KEYPAIR_BOB, KEYPAIR_ALICE.publicKey);
const result: Buffer[] = [];
await new Promise((rs, rj) => {
stream.on('error', rj);
stream.on('end', rs);
stream.on('data', chunk => result.push(chunk));
stream.end(SIGNCRYPTED);
});
expect(result.toString()).toBe(INPUT_STRING);
expect(stream.sender_public_key).toStrictEqual(KEYPAIR_ALICE.publicKey);
});
test('decrypt stream doesn\'t require sender public key', async () => {
const stream = new DesigncryptStream(KEYPAIR_BOB);
const result: Buffer[] = [];
......
......@@ -151,7 +151,9 @@ export interface DecryptResult extends Buffer {
sender_public_key: Uint8Array | null;
}
export async function decrypt(encrypted: Uint8Array, keypair: tweetnacl.BoxKeyPair): Promise<DecryptResult> {
export async function decrypt(
encrypted: Uint8Array, keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null
): Promise<DecryptResult> {
const stream = new Readable();
stream.push(encrypted);
stream.push(null);
......@@ -168,6 +170,10 @@ export async function decrypt(encrypted: Uint8Array, keypair: tweetnacl.BoxKeyPa
const [payload_key, recipient] = header.decryptPayloadKey(keypair);
const sender_public_key = header.decryptSender(payload_key);
if (sender && !Buffer.from(sender_public_key).equals(sender)) {
throw new Error('Sender public key doesn\'t match');
}
recipient.generateMacKeyForRecipient(header.hash, header.public_key, sender_public_key, keypair.secretKey);
let output = Buffer.alloc(0);
......@@ -197,14 +203,17 @@ export async function decrypt(encrypted: Uint8Array, keypair: tweetnacl.BoxKeyPa
}
export class DecryptStream extends Transform {
readonly sender: Uint8Array | null;
private decoder = new msgpack.Decoder(undefined!, undefined);
private header_data: [EncryptedMessageHeader, Uint8Array, EncryptedMessageRecipient, Uint8Array] | null = null;
private last_payload: EncryptedMessagePayload | null = null;
private payload_index = BigInt(-1);
private i = 0;
constructor(readonly keypair: tweetnacl.BoxKeyPair) {
constructor(readonly keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null) {
super();
this.sender = sender ?? null;
}
get header() {
......@@ -253,6 +262,10 @@ export class DecryptStream extends Transform {
const [payload_key, recipient] = header.decryptPayloadKey(this.keypair);
const sender_public_key = header.decryptSender(payload_key);
if (this.sender && !Buffer.from(sender_public_key).equals(this.sender)) {
throw new Error('Sender public key doesn\'t match');
}
recipient.generateMacKeyForRecipient(
header.hash, header.public_key, sender_public_key, this.keypair.secretKey
);
......
......@@ -146,7 +146,9 @@ export interface DesigncryptResult extends Buffer {
sender_public_key: Uint8Array | null;
}
export async function designcrypt(signcrypted: Uint8Array, keypair: tweetnacl.BoxKeyPair): Promise<DesigncryptResult> {
export async function designcrypt(
signcrypted: Uint8Array, keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null
): Promise<DesigncryptResult> {
const stream = new Readable();
stream.push(signcrypted);
stream.push(null);
......@@ -166,6 +168,10 @@ export async function designcrypt(signcrypted: Uint8Array, keypair: tweetnacl.Bo
const [payload_key, recipient] = payload_key_and_recipient;
const sender_public_key = header.decryptSender(payload_key);
if (sender && (!sender_public_key || !Buffer.from(sender_public_key).equals(sender))) {
throw new Error('Sender public key doesn\'t match');
}
let output = Buffer.alloc(0);
for (const i in items) {
......@@ -184,7 +190,7 @@ export async function designcrypt(signcrypted: Uint8Array, keypair: tweetnacl.Bo
}
if (!items.length) {
throw new Error('No encrypted payloads, message truncated?');
throw new Error('No signcrypted payloads, message truncated?');
}
return Object.assign(output, {
......@@ -193,6 +199,7 @@ export async function designcrypt(signcrypted: Uint8Array, keypair: tweetnacl.Bo
}
export class DesigncryptStream extends Transform {
readonly sender: Uint8Array | null;
private decoder = new msgpack.Decoder(undefined!, undefined);
private header_data: [
SigncryptedMessageHeader, Uint8Array, SigncryptedMessageRecipient, Uint8Array | null,
......@@ -201,8 +208,10 @@ export class DesigncryptStream extends Transform {
private payload_index = BigInt(-1);
private i = 0;
constructor(readonly keypair: tweetnacl.BoxKeyPair) {
constructor(readonly keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null) {
super();
this.sender = sender;
}
get header() {
......@@ -254,6 +263,10 @@ export class DesigncryptStream extends Transform {
const [payload_key, recipient] = payload_key_and_recipient;
const sender_public_key = header.decryptSender(payload_key);
if (this.sender && (!sender_public_key || !Buffer.from(sender_public_key).equals(this.sender))) {
throw new Error('Sender public key doesn\'t match');
}
this.header_data = [header, payload_key, recipient, sender_public_key];
} else {
this.payload_index++;
......@@ -286,7 +299,7 @@ export class DesigncryptStream extends Transform {
}
if (!this.last_payload) {
throw new Error('No encrypted payloads, message truncated?');
throw new Error('No signcrypted payloads, message truncated?');
}
} catch (err) {
return callback(err);
......
......@@ -15,10 +15,10 @@ export async function encryptAndArmor(
return armor(encrypted, {message_type: MessageType.ENCRYPTED_MESSAGE});
}
export async function dearmorAndDecrypt(
encrypted: string, keypair: tweetnacl.BoxKeyPair
encrypted: string, keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null
): Promise<DearmorAndDecryptResult> {
const dearmored = dearmor(encrypted);
return Object.assign(await decrypt(dearmored, keypair), {
return Object.assign(await decrypt(dearmored, keypair, sender), {
remaining: dearmored.remaining,
header_info: dearmored.header_info,
});
......@@ -42,9 +42,9 @@ export class DearmorAndDecryptStream extends Pumpify {
readonly dearmor: DearmorStream;
readonly decrypt: DecryptStream;
constructor(keypair: tweetnacl.BoxKeyPair, armor_options?: Partial<ArmorOptions>) {
constructor(keypair: tweetnacl.BoxKeyPair, sender?: Uint8Array | null, armor_options?: Partial<ArmorOptions>) {
const dearmor = new DearmorStream(armor_options);
const decrypt = new DecryptStream(keypair);
const decrypt = new DecryptStream(keypair, sender);
super(dearmor, decrypt);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment