@learncard/lca-api-service 1.4.0 → 1.5.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/.env.example +73 -0
- package/AGENTS.md +106 -10
- package/CHANGELOG.md +52 -0
- package/bootstrapLambda.test.ts +76 -0
- package/bundle.mjs +9 -1
- package/config/config.dev.json +23 -0
- package/config/config.example.json +22 -0
- package/config/config.json +1 -0
- package/config/config.production.json +12 -0
- package/config/config.scouts.dev.json +11 -0
- package/config/config.scouts.production.json +11 -0
- package/dist/app.d.ts +67 -3
- package/dist/cache/getDel.d.ts +2 -0
- package/dist/cache/getDel.test.d.ts +1 -0
- package/dist/cache/login-tickets.d.ts +12 -0
- package/dist/config/applyDockerStageConfig.d.ts +1 -0
- package/dist/config/cacheEnvironment.d.ts +16 -0
- package/dist/config/environment.d.ts +101 -0
- package/dist/config/escrowBundleMode.test.d.ts +1 -0
- package/dist/config/escrowDeployment.test.d.ts +1 -0
- package/dist/config/oidcEnvironment.d.ts +74 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts +1 -0
- package/dist/config/stageConfig.d.ts +62 -0
- package/dist/config/stageConfig.test.d.ts +1 -0
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/helpers/auth.helpers.d.ts +1 -1
- package/dist/helpers/escrowCancelLink.d.ts +7 -0
- package/dist/helpers/escrowCancelLink.test.d.ts +1 -0
- package/dist/helpers/escrowCancelToken.d.ts +4 -0
- package/dist/helpers/escrowCancelToken.test.d.ts +1 -0
- package/dist/helpers/keycloak.helpers.d.ts +23 -0
- package/dist/helpers/login-ticket.helpers.d.ts +31 -0
- package/dist/helpers/login-ticket.helpers.test.d.ts +1 -0
- package/dist/helpers/oidc.helpers.d.ts +47 -0
- package/dist/helpers/rate-limit.helpers.d.ts +6 -0
- package/dist/helpers/rate-limit.helpers.test.d.ts +1 -0
- package/dist/helpers/social-token.helpers.d.ts +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/jobs/escrowBlobRewrap.d.ts +9 -0
- package/dist/jobs/escrowHoldReminders.d.ts +12 -0
- package/dist/models/AuthSubject.d.ts +20 -0
- package/dist/models/EscrowHold.d.ts +79 -1
- package/dist/models/Notification.d.ts +5 -0
- package/dist/models/UserKey.d.ts +11 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/oidc.d.ts +5 -0
- package/dist/publish-metafile.json +1 -0
- package/dist/routes/auth.d.ts +31 -0
- package/dist/routes/escrow.d.ts +18 -4
- package/dist/routes/keys.d.ts +1 -0
- package/dist/routes/notifications.d.ts +5 -0
- package/dist/routes/signing-authority.d.ts +14 -0
- package/dist/services/escrow-enclave/blobIdentity.d.ts +10 -0
- package/dist/services/escrow-enclave/blobIdentity.test.d.ts +1 -0
- package/dist/services/escrow-enclave/cancellation.d.ts +3 -0
- package/dist/services/escrow-enclave/enclaveTrustAnchors.d.ts +1 -0
- package/dist/services/escrow-enclave/index.d.ts +16 -0
- package/dist/services/escrow-enclave/notifications.d.ts +19 -4
- package/dist/services/escrow-enclave/notifications.test.d.ts +1 -0
- package/dist/services/escrow-enclave/production.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.contract.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.d.ts +20 -0
- package/dist/services/escrow-enclave/remoteEnclave.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.tls.test.d.ts +1 -0
- package/dist/services/escrow-enclave/softwareEnclave.d.ts +12 -2
- package/dist/services/escrow-enclave/types.d.ts +52 -8
- package/dist/types/notifications.d.ts +5 -0
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +27 -117
- package/lambdaApp.ts +146 -0
- package/oidcLambda.test.ts +38 -0
- package/oidcLambda.ts +18 -0
- package/oidcLambdaApp.ts +11 -0
- package/package.json +22 -13
- package/project.json +8 -1
- package/scripts/README.md +88 -0
- package/scripts/check-seed-encryption-infrastructure.mjs +18 -2
- package/scripts/escrow-blob-mode-report.test.ts +227 -0
- package/scripts/escrow-blob-mode-report.ts +465 -0
- package/scripts/keycloak-admin.ts +108 -0
- package/scripts/keycloak-provider-links.ts +147 -0
- package/scripts/print-seed-rollout-flags.ts +21 -0
- package/scripts/provision-keycloak-users.ts +419 -0
- package/seedMigrationApp.test.ts +145 -0
- package/seedMigrationApp.ts +50 -0
- package/seedMigrationLambda.test.ts +30 -135
- package/seedMigrationLambda.ts +15 -46
- package/serverless-local.yml +25 -1
- package/serverless.function-env.cjs +58 -0
- package/serverless.yml +174 -37
- package/src/accesslayer/notifications/index.ts +9 -2
- package/src/accesslayer/signing-authority/index.ts +4 -1
- package/src/app.ts +2 -0
- package/src/cache/getDel.test.ts +34 -0
- package/src/cache/getDel.ts +32 -0
- package/src/cache/index.ts +1 -1
- package/src/cache/login-tickets.ts +26 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/cacheEnvironment.ts +34 -0
- package/src/config/environment.test.ts +45 -2
- package/src/config/environment.ts +90 -23
- package/src/config/escrowBundleMode.test.ts +52 -0
- package/src/config/escrowDeployment.test.ts +114 -0
- package/src/config/oidcEnvironment.ts +59 -0
- package/src/config/serverlessFunctionEnv.test.ts +98 -0
- package/src/config/stageConfig.test.ts +26 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/docker-entry.ts +9 -0
- package/src/helpers/auth.helpers.ts +68 -30
- package/src/helpers/escrowCancelLink.test.ts +60 -0
- package/src/helpers/escrowCancelLink.ts +38 -0
- package/src/helpers/escrowCancelToken.test.ts +105 -0
- package/src/helpers/escrowCancelToken.ts +47 -0
- package/src/helpers/keycloak.helpers.ts +120 -0
- package/src/helpers/login-ticket.helpers.test.ts +45 -0
- package/src/helpers/login-ticket.helpers.ts +91 -0
- package/src/helpers/oidc.helpers.ts +354 -0
- package/src/helpers/rate-limit.helpers.test.ts +61 -0
- package/src/helpers/rate-limit.helpers.ts +73 -0
- package/src/helpers/social-token.helpers.ts +144 -0
- package/src/index.ts +1 -0
- package/src/jobs/escrowBlobRewrap.ts +121 -0
- package/src/jobs/escrowHoldReminders.ts +177 -0
- package/src/models/AuthSubject.ts +147 -0
- package/src/models/EscrowHold.ts +321 -5
- package/src/models/UserKey.ts +91 -6
- package/src/models/index.ts +1 -0
- package/src/oidc.ts +371 -0
- package/src/routes/auth.ts +173 -0
- package/src/routes/escrow.ts +281 -34
- package/src/routes/keys.ts +5 -0
- package/src/routes/signing-authority.ts +39 -1
- package/src/services/delivery/adapters/postmark.adapter.ts +43 -1
- package/src/services/escrow-enclave/README.md +81 -10
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-ca.pem +11 -0
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.key +5 -0
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.pem +12 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-ca.pem +11 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.key +5 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.pem +12 -0
- package/src/services/escrow-enclave/blobIdentity.test.ts +42 -0
- package/src/services/escrow-enclave/blobIdentity.ts +55 -0
- package/src/services/escrow-enclave/cancellation.ts +59 -0
- package/src/services/escrow-enclave/enclaveTrustAnchors.ts +34 -0
- package/src/services/escrow-enclave/index.ts +145 -7
- package/src/services/escrow-enclave/notifications.test.ts +377 -0
- package/src/services/escrow-enclave/notifications.ts +340 -6
- package/src/services/escrow-enclave/production.test.ts +34 -0
- package/src/services/escrow-enclave/remoteEnclave.contract.test.ts +344 -0
- package/src/services/escrow-enclave/remoteEnclave.test.ts +490 -0
- package/src/services/escrow-enclave/remoteEnclave.tls.test.ts +62 -0
- package/src/services/escrow-enclave/remoteEnclave.ts +263 -0
- package/src/services/escrow-enclave/softwareEnclave.test.ts +231 -30
- package/src/services/escrow-enclave/softwareEnclave.ts +97 -11
- package/src/services/escrow-enclave/types.ts +63 -17
- package/test/auth-tickets.spec.ts +480 -0
- package/test/helpers/keycloak-broker.ts +181 -0
- package/test/helpers/live-mongo.ts +9 -0
- package/test/jobs/escrowBlobRewrap.spec.ts +262 -0
- package/test/jobs/escrowHoldReminders.spec.ts +319 -0
- package/test/keycloak-admin.spec.ts +58 -0
- package/test/keycloak-broker-roundtrip.integration.spec.ts +40 -0
- package/test/keycloak-migration.integration.spec.ts +266 -0
- package/test/keycloak-provider-links.spec.ts +201 -0
- package/test/keycloak-social-broker.integration.spec.ts +228 -0
- package/test/keycloak-verify.integration.spec.ts +100 -0
- package/test/keycloak-verify.spec.ts +307 -0
- package/test/keys-escrow.spec.ts +963 -24
- package/test/models/authSubjectIndexes.spec.ts +132 -0
- package/test/models/escrow.spec.ts +375 -13
- package/test/notifications.spec.ts +4 -0
- package/test/oidc-e2e/broker.spec.ts +116 -0
- package/test/oidc-e2e/compose.yaml +33 -0
- package/test/oidc-e2e/fixtures.ts +245 -0
- package/test/oidc-e2e/lock.test.mjs +73 -0
- package/test/oidc-e2e/playwright.config.ts +21 -0
- package/test/oidc-e2e/run.mjs +312 -0
- package/test/oidc-e2e/tsconfig.json +10 -0
- package/test/oidc-e2e/types.ts +21 -0
- package/test/oidc.integration.spec.ts +54 -0
- package/test/oidc.spec.ts +697 -0
- package/test/oidcLambdaApp.boot.spec.ts +105 -0
- package/test/provision-keycloak-users.spec.ts +299 -0
- package/test/signing-authority.spec.ts +50 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +24 -1
- package/vitest.integration.config.ts +20 -3
- package/serverless.escrow-env.cjs +0 -20
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { timingSafeEqual } from 'crypto';
|
|
2
|
+
import { escrowBlobIdentity } from './blobIdentity';
|
|
3
|
+
import type { EnclaveCreateHoldInput, EscrowHoldRecord, CancelHoldRequest } from './types';
|
|
2
4
|
import {
|
|
3
5
|
decryptEscrowBlob,
|
|
4
6
|
encryptEscrowBlob,
|
|
@@ -18,25 +20,72 @@ import {
|
|
|
18
20
|
type ReleaseRequest,
|
|
19
21
|
type ReleaseResult,
|
|
20
22
|
type CarryPinVerifierInput,
|
|
23
|
+
type RewrapEscrowBlobInput,
|
|
21
24
|
} from './types';
|
|
22
25
|
|
|
23
26
|
export interface SoftwareEnclaveConfig {
|
|
24
27
|
privateKeys: Record<string, string>;
|
|
25
28
|
activeKeyId: string;
|
|
29
|
+
/** Supplied by the factory to match its configured host-trusted waiting period. */
|
|
30
|
+
holdDurationMs?: number;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/** In-process backend for development; host time is not an attested clock. */
|
|
29
34
|
export class SoftwareEnclave implements EscrowEnclave {
|
|
30
35
|
private readonly privateKeys: ReadonlyMap<string, string>;
|
|
31
36
|
private readonly activeKeyId: string;
|
|
32
|
-
|
|
37
|
+
private readonly holdDurationMs: number;
|
|
33
38
|
constructor(config: SoftwareEnclaveConfig) {
|
|
34
39
|
this.privateKeys = new Map(Object.entries(config.privateKeys));
|
|
35
40
|
this.activeKeyId = config.activeKeyId;
|
|
36
41
|
if (!this.privateKeys.has(this.activeKeyId)) throw new EscrowUnavailableError();
|
|
42
|
+
// Match the factory configuration without a circular import; default is seven days.
|
|
43
|
+
this.holdDurationMs = config.holdDurationMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
37
44
|
}
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
async createHold(input: EnclaveCreateHoldInput): Promise<{ holdRecord: EscrowHoldRecord }> {
|
|
46
|
+
const blob = await this.decrypt(input.envelope);
|
|
47
|
+
if (
|
|
48
|
+
blob.did !== input.expectedDid ||
|
|
49
|
+
blob.shareVersion !== input.expectedShareVersion ||
|
|
50
|
+
!Number.isInteger(input.enrollmentEpoch) ||
|
|
51
|
+
input.enrollmentEpoch <= 0 ||
|
|
52
|
+
(input.releasePolicy === 'pin' && !blob.pinVerifier)
|
|
53
|
+
)
|
|
54
|
+
throw new EscrowPolicyError();
|
|
55
|
+
const blobHash = escrowBlobIdentity(input.envelope);
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
return {
|
|
58
|
+
holdRecord: {
|
|
59
|
+
hold: {
|
|
60
|
+
holdId: input.holdId,
|
|
61
|
+
did: blob.did,
|
|
62
|
+
shareVersion: input.expectedShareVersion,
|
|
63
|
+
blobHash,
|
|
64
|
+
enrollmentEpoch: input.enrollmentEpoch,
|
|
65
|
+
releasePolicy: input.releasePolicy,
|
|
66
|
+
clientEphemeralPublicKey: input.clientEphemeralPublicKey,
|
|
67
|
+
createdLo: now,
|
|
68
|
+
createdHi: now,
|
|
69
|
+
policyVersion: 1,
|
|
70
|
+
// Non-cryptographic placeholder: software mode trusts the host entirely.
|
|
71
|
+
signature: 'software-mode-unsigned',
|
|
72
|
+
},
|
|
73
|
+
holdDurationMs: input.releasePolicy === 'pin' ? 0 : this.holdDurationMs,
|
|
74
|
+
ledgerSeq: 0,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async cancelHold(input: CancelHoldRequest): Promise<void> {
|
|
79
|
+
const blob = await this.decrypt(input.envelope);
|
|
80
|
+
if (
|
|
81
|
+
blob.did !== input.expectedDid ||
|
|
82
|
+
blob.did !== input.hold.hold.did ||
|
|
83
|
+
input.clientEphemeralPublicKey !== input.hold.hold.clientEphemeralPublicKey
|
|
84
|
+
)
|
|
85
|
+
throw new EscrowPolicyError();
|
|
86
|
+
// Development only: Mongo owns cancellation; there is no software enclave ledger.
|
|
87
|
+
}
|
|
88
|
+
async getAttestation(_nonce?: Uint8Array): Promise<EnclaveAttestation> {
|
|
40
89
|
try {
|
|
41
90
|
const subtle = globalThis.crypto.subtle;
|
|
42
91
|
const key = await subtle.importKey(
|
|
@@ -62,6 +111,12 @@ export class SoftwareEnclave implements EscrowEnclave {
|
|
|
62
111
|
const description = {
|
|
63
112
|
mode: 'software' as const,
|
|
64
113
|
keyId: this.activeKeyId,
|
|
114
|
+
// P9.1 software-enclave equivalent: every OTHER configured key is
|
|
115
|
+
// already decrypt-eligible in `decrypt()` below (keyed lookup by
|
|
116
|
+
// envelope keyId, not restricted to `activeKeyId`) — this just
|
|
117
|
+
// advertises that existing behaviour the same way the Nitro
|
|
118
|
+
// backend advertises its `ESCROW_PREVIOUS_KEY_IDS`.
|
|
119
|
+
previousKeyIds: [...this.privateKeys.keys()].filter(id => id !== this.activeKeyId),
|
|
65
120
|
publicKey,
|
|
66
121
|
issuedAt: new Date().toISOString(),
|
|
67
122
|
};
|
|
@@ -98,7 +153,17 @@ export class SoftwareEnclave implements EscrowEnclave {
|
|
|
98
153
|
return { ok: true, hasPin: !!blob.pinVerifier };
|
|
99
154
|
}
|
|
100
155
|
|
|
101
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Transfer only the verifier; recovery material remains sealed within the enclave.
|
|
158
|
+
*
|
|
159
|
+
* `input.sourceEnrollmentEpoch` is intentionally unused: software mode has no
|
|
160
|
+
* enclave ledger to reset in the first place, so there is no epoch-scoped PIN
|
|
161
|
+
* attempt budget to carry. The PIN attempt budget here is `escrowPin.failedAttempts`
|
|
162
|
+
* / `verifiedFailedAttempts` in MongoDB (see routes/escrow.ts), which the enroll
|
|
163
|
+
* route already copies forward unchanged on every carry, independent of
|
|
164
|
+
* `enrollmentEpoch` — so software mode never had the epoch-reset bug this field
|
|
165
|
+
* exists to fix in remote/nitro mode.
|
|
166
|
+
*/
|
|
102
167
|
async carryPinVerifier(input: CarryPinVerifierInput) {
|
|
103
168
|
const source = await this.decrypt(input.sourceEnvelope);
|
|
104
169
|
const target = await this.decrypt(input.targetEnvelope);
|
|
@@ -122,19 +187,40 @@ export class SoftwareEnclave implements EscrowEnclave {
|
|
|
122
187
|
};
|
|
123
188
|
}
|
|
124
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Software-mode equivalent of the Nitro enclave's `rewrap_escrow_blob`
|
|
192
|
+
* (P9.3): moves a blob sealed under any OTHER configured key onto the
|
|
193
|
+
* active key, unchanged otherwise. Mirrors its refusal shape — an
|
|
194
|
+
* envelope already under `activeKeyId` has nothing to migrate, and
|
|
195
|
+
* `decrypt()` already throws `EscrowBlobError` for a keyId this backend
|
|
196
|
+
* was never configured with, so both collapse into the same generic
|
|
197
|
+
* error. `sourceEnrollmentEpoch` is unused for the same reason
|
|
198
|
+
* `carryPinVerifier` ignores it above: no enclave ledger here, so no
|
|
199
|
+
* epoch-scoped PIN attempt budget to carry.
|
|
200
|
+
*/
|
|
201
|
+
async rewrapEscrowBlob(input: RewrapEscrowBlobInput) {
|
|
202
|
+
const parsed = parseEscrowEnvelope(input.envelope);
|
|
203
|
+
if (parsed.keyId === this.activeKeyId) throw new EscrowBlobError();
|
|
204
|
+
const blob = await this.decrypt(input.envelope);
|
|
205
|
+
if (blob.did !== input.expectedDid || blob.shareVersion !== input.expectedShareVersion)
|
|
206
|
+
throw new EscrowBlobError();
|
|
207
|
+
const attestation = await this.getAttestation();
|
|
208
|
+
return {
|
|
209
|
+
envelope: await encryptEscrowBlob(blob, attestation.publicKey, attestation.keyId),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
125
213
|
async releaseEscrow(input: ReleaseRequest): Promise<ReleaseResult> {
|
|
126
|
-
const { hold } = input;
|
|
214
|
+
const { hold } = input.hold;
|
|
127
215
|
if (
|
|
128
|
-
hold.status !== 'pending' ||
|
|
129
216
|
input.clientEphemeralPublicKey !== hold.clientEphemeralPublicKey ||
|
|
130
|
-
!((input.now ?? new Date()).getTime() >= hold.
|
|
131
|
-
)
|
|
217
|
+
!((input.now ?? new Date()).getTime() >= hold.createdHi + input.hold.holdDurationMs)
|
|
218
|
+
)
|
|
132
219
|
throw new EscrowPolicyError();
|
|
133
|
-
}
|
|
134
220
|
const blob = await this.decrypt(input.envelope);
|
|
135
221
|
if (
|
|
136
222
|
blob.did !== input.expectedDid ||
|
|
137
|
-
blob.did !== hold.
|
|
223
|
+
blob.did !== hold.did ||
|
|
138
224
|
blob.shareVersion !== hold.shareVersion
|
|
139
225
|
)
|
|
140
226
|
throw new EscrowPolicyError();
|
|
@@ -156,7 +242,7 @@ export class SoftwareEnclave implements EscrowEnclave {
|
|
|
156
242
|
recoveryShare: blob.recoveryShare,
|
|
157
243
|
did: blob.did,
|
|
158
244
|
shareVersion: blob.shareVersion,
|
|
159
|
-
holdId: hold.
|
|
245
|
+
holdId: hold.holdId,
|
|
160
246
|
},
|
|
161
247
|
input.clientEphemeralPublicKey
|
|
162
248
|
),
|
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
import type { EscrowEnvelope } from '@learncard/sss-key-manager';
|
|
2
|
-
|
|
2
|
+
// Hold records are independent of host Mongo status and deadlines.
|
|
3
3
|
|
|
4
4
|
export interface EnclaveAttestation {
|
|
5
5
|
mode: 'software' | 'nitro';
|
|
6
6
|
keyId: string;
|
|
7
|
+
/** P9.1: retired keyIds the enclave still accepts for decrypt only (never
|
|
8
|
+
* for sealing output). Empty/absent means no previous key is configured. */
|
|
9
|
+
previousKeyIds?: string[];
|
|
7
10
|
publicKey: string;
|
|
8
11
|
measurements: { imageSha384?: string; pcr0?: string; pcr1?: string; pcr2?: string };
|
|
9
12
|
document: string;
|
|
10
13
|
issuedAt: string;
|
|
11
14
|
}
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
export interface EscrowHoldRecord {
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
hold: {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
holdId: string;
|
|
20
|
+
did: string;
|
|
21
|
+
shareVersion: number;
|
|
22
|
+
blobHash: string;
|
|
23
|
+
enrollmentEpoch: number;
|
|
24
|
+
releasePolicy: 'hold' | 'pin';
|
|
25
|
+
clientEphemeralPublicKey: string;
|
|
26
|
+
createdLo: number;
|
|
27
|
+
createdHi: number;
|
|
28
|
+
policyVersion: number;
|
|
29
|
+
signature: string;
|
|
30
|
+
};
|
|
31
|
+
holdDurationMs: number;
|
|
32
|
+
ledgerSeq: number;
|
|
33
|
+
}
|
|
34
|
+
export interface EnclaveCreateHoldInput {
|
|
23
35
|
envelope: EscrowEnvelope;
|
|
36
|
+
holdId: string;
|
|
24
37
|
expectedDid: string;
|
|
25
38
|
expectedShareVersion: number;
|
|
39
|
+
enrollmentEpoch: number;
|
|
40
|
+
releasePolicy: 'hold' | 'pin';
|
|
41
|
+
clientEphemeralPublicKey: string;
|
|
26
42
|
}
|
|
27
|
-
export
|
|
28
|
-
{ ok: true; hasPin: boolean } | { ok: false; hasPin: boolean; reason: string };
|
|
29
|
-
export interface ReleaseRequest {
|
|
43
|
+
export interface CancelHoldRequest {
|
|
30
44
|
envelope: EscrowEnvelope;
|
|
31
|
-
hold:
|
|
45
|
+
hold: EscrowHoldRecord;
|
|
32
46
|
clientEphemeralPublicKey: string;
|
|
33
47
|
expectedDid: string;
|
|
48
|
+
}
|
|
49
|
+
export interface VerifyEscrowBlobInput {
|
|
50
|
+
envelope: EscrowEnvelope;
|
|
51
|
+
expectedDid: string;
|
|
52
|
+
expectedShareVersion: number;
|
|
53
|
+
}
|
|
54
|
+
export type VerifyEscrowBlobResult =
|
|
55
|
+
{ ok: true; hasPin: boolean } | { ok: false; hasPin: boolean; reason: string };
|
|
56
|
+
export interface ReleaseRequest extends CancelHoldRequest {
|
|
57
|
+
/** Development-only host clock; never forwarded by the remote backend. */
|
|
34
58
|
now?: Date;
|
|
35
59
|
pinProof?: string;
|
|
36
60
|
}
|
|
@@ -43,12 +67,34 @@ export interface CarryPinVerifierInput {
|
|
|
43
67
|
expectedDid: string;
|
|
44
68
|
sourceShareVersion: number;
|
|
45
69
|
targetShareVersion: number;
|
|
70
|
+
/** Signed source-record binding; blob hash, not epoch, selects the chain. */
|
|
71
|
+
sourceEnrollmentEpoch: number;
|
|
72
|
+
/** Signed binding for the output blob's first Carried record. */
|
|
73
|
+
targetEnrollmentEpoch: number;
|
|
74
|
+
}
|
|
75
|
+
export interface RewrapEscrowBlobInput {
|
|
76
|
+
envelope: EscrowEnvelope;
|
|
77
|
+
expectedDid: string;
|
|
78
|
+
expectedShareVersion: number;
|
|
79
|
+
/** Source and destination record binding. Rewrap preserves this epoch. */
|
|
80
|
+
sourceEnrollmentEpoch: number;
|
|
46
81
|
}
|
|
47
82
|
export interface EscrowEnclave {
|
|
48
|
-
|
|
83
|
+
/** `nonce` binds a nitro attestation to one client-generated challenge (64 hex chars,
|
|
84
|
+
* decoded to bytes); the software backend has no freshness story and ignores it. */
|
|
85
|
+
getAttestation(nonce?: Uint8Array): Promise<EnclaveAttestation>;
|
|
49
86
|
verifyEscrowBlob(input: VerifyEscrowBlobInput): Promise<VerifyEscrowBlobResult>;
|
|
50
87
|
carryPinVerifier(input: CarryPinVerifierInput): Promise<{ envelope: EscrowEnvelope }>;
|
|
88
|
+
/** P9.3: migrates a copy sealed under a recognised PREVIOUS key onto the
|
|
89
|
+
* CURRENT key (same did/shareVersion/recoveryShare/pinVerifier), so a
|
|
90
|
+
* previous key can eventually be retired. Refuses (generic
|
|
91
|
+
* `EscrowBlobError`) unless the envelope's own keyId is a recognised
|
|
92
|
+
* previous key — never the current key (nothing to migrate) or an
|
|
93
|
+
* unknown one. */
|
|
94
|
+
rewrapEscrowBlob(input: RewrapEscrowBlobInput): Promise<{ envelope: EscrowEnvelope }>;
|
|
51
95
|
releaseEscrow(input: ReleaseRequest): Promise<ReleaseResult>;
|
|
96
|
+
createHold(input: EnclaveCreateHoldInput): Promise<{ holdRecord: EscrowHoldRecord }>;
|
|
97
|
+
cancelHold(input: CancelHoldRequest): Promise<void>;
|
|
52
98
|
}
|
|
53
99
|
export class EscrowPolicyError extends Error {
|
|
54
100
|
constructor() {
|