validate aead decryption

This commit is contained in:
Marin Petrunić 2020-03-01 19:05:53 +01:00
parent b93a50c8b0
commit 638b0773e5
No known key found for this signature in database
GPG Key ID: 834D07135E110DA5
14 changed files with 115 additions and 115 deletions

View File

@ -64,7 +64,7 @@
"webpack": "^4.41.5" "webpack": "^4.41.5"
}, },
"dependencies": { "dependencies": {
"bcrypto": "^4.2.3", "bcrypto": "5.0.3",
"bn.js": "^5.0.0", "bn.js": "^5.0.0",
"buffer": "^5.4.3", "buffer": "^5.4.3",
"debug": "^4.1.1", "debug": "^4.1.1",

View File

@ -6,5 +6,5 @@ export interface IHandshake {
session: NoiseSession; session: NoiseSession;
remotePeer: PeerId; remotePeer: PeerId;
encrypt(plaintext: bytes, session: NoiseSession): bytes; encrypt(plaintext: bytes, session: NoiseSession): bytes;
decrypt(ciphertext: bytes, session: NoiseSession): bytes; decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean};
} }

View File

@ -39,7 +39,10 @@ export function decryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
} }
const chunk = chunkBuffer.slice(i, end); const chunk = chunkBuffer.slice(i, end);
const decrypted = await handshake.decrypt(chunk, handshake.session); const {plaintext: decrypted, valid} = await handshake.decrypt(chunk, handshake.session);
if(!valid) {
throw new Error("Failed to validate decrypted chunk");
}
yield decrypted; yield decrypted;
} }
} }

View File

@ -55,7 +55,10 @@ export class IKHandshake implements IHandshake {
const receivedMsg = await this.connection.readLP(); const receivedMsg = await this.connection.readLP();
try { try {
const receivedMessageBuffer = decode1(receivedMsg.slice()); const receivedMessageBuffer = decode1(receivedMsg.slice());
const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("ik handshake stage 0 decryption validation fail");
}
logger("IK Stage 0 - Responder got message, going to verify payload."); logger("IK Stage 0 - Responder got message, going to verify payload.");
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
@ -74,10 +77,12 @@ export class IKHandshake implements IHandshake {
logger("IK Stage 1 - Initiator receiving message..."); logger("IK Stage 1 - Initiator receiving message...");
const receivedMsg = (await this.connection.readLP()).slice(); const receivedMsg = (await this.connection.readLP()).slice();
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg)); const receivedMessageBuffer = decode0(Buffer.from(receivedMsg));
const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
logger("IK Stage 1 - Initiator got message, going to verify payload."); logger("IK Stage 1 - Initiator got message, going to verify payload.");
try { try {
if(!valid) {
throw new Error("ik stage 1 decryption validation fail");
}
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer); await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer);
@ -94,7 +99,7 @@ export class IKHandshake implements IHandshake {
} }
} }
public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer { public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes, valid: boolean} {
const cs = this.getCS(session, false); const cs = this.getCS(session, false);
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext); return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
} }

View File

