taymon | f6a87a3 | 2020-08-07 00:16:40 -0700 | [diff] [blame] | 1 | /** |
| 2 | * @license |
| 3 | * Copyright 2020 Google LLC |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
| 5 | */ |
| 6 | |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 7 | import {Aead} from '../aead/internal/aead'; |
| 8 | import {InvalidArgumentsException} from '../exception/invalid_arguments_exception'; |
| 9 | import {SecurityException} from '../exception/security_exception'; |
| 10 | import * as Random from '../subtle/random'; |
| 11 | |
| 12 | import * as KeyManager from './key_manager'; |
| 13 | import {KeysetReader} from './keyset_reader'; |
| 14 | import {KeysetWriter} from './keyset_writer'; |
| 15 | import * as PrimitiveSet from './primitive_set'; |
Tink Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 16 | import {PbKeyData, PbKeyMaterialType, PbKeyset, PbKeysetKey, PbKeyStatusType, PbKeyTemplate} from './proto'; |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 17 | import * as Registry from './registry'; |
| 18 | import * 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 | */ |
| 26 | export 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 Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 102 | * 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 Team | 7f64e0e | 2021-12-21 07:45:23 -0800 | [diff] [blame] | 111 | return writer.encodeBinary(this.keyset_); |
Tink Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 112 | } |
| 113 | |
| 114 | /** |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 115 | * Returns the keyset held by this KeysetHandle. |
| 116 | * |
| 117 | */ |
| 118 | getKeyset(): PbKeyset { |
| 119 | return this.keyset_; |
| 120 | } |
Tink Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 121 | |
| 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 Team | a38dbf8 | 2022-06-02 14:38:29 -0700 | [diff] [blame] | 129 | publicKeyset.addKey(key.clone().setKeyData(createPublicKeyData( |
| 130 | nonNull('Key data', key.getKeyData())))); |
Tink Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 131 | } |
| 132 | publicKeyset.setPrimaryKeyId(this.keyset_.getPrimaryKeyId()); |
| 133 | return new KeysetHandle(publicKeyset); |
| 134 | } |
| 135 | } |
| 136 | |
Tink Team | a38dbf8 | 2022-06-02 14:38:29 -0700 | [diff] [blame] | 137 | function nonNull<T>(desc: string, value: T|null|undefined): T { |
| 138 | if (value == null) { |
Tink Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 139 | throw new SecurityException(`${desc} has to be non null.`); |
| 140 | } |
| 141 | return value; |
| 142 | } |
| 143 | |
| 144 | function 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 | */ |
| 158 | function 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. */ |
| 168 | function isSecretKeyMaterialType(type: PbKeyMaterialType) { |
| 169 | return type === PbKeyMaterialType.UNKNOWN_KEYMATERIAL || |
| 170 | type === PbKeyMaterialType.SYMMETRIC || |
| 171 | type === PbKeyMaterialType.ASYMMETRIC_PRIVATE; |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Creates a KeysetHandle from an encrypted keyset obtained via reader, using |
| 176 | * masterKeyAead to decrypt the keyset. |
| 177 | * |
| 178 | * |
| 179 | */ |
| 180 | export 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 | */ |
| 192 | export 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 | */ |
| 204 | async function generateNewKeyset_(keyTemplate: PbKeyTemplate): |
| 205 | Promise<PbKeyset> { |
taymon | aa14afc | 2020-08-24 05:35:00 -0700 | [diff] [blame] | 206 | const key = (new PbKeysetKey()) |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 207 | .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 | */ |
| 224 | function 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 | */ |
| 243 | export 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 Team | 01a6602 | 2021-10-12 07:11:11 -0700 | [diff] [blame] | 248 | assertNoSecretKeyMaterial(keyset); |
taymon | 5baa791 | 2020-07-13 07:37:04 -0700 | [diff] [blame] | 249 | return new KeysetHandle(keyset); |
| 250 | } |