| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| |
| /** |
| * @fileoverview Common enums. |
| */ |
| |
| import {InvalidArgumentsException} from '../exception/invalid_arguments_exception'; |
| |
| import * as Bytes from './bytes'; |
| |
| /** |
| * Supported elliptic curves. |
| */ |
| export enum CurveType { |
| P256 = 1, |
| P384, |
| P521 |
| } |
| |
| /** |
| * Supported point format. |
| */ |
| export enum PointFormatType { |
| UNCOMPRESSED = 1, |
| COMPRESSED, |
| |
| // Like UNCOMPRESSED but without the \x04 prefix. Crunchy uses this format. |
| // DO NOT USE unless you are a Crunchy user moving to Tink. |
| DO_NOT_USE_CRUNCHY_UNCOMPRESSED |
| } |
| |
| /** |
| * Supported ECDSA signature encoding. |
| */ |
| export enum EcdsaSignatureEncodingType { |
| |
| // The DER signature is encoded using ASN.1 |
| // (https://tools.ietf.org/html/rfc5480#appendix-A): |
| // ECDSA-Sig-Value :: = SEQUENCE { r INTEGER, s INTEGER }. In particular, the |
| // encoding is: |
| // 0x30 || totalLength || 0x02 || r's length || r || 0x02 || s's length || s. |
| DER = 1, |
| |
| // The IEEE_P1363 signature's format is r || s, where r and s are zero-padded |
| // and have the same size in bytes as the order of the curve. For example, for |
| // NIST P-256 curve, r and s are zero-padded to 32 bytes. |
| IEEE_P1363 |
| } |
| |
| /** |
| * Transform an ECDSA signature in DER encoding to IEEE P1363 encoding. |
| * |
| * @param der the ECDSA signature in DER encoding |
| * @param ieeeLength the length of the ECDSA signature in IEEE |
| * encoding. This is usually 2 * size of the elliptic curve field. |
| * @return ECDSA signature in IEEE encoding |
| */ |
| export function ecdsaDer2Ieee(der: Uint8Array, ieeeLength: number): Uint8Array { |
| if (!isValidDerEcdsaSignature(der)) { |
| throw new InvalidArgumentsException('invalid DER signature'); |
| } |
| if (!Number.isInteger(ieeeLength) || ieeeLength < 0) { |
| throw new InvalidArgumentsException( |
| 'ieeeLength must be a nonnegative integer'); |
| } |
| const ieee = new Uint8Array(ieeeLength); |
| const length = der[1] & 255; |
| let offset = 1 + |
| /* 0x30 */ |
| 1; |
| |
| /* totalLength */ |
| if (length >= 128) { |
| offset++; |
| } |
| |
| // Long form length |
| offset++; |
| |
| // 0x02 |
| const rLength = der[offset++]; |
| let extraZero = 0; |
| if (der[offset] === 0) { |
| extraZero = 1; |
| } |
| const rOffset = ieeeLength / 2 - rLength + extraZero; |
| ieee.set(der.subarray(offset + extraZero, offset + rLength), rOffset); |
| offset += rLength + |
| /* r byte array */ |
| 1; |
| |
| /* 0x02 */ |
| const sLength = der[offset++]; |
| extraZero = 0; |
| if (der[offset] === 0) { |
| extraZero = 1; |
| } |
| const sOffset = ieeeLength - sLength + extraZero; |
| ieee.set(der.subarray(offset + extraZero, offset + sLength), sOffset); |
| return ieee; |
| } |
| |
| /** |
| * Transform an ECDSA signature in IEEE 1363 encoding to DER encoding. |
| * |
| * @param ieee the ECDSA signature in IEEE encoding |
| * @return ECDSA signature in DER encoding |
| */ |
| export function ecdsaIeee2Der(ieee: Uint8Array): Uint8Array { |
| if (ieee.length % 2 != 0 || ieee.length == 0 || ieee.length > 132) { |
| throw new InvalidArgumentsException( |
| 'Invalid IEEE P1363 signature encoding. Length: ' + ieee.length); |
| } |
| const r = toUnsignedBigNum(ieee.subarray(0, ieee.length / 2)); |
| const s = toUnsignedBigNum(ieee.subarray(ieee.length / 2, ieee.length)); |
| let offset = 0; |
| const length = 1 + 1 + r.length + 1 + 1 + s.length; |
| let der; |
| if (length >= 128) { |
| der = new Uint8Array(length + 3); |
| der[offset++] = 48; |
| der[offset++] = 128 + 1; |
| der[offset++] = length; |
| } else { |
| der = new Uint8Array(length + 2); |
| der[offset++] = 48; |
| der[offset++] = length; |
| } |
| der[offset++] = 2; |
| der[offset++] = r.length; |
| der.set(r, offset); |
| offset += r.length; |
| der[offset++] = 2; |
| der[offset++] = s.length; |
| der.set(s, offset); |
| return der; |
| } |
| |
| /** |
| * Validate that the ECDSA signature is in DER encoding, based on |
| * https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki. |
| * |
| * @param sig an ECDSA siganture |
| */ |
| export function isValidDerEcdsaSignature(sig: Uint8Array): boolean { |
| // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] |
| // * total-length: 1-byte or 2-byte length descriptor of everything that |
| // follows. |
| // * R-length: 1-byte length descriptor of the R value that follows. |
| // * R: arbitrary-length big-endian encoded R value. It must use the shortest |
| // possible encoding for a positive integers (which means no null bytes at |
| // the start, except a single one when the next byte has its highest bit |
| // set). |
| // * S-length: 1-byte length descriptor of the S value that follows. |
| // * S: arbitrary-length big-endian encoded S value. The same rules apply. |
| /* S */ |
| if (sig.length < 1 + |
| /* 0x30 */ |
| 1 + |
| /* total-length */ |
| 1 + |
| /* 0x02 */ |
| 1 + |
| /* R-length */ |
| 1 + |
| /* R */ |
| 1 + |
| /* 0x02 */ |
| 1 + |
| /* S-length */ |
| 1) { |
| // Signature is too short. |
| return false; |
| } |
| |
| // Checking bytes from left to right. |
| |
| // byte #1: a signature is of type 0x30 (compound). |
| if (sig[0] != 48) { |
| return false; |
| } |
| |
| // byte #2 and maybe #3: the total length of the signature. |
| let totalLen = sig[1] & 255; |
| let totalLenLen = 1; |
| |
| // the length of the total length field, could be 2-byte. |
| if (totalLen == 129) { |
| // The signature is >= 128 bytes thus total length field is in long-form |
| // encoding and occupies 2 bytes. |
| totalLenLen = 2; |
| |
| // byte #3 is the total length. |
| totalLen = sig[2] & 255; |
| if (totalLen < 128) { |
| // Length in long-form encoding must be >= 128. |
| return false; |
| } |
| } else if (totalLen == 128 || totalLen > 129) { |
| // Impossible values for the second byte. |
| return false; |
| } |
| |
| |
| // Make sure the length covers the entire sig. |
| if (totalLen != sig.length - 1 - totalLenLen) { |
| return false; |
| } |
| |
| // Start checking R. |
| // Check whether the R element is an integer. |
| if (sig[1 + totalLenLen] != 2) { |
| return false; |
| } |
| |
| // Extract the length of the R element. |
| const rLen = sig[1 + |
| /* 0x30 */ |
| totalLenLen + 1] & |
| /* 0x02 */ |
| 255; |
| |
| // Make sure the length of the S element is still inside the signature. |
| if (1 + |
| /* 0x30 */ |
| totalLenLen + 1 + |
| /* 0x02 */ |
| 1 + |
| /* rLen */ |
| rLen + 1 >= |
| /* 0x02 */ |
| sig.length) { |
| return false; |
| } |
| |
| // Zero-length integers are not allowed for R. |
| if (rLen == 0) { |
| return false; |
| } |
| |
| // Negative numbers are not allowed for R. |
| if ((sig[3 + totalLenLen] & 255) >= 128) { |
| return false; |
| } |
| |
| // Null bytes at the start of R are not allowed, unless R would |
| // otherwise be interpreted as a negative number. |
| if (rLen > 1 && sig[3 + totalLenLen] == 0 && |
| (sig[4 + totalLenLen] & 255) < 128) { |
| return false; |
| } |
| |
| // Start checking S. |
| // Check whether the S element is an integer. |
| if (sig[3 + totalLenLen + rLen] != 2) { |
| return false; |
| } |
| |
| // Extract the length of the S element. |
| const sLen = sig[1 + |
| /* 0x30 */ |
| totalLenLen + 1 + |
| /* 0x02 */ |
| 1 + |
| /* rLen */ |
| rLen + 1] & |
| /* 0x02 */ |
| 255; |
| |
| // Verify that the length of the signature matches the sum of the length of |
| // the elements. |
| if (1 + |
| /* 0x30 */ |
| totalLenLen + 1 + |
| /* 0x02 */ |
| 1 + |
| /* rLen */ |
| rLen + 1 + |
| /* 0x02 */ |
| 1 + |
| /* sLen */ |
| sLen != |
| sig.length) { |
| return false; |
| } |
| |
| // Zero-length integers are not allowed for S. |
| if (sLen == 0) { |
| return false; |
| } |
| |
| // Negative numbers are not allowed for S. |
| if ((sig[5 + totalLenLen + rLen] & 255) >= 128) { |
| return false; |
| } |
| |
| // Null bytes at the start of S are not allowed, unless S would |
| // otherwise be interpreted as a negative number. |
| if (sLen > 1 && sig[5 + totalLenLen + rLen] == 0 && |
| (sig[6 + totalLenLen + rLen] & 255) < 128) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Transform a big integer in big endian to minimal unsigned form which has |
| * no extra zero at the beginning except when the highest bit is set. |
| * |
| */ |
| function toUnsignedBigNum(bytes: Uint8Array): Uint8Array { |
| // Remove zero prefixes. |
| let start = 0; |
| while (start < bytes.length && bytes[start] == 0) { |
| start++; |
| } |
| if (start == bytes.length) { |
| start = bytes.length - 1; |
| } |
| let extraZero = 0; |
| |
| // If the 1st bit is not zero, add 1 zero byte. |
| if ((bytes[start] & 128) == 128) { |
| // Add extra zero. |
| extraZero = 1; |
| } |
| const res = new Uint8Array(bytes.length - start + extraZero); |
| res.set(bytes.subarray(start), extraZero); |
| return res; |
| } |
| |
| export function curveToString(curve: CurveType): string { |
| switch (curve) { |
| case CurveType.P256: |
| return 'P-256'; |
| case CurveType.P384: |
| return 'P-384'; |
| case CurveType.P521: |
| return 'P-521'; |
| } |
| } |
| |
| export function curveFromString(curve: string): CurveType { |
| switch (curve) { |
| case 'P-256': |
| return CurveType.P256; |
| case 'P-384': |
| return CurveType.P384; |
| case 'P-521': |
| return CurveType.P521; |
| } |
| throw new InvalidArgumentsException('unknown curve: ' + curve); |
| } |
| |
| /** Helper method for unit tests. */ |
| export function formatFromString(format: string): PointFormatType { |
| switch (format) { |
| case 'UNCOMPRESSED': |
| return PointFormatType.UNCOMPRESSED; |
| case 'DO_NOT_USE_CRUNCHY_UNCOMPRESSED': |
| return PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED; |
| case 'COMPRESSED': |
| return PointFormatType.COMPRESSED; |
| default: |
| throw new InvalidArgumentsException('unknown format: ' + format); |
| } |
| } |
| |
| export function pointEncode( |
| curve: string, format: PointFormatType, point: JsonWebKey): Uint8Array { |
| const fieldSize = fieldSizeInBytes(curveFromString(curve)); |
| switch (format) { |
| case PointFormatType.UNCOMPRESSED: { |
| const {x, y} = point; |
| if (x === undefined) { |
| throw new InvalidArgumentsException('x must be provided'); |
| } |
| if (y === undefined) { |
| throw new InvalidArgumentsException('y must be provided'); |
| } |
| const result = new Uint8Array(1 + 2 * fieldSize); |
| result[0] = 4; |
| result.set( |
| /* opt_webSafe = */ |
| Bytes.fromBase64(x, true), 1); |
| result.set( |
| /* opt_webSafe = */ |
| Bytes.fromBase64(y, true), 1 + fieldSize); |
| return result; |
| } |
| case PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED: { |
| const {x, y} = point; |
| if (x === undefined) { |
| throw new InvalidArgumentsException('x must be provided'); |
| } |
| if (y === undefined) { |
| throw new InvalidArgumentsException('y must be provided'); |
| } |
| let decodedX = Bytes.fromBase64(x, /* opt_webSafe = */ true); |
| let decodedY = Bytes.fromBase64(y, /* opt_webSafe = */ true); |
| if (decodedX.length > fieldSize) { |
| // x has leading 0's, strip them. |
| decodedX = decodedX.slice(decodedX.length - fieldSize, decodedX.length); |
| } |
| if (decodedY.length > fieldSize) { |
| // y has leading 0's, strip them. |
| decodedY = decodedY.slice(decodedY.length - fieldSize, decodedY.length); |
| } |
| const result = new Uint8Array(2 * fieldSize); |
| result.set(decodedX, 0); |
| result.set(decodedY, fieldSize); |
| return result; |
| } |
| case PointFormatType.COMPRESSED: { |
| const {x, y} = point; |
| if (x === undefined) { |
| throw new InvalidArgumentsException('x must be provided'); |
| } |
| if (y === undefined) { |
| throw new InvalidArgumentsException('y must be provided'); |
| } |
| let decodedX = Bytes.fromBase64(x, /* opt_webSafe = */ true); |
| let decodedY = Bytes.fromBase64(y, /* opt_webSafe = */ true); |
| if (decodedX.length > fieldSize) { |
| // x has leading 0's, strip them. |
| decodedX = decodedX.slice(decodedX.length - fieldSize, decodedX.length); |
| } |
| if (decodedY.length > fieldSize) { |
| // y has leading 0's, strip them. |
| decodedY = decodedY.slice(decodedY.length - fieldSize, decodedY.length); |
| } |
| const result = new Uint8Array(1 + fieldSize); |
| result.set(decodedX, /* offset = */ 1 + fieldSize - decodedX.length); |
| result[0] = testBit(byteArrayToInteger(decodedY), 0) ? 3 : 2; |
| return result; |
| } |
| default: |
| throw new InvalidArgumentsException('invalid format'); |
| } |
| } |
| |
| function getModulus(curve: CurveType): bigint { |
| // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf (Appendix D). |
| switch (curve) { |
| case CurveType.P256: |
| return BigInt( |
| '115792089210356248762697446949407573530086143415290314195533631308' + |
| '867097853951'); |
| case CurveType.P384: |
| return BigInt( |
| '394020061963944792122790401001436138050797392704654466679482934042' + |
| '45721771496870329047266088258938001861606973112319'); |
| case CurveType.P521: |
| return BigInt( |
| '686479766013060971498190079908139321726943530014330540939446345918' + |
| '55431833976560521225596406614545549772963113914808580371219879' + |
| '99716643812574028291115057151'); |
| default: |
| throw new InvalidArgumentsException('invalid curve'); |
| } |
| } |
| |
| function getB(curve: CurveType): bigint { |
| // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf (Appendix D). |
| switch (curve) { |
| case CurveType.P256: |
| return BigInt( |
| '0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'); |
| case CurveType.P384: |
| return BigInt( |
| '0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875a' + |
| 'c656398d8a2ed19d2a85c8edd3ec2aef'); |
| case CurveType.P521: |
| return BigInt( |
| '0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef10' + |
| '9e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b5' + |
| '03f00'); |
| default: |
| throw new InvalidArgumentsException('invalid curve'); |
| } |
| } |
| |
| /** Converts byte array to bigint. */ |
| export function byteArrayToInteger(bytes: Uint8Array): bigint { |
| return BigInt('0x' + Bytes.toHex(bytes)); |
| } |
| |
| /** Converts bigint to byte array. */ |
| export function integerToByteArray(i: bigint): Uint8Array { |
| let input = i.toString(16); |
| // If necessary, prepend leading zero to ensure that input length is even. |
| input = input.length % 2 === 0 ? input : '0' + input; |
| return Bytes.fromHex(input); |
| } |
| |
| /** Returns true iff the ith bit (in lsb order) of n is set. */ |
| function testBit(n: bigint, i: number): boolean { |
| 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: bigint, exp: bigint, p: bigint): bigint { |
| 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: bigint, p: bigint): bigint { |
| if (p <= BigInt(0)) { |
| throw new InvalidArgumentsException('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 InvalidArgumentsException( |
| 'could not find a modular square root'); |
| } |
| return squareRoot; |
| } |
| // Skipping other elliptic curve types that require Cipolla's algorithm. |
| throw new InvalidArgumentsException('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. |
| * |
| * @param x x-coordinate |
| * @param lsb least significant bit of the y-coordinate |
| * @param curve NIST curve P-256, P-384, or P-521 |
| * @return y-coordinate |
| */ |
| function getY(x: bigint, lsb: boolean, curve: string): bigint { |
| const p = getModulus(curveFromString(curve)); |
| const a = p - BigInt(3); |
| const b = getB(curveFromString(curve)); |
| const rhs = (((x * x) + a) * x + b) % p; |
| let y = modSqrt(rhs, p); |
| if (lsb !== testBit(y, 0)) { |
| y = (p - y) % p; |
| } |
| return y; |
| } |
| |
| export function pointDecode( |
| curve: string, format: PointFormatType, point: Uint8Array): JsonWebKey { |
| const fieldSize = fieldSizeInBytes(curveFromString(curve)); |
| switch (format) { |
| case PointFormatType.UNCOMPRESSED: { |
| if (point.length !== 1 + 2 * fieldSize || point[0] !== 4) { |
| throw new InvalidArgumentsException('invalid point'); |
| } |
| const result = ({ |
| 'kty': 'EC', |
| 'crv': curve, |
| 'x': Bytes.toBase64( |
| new Uint8Array(point.subarray(1, 1 + fieldSize)), |
| /* websafe */ |
| true), |
| 'y': Bytes.toBase64( |
| new Uint8Array(point.subarray(1 + fieldSize, point.length)), |
| /* websafe */ |
| true), |
| 'ext': true |
| } as JsonWebKey); |
| return result; |
| } |
| case PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED: { |
| if (point.length !== 2 * fieldSize) { |
| throw new InvalidArgumentsException('invalid point'); |
| } |
| const result = ({ |
| 'kty': 'EC', |
| 'crv': curve, |
| 'x': Bytes.toBase64( |
| new Uint8Array(point.subarray(0, fieldSize)), /* websafe */ true), |
| 'y': Bytes.toBase64( |
| new Uint8Array(point.subarray(fieldSize, point.length)), |
| /* websafe */ true), |
| 'ext': true |
| } as JsonWebKey); |
| return result; |
| } |
| case PointFormatType.COMPRESSED: { |
| if (point.length !== 1 + fieldSize) { |
| throw new InvalidArgumentsException( |
| 'compressed point has wrong length'); |
| } |
| if (point[0] !== 2 && point[0] !== 3) { |
| throw new InvalidArgumentsException('invalid format'); |
| } |
| 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(curveFromString(curve)); |
| if (x < BigInt(0) || x >= p) { |
| throw new InvalidArgumentsException('x is out of range'); |
| } |
| const y = getY(x, lsb, curve); |
| const result : JsonWebKey = { |
| 'kty': 'EC', |
| 'crv': curve, |
| 'x': Bytes.toBase64(integerToByteArray(x), /* websafe */ true), |
| 'y': Bytes.toBase64(integerToByteArray(y), /* websafe */ true), |
| 'ext': true |
| }; |
| return result; |
| } |
| default: |
| throw new InvalidArgumentsException('invalid format'); |
| } |
| } |
| |
| export function getJsonWebKey( |
| curve: CurveType, x: Uint8Array, y: Uint8Array, |
| d?: Uint8Array|null): JsonWebKey { |
| const key = ({ |
| 'kty': 'EC', |
| 'crv': curveToString(curve), |
| 'x': Bytes.toBase64( |
| x, |
| /* websafe */ |
| true), |
| 'y': Bytes.toBase64( |
| y, |
| /* websafe */ |
| true), |
| 'ext': true |
| } as JsonWebKey); |
| if (d) { |
| key['d'] = Bytes.toBase64( |
| d, |
| /* websafe */ |
| true); |
| } |
| return key; |
| } |
| |
| export function fieldSizeInBytes(curve: CurveType): number { |
| switch (curve) { |
| case CurveType.P256: |
| return 32; |
| case CurveType.P384: |
| return 48; |
| case CurveType.P521: |
| return 66; |
| } |
| } |
| |
| export function encodingSizeInBytes( |
| curve: CurveType, pointFormat: PointFormatType): number { |
| switch (pointFormat) { |
| case PointFormatType.UNCOMPRESSED: |
| return 2 * fieldSizeInBytes(curve) + 1; |
| case PointFormatType.COMPRESSED: |
| return fieldSizeInBytes(curve) + 1; |
| case PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED: |
| return 2 * fieldSizeInBytes(curve); |
| } |
| } |
| |
| export async function computeEcdhSharedSecret( |
| privateKey: CryptoKey, publicKey: CryptoKey): Promise<Uint8Array> { |
| const {namedCurve}: Partial<EcKeyImportParams> = privateKey.algorithm; |
| if (!namedCurve) { |
| throw new InvalidArgumentsException('namedCurve must be provided'); |
| } |
| const ecdhParams = {'public': publicKey, ...privateKey.algorithm}; |
| const fieldSizeInBits = 8 * fieldSizeInBytes(curveFromString(namedCurve)); |
| const sharedSecret = await window.crypto.subtle.deriveBits( |
| ecdhParams, privateKey, fieldSizeInBits); |
| return new Uint8Array(sharedSecret); |
| } |
| |
| export async function generateKeyPair( |
| algorithm: 'ECDH'|'ECDSA', curve: string): Promise<CryptoKeyPair> { |
| if (algorithm != 'ECDH' && algorithm != 'ECDSA') { |
| throw new InvalidArgumentsException( |
| 'algorithm must be either ECDH or ECDSA'); |
| } |
| const params = {'name': algorithm, 'namedCurve': curve}; |
| const ephemeralKeyPair = await window.crypto.subtle.generateKey( |
| params, /* extractable= */ true, |
| algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] : ['sign', 'verify']); |
| return ephemeralKeyPair as CryptoKeyPair; |
| } |
| |
| export async function exportCryptoKey(cryptoKey: CryptoKey): |
| Promise<JsonWebKey> { |
| const jwk = await window.crypto.subtle.exportKey('jwk', cryptoKey); |
| return (jwk); |
| } |
| |
| export async function importPublicKey( |
| algorithm: string, jwk: JsonWebKey): Promise<CryptoKey> { |
| if (algorithm != 'ECDH' && algorithm != 'ECDSA') { |
| throw new InvalidArgumentsException( |
| 'algorithm must be either ECDH or ECDSA'); |
| } |
| const {crv} = jwk; |
| if (!crv) { |
| throw new InvalidArgumentsException('crv must be provided'); |
| } |
| const publicKey = await window.crypto.subtle.importKey( |
| /* format */ |
| 'jwk', jwk, {'name': algorithm, 'namedCurve': crv}, |
| /* algorithm */ |
| true, |
| /* extractable */ |
| algorithm == 'ECDH' ? [] : ['verify']); |
| |
| /* usage */ |
| return publicKey; |
| } |
| |
| export async function importPrivateKey( |
| algorithm: string, jwk: JsonWebKey): Promise<CryptoKey> { |
| if (algorithm != 'ECDH' && algorithm != 'ECDSA') { |
| throw new InvalidArgumentsException( |
| 'algorithm must be either ECDH or ECDSA'); |
| } |
| const {crv} = jwk; |
| if (!crv) { |
| throw new InvalidArgumentsException('crv must be provided'); |
| } |
| const privateKey = await window.crypto.subtle.importKey( |
| /* format */ |
| 'jwk', jwk, |
| /* key material */ |
| {'name': algorithm, 'namedCurve': crv}, |
| /* algorithm */ |
| true, |
| /* extractable */ |
| algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] : ['sign']); |
| |
| /* usage */ |
| return privateKey; |
| } |