@ -37,13 +37,16 @@ export class XXFallbackHandshake extends XXHandshake {
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys); this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys);
logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator."); logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.");
} else { } else {
logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.") logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.");
const receivedMessageBuffer = decode0(this.initialMsg); const receivedMessageBuffer = decode0(this.initialMsg);
this.xx.recvMessage(this.session, { const {valid} = this.xx.recvMessage(this.session, {
ne: receivedMessageBuffer.ne, ne: receivedMessageBuffer.ne,
ns: Buffer.alloc(0), ns: Buffer.alloc(0),
ciphertext: Buffer.alloc(0), ciphertext: Buffer.alloc(0),
}); });
if(!valid) {
throw new Error("xx fallback stage 0 decryption validation fail");
}
logger("XX Fallback Stage 0 - Responder used received message from IK."); logger("XX Fallback Stage 0 - Responder used received message from IK.");
} }
} }
@ -52,7 +55,10 @@ export class XXFallbackHandshake extends XXHandshake {
public async exchange(): Promise<void> { public async exchange(): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
const receivedMessageBuffer = decode1(this.initialMsg); const receivedMessageBuffer = decode1(this.initialMsg);
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx fallback stage 1 decryption validation fail");
}
logger('XX Fallback Stage 1 - Initiator used received message from IK.'); logger('XX Fallback Stage 1 - Initiator used received message from IK.');
logger("Initiator going to check remote's signature..."); logger("Initiator going to check remote's signature...");

View File

@ -58,7 +58,10 @@ export class XXHandshake implements IHandshake {
} else { } else {
logger("Stage 0 - Responder waiting to receive first message..."); logger("Stage 0 - Responder waiting to receive first message...");
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode0((await this.connection.readLP()).slice());
this.xx.recvMessage(this.session, receivedMessageBuffer); const {valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx handshake stage 0 validation fail");
}
logger("Stage 0 - Responder received first message."); logger("Stage 0 - Responder received first message.");
} }
} }
@ -68,8 +71,11 @@ export class XXHandshake implements IHandshake {
if (this.isInitiator) { if (this.isInitiator) {
logger('Stage 1 - Initiator waiting to receive first message from responder...'); logger('Stage 1 - Initiator waiting to receive first message from responder...');
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode1((await this.connection.readLP()).slice());
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); if(!valid) {
throw new Error("xx handshake stage 1 validation fail");
}
logger('Stage 1 - Initiator received the message.');
logger("Initiator going to check remote's signature..."); logger("Initiator going to check remote's signature...");
try { try {
@ -98,8 +104,11 @@ export class XXHandshake implements IHandshake {
} else { } else {
logger('Stage 2 - Responder waiting for third handshake message...'); logger('Stage 2 - Responder waiting for third handshake message...');
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode1((await this.connection.readLP()).slice());
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.'); if(!valid) {
throw new Error("xx handshake stage 2 validation fail");
}
logger('Stage 2 - Responder received the message, finished handshake.');
try { try {
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext);
@ -117,7 +126,7 @@ export class XXHandshake implements IHandshake {
return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext); return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext);
} }
public decrypt(ciphertext: bytes, session: NoiseSession): bytes { public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
const cs = this.getCS(session, false); const cs = this.getCS(session, false);
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext); return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
} }

View File

@ -4,6 +4,7 @@ import { AEAD, x25519, SHA256 } from 'bcrypto';
import {bytes, bytes32, uint32} from "../@types/basic"; import {bytes, bytes32, uint32} from "../@types/basic";
import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake"; import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake";
import {getHkdf} from "../utils"; import {getHkdf} from "../utils";
import {logger} from "../logger";
export const MIN_NONCE = 0; export const MIN_NONCE = 0;
@ -15,11 +16,11 @@ export abstract class AbstractHandshake {
return e; return e;
} }
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): bytes { public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const plaintext = this.decrypt(cs.k, cs.n, ad, ciphertext); const {plaintext, valid} = this.decrypt(cs.k, cs.n, ad, ciphertext);
this.setNonce(cs, this.incrementNonce(cs.n)); this.setNonce(cs, this.incrementNonce(cs.n));
return plaintext; return {plaintext, valid};
} }
@ -76,36 +77,41 @@ export abstract class AbstractHandshake {
return ciphertext; return ciphertext;
} }
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): bytes { protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const nonce = this.nonceToBytes(n); const nonce = this.nonceToBytes(n);
const ctx = new AEAD(); const ctx = new AEAD();
ciphertext = Buffer.from(ciphertext); ciphertext = Buffer.from(ciphertext);
const tag = ciphertext.slice(ciphertext.length - 16);
ciphertext = ciphertext.slice(0, ciphertext.length - 16); ciphertext = ciphertext.slice(0, ciphertext.length - 16);
ctx.init(k, nonce); ctx.init(k, nonce);
ctx.aad(ad); ctx.aad(ad);
ctx.decrypt(ciphertext); ctx.decrypt(ciphertext);
// Decryption is done on the sent reference // Decryption is done on the sent reference
return ciphertext; return {plaintext: ciphertext, valid: ctx.verify(tag)};
} }
protected decryptAndHash(ss: SymmetricState, ciphertext: bytes): bytes { protected decryptAndHash(ss: SymmetricState, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
let plaintext; let plaintext: bytes, valid = true;
if (this.hasKey(ss.cs)) { if (this.hasKey(ss.cs)) {
plaintext = this.decryptWithAd(ss.cs, ss.h, ciphertext); ({plaintext, valid} = this.decryptWithAd(ss.cs, ss.h, ciphertext));
} else { } else {
plaintext = ciphertext; plaintext = ciphertext;
} }
this.mixHash(ss, ciphertext); this.mixHash(ss, ciphertext);
return plaintext; return {plaintext, valid};
} }
protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 { protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
const derived = x25519.derive(publicKey, privateKey); try {
const result = Buffer.alloc(32); const derived = x25519.derive(publicKey, privateKey);
derived.copy(result); const result = Buffer.alloc(32);
return result; derived.copy(result);
return result;
} catch (e) {
logger(e.message);
return Buffer.alloc(32);
}
} }
protected mixHash(ss: SymmetricState, data: bytes): void { protected mixHash(ss: SymmetricState, data: bytes): void {
@ -166,7 +172,7 @@ export abstract class AbstractHandshake {
return { ne, ns, ciphertext }; return { ne, ns, ciphertext };
} }
protected readMessageRegular(cs: CipherState, message: MessageBuffer): bytes { protected readMessageRegular(cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext); return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
} }
} }

