blob: 68602e73fb8ae8c35246735a0499304871ffa222 [file] [log] [blame]
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Aead} from '../aead/internal/aead';
import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
import {SecurityException} from '../exception/security_exception';
import * as Random from '../subtle/random';
import * as KeyManager from './key_manager';
import {KeysetReader} from './keyset_reader';
import {KeysetWriter} from './keyset_writer';
import * as PrimitiveSet from './primitive_set';
import {PbKeyMaterialType, PbKeyset, PbKeysetKey, PbKeyStatusType, PbKeyTemplate} from './proto';
import * as Registry from './registry';
import * as Util from './util';
/**
* Keyset handle provide abstracted access to Keysets, to limit the exposure of
* actual protocol buffers that hold sensitive key material.
*
* @final
*/
export class KeysetHandle {
private readonly keyset_: PbKeyset;
constructor(keyset: PbKeyset) {
Util.validateKeyset(keyset);
this.keyset_ = keyset;
}
/**
* Returns a primitive that uses key material from this keyset handle. If
* opt_customKeyManager is defined then the provided key manager is used to
* instantiate primitives. Otherwise key manager from Registry is used.
*/
async getPrimitive<P>(
primitiveType: Util.Constructor<P>,
opt_customKeyManager?: KeyManager.KeyManager<P>|null): Promise<P> {
if (!primitiveType) {
throw new InvalidArgumentsException('primitive type must be non-null');
}
const primitiveSet =
await this.getPrimitiveSet(primitiveType, opt_customKeyManager);
return Registry.wrap(primitiveSet);
}
/**
* Creates a set of primitives corresponding to the keys with status Enabled
* in the given keysetHandle, assuming all the correspoding key managers are
* present (keys with status different from Enabled are skipped). If provided
* uses customKeyManager instead of registered key managers for keys supported
* by the customKeyManager.
*
* Visible for testing.
*/
async getPrimitiveSet<P>(
primitiveType: Util.Constructor<P>,
opt_customKeyManager?: KeyManager.KeyManager<P>|
null): Promise<PrimitiveSet.PrimitiveSet<P>> {
const primitiveSet = new PrimitiveSet.PrimitiveSet<P>(primitiveType);
const keys = this.keyset_.getKeyList();
const keysLength = keys.length;
for (let i = 0; i < keysLength; i++) {
const key = keys[i];
if (key.getStatus() === PbKeyStatusType.ENABLED) {
const keyData = key.getKeyData();
if (!keyData) {
throw new SecurityException('Key data has to be non null.');
}
let primitive;
if (opt_customKeyManager &&
opt_customKeyManager.getKeyType() === keyData.getTypeUrl()) {
primitive =
await opt_customKeyManager.getPrimitive(primitiveType, keyData);
} else {
primitive = await Registry.getPrimitive<P>(primitiveType, keyData);
}
const entry = primitiveSet.addPrimitive(primitive, key);
if (key.getKeyId() === this.keyset_.getPrimaryKeyId()) {
primitiveSet.setPrimary(entry);
}
}
}
return primitiveSet;
}
/**
* Encrypts the underlying keyset with the provided masterKeyAead wnd writes
* the resulting encryptedKeyset to the given writer which must be non-null.
*
*
*/
async write(writer: KeysetWriter, masterKeyAead: Aead) {
// TODO implement
throw new SecurityException('KeysetHandle -- write: Not implemented yet.');
}
/**
* Returns the keyset held by this KeysetHandle.
*
*/
getKeyset(): PbKeyset {
return this.keyset_;
}
}
/**
* Creates a KeysetHandle from an encrypted keyset obtained via reader, using
* masterKeyAead to decrypt the keyset.
*
*
*/
export async function read(
reader: KeysetReader, masterKeyAead: Aead): Promise<KeysetHandle> {
// TODO implement
throw new SecurityException('KeysetHandle -- read: Not implemented yet.');
}
/**
* Returns a new KeysetHandle that contains a single new key generated
* according to keyTemplate.
*
*
*/
export async function generateNew(keyTemplate: PbKeyTemplate):
Promise<KeysetHandle> {
// TODO(thaidn): move this to a key manager.
const keyset = await generateNewKeyset_(keyTemplate);
return new KeysetHandle(keyset);
}
/**
* Generates a new Keyset that contains a single new key generated
* according to keyTemplate.
*
*/
async function generateNewKeyset_(keyTemplate: PbKeyTemplate):
Promise<PbKeyset> {
const key = (new PbKeysetKey())
.setStatus(PbKeyStatusType.ENABLED)
.setOutputPrefixType(keyTemplate.getOutputPrefixType());
const keyId = generateNewKeyId_();
key.setKeyId(keyId);
const keyData = await Registry.newKeyData(keyTemplate);
key.setKeyData(keyData);
const keyset = new PbKeyset();
keyset.addKey(key);
keyset.setPrimaryKeyId(keyId);
return keyset;
}
/**
* Generates a new random key ID.
*
* @return The key ID.
*/
function generateNewKeyId_(): number {
const bytes = Random.randBytes(4);
let value = 0;
for (let i = 0; i < bytes.length; i++) {
value += (bytes[i] & 255) << i * 8;
}
// Make sure the key ID is a positive integer smaller than 2^32.
return Math.abs(value) % 2 ** 32;
}
/**
* Creates a KeysetHandle from a keyset, obtained via reader, which
* must contain no secret key material.
*
* This can be used to load public keysets or envelope encryption keysets.
* Users that need to load cleartext keysets can use CleartextKeysetHandle.
*
*/
export function readNoSecret(reader: KeysetReader): KeysetHandle {
if (reader === null) {
throw new SecurityException('Reader has to be non-null.');
}
const keyset = reader.read();
const keyList = keyset.getKeyList();
for (const key of keyList) {
const keyData = key.getKeyData();
if (keyData) {
switch (keyData.getKeyMaterialType()) {
case PbKeyMaterialType.ASYMMETRIC_PUBLIC:
// fall through
case PbKeyMaterialType.REMOTE:
continue;
}
}
throw new SecurityException('Keyset contains secret key material.');
}
return new KeysetHandle(keyset);
}