mirror of
https://github.com/fluencelabs/js-libp2p-noise
synced 2025-04-25 18:42:32 +00:00
handle aead auth tag
This commit is contained in:
parent
3cbb1d525c
commit
c6e7f361c7
@ -1,2 +1,3 @@
|
|||||||
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535;
|
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535;
|
||||||
|
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16;
|
||||||
|
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
import {IHandshake} from "./@types/handshake-interface";
|
import {IHandshake} from "./@types/handshake-interface";
|
||||||
|
import {NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG} from "./constants";
|
||||||
|
|
||||||
interface IReturnEncryptionWrapper {
|
interface IReturnEncryptionWrapper {
|
||||||
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>;
|
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxPlaintextLength = 65519;
|
|
||||||
|
|
||||||
// Returns generator that encrypts payload from the user
|
// Returns generator that encrypts payload from the user
|
||||||
export function encryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
|
export function encryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
|
||||||
return async function * (source) {
|
return async function * (source) {
|
||||||
for await (const chunk of source) {
|
for await (const chunk of source) {
|
||||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
||||||
|
|
||||||
for (let i = 0; i < chunkBuffer.length; i += maxPlaintextLength) {
|
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) {
|
||||||
let end = i + maxPlaintextLength;
|
let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG;
|
||||||
if (end > chunkBuffer.length) {
|
if (end > chunkBuffer.length) {
|
||||||
end = chunkBuffer.length;
|
end = chunkBuffer.length;
|
||||||
}
|
}
|
||||||
@ -33,8 +32,8 @@ export function decryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
|
|||||||
for await (const chunk of source) {
|
for await (const chunk of source) {
|
||||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
||||||
|
|
||||||
for (let i = 0; i < chunkBuffer.length; i += maxPlaintextLength) {
|
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES) {
|
||||||
let end = i + maxPlaintextLength;
|
let end = i + NOISE_MSG_MAX_LENGTH_BYTES;
|
||||||
if (end > chunkBuffer.length) {
|
if (end > chunkBuffer.length) {
|
||||||
end = chunkBuffer.length;
|
end = chunkBuffer.length;
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ export function decode0(input: bytes): MessageBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function decode1(input: bytes): MessageBuffer {
|
export function decode1(input: bytes): MessageBuffer {
|
||||||
if (input.length < 96) {
|
if (input.length < 80) {
|
||||||
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 96 bytes.");
|
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 96 bytes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ne: input.slice(0, 32),
|
ne: input.slice(0, 32),
|
||||||
ns: input.slice(32, 64),
|
ns: input.slice(32, 80),
|
||||||
ciphertext: input.slice(64, input.length),
|
ciphertext: input.slice(80, input.length),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ export class IKHandshake implements IHandshake {
|
|||||||
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);
|
||||||
await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer);
|
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||||
logger("IK Stage 0 - Responder successfully verified payload!");
|
logger("IK Stage 0 - Responder successfully verified payload!");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger("Responder breaking up with IK handshake in stage 0.");
|
logger("Responder breaking up with IK handshake in stage 0.");
|
||||||
@ -80,7 +80,7 @@ export class IKHandshake implements IHandshake {
|
|||||||
try {
|
try {
|
||||||
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, decodedPayload, this.remotePeer);
|
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer);
|
||||||
logger("IK Stage 1 - Initiator successfully verified payload!");
|
logger("IK Stage 1 - Initiator successfully verified payload!");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger("Initiator breaking up with IK handshake in stage 1.");
|
logger("Initiator breaking up with IK handshake in stage 1.");
|
||||||
|
@ -59,7 +59,7 @@ export class XXFallbackHandshake extends XXHandshake {
|
|||||||
try {
|
try {
|
||||||
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, decodedPayload, this.remotePeer);
|
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Error occurred while verifying signed payload from responder: ${e.message}`);
|
throw new Error(`Error occurred while verifying signed payload from responder: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ export class XXHandshake implements IHandshake {
|
|||||||
try {
|
try {
|
||||||
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, decodedPayload, this.remotePeer);
|
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
|
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
@ -55,13 +55,13 @@ export abstract class AbstractHandshake {
|
|||||||
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
|
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
|
||||||
const nonce = this.nonceToBytes(n);
|
const nonce = this.nonceToBytes(n);
|
||||||
const ctx = new AEAD();
|
const ctx = new AEAD();
|
||||||
|
plaintext = Buffer.from(plaintext);
|
||||||
ctx.init(k, nonce);
|
ctx.init(k, nonce);
|
||||||
ctx.aad(ad);
|
ctx.aad(ad);
|
||||||
ctx.encrypt(plaintext);
|
ctx.encrypt(plaintext);
|
||||||
|
|
||||||
// Encryption is done on the sent reference
|
// Encryption is done on the sent reference
|
||||||
return plaintext;
|
return Buffer.concat([plaintext, ctx.final()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes {
|
protected encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes {
|
||||||
@ -79,7 +79,8 @@ export abstract class AbstractHandshake {
|
|||||||
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): bytes {
|
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): bytes {
|
||||||
const nonce = this.nonceToBytes(n);
|
const nonce = this.nonceToBytes(n);
|
||||||
const ctx = new AEAD();
|
const ctx = new AEAD();
|
||||||
|
ciphertext = Buffer.from(ciphertext);
|
||||||
|
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);
|
||||||
|
@ -125,7 +125,7 @@ export class IK extends AbstractHandshake {
|
|||||||
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 ns = this.decryptAndHash(hs.ss, message.ns);
|
||||||
if (ns.length === 32 && isValidPublicKey(message.ns)) {
|
if (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));
|
||||||
|
@ -91,7 +91,7 @@ export class XX extends AbstractHandshake {
|
|||||||
}
|
}
|
||||||
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 ns = this.decryptAndHash(hs.ss, message.ns);
|
||||||
if (ns.length === 32 && isValidPublicKey(message.ns)) {
|
if (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));
|
||||||
@ -100,7 +100,7 @@ export class XX extends AbstractHandshake {
|
|||||||
|
|
||||||
private readMessageC(hs: HandshakeState, message: MessageBuffer) {
|
private readMessageC(hs: HandshakeState, message: MessageBuffer) {
|
||||||
const ns = this.decryptAndHash(hs.ss, message.ns);
|
const ns = this.decryptAndHash(hs.ss, message.ns);
|
||||||
if (ns.length === 32 && isValidPublicKey(message.ns)) {
|
if (ns.length === 32 && isValidPublicKey(ns)) {
|
||||||
hs.rs = ns;
|
hs.rs = ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ export class Noise implements INoiseConnection {
|
|||||||
encryptStream(handshake), // data is encrypted
|
encryptStream(handshake), // data is encrypted
|
||||||
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
|
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
|
||||||
network, // send to the remote peer
|
network, // send to the remote peer
|
||||||
decode({ lengthDecoder: uint16BEDecode, maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES }), // read message length prefix
|
decode({ lengthDecoder: uint16BEDecode}), // read message length prefix
|
||||||
ensureBuffer, // ensure any type of data is converted to buffer
|
ensureBuffer, // ensure any type of data is converted to buffer
|
||||||
decryptStream(handshake), // decrypt the incoming data
|
decryptStream(handshake), // decrypt the incoming data
|
||||||
secure // pipe to the wrapper
|
secure // pipe to the wrapper
|
||||||
|
@ -114,5 +114,5 @@ export function getHkdf(ck: bytes32, ikm: bytes): Hkdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isValidPublicKey(pk: bytes): boolean {
|
export function isValidPublicKey(pk: bytes): boolean {
|
||||||
return x25519.publicKeyVerify(pk);
|
return x25519.publicKeyVerify(pk.slice(0, 32));
|
||||||
}
|
}
|
||||||
|
@ -114,9 +114,9 @@ describe("XX Handshake", () => {
|
|||||||
const ad = Buffer.from("authenticated");
|
const ad = Buffer.from("authenticated");
|
||||||
const message = Buffer.from("HelloCrypto");
|
const message = Buffer.from("HelloCrypto");
|
||||||
|
|
||||||
xx.encryptWithAd(nsInit.cs1, ad, message);
|
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message);
|
||||||
assert(!Buffer.from("HelloCrypto").equals(message), "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, message);
|
const decrypted = 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.");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -125,22 +125,18 @@ describe("XX Handshake", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Test multiple messages encryption and decryption", async () => {
|
it("Test multiple messages encryption and decryption", async () => {
|
||||||
try {
|
|
||||||
const xx = new XX();
|
const xx = new XX();
|
||||||
const { nsInit, nsResp } = await doHandshake(xx);
|
const { nsInit, nsResp } = await doHandshake(xx);
|
||||||
const ad = Buffer.from("authenticated");
|
const ad = Buffer.from("authenticated");
|
||||||
const message = Buffer.from("ethereum1");
|
const message = Buffer.from("ethereum1");
|
||||||
|
|
||||||
xx.encryptWithAd(nsInit.cs1, ad, message);
|
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message);
|
||||||
const decrypted = xx.decryptWithAd(nsResp.cs1, ad, message);
|
const decrypted = xx.decryptWithAd(nsResp.cs1, ad, encrypted);
|
||||||
assert(Buffer.from("ethereum1").equals(decrypted), "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");
|
||||||
xx.encryptWithAd(nsInit.cs1, ad, message2);
|
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2);
|
||||||
const decrypted2 = xx.decryptWithAd(nsResp.cs1, ad, message2);
|
const decrypted2 = xx.decryptWithAd(nsResp.cs1, ad, encrypted2);
|
||||||
assert(Buffer.from("ethereum2").equals(decrypted2), "Decrypted text not equal to original message.");
|
assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message.");
|
||||||
} catch (e) {
|
|
||||||
assert(false, e.message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -113,7 +113,8 @@ describe("Noise", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it("should test large payloads", async() => {
|
it("should test large payloads", async function() {
|
||||||
|
this.timeout(10000);
|
||||||
try {
|
try {
|
||||||
const noiseInit = new Noise(undefined, undefined, false);
|
const noiseInit = new Noise(undefined, undefined, false);
|
||||||
const noiseResp = new Noise(undefined, undefined, false);
|
const noiseResp = new Noise(undefined, undefined, false);
|
||||||
@ -128,11 +129,11 @@ describe("Noise", () => {
|
|||||||
|
|
||||||
const largePlaintext = random.randomBytes(100000);
|
const largePlaintext = random.randomBytes(100000);
|
||||||
wrappedOutbound.writeLP(largePlaintext);
|
wrappedOutbound.writeLP(largePlaintext);
|
||||||
const response = await wrappedInbound.readLP();
|
const response = await wrappedInbound.read(100000);
|
||||||
|
|
||||||
expect(response.length).equals(largePlaintext.length);
|
expect(response.length).equals(largePlaintext.length);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.log(e);
|
||||||
assert(false, e.message);
|
assert(false, e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user