- Notifications
You must be signed in to change notification settings - Fork0
Noise Protocol implementation wrapping noise-c.wasm but with a *slightly* easier API. | Moved tohttps://tcrowe.commons.host/contact
License
tcrowe/noise-wasm-wrapper
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Handshake, encrypt, and decrypt with noise.
It wrapsnoise-c.wasm into aslightly easier API. Being that it depends on wasm it should work in the browser and nodejs.
The examples use a TCP server but you could use other protocols likeUTP. It just assumes the stream or socket have the.write
function so it can handshake. After the handshake you getencrypt
anddecrypt
methods for the rest.
The hope is that it's a useful and robust module which can be depended on as a fundamental building block for bigger things. Maybe someone who knows how can roll this into a duplex stream module to make it easier.
In short it's like this:
const{ createKeyPair, createNoise}require("noise-wasm-wrapper");// const socket = (net socket or stream)createKeyPair(function(err,keyPair){createNoise({ keyPair},function({ handshake}){handshake({ socket},function(err,{ encrypt, decrypt}){socket.on("data",chunk=>console.log(decrypt(chunk)));socket.write(encrypt(Buffer.from("encrypt me pls")));});});});
Create key pairs for the local and remote nodes../examples/create-key-pairs.js
node ./examples/create-key-pairs.js
constfs=require("fs");constpath=require("path");const{ createKeyPair}=require("noise-wasm-wrapper");constinitiatorKeyPairPath=path.join(__dirname,"initiator-keypair.json");constresponderKeyPairPath=path.join(__dirname,"responder-keypair.json");/** * Create a callback that saves the key pair *@method saveKeyPair *@param {string} absolutePath *@returns {function} */functionsaveKeyPair(absolutePath){/** * Write file *@method saveKeyPairInner *@param {object} [err] *@param {object} [keyPair] */returnfunctionsaveKeyPairCallback(err,keyPair){if(err!==null&&err!==undefined){console.error("error creating key pair",err);return;}letop=JSON.stringify(keyPair);fs.writeFile(absolutePath,op,function(err){if(err!==null&&err!==undefined){returnconsole.error("error saving key pair",absolutePath,err);}console.log("saved",absolutePath);});};}createKeyPair(saveKeyPair(initiatorKeyPairPath));createKeyPair(saveKeyPair(responderKeyPairPath));
Create responder socket. The resonder waits until the initiator initiates the handshake../examples/responder.js
node ./examples/responder.js
const{ createServer}=require("net");constisNil=require("lodash/isNil");constnoise=require("../src");constkeyPair=require("./responder-keypair.json");constnoiseOpts={ keyPair};constport=14912;consthost="127.0.0.1";letresponder;letintervals=[];constintervalDelay=500;// time between messages/** * Try to gracefully shutdown so the port doesn't stay active *@method shutdown *@param {number} code */functionshutdown(code=0){// stop the serverif(isNil(responder)===false){try{responder.close();}catch(err){console.error("error closing responder",err);}}// stop timersintervals.forEach(interval=>clearInterval(interval));process.exit(code);}//// 1. Wait for a connection//responder=createServer(function(socket){console.log("responder connection");//// 2. Make some Noise!//noise.createNoise(noiseOpts,function(err,{ handshake}){if(isNil(err)===false){console.error("error creating noise objec",err);returnshutdown(1);}socket.on("error",function(err){console.error("responder socket error",err);});socket.on("close",function(){console.log("responder socket close");});//// 3. Wait for handshake//handshake({ socket},function(err,res){if(isNil(err)===false){console.error("error handshaking socket",err);returnshutdown(1);}//// 4. Send and receive encrypted data//const{ encrypt, decrypt}=res;console.log("responder handshake success");socket.on("data",function(chunk){//// decrypt incoming data//letop=decrypt(chunk);op=Buffer.from(op);op=op.toString();console.log("responder decrypted:",op);});//// Encrypt outgoing data//letcounter=0;constinterval=setInterval(function(){letop=`responder message${counter}`;op=Buffer.from(op);op=encrypt(op);socket.write(op);counter+=1;},intervalDelay);intervals.push(interval);socket.on("close",function(){intervals=intervals.filter(function(item){if(item===interval){clearInterval(interval);returnfalse;}returntrue;});});});});});responder.on("error",function(err){console.error("responder error",err);shutdown(1);});responder.listen(port,host,function(err){if(isNil(err)===false){console.error("error listening",err);returnshutdown(1);}console.log(`responder listening tcp://${host}:${port}`);});
Create the initiator and start the handshake with the responder../examples/initiator.js
node ./examples/inititator.js
const{ connect}=require("net");constisNil=require("lodash/isNil");constnoise=require("../src");constkeyPair=require("./initiator-keypair.json");constremotePublicKey=require("./responder-keypair.json").publicKey;constnoiseOpts={mode:"initiator", keyPair, remotePublicKey};constport=14912;consthost="127.0.0.1";letinterval;constintervalDelay=500;// time between messagesletinitiator;/** * Try to gracefully shutdown *@method shutdown *@param {number} code */functionshutdown(code=0){// stop the serverif(isNil(initiator)===false){try{initiator.end();}catch(err){console.error("error closing initiator",err);}}// stop timersif(isNil(interval)===false){clearInterval(interval);}process.exit(code);}//// 1. Make some Noise!//noise.createNoise(noiseOpts,function(err,{ handshake}){if(isNil(err)===false){console.error("error creating noise objec",err);returnshutdown(1);}//// 2. Connect to the responder//initiator=connect({ port, host},function(){console.log("initiator connected");//// 3. Initiate the handshake//handshake({socket:initiator},function(err,res){if(isNil(err)===false){console.error("error handshaking socket",err);returnshutdown(1);}//// 4. Send and receive encrypted data//const{ encrypt, decrypt}=res;console.log("initiator handshake success");initiator.on("data",function(chunk){//// decrypt incoming data//letop=decrypt(chunk);op=Buffer.from(op);op=op.toString();console.log("initiator decrypted:",op);});//// Encrypt outgoing data//letcounter=0;interval=setInterval(function(){letop=`initiator message${counter}`;op=Buffer.from(op);op=encrypt(op);initiator.write(op);counter+=1;},intervalDelay);});});initiator.on("error",function(err){console.error("initiator error",err);shutdown(1);});initiator.on("close",function(){console.log("initiator close");shutdown();});});
The default curve id isNOISE_DH_CURVE25519
.
Create the public and private key pair@method createKeyPair@param {string} [curveId]@param {function} done
You can review hekey pair curve id list. At the time of writing this only two work,NOISE_DH_CURVE25519
andNOISE_DH_CURVE448
.
const{ createKeyPair}=require("noise-wasm-wrapper");createKeyPair(function(err,keyPair){console.log("err",err);console.log("keyPair",keyPair);});
Create the noise object with the configurations for your network.
The callback provides (err, { noise, handshakeState, handshake })
- {object} error
- {object} response
- {object} response.noise
- {object} response.handshakeState noise wasm handshake object
- {function} response.handshake
@method createNoise
- @param {object} opts
- @param {string} [opts.pattern="XK"]
- @param {string} [opts.curve="25519"]
- @param {string} [opts.cipher="ChaChaPoly"]
- @param {string} [opts.hash="BLAKE2b"]
- @param {string|buffer} [opts.prologue] shared secret message
- @param {string|buffer} [opts.psk] pre-shared symmetric key
- @param {string} [opts.mode="responder"] "initiator" or "responder"
- @param {object} opts.keyPair
- @param {array|buffer} opts.keyPair.publicKey
- @param {array|buffer} opts.keyPair.privateKey
- @param {array|buffer} [opts.remotePublicKey] required if initiator
- @param {function} done
const{ createNoise}=require("noise-wasm-wrapper");// get the key pairs// const responderKeyPair = { publicKey: [...], privateKey: [...]}// const initiatorKeyPair = { publicKey: [...], privateKey: [...]}// create responder noise instancecreateNoise({keyPair:responderKeyPair},function({ handshake}){// go handshake the stream});// create initiator noise instancecreateNoise({keyPair:initiatorKeyPair,remotePublicKey:responderKeyPair.publicKey},function({ handshake}){// go handshake the stream});
Once youcreateNoise
you'll get a handshake function. It will listen for or initiate the handshake with the peer.
The callback provides:
- {object} error
- {object} response.handshakeState
- {function} response.encrypt
- {function} response.decrypt
- {function} response.getRemotePublicKey
- {function} response.getHandshakeHash
@method handshake
- @param {object} options.socket
- @param {number} [options.maxHandshakeOperations=12]
- @param {function} done
cipherStateSend
andcipherStateReceive
are from aCipherState created by callingHandshakeState.Split()
.
# clean up coverage files and eslint cachenpm run clean# run for continuous development tasksnpm run dev# run before publishing to ensure tests runnpm run prd# other tasks which can be run individuallynpm run dev-eslintnpm run dev-testnpm runtestnpm run coveragenpm run prd-eslint
- Possibly bugs out if trying to send data too quickly after the handshake
- PSK(pre-shared symmetric key) feature withSymmetricState
- It should be determined when we should call
free()
if needed as well. getRemotePublicKey
implemented, not testedgetHandshakeHash
implemented, not tested
Copyright (C) Tony Crowegithub@tonycrowe.com (https://tcrowe.github.io) 2018
Thank you for using and contributing to make noise-wasm-wrapper better.
npm run prd
before submitting a patch.
⚖️ noise-wasm-wrapper is Free Software protected by the GPL 3.0 license. See./COPYING for more information. (free as in freedom)
About
Noise Protocol implementation wrapping noise-c.wasm but with a *slightly* easier API. | Moved tohttps://tcrowe.commons.host/contact