burnledger 0.8.0 → 0.9.0

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.
Files changed (124) hide show
  1. package/README.md +139 -2
  2. package/dist/cjs/anchor.d.ts +5 -3
  3. package/dist/cjs/anchor.d.ts.map +1 -1
  4. package/dist/cjs/anchor.js +10 -4
  5. package/dist/cjs/anchor.js.map +1 -1
  6. package/dist/cjs/client.d.ts +95 -0
  7. package/dist/cjs/client.d.ts.map +1 -1
  8. package/dist/cjs/client.js +133 -2
  9. package/dist/cjs/client.js.map +1 -1
  10. package/dist/cjs/customer-keys.d.ts +146 -0
  11. package/dist/cjs/customer-keys.d.ts.map +1 -0
  12. package/dist/cjs/customer-keys.js +465 -0
  13. package/dist/cjs/customer-keys.js.map +1 -0
  14. package/dist/cjs/enclave-registration.d.ts +153 -0
  15. package/dist/cjs/enclave-registration.d.ts.map +1 -0
  16. package/dist/cjs/enclave-registration.js +275 -0
  17. package/dist/cjs/enclave-registration.js.map +1 -0
  18. package/dist/cjs/enclave-seal.d.ts +43 -0
  19. package/dist/cjs/enclave-seal.d.ts.map +1 -1
  20. package/dist/cjs/enclave-seal.js +62 -1
  21. package/dist/cjs/enclave-seal.js.map +1 -1
  22. package/dist/cjs/errors.d.ts +10 -0
  23. package/dist/cjs/errors.d.ts.map +1 -1
  24. package/dist/cjs/errors.js +11 -1
  25. package/dist/cjs/errors.js.map +1 -1
  26. package/dist/cjs/index.browser.d.ts +7 -3
  27. package/dist/cjs/index.browser.d.ts.map +1 -1
  28. package/dist/cjs/index.browser.js +6 -3
  29. package/dist/cjs/index.browser.js.map +1 -1
  30. package/dist/cjs/index.d.ts +23 -9
  31. package/dist/cjs/index.d.ts.map +1 -1
  32. package/dist/cjs/index.js +40 -6
  33. package/dist/cjs/index.js.map +1 -1
  34. package/dist/cjs/key-group.d.ts +80 -0
  35. package/dist/cjs/key-group.d.ts.map +1 -0
  36. package/dist/cjs/key-group.js +136 -0
  37. package/dist/cjs/key-group.js.map +1 -0
  38. package/dist/cjs/models.d.ts +60 -0
  39. package/dist/cjs/models.d.ts.map +1 -1
  40. package/dist/cjs/models.js +63 -0
  41. package/dist/cjs/models.js.map +1 -1
  42. package/dist/cjs/status-document.d.ts +25 -0
  43. package/dist/cjs/status-document.d.ts.map +1 -0
  44. package/dist/cjs/status-document.js +62 -0
  45. package/dist/cjs/status-document.js.map +1 -0
  46. package/dist/cjs/verify.d.ts +112 -3
  47. package/dist/cjs/verify.d.ts.map +1 -1
  48. package/dist/cjs/verify.js +393 -146
  49. package/dist/cjs/verify.js.map +1 -1
  50. package/dist/cjs/web-verifier.d.ts +23 -3
  51. package/dist/cjs/web-verifier.d.ts.map +1 -1
  52. package/dist/cjs/web-verifier.js +31 -5
  53. package/dist/cjs/web-verifier.js.map +1 -1
  54. package/dist/esm/anchor.d.ts +5 -3
  55. package/dist/esm/anchor.d.ts.map +1 -1
  56. package/dist/esm/anchor.js +10 -4
  57. package/dist/esm/anchor.js.map +1 -1
  58. package/dist/esm/cli.d.ts +13 -19
  59. package/dist/esm/cli.d.ts.map +1 -1
  60. package/dist/esm/cli.js +109 -81
  61. package/dist/esm/cli.js.map +1 -1
  62. package/dist/esm/client.d.ts +95 -0
  63. package/dist/esm/client.d.ts.map +1 -1
  64. package/dist/esm/client.js +133 -2
  65. package/dist/esm/client.js.map +1 -1
  66. package/dist/esm/customer-keys.d.ts +146 -0
  67. package/dist/esm/customer-keys.d.ts.map +1 -0
  68. package/dist/esm/customer-keys.js +450 -0
  69. package/dist/esm/customer-keys.js.map +1 -0
  70. package/dist/esm/enclave-registration.d.ts +153 -0
  71. package/dist/esm/enclave-registration.d.ts.map +1 -0
  72. package/dist/esm/enclave-registration.js +265 -0
  73. package/dist/esm/enclave-registration.js.map +1 -0
  74. package/dist/esm/enclave-seal.d.ts +43 -0
  75. package/dist/esm/enclave-seal.d.ts.map +1 -1
  76. package/dist/esm/enclave-seal.js +61 -1
  77. package/dist/esm/enclave-seal.js.map +1 -1
  78. package/dist/esm/errors.d.ts +10 -0
  79. package/dist/esm/errors.d.ts.map +1 -1
  80. package/dist/esm/errors.js +10 -0
  81. package/dist/esm/errors.js.map +1 -1
  82. package/dist/esm/index.browser.d.ts +7 -3
  83. package/dist/esm/index.browser.d.ts.map +1 -1
  84. package/dist/esm/index.browser.js +6 -3
  85. package/dist/esm/index.browser.js.map +1 -1
  86. package/dist/esm/index.d.ts +23 -9
  87. package/dist/esm/index.d.ts.map +1 -1
  88. package/dist/esm/index.js +18 -7
  89. package/dist/esm/index.js.map +1 -1
  90. package/dist/esm/key-group.d.ts +80 -0
  91. package/dist/esm/key-group.d.ts.map +1 -0
  92. package/dist/esm/key-group.js +130 -0
  93. package/dist/esm/key-group.js.map +1 -0
  94. package/dist/esm/models.d.ts +60 -0
  95. package/dist/esm/models.d.ts.map +1 -1
  96. package/dist/esm/models.js +59 -0
  97. package/dist/esm/models.js.map +1 -1
  98. package/dist/esm/status-document.d.ts +25 -0
  99. package/dist/esm/status-document.d.ts.map +1 -0
  100. package/dist/esm/status-document.js +59 -0
  101. package/dist/esm/status-document.js.map +1 -0
  102. package/dist/esm/verify.d.ts +112 -3
  103. package/dist/esm/verify.d.ts.map +1 -1
  104. package/dist/esm/verify.js +390 -147
  105. package/dist/esm/verify.js.map +1 -1
  106. package/dist/esm/web-verifier.d.ts +23 -3
  107. package/dist/esm/web-verifier.d.ts.map +1 -1
  108. package/dist/esm/web-verifier.js +27 -5
  109. package/dist/esm/web-verifier.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/anchor.ts +10 -4
  112. package/src/cli.ts +122 -79
  113. package/src/client.ts +198 -6
  114. package/src/customer-keys.ts +550 -0
  115. package/src/enclave-registration.ts +390 -0
  116. package/src/enclave-seal.ts +108 -1
  117. package/src/errors.ts +11 -0
  118. package/src/index.browser.ts +9 -3
  119. package/src/index.ts +48 -6
  120. package/src/key-group.ts +181 -0
  121. package/src/models.ts +131 -0
  122. package/src/status-document.ts +59 -0
  123. package/src/verify.ts +503 -152
  124. package/src/web-verifier.ts +36 -3
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The customer's side of ADR-025: verify the enclave, enrol a key group, rotate
3
+ * it, register a system under it.
4
+ *
5
+ * Request-building and response-checking for the BurnLedger client's methods of
6
+ * the same names, kept in a Node-only module the client imports lazily — the
7
+ * client is also the browser entry's, and these need node:crypto. Nothing here
8
+ * holds a private key: every signature comes from a {@link KeyGroupSigner} (see
9
+ * key-group.ts for why, and for what losing a key costs).
10
+ *
11
+ * WHAT THE CHECKS ON EACH RESPONSE ARE FOR. The API relays every byte, so the
12
+ * documents it returns are only worth what the client checks, and it checks two
13
+ * things. First, that the enclave signed them — under the signing key of an
14
+ * enclave the caller attested against its own pinned PCR0. Without that, a relay
15
+ * could hand the customer a forged enrolment and they would believe themselves
16
+ * registered when nothing was. The key set at /.well-known/burnledger-keys is not
17
+ * the anchor for this: the same API serves it, so a relay that forged a document
18
+ * could publish the forging key beside it. Second, that they are about what the
19
+ * caller asked for. ADR-025 §2 names the load-bearing comparison: the enrolment
20
+ * statement must name the key group the customer generated. A relay that enrolled
21
+ * its own group instead would hand back a genuine, enclave-signed statement
22
+ * naming a key the customer does not hold; this comparison is where that stops
23
+ * being invisible. The enrolment and the authorization must also be for the team
24
+ * the customer named, and the authorization must end exactly at the `not_after`
25
+ * the customer signed and not start in the future: a relay that replayed an
26
+ * older genuine answer, or another team's, would otherwise have the caller
27
+ * believe a fresh window exists. The registration must name this key, this
28
+ * system, this config digest and this connector, or it registered something
29
+ * else.
30
+ */
31
+ import { type CustomerKeyGroup } from "./customer-keys.js";
32
+ import { type EnclaveIdentity, type SealOptions } from "./enclave-seal.js";
33
+ import { type KeyGroupSigner } from "./key-group.js";
34
+ import { type KeyEnrollmentResult, type System, type SystemRegistrationCertificate } from "./models.js";
35
+ type Raw = Record<string, unknown>;
36
+ /** How to check the enclave: the PCR0 you pinned out of band, and optionally
37
+ * your own challenge (otherwise 32 bytes are drawn from the OS CSPRNG). */
38
+ export interface EnclavePinOptions extends SealOptions {
39
+ nonce?: Uint8Array;
40
+ }
41
+ /** What `createRegisteredSystem` did, in order. */
42
+ export interface RegisteredSystem {
43
+ /** The system, created with its config sealed so the API never read it. */
44
+ readonly system: System;
45
+ /** The enclave's registration of that system under your key group. */
46
+ readonly registration: SystemRegistrationCertificate;
47
+ }
48
+ /**
49
+ * The challenge and the query string that carries it. A nonce the checked party
50
+ * chose — including one the API suggested — proves nothing about when the
51
+ * document was produced, so it is drawn here unless the caller brings its own.
52
+ */
53
+ export declare function attestationParams(nonce?: Uint8Array): {
54
+ nonce: Uint8Array;
55
+ params: Record<string, string>;
56
+ };
57
+ /**
58
+ * Verify the document the API returned against this caller's own challenge. The
59
+ * `nonce` the API echoes beside the document is ignored: nothing signs it. The
60
+ * check is against the nonce inside the document, under AWS's signature.
61
+ */
62
+ export declare function identityFromResponse(data: unknown, options: SealOptions & {
63
+ nonce: Uint8Array;
64
+ }): Promise<EnclaveIdentity>;
65
+ /**
66
+ * The ENROLL_KEY request, the key id it enrols and the `not_after` it signs.
67
+ * EVERY member signs.
68
+ */
69
+ export declare function enrollBody(opts: {
70
+ teamId: string;
71
+ group: CustomerKeyGroup;
72
+ signers: readonly KeyGroupSigner[];
73
+ notAfter?: Date;
74
+ }): Promise<{
75
+ body: Raw;
76
+ keyId: string;
77
+ notAfter: string;
78
+ }>;
79
+ /**
80
+ * The ROTATE_KEY request. Both groups sign the same key_rotation_request.v2: the
81
+ * OUTGOING group at its threshold, to spend its authority, and EVERY incoming
82
+ * member, to prove it holds its key.
83
+ */
84
+ export declare function rotateBody(opts: {
85
+ teamId: string;
86
+ previousGroup: CustomerKeyGroup;
87
+ previousSigners: readonly KeyGroupSigner[];
88
+ nextGroup: CustomerKeyGroup;
89
+ nextSigners: readonly KeyGroupSigner[];
90
+ notAfter?: Date;
91
+ }): Promise<{
92
+ body: Raw;
93
+ prevKeyId: string;
94
+ nextKeyId: string;
95
+ notAfter: string;
96
+ }>;
97
+ /**
98
+ * Verify both documents ENROLL_KEY and ROTATE_KEY return under the attested
99
+ * enclave's signing key, then refuse them unless they name the group this caller
100
+ * holds and the team it asked for, link to the predecessor it expects, end
101
+ * exactly at the `notAfter` (whole-second UTC) it signed, and do not start in
102
+ * the future. `teamId` is the team the request asserted; the SDK always asserts
103
+ * one, so there is no request this check has no team for.
104
+ *
105
+ * The end must be EXACT. A protocol-12 enclave ends the grant at the signed
106
+ * bound, to the second, so any other end answers some other request -- which is
107
+ * what a relay replaying an older genuine response, or dropping a renewal,
108
+ * hands back.
109
+ */
110
+ export declare function checkEnrollment(data: unknown, opts: {
111
+ enclave: EnclaveIdentity;
112
+ keyId: string;
113
+ prevKeyId: string | undefined;
114
+ teamId: string;
115
+ notAfter: string;
116
+ /** The time to judge `not_before` against. Defaults to this machine's clock. */
117
+ now?: Date;
118
+ }): Promise<KeyEnrollmentResult>;
119
+ /** The exact bytes sealed, and therefore the bytes `config_digest` is over. */
120
+ export declare function configBytes(config: Record<string, unknown>): Uint8Array;
121
+ /**
122
+ * The REGISTER_SYSTEM request, its key id and the config digest it signs.
123
+ *
124
+ * `config` must be the EXACT bytes submitted when the system was created — the
125
+ * plaintext that was sealed, or the `connection_config` JSON text as sent. An
126
+ * object is refused: re-serializing it produces bytes that may differ by one
127
+ * space from what the enclave decrypts, and the refusal would be a 401 with no
128
+ * way to see why.
129
+ */
130
+ export declare function registrationBody(opts: {
131
+ teamId: string;
132
+ group: CustomerKeyGroup;
133
+ signers: readonly KeyGroupSigner[];
134
+ systemId: string;
135
+ config: Uint8Array | string;
136
+ queryTemplate: string;
137
+ connectorType: string;
138
+ }): Promise<{
139
+ body: Raw;
140
+ keyId: string;
141
+ configDigest: Uint8Array;
142
+ }>;
143
+ /** Verify the registration under the attested enclave's signing key, then
144
+ * refuse it unless it binds what this caller signed. */
145
+ export declare function checkRegistration(data: unknown, opts: {
146
+ enclave: EnclaveIdentity;
147
+ keyId: string;
148
+ systemId: string;
149
+ configDigest: Uint8Array;
150
+ connectorType: string;
151
+ }): Promise<SystemRegistrationCertificate>;
152
+ export {};
153
+ //# sourceMappingURL=enclave-registration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enclave-registration.d.ts","sourceRoot":"","sources":["../../src/enclave-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,EASL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,MAAM,EACX,KAAK,6BAA6B,EACnC,MAAM,aAAa,CAAC;AAGrB,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC;2EAC2E;AAC3E,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,6BAA6B,CAAC;CACtD;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG;IACrD,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAGA;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GAC3C,OAAO,CAAC,eAAe,CAAC,CAM1B;AAYD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB1D;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,gBAAgB,CAAC;IAChC,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IACJ,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,OAAO,CAAC,mBAAmB,CAAC,CAuD9B;AAID,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAEvE;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,UAAU,CAAA;CAAE,CAAC,CA8BlE;AAED;wDACwD;AACxD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IACJ,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB,GACA,OAAO,CAAC,6BAA6B,CAAC,CAuBxC"}
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ /**
3
+ * The customer's side of ADR-025: verify the enclave, enrol a key group, rotate
4
+ * it, register a system under it.
5
+ *
6
+ * Request-building and response-checking for the BurnLedger client's methods of
7
+ * the same names, kept in a Node-only module the client imports lazily — the
8
+ * client is also the browser entry's, and these need node:crypto. Nothing here
9
+ * holds a private key: every signature comes from a {@link KeyGroupSigner} (see
10
+ * key-group.ts for why, and for what losing a key costs).
11
+ *
12
+ * WHAT THE CHECKS ON EACH RESPONSE ARE FOR. The API relays every byte, so the
13
+ * documents it returns are only worth what the client checks, and it checks two
14
+ * things. First, that the enclave signed them — under the signing key of an
15
+ * enclave the caller attested against its own pinned PCR0. Without that, a relay
16
+ * could hand the customer a forged enrolment and they would believe themselves
17
+ * registered when nothing was. The key set at /.well-known/burnledger-keys is not
18
+ * the anchor for this: the same API serves it, so a relay that forged a document
19
+ * could publish the forging key beside it. Second, that they are about what the
20
+ * caller asked for. ADR-025 §2 names the load-bearing comparison: the enrolment
21
+ * statement must name the key group the customer generated. A relay that enrolled
22
+ * its own group instead would hand back a genuine, enclave-signed statement
23
+ * naming a key the customer does not hold; this comparison is where that stops
24
+ * being invisible. The enrolment and the authorization must also be for the team
25
+ * the customer named, and the authorization must end exactly at the `not_after`
26
+ * the customer signed and not start in the future: a relay that replayed an
27
+ * older genuine answer, or another team's, would otherwise have the caller
28
+ * believe a fresh window exists. The registration must name this key, this
29
+ * system, this config digest and this connector, or it registered something
30
+ * else.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.attestationParams = attestationParams;
34
+ exports.identityFromResponse = identityFromResponse;
35
+ exports.enrollBody = enrollBody;
36
+ exports.rotateBody = rotateBody;
37
+ exports.checkEnrollment = checkEnrollment;
38
+ exports.configBytes = configBytes;
39
+ exports.registrationBody = registrationBody;
40
+ exports.checkRegistration = checkRegistration;
41
+ const node_crypto_1 = require("node:crypto");
42
+ const crypto_node_js_1 = require("./crypto-node.js");
43
+ const customer_keys_js_1 = require("./customer-keys.js");
44
+ const enclave_seal_js_1 = require("./enclave-seal.js");
45
+ const errors_js_1 = require("./errors.js");
46
+ const key_group_js_1 = require("./key-group.js");
47
+ const models_js_1 = require("./models.js");
48
+ const verify_js_1 = require("./verify.js");
49
+ /** Mirrors core.DefaultAuthorizationWindow, the longest window the enclave
50
+ * grants: a request that names no `notAfter` asks for all of it. */
51
+ const DEFAULT_AUTHORIZATION_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
52
+ /** How far ahead of this machine's clock an authorization may start: room for
53
+ * an enclave clock that runs a little ahead, as core's notAfterSkew leaves room
54
+ * for a customer clock that does. Not room for a document from the future. */
55
+ const NOT_BEFORE_SKEW_MS = 5 * 60 * 1000;
56
+ function toHex(bytes) {
57
+ return Buffer.from(bytes).toString("hex");
58
+ }
59
+ function sha256(bytes) {
60
+ return new Uint8Array((0, node_crypto_1.createHash)("sha256").update(bytes).digest());
61
+ }
62
+ function boundOf(notAfter) {
63
+ return notAfter ?? new Date(Date.now() + DEFAULT_AUTHORIZATION_WINDOW_MS);
64
+ }
65
+ // --- ATTEST_IDENTITY ----------------------------------------------------------
66
+ /**
67
+ * The challenge and the query string that carries it. A nonce the checked party
68
+ * chose — including one the API suggested — proves nothing about when the
69
+ * document was produced, so it is drawn here unless the caller brings its own.
70
+ */
71
+ function attestationParams(nonce) {
72
+ const drawn = nonce ?? node_crypto_1.webcrypto.getRandomValues(new Uint8Array(enclave_seal_js_1.IDENTITY_NONCE_SIZE));
73
+ return { nonce: drawn, params: { nonce: Buffer.from(drawn).toString("base64url") } };
74
+ }
75
+ /**
76
+ * Verify the document the API returned against this caller's own challenge. The
77
+ * `nonce` the API echoes beside the document is ignored: nothing signs it. The
78
+ * check is against the nonce inside the document, under AWS's signature.
79
+ */
80
+ async function identityFromResponse(data, options) {
81
+ const raw = data?.nsm_attestation;
82
+ if (typeof raw !== "string") {
83
+ throw new errors_js_1.VerificationError("the attestation response carries no nsm_attestation");
84
+ }
85
+ return (0, enclave_seal_js_1.verifyEnclaveIdentity)(new Uint8Array(Buffer.from(raw, "base64")), options);
86
+ }
87
+ // --- ENROLL_KEY / ROTATE_KEY --------------------------------------------------
88
+ function groupBody(group, signatures) {
89
+ return {
90
+ threshold: group.threshold,
91
+ member_keys: group.members.map(toHex),
92
+ signatures,
93
+ };
94
+ }
95
+ /**
96
+ * The ENROLL_KEY request, the key id it enrols and the `not_after` it signs.
97
+ * EVERY member signs.
98
+ */
99
+ async function enrollBody(opts) {
100
+ const keyId = await (0, customer_keys_js_1.customerKeyId)(crypto_node_js_1.nodeCrypto, opts.group);
101
+ const bound = boundOf(opts.notAfter);
102
+ const payload = (0, customer_keys_js_1.buildKeyEnrollmentRequestPayload)({
103
+ assertedTeamId: opts.teamId,
104
+ customerKeyId: keyId,
105
+ notAfter: bound,
106
+ });
107
+ const signatures = await (0, key_group_js_1.signWithGroup)(payload, opts.group, opts.signers, opts.group.members.length);
108
+ const notAfter = (0, customer_keys_js_1.keyTime)(bound);
109
+ return {
110
+ body: {
111
+ asserted_team_id: opts.teamId.toLowerCase(),
112
+ not_after: notAfter,
113
+ ...groupBody(opts.group, signatures),
114
+ },
115
+ keyId,
116
+ notAfter,
117
+ };
118
+ }
119
+ /**
120
+ * The ROTATE_KEY request. Both groups sign the same key_rotation_request.v2: the
121
+ * OUTGOING group at its threshold, to spend its authority, and EVERY incoming
122
+ * member, to prove it holds its key.
123
+ */
124
+ async function rotateBody(opts) {
125
+ const prevKeyId = await (0, customer_keys_js_1.customerKeyId)(crypto_node_js_1.nodeCrypto, opts.previousGroup);
126
+ const nextKeyId = await (0, customer_keys_js_1.customerKeyId)(crypto_node_js_1.nodeCrypto, opts.nextGroup);
127
+ const bound = boundOf(opts.notAfter);
128
+ const payload = (0, customer_keys_js_1.buildKeyRotationRequestPayload)({
129
+ assertedTeamId: opts.teamId,
130
+ prevKeyId,
131
+ nextKeyId,
132
+ notAfter: bound,
133
+ });
134
+ const signatures = await (0, key_group_js_1.signWithGroup)(payload, opts.previousGroup, opts.previousSigners, opts.previousGroup.threshold);
135
+ const nextSignatures = await (0, key_group_js_1.signWithGroup)(payload, opts.nextGroup, opts.nextSigners, opts.nextGroup.members.length);
136
+ const notAfter = (0, customer_keys_js_1.keyTime)(bound);
137
+ return {
138
+ body: {
139
+ asserted_team_id: opts.teamId.toLowerCase(),
140
+ prev_key_id: prevKeyId,
141
+ not_after: notAfter,
142
+ next_signatures: nextSignatures,
143
+ ...groupBody(opts.nextGroup, signatures),
144
+ },
145
+ prevKeyId,
146
+ nextKeyId,
147
+ notAfter,
148
+ };
149
+ }
150
+ /**
151
+ * Verify both documents ENROLL_KEY and ROTATE_KEY return under the attested
152
+ * enclave's signing key, then refuse them unless they name the group this caller
153
+ * holds and the team it asked for, link to the predecessor it expects, end
154
+ * exactly at the `notAfter` (whole-second UTC) it signed, and do not start in
155
+ * the future. `teamId` is the team the request asserted; the SDK always asserts
156
+ * one, so there is no request this check has no team for.
157
+ *
158
+ * The end must be EXACT. A protocol-12 enclave ends the grant at the signed
159
+ * bound, to the second, so any other end answers some other request -- which is
160
+ * what a relay replaying an older genuine response, or dropping a renewal,
161
+ * hands back.
162
+ */
163
+ async function checkEnrollment(data, opts) {
164
+ const raw = (data ?? {});
165
+ await (0, customer_keys_js_1.verifyKeyEnrollmentStatement)(crypto_node_js_1.nodeCrypto, raw.enrollment, opts.enclave.signingKey);
166
+ await (0, customer_keys_js_1.verifyTeamAuthorization)(crypto_node_js_1.nodeCrypto, raw.authorization, opts.enclave.signingKey);
167
+ const result = (0, models_js_1.parseKeyEnrollmentResult)(raw);
168
+ const { enrollment, authorization } = result;
169
+ // One comparison covers the members and threshold too: a key id is a hash
170
+ // over both, and a verified statement's id is the one its own group derives.
171
+ if (enrollment.customerKeyId !== opts.keyId || authorization.customerKeyId !== opts.keyId) {
172
+ throw new errors_js_1.VerificationError(`the enclave's statement names key ${JSON.stringify(enrollment.customerKeyId)} ` +
173
+ `(authorization ${JSON.stringify(authorization.customerKeyId)}), not the group you ` +
174
+ `enrolled (${JSON.stringify(opts.keyId)}). Do not use it: this is what a substituted key looks like.`);
175
+ }
176
+ if (authorization.prevKeyId !== opts.prevKeyId) {
177
+ throw new errors_js_1.VerificationError(`the authorization links to predecessor ${JSON.stringify(authorization.prevKeyId ?? null)}, ` +
178
+ `expected ${JSON.stringify(opts.prevKeyId ?? null)}`);
179
+ }
180
+ const team = opts.teamId.toLowerCase();
181
+ for (const [what, named] of [
182
+ ["enrolment statement", enrollment.assertedTeamId],
183
+ ["authorization", authorization.assertedTeamId],
184
+ ]) {
185
+ if (named.toLowerCase() !== team) {
186
+ throw new errors_js_1.VerificationError(`the ${what} is for team ${named}, not the team you asked for (${team}). ` +
187
+ "Do not use it: a relay replaying another team's answer would produce exactly this.");
188
+ }
189
+ }
190
+ // Compared as the whole-second strings both sides sign, not as parsed
191
+ // dates: a Date has milliseconds, and a fraction on the wire is nobody's.
192
+ const granted = (0, verify_js_1.formatTimestamp)(raw.authorization.not_after, "authorization.not_after");
193
+ if (granted !== opts.notAfter) {
194
+ throw new errors_js_1.VerificationError(`the authorization ends at ${granted}, not at the not_after you signed (${opts.notAfter}). ` +
195
+ "The enclave ends the grant exactly there, so this answers some other request; " +
196
+ "a relay replaying an older response would produce exactly this.");
197
+ }
198
+ const now = opts.now ?? new Date();
199
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
200
+ throw new TypeError("now must be a valid Date");
201
+ }
202
+ if (authorization.notBefore.getTime() > now.getTime() + NOT_BEFORE_SKEW_MS) {
203
+ const starts = (0, verify_js_1.formatTimestamp)(raw.authorization.not_before, "authorization.not_before");
204
+ throw new errors_js_1.VerificationError(`the authorization starts at ${starts}, more than five minutes after this machine's clock ` +
205
+ `(${(0, customer_keys_js_1.keyTime)(now)}): the answer to a request just made cannot start in the future`);
206
+ }
207
+ return result;
208
+ }
209
+ // --- REGISTER_SYSTEM ----------------------------------------------------------
210
+ /** The exact bytes sealed, and therefore the bytes `config_digest` is over. */
211
+ function configBytes(config) {
212
+ return new TextEncoder().encode(JSON.stringify(config));
213
+ }
214
+ /**
215
+ * The REGISTER_SYSTEM request, its key id and the config digest it signs.
216
+ *
217
+ * `config` must be the EXACT bytes submitted when the system was created — the
218
+ * plaintext that was sealed, or the `connection_config` JSON text as sent. An
219
+ * object is refused: re-serializing it produces bytes that may differ by one
220
+ * space from what the enclave decrypts, and the refusal would be a 401 with no
221
+ * way to see why.
222
+ */
223
+ async function registrationBody(opts) {
224
+ const config = typeof opts.config === "string" ? new TextEncoder().encode(opts.config) : opts.config;
225
+ if (!(config instanceof Uint8Array)) {
226
+ throw new TypeError("config must be the exact bytes (or text) submitted when the system was created, " +
227
+ "not a re-serializable object");
228
+ }
229
+ const keyId = await (0, customer_keys_js_1.customerKeyId)(crypto_node_js_1.nodeCrypto, opts.group);
230
+ const configDigest = sha256(config);
231
+ const payload = (0, customer_keys_js_1.buildSystemRegistrationRequestPayload)({
232
+ assertedTeamId: opts.teamId,
233
+ systemId: opts.systemId,
234
+ configDigest,
235
+ queryTemplateSha256: sha256(new TextEncoder().encode(opts.queryTemplate)),
236
+ connectorType: opts.connectorType,
237
+ customerKeyId: keyId,
238
+ });
239
+ const signatures = await (0, key_group_js_1.signWithGroup)(payload, opts.group, opts.signers, opts.group.threshold);
240
+ return {
241
+ body: {
242
+ asserted_team_id: opts.teamId.toLowerCase(),
243
+ system_id: opts.systemId.toLowerCase(),
244
+ query_template: opts.queryTemplate,
245
+ ...groupBody(opts.group, signatures),
246
+ },
247
+ keyId,
248
+ configDigest,
249
+ };
250
+ }
251
+ /** Verify the registration under the attested enclave's signing key, then
252
+ * refuse it unless it binds what this caller signed. */
253
+ async function checkRegistration(data, opts) {
254
+ const raw = data?.registration;
255
+ await (0, customer_keys_js_1.verifySystemRegistration)(crypto_node_js_1.nodeCrypto, raw, opts.enclave.signingKey);
256
+ const registration = (0, models_js_1.parseSystemRegistrationCertificate)(raw);
257
+ const wrong = [];
258
+ if (registration.customerKeyId !== opts.keyId) {
259
+ wrong.push(`customer_key_id is ${JSON.stringify(registration.customerKeyId)}, expected ${JSON.stringify(opts.keyId)}`);
260
+ }
261
+ if (String(registration.systemId).toLowerCase() !== opts.systemId.toLowerCase()) {
262
+ wrong.push(`system_id is ${JSON.stringify(registration.systemId)}, expected ${JSON.stringify(opts.systemId)}`);
263
+ }
264
+ if (toHex(registration.configDigest) !== toHex(opts.configDigest)) {
265
+ wrong.push(`config_digest is ${toHex(registration.configDigest)}, expected ${toHex(opts.configDigest)}`);
266
+ }
267
+ if (registration.connectorType !== opts.connectorType) {
268
+ wrong.push(`connector_type is ${JSON.stringify(registration.connectorType)}, expected ${JSON.stringify(opts.connectorType)}`);
269
+ }
270
+ if (wrong.length > 0) {
271
+ throw new errors_js_1.VerificationError(`the enclave's registration does not bind what you signed: ${wrong.join(", ")}`);
272
+ }
273
+ return registration;
274
+ }
275
+ //# sourceMappingURL=enclave-registration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enclave-registration.js","sourceRoot":"","sources":["../../src/enclave-registration.ts"],"names":[],"mappings":"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"}
@@ -98,4 +98,47 @@ export declare function sealToKey(recipientPublicKey: Uint8Array, plaintext: Uin
98
98
  * one from an enclave the host itself booted — would pass.
99
99
  */
100
100
  export declare function verifyEnclaveConfigSealKey(attestationDocument: Uint8Array, options: SealOptions): Promise<Uint8Array>;
101
+ /** Mirrors enclave.AttestIdentityNonceSize. */
102
+ export declare const IDENTITY_NONCE_SIZE = 32;
103
+ /**
104
+ * What an ATTEST_IDENTITY document establishes once verified: which keys, held
105
+ * by an enclave running which image, answering THIS caller's challenge.
106
+ */
107
+ export interface EnclaveIdentity {
108
+ /** The X25519 key to seal connection configs to. */
109
+ readonly configSealKey: Uint8Array;
110
+ /** The enclave's Ed25519 signing key, read out of the signed document. */
111
+ readonly signingKey: Uint8Array;
112
+ /** `dp_k_` + hex(SHA-256(signingKey)) — the id /.well-known/burnledger-keys publishes. */
113
+ readonly signingKeyId: string;
114
+ /** The PCR0 the document reports, hex. Equal to the one pinned, or this would have thrown. */
115
+ readonly pcr0: string;
116
+ }
117
+ export interface IdentityOptions extends SealOptions {
118
+ /**
119
+ * The 32-byte challenge sent with the request. Draw it from your own entropy:
120
+ * a nonce the party being checked chose proves nothing about when the
121
+ * document was produced.
122
+ */
123
+ nonce: Uint8Array;
124
+ }
125
+ /**
126
+ * Verify an ATTEST_IDENTITY document (`GET /v1/enclave/attestation?nonce=`).
127
+ *
128
+ * Port of enclave.VerifyIdentityAttestation: everything
129
+ * {@link verifyEnclaveConfigSealKey} checks, and then that the document carries
130
+ * exactly the nonce this caller sent and binds a 32-byte Ed25519 signing key in
131
+ * `user_data`.
132
+ *
133
+ * THE NONCE CHECK IS THE POINT. The other checks pass for every document this
134
+ * enclave image has ever produced, including one captured months ago and
135
+ * replayed by a relay that has since swapped keys. Only a document carrying the
136
+ * challenge this caller drew, under AWS's signature, is a statement about now.
137
+ *
138
+ * What a pass means, and no more: these keys are held by an AWS Nitro Enclave
139
+ * running an image that measures to the pinned PCR0, and it answered this
140
+ * challenge within five minutes of `now`. It does not mean the image was built
141
+ * from any particular source.
142
+ */
143
+ export declare function verifyEnclaveIdentity(attestationDocument: Uint8Array, options: IdentityOptions): Promise<EnclaveIdentity>;
101
144
  //# sourceMappingURL=enclave-seal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"enclave-seal.d.ts","sourceRoot":"","sources":["../../src/enclave-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,yBAAmE,CAAC;AAG/F,eAAO,MAAM,WAAW,QAAoD,CAAC;AAG7E;;;GAGG;AACH,eAAO,MAAM,iBAAiB,6xBAc7B,CAAC;AAMF,+EAA+E;AAC/E,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,sEAAsE;IACtE,GAAG,CAAC,EAAE,IAAI,CAAC;IACX;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,UAAU,EACrD,mBAAmB,EAAE,UAAU,EAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,kBAAkB,EAAE,UAAU,EAC9B,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,UAAU,CAAC,CAuCrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,0BAA0B,CAC9C,mBAAmB,EAAE,UAAU,EAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CA8CrB"}
1
+ {"version":3,"file":"enclave-seal.d.ts","sourceRoot":"","sources":["../../src/enclave-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAUH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,yBAAmE,CAAC;AAG/F,eAAO,MAAM,WAAW,QAAoD,CAAC;AAG7E;;;GAGG;AACH,eAAO,MAAM,iBAAiB,6xBAc7B,CAAC;AAMF,+EAA+E;AAC/E,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,sEAAsE;IACtE,GAAG,CAAC,EAAE,IAAI,CAAC;IACX;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,UAAU,EACrD,mBAAmB,EAAE,UAAU,EAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,kBAAkB,EAAE,UAAU,EAC9B,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,UAAU,CAAC,CAuCrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,0BAA0B,CAC9C,mBAAmB,EAAE,UAAU,EAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAErB;AAED,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD;;;;OAIG;IACH,KAAK,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CACzC,mBAAmB,EAAE,UAAU,EAC/B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC,CAmC1B"}
@@ -35,10 +35,11 @@
35
35
  * point.
36
36
  */
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.EnclaveAttestationError = exports.NITRO_ROOT_G1_PEM = exports.HEADER_SIZE = exports.ENVELOPE_MAGIC = void 0;
38
+ exports.IDENTITY_NONCE_SIZE = exports.EnclaveAttestationError = exports.NITRO_ROOT_G1_PEM = exports.HEADER_SIZE = exports.ENVELOPE_MAGIC = void 0;
39
39
  exports.sealConnectionConfig = sealConnectionConfig;
40
40
  exports.sealToKey = sealToKey;
41
41
  exports.verifyEnclaveConfigSealKey = verifyEnclaveConfigSealKey;
42
+ exports.verifyEnclaveIdentity = verifyEnclaveIdentity;
42
43
  const node_crypto_1 = require("node:crypto");
43
44
  /**
44
45
  * Envelope wire format (v1). Must match enclave/configsealkey.go byte for byte:
@@ -140,6 +141,62 @@ async function sealToKey(recipientPublicKey, plaintext) {
140
141
  * one from an enclave the host itself booted — would pass.
141
142
  */
142
143
  async function verifyEnclaveConfigSealKey(attestationDocument, options) {
144
+ return configSealKeyOf(verifyDocument(attestationDocument, options));
145
+ }
146
+ /** Mirrors enclave.AttestIdentityNonceSize. */
147
+ exports.IDENTITY_NONCE_SIZE = 32;
148
+ /**
149
+ * Verify an ATTEST_IDENTITY document (`GET /v1/enclave/attestation?nonce=`).
150
+ *
151
+ * Port of enclave.VerifyIdentityAttestation: everything
152
+ * {@link verifyEnclaveConfigSealKey} checks, and then that the document carries
153
+ * exactly the nonce this caller sent and binds a 32-byte Ed25519 signing key in
154
+ * `user_data`.
155
+ *
156
+ * THE NONCE CHECK IS THE POINT. The other checks pass for every document this
157
+ * enclave image has ever produced, including one captured months ago and
158
+ * replayed by a relay that has since swapped keys. Only a document carrying the
159
+ * challenge this caller drew, under AWS's signature, is a statement about now.
160
+ *
161
+ * What a pass means, and no more: these keys are held by an AWS Nitro Enclave
162
+ * running an image that measures to the pinned PCR0, and it answered this
163
+ * challenge within five minutes of `now`. It does not mean the image was built
164
+ * from any particular source.
165
+ */
166
+ async function verifyEnclaveIdentity(attestationDocument, options) {
167
+ const sent = options.nonce;
168
+ if (!(sent instanceof Uint8Array) || sent.length !== exports.IDENTITY_NONCE_SIZE) {
169
+ throw new EnclaveAttestationError(`challenge nonce is ${sent instanceof Uint8Array ? sent.length : 0} bytes, want exactly ` +
170
+ `${exports.IDENTITY_NONCE_SIZE} (this is a bug in the caller, not a bad document)`);
171
+ }
172
+ const payload = verifyDocument(attestationDocument, options);
173
+ const carried = payload.get("nonce");
174
+ if (!(carried instanceof Uint8Array) ||
175
+ carried.length !== sent.length ||
176
+ !(0, node_crypto_1.timingSafeEqual)(carried, sent)) {
177
+ throw new EnclaveAttestationError(`the document answers a different challenge than the one sent (sent ${toHex(sent)}, ` +
178
+ `document carries ${carried instanceof Uint8Array ? toHex(carried) : "none"}) — a genuine ` +
179
+ "document replayed for someone else's nonce proves nothing about now");
180
+ }
181
+ const signingKey = payload.get("user_data");
182
+ if (!(signingKey instanceof Uint8Array) || signingKey.length !== 32) {
183
+ const got = signingKey instanceof Uint8Array ? signingKey.length : 0;
184
+ throw new EnclaveAttestationError(`the document binds ${got} bytes where an Ed25519 public key belongs, want 32`);
185
+ }
186
+ const pcr0 = payload.get("pcrs").get(0);
187
+ return {
188
+ configSealKey: configSealKeyOf(payload),
189
+ signingKey,
190
+ signingKeyId: "dp_k_" + (0, node_crypto_1.createHash)("sha256").update(signingKey).digest("hex"),
191
+ pcr0: toHex(pcr0),
192
+ };
193
+ }
194
+ /**
195
+ * The checks every enclave document gets, whatever is read out of it after:
196
+ * COSE structure and algorithm, the chain to the pinned root, the signature, the
197
+ * AWS timestamp against `now`, and the pinned PCRs. Returns the verified payload.
198
+ */
199
+ function verifyDocument(attestationDocument, options) {
143
200
  if (!attestationDocument || attestationDocument.length === 0) {
144
201
  throw new EnclaveAttestationError("no attestation document (nothing to verify — this is not a pass)");
145
202
  }
@@ -168,6 +225,10 @@ async function verifyEnclaveConfigSealKey(attestationDocument, options) {
168
225
  verifyCoseSignature(leaf, protectedBytes, payloadBytes, signature);
169
226
  checkClock(payload.get("timestamp"), now);
170
227
  matchPcrs(pcrs, expected);
228
+ return payload;
229
+ }
230
+ /** The 32-byte X25519 config-seal key a verified payload binds in `public_key`. */
231
+ function configSealKeyOf(payload) {
171
232
  const publicKey = payload.get("public_key");
172
233
  if (!(publicKey instanceof Uint8Array) || publicKey.length !== KEY_SIZE) {
173
234
  const got = publicKey instanceof Uint8Array ? publicKey.length : 0;