/** * RFC5054 SRP group parameters. * These must match the server-side implementation exactly. */ import { SRPGroup } from './types'; /** * Get the SRP group parameters for the specified group ID. * * @param groupID - Group ID (3, 4, or 5) * @returns SRP group parameters * @throws Error if group ID is invalid * * @risk Spoofing: Weak group parameters allow offline attacks. * Always use RFC5054 standard groups. */ export function getGroup(groupID: number): SRPGroup { switch (groupID) { case 3: // RFC5054 2048-bit group (recommended minimum). return { N: BigInt( '0x' + 'AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050' + 'A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50' + 'E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B8' + '55F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773B' + 'CA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748' + '544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6' + 'AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6' + '94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73' ), g: BigInt(2), bitLength: 2048 } case 4: //RFC5054 3072-bit group (stronger security, higher CPU cost). return { N: BigInt( '0x' + 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF' ), g: BigInt(5), bitLength: 3072 } case 5: // RFC5054 4096-bit group (strongest security, significant CPU cost). return { N: BigInt( '0x' + 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF' ), g: BigInt(5), bitLength: 4096 } default: throw new Error(`Invalid group ID: ${groupID} (valid: 3, 4, 5)`); } } /** * Compute the SRP multiplier parameter k = H(N | g). * * @param group - SRP group parameters * @returns Multiplier k * * @mitigation Tampering: Correct k computation per RFC5054/SRP-6a. */ export async function computeK(group: SRPGroup): Promise { // k = H(N | PAD(g)) const NBytes = bigIntToBytes(group.N); const gBytes = bigIntToBytes(group.g); // Pad g to the same length as N const paddedG = new Uint8Array(NBytes.length); paddedG.set(gBytes, paddedG.length - gBytes.length); // Concatenate N and paddedG const combined = new Uint8Array(NBytes.length + paddedG.length); combined.set(NBytes, 0); combined.set(paddedG, NBytes.length); // Hash with SHA-256 const hashBuffer = await crypto.subtle.digest('SHA-256', combined); const hashBytes = new Uint8Array(hashBuffer); return bytesToBigInt(hashBytes); } /** * Convert a bigint to a Uint8Array (big-endian). */ export function bigIntToBytes(value: bigint): Uint8Array { const hex = value.toString(16); const paddedHex = hex.length % 2 === 0 ? hex : '0' + hex; const bytes = new Uint8Array(paddedHex.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(paddedHex.substr(i * 2, 2), 16); } return bytes; } /** * Convert a Uint8Array to a bigint (big-endian). */ export function bytesToBigInt(bytes: Uint8Array): bigint { let hex = ''; for (let i = 0; i < bytes.length; i++) { const byte = bytes[i].toString(16).padStart(2, '0'); hex += byte; } return BigInt('0x' + hex); } /** * Pad bytes to a specific length with leading zeros. */ export function padBytes(bytes: Uint8Array, length: number): Uint8Array { if (bytes.length >= length) { return bytes; } const padded = new Uint8Array(length); padded.set(bytes, length - bytes.length); return padded; }