etebase
Version:
Etebase TypeScript API for the web and node
269 lines • 12.1 kB
JavaScript
"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