@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
JavaScript
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
;