blob: 5fdc6b6398c6be1a4eaeea7fb08c1be2e0df600c [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
/**
* @fileoverview An implementation of HKDF, RFC 5869.
*/
goog.module('tink.subtle.Hkdf');
const Hmac = goog.require('tink.subtle.Hmac');
const InvalidArgumentsException = goog.require('tink.exception.InvalidArgumentsException');
const Validators = goog.require('tink.subtle.Validators');
/**
* Computes an HKDF.
*
* @param {number} 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 {string} hash the name of the hash function. Accepted names are SHA-1,
* SHA-256 and SHA-512
* @param {!Uint8Array} ikm Input keying material.
* @param {!Uint8Array} info Context and application specific
* information (can be a zero-length array).
* @param {!Uint8Array=} opt_salt Salt value (a non-secret random
* value). If not provided, it is set to a string of hash length zeros.
* @return {!Promise.<!Uint8Array>} Output keying material (okm).
*/
const compute = async function(size, hash, ikm, info, opt_salt) {
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 Hmac.newInstance(hash, salt, digestSize);
const prk = await hmac.computeMac(ikm); // Pseudorandom Key
// Expand
hmac = await Hmac.newInstance(hash, prk, digestSize);
let ctr = 1;
let pos = 0;
let digest = new Uint8Array(0);
let result = new Uint8Array(size);
while (true) {
let 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;
};
exports = {
compute,
};