@starbase/encryption
Version:
Starbase Encryption
1 lines • 8.56 kB
JavaScript
;function Encryption(cryptic){if(!cryptic)throw"An instance of Starbase Cryptic is require.";async function rootKDF(rk,dh){let ratchet=await cryptic.kdf(cryptic.decode(dh),cryptic.decode(rk),cryptic.fromText("ROOT"),512);return[cryptic.encode(cryptic.decode(ratchet).slice(0,32)),cryptic.encode(cryptic.decode(ratchet).slice(32))]}async function chainKDF(ck){let mk=await cryptic.hmacSign(cryptic.decode(ck),"");return[await cryptic.hmacSign(cryptic.decode(ck),""),mk]}async function skipMessageKeys(state,until,maxSkip){if(state.Nr+maxSkip<until)return Promise.reject({message:"Too many skipped messages!"});if(state.CKr)for(;state.Nr<until;){let mk=null;[state.CKr,mk]=await chainKDF(state.CKr),state.MKSKIPPED[state.DHr]||(state.MKSKIPPED[state.DHr]={}),state.MKSKIPPED[state.DHr][state.Nr]=mk,state.Nr+=1}}async function ratchetDecrypt(state,msgPayload={},AD,maxSkip=10){let{header:header,ciphertext:ciphertext}=msgPayload,AEAD=await cryptic.kdf(cryptic.combine(cryptic.decode(AD),cryptic.fromText(JSON.stringify(header))),new Uint8Array(32),cryptic.fromText("AEAD"),256),found=await async function(state,header,ciphertext,AEAD){if(state.MKSKIPPED[header.dh]&&state.MKSKIPPED[header.dh][header.n]){let mk=state.MKSKIPPED[header.dh][header.n],KEY=await cryptic.kdf(cryptic.combine(cryptic.decode(mk),cryptic.fromText(JSON.stringify(header))),new Uint8Array(32),cryptic.fromText("ENCRYPT"),256),plaintext=await cryptic.decrypt(ciphertext,cryptic.decode(KEY),cryptic.decode(AEAD)).catch(err=>null);return plaintext?(delete state.MKSKIPPED[header.dh][header.n],{header:header,plaintext:plaintext}):null}}(state,header,ciphertext,AEAD||null);if(found)return found;header.dh!==state.DHr&&(await skipMessageKeys(state,header.pn,maxSkip),await async function(state,header){state.PN=state.Ns,state.Ns=0,state.Nr=0,state.DHr=header.dh;let dh1=await cryptic.ecdh(state.DHs.key,state.DHr);[state.RK,state.CKr]=await rootKDF(state.RK,dh1),state.DHs=await cryptic.createECDH();let dh2=await cryptic.ecdh(state.DHs.key,state.DHr);return[state.RK,state.CKs]=await rootKDF(state.RK,dh2),!0}(state,header)),await skipMessageKeys(state,header.n,maxSkip);let mk=null;[state.CKr,mk]=await chainKDF(state.CKr),state.Nr+=1;let KEY=await cryptic.kdf(cryptic.combine(cryptic.decode(mk),cryptic.fromText(JSON.stringify(header))),new Uint8Array(32),cryptic.fromText("ENCRYPT"),256),plaintext=await cryptic.decrypt(ciphertext,cryptic.decode(KEY),cryptic.decode(AEAD)).catch(err=>{throw{error:"failed to decrypt"}});return delete state.init,{header:header,plaintext:JSON.parse(plaintext)}}function cloneState(src){let target={};if("string"==typeof src)return target=src.toString();src instanceof Array&&(target=[]);for(let prop in src)src[prop]&&"object"==typeof src[prop]?target[prop]=cloneState(src[prop]):target[prop]=src[prop];return target}function Session(sessionData){let sessionState=cloneState(sessionData),session={to:()=>cloneState(sessionData).user};return session.send=(async message=>{let state=cloneState(sessionState.state),payload=await async function(state,msg,AD,init){let mk=null;[state.CKs,mk]=await chainKDF(state.CKs);let header={dh:state.DHs.pub,pn:state.PN,n:state.Ns};state.Ns+=1;let AEAD=await cryptic.kdf(cryptic.combine(cryptic.decode(AD),cryptic.fromText(JSON.stringify(header))),new Uint8Array(32),cryptic.fromText("AEAD"),256),KEY=await cryptic.kdf(cryptic.combine(cryptic.decode(mk),cryptic.fromText(JSON.stringify(header))),new Uint8Array(32),cryptic.fromText("ENCRYPT"),256),encrypted={header:header,ciphertext:await cryptic.encrypt(JSON.stringify(msg),cryptic.decode(KEY),cryptic.decode(AEAD))};return init&&(encrypted.init=cloneState(init)),encrypted}(state,message,sessionState.AD||null,sessionState.init||null);return payload.to=sessionData.user.toString(),sessionState.state=cloneState(state),payload}),session.read=(async payload=>{let state=cloneState(sessionState.state),decrypted=await ratchetDecrypt(state,payload,sessionState.AD||null);sessionState.init&&delete sessionState.init;let msg={header:payload.header,plaintext:decrypted.plaintext,from:session.to()};return sessionState.state=cloneState(state),msg}),session.save=(()=>{let backup={state:cloneState(sessionState.state)};return sessionState.init&&(backup.init=cloneState(sessionState.init)),sessionState.user&&(backup.user=cloneState(sessionState.user)),sessionState.AD&&(backup.AD=cloneState(sessionState.AD)),backup}),session}function User(userData){let userState=cloneState(userData),user={},useUser=()=>userState;return user.createOPK=(async()=>{let secret=await cryptic.createECDH();return{card:{user:userState.pub,opk:secret.pub},secret:secret}}),user.sealEnvelope=(async(to,msg)=>(async function(user,to,message){let ek=await cryptic.createECDH(),dh1=await cryptic.ecdh(ek.key,to),sbits=await cryptic.kdf(cryptic.decode(dh1),new Uint8Array(32),cryptic.fromText("SEAL"),512),sealkey=cryptic.decode(sbits).slice(0,32),chainkey=cryptic.decode(sbits).slice(32,64),sealAD=cryptic.combine(cryptic.decode(ek.pub),cryptic.decode(to)),seal=await cryptic.encrypt(user.pub,sealkey,sealAD),dh2=await cryptic.ecdh(user.key,to),mbits=await cryptic.kdf(cryptic.decode(dh2),chainkey,cryptic.fromText("MESSAGE"),256),msgkey=cryptic.decode(mbits),msgAD=cryptic.combine(cryptic.decode(user.pub),cryptic.decode(to)),ciphertext=await cryptic.encrypt(JSON.stringify(message),msgkey,msgAD);return{type:"envelope",to:to,ek:ek.pub,seal:seal,ciphertext:ciphertext}})(useUser(),to,msg)),user.openEnvelope=(async env=>(async function(user,envelope){let dh1=await cryptic.ecdh(user.key,envelope.ek),sbits=await cryptic.kdf(cryptic.decode(dh1),new Uint8Array(32),cryptic.fromText("SEAL"),512),sealkey=cryptic.decode(sbits).slice(0,32),chainkey=cryptic.decode(sbits).slice(32,64),sealAD=cryptic.combine(cryptic.decode(envelope.ek),cryptic.decode(user.pub)),from=await cryptic.decrypt(envelope.seal,sealkey,sealAD),dh2=await cryptic.ecdh(user.key,from),mbits=await cryptic.kdf(cryptic.decode(dh2),chainkey,cryptic.fromText("MESSAGE"),256),msgkey=cryptic.decode(mbits),msgAD=cryptic.combine(cryptic.decode(from),cryptic.decode(user.pub)),decrypted=await cryptic.decrypt(envelope.ciphertext,msgkey,msgAD);return{type:"envelope",to:envelope.to,from:from,plaintext:JSON.parse(decrypted)}})(useUser(),env)),user.createSession=(async card=>{let session=await async function(user,card){let epk=await cryptic.createECDH(),dh1=await cryptic.ecdh(user.key,card.opk),dh2=await cryptic.ecdh(epk.key,card.user),dh3=await cryptic.ecdh(epk.key,card.opk),combined=cryptic.combine(cryptic.combine(cryptic.decode(dh1),cryptic.decode(dh2)),cryptic.decode(dh3)),sk=await cryptic.kdf(combined,new Uint8Array(32),cryptic.fromText("SESSION"),256),AD=cryptic.encode(cryptic.combine(cryptic.decode(user.pub),cryptic.decode(card.user))),init={type:"init",to:card.user,from:user.pub,epk:epk.pub,opk:card.opk};return{type:"session",user:card.user,sk:sk,AD:AD,init:init}}(useUser(),card);return session.state=await async function(session){let state={};state.DHs=await cryptic.createECDH(),state.DHr=session.init.opk;let dh=await cryptic.ecdh(state.DHs.key,state.DHr);return[state.RK,state.CKs]=await rootKDF(session.sk,dh),state.CKr=null,state.Ns=0,state.Nr=0,state.PN=0,state.MKSKIPPED={},state}(session),delete session.sk,Session(session)}),user.openSession=(async(init,secretOPK)=>{let session=await async function(user,opk,init){let dh1=await cryptic.ecdh(opk.key,init.from),dh2=await cryptic.ecdh(user.key,init.epk),dh3=await cryptic.ecdh(opk.key,init.epk),combined=cryptic.combine(cryptic.combine(cryptic.decode(dh1),cryptic.decode(dh2)),cryptic.decode(dh3)),sk=await cryptic.kdf(combined,new Uint8Array(32),cryptic.fromText("SESSION"),256),AD=cryptic.encode(cryptic.combine(cryptic.decode(init.from),cryptic.decode(user.pub)));return{type:"session",user:init.from,sk:sk,AD:AD}}(useUser(),secretOPK,init);return session.state=await async function(session,opk){let state={};return state.DHs=opk,state.DHr=null,state.RK=session.sk,state.CKs=null,state.CKr=null,state.Ns=0,state.Nr=0,state.PN=0,state.MKSKIPPED={},state}(session,secretOPK),delete session.sk,Session(session)}),user.loadSession=(async session=>Session(session)),user.save=(()=>cloneState(userState)),user.getID=(()=>cloneState(userState).pub),user}let cynops={};return cynops.cloneState=cloneState,cynops.cryptic=cryptic,cynops.createUser=(async()=>{return User(await async function(){return cryptic.createECDH()}())}),cynops.loadUser=(async userData=>User(userData)),cynops}"undefined"!=typeof module&&module&&module.exports&&(module.exports=Encryption);