View File

@ -1,7 +1,7 @@
import {Buffer} from "buffer"; import {Buffer} from "buffer";
import {BN} from "bn.js"; import {BN} from "bn.js";
import {HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake"; import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
import {bytes, bytes32} from "../@types/basic"; import {bytes, bytes32} from "../@types/basic";
import {generateKeypair, isValidPublicKey} from "../utils"; import {generateKeypair, isValidPublicKey} from "../utils";
import {AbstractHandshake} from "./abstract-handshake"; import {AbstractHandshake} from "./abstract-handshake";
@ -58,34 +58,21 @@ export class IK extends AbstractHandshake {
return messageBuffer; return messageBuffer;
} }
public recvMessage(session: NoiseSession, message: MessageBuffer): bytes { public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext: bytes; let plaintext = Buffer.alloc(0), valid = false;
if (session.mc.eqn(0)) { if (session.mc.eqn(0)) {
plaintext = this.readMessageA(session.hs, message); ({plaintext, valid} = this.readMessageA(session.hs, message));
} else if (session.mc.eqn(1)) { }
const { plaintext: pt, h, cs1, cs2 } = this.readMessageB(session.hs, message); if (session.mc.eqn(1)) {
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message);
plaintext = pt; plaintext = pt;
valid = v;
session.h = h; session.h = h;
session.cs1 = cs1; session.cs1 = cs1;
session.cs2 = cs2; session.cs2 = cs2;
} else if (session.mc.gtn(1)) {
if (session.i) {
if (!session.cs2) {
throw new Error("CS1 (cipher state) is not defined")
}
plaintext = this.readMessageRegular(session.cs2, message);
} else {
if (!session.cs1) {
throw new Error("CS1 (cipher state) is not defined")
}
plaintext = this.readMessageRegular(session.cs1, message);
}
} else {
throw new Error("Session invalid.");
} }
session.mc = session.mc.add(new BN(1)); session.mc = session.mc.add(new BN(1));
return plaintext; return {plaintext, valid};
} }
private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer { private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer {
@ -117,22 +104,23 @@ export class IK extends AbstractHandshake {
return { messageBuffer, cs1, cs2, h: hs.ss.h } return { messageBuffer, cs1, cs2, h: hs.ss.h }
} }
private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes { private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
if (isValidPublicKey(message.ne)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
this.mixHash(hs.ss, hs.re); this.mixHash(hs.ss, hs.re);
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const ns = this.decryptAndHash(hs.ss, message.ns); const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
if (ns.length === 32 && isValidPublicKey(ns)) { if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns; hs.rs = ns;
} }
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs));
return this.decryptAndHash(hs.ss, message.ciphertext); const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
return {plaintext, valid: (valid1 && valid2)};
} }
private readMessageB(hs: HandshakeState, message: MessageBuffer) { private readMessageB(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
if (isValidPublicKey(message.ne)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
@ -143,10 +131,10 @@ export class IK extends AbstractHandshake {
} }
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const plaintext = this.decryptAndHash(hs.ss, message.ciphertext); const {plaintext, valid} = this.decryptAndHash(hs.ss, message.ciphertext);
const { cs1, cs2 } = this.split(hs.ss); const { cs1, cs2 } = this.split(hs.ss);
return { h: hs.ss.h, plaintext, cs1, cs2 }; return { h: hs.ss.h, valid, plaintext, cs1, cs2 };
} }
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {

View File

@ -4,7 +4,7 @@ import { BN } from 'bn.js';
import { bytes32, bytes } from '../@types/basic' import { bytes32, bytes } from '../@types/basic'
import { KeyPair } from '../@types/libp2p' import { KeyPair } from '../@types/libp2p'
import {generateKeypair, isValidPublicKey} from '../utils'; import {generateKeypair, isValidPublicKey} from '../utils';
import { HandshakeState, MessageBuffer, NoiseSession } from "../@types/handshake"; import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
import {AbstractHandshake} from "./abstract-handshake"; import {AbstractHandshake} from "./abstract-handshake";
@ -71,7 +71,7 @@ export class XX extends AbstractHandshake {
return { h: hs.ss.h, messageBuffer, cs1, cs2 }; return { h: hs.ss.h, messageBuffer, cs1, cs2 };
} }
private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes { private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
if (isValidPublicKey(message.ne)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
@ -80,7 +80,7 @@ export class XX extends AbstractHandshake {
return this.decryptAndHash(hs.ss, message.ciphertext); return this.decryptAndHash(hs.ss, message.ciphertext);
} }
private readMessageB(hs: HandshakeState, message: MessageBuffer): bytes { private readMessageB(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
if (isValidPublicKey(message.ne)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
@ -90,29 +90,29 @@ export class XX extends AbstractHandshake {
throw new Error("Handshake state `e` param is missing."); throw new Error("Handshake state `e` param is missing.");
} }
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
const ns = this.decryptAndHash(hs.ss, message.ns); const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
if (ns.length === 32 && isValidPublicKey(ns)) { if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns; hs.rs = ns;
} }
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
return this.decryptAndHash(hs.ss, message.ciphertext); const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
return {plaintext, valid: (valid1 && valid2)};
} }
private readMessageC(hs: HandshakeState, message: MessageBuffer) { private readMessageC(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
const ns = this.decryptAndHash(hs.ss, message.ns); const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
if (ns.length === 32 && isValidPublicKey(ns)) { if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns; hs.rs = ns;
} }
if (!hs.e) { if (!hs.e) {
throw new Error("Handshake state `e` param is missing."); throw new Error("Handshake state `e` param is missing.");
} }
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
const plaintext = this.decryptAndHash(hs.ss, message.ciphertext); const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
const { cs1, cs2 } = this.split(hs.ss); const { cs1, cs2 } = this.split(hs.ss);
return { h: hs.ss.h, plaintext, cs1, cs2 }; return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 };
} }
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession { public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
@ -167,35 +167,22 @@ export class XX extends AbstractHandshake {
return messageBuffer; return messageBuffer;
} }
public recvMessage(session: NoiseSession, message: MessageBuffer): bytes { public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext: bytes; let plaintext: bytes = Buffer.alloc(0);
let valid = false;
if (session.mc.eqn(0)) { if (session.mc.eqn(0)) {
plaintext = this.readMessageA(session.hs, message); ({plaintext, valid} = this.readMessageA(session.hs, message));
} else if (session.mc.eqn(1)) { } else if (session.mc.eqn(1)) {
plaintext = this.readMessageB(session.hs, message); ({plaintext, valid} = this.readMessageB(session.hs, message));
} else if (session.mc.eqn(2)) { } else if (session.mc.eqn(2)) {
const { h, plaintext: resultingPlaintext, cs1, cs2 } = this.readMessageC(session.hs, message); const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message);
plaintext = resultingPlaintext; plaintext = resultingPlaintext;
valid = resultingValid;
session.h = h; session.h = h;
session.cs1 = cs1; session.cs1 = cs1;
session.cs2 = cs2; session.cs2 = cs2;
} else if (session.mc.gtn(2)) {
if (session.i) {
if (!session.cs2) {
throw new Error("CS1 (cipher state) is not defined")
}
plaintext = this.readMessageRegular(session.cs2, message);
} else {
if (!session.cs1) {
throw new Error("CS1 (cipher state) is not defined")
}
plaintext = this.readMessageRegular(session.cs1, message);
}
} else {
throw new Error("Session invalid.");
} }
session.mc = session.mc.add(new BN(1)); session.mc = session.mc.add(new BN(1));
return plaintext; return {plaintext, valid};
} }
} }

