@vercel/cli-auth 0.3.12 → 0.3.14-snapshot-dd15927e8ded

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/linux-tpm.ts ADDED
@@ -0,0 +1,371 @@
1
+ // Seals a secret with this Linux machine's TPM 2.0, by sending TPM commands
2
+ // to the kernel's resource manager (`/dev/tpmrm0`) directly, so there's no
3
+ // native code or library (tpm2-tss) to install.
4
+ //
5
+ // The secret goes into a sealed data object under the owner hierarchy's
6
+ // storage key. The TPM derives that storage key from a seed that never
7
+ // leaves it, the same way every time, so nothing is kept in the TPM: what we
8
+ // save is the object's public area and its private area, which the TPM
9
+ // encrypted with that storage key and which only this TPM can load.
10
+ //
11
+ // The commands use password sessions with empty passwords, without
12
+ // parameter encryption, so the secret crosses the bus between the CPU and a
13
+ // discrete TPM in the clear when it's sealed and unsealed. That's within
14
+ // what sealing protects against (a copied login), not a physical attacker.
15
+ //
16
+ // Buffers that held the secret are zeroed once they're used. That's best
17
+ // effort: JavaScript can keep other copies, and the caller turns the secret
18
+ // into a key.
19
+
20
+ import { closeSync, existsSync, openSync, readSync, writeSync } from 'node:fs';
21
+
22
+ export const TPM_DEVICE = '/dev/tpmrm0';
23
+
24
+ /** A connection to a TPM: sends a command and returns its response. */
25
+ export interface TpmConnection {
26
+ transmit(command: Buffer): Buffer;
27
+ close(): void;
28
+ }
29
+
30
+ export type ConnectTpm = () => TpmConnection;
31
+
32
+ // Constants from the TPM 2.0 Library spec, Part 2.
33
+ const TPM_ST_NO_SESSIONS = 0x8001;
34
+ const TPM_ST_SESSIONS = 0x8002;
35
+ const TPM_CC_CREATE_PRIMARY = 0x131;
36
+ const TPM_CC_CREATE = 0x153;
37
+ const TPM_CC_LOAD = 0x157;
38
+ const TPM_CC_UNSEAL = 0x15e;
39
+ const TPM_CC_FLUSH_CONTEXT = 0x165;
40
+ const TPM_RH_OWNER = 0x40000001;
41
+ const TPM_RS_PW = 0x40000009;
42
+ const TPM_ALG_AES = 0x0006;
43
+ const TPM_ALG_KEYEDHASH = 0x0008;
44
+ const TPM_ALG_SHA256 = 0x000b;
45
+ const TPM_ALG_NULL = 0x0010;
46
+ const TPM_ALG_ECC = 0x0023;
47
+ const TPM_ALG_CFB = 0x0043;
48
+ const TPM_ECC_NIST_P256 = 0x0003;
49
+ const FIXED_TPM = 1 << 1;
50
+ const FIXED_PARENT = 1 << 4;
51
+ const SENSITIVE_DATA_ORIGIN = 1 << 5;
52
+ const USER_WITH_AUTH = 1 << 6;
53
+ const NO_DA = 1 << 10;
54
+ const RESTRICTED = 1 << 16;
55
+ const DECRYPT = 1 << 17;
56
+ // The TPM is busy: send the command again.
57
+ const TPM_RC_RETRY = 0x922;
58
+ const TPM_RC_YIELDED = 0x908;
59
+ const TPM_RC_TESTING = 0x90a;
60
+ const MAX_RETRIES = 10;
61
+ // How long to wait before sending a command again, times the attempt.
62
+ const RETRY_DELAY_MS = 50;
63
+ // Format-one response codes (bit 7 set) carry an error number in their low
64
+ // six bits: TPM_RC_BAD_AUTH and TPM_RC_AUTH_FAIL mean a wrong password.
65
+ const RC_FMT1 = 0x080;
66
+ const RC_BAD_AUTH = 0x022;
67
+ const RC_AUTH_FAIL = 0x00e;
68
+ const MAX_RESPONSE_SIZE = 4096;
69
+
70
+ // The storage key's template: the TCG's standard ECC P-256 SRK template, so
71
+ // the TPM derives the same key from the owner seed every time.
72
+ const STORAGE_KEY_TEMPLATE = tpm2b(
73
+ concat([
74
+ u16(TPM_ALG_ECC),
75
+ u16(TPM_ALG_SHA256),
76
+ u32(
77
+ FIXED_TPM |
78
+ FIXED_PARENT |
79
+ SENSITIVE_DATA_ORIGIN |
80
+ USER_WITH_AUTH |
81
+ NO_DA |
82
+ RESTRICTED |
83
+ DECRYPT
84
+ ),
85
+ tpm2b(Buffer.alloc(0)), // authPolicy
86
+ u16(TPM_ALG_AES),
87
+ u16(128),
88
+ u16(TPM_ALG_CFB),
89
+ u16(TPM_ALG_NULL), // scheme
90
+ u16(TPM_ECC_NIST_P256),
91
+ u16(TPM_ALG_NULL), // kdf
92
+ tpm2b(Buffer.alloc(0)), // unique.x
93
+ tpm2b(Buffer.alloc(0)), // unique.y
94
+ ])
95
+ );
96
+
97
+ // The sealed object's template: data only this TPM can load, and only under
98
+ // this storage key, unsealed with an empty password.
99
+ const SEALED_OBJECT_TEMPLATE = tpm2b(
100
+ concat([
101
+ u16(TPM_ALG_KEYEDHASH),
102
+ u16(TPM_ALG_SHA256),
103
+ u32(FIXED_TPM | FIXED_PARENT | USER_WITH_AUTH | NO_DA),
104
+ tpm2b(Buffer.alloc(0)), // authPolicy
105
+ u16(TPM_ALG_NULL), // scheme
106
+ tpm2b(Buffer.alloc(0)), // unique
107
+ ])
108
+ );
109
+
110
+ export class LinuxTpmError extends Error {
111
+ constructor(
112
+ what: string,
113
+ readonly rc: number,
114
+ reason = ''
115
+ ) {
116
+ super(`${reason}${what} failed with 0x${rc.toString(16)}.`);
117
+ }
118
+ }
119
+
120
+ /** This user isn't allowed to open the TPM device. */
121
+ export class TpmAccessError extends Error {
122
+ constructor() {
123
+ super(
124
+ `Your user can't open ${TPM_DEVICE}. The group that owns it (usually tss) can, but joining it gives your user access to the rest of the TPM too, so only join it if that's fine on this machine.`
125
+ );
126
+ }
127
+ }
128
+
129
+ /** Whether this machine has a TPM the kernel exposes to user space. */
130
+ export function hasLinuxTpm(): boolean {
131
+ return existsSync(TPM_DEVICE);
132
+ }
133
+
134
+ /**
135
+ * Seal `secret` with this machine's TPM. Returns the sealed object's public
136
+ * area and its private area, encrypted by the TPM, both as TPM2B structures.
137
+ */
138
+ export function sealWithLinuxTpm(
139
+ secret: Buffer,
140
+ connect: ConnectTpm = connectToDevice
141
+ ): { public: Buffer; private: Buffer } {
142
+ return withStorageKey(connect, (tpm, parent) => {
143
+ const data = tpm2b(secret);
144
+ const body = concat([tpm2b(Buffer.alloc(0)), data]);
145
+ const sensitive = tpm2b(body);
146
+ const cmd = command(TPM_CC_CREATE, parent, [
147
+ sensitive,
148
+ SEALED_OBJECT_TEMPLATE,
149
+ tpm2b(Buffer.alloc(0)), // outsideInfo
150
+ u32(0), // creationPCR
151
+ ]);
152
+ for (const copy of [data, body, sensitive]) {
153
+ copy.fill(0);
154
+ }
155
+ let response: Buffer;
156
+ try {
157
+ response = send(tpm, 'TPM2_Create', cmd);
158
+ } finally {
159
+ cmd.fill(0);
160
+ }
161
+ // Header, then parameterSize, then outPrivate and outPublic.
162
+ const sealedPrivate = readTpm2b(response, 14);
163
+ const sealedPublic = readTpm2b(response, 14 + sealedPrivate.length);
164
+ return { public: sealedPublic, private: sealedPrivate };
165
+ });
166
+ }
167
+
168
+ /** Unseal what `sealWithLinuxTpm` sealed, with this machine's TPM. */
169
+ export function unsealWithLinuxTpm(
170
+ sealed: { public: Buffer; private: Buffer },
171
+ connect: ConnectTpm = connectToDevice
172
+ ): Buffer {
173
+ return withStorageKey(connect, (tpm, parent) => {
174
+ const object = send(
175
+ tpm,
176
+ 'TPM2_Load',
177
+ command(TPM_CC_LOAD, parent, [sealed.private, sealed.public])
178
+ ).readUInt32BE(10);
179
+ try {
180
+ const response = send(
181
+ tpm,
182
+ 'TPM2_Unseal',
183
+ command(TPM_CC_UNSEAL, object, [])
184
+ );
185
+ const outData = readTpm2b(response, 14);
186
+ const secret = Buffer.from(new Uint8Array(outData.subarray(2)));
187
+ outData.fill(0);
188
+ response.fill(0);
189
+ return secret;
190
+ } finally {
191
+ flush(tpm, object);
192
+ }
193
+ });
194
+ }
195
+
196
+ function withStorageKey<T>(
197
+ connect: ConnectTpm,
198
+ fn: (tpm: TpmConnection, parent: number) => T
199
+ ): T {
200
+ const tpm = connect();
201
+ try {
202
+ let parent: number;
203
+ try {
204
+ parent = send(
205
+ tpm,
206
+ 'TPM2_CreatePrimary',
207
+ command(TPM_CC_CREATE_PRIMARY, TPM_RH_OWNER, [
208
+ // inSensitive: no password, no data.
209
+ tpm2b(concat([tpm2b(Buffer.alloc(0)), tpm2b(Buffer.alloc(0))])),
210
+ STORAGE_KEY_TEMPLATE,
211
+ tpm2b(Buffer.alloc(0)), // outsideInfo
212
+ u32(0), // creationPCR
213
+ ])
214
+ ).readUInt32BE(10);
215
+ } catch (error) {
216
+ if (error instanceof LinuxTpmError && isWrongPassword(error.rc)) {
217
+ throw new LinuxTpmError(
218
+ 'TPM2_CreatePrimary',
219
+ error.rc,
220
+ "This TPM's owner hierarchy has a password, which the CLI doesn't use: "
221
+ );
222
+ }
223
+ throw error;
224
+ }
225
+ try {
226
+ return fn(tpm, parent);
227
+ } finally {
228
+ flush(tpm, parent);
229
+ }
230
+ } finally {
231
+ // The resource manager also flushes what this connection loaded.
232
+ tpm.close();
233
+ }
234
+ }
235
+
236
+ function isWrongPassword(rc: number): boolean {
237
+ const error = rc & 0x3f;
238
+ return (
239
+ (rc & RC_FMT1) !== 0 && (error === RC_BAD_AUTH || error === RC_AUTH_FAIL)
240
+ );
241
+ }
242
+
243
+ function flush(tpm: TpmConnection, handle: number): void {
244
+ try {
245
+ const body = u32(handle);
246
+ send(
247
+ tpm,
248
+ 'TPM2_FlushContext',
249
+ concat([
250
+ u16(TPM_ST_NO_SESSIONS),
251
+ u32(10 + body.length),
252
+ u32(TPM_CC_FLUSH_CONTEXT),
253
+ body,
254
+ ])
255
+ );
256
+ } catch {
257
+ // Closing the connection flushes it anyway.
258
+ }
259
+ }
260
+
261
+ // A command with one handle, authorized by an empty password.
262
+ function command(code: number, handle: number, parameters: Buffer[]): Buffer {
263
+ const auth = concat([
264
+ u32(TPM_RS_PW),
265
+ tpm2b(Buffer.alloc(0)), // nonce
266
+ Buffer.from([0]), // sessionAttributes
267
+ tpm2b(Buffer.alloc(0)), // password
268
+ ]);
269
+ const body = concat([u32(handle), u32(auth.length), auth, ...parameters]);
270
+ const cmd = concat([
271
+ u16(TPM_ST_SESSIONS),
272
+ u32(10 + body.length),
273
+ u32(code),
274
+ body,
275
+ ]);
276
+ // It may hold a secret.
277
+ body.fill(0);
278
+ return cmd;
279
+ }
280
+
281
+ function send(tpm: TpmConnection, what: string, cmd: Buffer): Buffer {
282
+ for (let attempt = 0; ; attempt++) {
283
+ const response = tpm.transmit(cmd);
284
+ if (response.length < 10 || response.readUInt32BE(2) !== response.length) {
285
+ throw new Error(`${what} returned a malformed response.`);
286
+ }
287
+ const rc = response.readUInt32BE(6);
288
+ if (rc === 0) {
289
+ return response;
290
+ }
291
+ const busy =
292
+ rc === TPM_RC_RETRY || rc === TPM_RC_YIELDED || rc === TPM_RC_TESTING;
293
+ if (!busy || attempt >= MAX_RETRIES) {
294
+ throw new LinuxTpmError(what, rc);
295
+ }
296
+ sleep(RETRY_DELAY_MS * (attempt + 1));
297
+ }
298
+ }
299
+
300
+ // Wait without returning to the event loop: every TPM call is synchronous.
301
+ function sleep(ms: number): void {
302
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
303
+ }
304
+
305
+ function connectToDevice(): TpmConnection {
306
+ let fd: number;
307
+ try {
308
+ fd = openSync(TPM_DEVICE, 'r+');
309
+ } catch (error) {
310
+ const code = (error as NodeJS.ErrnoException).code;
311
+ if (code === 'EACCES' || code === 'EPERM') {
312
+ throw new TpmAccessError();
313
+ }
314
+ throw error;
315
+ }
316
+ return {
317
+ transmit(cmd) {
318
+ writeSync(fd, view(cmd));
319
+ // The kernel returns the whole response in one read.
320
+ const response = Buffer.alloc(MAX_RESPONSE_SIZE);
321
+ const size = readSync(fd, view(response), 0, response.length, null);
322
+ const result = Buffer.from(new Uint8Array(response.subarray(0, size)));
323
+ response.fill(0);
324
+ return result;
325
+ },
326
+ close() {
327
+ closeSync(fd);
328
+ },
329
+ };
330
+ }
331
+
332
+ // A TPM2B structure (a 16-bit size, then that many bytes) at `offset`,
333
+ // including its size.
334
+ function readTpm2b(buffer: Buffer, offset: number): Buffer {
335
+ const end = offset + 2 + buffer.readUInt16BE(offset);
336
+ if (end > buffer.length) {
337
+ throw new Error('The TPM returned a truncated response.');
338
+ }
339
+ return Buffer.from(new Uint8Array(buffer.subarray(offset, end)));
340
+ }
341
+
342
+ function tpm2b(bytes: Buffer): Buffer {
343
+ return concat([u16(bytes.length), bytes]);
344
+ }
345
+
346
+ function concat(parts: Buffer[]): Buffer {
347
+ const result = Buffer.alloc(parts.reduce((size, p) => size + p.length, 0));
348
+ let offset = 0;
349
+ for (const part of parts) {
350
+ result.set(part, offset);
351
+ offset += part.length;
352
+ }
353
+ return result;
354
+ }
355
+
356
+ // A view `fs` accepts, whichever `@types/node` is installed.
357
+ function view(buffer: Buffer): Uint8Array {
358
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length);
359
+ }
360
+
361
+ function u16(value: number): Buffer {
362
+ const bytes = Buffer.alloc(2);
363
+ bytes.writeUInt16BE(value);
364
+ return bytes;
365
+ }
366
+
367
+ function u32(value: number): Buffer {
368
+ const bytes = Buffer.alloc(4);
369
+ bytes.writeUInt32BE(value >>> 0);
370
+ return bytes;
371
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vercel/cli-auth",
3
3
  "description": "Used by Vercel's CLIs to handle authentication",
4
- "version": "0.3.12",
4
+ "version": "0.3.14-snapshot-dd15927e8ded",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -23,7 +23,7 @@
23
23
  "async-listen": "3.0.0",
24
24
  "open": "8.4.0",
25
25
  "zod": "4.1.11",
26
- "@vercel/cli-config": "0.3.3"
26
+ "@vercel/cli-config": "0.3.5-snapshot-dd15927e8ded"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "20.11.0",
@@ -39,10 +39,14 @@
39
39
  ]
