UNPKG

@worker-tools/deno-kv-storage

Version:

An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.

234 lines 11.5 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Client_auth_message, _Client_client_nonce, _Client_key_signatures, _Client_password, _Client_server_nonce, _Client_state, _Client_username; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = exports.Reason = void 0; const deps_js_1 = require("../deps.js"); /** Number of random bytes used to generate a nonce */ const defaultNonceSize = 16; const text_encoder = new TextEncoder(); var AuthenticationState; (function (AuthenticationState) { AuthenticationState[AuthenticationState["Init"] = 0] = "Init"; AuthenticationState[AuthenticationState["ClientChallenge"] = 1] = "ClientChallenge"; AuthenticationState[AuthenticationState["ServerChallenge"] = 2] = "ServerChallenge"; AuthenticationState[AuthenticationState["ClientResponse"] = 3] = "ClientResponse"; AuthenticationState[AuthenticationState["ServerResponse"] = 4] = "ServerResponse"; AuthenticationState[AuthenticationState["Failed"] = 5] = "Failed"; })(AuthenticationState || (AuthenticationState = {})); /** * Reason of authentication failure */ var Reason; (function (Reason) { Reason["BadMessage"] = "server sent an ill-formed message"; Reason["BadServerNonce"] = "server sent an invalid nonce"; Reason["BadSalt"] = "server specified an invalid salt"; Reason["BadIterationCount"] = "server specified an invalid iteration count"; Reason["BadVerifier"] = "server sent a bad verifier"; Reason["Rejected"] = "rejected by server"; })(Reason = exports.Reason || (exports.Reason = {})); function assert(cond) { if (!cond) { throw new Error("Scram protocol assertion failed"); } } // TODO // Handle mapping and maybe unicode normalization. // Add tests for invalid string values /** * Normalizes string per SASLprep. * @see {@link https://tools.ietf.org/html/rfc3454} * @see {@link https://tools.ietf.org/html/rfc4013} */ function assertValidScramString(str) { const unsafe = /[^\x21-\x7e]/; if (unsafe.test(str)) { throw new Error("scram username/password is currently limited to safe ascii characters"); } } async function computeScramSignature(message, raw_key) { const key = await crypto.subtle.importKey("raw", raw_key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); return new Uint8Array(await crypto.subtle.sign({ name: "HMAC", hash: "SHA-256" }, key, text_encoder.encode(message))); } function computeScramProof(signature, key) { const digest = new Uint8Array(signature.length); for (let i = 0; i < digest.length; i++) { digest[i] = signature[i] ^ key[i]; } return digest; } /** * Derives authentication key signatures from a plaintext password */ async function deriveKeySignatures(password, salt, iterations) { const pbkdf2_password = await crypto.subtle.importKey("raw", text_encoder.encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"]); const key = await crypto.subtle.deriveKey({ hash: "SHA-256", iterations, name: "PBKDF2", salt, }, pbkdf2_password, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); const client = new Uint8Array(await crypto.subtle.sign("HMAC", key, text_encoder.encode("Client Key"))); const server = new Uint8Array(await crypto.subtle.sign("HMAC", key, text_encoder.encode("Server Key"))); const stored = new Uint8Array(await crypto.subtle.digest("SHA-256", client)); return { client, server, stored }; } /** Escapes "=" and "," in a string. */ function escape(str) { return str .replace(/=/g, "=3D") .replace(/,/g, "=2C"); } function generateRandomNonce(size) { return deps_js_1.base64.encode(crypto.getRandomValues(new Uint8Array(size))); } function parseScramAttributes(message) { const attrs = {}; for (const entry of message.split(",")) { const pos = entry.indexOf("="); if (pos < 1) { throw new Error(Reason.BadMessage); } // TODO // Replace with String.prototype.substring const key = entry.substr(0, pos); const value = entry.substr(pos + 1); attrs[key] = value; } return attrs; } /** * Client composes and verifies SCRAM authentication messages, keeping track * of authentication #state and parameters. * @see {@link https://tools.ietf.org/html/rfc5802} */ class Client { constructor(username, password, nonce) { _Client_auth_message.set(this, void 0); _Client_client_nonce.set(this, void 0); _Client_key_signatures.set(this, void 0); _Client_password.set(this, void 0); _Client_server_nonce.set(this, void 0); _Client_state.set(this, void 0); _Client_username.set(this, void 0); assertValidScramString(password); assertValidScramString(username); __classPrivateFieldSet(this, _Client_auth_message, "", "f"); __classPrivateFieldSet(this, _Client_client_nonce, nonce !== null && nonce !== void 0 ? nonce : generateRandomNonce(defaultNonceSize), "f"); __classPrivateFieldSet(this, _Client_password, password, "f"); __classPrivateFieldSet(this, _Client_state, AuthenticationState.Init, "f"); __classPrivateFieldSet(this, _Client_username, escape(username), "f"); } /** * Composes client-first-message */ composeChallenge() { assert(__classPrivateFieldGet(this, _Client_state, "f") === AuthenticationState.Init); try { // "n" for no channel binding, then an empty authzid option follows. const header = "n,,"; const challenge = `n=${__classPrivateFieldGet(this, _Client_username, "f")},r=${__classPrivateFieldGet(this, _Client_client_nonce, "f")}`; const message = header + challenge; __classPrivateFieldSet(this, _Client_auth_message, __classPrivateFieldGet(this, _Client_auth_message, "f") + challenge, "f"); __classPrivateFieldSet(this, _Client_state, AuthenticationState.ClientChallenge, "f"); return message; } catch (e) { __classPrivateFieldSet(this, _Client_state, AuthenticationState.Failed, "f"); throw e; } } /** * Processes server-first-message */ async receiveChallenge(challenge) { assert(__classPrivateFieldGet(this, _Client_state, "f") === AuthenticationState.ClientChallenge); try { const attrs = parseScramAttributes(challenge); const nonce = attrs.r; if (!attrs.r || !attrs.r.startsWith(__classPrivateFieldGet(this, _Client_client_nonce, "f"))) { throw new Error(Reason.BadServerNonce); } __classPrivateFieldSet(this, _Client_server_nonce, nonce, "f"); let salt; if (!attrs.s) { throw new Error(Reason.BadSalt); } try { salt = deps_js_1.base64.decode(attrs.s); } catch { throw new Error(Reason.BadSalt); } const iterCount = parseInt(attrs.i) | 0; if (iterCount <= 0) { throw new Error(Reason.BadIterationCount); } __classPrivateFieldSet(this, _Client_key_signatures, await deriveKeySignatures(__classPrivateFieldGet(this, _Client_password, "f"), salt, iterCount), "f"); __classPrivateFieldSet(this, _Client_auth_message, __classPrivateFieldGet(this, _Client_auth_message, "f") + ("," + challenge), "f"); __classPrivateFieldSet(this, _Client_state, AuthenticationState.ServerChallenge, "f"); } catch (e) { __classPrivateFieldSet(this, _Client_state, AuthenticationState.Failed, "f"); throw e; } } /** * Composes client-final-message */ async composeResponse() { assert(__classPrivateFieldGet(this, _Client_state, "f") === AuthenticationState.ServerChallenge); assert(__classPrivateFieldGet(this, _Client_key_signatures, "f")); assert(__classPrivateFieldGet(this, _Client_server_nonce, "f")); try { // "biws" is the base-64 encoded form of the gs2-header "n,,". const responseWithoutProof = `c=biws,r=${__classPrivateFieldGet(this, _Client_server_nonce, "f")}`; __classPrivateFieldSet(this, _Client_auth_message, __classPrivateFieldGet(this, _Client_auth_message, "f") + ("," + responseWithoutProof), "f"); const proof = deps_js_1.base64.encode(computeScramProof(await computeScramSignature(__classPrivateFieldGet(this, _Client_auth_message, "f"), __classPrivateFieldGet(this, _Client_key_signatures, "f").stored), __classPrivateFieldGet(this, _Client_key_signatures, "f").client)); const message = `${responseWithoutProof},p=${proof}`; __classPrivateFieldSet(this, _Client_state, AuthenticationState.ClientResponse, "f"); return message; } catch (e) { __classPrivateFieldSet(this, _Client_state, AuthenticationState.Failed, "f"); throw e; } } /** * Processes server-final-message */ async receiveResponse(response) { var _a; assert(__classPrivateFieldGet(this, _Client_state, "f") === AuthenticationState.ClientResponse); assert(__classPrivateFieldGet(this, _Client_key_signatures, "f")); try { const attrs = parseScramAttributes(response); if (attrs.e) { throw new Error((_a = attrs.e) !== null && _a !== void 0 ? _a : Reason.Rejected); } const verifier = deps_js_1.base64.encode(await computeScramSignature(__classPrivateFieldGet(this, _Client_auth_message, "f"), __classPrivateFieldGet(this, _Client_key_signatures, "f").server)); if (attrs.v !== verifier) { throw new Error(Reason.BadVerifier); } __classPrivateFieldSet(this, _Client_state, AuthenticationState.ServerResponse, "f"); } catch (e) { __classPrivateFieldSet(this, _Client_state, AuthenticationState.Failed, "f"); throw e; } } } exports.Client = Client; _Client_auth_message = new WeakMap(), _Client_client_nonce = new WeakMap(), _Client_key_signatures = new WeakMap(), _Client_password = new WeakMap(), _Client_server_nonce = new WeakMap(), _Client_state = new WeakMap(), _Client_username = new WeakMap(); //# sourceMappingURL=scram.js.map