UNPKG

@turnkey/api-key-stamper

Version:

API key stamper for @turnkey/http

211 lines (207 loc) 6.98 kB
'use strict'; var bytes = require('./bytes.js'); /** * Code modified from https://github.com/google/tink/blob/6f74b99a2bfe6677e3670799116a57268fd067fa/javascript/subtle/elliptic_curves.ts * - The implementation of integerToByteArray has been modified to augment the resulting byte array to a certain length. * - The implementation of PointDecode has been modified to decode both compressed and uncompressed points by checking for correct format * - Method isP256CurvePoint added to check whether an uncompressed point is valid * * @license * Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * P-256 only */ function getModulus() { // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf (Appendix D). return BigInt("115792089210356248762697446949407573530086143415290314195533631308" + "867097853951"); } /** * P-256 only */ function getB() { // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf (Appendix D). return BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); } /** Converts byte array to bigint. */ function byteArrayToInteger(bytes$1) { return BigInt("0x" + bytes.toHex(bytes$1)); } /** Converts bigint to byte array. */ function integerToByteArray(i, length) { const input = i.toString(16); const numHexChars = length * 2; let padding = ""; if (numHexChars < input.length) { throw new Error(`cannot pack integer with ${input.length} hex chars into ${length} bytes`); } else { padding = "0".repeat(numHexChars - input.length); } return bytes.fromHex(padding + input); } /** Returns true iff the ith bit (in lsb order) of n is set. */ function testBit(n, i) { const m = BigInt(1) << BigInt(i); return (n & m) !== BigInt(0); } /** * Computes a modular exponent. Since JavaScript BigInt operations are not * constant-time, information about the inputs could leak. Therefore, THIS * METHOD SHOULD ONLY BE USED FOR POINT DECOMPRESSION. * * @param b base * @param exp exponent * @param p modulus * @return b^exp modulo p */ function modPow(b, exp, p) { if (exp === BigInt(0)) { return BigInt(1); } let result = b; const exponentBitString = exp.toString(2); for (let i = 1; i < exponentBitString.length; ++i) { result = (result * result) % p; if (exponentBitString[i] === "1") { result = (result * b) % p; } } return result; } /** * Computes a square root modulo an odd prime. Since timing and exceptions can * leak information about the inputs, THIS METHOD SHOULD ONLY BE USED FOR * POINT DECOMPRESSION. * * @param x square * @param p prime modulus * @return square root of x modulo p */ function modSqrt(x, p) { if (p <= BigInt(0)) { throw new Error("p must be positive"); } const base = x % p; // The currently supported NIST curves P-256, P-384, and P-521 all satisfy // p % 4 == 3. However, although currently a no-op, the following check // should be left in place in case other curves are supported in the future. if (testBit(p, 0) && /* istanbul ignore next */ testBit(p, 1)) { // Case p % 4 == 3 (applies to NIST curves P-256, P-384, and P-521) // q = (p + 1) / 4 const q = (p + BigInt(1)) >> BigInt(2); const squareRoot = modPow(base, q, p); if ((squareRoot * squareRoot) % p !== base) { throw new Error("could not find a modular square root"); } return squareRoot; } // Skipping other elliptic curve types that require Cipolla's algorithm. throw new Error("unsupported modulus value"); } /** * Computes the y-coordinate of a point on an elliptic curve given its * x-coordinate. Since timing and exceptions can leak information about the * inputs, THIS METHOD SHOULD ONLY BE USED FOR POINT DECOMPRESSION. * * P-256 only * * @param x x-coordinate * @param lsb least significant bit of the y-coordinate * @return y-coordinate */ function getY(x, lsb) { const p = getModulus(); const a = p - BigInt(3); const b = getB(); const rhs = ((x * x + a) * x + b) % p; let y = modSqrt(rhs, p); if (lsb !== testBit(y, 0)) { y = (p - y) % p; } return y; } /** * * Given x and y coordinates of a JWK, checks whether these are valid points on * the P-256 elliptic curve. * * P-256 only * * @param x x-coordinate * @param y y-coordinate * @return boolean validity */ function isP256CurvePoint(x, y) { const p = getModulus(); const a = p - BigInt(3); const b = getB(); const rhs = ((x * x + a) * x + b) % p; const lhs = y ** BigInt(2) % p; return lhs === rhs; } /** * Decodes a public key in _compressed_ OR _uncompressed_ format. * Augmented to ensure that the x and y components are padded to fit 32 bytes. * * P-256 only */ function pointDecode(point) { const fieldSize = fieldSizeInBytes(); const compressedLength = fieldSize + 1; const uncompressedLength = 2 * fieldSize + 1; if (point.length !== compressedLength && point.length !== uncompressedLength) { throw new Error("Invalid length: point is not in compressed or uncompressed format"); } // Decodes point if its length and first bit match the compressed format if ((point[0] === 2 || point[0] === 3) && point.length == compressedLength) { const lsb = point[0] === 3; // point[0] must be 2 (false) or 3 (true). const x = byteArrayToInteger(point.subarray(1, point.length)); const p = getModulus(); if (x < BigInt(0) || x >= p) { throw new Error("x is out of range"); } const y = getY(x, lsb); const result = { kty: "EC", crv: "P-256", x: bytes.toBase64(integerToByteArray(x, 32)), y: bytes.toBase64(integerToByteArray(y, 32)), ext: true, }; return result; // Decodes point if its length and first bit match the uncompressed format } else if (point[0] === 4 && point.length == uncompressedLength) { const x = byteArrayToInteger(point.subarray(1, fieldSize + 1)); const y = byteArrayToInteger(point.subarray(fieldSize + 1, 2 * fieldSize + 1)); const p = getModulus(); if (x < BigInt(0) || x >= p || y < BigInt(0) || y >= p || !isP256CurvePoint(x, y)) { throw new Error("invalid uncompressed x and y coordinates"); } const result = { kty: "EC", crv: "P-256", x: bytes.toBase64(integerToByteArray(x, 32)), y: bytes.toBase64(integerToByteArray(y, 32)), ext: true, }; return result; } throw new Error("invalid format"); } /** * P-256 only */ function fieldSizeInBytes() { return 32; } exports.pointDecode = pointDecode; //# sourceMappingURL=elliptic_curves.js.map