40
40
  }
41
41
  },
42
+ "optionalDependencies": {
43
+ "koffi": "3.3.3"
44
+ },
42
45
  "scripts": {
43
46
  "build": "pnpm clear && tsc -b",
44
47
  "clear": "rm -rf *.js *.d.ts *.js.map tsconfig.tsbuildinfo",
45
48
  "test": "vitest",
49
+ "test-unit": "vitest run",
46
50
  "typecheck": "tsc --noEmit"
47
51
  }
48
52
  }
@@ -0,0 +1,18 @@
1
+ export type Pointer = bigint;
2
+ /** The Secure Enclave can't use the key while this Mac is locked. */
3
+ export declare class SecureEnclaveLockedError extends Error {
4
+ }
5
+ /**
6
+ * Create a key in this Mac's Secure Enclave and seal `secret` with it.
7
+ * Returns the key as wrapped by the Secure Enclave, and the sealed secret.
8
+ */
9
+ export declare function createSecureEnclaveKey(secret: Buffer): {
10
+ wrapped: Buffer;
11
+ sealed: Buffer;
12
+ };
13
+ /**
14
+ * Unseal what `createSecureEnclaveKey` sealed, with the key `wrapped` by
15
+ * this Mac's Secure Enclave. The Secure Enclave's private key never leaves
16
+ * it. Fails while the Mac is locked.
17
+ */
18
+ export declare function unsealWithSecureEnclaveKey(wrapped: Buffer, sealed: Buffer): Buffer;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // Seals a secret with a P-256 key that lives in this Mac's Secure Enclave,
3
+ // through the Security framework's C API (called with koffi, so there's no
4
+ // native code to build). Sealing is ECIES (ECDH with the Secure Enclave key,
5
+ // then AES-GCM), so only that key can unseal it. The key is created outside
6
+ // the keychain, so it needs no keychain entitlement and works from any
7
+ // binary, `node` included. What we save is the key as wrapped by this Mac's
8
+ // Secure Enclave (`kSecAttrTokenOID`), which only it can unwrap: a copy of
9
+ // the file can't unseal anything anywhere else.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SecureEnclaveLockedError = void 0;
12
+ exports.createSecureEnclaveKey = createSecureEnclaveKey;
13
+ exports.unsealWithSecureEnclaveKey = unsealWithSecureEnclaveKey;
14
+ const koffi_1 = require("./koffi");
15
+ const CORE_FOUNDATION = '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation';
16
+ const SECURITY = '/System/Library/Frameworks/Security.framework/Security';
17
+ const kCFStringEncodingUTF8 = 0x08000100;
18
+ const kCFNumberSInt32Type = 3;
19
+ const kSecAccessControlPrivateKeyUsage = 1 << 30;
20
+ // `SecBase.h` errSecInteractionNotAllowed: the key can't be used while this
21
+ // Mac is locked (`kSecAttrAccessibleWhenUnlockedThisDeviceOnly`).
22
+ const errSecInteractionNotAllowed = -25308;
23
+ /** The Secure Enclave can't use the key while this Mac is locked. */
24
+ class SecureEnclaveLockedError extends Error {
25
+ }
26
+ exports.SecureEnclaveLockedError = SecureEnclaveLockedError;
27
+ let api;
28
+ function loadApi(koffi) {
29
+ const cf = koffi.load(CORE_FOUNDATION);
30
+ const sec = koffi.load(SECURITY);
31
+ // The value of an exported constant, such as a `CFStringRef`.
32
+ const constant = (lib, name) => koffi.decode(lib.symbol(name), 'void *');
33
+ return {
34
+ koffi,
35
+ CFRelease: cf.func('void CFRelease(void *cf)'),
36
+ CFDictionaryCreateMutable: cf.func('void *CFDictionaryCreateMutable(void *allocator, long capacity, void *keyCallBacks, void *valueCallBacks)'),
37
+ CFDictionarySetValue: cf.func('void CFDictionarySetValue(void *dict, void *key, void *value)'),
38
+ CFDictionaryGetValue: cf.func('void *CFDictionaryGetValue(void *dict, void *key)'),
39
+ CFDataCreate: cf.func('void *CFDataCreate(void *allocator, const uint8_t *bytes, long length)'),
40
+ CFDataGetLength: cf.func('long CFDataGetLength(void *data)'),
41
+ CFDataGetBytePtr: cf.func('void *CFDataGetBytePtr(void *data)'),
42
+ CFNumberCreate: cf.func('void *CFNumberCreate(void *allocator, long type, const int32_t *value)'),
43
+ CFStringCreateWithCString: cf.func('void *CFStringCreateWithCString(void *allocator, const char *str, uint32_t encoding)'),
44
+ CFErrorGetCode: cf.func('long CFErrorGetCode(void *error)'),
45
+ SecAccessControlCreateWithFlags: sec.func('void *SecAccessControlCreateWithFlags(void *allocator, void *protection, unsigned long flags, _Out_ void **error)'),
46
+ SecKeyCreateRandomKey: sec.func('void *SecKeyCreateRandomKey(void *parameters, _Out_ void **error)'),
47
+ SecKeyCreateWithData: sec.func('void *SecKeyCreateWithData(void *keyData, void *attributes, _Out_ void **error)'),
48
+ SecKeyCopyAttributes: sec.func('void *SecKeyCopyAttributes(void *key)'),
49
+ SecKeyCopyPublicKey: sec.func('void *SecKeyCopyPublicKey(void *key)'),
50
+ SecKeyCreateEncryptedData: sec.func('void *SecKeyCreateEncryptedData(void *key, void *algorithm, void *plaintext, _Out_ void **error)'),
51
+ SecKeyCreateDecryptedData: sec.func('void *SecKeyCreateDecryptedData(void *key, void *algorithm, void *ciphertext, _Out_ void **error)'),
52
+ keyCallBacks: cf.symbol('kCFTypeDictionaryKeyCallBacks'),
53
+ valueCallBacks: cf.symbol('kCFTypeDictionaryValueCallBacks'),
54
+ kCFBooleanFalse: constant(cf, 'kCFBooleanFalse'),
55
+ kSecAttrKeyType: constant(sec, 'kSecAttrKeyType'),
56
+ kSecAttrKeyTypeECSECPrimeRandom: constant(sec, 'kSecAttrKeyTypeECSECPrimeRandom'),
57
+ kSecAttrKeySizeInBits: constant(sec, 'kSecAttrKeySizeInBits'),
58
+ kSecAttrKeyClass: constant(sec, 'kSecAttrKeyClass'),
59
+ kSecAttrKeyClassPrivate: constant(sec, 'kSecAttrKeyClassPrivate'),
60
+ kSecAttrTokenID: constant(sec, 'kSecAttrTokenID'),
61
+ kSecAttrTokenIDSecureEnclave: constant(sec, 'kSecAttrTokenIDSecureEnclave'),
62
+ kSecPrivateKeyAttrs: constant(sec, 'kSecPrivateKeyAttrs'),
63
+ kSecAttrIsPermanent: constant(sec, 'kSecAttrIsPermanent'),
64
+ kSecAttrAccessControl: constant(sec, 'kSecAttrAccessControl'),
65
+ kSecAttrAccessibleWhenUnlockedThisDeviceOnly: constant(sec, 'kSecAttrAccessibleWhenUnlockedThisDeviceOnly'),
66
+ kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM: constant(sec, 'kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM'),
67
+ };
68
+ }
69
+ /**
70
+ * Runs `fn` with an `own` function that records each Core Foundation object
71
+ * `fn` creates, and releases them all when it returns or throws.
72
+ */
73
+ function withObjects(fn) {
74
+ const a = (api ??= loadApi((0, koffi_1.loadKoffi)()));
75
+ const objects = [];
76
+ try {
77
+ return fn(a, (object, what) => {
78
+ if (!object) {
79
+ throw new Error(`${what} failed.`);
80
+ }
81
+ objects.push(object);
82
+ return object;
83
+ });
84
+ }
85
+ finally {
86
+ for (const object of objects.reverse()) {
87
+ a.CFRelease(object);
88
+ }
89
+ }
90
+ }
91
+ /** Calls a function that reports failure with a `CFErrorRef` out argument. */
92
+ function check(a, what, call) {
93
+ const error = [null];
94
+ const result = call(error);
95
+ if (!result && error[0]) {
96
+ const code = a.CFErrorGetCode(error[0]);
97
+ a.CFRelease(error[0]);
98
+ const message = `${what} failed with error ${code}.`;
99
+ if (Number(code) === errSecInteractionNotAllowed) {
100
+ throw new SecureEnclaveLockedError(message);
101
+ }
102
+ throw new Error(message);
103
+ }
104
+ return result;
105
+ }
106
+ function dictionary(a, own, entries) {
107
+ const dict = own(a.CFDictionaryCreateMutable(null, 0, a.keyCallBacks, a.valueCallBacks), 'CFDictionaryCreateMutable');
108
+ for (const [key, value] of entries) {
109
+ a.CFDictionarySetValue(dict, key, value);
110
+ }
111
+ return dict;
112
+ }
113
+ function toData(a, own, bytes) {
114
+ return own(a.CFDataCreate(null, bytes, bytes.length), 'CFDataCreate');
115
+ }
116
+ function fromData(a, data) {
117
+ const length = Number(a.CFDataGetLength(data));
118
+ if (length === 0) {
119
+ return Buffer.alloc(0);
120
+ }
121
+ // Copy out of the CFData before it's released.
122
+ return Buffer.from(new Uint8Array(a.koffi.view(a.CFDataGetBytePtr(data), length)));
123
+ }
124
+ // Not exported by the SDK headers: the attribute that holds a Secure Enclave
125
+ // key's wrapped form, which CryptoKit calls `dataRepresentation`.
126
+ function tokenOid(a, own) {
127
+ return own(a.CFStringCreateWithCString(null, 'toid', kCFStringEncodingUTF8), 'CFStringCreateWithCString');
128
+ }
129
+ /**
130
+ * Create a key in this Mac's Secure Enclave and seal `secret` with it.
131
+ * Returns the key as wrapped by the Secure Enclave, and the sealed secret.
132
+ */
133
+ function createSecureEnclaveKey(secret) {
134
+ return withObjects((a, own) => {
135
+ const accessControl = own(check(a, 'SecAccessControlCreateWithFlags', error => a.SecAccessControlCreateWithFlags(null, a.kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage, error)), 'SecAccessControlCreateWithFlags');
136
+ const keySize = own(a.CFNumberCreate(null, kCFNumberSInt32Type, Int32Array.of(256)), 'CFNumberCreate');
137
+ const parameters = dictionary(a, own, [
138
+ [a.kSecAttrKeyType, a.kSecAttrKeyTypeECSECPrimeRandom],
139
+ [a.kSecAttrKeySizeInBits, keySize],
140
+ [a.kSecAttrTokenID, a.kSecAttrTokenIDSecureEnclave],
141
+ [
142
+ a.kSecPrivateKeyAttrs,
143
+ dictionary(a, own, [
144
+ // Not in the keychain, which would need a keychain entitlement.
145
+ [a.kSecAttrIsPermanent, a.kCFBooleanFalse],
146
+ [a.kSecAttrAccessControl, accessControl],
147
+ ]),
148
+ ],
149
+ ]);
150
+ const key = own(check(a, 'SecKeyCreateRandomKey', error => a.SecKeyCreateRandomKey(parameters, error)), 'SecKeyCreateRandomKey');
151
+ const attributes = own(a.SecKeyCopyAttributes(key), 'SecKeyCopyAttributes');
152
+ const wrapped = a.CFDictionaryGetValue(attributes, tokenOid(a, own));
153
+ if (!wrapped) {
154
+ throw new Error("The Secure Enclave didn't return the wrapped key.");
155
+ }
156
+ const publicKey = own(a.SecKeyCopyPublicKey(key), 'SecKeyCopyPublicKey');
157
+ const sealed = own(check(a, 'SecKeyCreateEncryptedData', error => a.SecKeyCreateEncryptedData(publicKey, a.kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM, toData(a, own, secret), error)), 'SecKeyCreateEncryptedData');
158
+ return { wrapped: fromData(a, wrapped), sealed: fromData(a, sealed) };
159
+ });
160
+ }
161
+ /**
162
+ * Unseal what `createSecureEnclaveKey` sealed, with the key `wrapped` by
163
+ * this Mac's Secure Enclave. The Secure Enclave's private key never leaves
164
+ * it. Fails while the Mac is locked.
165
+ */
166
+ function unsealWithSecureEnclaveKey(wrapped, sealed) {
167
+ return withObjects((a, own) => {
168
+ const attributes = dictionary(a, own, [
169
+ [a.kSecAttrKeyType, a.kSecAttrKeyTypeECSECPrimeRandom],
170
+ [a.kSecAttrKeyClass, a.kSecAttrKeyClassPrivate],
171
+ [a.kSecAttrTokenID, a.kSecAttrTokenIDSecureEnclave],
172
+ [tokenOid(a, own), toData(a, own, wrapped)],
173
+ ]);
174
+ // With a token ID the key comes from the attributes, not `keyData`.
175
+ const key = own(check(a, 'SecKeyCreateWithData', error => a.SecKeyCreateWithData(toData(a, own, new Uint8Array(0)), attributes, error)), 'SecKeyCreateWithData');
176
+ const secret = own(check(a, 'SecKeyCreateDecryptedData', error => a.SecKeyCreateDecryptedData(key, a.kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM, toData(a, own, sealed), error)), 'SecKeyCreateDecryptedData');
177
+ return fromData(a, secret);
178
+ });
179
+ }
180
+ //# sourceMappingURL=secure-enclave.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-enclave.js","sourceRoot":"","sources":["secure-enclave.ts"],"names":[],"mappings":"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"}