blob: 015ac8332b749a1a0a2e2b65fc79c72d16d119ab [file] [log] [blame]
taymonf6a87a32020-08-07 00:16:40 -07001/**
2 * @license
3 * Copyright 2020 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
taymon5baa7912020-07-13 07:37:04 -07007import {Aead} from '../aead/internal/aead';
8import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
9import {SecurityException} from '../exception/security_exception';
10import * as Random from '../subtle/random';
11
12import * as KeyManager from './key_manager';
13import {KeysetReader} from './keyset_reader';
14import {KeysetWriter} from './keyset_writer';
15import * as PrimitiveSet from './primitive_set';
Tink Team01a66022021-10-12 07:11:11 -070016import {PbKeyData, PbKeyMaterialType, PbKeyset, PbKeysetKey, PbKeyStatusType, PbKeyTemplate} from './proto';
taymon5baa7912020-07-13 07:37:04 -070017import * as Registry from './registry';
18import * as Util from './util';
19
20/**
21 * Keyset handle provide abstracted access to Keysets, to limit the exposure of
22 * actual protocol buffers that hold sensitive key material.
23 *
24 * @final
25 */
26export class KeysetHandle {
27 private readonly keyset_: PbKeyset;
28
29 constructor(keyset: PbKeyset) {
30 Util.validateKeyset(keyset);
31 this.keyset_ = keyset;
32 }
33
34 /**
35 * Returns a primitive that uses key material from this keyset handle. If
36 * opt_customKeyManager is defined then the provided key manager is used to
37 * instantiate primitives. Otherwise key manager from Registry is used.
38 */
39 async getPrimitive<P>(
40 primitiveType: Util.Constructor<P>,
41 opt_customKeyManager?: KeyManager.KeyManager<P>|null): Promise<P> {
42 if (!primitiveType) {
43 throw new InvalidArgumentsException('primitive type must be non-null');
44 }
45 const primitiveSet =
46 await this.getPrimitiveSet(primitiveType, opt_customKeyManager);
47 return Registry.wrap(primitiveSet);
48 }
49
50 /**
51 * Creates a set of primitives corresponding to the keys with status Enabled
52 * in the given keysetHandle, assuming all the correspoding key managers are
53 * present (keys with status different from Enabled are skipped). If provided
54 * uses customKeyManager instead of registered key managers for keys supported
55 * by the customKeyManager.
56 *
57 * Visible for testing.
58 */
59 async getPrimitiveSet<P>(
60 primitiveType: Util.Constructor<P>,
61 opt_customKeyManager?: KeyManager.KeyManager<P>|
62 null): Promise<PrimitiveSet.PrimitiveSet<P>> {
63 const primitiveSet = new PrimitiveSet.PrimitiveSet<P>(primitiveType);
64 const keys = this.keyset_.getKeyList();
65 const keysLength = keys.length;
66 for (let i = 0; i < keysLength; i++) {
67 const key = keys[i];
68 if (key.getStatus() === PbKeyStatusType.ENABLED) {
69 const keyData = key.getKeyData();
70 if (!keyData) {
71 throw new SecurityException('Key data has to be non null.');
72 }
73 let primitive;
74 if (opt_customKeyManager &&
75 opt_customKeyManager.getKeyType() === keyData.getTypeUrl()) {
76 primitive =
77 await opt_customKeyManager.getPrimitive(primitiveType, keyData);
78 } else {
79 primitive = await Registry.getPrimitive<P>(primitiveType, keyData);
80 }
81 const entry = primitiveSet.addPrimitive(primitive, key);
82 if (key.getKeyId() === this.keyset_.getPrimaryKeyId()) {
83 primitiveSet.setPrimary(entry);
84 }
85 }
86 }
87 return primitiveSet;
88 }
89
90 /**
91 * Encrypts the underlying keyset with the provided masterKeyAead wnd writes
92 * the resulting encryptedKeyset to the given writer which must be non-null.
93 *
94 *
95 */
96 async write(writer: KeysetWriter, masterKeyAead: Aead) {
97 // TODO implement
98 throw new SecurityException('KeysetHandle -- write: Not implemented yet.');
99 }
100
101 /**
Tink Team01a66022021-10-12 07:11:11 -0700102 * Writes this keyset using `writer` if and only if the keyset doesn't contain
103 * any secret key material.
104 *
105 * This can be used to persist public keysets or envelope encryption keysets.
106 * Use `CleartextKeysetHandle` to persist keysets containing secret key
107 * material.
108 */
109 writeNoSecret(writer: KeysetWriter): Uint8Array {
110 assertNoSecretKeyMaterial(this.keyset_);
Tink Team7f64e0e2021-12-21 07:45:23 -0800111 return writer.encodeBinary(this.keyset_);
Tink Team01a66022021-10-12 07:11:11 -0700112 }
113
114 /**
taymon5baa7912020-07-13 07:37:04 -0700115 * Returns the keyset held by this KeysetHandle.
116 *
117 */
118 getKeyset(): PbKeyset {
119 return this.keyset_;
120 }
Tink Team01a66022021-10-12 07:11:11 -0700121
122 /**
123 * If the managed keyset contains private keys, returns a `KeysetHandle` of
124 * the public keys.
125 */
126 getPublicKeysetHandle(): KeysetHandle {
127 const publicKeyset = new PbKeyset();
128 for (const key of this.keyset_.getKeyList()) {
Tink Teama38dbf82022-06-02 14:38:29 -0700129 publicKeyset.addKey(key.clone().setKeyData(createPublicKeyData(
130 nonNull('Key data', key.getKeyData()))));
Tink Team01a66022021-10-12 07:11:11 -0700131 }
132 publicKeyset.setPrimaryKeyId(this.keyset_.getPrimaryKeyId());
133 return new KeysetHandle(publicKeyset);
134 }
135}
136
Tink Teama38dbf82022-06-02 14:38:29 -0700137function nonNull<T>(desc: string, value: T|null|undefined): T {
138 if (value == null) {
Tink Team01a66022021-10-12 07:11:11 -0700139 throw new SecurityException(`${desc} has to be non null.`);
140 }
141 return value;
142}
143
144function createPublicKeyData(privateKeyData: PbKeyData): PbKeyData {
145 if (privateKeyData.getKeyMaterialType() !==
146 PbKeyData.KeyMaterialType.ASYMMETRIC_PRIVATE) {
147 throw new SecurityException('The keyset contains a non-private key');
148 }
149 return Registry.getPublicKeyData(
150 privateKeyData.getTypeUrl(), privateKeyData.getValue_asU8());
151}
152
153/**
154 * Validates that `keyset` doesn't contain any secret key material.
155 *
156 * @throws SecurityException if `keyset` contains secret key material.
157 */
158function assertNoSecretKeyMaterial(keyset: PbKeyset) {
159 for (const key of keyset.getKeyList()) {
160 const keyData = nonNull('Key data', key.getKeyData());
161 if (isSecretKeyMaterialType(keyData.getKeyMaterialType())) {
162 throw new SecurityException('Keyset contains secret key material.');
163 }
164 }
165}
166
167/** Returns true if the key material type is secret. */
168function isSecretKeyMaterialType(type: PbKeyMaterialType) {
169 return type === PbKeyMaterialType.UNKNOWN_KEYMATERIAL ||
170 type === PbKeyMaterialType.SYMMETRIC ||
171 type === PbKeyMaterialType.ASYMMETRIC_PRIVATE;
taymon5baa7912020-07-13 07:37:04 -0700172}
173
174/**
175 * Creates a KeysetHandle from an encrypted keyset obtained via reader, using
176 * masterKeyAead to decrypt the keyset.
177 *
178 *
179 */
180export async function read(
181 reader: KeysetReader, masterKeyAead: Aead): Promise<KeysetHandle> {
182 // TODO implement
183 throw new SecurityException('KeysetHandle -- read: Not implemented yet.');
184}
185
186/**
187 * Returns a new KeysetHandle that contains a single new key generated
188 * according to keyTemplate.
189 *
190 *
191 */
192export async function generateNew(keyTemplate: PbKeyTemplate):
193 Promise<KeysetHandle> {
194 // TODO(thaidn): move this to a key manager.
195 const keyset = await generateNewKeyset_(keyTemplate);
196 return new KeysetHandle(keyset);
197}
198
199/**
200 * Generates a new Keyset that contains a single new key generated
201 * according to keyTemplate.
202 *
203 */
204async function generateNewKeyset_(keyTemplate: PbKeyTemplate):
205 Promise<PbKeyset> {
taymonaa14afc2020-08-24 05:35:00 -0700206 const key = (new PbKeysetKey())
taymon5baa7912020-07-13 07:37:04 -0700207 .setStatus(PbKeyStatusType.ENABLED)
208 .setOutputPrefixType(keyTemplate.getOutputPrefixType());
209 const keyId = generateNewKeyId_();
210 key.setKeyId(keyId);
211 const keyData = await Registry.newKeyData(keyTemplate);
212 key.setKeyData(keyData);
213 const keyset = new PbKeyset();
214 keyset.addKey(key);
215 keyset.setPrimaryKeyId(keyId);
216 return keyset;
217}
218
219/**
220 * Generates a new random key ID.
221 *
222 * @return The key ID.
223 */
224function generateNewKeyId_(): number {
225 const bytes = Random.randBytes(4);
226 let value = 0;
227 for (let i = 0; i < bytes.length; i++) {
228 value += (bytes[i] & 255) << i * 8;
229 }
230
231 // Make sure the key ID is a positive integer smaller than 2^32.
232 return Math.abs(value) % 2 ** 32;
233}
234
235/**
236 * Creates a KeysetHandle from a keyset, obtained via reader, which
237 * must contain no secret key material.
238 *
239 * This can be used to load public keysets or envelope encryption keysets.
240 * Users that need to load cleartext keysets can use CleartextKeysetHandle.
241 *
242 */
243export function readNoSecret(reader: KeysetReader): KeysetHandle {
244 if (reader === null) {
245 throw new SecurityException('Reader has to be non-null.');
246 }
247 const keyset = reader.read();
Tink Team01a66022021-10-12 07:11:11 -0700248 assertNoSecretKeyMaterial(keyset);
taymon5baa7912020-07-13 07:37:04 -0700249 return new KeysetHandle(keyset);
250}
OSZAR »