noise-protocol
Advanced tools
Comparing version 3.0.1 to 3.0.2
@@ -5,126 +5,127 @@ /* eslint-disable camelcase */ | ||
var assert = require('nanoassert') | ||
var cipher = require('./cipher') | ||
const assert = require('nanoassert') | ||
var STATELEN = cipher.KEYLEN + cipher.NONCELEN | ||
var NONCELEN = cipher.NONCELEN | ||
var MACLEN = cipher.MACLEN | ||
module.exports = ({ cipher }) => { | ||
const STATELEN = cipher.KEYLEN + cipher.NONCELEN | ||
const NONCELEN = cipher.NONCELEN | ||
const MACLEN = cipher.MACLEN | ||
module.exports = { | ||
STATELEN, | ||
NONCELEN, | ||
MACLEN, | ||
initializeKey, | ||
hasKey, | ||
setNonce, | ||
encryptWithAd, | ||
decryptWithAd, | ||
rekey | ||
} | ||
const KEY_BEGIN = 0 | ||
const KEY_END = cipher.KEYLEN | ||
const NONCE_BEGIN = KEY_END | ||
const NONCE_END = NONCE_BEGIN + cipher.NONCELEN | ||
var KEY_BEGIN = 0 | ||
var KEY_END = cipher.KEYLEN | ||
var NONCE_BEGIN = KEY_END | ||
var NONCE_END = NONCE_BEGIN + cipher.NONCELEN | ||
function initializeKey (state, key) { | ||
assert(state.byteLength === STATELEN) | ||
assert(key == null ? true : key.byteLength === cipher.KEYLEN) | ||
function initializeKey (state, key) { | ||
assert(state.byteLength === STATELEN) | ||
assert(key == null ? true : key.byteLength === cipher.KEYLEN) | ||
if (key == null) { | ||
sodium_memzero(state.subarray(KEY_BEGIN, KEY_END)) | ||
return | ||
} | ||
if (key == null) { | ||
sodium_memzero(state.subarray(KEY_BEGIN, KEY_END)) | ||
return | ||
state.set(key) | ||
sodium_memzero(state.subarray(NONCE_BEGIN, NONCE_END)) | ||
} | ||
state.set(key) | ||
sodium_memzero(state.subarray(NONCE_BEGIN, NONCE_END)) | ||
} | ||
function hasKey (state) { | ||
assert(state.byteLength === STATELEN) | ||
const k = state.subarray(KEY_BEGIN, KEY_END) | ||
return sodium_is_zero(k) === false | ||
} | ||
function hasKey (state) { | ||
assert(state.byteLength === STATELEN) | ||
var k = state.subarray(KEY_BEGIN, KEY_END) | ||
return sodium_is_zero(k) === false | ||
} | ||
function setNonce (state, nonce) { | ||
assert(state.byteLength === STATELEN) | ||
assert(nonce.byteLength === NONCELEN) | ||
function setNonce (state, nonce) { | ||
assert(state.byteLength === STATELEN) | ||
assert(nonce.byteLength === NONCELEN) | ||
state.set(nonce, NONCE_BEGIN) | ||
} | ||
state.set(nonce, NONCE_BEGIN) | ||
} | ||
const maxnonce = new Uint8Array(8).fill(0xff) | ||
function encryptWithAd (state, out, ad, plaintext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength != null) | ||
assert(plaintext.byteLength != null) | ||
var maxnonce = new Uint8Array(8).fill(0xff) | ||
function encryptWithAd (state, out, ad, plaintext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength != null) | ||
assert(plaintext.byteLength != null) | ||
const n = state.subarray(NONCE_BEGIN, NONCE_END) | ||
if (sodium_memcmp(n, maxnonce)) throw new Error('Nonce overflow') | ||
var n = state.subarray(NONCE_BEGIN, NONCE_END) | ||
if (sodium_memcmp(n, maxnonce)) throw new Error('Nonce overflow') | ||
if (hasKey(state) === false) { | ||
out.set(plaintext) | ||
encryptWithAd.bytesRead = plaintext.byteLength | ||
encryptWithAd.bytesWritten = encryptWithAd.bytesRead | ||
return | ||
} | ||
if (hasKey(state) === false) { | ||
out.set(plaintext) | ||
encryptWithAd.bytesRead = plaintext.byteLength | ||
encryptWithAd.bytesWritten = encryptWithAd.bytesRead | ||
return | ||
const k = state.subarray(KEY_BEGIN, KEY_END) | ||
cipher.encrypt( | ||
out, | ||
k, | ||
n, | ||
ad, | ||
plaintext | ||
) | ||
encryptWithAd.bytesRead = cipher.encrypt.bytesRead | ||
encryptWithAd.bytesWritten = cipher.encrypt.bytesWritten | ||
sodium_increment(n) | ||
} | ||
encryptWithAd.bytesRead = 0 | ||
encryptWithAd.bytesWritten = 0 | ||
var k = state.subarray(KEY_BEGIN, KEY_END) | ||
function decryptWithAd (state, out, ad, ciphertext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength != null) | ||
assert(ciphertext.byteLength != null) | ||
cipher.encrypt( | ||
out, | ||
k, | ||
n, | ||
ad, | ||
plaintext | ||
) | ||
encryptWithAd.bytesRead = cipher.encrypt.bytesRead | ||
encryptWithAd.bytesWritten = cipher.encrypt.bytesWritten | ||
const n = state.subarray(NONCE_BEGIN, NONCE_END) | ||
if (sodium_memcmp(n, maxnonce)) throw new Error('Nonce overflow') | ||
sodium_increment(n) | ||
} | ||
encryptWithAd.bytesRead = 0 | ||
encryptWithAd.bytesWritten = 0 | ||
if (hasKey(state) === false) { | ||
out.set(ciphertext) | ||
decryptWithAd.bytesRead = ciphertext.byteLength | ||
decryptWithAd.bytesWritten = decryptWithAd.bytesRead | ||
return | ||
} | ||
function decryptWithAd (state, out, ad, ciphertext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength != null) | ||
assert(ciphertext.byteLength != null) | ||
const k = state.subarray(KEY_BEGIN, KEY_END) | ||
var n = state.subarray(NONCE_BEGIN, NONCE_END) | ||
if (sodium_memcmp(n, maxnonce)) throw new Error('Nonce overflow') | ||
cipher.decrypt( | ||
out, | ||
k, | ||
n, | ||
ad, | ||
ciphertext | ||
) | ||
decryptWithAd.bytesRead = cipher.decrypt.bytesRead | ||
decryptWithAd.bytesWritten = cipher.decrypt.bytesWritten | ||
if (hasKey(state) === false) { | ||
out.set(ciphertext) | ||
decryptWithAd.bytesRead = ciphertext.byteLength | ||
decryptWithAd.bytesWritten = decryptWithAd.bytesRead | ||
return | ||
sodium_increment(n) | ||
} | ||
decryptWithAd.bytesRead = 0 | ||
decryptWithAd.bytesWritten = 0 | ||
var k = state.subarray(KEY_BEGIN, KEY_END) | ||
function rekey (state) { | ||
assert(state.byteLength === STATELEN) | ||
cipher.decrypt( | ||
out, | ||
k, | ||
n, | ||
ad, | ||
ciphertext | ||
) | ||
decryptWithAd.bytesRead = cipher.decrypt.bytesRead | ||
decryptWithAd.bytesWritten = cipher.decrypt.bytesWritten | ||
const k = state.subarray(KEY_BEGIN, KEY_END) | ||
cipher.rekey(k, k) | ||
rekey.bytesRead = cipher.rekey.bytesRead | ||
rekey.bytesWritten = cipher.rekey.bytesWritten | ||
} | ||
rekey.bytesRead = 0 | ||
rekey.bytesWritten = 0 | ||
sodium_increment(n) | ||
return { | ||
STATELEN, | ||
NONCELEN, | ||
MACLEN, | ||
initializeKey, | ||
hasKey, | ||
setNonce, | ||
encryptWithAd, | ||
decryptWithAd, | ||
rekey | ||
} | ||
} | ||
decryptWithAd.bytesRead = 0 | ||
decryptWithAd.bytesWritten = 0 | ||
function rekey (state) { | ||
assert(state.byteLength === STATELEN) | ||
var k = state.subarray(KEY_BEGIN, KEY_END) | ||
cipher.rekey(k, k) | ||
rekey.bytesRead = cipher.rekey.bytesRead | ||
rekey.bytesWritten = cipher.rekey.bytesWritten | ||
} | ||
rekey.bytesRead = 0 | ||
rekey.bytesWritten = 0 |
@@ -11,8 +11,13 @@ /* eslint-disable camelcase */ | ||
var assert = require('nanoassert') | ||
const assert = require('nanoassert') | ||
var KEYLEN = 32 | ||
var NONCELEN = 8 | ||
var MACLEN = 16 | ||
const KEYLEN = 32 | ||
const NONCELEN = 8 | ||
const MACLEN = 16 | ||
const ALG = 'ChaChaPoly' | ||
const maxnonce = new Uint8Array(8).fill(0xff) | ||
const zerolen = new Uint8Array(0) | ||
const zeros = new Uint8Array(32) | ||
assert(crypto_aead_chacha20poly1305_ietf_KEYBYTES === KEYLEN) | ||
@@ -23,12 +28,13 @@ // 16 bytes are cut off in the following functions | ||
module.exports = { | ||
module.exports = () => ({ | ||
KEYLEN, | ||
NONCELEN, | ||
MACLEN, | ||
ALG, | ||
encrypt, | ||
decrypt, | ||
rekey | ||
} | ||
}) | ||
var ElongatedNonce = sodium_malloc(crypto_aead_chacha20poly1305_ietf_NPUBBYTES) | ||
const ElongatedNonce = sodium_malloc(crypto_aead_chacha20poly1305_ietf_NPUBBYTES) | ||
function encrypt (out, k, n, ad, plaintext) { | ||
@@ -68,7 +74,3 @@ assert(out.byteLength >= plaintext.byteLength + MACLEN, 'output buffer must be at least plaintext plus MACLEN bytes long') | ||
var maxnonce = new Uint8Array(8).fill(0xff) | ||
var zerolen = new Uint8Array(0) | ||
var zeros = new Uint8Array(32) | ||
var IntermediateKey = sodium_malloc(KEYLEN + MACLEN) | ||
const IntermediateKey = sodium_malloc(KEYLEN + MACLEN) | ||
sodium_memzero(IntermediateKey) | ||
@@ -75,0 +77,0 @@ function rekey (out, k) { |
16
dh.js
@@ -5,10 +5,11 @@ /* eslint-disable camelcase */ | ||
var assert = require('nanoassert') | ||
const assert = require('nanoassert') | ||
var DHLEN = crypto_scalarmult_BYTES | ||
var PKLEN = crypto_scalarmult_BYTES | ||
var SKLEN = crypto_scalarmult_SCALARBYTES | ||
var SEEDLEN = crypto_kx_SEEDBYTES | ||
const DHLEN = crypto_scalarmult_BYTES | ||
const PKLEN = crypto_scalarmult_BYTES | ||
const SKLEN = crypto_scalarmult_SCALARBYTES | ||
const SEEDLEN = crypto_kx_SEEDBYTES | ||
const ALG = '25519' | ||
module.exports = { | ||
module.exports = () => ({ | ||
DHLEN, | ||
@@ -18,6 +19,7 @@ PKLEN, | ||
SEEDLEN, | ||
ALG, | ||
generateKeypair, | ||
generateSeedKeypair, | ||
dh | ||
} | ||
}) | ||
@@ -24,0 +26,0 @@ function generateKeypair (pk, sk) { |
@@ -1,14 +0,14 @@ | ||
var noise = require('.') | ||
const noise = require('./index2') | ||
var sClient = noise.keygen() | ||
var sServer = noise.keygen() | ||
const sClient = noise.keygen() | ||
const sServer = noise.keygen() | ||
var client = noise.initialize('KK', true, Buffer.alloc(0), sClient, null, sServer.publicKey) | ||
var server = noise.initialize('KK', false, Buffer.alloc(0), sServer, null, sClient.publicKey) | ||
const client = noise.initialize('KK', true, Buffer.alloc(0), sClient, null, sServer.publicKey) | ||
const server = noise.initialize('KK', false, Buffer.alloc(0), sServer, null, sClient.publicKey) | ||
var clientTx = Buffer.alloc(128) | ||
var serverTx = Buffer.alloc(128) | ||
const clientTx = Buffer.alloc(128) | ||
const serverTx = Buffer.alloc(128) | ||
var clientRx = Buffer.alloc(128) | ||
var serverRx = Buffer.alloc(128) | ||
const clientRx = Buffer.alloc(128) | ||
const serverRx = Buffer.alloc(128) | ||
@@ -20,4 +20,4 @@ // -> e, es, ss | ||
// <- e, ee, se | ||
var serverSplit = noise.writeMessage(server, Buffer.alloc(0), serverTx) | ||
var clientSplit = noise.readMessage(client, serverTx.subarray(0, noise.writeMessage.bytes), clientRx) | ||
const serverSplit = noise.writeMessage(server, Buffer.alloc(0), serverTx) | ||
const clientSplit = noise.readMessage(client, serverTx.subarray(0, noise.writeMessage.bytes), clientRx) | ||
@@ -24,0 +24,0 @@ noise.destroy(client) |
@@ -5,35 +5,310 @@ /* eslint-disable camelcase */ | ||
const clone = require('clone') | ||
const symmetricState = require('./symmetric-state') | ||
const cipherState = require('./cipher-state') | ||
const dh = require('./dh') | ||
const PKLEN = dh.PKLEN | ||
const SKLEN = dh.SKLEN | ||
function createHandshake ({ dh, hash, cipher, symmetricState, cipherState }) { | ||
const DhResult = sodium_malloc(dh.DHLEN) | ||
module.exports = Object.freeze({ | ||
initialize, | ||
writeMessage, | ||
readMessage, | ||
destroy, | ||
keygen, | ||
seedKeygen, | ||
SKLEN, | ||
PKLEN | ||
}) | ||
function HandshakeState () { | ||
this.symmetricState = sodium_malloc(symmetricState.STATELEN) | ||
function HandshakeState () { | ||
this.symmetricState = sodium_malloc(symmetricState.STATELEN) | ||
this.initiator = null | ||
this.initiator = null | ||
this.spk = null | ||
this.ssk = null | ||
this.spk = null | ||
this.ssk = null | ||
this.epk = null | ||
this.esk = null | ||
this.epk = null | ||
this.esk = null | ||
this.rs = null | ||
this.re = null | ||
this.rs = null | ||
this.re = null | ||
this.messagePatterns = null | ||
} | ||
this.messagePatterns = null | ||
function initialize (handshakePattern, initiator, prologue, s, e, rs, re) { | ||
assert(Object.keys(PATTERNS).includes(handshakePattern), 'Unsupported handshake pattern') | ||
assert(typeof initiator === 'boolean', 'Initiator must be a boolean') | ||
assert(prologue.byteLength != null, 'prolouge must be a Buffer') | ||
assert(e == null ? true : e.publicKey.byteLength === dh.PKLEN, `e.publicKey must be ${dh.PKLEN} bytes`) | ||
assert(e == null ? true : e.secretKey.byteLength === dh.SKLEN, `e.secretKey must be ${dh.SKLEN} bytes`) | ||
assert(rs == null ? true : rs.byteLength === dh.PKLEN, `rs must be ${dh.PKLEN} bytes`) | ||
assert(re == null ? true : re.byteLength === dh.PKLEN, `re must be ${dh.PKLEN} bytes`) | ||
const state = new HandshakeState() | ||
const protocolName = Uint8Array.from(`Noise_${handshakePattern}_${dh.ALG}_${cipher.ALG}_${hash.ALG}`, toCharCode) | ||
symmetricState.initializeSymmetric(state.symmetricState, protocolName) | ||
symmetricState.mixHash(state.symmetricState, prologue) | ||
state.role = initiator === true ? INITIATOR : RESPONDER | ||
if (s != null) { | ||
assert(s.publicKey.byteLength === dh.PKLEN, `s.publicKey must be ${dh.PKLEN} bytes`) | ||
assert(s.secretKey.byteLength === dh.SKLEN, `s.secretKey must be ${dh.SKLEN} bytes`) | ||
state.spk = sodiumBufferCopy(s.publicKey) | ||
state.ssk = sodiumBufferCopy(s.secretKey) | ||
} | ||
if (e != null) { | ||
assert(e.publicKey.byteLength === dh.PKLEN) | ||
assert(e.secretKey.byteLength === dh.SKLEN) | ||
state.epk = sodiumBufferCopy(e.publicKey) | ||
state.esk = sodiumBufferCopy(e.secretKey) | ||
} | ||
if (rs != null) { | ||
assert(rs.byteLength === dh.PKLEN) | ||
state.rs = sodiumBufferCopy(rs) | ||
} | ||
if (re != null) { | ||
assert(re.byteLength === dh.PKLEN) | ||
state.re = sodiumBufferCopy(re) | ||
} | ||
// hashing | ||
const pat = PATTERNS[handshakePattern] | ||
for (const pattern of clone(pat.premessages)) { | ||
const patternRole = pattern.shift() | ||
for (const token of pattern) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.role === patternRole ? state.epk.byteLength != null : state.re.byteLength != null) | ||
symmetricState.mixHash(state.symmetricState, state.role === patternRole ? state.epk : state.re) | ||
break | ||
case TOK_S: | ||
assert(state.role === patternRole ? state.spk.byteLength != null : state.rs.byteLength != null) | ||
symmetricState.mixHash(state.symmetricState, state.role === patternRole ? state.spk : state.rs) | ||
break | ||
default: | ||
throw new Error('Invalid premessage pattern') | ||
} | ||
} | ||
} | ||
state.messagePatterns = clone(pat.messagePatterns) | ||
assert(state.messagePatterns.filter(p => p[0] === INITIATOR).some(p => p.includes(TOK_S)) | ||
? (state.spk !== null && state.ssk !== null) | ||
: true, // Default if none is found | ||
'This handshake pattern requires a static keypair') | ||
return state | ||
} | ||
function writeMessage (state, payload, messageBuffer) { | ||
assert(state instanceof HandshakeState) | ||
assert(payload.byteLength != null) | ||
assert(messageBuffer.byteLength != null) | ||
const mpat = state.messagePatterns.shift() | ||
let moffset = 0 | ||
assert(mpat != null) | ||
assert(state.role === mpat.shift()) | ||
for (const token of mpat) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.epk == null) | ||
assert(state.esk == null) | ||
state.epk = sodium_malloc(dh.PKLEN) | ||
state.esk = sodium_malloc(dh.SKLEN) | ||
dh.generateKeypair(state.epk, state.esk) | ||
messageBuffer.set(state.epk, moffset) | ||
moffset += state.epk.byteLength | ||
symmetricState.mixHash(state.symmetricState, state.epk) | ||
break | ||
case TOK_S: | ||
assert(state.spk.byteLength === dh.PKLEN) | ||
symmetricState.encryptAndHash(state.symmetricState, messageBuffer.subarray(moffset), state.spk) | ||
moffset += symmetricState.encryptAndHash.bytesWritten | ||
break | ||
case TOK_EE: | ||
dh.dh(DhResult, state.esk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_ES: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.esk, state.rs) | ||
else dh.dh(DhResult, state.ssk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SE: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.ssk, state.re) | ||
else dh.dh(DhResult, state.esk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SS: | ||
dh.dh(DhResult, state.ssk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
default: | ||
throw new Error('Invalid message pattern') | ||
} | ||
} | ||
symmetricState.encryptAndHash(state.symmetricState, messageBuffer.subarray(moffset), payload) | ||
moffset += symmetricState.encryptAndHash.bytesWritten | ||
writeMessage.bytes = moffset | ||
if (state.messagePatterns.length === 0) { | ||
const tx = sodium_malloc(cipherState.STATELEN) | ||
const rx = sodium_malloc(cipherState.STATELEN) | ||
symmetricState.split(state.symmetricState, tx, rx, dh.DHLEN, dh.PKLEN) | ||
return { tx, rx } | ||
} | ||
} | ||
writeMessage.bytes = 0 | ||
function readMessage (state, message, payloadBuffer) { | ||
assert(state instanceof HandshakeState) | ||
assert(message.byteLength != null) | ||
assert(payloadBuffer.byteLength != null) | ||
const mpat = state.messagePatterns.shift() | ||
let moffset = 0 | ||
assert(mpat != null) | ||
assert(mpat.shift() !== state.role) | ||
for (const token of mpat) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.re == null) | ||
assert(message.byteLength - moffset >= dh.PKLEN) | ||
// PKLEN instead of DHLEN since they are different in out case | ||
state.re = sodium_malloc(dh.PKLEN) | ||
state.re.set(message.subarray(moffset, moffset + dh.PKLEN)) | ||
moffset += dh.PKLEN | ||
symmetricState.mixHash(state.symmetricState, state.re) | ||
break | ||
case TOK_S: { | ||
assert(state.rs == null) | ||
state.rs = sodium_malloc(dh.PKLEN) | ||
let bytes = 0 | ||
if (symmetricState._hasKey(state.symmetricState)) { | ||
bytes = dh.PKLEN + 16 | ||
} else { | ||
bytes = dh.PKLEN | ||
} | ||
assert(message.byteLength - moffset >= bytes) | ||
symmetricState.decryptAndHash( | ||
state.symmetricState, | ||
state.rs, | ||
message.subarray(moffset, moffset + bytes) // <- called temp in noise spec | ||
) | ||
moffset += symmetricState.decryptAndHash.bytesRead | ||
break | ||
} | ||
case TOK_EE: | ||
dh.dh(DhResult, state.esk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_ES: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.esk, state.rs) | ||
else dh.dh(DhResult, state.ssk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SE: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.ssk, state.re) | ||
else dh.dh(DhResult, state.esk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SS: | ||
dh.dh(DhResult, state.ssk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
default: | ||
throw new Error('Invalid message pattern') | ||
} | ||
} | ||
symmetricState.decryptAndHash(state.symmetricState, payloadBuffer, message.subarray(moffset)) | ||
// How many bytes were written to payload (minus the TAG/MAC) | ||
readMessage.bytes = symmetricState.decryptAndHash.bytesWritten | ||
if (state.messagePatterns.length === 0) { | ||
const tx = sodium_malloc(cipherState.STATELEN) | ||
const rx = sodium_malloc(cipherState.STATELEN) | ||
symmetricState.split(state.symmetricState, rx, tx, dh.DHLEN, dh.PKLEN) | ||
return { tx, rx } | ||
} | ||
} | ||
readMessage.bytes = 0 | ||
function keygen (obj, sk) { | ||
if (!obj) { | ||
obj = { publicKey: sodium_malloc(dh.PKLEN), secretKey: sodium_malloc(dh.SKLEN) } | ||
return keygen(obj) | ||
} | ||
if (obj.publicKey) { | ||
dh.generateKeypair(obj.publicKey, obj.secretKey) | ||
return obj | ||
} | ||
if (obj.byteLength != null) dh.generateKeypair(null, obj) | ||
} | ||
function seedKeygen (seed) { | ||
const obj = { publicKey: sodium_malloc(dh.PKLEN), secretKey: sodium_malloc(dh.SKLEN) } | ||
dh.generateSeedKeypair(obj.publicKey, obj.secretKey, seed) | ||
return obj | ||
} | ||
return Object.freeze({ | ||
initialize, | ||
writeMessage, | ||
readMessage, | ||
destroy, | ||
keygen, | ||
seedKeygen, | ||
createHandshake, | ||
SKLEN: dh.SKLEN, | ||
PKLEN: dh.PKLEN | ||
}) | ||
} | ||
@@ -53,3 +328,3 @@ | ||
// responder, <- | ||
var PATTERNS = Object.freeze({ | ||
const PATTERNS = Object.freeze({ | ||
N: { | ||
@@ -183,3 +458,3 @@ premessages: [ | ||
function sodiumBufferCopy (src) { | ||
var buf = sodium_malloc(src.byteLength) | ||
const buf = sodium_malloc(src.byteLength) | ||
buf.set(src) | ||
@@ -189,258 +464,2 @@ return buf | ||
function initialize (handshakePattern, initiator, prologue, s, e, rs, re) { | ||
assert(Object.keys(PATTERNS).includes(handshakePattern), 'Unsupported handshake pattern') | ||
assert(typeof initiator === 'boolean', 'Initiator must be a boolean') | ||
assert(prologue.byteLength != null, 'prolouge must be a Buffer') | ||
assert(e == null ? true : e.publicKey.byteLength === dh.PKLEN, `e.publicKey must be ${dh.PKLEN} bytes`) | ||
assert(e == null ? true : e.secretKey.byteLength === dh.SKLEN, `e.secretKey must be ${dh.SKLEN} bytes`) | ||
assert(rs == null ? true : rs.byteLength === dh.PKLEN, `rs must be ${dh.PKLEN} bytes`) | ||
assert(re == null ? true : re.byteLength === dh.PKLEN, `re must be ${dh.PKLEN} bytes`) | ||
var state = new HandshakeState() | ||
var protocolName = Uint8Array.from(`Noise_${handshakePattern}_25519_ChaChaPoly_BLAKE2b`, toCharCode) | ||
symmetricState.initializeSymmetric(state.symmetricState, protocolName) | ||
symmetricState.mixHash(state.symmetricState, prologue) | ||
state.role = initiator === true ? INITIATOR : RESPONDER | ||
if (s != null) { | ||
assert(s.publicKey.byteLength === dh.PKLEN, `s.publicKey must be ${dh.PKLEN} bytes`) | ||
assert(s.secretKey.byteLength === dh.SKLEN, `s.secretKey must be ${dh.SKLEN} bytes`) | ||
state.spk = sodiumBufferCopy(s.publicKey) | ||
state.ssk = sodiumBufferCopy(s.secretKey) | ||
} | ||
if (e != null) { | ||
assert(e.publicKey.byteLength === dh.PKLEN) | ||
assert(e.secretKey.byteLength === dh.SKLEN) | ||
state.epk = sodiumBufferCopy(e.publicKey) | ||
state.esk = sodiumBufferCopy(e.secretKey) | ||
} | ||
if (rs != null) { | ||
assert(rs.byteLength === dh.PKLEN) | ||
state.rs = sodiumBufferCopy(rs) | ||
} | ||
if (re != null) { | ||
assert(re.byteLength === dh.PKLEN) | ||
state.re = sodiumBufferCopy(re) | ||
} | ||
// hashing | ||
var pat = PATTERNS[handshakePattern] | ||
for (var pattern of clone(pat.premessages)) { | ||
var patternRole = pattern.shift() | ||
for (var token of pattern) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.role === patternRole ? state.epk.byteLength != null : state.re.byteLength != null) | ||
symmetricState.mixHash(state.symmetricState, state.role === patternRole ? state.epk : state.re) | ||
break | ||
case TOK_S: | ||
assert(state.role === patternRole ? state.spk.byteLength != null : state.rs.byteLength != null) | ||
symmetricState.mixHash(state.symmetricState, state.role === patternRole ? state.spk : state.rs) | ||
break | ||
default: | ||
throw new Error('Invalid premessage pattern') | ||
} | ||
} | ||
} | ||
state.messagePatterns = clone(pat.messagePatterns) | ||
assert(state.messagePatterns.filter(p => p[0] === INITIATOR).some(p => p.includes(TOK_S)) | ||
? (state.spk !== null && state.ssk !== null) | ||
: true, // Default if none is found | ||
'This handshake pattern requires a static keypair') | ||
return state | ||
} | ||
var DhResult = sodium_malloc(dh.DHLEN) | ||
function writeMessage (state, payload, messageBuffer) { | ||
assert(state instanceof HandshakeState) | ||
assert(payload.byteLength != null) | ||
assert(messageBuffer.byteLength != null) | ||
var mpat = state.messagePatterns.shift() | ||
var moffset = 0 | ||
assert(mpat != null) | ||
assert(state.role === mpat.shift()) | ||
for (var token of mpat) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.epk == null) | ||
assert(state.esk == null) | ||
state.epk = sodium_malloc(dh.PKLEN) | ||
state.esk = sodium_malloc(dh.SKLEN) | ||
dh.generateKeypair(state.epk, state.esk) | ||
messageBuffer.set(state.epk, moffset) | ||
moffset += state.epk.byteLength | ||
symmetricState.mixHash(state.symmetricState, state.epk) | ||
break | ||
case TOK_S: | ||
assert(state.spk.byteLength === dh.PKLEN) | ||
symmetricState.encryptAndHash(state.symmetricState, messageBuffer.subarray(moffset), state.spk) | ||
moffset += symmetricState.encryptAndHash.bytesWritten | ||
break | ||
case TOK_EE: | ||
dh.dh(DhResult, state.esk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_ES: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.esk, state.rs) | ||
else dh.dh(DhResult, state.ssk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SE: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.ssk, state.re) | ||
else dh.dh(DhResult, state.esk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SS: | ||
dh.dh(DhResult, state.ssk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
default: | ||
throw new Error('Invalid message pattern') | ||
} | ||
} | ||
symmetricState.encryptAndHash(state.symmetricState, messageBuffer.subarray(moffset), payload) | ||
moffset += symmetricState.encryptAndHash.bytesWritten | ||
writeMessage.bytes = moffset | ||
if (state.messagePatterns.length === 0) { | ||
var tx = sodium_malloc(cipherState.STATELEN) | ||
var rx = sodium_malloc(cipherState.STATELEN) | ||
symmetricState.split(state.symmetricState, tx, rx) | ||
return { tx, rx } | ||
} | ||
} | ||
writeMessage.bytes = 0 | ||
function readMessage (state, message, payloadBuffer) { | ||
assert(state instanceof HandshakeState) | ||
assert(message.byteLength != null) | ||
assert(payloadBuffer.byteLength != null) | ||
var mpat = state.messagePatterns.shift() | ||
var moffset = 0 | ||
assert(mpat != null) | ||
assert(mpat.shift() !== state.role) | ||
for (var token of mpat) { | ||
switch (token) { | ||
case TOK_E: | ||
assert(state.re == null) | ||
assert(message.byteLength - moffset >= dh.PKLEN) | ||
// PKLEN instead of DHLEN since they are different in out case | ||
state.re = sodium_malloc(dh.PKLEN) | ||
state.re.set(message.subarray(moffset, moffset + dh.PKLEN)) | ||
moffset += dh.PKLEN | ||
symmetricState.mixHash(state.symmetricState, state.re) | ||
break | ||
case TOK_S: | ||
assert(state.rs == null) | ||
state.rs = sodium_malloc(dh.PKLEN) | ||
var bytes = 0 | ||
if (symmetricState._hasKey(state.symmetricState)) { | ||
bytes = dh.PKLEN + 16 | ||
} else { | ||
bytes = dh.PKLEN | ||
} | ||
assert(message.byteLength - moffset >= bytes) | ||
symmetricState.decryptAndHash( | ||
state.symmetricState, | ||
state.rs, | ||
message.subarray(moffset, moffset + bytes) // <- called temp in noise spec | ||
) | ||
moffset += symmetricState.decryptAndHash.bytesRead | ||
break | ||
case TOK_EE: | ||
dh.dh(DhResult, state.esk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_ES: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.esk, state.rs) | ||
else dh.dh(DhResult, state.ssk, state.re) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SE: | ||
if (state.role === INITIATOR) dh.dh(DhResult, state.ssk, state.re) | ||
else dh.dh(DhResult, state.esk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
case TOK_SS: | ||
dh.dh(DhResult, state.ssk, state.rs) | ||
symmetricState.mixKey(state.symmetricState, DhResult) | ||
sodium_memzero(DhResult) | ||
break | ||
default: | ||
throw new Error('Invalid message pattern') | ||
} | ||
} | ||
symmetricState.decryptAndHash(state.symmetricState, payloadBuffer, message.subarray(moffset)) | ||
// How many bytes were written to payload (minus the TAG/MAC) | ||
readMessage.bytes = symmetricState.decryptAndHash.bytesWritten | ||
if (state.messagePatterns.length === 0) { | ||
var tx = sodium_malloc(cipherState.STATELEN) | ||
var rx = sodium_malloc(cipherState.STATELEN) | ||
symmetricState.split(state.symmetricState, rx, tx) | ||
return { tx, rx } | ||
} | ||
} | ||
readMessage.bytes = 0 | ||
function destroy (state) { | ||
@@ -487,24 +506,6 @@ if (state.symmetricState != null) { | ||
function keygen (obj, sk) { | ||
if (!obj) { | ||
obj = { publicKey: sodium_malloc(PKLEN), secretKey: sodium_malloc(SKLEN) } | ||
return keygen(obj) | ||
} | ||
if (obj.publicKey) { | ||
dh.generateKeypair(obj.publicKey, obj.secretKey) | ||
return obj | ||
} | ||
if (obj.byteLength != null) dh.generateKeypair(null, obj) | ||
} | ||
function seedKeygen (seed) { | ||
var obj = { publicKey: sodium_malloc(PKLEN), secretKey: sodium_malloc(SKLEN) } | ||
dh.generateSeedKeypair(obj.publicKey, obj.secretKey, seed) | ||
return obj | ||
} | ||
function toCharCode (s) { | ||
return s.charCodeAt(0) | ||
} | ||
module.exports = createHandshake |
63
hash.js
@@ -7,6 +7,6 @@ /* eslint-disable camelcase */ | ||
const hmacBlake2b = require('hmac-blake2b') | ||
const dh = require('./dh') | ||
const HASHLEN = 64 | ||
const BLOCKLEN = 128 | ||
const ALG = 'BLAKE2b' | ||
@@ -16,7 +16,34 @@ assert(hmacBlake2b.KEYBYTES === BLOCKLEN, 'mismatching hmac BLOCKLEN') | ||
module.exports = { | ||
HASHLEN, | ||
BLOCKLEN, | ||
hash, | ||
hkdf | ||
const TempKey = sodium_malloc(HASHLEN) | ||
const Byte0x01 = new Uint8Array([0x01]) | ||
const Byte0x02 = new Uint8Array([0x02]) | ||
const Byte0x03 = new Uint8Array([0x03]) | ||
module.exports = ({ dh }) => { | ||
return { | ||
HASHLEN, | ||
BLOCKLEN, | ||
ALG, | ||
hash, | ||
hkdf | ||
} | ||
function hkdf (out1, out2, out3, chainingKey, inputKeyMaterial) { | ||
assert(out1.byteLength === HASHLEN) | ||
assert(out2.byteLength === HASHLEN) | ||
assert(out3 == null ? true : out3.byteLength === HASHLEN) | ||
assert(chainingKey.byteLength === HASHLEN) | ||
assert([0, 32, dh.DHLEN, dh.PKLEN].includes(inputKeyMaterial.byteLength)) | ||
sodium_memzero(TempKey) | ||
hmac(TempKey, chainingKey, [inputKeyMaterial]) | ||
hmac(out1, TempKey, [Byte0x01]) | ||
hmac(out2, TempKey, [out1, Byte0x02]) | ||
if (out3 != null) { | ||
hmac(out3, TempKey, [out2, Byte0x03]) | ||
} | ||
sodium_memzero(TempKey) | ||
} | ||
} | ||
@@ -34,25 +61,1 @@ | ||
} | ||
const TempKey = sodium_malloc(HASHLEN) | ||
const Byte0x01 = new Uint8Array([0x01]) | ||
const Byte0x02 = new Uint8Array([0x02]) | ||
const Byte0x03 = new Uint8Array([0x03]) | ||
function hkdf (out1, out2, out3, chainingKey, inputKeyMaterial) { | ||
assert(out1.byteLength === HASHLEN) | ||
assert(out2.byteLength === HASHLEN) | ||
assert(out3 == null ? true : out3.byteLength === HASHLEN) | ||
assert(chainingKey.byteLength === HASHLEN) | ||
assert([0, 32, dh.DHLEN, dh.PKLEN].includes(inputKeyMaterial.byteLength)) | ||
sodium_memzero(TempKey) | ||
hmac(TempKey, chainingKey, [inputKeyMaterial]) | ||
hmac(out1, TempKey, [Byte0x01]) | ||
hmac(out2, TempKey, [out1, Byte0x02]) | ||
if (out3 != null) { | ||
hmac(out3, TempKey, [out2, Byte0x03]) | ||
} | ||
sodium_memzero(TempKey) | ||
} |
14
index.js
@@ -1,1 +0,13 @@ | ||
module.exports = require('./handshake-state') | ||
const dh = require('./dh')() | ||
const hash = require('./hash')({ dh }) | ||
const cipher = require('./cipher')() | ||
const cipherState = require('./cipher-state')({ cipher }) | ||
const symmetricState = require('./symmetric-state')({ hash, cipherState }) | ||
module.exports = require('./handshake-state')({ | ||
dh, | ||
hash, | ||
cipher, | ||
cipherState, | ||
symmetricState | ||
}) |
{ | ||
"name": "noise-protocol", | ||
"version": "3.0.1", | ||
"version": "3.0.2", | ||
"description": "Javascript implementation of the Noise Protocol Framework based on libsodium", | ||
@@ -8,12 +8,12 @@ "main": "index.js", | ||
"clone": "^2.1.2", | ||
"hmac-blake2b": "^2.0.0", | ||
"hmac-blake2b": "^2.0.2", | ||
"nanoassert": "^2.0.0", | ||
"sodium-universal": "^3.0.2" | ||
"sodium-universal": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"browserify": "^16.5.1", | ||
"c8": "^7.1.2", | ||
"standard": "^14.3.4", | ||
"tape": "^5.0.0", | ||
"tape-run": "^7.0.0" | ||
"browserify": "^17.0.0", | ||
"c8": "^7.12.0", | ||
"standard": "^17.0.0", | ||
"tape": "^5.6.1", | ||
"tape-run": "^10.0.0" | ||
}, | ||
@@ -20,0 +20,0 @@ "scripts": { |
# `noise-protocol` | ||
[](https://travis-ci.org/emilbayes/noise-protocol) | ||
[](https://github.com/emilbayes/noise-protocol/actions/workflows/npm.yml) | ||
@@ -188,2 +188,12 @@ > Javascript implementation of the Noise Protocol Framework based on libsodium | ||
### Custom algorithms | ||
You can customise the handshake state by calling `noise.createHandshake(algs)` | ||
with `algs = { dh, hash, cipher, cipherState, symmetricState }`. Please refer | ||
to the respective implementations in the source to see how to swap pieces. | ||
Please note that this is dangerous, unless you've read the noise specification | ||
very carefully and know what you are doing. `createHandshake` will then return | ||
a new `initialize` function from which you can create start handshakes with | ||
your custom algorithms. | ||
## Install | ||
@@ -190,0 +200,0 @@ |
/* eslint-disable camelcase */ | ||
const { sodium_malloc, sodium_memzero } = require('sodium-universal/memory') | ||
var assert = require('nanoassert') | ||
var cipherState = require('./cipher-state') | ||
var hash = require('./hash') | ||
const assert = require('nanoassert') | ||
var STATELEN = hash.HASHLEN + hash.HASHLEN + cipherState.STATELEN | ||
var HASHLEN = hash.HASHLEN | ||
module.exports = ({ hash, cipherState }) => { | ||
const STATELEN = hash.HASHLEN + hash.HASHLEN + cipherState.STATELEN | ||
const HASHLEN = hash.HASHLEN | ||
module.exports = { | ||
STATELEN, | ||
initializeSymmetric, | ||
mixKey, | ||
mixHash, | ||
mixKeyAndHash, | ||
getHandshakeHash, | ||
encryptAndHash, | ||
decryptAndHash, | ||
split, | ||
_hasKey | ||
} | ||
const CHAINING_KEY_BEGIN = 0 | ||
const CHAINING_KEY_END = hash.HASHLEN | ||
const HASH_BEGIN = CHAINING_KEY_END | ||
const HASH_END = HASH_BEGIN + hash.HASHLEN | ||
const CIPHER_BEGIN = HASH_END | ||
const CIPHER_END = CIPHER_BEGIN + cipherState.STATELEN | ||
var CHAINING_KEY_BEGIN = 0 | ||
var CHAINING_KEY_END = hash.HASHLEN | ||
var HASH_BEGIN = CHAINING_KEY_END | ||
var HASH_END = HASH_BEGIN + hash.HASHLEN | ||
var CIPHER_BEGIN = HASH_END | ||
var CIPHER_END = CIPHER_BEGIN + cipherState.STATELEN | ||
function initializeSymmetric (state, protocolName) { | ||
assert(state.byteLength === STATELEN) | ||
assert(protocolName.byteLength != null) | ||
function initializeSymmetric (state, protocolName) { | ||
assert(state.byteLength === STATELEN) | ||
assert(protocolName.byteLength != null) | ||
sodium_memzero(state) | ||
if (protocolName.byteLength <= HASHLEN) state.set(protocolName, HASH_BEGIN) | ||
else hash.hash(state.subarray(HASH_BEGIN, HASH_END), [protocolName]) | ||
sodium_memzero(state) | ||
if (protocolName.byteLength <= HASHLEN) state.set(protocolName, HASH_BEGIN) | ||
else hash.hash(state.subarray(HASH_BEGIN, HASH_END), [protocolName]) | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END).set(state.subarray(HASH_BEGIN, HASH_END)) | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END).set(state.subarray(HASH_BEGIN, HASH_END)) | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), null) | ||
} | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), null) | ||
} | ||
const TempKey = sodium_malloc(HASHLEN) | ||
function mixKey (state, inputKeyMaterial, dhlen, pklen) { | ||
assert(state.byteLength === STATELEN) | ||
assert(inputKeyMaterial.byteLength != null) | ||
var TempKey = sodium_malloc(HASHLEN) | ||
function mixKey (state, inputKeyMaterial) { | ||
assert(state.byteLength === STATELEN) | ||
assert(inputKeyMaterial.byteLength != null) | ||
hash.hkdf( | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
TempKey, | ||
null, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
inputKeyMaterial, | ||
dhlen, | ||
pklen | ||
) | ||
hash.hkdf( | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
TempKey, | ||
null, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
inputKeyMaterial | ||
) | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32)) | ||
sodium_memzero(TempKey) | ||
} | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32)) | ||
sodium_memzero(TempKey) | ||
} | ||
function mixHash (state, data) { | ||
assert(state.byteLength === STATELEN) | ||
function mixHash (state, data) { | ||
assert(state.byteLength === STATELEN) | ||
const h = state.subarray(HASH_BEGIN, HASH_END) | ||
var h = state.subarray(HASH_BEGIN, HASH_END) | ||
hash.hash(h, [h, data]) | ||
} | ||
hash.hash(h, [h, data]) | ||
} | ||
const TempHash = sodium_malloc(HASHLEN) | ||
function mixKeyAndHash (state, inputKeyMaterial, dhlen, pklen) { | ||
assert(state.byteLength === STATELEN) | ||
assert(inputKeyMaterial.byteLength != null) | ||
var TempHash = sodium_malloc(HASHLEN) | ||
function mixKeyAndHash (state, inputKeyMaterial) { | ||
assert(state.byteLength === STATELEN) | ||
assert(inputKeyMaterial.byteLength != null) | ||
hash.hkdf( | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
TempHash, | ||
TempKey, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
inputKeyMaterial, | ||
dhlen, | ||
pklen | ||
) | ||
hash.hkdf( | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
TempHash, | ||
TempKey, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
inputKeyMaterial | ||
) | ||
mixHash(state, TempHash) | ||
sodium_memzero(TempHash) | ||
mixHash(state, TempHash) | ||
sodium_memzero(TempHash) | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32)) | ||
sodium_memzero(TempKey) | ||
} | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32)) | ||
sodium_memzero(TempKey) | ||
} | ||
function getHandshakeHash (state, out) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength === HASHLEN) | ||
function getHandshakeHash (state, out) { | ||
assert(state.byteLength === STATELEN) | ||
assert(out.byteLength === HASHLEN) | ||
out.set(state.subarray(HASH_BEGIN, HASH_END)) | ||
} | ||
out.set(state.subarray(HASH_BEGIN, HASH_END)) | ||
} | ||
// ciphertext is the output here | ||
function encryptAndHash (state, ciphertext, plaintext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(ciphertext.byteLength != null) | ||
assert(plaintext.byteLength != null) | ||
// ciphertext is the output here | ||
function encryptAndHash (state, ciphertext, plaintext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(ciphertext.byteLength != null) | ||
assert(plaintext.byteLength != null) | ||
const cstate = state.subarray(CIPHER_BEGIN, CIPHER_END) | ||
const h = state.subarray(HASH_BEGIN, HASH_END) | ||
var cstate = state.subarray(CIPHER_BEGIN, CIPHER_END) | ||
var h = state.subarray(HASH_BEGIN, HASH_END) | ||
cipherState.encryptWithAd(cstate, ciphertext, h, plaintext) | ||
encryptAndHash.bytesRead = cipherState.encryptWithAd.bytesRead | ||
encryptAndHash.bytesWritten = cipherState.encryptWithAd.bytesWritten | ||
mixHash(state, ciphertext.subarray(0, encryptAndHash.bytesWritten)) | ||
} | ||
encryptAndHash.bytesRead = 0 | ||
encryptAndHash.bytesWritten = 0 | ||
cipherState.encryptWithAd(cstate, ciphertext, h, plaintext) | ||
encryptAndHash.bytesRead = cipherState.encryptWithAd.bytesRead | ||
encryptAndHash.bytesWritten = cipherState.encryptWithAd.bytesWritten | ||
mixHash(state, ciphertext.subarray(0, encryptAndHash.bytesWritten)) | ||
} | ||
encryptAndHash.bytesRead = 0 | ||
encryptAndHash.bytesWritten = 0 | ||
// plaintext is the output here | ||
function decryptAndHash (state, plaintext, ciphertext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(plaintext.byteLength != null) | ||
assert(ciphertext.byteLength != null) | ||
// plaintext is the output here | ||
function decryptAndHash (state, plaintext, ciphertext) { | ||
assert(state.byteLength === STATELEN) | ||
assert(plaintext.byteLength != null) | ||
assert(ciphertext.byteLength != null) | ||
const cstate = state.subarray(CIPHER_BEGIN, CIPHER_END) | ||
const h = state.subarray(HASH_BEGIN, HASH_END) | ||
var cstate = state.subarray(CIPHER_BEGIN, CIPHER_END) | ||
var h = state.subarray(HASH_BEGIN, HASH_END) | ||
cipherState.decryptWithAd(cstate, plaintext, h, ciphertext) | ||
decryptAndHash.bytesRead = cipherState.decryptWithAd.bytesRead | ||
decryptAndHash.bytesWritten = cipherState.decryptWithAd.bytesWritten | ||
mixHash(state, ciphertext.subarray(0, decryptAndHash.bytesRead)) | ||
} | ||
decryptAndHash.bytesRead = 0 | ||
decryptAndHash.bytesWritten = 0 | ||
cipherState.decryptWithAd(cstate, plaintext, h, ciphertext) | ||
decryptAndHash.bytesRead = cipherState.decryptWithAd.bytesRead | ||
decryptAndHash.bytesWritten = cipherState.decryptWithAd.bytesWritten | ||
mixHash(state, ciphertext.subarray(0, decryptAndHash.bytesRead)) | ||
} | ||
decryptAndHash.bytesRead = 0 | ||
decryptAndHash.bytesWritten = 0 | ||
const TempKey1 = sodium_malloc(HASHLEN) | ||
const TempKey2 = sodium_malloc(HASHLEN) | ||
const zerolen = new Uint8Array(0) | ||
function split (state, cipherstate1, cipherstate2, dhlen, pklen) { | ||
assert(state.byteLength === STATELEN) | ||
assert(cipherstate1.byteLength === cipherState.STATELEN) | ||
assert(cipherstate2.byteLength === cipherState.STATELEN) | ||
var TempKey1 = sodium_malloc(HASHLEN) | ||
var TempKey2 = sodium_malloc(HASHLEN) | ||
var zerolen = new Uint8Array(0) | ||
function split (state, cipherstate1, cipherstate2) { | ||
assert(state.byteLength === STATELEN) | ||
assert(cipherstate1.byteLength === cipherState.STATELEN) | ||
assert(cipherstate2.byteLength === cipherState.STATELEN) | ||
hash.hkdf( | ||
TempKey1, | ||
TempKey2, | ||
null, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
zerolen, | ||
dhlen, | ||
pklen | ||
) | ||
hash.hkdf( | ||
TempKey1, | ||
TempKey2, | ||
null, | ||
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END), | ||
zerolen | ||
) | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(cipherstate1, TempKey1.subarray(0, 32)) | ||
cipherState.initializeKey(cipherstate2, TempKey2.subarray(0, 32)) | ||
sodium_memzero(TempKey1) | ||
sodium_memzero(TempKey2) | ||
} | ||
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec | ||
cipherState.initializeKey(cipherstate1, TempKey1.subarray(0, 32)) | ||
cipherState.initializeKey(cipherstate2, TempKey2.subarray(0, 32)) | ||
sodium_memzero(TempKey1) | ||
sodium_memzero(TempKey2) | ||
} | ||
function _hasKey (state) { | ||
return cipherState.hasKey(state.subarray(CIPHER_BEGIN, CIPHER_END)) | ||
} | ||
function _hasKey (state) { | ||
return cipherState.hasKey(state.subarray(CIPHER_BEGIN, CIPHER_END)) | ||
return { | ||
STATELEN, | ||
initializeSymmetric, | ||
mixKey, | ||
mixHash, | ||
mixKeyAndHash, | ||
getHandshakeHash, | ||
encryptAndHash, | ||
decryptAndHash, | ||
split, | ||
_hasKey | ||
} | ||
} |
@@ -8,4 +8,4 @@ /* eslint-disable camelcase */ | ||
const { randombytes_buf } = require('sodium-universal/randombytes') | ||
var cipher = require('../cipher') | ||
var test = require('tape') | ||
const cipher = require('../cipher')() | ||
const test = require('tape') | ||
@@ -25,15 +25,15 @@ test('constants', function (assert) { | ||
test('identity', function (assert) { | ||
var key = Buffer.alloc(cipher.KEYLEN) | ||
var nonce = Buffer.alloc(cipher.NONCELEN) | ||
const key = Buffer.alloc(cipher.KEYLEN) | ||
const nonce = Buffer.alloc(cipher.NONCELEN) | ||
randombytes_buf(key) | ||
randombytes_buf(nonce) | ||
var key2 = Buffer.alloc(cipher.KEYLEN) | ||
var nonce2 = Buffer.alloc(cipher.NONCELEN) | ||
const key2 = Buffer.alloc(cipher.KEYLEN) | ||
const nonce2 = Buffer.alloc(cipher.NONCELEN) | ||
randombytes_buf(key2) | ||
randombytes_buf(nonce2) | ||
var plaintext = Buffer.from('Hello world') | ||
var ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
var decrypted = Buffer.alloc(plaintext.byteLength) | ||
const plaintext = Buffer.from('Hello world') | ||
const ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
const decrypted = Buffer.alloc(plaintext.byteLength) | ||
@@ -46,3 +46,3 @@ cipher.encrypt(ciphertext, key, nonce, null, plaintext) | ||
for (var i = 0; i < ciphertext.length; i++) { | ||
for (let i = 0; i < ciphertext.length; i++) { | ||
ciphertext[i] ^= i + 1 | ||
@@ -60,17 +60,17 @@ assert.throws(_ => cipher.decrypt(decrypted, key, nonce, null, ciphertext)) | ||
test('identity with ad', function (assert) { | ||
var key = Buffer.alloc(cipher.KEYLEN) | ||
var nonce = Buffer.alloc(cipher.NONCELEN) | ||
const key = Buffer.alloc(cipher.KEYLEN) | ||
const nonce = Buffer.alloc(cipher.NONCELEN) | ||
randombytes_buf(key) | ||
randombytes_buf(nonce) | ||
var ad = Buffer.from('version 0') | ||
const ad = Buffer.from('version 0') | ||
var key2 = Buffer.alloc(cipher.KEYLEN) | ||
var nonce2 = Buffer.alloc(cipher.NONCELEN) | ||
const key2 = Buffer.alloc(cipher.KEYLEN) | ||
const nonce2 = Buffer.alloc(cipher.NONCELEN) | ||
randombytes_buf(key2) | ||
randombytes_buf(nonce2) | ||
var plaintext = Buffer.from('Hello world') | ||
var ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
var decrypted = Buffer.alloc(plaintext.byteLength) | ||
const plaintext = Buffer.from('Hello world') | ||
const ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
const decrypted = Buffer.alloc(plaintext.byteLength) | ||
@@ -83,3 +83,3 @@ cipher.encrypt(ciphertext, key, nonce, ad, plaintext) | ||
for (var i = 0; i < ciphertext.length; i++) { | ||
for (let i = 0; i < ciphertext.length; i++) { | ||
ciphertext[i] ^= 255 | ||
@@ -97,14 +97,14 @@ assert.throws(_ => cipher.decrypt(decrypted, key, nonce, ad, ciphertext)) | ||
test('rekey', function (assert) { | ||
var key = Buffer.alloc(cipher.KEYLEN) | ||
var nonce = Buffer.alloc(cipher.NONCELEN) | ||
const key = Buffer.alloc(cipher.KEYLEN) | ||
const nonce = Buffer.alloc(cipher.NONCELEN) | ||
randombytes_buf(key) | ||
randombytes_buf(nonce) | ||
var keyCopy = Buffer.from(key) | ||
const keyCopy = Buffer.from(key) | ||
cipher.rekey(key, key) | ||
assert.notOk(key.equals(keyCopy)) | ||
var plaintext = Buffer.from('Hello world') | ||
var ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
var decrypted = Buffer.alloc(plaintext.byteLength) | ||
const plaintext = Buffer.from('Hello world') | ||
const ciphertext = Buffer.alloc(plaintext.byteLength + cipher.MACLEN) | ||
const decrypted = Buffer.alloc(plaintext.byteLength) | ||
@@ -111,0 +111,0 @@ cipher.encrypt(ciphertext, key, nonce, null, plaintext) |
@@ -1,3 +0,3 @@ | ||
var dh = require('../dh') | ||
var test = require('tape') | ||
const dh = require('../dh')() | ||
const test = require('tape') | ||
@@ -10,5 +10,5 @@ test('constants', function (assert) { | ||
test('generateKeypair', function (assert) { | ||
var kp1 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
var kp2 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
var kp3 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const kp1 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const kp2 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const kp3 = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
@@ -33,4 +33,4 @@ dh.generateKeypair(kp2.pk, kp2.sk) | ||
test('initiator / responder', function (assert) { | ||
var server = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
var client = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const server = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const client = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
@@ -40,4 +40,4 @@ dh.generateKeypair(server.pk, server.sk) | ||
var dhc = Buffer.alloc(dh.DHLEN) | ||
var dhs = Buffer.alloc(dh.DHLEN) | ||
const dhc = Buffer.alloc(dh.DHLEN) | ||
const dhs = Buffer.alloc(dh.DHLEN) | ||
@@ -76,7 +76,7 @@ dh.dh(dhc, client.sk, server.pk) | ||
test('bad keys', function (assert) { | ||
var keypair = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
const keypair = { sk: Buffer.alloc(dh.SKLEN), pk: Buffer.alloc(dh.PKLEN) } | ||
dh.generateKeypair(keypair.pk, keypair.sk) | ||
var dho = Buffer.alloc(dh.DHLEN) | ||
for (var i = 0; i < badKeys.length; i++) { | ||
const dho = Buffer.alloc(dh.DHLEN) | ||
for (let i = 0; i < badKeys.length; i++) { | ||
assert.throws(() => dh.dh(dho, keypair.sk, badKeys[i])) | ||
@@ -83,0 +83,0 @@ } |
@@ -1,3 +0,3 @@ | ||
var noise = require('..') | ||
var test = require('tape') | ||
const noise = require('..') | ||
const test = require('tape') | ||
@@ -4,0 +4,0 @@ test('Static key pattern without static keypair', function (assert) { |
@@ -1,3 +0,4 @@ | ||
var hash = require('../hash') | ||
var test = require('tape') | ||
const dh = require('../dh')() | ||
const hash = require('../hash')({ dh }) | ||
const test = require('tape') | ||
@@ -4,0 +5,0 @@ test('constants', function (assert) { |
@@ -1,14 +0,14 @@ | ||
var noise = require('../..') | ||
var test = require('tape') | ||
const noise = require('../..') | ||
const test = require('tape') | ||
test('N pattern', function (assert) { | ||
var serverKeys = noise.keygen() | ||
const serverKeys = noise.keygen() | ||
var client = noise.initialize('N', true, Buffer.alloc(0), null, null, serverKeys.publicKey) | ||
var server = noise.initialize('N', false, Buffer.alloc(0), serverKeys) | ||
const client = noise.initialize('N', true, Buffer.alloc(0), null, null, serverKeys.publicKey) | ||
const server = noise.initialize('N', false, Buffer.alloc(0), serverKeys) | ||
var clientTx = Buffer.alloc(512) | ||
var serverRx = Buffer.alloc(512) | ||
const clientTx = Buffer.alloc(512) | ||
const serverRx = Buffer.alloc(512) | ||
var splitClient = noise.writeMessage(client, Buffer.from('Hello world'), clientTx) | ||
const splitClient = noise.writeMessage(client, Buffer.from('Hello world'), clientTx) | ||
assert.ok(noise.writeMessage.bytes > 11) | ||
@@ -18,3 +18,3 @@ assert.false(Buffer.from(clientTx).includes(Buffer.from('Hello world'))) | ||
assert.false(Buffer.from(clientTx).includes(Buffer.from(client.esk))) | ||
var splitServer = noise.readMessage(server, clientTx.subarray(0, noise.writeMessage.bytes), serverRx) | ||
const splitServer = noise.readMessage(server, clientTx.subarray(0, noise.writeMessage.bytes), serverRx) | ||
assert.equal(noise.readMessage.bytes, 11) | ||
@@ -21,0 +21,0 @@ |
@@ -1,13 +0,13 @@ | ||
var noise = require('../..') | ||
var test = require('tape') | ||
const noise = require('../..') | ||
const test = require('tape') | ||
test('NN pattern', function (assert) { | ||
var client = noise.initialize('NN', true, Buffer.alloc(0)) | ||
var server = noise.initialize('NN', false, Buffer.alloc(0)) | ||
const client = noise.initialize('NN', true, Buffer.alloc(0)) | ||
const server = noise.initialize('NN', false, Buffer.alloc(0)) | ||
var clientTx = Buffer.alloc(512) | ||
var serverRx = Buffer.alloc(512) | ||
const clientTx = Buffer.alloc(512) | ||
const serverRx = Buffer.alloc(512) | ||
var serverTx = Buffer.alloc(512) | ||
var clientRx = Buffer.alloc(512) | ||
const serverTx = Buffer.alloc(512) | ||
const clientRx = Buffer.alloc(512) | ||
@@ -19,5 +19,5 @@ assert.false(noise.writeMessage(client, Buffer.alloc(0), clientTx)) | ||
var splitServer = noise.writeMessage(server, Buffer.alloc(0), serverTx) | ||
const splitServer = noise.writeMessage(server, Buffer.alloc(0), serverTx) | ||
assert.ok(noise.writeMessage.bytes > 0) | ||
var splitClient = noise.readMessage(client, serverTx.subarray(0, noise.writeMessage.bytes), clientRx) | ||
const splitClient = noise.readMessage(client, serverTx.subarray(0, noise.writeMessage.bytes), clientRx) | ||
assert.equal(noise.readMessage.bytes, 0) | ||
@@ -24,0 +24,0 @@ |
@@ -1,13 +0,13 @@ | ||
var noise = require('../..') | ||
var test = require('tape') | ||
const noise = require('../..') | ||
const test = require('tape') | ||
test('XX pattern', function (assert) { | ||
var client = noise.initialize('XX', true, Buffer.alloc(0), noise.keygen()) | ||
var server = noise.initialize('XX', false, Buffer.alloc(0), noise.keygen()) | ||
const client = noise.initialize('XX', true, Buffer.alloc(0), noise.keygen()) | ||
const server = noise.initialize('XX', false, Buffer.alloc(0), noise.keygen()) | ||
var clientTx = Buffer.alloc(512) | ||
var serverRx = Buffer.alloc(512) | ||
const clientTx = Buffer.alloc(512) | ||
const serverRx = Buffer.alloc(512) | ||
var serverTx = Buffer.alloc(512) | ||
var clientRx = Buffer.alloc(512) | ||
const serverTx = Buffer.alloc(512) | ||
const clientRx = Buffer.alloc(512) | ||
@@ -27,5 +27,5 @@ // -> | ||
// -> | ||
var splitClient = noise.writeMessage(client, Buffer.alloc(0), clientTx) | ||
const splitClient = noise.writeMessage(client, Buffer.alloc(0), clientTx) | ||
assert.ok(noise.writeMessage.bytes > 0) | ||
var splitServer = noise.readMessage(server, clientTx.subarray(0, noise.writeMessage.bytes), serverRx) | ||
const splitServer = noise.readMessage(server, clientTx.subarray(0, noise.writeMessage.bytes), serverRx) | ||
assert.equal(noise.readMessage.bytes, 0) | ||
@@ -32,0 +32,0 @@ |
52162
20
1137
217
- Removedb4a@1.6.7(transitive)
- Removedblake2b@2.1.4(transitive)
- Removedblake2b-wasm@2.4.0(transitive)
- Removedchacha20-universal@1.0.4(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedis-core-module@2.16.1(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedresolve@1.22.10(transitive)
- Removedsha256-universal@1.2.1(transitive)
- Removedsha256-wasm@2.2.2(transitive)
- Removedsha512-universal@1.2.1(transitive)
- Removedsha512-wasm@2.3.4(transitive)
- Removedsiphash24@1.3.1(transitive)
- Removedsodium-javascript@0.8.0(transitive)
- Removedsodium-native@3.4.1(transitive)
- Removedsodium-universal@3.1.0(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedxsalsa20@1.2.0(transitive)
Updatedhmac-blake2b@^2.0.2
Updatedsodium-universal@^4.0.0