@mdaemon/rsa-message
Version:
RSA message encryption, signing, decryption, and verification using webcrypto or node crypto
15 lines (14 loc) • 17.4 kB
JavaScript
"use strict";function e(e,r,t,i){return new(t||(t=Promise))((function(n,s){function o(e){try{a(i.next(e))}catch(e){s(e)}}function y(e){try{a(i.throw(e))}catch(e){s(e)}}function a(e){var r;e.done?n(e.value):(r=e.value,r instanceof t?r:new t((function(e){e(r)}))).then(o,y)}a((i=i.apply(e,r||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";const t="function"==typeof atob&&atob;var i;const n=/[\t\n\r\x20\x0C]+/g,s=String.fromCharCode;const o=t||"function"==typeof Buffer&&function(e){return Buffer.from(e,"base64").toString("binary")}||function(e){if(!e)return e;var t,o,y,a,c,d,h=(e=String(e).replace(n,"")).length,p=0,l=0,u=[];if(null==i){i={};for(var f=0;f<65;f++)i[r.charAt(f)]=f}do{t=(d=i[e.charAt(p++)]<<18|i[e.charAt(p++)]<<12|(a=i[e.charAt(p++)])<<6|(c=i[e.charAt(p++)]))>>16&255,o=d>>8&255,y=255&d,u[l++]=64==a?s(t):64==c?s(t,o):s(t,o,y)}while(p<h);return u.join("").replace(/\0+$/,"")};const y="function"==typeof btoa&&btoa||"function"==typeof Buffer&&function(e){return Buffer.from(e,"binary").toString("base64")}||function(e){if(!e)return e;var t,i,n,s,o,y=0,a=0,c="",d=[];do{t=(o=e.charCodeAt(y++)<<16|e.charCodeAt(y++)<<8|e.charCodeAt(y++))>>18&63,i=o>>12&63,n=o>>6&63,s=63&o,d[a++]=r.charAt(t)+r.charAt(i)+r.charAt(n)+r.charAt(s)}while(y<e.length);c=d.join("");var h=e.length%3;return(h?c.slice(0,h-3):c)+"===".slice(h||3)};function a(e){return e?y(unescape(encodeURI(e))):e}function c(e,r){let t=
/**
* Base64 string encoding and decoding utility.
*
* play @ https://duzun.me/playground/encode#base64Encode=Test%20String%20
*
* original of _btoa and _atob by: Tyler Akins (http://rumkin.com)
*
*
* @license MIT
* @version 2.2.0
* @author Dumitru Uzun (DUzun.Me)
*/
function(e){let r=e;return r&&(r=o(String(r).replace(/_/g,"/").replace(/-/g,"+"))),r}(e);if(t)try{t=decodeURIComponent(escape(t))}catch(e){}return t}const d=()=>"undefined"!=typeof window?window.crypto:require("crypto").webcrypto,h=()=>"undefined"!=typeof window?new window.TextEncoder:new(require("util").TextEncoder),p=()=>"undefined"!=typeof window?new window.TextDecoder:new(require("util").TextDecoder),l=e=>{const r=new Uint8Array(e);let t="";for(const e of r)t+=String.fromCharCode(e);return y(t)},u=e=>{const r=c(e),t=new ArrayBuffer(r.length),i=new Uint8Array(t);for(let e=0;e<r.length;e++)i[e]=r.charCodeAt(e);return t};class f extends Error{constructor(e,r,t){super(e),this.operation=r,this.originalError=t,this.name="CryptoOperationError"}}class E extends Error{constructor(e,r,t){super(e),this.keyType=r,this.originalError=t,this.name="KeyImportError"}}module.exports=class{generateAndSetMasterAESKey(){return e(this,void 0,void 0,(function*(){const e=yield this.generateAESKey(),r=yield d().subtle.exportKey("jwk",e),t=JSON.stringify(r),i=yield this.encryptMessage(t,"self"),n=this.exportEncryptedMessage(i);return this.encryptedMasterAESKey=n,this.masterAESKeyEncryptor="self",this.masterAESKeyCache=null,n}))}setEncryptedMasterAESKey(e,r="self"){this.encryptedMasterAESKey=e,this.masterAESKeyEncryptor=r,this.masterAESKeyCache=null}getDecryptedMasterAESKey(){return e(this,void 0,void 0,(function*(){if(!this.encryptedMasterAESKey)throw new Error("No master AES key set");const e=this.importEncryptedMessage(this.encryptedMasterAESKey),{iv:r,encryptedMessage:t,encryptedAESKey:i,signature:n}=e;if(!i)throw new Error("Master AES key must have been encrypted with RSA");let s,o,y,a;try{s=yield this.importPrivateKey(this.privateKey,"decrypt")}catch(e){throw new Error(`Failed to import private key: ${e}`)}try{o=yield d().subtle.decrypt({name:"RSA-OAEP"},s,new Uint8Array(i))}catch(e){throw new f(`Failed to decrypt AES key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{y=yield d().subtle.importKey("raw",o,"AES-GCM",!0,["decrypt"])}catch(e){throw new E(`Failed to import AES key: ${e instanceof Error?e.message:"Unknown error"}`,"private",e instanceof Error?e:void 0)}try{a=yield d().subtle.decrypt({name:"AES-GCM",iv:r},y,new Uint8Array(t))}catch(e){throw new f(`Failed to decrypt message: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{const e=p().decode(a);if(!(yield this.verifySignature(n,e,this.masterAESKeyEncryptor||"self")))throw new Error("Signature verification failed");const r=JSON.parse(e);return yield d().subtle.importKey("jwk",r,{name:"AES-GCM"},!0,["encrypt","decrypt"])}catch(e){throw new f(`Failed to verify signature: ${e instanceof Error?e.message:"Unknown error"}`,"verify",e instanceof Error?e:void 0)}}))}exportMasterAESKeyForUser(r){return e(this,void 0,void 0,(function*(){if(!this.encryptedMasterAESKey)throw new Error("No master AES key set");const e=yield this.getDecryptedMasterAESKey(),t=yield d().subtle.exportKey("jwk",e),i=JSON.stringify(t),n=yield this.encryptMessage(i,r);return this.exportEncryptedMessage(n)}))}setMasterAESKeyFromEncrypted(r){return e(this,arguments,void 0,(function*(e,r="self"){this.encryptedMasterAESKey=e,this.masterAESKeyEncryptor=r,this.masterAESKeyCache=null,yield this.getDecryptedMasterAESKey()}))}encryptWithMasterAESKey(r){return e(this,void 0,void 0,(function*(){if(!this.encryptedMasterAESKey)throw new Error("No master AES key set");const e=yield this.getDecryptedMasterAESKey(),t=h().encode(r),i=d().getRandomValues(new Uint8Array(12));return{iv:i,encryptedMessage:yield d().subtle.encrypt({name:"AES-GCM",iv:i},e,t),signature:yield this.signMessage(r)}}))}decryptWithMasterAESKey(r,t){return e(this,void 0,void 0,(function*(){if(!this.encryptedMasterAESKey)throw new Error("No master AES key set");const e=yield this.getDecryptedMasterAESKey(),{iv:i,encryptedMessage:n,signature:s}=r;let o;try{o=yield d().subtle.decrypt({name:"AES-GCM",iv:i},e,new Uint8Array(n))}catch(e){throw new f(`Failed to decrypt with master AES key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{const e=p().decode(o);if(!(yield this.verifySignature(s,e,t)))throw new Error("Signature verification failed");return e}catch(e){throw new f(`Failed to verify signature: ${e instanceof Error?e.message:"Unknown error"}`,"verify",e instanceof Error?e:void 0)}}))}decryptWithMasterAESKeyUnsafe(r,t){return e(this,void 0,void 0,(function*(){if(!this.encryptedMasterAESKey)throw new Error("No master AES key set");const e=yield this.getDecryptedMasterAESKey(),{iv:i,encryptedMessage:n,signature:s}=r;let o;try{o=yield d().subtle.decrypt({name:"AES-GCM",iv:i},e,new Uint8Array(n))}catch(e){throw new f(`Failed to decrypt with master AES key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}const y=p().decode(o);let a=!1;try{a=yield this.verifySignature(s,y,t)}catch(e){a=!1}return{message:y,verified:a}}))}constructor(){this.publicKeys=new Map,this.verifyKeys=new Map,this.ecdhPrivateKey=null,this.ecdhPublicKeys=new Map,this.sharedKeys=new Map,this.encryptedMasterAESKey=null,this.masterAESKeyEncryptor=null,this.masterAESKeyCache=null,this.genKeyPair=(...r)=>e(this,[...r],void 0,(function*(e="decrypt"){const r="decrypt"===e?["encrypt","decrypt"]:["sign","verify"],t=yield d().subtle.generateKey({name:"decrypt"===e?"RSA-OAEP":"RSA-PSS",modulusLength:2048,publicExponent:new Uint8Array([1,0,1]),hash:"SHA-256"},!0,r),i=yield d().subtle.exportKey("jwk",t.publicKey),n=yield d().subtle.exportKey("jwk",t.privateKey);return{publicKey:a(JSON.stringify(i)),privateKey:a(JSON.stringify(n))}})),this.importPrivateKey=(r,t)=>e(this,void 0,void 0,(function*(){const e={name:"decrypt"===t?"RSA-OAEP":"RSA-PSS",hash:"SHA-256"};"sign"===t&&(e.saltLength=32);try{const i=JSON.parse(c(r));return yield d().subtle.importKey("jwk",i,e,!1,[t])}catch(e){throw new E(`Failed to import private key: ${e instanceof Error?e.message:"Unknown error"}`,"private",e instanceof Error?e:void 0)}})),this.importPublicKey=(r,t)=>e(this,void 0,void 0,(function*(){const e={name:"encrypt"===t?"RSA-OAEP":"RSA-PSS",hash:"SHA-256"};"verify"===t&&(e.saltLength=32);try{const i=JSON.parse(c(r));return yield d().subtle.importKey("jwk",i,e,!1,[t])}catch(e){throw new E(`Failed to import public key: ${e instanceof Error?e.message:"Unknown error"}`,"public",e instanceof Error?e:void 0)}})),this.encryptMessage=(r,t,...i)=>e(this,[r,t,...i],void 0,(function*(e,r,t=!1){const i=this.publicKeys.get(r);if(!i)throw new Error("Public key not found for user");const n=h().encode(e);let s,o;if(t&&this.encryptedMasterAESKey)s=yield this.getDecryptedMasterAESKey(),o=void 0;else{s=yield this.generateAESKey();const e=yield this.importPublicKey(i,"encrypt"),r=yield d().subtle.exportKey("raw",s);o=yield d().subtle.encrypt({name:"RSA-OAEP"},e,r)}const y=d().getRandomValues(new Uint8Array(12));return{iv:y,encryptedMessage:yield d().subtle.encrypt({name:"AES-GCM",iv:y},s,n),encryptedAESKey:o,signature:yield this.signMessage(e)}})),this.decryptMessage=(r,t,...i)=>e(this,[r,t,...i],void 0,(function*(e,r,t=!1){const{iv:i,encryptedMessage:n,encryptedAESKey:s,signature:o}=e;let y;if(t&&this.encryptedMasterAESKey)y=yield this.getDecryptedMasterAESKey();else{if(!s)throw new Error("No AES key available for decryption - either provide encryptedAESKey or use master AES key");{let e;try{e=yield this.importPrivateKey(this.privateKey,"decrypt")}catch(e){throw new Error(`Failed to import private key: ${e}`)}let r="";try{r=yield d().subtle.decrypt({name:"RSA-OAEP"},e,new Uint8Array(s))}catch(e){throw new f(`Failed to decrypt AES key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{y=yield d().subtle.importKey("raw",r,"AES-GCM",!0,["decrypt"])}catch(e){throw new E(`Failed to import AES key: ${e instanceof Error?e.message:"Unknown error"}`,"private",e instanceof Error?e:void 0)}}}let a="";try{a=yield d().subtle.decrypt({name:"AES-GCM",iv:i},y,new Uint8Array(n))}catch(e){throw new f(`Failed to decrypt message: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{const e=p().decode(a);if(!(yield this.verifySignature(o,e,r)))throw new Error("Signature verification failed");return e}catch(e){throw new f(`Failed to verify signature: ${e instanceof Error?e.message:"Unknown error"}`,"verify",e instanceof Error?e:void 0)}})),this.signMessage=r=>e(this,void 0,void 0,(function*(){const e=h().encode(r);try{const r=yield this.importPrivateKey(this.signKey,"sign");return yield d().subtle.sign({name:"RSA-PSS",saltLength:32},r,e)}catch(e){throw new f(`Failed to sign message: ${e instanceof Error?e.message:"Unknown error"}`,"sign",e instanceof Error?e:void 0)}})),this.verifySignature=(r,t,i)=>e(this,void 0,void 0,(function*(){const e=this.verifyKeys.get(i);if(!e)throw new Error("Public key not found for user");try{const i=yield this.importPublicKey(e,"verify"),n=h().encode(t);return yield d().subtle.verify({name:"RSA-PSS",saltLength:32},i,new Uint8Array(r),n)}catch(e){throw new f(`Failed to verify signature: ${e instanceof Error?e.message:"Unknown error"}`,"verify",e instanceof Error?e:void 0)}})),this.privateKey="",this.publicKey="",this.verifyKey="",this.signKey=""}get publickey(){return this.publicKey}get verifykey(){return this.verifyKey}get privatekey(){return this.privateKey}get signkey(){return this.signKey}generateAESKey(){return e(this,void 0,void 0,(function*(){return yield d().subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}))}init(r,t,i,n){return e(this,void 0,void 0,(function*(){if(r&&t&&i&&n)return this.publicKey=r,this.privateKey=t,this.verifyKey=i,this.signKey=n,{publicKey:this.publicKey,verifyKey:this.verifyKey};const e=yield this.genKeyPair(),s=yield this.genKeyPair("sign");return this.publicKey=e.publicKey,this.privateKey=e.privateKey,this.verifyKey=s.publicKey,this.signKey=s.privateKey,{publicKey:e.publicKey,verifyKey:s.publicKey}}))}decryptMessageUnsafe(r,t){return e(this,arguments,void 0,(function*(e,r,t=!1){const{iv:i,encryptedMessage:n,encryptedAESKey:s,signature:o}=e;let y;if(t&&this.encryptedMasterAESKey)y=yield this.getDecryptedMasterAESKey();else{if(!s)throw new Error("No AES key available for decryption - either provide encryptedAESKey or use master AES key");{let e;try{e=yield this.importPrivateKey(this.privateKey,"decrypt")}catch(e){throw new Error(`Failed to import private key: ${e}`)}let r="";try{r=yield d().subtle.decrypt({name:"RSA-OAEP"},e,new Uint8Array(s))}catch(e){throw new f(`Failed to decrypt AES key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}try{y=yield d().subtle.importKey("raw",r,"AES-GCM",!0,["decrypt"])}catch(e){throw new E(`Failed to import AES key: ${e instanceof Error?e.message:"Unknown error"}`,"private",e instanceof Error?e:void 0)}}}let a="";try{a=yield d().subtle.decrypt({name:"AES-GCM",iv:i},y,new Uint8Array(n))}catch(e){throw new f(`Failed to decrypt message: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}const c=p().decode(a);let h=!1;try{h=yield this.verifySignature(o,c,r)}catch(e){h=!1}return{message:c,verified:h}}))}setPublicKey(e,r,t){if(!e||!r)throw new Error("Invalid arguments");this.publicKeys.set(e,r),t&&this.verifyKeys.set(e,t)}setVerifyKey(e,r){if(!e||!r)throw new Error("Invalid arguments");this.verifyKeys.set(e,r)}hasPublicKey(e){return this.publicKeys.has(e)}hasVerifyKey(e){return this.verifyKeys.has(e)}exportEncryptedMessage(e){return a(JSON.stringify({iv:String.fromCharCode(...e.iv),encryptedMessage:l(e.encryptedMessage),encryptedAESKey:l(e.encryptedAESKey),signature:l(e.signature)}))}importEncryptedMessage(e){const r=JSON.parse(c(e));return{iv:new Uint8Array([...r.iv].map((e=>e.charCodeAt(0)))),encryptedMessage:u(r.encryptedMessage),encryptedAESKey:u(r.encryptedAESKey),signature:u(r.signature)}}generateECDHKeyPair(){return e(this,void 0,void 0,(function*(){try{const e=yield d().subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveBits","deriveKey"]);this.ecdhPrivateKey=e.privateKey;const r=yield d().subtle.exportKey("jwk",e.publicKey);return{publicKey:a(JSON.stringify(r))}}catch(e){throw new f(`Failed to generate ECDH key pair: ${e instanceof Error?e.message:"Unknown error"}`,"encrypt",e instanceof Error?e:void 0)}}))}setECDHPublicKey(r,t){return e(this,void 0,void 0,(function*(){try{const e=JSON.parse(c(t)),i=yield d().subtle.importKey("jwk",e,{name:"ECDH",namedCurve:"P-256"},!1,[]);this.ecdhPublicKeys.set(r,i)}catch(e){throw new E(`Failed to import ECDH public key: ${e instanceof Error?e.message:"Unknown error"}`,"public",e instanceof Error?e:void 0)}}))}deriveSharedKey(r,t){return e(this,void 0,void 0,(function*(){if(!this.ecdhPrivateKey)throw new Error("ECDH private key not generated. Call generateECDHKeyPair() first.");const e=this.ecdhPublicKeys.get(r);if(!e)throw new Error(`ECDH public key not found for user: ${r}`);try{const i=t||d().getRandomValues(new Uint8Array(16)),n=yield d().subtle.deriveBits({name:"ECDH",public:e},this.ecdhPrivateKey,256),s=yield d().subtle.importKey("raw",n,"PBKDF2",!1,["deriveKey"]),o=yield d().subtle.deriveKey({name:"PBKDF2",salt:i,iterations:1e5,hash:"SHA-256"},s,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return this.sharedKeys.set(r,o),i}catch(e){throw new f(`Failed to derive shared key: ${e instanceof Error?e.message:"Unknown error"}`,"encrypt",e instanceof Error?e:void 0)}}))}encryptWithSharedKey(r,t){return e(this,void 0,void 0,(function*(){const e=this.sharedKeys.get(t);if(!e)throw new Error(`Shared key not found for user: ${t}. Call deriveSharedKey() first.`);try{const t=h().encode(r),i=d().getRandomValues(new Uint8Array(12)),n=d().getRandomValues(new Uint8Array(16));return{salt:n,encryptedMessage:yield d().subtle.encrypt({name:"AES-GCM",iv:i},e,t),iv:i}}catch(e){throw new f(`Failed to encrypt with shared key: ${e instanceof Error?e.message:"Unknown error"}`,"encrypt",e instanceof Error?e:void 0)}}))}decryptWithSharedKey(r,t){return e(this,void 0,void 0,(function*(){const e=this.sharedKeys.get(t);if(!e)throw new Error(`Shared key not found for user: ${t}. Call deriveSharedKey() first.`);try{const{iv:t,encryptedMessage:i}=r,n=yield d().subtle.decrypt({name:"AES-GCM",iv:t},e,new Uint8Array(i));return p().decode(n)}catch(e){throw new f(`Failed to decrypt with shared key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}}))}exportSharedKeyData(e){return a(JSON.stringify({salt:String.fromCharCode(...e.salt),encryptedMessage:l(e.encryptedMessage),iv:String.fromCharCode(...e.iv)}))}importSharedKeyData(e){const r=JSON.parse(c(e));return{salt:new Uint8Array([...r.salt].map((e=>e.charCodeAt(0)))),encryptedMessage:u(r.encryptedMessage),iv:new Uint8Array([...r.iv].map((e=>e.charCodeAt(0))))}}hasSharedKey(e){return this.sharedKeys.has(e)}hasECDHPublicKey(e){return this.ecdhPublicKeys.has(e)}removeSharedKey(e){this.sharedKeys.delete(e)}removeECDHPublicKey(e){this.ecdhPublicKeys.delete(e)}decryptWithSharedKeyUnsafe(r,t){return e(this,void 0,void 0,(function*(){const e=this.sharedKeys.get(t);if(!e)throw new Error(`Shared key not found for user: ${t}. Call deriveSharedKey() first.`);try{const{iv:t,encryptedMessage:i}=r,n=yield d().subtle.decrypt({name:"AES-GCM",iv:t},e,new Uint8Array(i)),s=p();return{message:s.decode(n),verified:!0}}catch(e){throw new f(`Failed to decrypt with shared key: ${e instanceof Error?e.message:"Unknown error"}`,"decrypt",e instanceof Error?e:void 0)}}))}};