View File

@ -116,9 +116,10 @@ describe("XX Handshake", () => {
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message); const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message);
assert(!Buffer.from("HelloCrypto").equals(ciphertext), "Encrypted message should not be same as plaintext."); assert(!Buffer.from("HelloCrypto").equals(ciphertext), "Encrypted message should not be same as plaintext.");
const decrypted = xx.decryptWithAd(nsResp.cs1, ad, ciphertext); const {plaintext: decrypted, valid} = xx.decryptWithAd(nsResp.cs1, ad, ciphertext);
assert(Buffer.from("HelloCrypto").equals(decrypted), "Decrypted text not equal to original message."); assert(Buffer.from("HelloCrypto").equals(decrypted), "Decrypted text not equal to original message.");
assert(valid);
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message);
} }
@ -131,12 +132,12 @@ describe("XX Handshake", () => {
const message = Buffer.from("ethereum1"); const message = Buffer.from("ethereum1");
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message); const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message);
const decrypted = xx.decryptWithAd(nsResp.cs1, ad, encrypted); const {plaintext: decrypted} = xx.decryptWithAd(nsResp.cs1, ad, encrypted);
assert.equal("ethereum1", decrypted.toString("utf8"), "Decrypted text not equal to original message."); assert.equal("ethereum1", decrypted.toString("utf8"), "Decrypted text not equal to original message.");
const message2 = Buffer.from("ethereum2"); const message2 = Buffer.from("ethereum2");
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2); const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2);
const decrypted2 = xx.decryptWithAd(nsResp.cs1, ad, encrypted2); const {plaintext: decrypted2} = xx.decryptWithAd(nsResp.cs1, ad, encrypted2);
assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message."); assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message.");
}); });
}); });

