UNPKG

@wault-pw/srp6a-webcrypto

Version:

Pure javascript implementation of SRP-6a (RFC-5054, RFC-2945) using web-crypto

156 lines 6.61 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientChallenge = exports.SrpClient = void 0; const engine_1 = require("./engine"); const util_1 = require("./util"); const errors_1 = require("./errors"); // @refs RFC-5054 https://datatracker.ietf.org/doc/html/rfc5054 // @refs RFC-2945 https://datatracker.ietf.org/doc/html/rfc2945 // // @refs https://github.com/simbo1905/thinbus-srp-npm/blob/master/server.js // @refs https://github.com/grempe/sirp // @refs https://github.com/opencoff/go-srp // // N, g: group parameters (prime and generator) // s: salt // B, b: server's public and private values // A, a: client's public and private values // I: user name (aka "identity") // P: password // v: verifier // k: SRP-6 multiplier (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) // S: pre-master secret // K = SHA_Interleave(S) shared secret key // // m1: client proof H(PAD(A) | PAD(B) | PAD(S)) // m2: server proof H(PAD(A) | M1 | PAD(S)) class SrpClient { constructor(username, password, params) { this.e = new engine_1.Engine(params); this.username = username; this.password = password; this.s = new Uint8Array(0); // @test this._a = null; } randomSalt() { return __awaiter(this, void 0, void 0, function* () { return yield (0, util_1.SecureRandom)(this.e.N_SIZE); }); } seed(s) { this.s = s; } get salt() { return this.s; } // The verifier (v) is computed based on the salt (s), user name (I), // password (P), and group parameters (N, g). The computation uses the // [SHA1] hash algorithm: // // x = SHA1(s | SHA1(I | ":" | P)) // v = g^x % N verifier() { return __awaiter(this, void 0, void 0, function* () { const hashed = yield this.e.HASHED_CRED(this.username, this.password); const x = (0, util_1.BigIntFromUint8Array)(yield this.e.HASH(this.s, hashed)); const v = (0, util_1.EuclideanModPow)(this.e.g, x, this.e.N); return (0, util_1.BigInt2Uint8Array)(v); }); } // The pre-master secret is calculated by the client as follows: // // I, P = <read from user> // N, g, s, B = <read from server> // a = random() // A = g^a % N // u = SHA1(PAD(A) | PAD(B)) // k = SHA1(N | PAD(g)) // x = SHA1(s | SHA1(I | ":" | P)) // <pre-master secret> = (B - (k * g^x)) ^ (a + (u * x)) % N // // The pre-master secret is calculated by the server as follows: // // N, g, s, v = <read from password file> // b = random() // k = SHA1(N | PAD(g)) // B = k*v + g^b % N // A = <read from client> // u = SHA1(PAD(A) | PAD(B)) // <pre-master secret> = (A * v^u) ^ b % N // // To verify that the client has generated the same key "K or S", the client sends // "M'" -- a hash of all the data it has and it received from the server. To // validate that the server also has the same value, it requires the server to send // its own proof. In the SRP paper [1], the authors use: // M2 = SHA(PAD(A) | M1 | PAD(S)) => from server // M1 = SHA(PAD(A) | PAD(B) | PAD(S)) => from client setServerPublicKey(B) { return __awaiter(this, void 0, void 0, function* () { // The client MUST abort authentication if B % N is zero. if (this.e.isModZero((0, util_1.BigIntFromUint8Array)(B), this.e.N)) { throw errors_1.ErrAbort; } const a = this._a ? (0, util_1.BigIntFromUint8Array)(this._a) : (0, util_1.BigIntFromUint8Array)(yield (0, util_1.SecureRandom)(this.e.N_SIZE)); const A = (0, util_1.EuclideanModPow)(this.e.g, a, this.e.N); const k = yield this.e.k(); const u = (0, util_1.BigIntFromUint8Array)(yield this.e.HASH(this.e.PAD((0, util_1.BigInt2Uint8Array)(A)), this.e.PAD(B))); const x = (0, util_1.BigIntFromUint8Array)(yield this.e.HASH(this.s, yield this.e.HASHED_CRED(this.username, this.password))); // (B - (k * g^x)) ^ (a + (u * x)) % N const _exp = u.multiply(x).add(a); const _tmp = (0, util_1.EuclideanModPow)(this.e.g, x, this.e.N).multiply(k); const S = (0, util_1.EuclideanModPow)((0, util_1.BigIntFromUint8Array)(B).subtract(_tmp), _exp, this.e.N); // H(A, M, K) const m1 = yield this.e.HASH(this.e.PAD((0, util_1.BigInt2Uint8Array)(A)), this.e.PAD(B), this.e.PAD((0, util_1.BigInt2Uint8Array)(S))); // H(PAD(A) | M1 | PAD(S)) const m2 = yield this.e.HASH(this.e.PAD((0, util_1.BigInt2Uint8Array)(A)), m1, this.e.PAD((0, util_1.BigInt2Uint8Array)(S))); const ch = new ClientChallenge(); ch.k = (0, util_1.BigInt2Uint8Array)(k); ch.x = (0, util_1.BigInt2Uint8Array)(x); ch.a = (0, util_1.BigInt2Uint8Array)(a); ch.A = (0, util_1.BigInt2Uint8Array)(A); ch.u = (0, util_1.BigInt2Uint8Array)(u); ch.S = (0, util_1.BigInt2Uint8Array)(S); ch.m1 = m1; ch.m2 = m2; // @refs https://go.dev/play/p/21hj3bfDs8U return ch; }); } } exports.SrpClient = SrpClient; class ClientChallenge { constructor() { this.k = new Uint8Array(0); this.x = new Uint8Array(0); this.a = new Uint8Array(0); this.A = new Uint8Array(0); this.u = new Uint8Array(0); this.S = new Uint8Array(0); this.m1 = new Uint8Array(0); this.m2 = new Uint8Array(0); } get secretKey() { return this.S; } get publicKey() { return this.A; } get proof() { return this.m1; } isProofValid(m2) { return (0, util_1.SecureEqual)(this.m2, m2); } } exports.ClientChallenge = ClientChallenge; //# sourceMappingURL=client.js.map