UNPKG

etebase

Version:

Etebase TypeScript API for the web and node

269 lines 12.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPrettyFingerprint = exports.CryptoMac = exports.BoxCryptoManager = exports.LoginCryptoManager = exports.CryptoManager = exports.deriveKey = exports.KeyDerivationDifficulty = exports.concatArrayBuffersArrays = exports.concatArrayBuffers = exports.ready = exports._setRnSodium = void 0; // Shim document if it doesn't exist (e.g. on React native) if ((typeof global !== "undefined") && !global.document) { global.document = {}; } const libsodium_wrappers_1 = __importDefault(require("libsodium-wrappers")); const Argon2 = __importStar(require("argon2-webworker")); const Constants = __importStar(require("./Constants")); const Helpers_1 = require("./Helpers"); const Chunker_1 = require("./Chunker"); const Exceptions_1 = require("./Exceptions"); const sodium = libsodium_wrappers_1.default; let rnsodium; function _setRnSodium(rnsodium_) { rnsodium = rnsodium_; } exports._setRnSodium = _setRnSodium; exports.ready = (async () => { await sodium.ready; })(); function concatArrayBuffers(buffer1, buffer2) { const ret = new Uint8Array(buffer1.length + buffer2.length); ret.set(buffer1, 0); ret.set(buffer2, buffer1.length); return ret; } exports.concatArrayBuffers = concatArrayBuffers; function concatArrayBuffersArrays(buffers) { const length = buffers.reduce((x, y) => x + y.length, 0); const ret = new Uint8Array(length); let pos = 0; for (const buffer of buffers) { ret.set(buffer, pos); pos += buffer.length; } return ret; } exports.concatArrayBuffersArrays = concatArrayBuffersArrays; var KeyDerivationDifficulty; (function (KeyDerivationDifficulty) { KeyDerivationDifficulty[KeyDerivationDifficulty["Hard"] = 90] = "Hard"; KeyDerivationDifficulty[KeyDerivationDifficulty["Medium"] = 50] = "Medium"; KeyDerivationDifficulty[KeyDerivationDifficulty["Easy"] = 10] = "Easy"; })(KeyDerivationDifficulty = exports.KeyDerivationDifficulty || (exports.KeyDerivationDifficulty = {})); async function deriveKey(salt, password, difficulty = KeyDerivationDifficulty.Hard) { salt = salt.subarray(0, sodium.crypto_pwhash_SALTBYTES); let opslimit; switch (difficulty) { case KeyDerivationDifficulty.Hard: opslimit = sodium.crypto_pwhash_OPSLIMIT_SENSITIVE; break; case KeyDerivationDifficulty.Medium: opslimit = sodium.crypto_pwhash_OPSLIMIT_MODERATE; break; case KeyDerivationDifficulty.Easy: opslimit = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; break; default: throw new Exceptions_1.ProgrammingError("Passed invalid difficulty."); } try { const ret = await Argon2.hash({ hashLen: 32, pass: password, salt, time: opslimit, mem: sodium.crypto_pwhash_MEMLIMIT_MODERATE / 1024, parallelism: 1, type: Argon2.ArgonType.Argon2id, }); return ret.hash; } catch (e) { if (typeof (Worker) !== "undefined") { // Web worker failed console.warn("Failed loading web worker!", e); } } if (rnsodium) { const ret = await rnsodium.crypto_pwhash(32, sodium.to_base64(sodium.from_string(password), sodium.base64_variants.ORIGINAL), sodium.to_base64(salt, sodium.base64_variants.ORIGINAL), opslimit, sodium.crypto_pwhash_MEMLIMIT_MODERATE, sodium.crypto_pwhash_ALG_DEFAULT); return sodium.from_base64(ret, sodium.base64_variants.ORIGINAL); } return sodium.crypto_pwhash(32, sodium.from_string(password), salt, opslimit, sodium.crypto_pwhash_MEMLIMIT_MODERATE, sodium.crypto_pwhash_ALG_DEFAULT); } exports.deriveKey = deriveKey; class CryptoManager { constructor(key, keyContext, version = Constants.CURRENT_VERSION) { keyContext = keyContext.padEnd(8); this.version = version; this.cipherKey = sodium.crypto_kdf_derive_from_key(32, 1, keyContext, key); this.macKey = sodium.crypto_kdf_derive_from_key(32, 2, keyContext, key); this.asymKeySeed = sodium.crypto_kdf_derive_from_key(32, 3, keyContext, key); this.subDerivationKey = sodium.crypto_kdf_derive_from_key(32, 4, keyContext, key); this.determinsticEncryptionKey = sodium.crypto_kdf_derive_from_key(32, 5, keyContext, key); } encrypt(message, additionalData = null) { const nonce = sodium.randombytes_buf(Helpers_1.symmetricNonceSize); return concatArrayBuffers(nonce, sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(message, additionalData, null, nonce, this.cipherKey)); } decrypt(nonceCiphertext, additionalData = null) { const nonce = nonceCiphertext.subarray(0, Helpers_1.symmetricNonceSize); const ciphertext = nonceCiphertext.subarray(Helpers_1.symmetricNonceSize); return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, additionalData, nonce, this.cipherKey); } encryptDetached(message, additionalData = null) { const nonce = sodium.randombytes_buf(Helpers_1.symmetricNonceSize); const ret = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(message, additionalData, null, nonce, this.cipherKey); return [ret.mac, concatArrayBuffers(nonce, ret.ciphertext)]; } decryptDetached(nonceCiphertext, mac, additionalData = null) { const nonce = nonceCiphertext.subarray(0, Helpers_1.symmetricNonceSize); const ciphertext = nonceCiphertext.subarray(Helpers_1.symmetricNonceSize); return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(null, ciphertext, mac, additionalData, nonce, this.cipherKey); } verify(nonceCiphertext, mac, additionalData = null) { const nonce = nonceCiphertext.subarray(0, Helpers_1.symmetricNonceSize); const ciphertext = nonceCiphertext.subarray(Helpers_1.symmetricNonceSize); sodium.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(null, ciphertext, mac, additionalData, nonce, this.cipherKey, null); return true; } deterministicEncrypt(message, additionalData = null) { // FIXME: we could me slightly more efficient (save 8 bytes) and use crypto_stream_xchacha20_xor directly, and // just have the mac be used to verify. Though that function is not exposed in libsodium.js (the slimmer version), // and it's easier to get wrong, so we are just using the full xchacha20poly1305 we already use anyway. const nonce = this.calculateMac(message).subarray(0, Helpers_1.symmetricNonceSize); return concatArrayBuffers(nonce, sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(message, additionalData, null, nonce, this.determinsticEncryptionKey)); } deterministicDecrypt(nonceCiphertext, additionalData = null) { const nonce = nonceCiphertext.subarray(0, Helpers_1.symmetricNonceSize); const ciphertext = nonceCiphertext.subarray(Helpers_1.symmetricNonceSize); return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, additionalData, nonce, this.determinsticEncryptionKey); } deriveSubkey(salt) { return sodium.crypto_generichash(32, this.subDerivationKey, salt); } getCryptoMac(withKey = true) { const key = (withKey) ? this.macKey : null; return new CryptoMac(key); } calculateMac(message, withKey = true) { const key = (withKey) ? this.macKey : null; return sodium.crypto_generichash(32, message, key); } getChunker() { return new Chunker_1.Rollsum(); } } exports.CryptoManager = CryptoManager; class LoginCryptoManager { constructor(keypair) { this.keypair = keypair; } static keygen(seed) { return new this(sodium.crypto_sign_seed_keypair(seed)); } signDetached(message) { return sodium.crypto_sign_detached(message, this.keypair.privateKey); } static verifyDetached(message, signature, pubkey) { return sodium.crypto_sign_verify_detached(signature, message, pubkey); } get pubkey() { return this.keypair.publicKey; } } exports.LoginCryptoManager = LoginCryptoManager; class BoxCryptoManager { constructor(keypair) { this.keypair = keypair; } static keygen(seed) { if (seed) { return new this(sodium.crypto_box_seed_keypair(seed)); } else { return new this(sodium.crypto_box_keypair()); } } static fromPrivkey(privkey) { return new this({ keyType: "x25519", privateKey: privkey, publicKey: sodium.crypto_scalarmult_base(privkey), }); } encrypt(message, pubkey) { const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES); const ret = sodium.crypto_box_easy(message, nonce, pubkey, this.keypair.privateKey); return concatArrayBuffers(nonce, ret); } decrypt(nonceCiphertext, pubkey) { const nonceSize = sodium.crypto_box_NONCEBYTES; const nonce = nonceCiphertext.subarray(0, nonceSize); const ciphertext = nonceCiphertext.subarray(nonceSize); return sodium.crypto_box_open_easy(ciphertext, nonce, pubkey, this.keypair.privateKey); } get pubkey() { return this.keypair.publicKey; } get privkey() { return this.keypair.privateKey; } } exports.BoxCryptoManager = BoxCryptoManager; class CryptoMac { constructor(key, length = 32) { this.length = length; this.state = sodium.crypto_generichash_init(key, length); } updateWithLenPrefix(messageChunk) { sodium.crypto_generichash_update(this.state, Helpers_1.numToUint8Array(messageChunk.length)); sodium.crypto_generichash_update(this.state, messageChunk); } update(messageChunk) { sodium.crypto_generichash_update(this.state, messageChunk); } finalize() { return sodium.crypto_generichash_final(this.state, this.length); } } exports.CryptoMac = CryptoMac; function getEncodedChunk(content, offset) { const num = ((content[offset] << 16) + (content[offset + 1] << 8) + content[offset + 2]) % 100000; return num.toString().padStart(5, "0"); } function getPrettyFingerprint(content, delimiter = " ") { const fingerprint = sodium.crypto_generichash(32, content); /* We use 3 bytes each time to generate a 5 digit number - this means 10 pairs for bytes 0-29 * We then use bytes 29-31 for another number, and then the 3 most significant bits of each first byte for the last. */ let ret = ""; let lastNum = 0; for (let i = 0; i < 10; i++) { const suffix = (i % 4 === 3) ? "\n" : delimiter; ret += getEncodedChunk(fingerprint, i * 3) + suffix; lastNum = (lastNum << 3) | ((fingerprint[i] & 0xE0) >>> 5); } ret += getEncodedChunk(fingerprint, 29) + delimiter; ret += (lastNum % 100000).toString().padStart(5, "0"); return ret; } exports.getPrettyFingerprint = getPrettyFingerprint; //# sourceMappingURL=Crypto.js.map