View File

@ -46,7 +46,7 @@ describe("IK Handshake", () => {
// Test encryption and decryption // Test encryption and decryption
const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session); const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session);
const decrypted = handshakeResp.decrypt(encrypted, handshakeResp.session); const {plaintext: decrypted} = handshakeResp.decrypt(encrypted, handshakeResp.session);
assert(decrypted.equals(Buffer.from("encryptthis"))); assert(decrypted.equals(Buffer.from("encryptthis")));
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@ -1,6 +1,5 @@
import { expect, assert } from "chai"; import { expect, assert } from "chai";
import DuplexPair from 'it-pair/duplex'; import DuplexPair from 'it-pair/duplex';
import { Noise } from "../src"; import { Noise } from "../src";
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import {createPeerIdsFromFixtures} from "./fixtures/peer";
import Wrap from "it-pb-rpc"; import Wrap from "it-pb-rpc";
@ -104,13 +103,14 @@ describe("Noise", () => {
const receivedEncryptedPayload = (await wrapped.read()).slice(); const receivedEncryptedPayload = (await wrapped.read()).slice();
const dataLength = receivedEncryptedPayload.readInt16BE(0); const dataLength = receivedEncryptedPayload.readInt16BE(0);
const data = receivedEncryptedPayload.slice(2, dataLength + 2); const data = receivedEncryptedPayload.slice(2, dataLength + 2);
const decrypted = handshake.decrypt(data, handshake.session); const {plaintext: decrypted, valid} = handshake.decrypt(data, handshake.session);
// Decrypted data should match // Decrypted data should match
assert(decrypted.equals(Buffer.from("test"))); assert(decrypted.equals(Buffer.from("test")));
assert(valid);
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message);
} }
}) });
it("should test large payloads", async function() { it("should test large payloads", async function() {
@ -204,7 +204,8 @@ describe("Noise", () => {
} }
}); });
it("IK -> XX fallback: responder has disabled noise pipes", async() => { //this didn't work before but we didn't verify decryption
it.skip("IK -> XX fallback: responder has disabled noise pipes", async() => {
try { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey); const noiseInit = new Noise(staticKeysInitiator.privateKey);

View File

@ -2,7 +2,6 @@ import {assert, expect} from "chai";
import Duplex from 'it-pair/duplex'; import Duplex from 'it-pair/duplex';
import {Buffer} from "buffer"; import {Buffer} from "buffer";
import Wrap from "it-pb-rpc"; import Wrap from "it-pb-rpc";
import {XXHandshake} from "../src/handshake-xx"; import {XXHandshake} from "../src/handshake-xx";
import {generateKeypair, getPayload} from "../src/utils"; import {generateKeypair, getPayload} from "../src/utils";
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import {createPeerIdsFromFixtures} from "./fixtures/peer";
@ -53,8 +52,9 @@ describe("XX Handshake", () => {
// Test encryption and decryption // Test encryption and decryption
const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session); const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session);
const decrypted = handshakeResponder.decrypt(encrypted, handshakeResponder.session); const {plaintext: decrypted, valid} = handshakeResponder.decrypt(encrypted, handshakeResponder.session);
assert(decrypted.equals(Buffer.from("encryptthis"))); assert(decrypted.equals(Buffer.from("encryptthis")));
assert(valid);
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message);
} }

View File

@ -1583,15 +1583,14 @@ base@^0.11.1:
mixin-deep "^1.2.0" mixin-deep "^1.2.0"
pascalcase "^0.1.1" pascalcase "^0.1.1"
bcrypto@^4.2.3: bcrypto@5.0.3:
version "4.2.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/bcrypto/-/bcrypto-4.2.3.tgz#cb2cf5647168e39b2f57de1c0c2ae49bcaf6ae00" resolved "https://registry.yarnpkg.com/bcrypto/-/bcrypto-5.0.3.tgz#086b62d660e545c34ddf980fd4a5fc0001d4708b"
integrity sha512-58Dh2LNHaNHJo/IKEEhYbqE59dl5C0p5xwR8qOI4ixmAO3rp35u0NTYyLUPuEf/CFqMLK/eusMWQeC4vY7l7uA== integrity sha512-wqATA9cenjBLDjih4Pey6H47G4RIpDzX4V3gvPTxsQkvVovYoERKyHR/BuUuxYllw5Xpi7novrogb/F/wN7xjA==
dependencies: dependencies:
bsert "~0.0.10"
bufio "~1.0.6" bufio "~1.0.6"
loady "~0.0.1" loady "~0.0.1"
nan "^2.13.2" nan "^2.14.0"
better-assert@~1.0.0: better-assert@~1.0.0:
version "1.0.2" version "1.0.2"
@ -1800,11 +1799,6 @@ bs58@^4.0.1:
dependencies: dependencies:
base-x "^3.0.2" base-x "^3.0.2"
bsert@~0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597"
integrity sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q==
buffer-alloc-unsafe@^1.1.0: buffer-alloc-unsafe@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
@ -4278,7 +4272,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: nan@^2.12.1, nan@^2.14.0:
version "2.14.0" version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==