@wault-pw/srp6a-webcrypto
Version:
Pure javascript implementation of SRP-6a (RFC-5054, RFC-2945) using web-crypto
156 lines • 6.61 kB
JavaScript
"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