| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| |
| /** |
| * @fileoverview An implementation of HKDF, RFC 5869. |
| */ |
| import {InvalidArgumentsException} from '../exception/invalid_arguments_exception'; |
| |
| import {fromRawKey as hmacFromRawKey} from './hmac'; |
| import * as Validators from './validators'; |
| |
| /** |
| * Computes an HKDF. |
| * |
| * @param size The length of the generated pseudorandom string in |
| * bytes. The maximal size is 255 * DigestSize, where DigestSize is the size |
| * of the underlying HMAC. |
| * @param hash the name of the hash function. Accepted names are SHA-1, |
| * SHA-256 and SHA-512 |
| * @param ikm Input keying material. |
| * @param info Context and application specific |
| * information (can be a zero-length array). |
| * @param opt_salt Salt value (a non-secret random |
| * value). If not provided, it is set to a string of hash length zeros. |
| * @return Output keying material (okm). |
| */ |
| export async function compute( |
| size: number, hash: string, ikm: Uint8Array, info: Uint8Array, |
| opt_salt?: Uint8Array): Promise<Uint8Array> { |
| let digestSize; |
| if (!Number.isInteger(size)) { |
| throw new InvalidArgumentsException('size must be an integer'); |
| } |
| if (size <= 0) { |
| throw new InvalidArgumentsException('size must be positive'); |
| } |
| switch (hash) { |
| case 'SHA-1': |
| digestSize = 20; |
| if (size > 255 * 20) { |
| throw new InvalidArgumentsException('size too large'); |
| } |
| break; |
| case 'SHA-256': |
| digestSize = 32; |
| if (size > 255 * 32) { |
| throw new InvalidArgumentsException('size too large'); |
| } |
| break; |
| case 'SHA-512': |
| digestSize = 64; |
| if (size > 255 * 64) { |
| throw new InvalidArgumentsException('size too large'); |
| } |
| break; |
| default: |
| throw new InvalidArgumentsException(hash + ' is not supported'); |
| } |
| Validators.requireUint8Array(ikm); |
| Validators.requireUint8Array(info); |
| let salt = opt_salt; |
| if (opt_salt == null || salt === undefined || salt.length == 0) { |
| salt = new Uint8Array(digestSize); |
| } |
| Validators.requireUint8Array(salt); |
| |
| // Extract. |
| let hmac = await hmacFromRawKey(hash, salt, digestSize); |
| const prk = await hmac.computeMac( |
| // Pseudorandom Key |
| ikm); |
| |
| // Expand |
| hmac = await hmacFromRawKey(hash, prk, digestSize); |
| let ctr = 1; |
| let pos = 0; |
| let digest = new Uint8Array(0); |
| const result = new Uint8Array(size); |
| while (true) { |
| const input = new Uint8Array(digest.length + info.length + 1); |
| input.set(digest, 0); |
| input.set(info, digest.length); |
| input[input.length - 1] = ctr; |
| digest = await hmac.computeMac(input); |
| if (pos + digest.length < size) { |
| result.set(digest, pos); |
| pos += digest.length; |
| ctr++; |
| } else { |
| result.set(digest.subarray(0, size - pos), pos); |
| break; |
| } |
| } |
| return result; |
| } |