attest-tpm 1.0.1
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/Readme.md +159 -0
- package/attest-tpm.mjs +1261 -0
- package/package.json +8 -0
package/attest-tpm.mjs
ADDED
|
@@ -0,0 +1,1261 @@
|
|
|
1
|
+
import koffi from "koffi"
|
|
2
|
+
/**@import os from "os"*/
|
|
3
|
+
|
|
4
|
+
const authorityFields = {
|
|
5
|
+
/**The Trusted Root certificate's Issued by and Issued to and the Intermediate certificate's Issued by.*/ "Issued by": "",
|
|
6
|
+
/**O in the Trusted Root certificate's Issuer and Subject and in the Intermediate certificate's Issuer.*/ O: "",
|
|
7
|
+
/**C in the Trusted Root certificate's Issuer and Subject and in the Intermediate certificate's Issuer. Must be two characters.*/ C: "",
|
|
8
|
+
/**The Intermediate certificate's Issued to.*/ "Issued to": "",
|
|
9
|
+
/**The On-line Certificate Status Protocol URL in the Intermediate certificate's Authority Information Access.*/ "On-line Certificate Status Protocol": "",
|
|
10
|
+
/**The Intermediate certificate's CRL Distribution Points URL.*/ "CRL Distribution Points": ""
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const C = (/**@type {string=}*/ C) => {
|
|
14
|
+
const subjectC = [11, 48, 9, 6, 3, 85, 4, 6, 12, 2]
|
|
15
|
+
|
|
16
|
+
if (C) {
|
|
17
|
+
subjectC.push(...Array.from(
|
|
18
|
+
C,
|
|
19
|
+
(character) => {
|
|
20
|
+
return character.charCodeAt(0)
|
|
21
|
+
}
|
|
22
|
+
))
|
|
23
|
+
} else {
|
|
24
|
+
subjectC.push(85, 83)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return subjectC
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const prependLength = (/**@type {number[]}*/ bytes) => {
|
|
31
|
+
const prependedBytes = []
|
|
32
|
+
|
|
33
|
+
if (bytes[255] === undefined) {
|
|
34
|
+
if (bytes[127] !== undefined) {
|
|
35
|
+
prependedBytes[0] = 129
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
prependedBytes[prependedBytes.length] = bytes.length
|
|
39
|
+
} else {
|
|
40
|
+
const bytesLength = Math.floor(bytes.length / 256)
|
|
41
|
+
prependedBytes.push(130, bytesLength, bytes.length - 256 * bytesLength)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
prependedBytes.push(...bytes)
|
|
45
|
+
return prependedBytes
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const O = (/**@type {string=}*/ O) => {
|
|
49
|
+
const subjectO = []
|
|
50
|
+
|
|
51
|
+
if (O) {
|
|
52
|
+
subjectO.push(...Array.from(
|
|
53
|
+
O,
|
|
54
|
+
(character) => {
|
|
55
|
+
return character.charCodeAt(0)
|
|
56
|
+
}
|
|
57
|
+
))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [49, 128, 48, 128, 6, 3, 85, 4, 10, 12, ...prependLength(subjectO), 0, 0, 0, 0, 49]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const Issued = (/**@type {string}*/ Issued) => {
|
|
64
|
+
return [
|
|
65
|
+
128,
|
|
66
|
+
48,
|
|
67
|
+
128,
|
|
68
|
+
6,
|
|
69
|
+
3,
|
|
70
|
+
85,
|
|
71
|
+
4,
|
|
72
|
+
3,
|
|
73
|
+
12,
|
|
74
|
+
...prependLength(Array.from(
|
|
75
|
+
Issued,
|
|
76
|
+
(character) => {
|
|
77
|
+
return character.charCodeAt(0)
|
|
78
|
+
}
|
|
79
|
+
)),
|
|
80
|
+
0,
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
0
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const createCer = async (/**@type {number[]}*/ Issuer, /**@type {number[]}*/ Valid, /**@type {number[]}*/ Subject, /**@type {string}*/ Publickey, /**@type {number[]}*/ Fields, /**@type {number}*/ KeyUsage, /**@type {(Details: ArrayBuffer) => Promise<ArrayBuffer>}*/ sign) => {
|
|
88
|
+
const Serialnumber = [0, ...crypto.getRandomValues(new Uint8Array(8))]
|
|
89
|
+
|
|
90
|
+
const Details = Uint8Array.from([
|
|
91
|
+
48,
|
|
92
|
+
...prependLength([
|
|
93
|
+
160,
|
|
94
|
+
3,
|
|
95
|
+
2,
|
|
96
|
+
1,
|
|
97
|
+
2,
|
|
98
|
+
2,
|
|
99
|
+
9,
|
|
100
|
+
...Serialnumber,
|
|
101
|
+
48,
|
|
102
|
+
11,
|
|
103
|
+
6,
|
|
104
|
+
9,
|
|
105
|
+
42,
|
|
106
|
+
134,
|
|
107
|
+
72,
|
|
108
|
+
134,
|
|
109
|
+
247,
|
|
110
|
+
13,
|
|
111
|
+
1,
|
|
112
|
+
1,
|
|
113
|
+
11,
|
|
114
|
+
48,
|
|
115
|
+
...prependLength([49, ...Issuer]),
|
|
116
|
+
48,
|
|
117
|
+
24,
|
|
118
|
+
23,
|
|
119
|
+
10,
|
|
120
|
+
...Valid,
|
|
121
|
+
48,
|
|
122
|
+
...prependLength([49, ...Subject]),
|
|
123
|
+
48,
|
|
124
|
+
...prependLength([
|
|
125
|
+
48,
|
|
126
|
+
11,
|
|
127
|
+
6,
|
|
128
|
+
9,
|
|
129
|
+
42,
|
|
130
|
+
134,
|
|
131
|
+
72,
|
|
132
|
+
134,
|
|
133
|
+
247,
|
|
134
|
+
13,
|
|
135
|
+
1,
|
|
136
|
+
1,
|
|
137
|
+
1,
|
|
138
|
+
3,
|
|
139
|
+
...prependLength([
|
|
140
|
+
0,
|
|
141
|
+
48,
|
|
142
|
+
128,
|
|
143
|
+
2,
|
|
144
|
+
...prependLength(Array.from(
|
|
145
|
+
Publickey,
|
|
146
|
+
(character) => {
|
|
147
|
+
return character.charCodeAt(0)
|
|
148
|
+
}
|
|
149
|
+
)),
|
|
150
|
+
2,
|
|
151
|
+
3,
|
|
152
|
+
1,
|
|
153
|
+
0,
|
|
154
|
+
1,
|
|
155
|
+
0,
|
|
156
|
+
0
|
|
157
|
+
])
|
|
158
|
+
]),
|
|
159
|
+
163,
|
|
160
|
+
128,
|
|
161
|
+
48,
|
|
162
|
+
128,
|
|
163
|
+
48,
|
|
164
|
+
...Fields,
|
|
165
|
+
48,
|
|
166
|
+
14,
|
|
167
|
+
6,
|
|
168
|
+
3,
|
|
169
|
+
85,
|
|
170
|
+
29,
|
|
171
|
+
15,
|
|
172
|
+
1,
|
|
173
|
+
1,
|
|
174
|
+
255,
|
|
175
|
+
4,
|
|
176
|
+
4,
|
|
177
|
+
3,
|
|
178
|
+
2,
|
|
179
|
+
0,
|
|
180
|
+
KeyUsage,
|
|
181
|
+
0,
|
|
182
|
+
0,
|
|
183
|
+
0,
|
|
184
|
+
0
|
|
185
|
+
])
|
|
186
|
+
])
|
|
187
|
+
|
|
188
|
+
return { cer: Uint8Array.from([48, 128, ...Details, 48, 11, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 3, 130, 2, 1, 0, ...new Uint8Array(await sign(Details.buffer)), 0, 0]), "Serial number": Serialnumber }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const OnlineCertificateStatusProtocol = (/**@type {string}*/ URL) => {
|
|
192
|
+
return [
|
|
193
|
+
48,
|
|
194
|
+
128,
|
|
195
|
+
6,
|
|
196
|
+
8,
|
|
197
|
+
43,
|
|
198
|
+
6,
|
|
199
|
+
1,
|
|
200
|
+
5,
|
|
201
|
+
5,
|
|
202
|
+
7,
|
|
203
|
+
48,
|
|
204
|
+
1,
|
|
205
|
+
134,
|
|
206
|
+
...prependLength(Array.from(
|
|
207
|
+
URL,
|
|
208
|
+
(character) => {
|
|
209
|
+
return character.charCodeAt(0)
|
|
210
|
+
}
|
|
211
|
+
)),
|
|
212
|
+
0,
|
|
213
|
+
0
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const signingFields = (/**@type {number[]}*/ AuthorityInformationAccess, /**@type {{ "CRL Distribution Points"?: string }=}*/ Fields) => {
|
|
218
|
+
let URL = Fields?.["CRL Distribution Points"]
|
|
219
|
+
|
|
220
|
+
if (!URL) {
|
|
221
|
+
URL = ""
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return [
|
|
225
|
+
128,
|
|
226
|
+
6,
|
|
227
|
+
8,
|
|
228
|
+
43,
|
|
229
|
+
6,
|
|
230
|
+
1,
|
|
231
|
+
5,
|
|
232
|
+
5,
|
|
233
|
+
7,
|
|
234
|
+
1,
|
|
235
|
+
1,
|
|
236
|
+
4,
|
|
237
|
+
...prependLength([48, 128, ...AuthorityInformationAccess, 0, 0]),
|
|
238
|
+
0,
|
|
239
|
+
0,
|
|
240
|
+
48,
|
|
241
|
+
128,
|
|
242
|
+
6,
|
|
243
|
+
3,
|
|
244
|
+
85,
|
|
245
|
+
29,
|
|
246
|
+
31,
|
|
247
|
+
4,
|
|
248
|
+
...prependLength([
|
|
249
|
+
48,
|
|
250
|
+
128,
|
|
251
|
+
48,
|
|
252
|
+
128,
|
|
253
|
+
160,
|
|
254
|
+
128,
|
|
255
|
+
160,
|
|
256
|
+
128,
|
|
257
|
+
134,
|
|
258
|
+
...prependLength(Array.from(
|
|
259
|
+
URL,
|
|
260
|
+
(character) => {
|
|
261
|
+
return character.charCodeAt(0)
|
|
262
|
+
}
|
|
263
|
+
)),
|
|
264
|
+
0,
|
|
265
|
+
0,
|
|
266
|
+
0,
|
|
267
|
+
0,
|
|
268
|
+
0,
|
|
269
|
+
0,
|
|
270
|
+
0,
|
|
271
|
+
0
|
|
272
|
+
]),
|
|
273
|
+
0,
|
|
274
|
+
0,
|
|
275
|
+
48,
|
|
276
|
+
19,
|
|
277
|
+
6,
|
|
278
|
+
3,
|
|
279
|
+
85,
|
|
280
|
+
29,
|
|
281
|
+
37,
|
|
282
|
+
4,
|
|
283
|
+
12,
|
|
284
|
+
48,
|
|
285
|
+
10,
|
|
286
|
+
6,
|
|
287
|
+
8,
|
|
288
|
+
43,
|
|
289
|
+
6,
|
|
290
|
+
1,
|
|
291
|
+
5,
|
|
292
|
+
5,
|
|
293
|
+
7,
|
|
294
|
+
3,
|
|
295
|
+
3,
|
|
296
|
+
48,
|
|
297
|
+
19,
|
|
298
|
+
6,
|
|
299
|
+
3,
|
|
300
|
+
85,
|
|
301
|
+
29,
|
|
302
|
+
32,
|
|
303
|
+
4,
|
|
304
|
+
12,
|
|
305
|
+
48,
|
|
306
|
+
10,
|
|
307
|
+
48,
|
|
308
|
+
8,
|
|
309
|
+
6,
|
|
310
|
+
6,
|
|
311
|
+
103,
|
|
312
|
+
129,
|
|
313
|
+
12,
|
|
314
|
+
1,
|
|
315
|
+
4,
|
|
316
|
+
1,
|
|
317
|
+
48,
|
|
318
|
+
31,
|
|
319
|
+
6,
|
|
320
|
+
3,
|
|
321
|
+
85,
|
|
322
|
+
29,
|
|
323
|
+
35,
|
|
324
|
+
4,
|
|
325
|
+
24,
|
|
326
|
+
48,
|
|
327
|
+
22,
|
|
328
|
+
128,
|
|
329
|
+
20,
|
|
330
|
+
0,
|
|
331
|
+
0,
|
|
332
|
+
0,
|
|
333
|
+
0,
|
|
334
|
+
0,
|
|
335
|
+
0,
|
|
336
|
+
0,
|
|
337
|
+
0,
|
|
338
|
+
0,
|
|
339
|
+
0,
|
|
340
|
+
0,
|
|
341
|
+
0,
|
|
342
|
+
0,
|
|
343
|
+
0,
|
|
344
|
+
0,
|
|
345
|
+
0,
|
|
346
|
+
0,
|
|
347
|
+
0,
|
|
348
|
+
0,
|
|
349
|
+
0
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export const /**Create Trusted Root and Intermediate certificates.*/ authorityCers = async (
|
|
354
|
+
/**The Trusted Root certificate's Public key. Must have `algorithm: { name: "RSASSA-PKCS1-v1_5", modulusLength: 4096, publicExponent: Uint8Array.from([1, 0, 1]), hash: "SHA-256" }`. @type {CryptoKey}*/ TrustedRoot,
|
|
355
|
+
/**The Intermediate certificate's Public key. Must have `algorithm: { name: "RSASSA-PKCS1-v1_5", modulusLength: 4096, publicExponent: Uint8Array.from([1, 0, 1]), hash: "SHA-256" }`. @type {CryptoKey}*/ Intermediate,
|
|
356
|
+
/**A function to sign the certificates with the private key matching the Trusted Root certificate. @type {(Details: ArrayBuffer) => Promise<ArrayBuffer>}*/ sign,
|
|
357
|
+
/**The certificate fields. @type {Partial<authorityFields>}*/ Fields = {}
|
|
358
|
+
) => {
|
|
359
|
+
if (TrustedRoot.extractable) {
|
|
360
|
+
if (TrustedRoot.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
361
|
+
if (Intermediate.extractable) {
|
|
362
|
+
if (Intermediate.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
363
|
+
let Issuedby = Fields["Issued by"]
|
|
364
|
+
|
|
365
|
+
if (!Issuedby) {
|
|
366
|
+
Issuedby = "\0"
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let Issuedto = Fields["Issued to"]
|
|
370
|
+
|
|
371
|
+
if (!Issuedto) {
|
|
372
|
+
Issuedto = "\0"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (Issuedby === Issuedto) {
|
|
376
|
+
Issuedby += "\0"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const Issuer = [...C(Fields.C), ...O(Fields.O), ...Issued(Issuedby)]
|
|
380
|
+
const Valid = [48, 48, 48, 49, 48, 49, 48, 48, 48, 48, 23, 10, 52, 57, 49, 50, 51, 49, 50, 51, 53, 57]
|
|
381
|
+
let URL = Fields["On-line Certificate Status Protocol"]
|
|
382
|
+
|
|
383
|
+
if (!URL) {
|
|
384
|
+
URL = ""
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
/**A Trusted Root certificate.*/ "Trusted Root": (await createCer(Issuer, Valid, Issuer, atob(/**@type {string}*/((await crypto.subtle.exportKey("jwk", TrustedRoot)).n).replaceAll("_", "/").replaceAll("-", "+")), [15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255], 6, sign)).cer,
|
|
389
|
+
/**An Intermediate certificate.*/ Intermediate: (await createCer(Issuer, Valid, Issued(Issuedto), atob(/**@type {string}*/((await crypto.subtle.exportKey("jwk", Intermediate)).n).replaceAll("_", "/").replaceAll("-", "+")), [...signingFields(OnlineCertificateStatusProtocol(URL), Fields), 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255], 6, sign)).cer
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
throw new Error("`Intermediate` key isn't `RSASSA-PKCS1-v1_5`")
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
throw new Error("`Intermediate` key isn't `extractable`")
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
throw new Error("`Trusted Root` key isn't `RSASSA-PKCS1-v1_5`")
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
throw new Error("`Trusted Root` key isn't `extractable`")
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const Tbsi_Context_Create = () => {
|
|
406
|
+
let tbs
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
tbs = koffi.load("tbs").func
|
|
410
|
+
} catch {
|
|
411
|
+
throw new Error("`attestTPM.tpm` functions must run on Windows")
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const Tbsip_Submit_Command = tbs("Tbsip_Submit_Command", "int", ["int", "void *", "void *", "uint8 *", "int", "uint8 *", "uint8 *"])
|
|
415
|
+
const contextResponse = new ArrayBuffer(4)
|
|
416
|
+
tbs("Tbsi_Context_Create", "void", ["uint8 *", "uint8 *"])([2, 0, 0, 0, 4, 0, 0, 0], contextResponse)
|
|
417
|
+
const tbsContext = new DataView(contextResponse).getUint32(0, true)
|
|
418
|
+
|
|
419
|
+
return (/**@type {number[]}*/ command) => {
|
|
420
|
+
const commandResponse = new Uint8Array(1045)
|
|
421
|
+
|
|
422
|
+
if (!(Tbsip_Submit_Command(tbsContext, undefined, undefined, command, command.length, commandResponse, [21, 4, 0, 0]) || new DataView(commandResponse.buffer).getUint32(6))) {
|
|
423
|
+
return commandResponse
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export const /**Create TPM keys.*/ tpmCreate = async () => {
|
|
429
|
+
const Tbsip_Submit_Command = Tbsi_Context_Create()
|
|
430
|
+
const signingKey = Tbsip_Submit_Command([128, 2, 0, 0, 0, 63, 0, 0, 1, 83, 129, 0, 0, 1, 0, 0, 0, 9, 64, 0, 0, 9, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 22, 0, 1, 0, 11, 0, 4, 0, 114, 0, 0, 0, 16, 0, 16, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
431
|
+
|
|
432
|
+
if (signingKey) {
|
|
433
|
+
const keys = signingKey.slice(14, new DataView(signingKey.buffer).getUint16(14) + 424)
|
|
434
|
+
const savedCer = Tbsip_Submit_Command([128, 1, 0, 0, 0, 14, 0, 0, 1, 105, 1, 192, 0, 2])
|
|
435
|
+
let cer
|
|
436
|
+
|
|
437
|
+
if (savedCer) {
|
|
438
|
+
let tpmCer = ""
|
|
439
|
+
const cerLength = new DataView(savedCer.buffer).getUint16(24)
|
|
440
|
+
|
|
441
|
+
while (tpmCer.length !== cerLength) {
|
|
442
|
+
const cerCommand = [128, 2, 0, 0, 0, 35, 0, 0, 1, 78, 1, 192, 0, 2, 1, 192, 0, 2, 0, 0, 0, 9, 64, 0, 0, 9, 0, 0, 0, 0, 0]
|
|
443
|
+
let /**@type {number}*/ responseLength = cerLength - tpmCer.length
|
|
444
|
+
|
|
445
|
+
if (responseLength > 1023) {
|
|
446
|
+
cerCommand.push(4, 0)
|
|
447
|
+
responseLength = 1024
|
|
448
|
+
} else {
|
|
449
|
+
const lengthBytes = new Uint8Array(2)
|
|
450
|
+
new DataView(lengthBytes.buffer).setUint16(0, responseLength)
|
|
451
|
+
cerCommand.push(...lengthBytes)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
cerCommand.push(tpmCer.length / 256, 0)
|
|
455
|
+
tpmCer += String.fromCharCode(.../**@type {Uint8Array}*/(Tbsip_Submit_Command(cerCommand)).slice(16, responseLength + 16))
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
cer = btoa(tpmCer)
|
|
459
|
+
} else {
|
|
460
|
+
const cerKey = /**@type {Uint8Array}*/(Tbsip_Submit_Command([128, 1, 0, 0, 0, 14, 0, 0, 1, 115, 129, 1, 0, 1])).slice(70, 326)
|
|
461
|
+
|
|
462
|
+
if (String.fromCharCode(.../**@type {Uint8Array}*/(Tbsip_Submit_Command([128, 1, 0, 0, 0, 22, 0, 0, 1, 122, 0, 0, 0, 6, 0, 0, 1, 5, 0, 0, 0, 1])).slice(23, 27)) === "INTC") {
|
|
463
|
+
cer = decodeURIComponent((await (await fetch(`https://ekop.intel.com/ekcertservice/${encodeURIComponent(btoa(String.fromCharCode(...new Uint8Array(await crypto.subtle.digest("sha-256", Uint8Array.from([...cerKey, 1, 0, 1]))))))}`)).json()).certificate).replaceAll("-", "+").replaceAll("_", "/")
|
|
464
|
+
} else {
|
|
465
|
+
cer = btoa(String.fromCharCode(...await (await fetch(`https://ftpm.amd.com/pki/aia/${Array.from(
|
|
466
|
+
new Uint8Array(await crypto.subtle.digest("sha-256", Uint8Array.from([0, 0, 34, 34, 0, 1, 0, 1, ...cerKey]))),
|
|
467
|
+
(byte) => {
|
|
468
|
+
return byte.toString(16).padStart(2, "0")
|
|
469
|
+
}
|
|
470
|
+
).join("")}`)).bytes()))
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
/**A key pair that can be provided to `attestTPM.tpmDecrypt` and `attestTPM.tpmSign`. The private key is encrypted by the TPM.*/ keys,
|
|
476
|
+
/**JSON that can be provided to `attestTPM.authorityIssue`.*/ body: JSON.stringify({ cer, "Public key": btoa(String.fromCharCode(...keys.slice(-384))) })
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
throw new Error("computer's TPM doesn't support `modulusLength: 3072`")
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const tpmFields = {
|
|
484
|
+
/**The Issued to.*/ "Issued to": "",
|
|
485
|
+
/**The Subject O.*/ O: "",
|
|
486
|
+
/**The Subject L.*/ L: "",
|
|
487
|
+
/**The Subject C. Must be two characters.*/ C: "",
|
|
488
|
+
/**The On-line Certificate Status Protocol URL in Authority Information Access.*/ "On-line Certificate Status Protocol": "",
|
|
489
|
+
/**The Certification Authority Issuer URL in Authority Information Access.*/ "Certification Authority Issuer": "",
|
|
490
|
+
/**The CRL Distribution Points URL.*/ "CRL Distribution Points": ""
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**@typedef {{ asn: Uint8Array<ArrayBuffer>, content: Uint8Array, children: parsedASN }[]} parsedASN*/
|
|
494
|
+
|
|
495
|
+
const parseASN = (/**@type {Uint8Array}*/ asn) => {
|
|
496
|
+
let asnByte = 0
|
|
497
|
+
|
|
498
|
+
const parseASN = (/**@type {number}*/ end) => {
|
|
499
|
+
const asnType = asnByte
|
|
500
|
+
let contentLength = asn[asnType + 1]
|
|
501
|
+
let contentStart = asnType + 2
|
|
502
|
+
const indefiniteLength = contentLength === 128
|
|
503
|
+
let contentEnd
|
|
504
|
+
|
|
505
|
+
if (indefiniteLength) {
|
|
506
|
+
contentEnd = end
|
|
507
|
+
} else {
|
|
508
|
+
if (contentLength === 129) {
|
|
509
|
+
contentLength = asn[contentStart++]
|
|
510
|
+
} else if (contentLength === 130) {
|
|
511
|
+
contentLength = 256 * asn[contentStart++] + asn[contentStart++]
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
contentEnd = contentLength + contentStart
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const /**@type {parsedASN}*/ children = []
|
|
518
|
+
|
|
519
|
+
if (contentEnd <= end) {
|
|
520
|
+
asnByte = contentStart
|
|
521
|
+
|
|
522
|
+
while (asnByte !== contentEnd) {
|
|
523
|
+
const parsedASN = children[children.length] = parseASN(contentEnd)
|
|
524
|
+
|
|
525
|
+
if (indefiniteLength && !(parsedASN.asn[0] || parsedASN.asn[1])) {
|
|
526
|
+
break
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
asnByte = end
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return { asn: asn.slice(asnType, asnByte), content: asn.slice(contentStart, asnByte), children }
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return parseASN(asn.length)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const nuvotonKey = crypto.subtle.importKey("raw", Uint8Array.from([4, 218, 156, 220, 176, 62, 65, 63, 68, 128, 129, 103, 133, 99, 192, 44, 166, 44, 59, 108, 7, 181, 39, 191, 157, 142, 143, 65, 242, 18, 192, 31, 115, 191, 175, 140, 233, 118, 12, 255, 9, 110, 183, 40, 205, 141, 57, 179, 177, 133, 125, 10, 145, 222, 248, 111, 225, 151, 100, 36, 198, 165, 128, 234, 246]), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"])
|
|
540
|
+
const globalsignKey = crypto.subtle.importKey("jwk", { kty: "RSA", n: "68iNcEttUF8gLtIWNzPiGbpHQjK5EmknjmeljK2UAeahWiyhPeKyXnZPhQHhPWvVvzLtj69bcR3Sxk76b+ke1RzmH3TnTKcZ523S6+41nVxWdfaH9dNx0ohrCoQFVdFlDk4kyn9oOVTCxR4FP5KWEBqA2GbI3Da0LZpvl3uqvwVTTcOzqKjz3ZpIPbfPnXIsCLHXbW5Qcg+YcHJplGdopy1Nu7u6XAg2Wt1wH3ndnRZVCGKXnKNmzXjJ3jICcDdnVlb6as6Svx6FWxRu+tTHJ+EwicysRMjFHhH1flxMhWJrZJVO1+qtvpcwLX9PsrU8f3RBe2W3dYgsww0mEsPL6Q", e: "AQAB" }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"])
|
|
541
|
+
const integrityBytes = Uint8Array.from([0, 0, 0, 1, 73, 78, 84, 69, 71, 82, 73, 84, 89, 0, 0, 0, 1, 0])
|
|
542
|
+
const aesCBC = { name: "aes-cbc", iv: new ArrayBuffer(16) }
|
|
543
|
+
const label = Uint8Array.from([73, 68, 69, 78, 84, 73, 84, 89, 0])
|
|
544
|
+
|
|
545
|
+
export const /**`verify` whether a certificate was issued by a TPM manufacturer and create a Code Signing certificate if so.*/ authorityIssue = async (
|
|
546
|
+
/**A `.cer` of an Intermediate certificate. @type {Uint8Array}*/ Intermediate,
|
|
547
|
+
/**A `body` from `attestTPM.tpmCreate`. @type {Uint8Array}*/ body,
|
|
548
|
+
/**A function to sign the Code Signing certificate with the private key matching the Intermediate certificate's Public key. @type {(Details: ArrayBuffer) => Promise<ArrayBuffer>}*/ sign,
|
|
549
|
+
/**The Code Signing certificate fields. @type {Partial<tpmFields>}*/ Fields = {}
|
|
550
|
+
) => {
|
|
551
|
+
const Issuedby = parseASN(Intermediate).children[0]?.children[5]?.children
|
|
552
|
+
|
|
553
|
+
if (Issuedby?.[0]) {
|
|
554
|
+
const bodyJSON = String.fromCharCode(...body)
|
|
555
|
+
let tpmBody
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
tpmBody = JSON.parse(bodyJSON)
|
|
559
|
+
} catch {
|
|
560
|
+
return { error: "body isn't in `JSON.stringify` format" }
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let tpmCer
|
|
564
|
+
|
|
565
|
+
try {
|
|
566
|
+
tpmCer = atob(tpmBody.cer)
|
|
567
|
+
} catch {
|
|
568
|
+
return { error: "`cer` isn't in `btoa` format" }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let Publickey
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
Publickey = atob(tpmBody["Public key"])
|
|
575
|
+
} catch {
|
|
576
|
+
return { error: "`Public key` isn't in `btoa` format" }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const parsedCer = parseASN(Uint8Array.from(
|
|
580
|
+
tpmCer,
|
|
581
|
+
(character) => {
|
|
582
|
+
return character.charCodeAt(0)
|
|
583
|
+
}
|
|
584
|
+
)).children
|
|
585
|
+
|
|
586
|
+
if (parsedCer[2] && parsedCer[0].children[6]?.children[1]) {
|
|
587
|
+
const /**@type {{ [key: string]: parsedASN }}*/ cerFields = {}
|
|
588
|
+
|
|
589
|
+
for (const asn in parsedCer[0].children[7]?.children[0]?.children) {
|
|
590
|
+
if (parsedCer[0].children[7].children[0].children[asn].children[0]) {
|
|
591
|
+
cerFields[String.fromCharCode(...parsedCer[0].children[7].children[0].children[asn].children[0].content)] = parsedCer[0].children[7].children[0].children[asn].children
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (cerFields["+\x06\x01\x05\x05\x07\x01\x01"]?.[1].children[0]?.children[0]?.children[1]) {
|
|
596
|
+
const URL = String.fromCharCode(...cerFields["+\x06\x01\x05\x05\x07\x01\x01"][1].children[0].children[0].children[1].content).replace("http://", "https://")
|
|
597
|
+
let TPMModel, TPMVersion, urlDomain
|
|
598
|
+
const signatureBytes = parsedCer[2].content.slice(1)
|
|
599
|
+
let cerSignature
|
|
600
|
+
|
|
601
|
+
if (URL === "https://www.nuvoton.com/security/NTC-TPM-EK-Cert/Nuvoton TPM Root CA 2111.cer") {
|
|
602
|
+
TPMModel = cerFields["U\x1d\x11"]?.[2]?.children[0]?.children[0]?.children[0]?.children[0]?.children[1]?.children[1]
|
|
603
|
+
TPMVersion = cerFields["U\x1d\x11"]?.[2]?.children[0]?.children[0]?.children[0]?.children[0]?.children[2]?.children[1]
|
|
604
|
+
urlDomain = "nuvoton"
|
|
605
|
+
} else {
|
|
606
|
+
TPMModel = cerFields["U\x1d\x11"]?.[2]?.children[0]?.children[0]?.children[0]?.children[1]?.children[0]?.children[1]
|
|
607
|
+
TPMVersion = cerFields["U\x1d\x11"]?.[2]?.children[0]?.children[0]?.children[0]?.children[2]?.children[0]?.children[1]
|
|
608
|
+
|
|
609
|
+
if (URL === "https://secure.globalsign.com/stmtpmekint06.crt") {
|
|
610
|
+
urlDomain = "globalsign"
|
|
611
|
+
cerSignature = signatureBytes
|
|
612
|
+
} else if (URL.startsWith("https://ftpm.amd.com/pki/aia/")) {
|
|
613
|
+
urlDomain = "amd"
|
|
614
|
+
cerSignature = signatureBytes
|
|
615
|
+
} else if (URL.startsWith("https://trustedservices.intel.com/content/CRL/ekcert/")) {
|
|
616
|
+
urlDomain = "intel"
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (TPMModel && TPMVersion && urlDomain) {
|
|
621
|
+
if (!cerSignature) {
|
|
622
|
+
const ecdsaSignature = parseASN(signatureBytes).children
|
|
623
|
+
|
|
624
|
+
if (ecdsaSignature[1]) {
|
|
625
|
+
cerSignature = Uint8Array.from([...ecdsaSignature[0].content, ...ecdsaSignature[1].content.slice(1)])
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (cerSignature) {
|
|
630
|
+
let verifyCer
|
|
631
|
+
|
|
632
|
+
if (urlDomain === "nuvoton") {
|
|
633
|
+
verifyCer = crypto.subtle.verify({ name: "ecdsa", hash: "sha-256" }, await nuvotonKey, cerSignature, parsedCer[0].asn)
|
|
634
|
+
} else if (urlDomain === "globalsign") {
|
|
635
|
+
verifyCer = crypto.subtle.verify("rsassa-pkcs1-v1_5", await globalsignKey, cerSignature, parsedCer[0].asn)
|
|
636
|
+
} else {
|
|
637
|
+
const Publickey = parseASN(await (await fetch(URL)).bytes()).children[0].children[6]?.children[1].content
|
|
638
|
+
|
|
639
|
+
if (Publickey) {
|
|
640
|
+
if (urlDomain === "amd") {
|
|
641
|
+
verifyCer = crypto.subtle.verify("rsassa-pkcs1-v1_5", await crypto.subtle.importKey("jwk", { kty: "RSA", n: btoa(String.fromCharCode(...Publickey.slice(10, 266))), e: "AQAB" }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]), cerSignature, parsedCer[0].asn)
|
|
642
|
+
} else {
|
|
643
|
+
verifyCer = crypto.subtle.verify({ name: "ecdsa", hash: "sha-256" }, await crypto.subtle.importKey("raw", Publickey.slice(1), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]), cerSignature, parsedCer[0].asn)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (await verifyCer) {
|
|
649
|
+
const identityRandom = crypto.getRandomValues(new Uint8Array(32))
|
|
650
|
+
const integrityKey = await crypto.subtle.importKey("raw", identityRandom, { name: "HMAC", hash: "SHA-256" }, false, ["sign"])
|
|
651
|
+
const /**@type {number[]}*/ key = []
|
|
652
|
+
|
|
653
|
+
const storageDigest = [
|
|
654
|
+
0,
|
|
655
|
+
11,
|
|
656
|
+
...new Uint8Array(await crypto.subtle.digest(
|
|
657
|
+
"sha-256",
|
|
658
|
+
Uint8Array.from([
|
|
659
|
+
0,
|
|
660
|
+
1,
|
|
661
|
+
0,
|
|
662
|
+
11,
|
|
663
|
+
0,
|
|
664
|
+
4,
|
|
665
|
+
0,
|
|
666
|
+
114,
|
|
667
|
+
0,
|
|
668
|
+
0,
|
|
669
|
+
0,
|
|
670
|
+
16,
|
|
671
|
+
0,
|
|
672
|
+
16,
|
|
673
|
+
12,
|
|
674
|
+
0,
|
|
675
|
+
0,
|
|
676
|
+
0,
|
|
677
|
+
0,
|
|
678
|
+
0,
|
|
679
|
+
1,
|
|
680
|
+
128,
|
|
681
|
+
...Array.from(
|
|
682
|
+
Publickey,
|
|
683
|
+
(character) => {
|
|
684
|
+
return character.charCodeAt(0)
|
|
685
|
+
}
|
|
686
|
+
)
|
|
687
|
+
])
|
|
688
|
+
))
|
|
689
|
+
]
|
|
690
|
+
|
|
691
|
+
const storageKey = await crypto.subtle.importKey("raw", (await crypto.subtle.sign("hmac", integrityKey, Uint8Array.from([0, 0, 0, 1, 83, 84, 79, 82, 65, 71, 69, 0, ...storageDigest, 0, 0, 0, 128]))).slice(0, 16), "AES-CBC", false, ["encrypt"])
|
|
692
|
+
let keyBytes = new Uint8Array(16)
|
|
693
|
+
const prependedIdentity = [0, 32, ...identityRandom]
|
|
694
|
+
|
|
695
|
+
const encryptKey = async (/**@type {number}*/ bytes) => {
|
|
696
|
+
keyBytes = new Uint8Array((await crypto.subtle.encrypt(aesCBC, storageKey, keyBytes)).slice(0, bytes)).map((byte, index) => {
|
|
697
|
+
return prependedIdentity[index + key.length] ^ byte
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
key.push(...keyBytes)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
await encryptKey(16)
|
|
704
|
+
await encryptKey(16)
|
|
705
|
+
await encryptKey(2)
|
|
706
|
+
const Validfrom = new Date
|
|
707
|
+
|
|
708
|
+
const validMonth = Array.from(
|
|
709
|
+
Validfrom.toISOString().slice(5, 7),
|
|
710
|
+
(character) => {
|
|
711
|
+
return character.charCodeAt(0)
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
const Valid = (/**@type {Date}*/ Valid) => {
|
|
716
|
+
return [
|
|
717
|
+
...Array.from(
|
|
718
|
+
Valid.toISOString().slice(2, 4),
|
|
719
|
+
(character) => {
|
|
720
|
+
return character.charCodeAt(0)
|
|
721
|
+
}
|
|
722
|
+
),
|
|
723
|
+
...validMonth,
|
|
724
|
+
48,
|
|
725
|
+
49,
|
|
726
|
+
48,
|
|
727
|
+
48,
|
|
728
|
+
48,
|
|
729
|
+
48
|
|
730
|
+
]
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const Validto = new Date(Validfrom)
|
|
734
|
+
Validto.setFullYear(Validto.getFullYear() + 1)
|
|
735
|
+
const Subject = C(Fields.C)
|
|
736
|
+
const L = []
|
|
737
|
+
|
|
738
|
+
if (Fields.L) {
|
|
739
|
+
L.push(...Array.from(
|
|
740
|
+
Fields.L,
|
|
741
|
+
(character) => {
|
|
742
|
+
return character.charCodeAt(0)
|
|
743
|
+
}
|
|
744
|
+
))
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
let Issuedto = Fields["Issued to"]
|
|
748
|
+
|
|
749
|
+
if (!Issuedto) {
|
|
750
|
+
Issuedto = ""
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
Subject.push(49, 128, 48, 128, 6, 3, 85, 4, 7, 12, ...prependLength(L), 0, 0, 0, 0, ...O(Fields.O), ...Issued(Issuedto))
|
|
754
|
+
let URL = Fields["On-line Certificate Status Protocol"]
|
|
755
|
+
|
|
756
|
+
if (!URL) {
|
|
757
|
+
URL = ""
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let CertificationAuthorityIssuer = Fields["Certification Authority Issuer"]
|
|
761
|
+
|
|
762
|
+
if (!CertificationAuthorityIssuer) {
|
|
763
|
+
CertificationAuthorityIssuer = ""
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const CodeSigning = await createCer(
|
|
767
|
+
Array.from(Issuedby[Issuedby.length - 1].asn.slice(1)),
|
|
768
|
+
[...Valid(Validfrom), 23, 10, ...Valid(Validto)],
|
|
769
|
+
Subject,
|
|
770
|
+
Publickey,
|
|
771
|
+
signingFields(
|
|
772
|
+
[
|
|
773
|
+
...OnlineCertificateStatusProtocol(URL),
|
|
774
|
+
48,
|
|
775
|
+
128,
|
|
776
|
+
6,
|
|
777
|
+
8,
|
|
778
|
+
43,
|
|
779
|
+
6,
|
|
780
|
+
1,
|
|
781
|
+
5,
|
|
782
|
+
5,
|
|
783
|
+
7,
|
|
784
|
+
48,
|
|
785
|
+
2,
|
|
786
|
+
134,
|
|
787
|
+
...prependLength(Array.from(
|
|
788
|
+
CertificationAuthorityIssuer,
|
|
789
|
+
(character) => {
|
|
790
|
+
return character.charCodeAt(0)
|
|
791
|
+
}
|
|
792
|
+
)),
|
|
793
|
+
0,
|
|
794
|
+
0
|
|
795
|
+
],
|
|
796
|
+
Fields
|
|
797
|
+
),
|
|
798
|
+
128,
|
|
799
|
+
sign
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
/**The model.*/ TPMModel: String.fromCharCode(...TPMModel.content),
|
|
804
|
+
/**The firmware version.*/ TPMVersion: String.fromCharCode(...TPMVersion.content.slice(3)),
|
|
805
|
+
/**JSON that can be provided to `attestTPM.tpmDecrypt`.*/ response: JSON.stringify({
|
|
806
|
+
Intermediate: btoa(String.fromCharCode(...Intermediate)),
|
|
807
|
+
key: btoa(String.fromCharCode(...new Uint8Array(await crypto.subtle.sign("hmac", await crypto.subtle.importKey("raw", await crypto.subtle.sign("hmac", integrityKey, integrityBytes), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]), Uint8Array.from([...key, ...storageDigest]))), ...key, 1, 0, ...new Uint8Array(await crypto.subtle.encrypt({ name: "rsa-oaep", label }, await crypto.subtle.importKey("jwk", { kty: "RSA", n: btoa(String.fromCharCode(...parsedCer[0].children[6].children[1].content.slice(10, 266))), e: "AQAB" }, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]), identityRandom)))),
|
|
808
|
+
"Code Signing": btoa(String.fromCharCode(...new Uint8Array(await crypto.subtle.encrypt(aesCBC, await crypto.subtle.importKey("raw", identityRandom, "AES-CBC", false, ["encrypt"]), CodeSigning.cer))))
|
|
809
|
+
}),
|
|
810
|
+
/**`Code Signing`'s Serial number.*/ "Serial number": btoa(String.fromCharCode(...CodeSigning["Serial number"]))
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return { error: "`cer` isn't a TPM certificate" }
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
throw new Error("`Intermediate` isn't a certificate")
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const keyRequest = (/**@type {Uint8Array}*/ keys) => {
|
|
825
|
+
const keyLength = new Uint8Array(2)
|
|
826
|
+
new DataView(keyLength.buffer).setUint16(0, keys.length + 27)
|
|
827
|
+
return [128, 2, 0, 0, ...keyLength, 0, 0, 1, 87, 129, 0, 0, 1, 0, 0, 0, 9, 64, 0, 0, 9, 0, 0, 0, 0, 0, ...keys]
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export const /**Use the TPM to decrypt a Code Signing certificate.*/ tpmDecrypt = async (
|
|
831
|
+
/**`keys` from `attestTPM.tpmCreate`. @type {Uint8Array}*/ keys,
|
|
832
|
+
/**The parsed `response` from `attestTPM.authorityIssue`. @type {{ Intermediate: string, key: string, "Code Signing": string }}*/ response
|
|
833
|
+
) => {
|
|
834
|
+
let Intermediate
|
|
835
|
+
|
|
836
|
+
try {
|
|
837
|
+
Intermediate = atob(response.Intermediate)
|
|
838
|
+
} catch {
|
|
839
|
+
throw new Error("`Intermediate` isn't in `btoa` format")
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
let responseKey
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
responseKey = atob(response.key)
|
|
846
|
+
} catch {
|
|
847
|
+
throw new Error("`key` isn't in `btoa` format")
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
let CodeSigning
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
CodeSigning = atob(response["Code Signing"])
|
|
854
|
+
} catch {
|
|
855
|
+
throw new Error("`Code Signing` isn't in `btoa` format")
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const Tbsip_Submit_Command = Tbsi_Context_Create()
|
|
859
|
+
|
|
860
|
+
if (Tbsip_Submit_Command(keyRequest(keys))) {
|
|
861
|
+
if (process.env.sessionname) {
|
|
862
|
+
throw new Error("`tpmDecrypt` must run as administrator")
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
Tbsip_Submit_Command([128, 1, 0, 0, 0, 43, 0, 0, 1, 118, 64, 0, 0, 7, 64, 0, 0, 7, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 16, 0, 11])
|
|
866
|
+
Tbsip_Submit_Command([128, 2, 0, 0, 0, 41, 0, 0, 1, 81, 64, 0, 0, 11, 3, 0, 0, 0, 0, 0, 0, 9, 64, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
867
|
+
|
|
868
|
+
const decryptedKey = Tbsip_Submit_Command([
|
|
869
|
+
128,
|
|
870
|
+
2,
|
|
871
|
+
0,
|
|
872
|
+
0,
|
|
873
|
+
1,
|
|
874
|
+
112,
|
|
875
|
+
0,
|
|
876
|
+
0,
|
|
877
|
+
1,
|
|
878
|
+
71,
|
|
879
|
+
128,
|
|
880
|
+
255,
|
|
881
|
+
255,
|
|
882
|
+
255,
|
|
883
|
+
129,
|
|
884
|
+
1,
|
|
885
|
+
0,
|
|
886
|
+
1,
|
|
887
|
+
0,
|
|
888
|
+
0,
|
|
889
|
+
0,
|
|
890
|
+
18,
|
|
891
|
+
64,
|
|
892
|
+
0,
|
|
893
|
+
0,
|
|
894
|
+
9,
|
|
895
|
+
0,
|
|
896
|
+
0,
|
|
897
|
+
0,
|
|
898
|
+
0,
|
|
899
|
+
0,
|
|
900
|
+
3,
|
|
901
|
+
0,
|
|
902
|
+
0,
|
|
903
|
+
0,
|
|
904
|
+
0,
|
|
905
|
+
0,
|
|
906
|
+
0,
|
|
907
|
+
0,
|
|
908
|
+
0,
|
|
909
|
+
0,
|
|
910
|
+
68,
|
|
911
|
+
0,
|
|
912
|
+
32,
|
|
913
|
+
...Array.from(
|
|
914
|
+
responseKey,
|
|
915
|
+
(character) => {
|
|
916
|
+
return character.charCodeAt(0)
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
])?.slice(16, 48)
|
|
920
|
+
|
|
921
|
+
if (decryptedKey) {
|
|
922
|
+
const decryptCer = crypto.subtle.decrypt(
|
|
923
|
+
aesCBC,
|
|
924
|
+
await crypto.subtle.importKey("raw", decryptedKey, "AES-CBC", false, ["decrypt"]),
|
|
925
|
+
Uint8Array.from(
|
|
926
|
+
CodeSigning,
|
|
927
|
+
(character) => {
|
|
928
|
+
return character.charCodeAt(0)
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
let decryptedCer
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
decryptedCer = await decryptCer
|
|
937
|
+
} catch {
|
|
938
|
+
throw new Error("`key` doesn't match `Code Signing`")
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return Uint8Array.from([
|
|
942
|
+
48,
|
|
943
|
+
128,
|
|
944
|
+
6,
|
|
945
|
+
9,
|
|
946
|
+
42,
|
|
947
|
+
134,
|
|
948
|
+
72,
|
|
949
|
+
134,
|
|
950
|
+
247,
|
|
951
|
+
13,
|
|
952
|
+
1,
|
|
953
|
+
7,
|
|
954
|
+
2,
|
|
955
|
+
160,
|
|
956
|
+
128,
|
|
957
|
+
48,
|
|
958
|
+
128,
|
|
959
|
+
2,
|
|
960
|
+
1,
|
|
961
|
+
0,
|
|
962
|
+
49,
|
|
963
|
+
0,
|
|
964
|
+
48,
|
|
965
|
+
11,
|
|
966
|
+
6,
|
|
967
|
+
9,
|
|
968
|
+
42,
|
|
969
|
+
134,
|
|
970
|
+
72,
|
|
971
|
+
134,
|
|
972
|
+
247,
|
|
973
|
+
13,
|
|
974
|
+
1,
|
|
975
|
+
7,
|
|
976
|
+
1,
|
|
977
|
+
160,
|
|
978
|
+
128,
|
|
979
|
+
...new Uint8Array(decryptedCer),
|
|
980
|
+
...Array.from(
|
|
981
|
+
Intermediate,
|
|
982
|
+
(character) => {
|
|
983
|
+
return character.charCodeAt(0)
|
|
984
|
+
}
|
|
985
|
+
),
|
|
986
|
+
0,
|
|
987
|
+
0,
|
|
988
|
+
49,
|
|
989
|
+
0,
|
|
990
|
+
0,
|
|
991
|
+
0,
|
|
992
|
+
0,
|
|
993
|
+
0,
|
|
994
|
+
0,
|
|
995
|
+
0
|
|
996
|
+
])
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
throw new Error("TPM keys don't match `key`")
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
throw new Error("keys don't match TPM")
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export const /**Use the TPM to sign an `.exe`.*/ tpmSign = async (
|
|
1006
|
+
/**`keys` from `attestTPM.tpmCreate`. @type {Uint8Array}*/ keys,
|
|
1007
|
+
/**A `.p7b` of the Code Signing and Intermediate certificates. @type {Uint8Array}*/ p7b,
|
|
1008
|
+
/**An `.exe`. @type {Uint8Array}*/ exe
|
|
1009
|
+
) => {
|
|
1010
|
+
const p7bCers = parseASN(p7b).children[1]?.children[0]?.children[3]
|
|
1011
|
+
|
|
1012
|
+
if (p7bCers?.children[0]?.children[0]?.children[3]) {
|
|
1013
|
+
if (exe[61] === undefined) {
|
|
1014
|
+
throw new Error("invalid `exe`")
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const Tbsip_Submit_Command = Tbsi_Context_Create()
|
|
1018
|
+
|
|
1019
|
+
if (Tbsip_Submit_Command(keyRequest(keys))) {
|
|
1020
|
+
const exeHeader = exe.slice(0, new DataView(exe.buffer).getUint16(60, true) + 168)
|
|
1021
|
+
const exeLength = 8 * Math.ceil(exe.length / 8)
|
|
1022
|
+
const exeContent = [...exe.slice(exeHeader.length + 8), ...Array(exeLength - exe.length)]
|
|
1023
|
+
|
|
1024
|
+
const signatureDigest = Uint8Array.from([
|
|
1025
|
+
48,
|
|
1026
|
+
14,
|
|
1027
|
+
6,
|
|
1028
|
+
10,
|
|
1029
|
+
43,
|
|
1030
|
+
6,
|
|
1031
|
+
1,
|
|
1032
|
+
4,
|
|
1033
|
+
1,
|
|
1034
|
+
130,
|
|
1035
|
+
55,
|
|
1036
|
+
2,
|
|
1037
|
+
1,
|
|
1038
|
+
15,
|
|
1039
|
+
48,
|
|
1040
|
+
0,
|
|
1041
|
+
48,
|
|
1042
|
+
47,
|
|
1043
|
+
48,
|
|
1044
|
+
11,
|
|
1045
|
+
6,
|
|
1046
|
+
9,
|
|
1047
|
+
96,
|
|
1048
|
+
134,
|
|
1049
|
+
72,
|
|
1050
|
+
1,
|
|
1051
|
+
101,
|
|
1052
|
+
3,
|
|
1053
|
+
4,
|
|
1054
|
+
2,
|
|
1055
|
+
1,
|
|
1056
|
+
4,
|
|
1057
|
+
32,
|
|
1058
|
+
...new Uint8Array(await crypto.subtle.digest("sha-256", Uint8Array.from([...exeHeader.slice(0, -80), ...exeHeader.slice(-76), ...exeContent])))
|
|
1059
|
+
])
|
|
1060
|
+
|
|
1061
|
+
const Authenticatedattributes = [49, 48, 47, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 4, 49, 34, 4, 32, ...new Uint8Array(await crypto.subtle.digest("sha-256", signatureDigest))]
|
|
1062
|
+
|
|
1063
|
+
const encryptedAttributes = /**@type {Uint8Array}*/(Tbsip_Submit_Command([
|
|
1064
|
+
128,
|
|
1065
|
+
2,
|
|
1066
|
+
0,
|
|
1067
|
+
0,
|
|
1068
|
+
0,
|
|
1069
|
+
73,
|
|
1070
|
+
0,
|
|
1071
|
+
0,
|
|
1072
|
+
1,
|
|
1073
|
+
93,
|
|
1074
|
+
128,
|
|
1075
|
+
255,
|
|
1076
|
+
255,
|
|
1077
|
+
255,
|
|
1078
|
+
0,
|
|
1079
|
+
0,
|
|
1080
|
+
0,
|
|
1081
|
+
9,
|
|
1082
|
+
64,
|
|
1083
|
+
0,
|
|
1084
|
+
0,
|
|
1085
|
+
9,
|
|
1086
|
+
0,
|
|
1087
|
+
0,
|
|
1088
|
+
0,
|
|
1089
|
+
0,
|
|
1090
|
+
0,
|
|
1091
|
+
0,
|
|
1092
|
+
32,
|
|
1093
|
+
...new Uint8Array(await crypto.subtle.digest("sha-256", Uint8Array.from([49, ...Authenticatedattributes]))),
|
|
1094
|
+
0,
|
|
1095
|
+
20,
|
|
1096
|
+
0,
|
|
1097
|
+
11,
|
|
1098
|
+
128,
|
|
1099
|
+
36,
|
|
1100
|
+
64,
|
|
1101
|
+
0,
|
|
1102
|
+
0,
|
|
1103
|
+
7,
|
|
1104
|
+
0,
|
|
1105
|
+
0
|
|
1106
|
+
])).slice(20, 404)
|
|
1107
|
+
|
|
1108
|
+
const DigitalSignature = [
|
|
1109
|
+
48,
|
|
1110
|
+
128,
|
|
1111
|
+
6,
|
|
1112
|
+
9,
|
|
1113
|
+
42,
|
|
1114
|
+
134,
|
|
1115
|
+
72,
|
|
1116
|
+
134,
|
|
1117
|
+
247,
|
|
1118
|
+
13,
|
|
1119
|
+
1,
|
|
1120
|
+
7,
|
|
1121
|
+
2,
|
|
1122
|
+
160,
|
|
1123
|
+
128,
|
|
1124
|
+
48,
|
|
1125
|
+
128,
|
|
1126
|
+
2,
|
|
1127
|
+
1,
|
|
1128
|
+
0,
|
|
1129
|
+
49,
|
|
1130
|
+
13,
|
|
1131
|
+
48,
|
|
1132
|
+
11,
|
|
1133
|
+
6,
|
|
1134
|
+
9,
|
|
1135
|
+
96,
|
|
1136
|
+
134,
|
|
1137
|
+
72,
|
|
1138
|
+
1,
|
|
1139
|
+
101,
|
|
1140
|
+
3,
|
|
1141
|
+
4,
|
|
1142
|
+
2,
|
|
1143
|
+
1,
|
|
1144
|
+
48,
|
|
1145
|
+
81,
|
|
1146
|
+
6,
|
|
1147
|
+
10,
|
|
1148
|
+
43,
|
|
1149
|
+
6,
|
|
1150
|
+
1,
|
|
1151
|
+
4,
|
|
1152
|
+
1,
|
|
1153
|
+
130,
|
|
1154
|
+
55,
|
|
1155
|
+
2,
|
|
1156
|
+
1,
|
|
1157
|
+
4,
|
|
1158
|
+
160,
|
|
1159
|
+
67,
|
|
1160
|
+
48,
|
|
1161
|
+
65,
|
|
1162
|
+
...signatureDigest,
|
|
1163
|
+
...p7bCers.asn,
|
|
1164
|
+
49,
|
|
1165
|
+
128,
|
|
1166
|
+
48,
|
|
1167
|
+
128,
|
|
1168
|
+
2,
|
|
1169
|
+
1,
|
|
1170
|
+
0,
|
|
1171
|
+
48,
|
|
1172
|
+
128,
|
|
1173
|
+
...p7bCers.children[0].children[0].children[3].asn,
|
|
1174
|
+
...p7bCers.children[0].children[0].children[1].asn,
|
|
1175
|
+
0,
|
|
1176
|
+
0,
|
|
1177
|
+
48,
|
|
1178
|
+
11,
|
|
1179
|
+
6,
|
|
1180
|
+
9,
|
|
1181
|
+
96,
|
|
1182
|
+
134,
|
|
1183
|
+
72,
|
|
1184
|
+
1,
|
|
1185
|
+
101,
|
|
1186
|
+
3,
|
|
1187
|
+
4,
|
|
1188
|
+
2,
|
|
1189
|
+
1,
|
|
1190
|
+
160,
|
|
1191
|
+
...Authenticatedattributes,
|
|
1192
|
+
48,
|
|
1193
|
+
11,
|
|
1194
|
+
6,
|
|
1195
|
+
9,
|
|
1196
|
+
42,
|
|
1197
|
+
134,
|
|
1198
|
+
72,
|
|
1199
|
+
134,
|
|
1200
|
+
247,
|
|
1201
|
+
13,
|
|
1202
|
+
1,
|
|
1203
|
+
1,
|
|
1204
|
+
1,
|
|
1205
|
+
4,
|
|
1206
|
+
130,
|
|
1207
|
+
1,
|
|
1208
|
+
128,
|
|
1209
|
+
...encryptedAttributes,
|
|
1210
|
+
161,
|
|
1211
|
+
128,
|
|
1212
|
+
48,
|
|
1213
|
+
128,
|
|
1214
|
+
6,
|
|
1215
|
+
10,
|
|
1216
|
+
43,
|
|
1217
|
+
6,
|
|
1218
|
+
1,
|
|
1219
|
+
4,
|
|
1220
|
+
1,
|
|
1221
|
+
130,
|
|
1222
|
+
55,
|
|
1223
|
+
3,
|
|
1224
|
+
3,
|
|
1225
|
+
1,
|
|
1226
|
+
49,
|
|
1227
|
+
128,
|
|
1228
|
+
...(await (await fetch("https://timestamp.acs.microsoft.com", { method: "POST", body: Uint8Array.from([48, 55, 2, 1, 0, 48, 47, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 4, 32, ...new Uint8Array(await crypto.subtle.digest("sha-256", encryptedAttributes)), 1, 1, 255]) })).bytes()).slice(9),
|
|
1229
|
+
0,
|
|
1230
|
+
0,
|
|
1231
|
+
0,
|
|
1232
|
+
0,
|
|
1233
|
+
0,
|
|
1234
|
+
0,
|
|
1235
|
+
0,
|
|
1236
|
+
0,
|
|
1237
|
+
0,
|
|
1238
|
+
0,
|
|
1239
|
+
0,
|
|
1240
|
+
0,
|
|
1241
|
+
0,
|
|
1242
|
+
0,
|
|
1243
|
+
0,
|
|
1244
|
+
0
|
|
1245
|
+
]
|
|
1246
|
+
|
|
1247
|
+
const lengthBytes = new Uint8Array(4)
|
|
1248
|
+
new DataView(lengthBytes.buffer).setUint32(0, exeLength, true)
|
|
1249
|
+
const signaturesLength = new Uint8Array(2)
|
|
1250
|
+
const signatureLength = new Uint8Array(2)
|
|
1251
|
+
new DataView(signatureLength.buffer).setUint16(0, DigitalSignature.length + 8, true)
|
|
1252
|
+
const DigitalSignatures = [...signatureLength, 0, 0, 0, 0, 2, 0, ...DigitalSignature, ...Array(8 * Math.ceil(DigitalSignature.length / 8) - DigitalSignature.length)]
|
|
1253
|
+
new DataView(signaturesLength.buffer).setUint16(0, DigitalSignatures.length, true)
|
|
1254
|
+
return Uint8Array.from([...exeHeader, ...lengthBytes, ...signaturesLength, 0, 0, ...exeContent, ...DigitalSignatures])
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
throw new Error("keys don't match TPM")
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
throw new Error("invalid `p7b`")
|
|
1261
|
+
}
|