@learncard/sss-key-manager 0.2.7 → 0.3.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.
- package/README.md +9 -0
- package/dist/escrow-authority.d.ts +4 -3
- package/dist/escrow-authority.d.ts.map +1 -1
- package/dist/escrow-authority.types.d.ts +15 -0
- package/dist/escrow-authority.types.d.ts.map +1 -1
- package/dist/escrow-crypto.d.ts +19 -1
- package/dist/escrow-crypto.d.ts.map +1 -1
- package/dist/escrow-enrollment-intent.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/sss-key-manager.cjs.development.js +720 -73
- package/dist/sss-key-manager.cjs.development.js.map +3 -3
- package/dist/sss-key-manager.cjs.production.min.js +30 -30
- package/dist/sss-key-manager.cjs.production.min.js.map +4 -4
- package/dist/sss-key-manager.esm.js +722 -73
- package/dist/sss-key-manager.esm.js.map +3 -3
- package/dist/sss-strategy.d.ts +26 -0
- package/dist/sss-strategy.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__fixtures__/escrow-authority-brand-names-v1.json +29 -0
- package/src/__fixtures__/escrow-carry-contact-contract-v1.json +25 -1
- package/src/__fixtures__/escrow-email-brand-contract-v1.json +225 -0
- package/src/escrow-authority.test.ts +2 -0
- package/src/escrow-authority.ts +24 -7
- package/src/escrow-authority.types.ts +27 -0
- package/src/escrow-crypto.test.ts +75 -0
- package/src/escrow-crypto.ts +43 -1
- package/src/escrow-enrollment-intent.ts +2 -1
- package/src/index.ts +8 -1
- package/src/sss-authority.test.ts +392 -32
- package/src/sss-strategy.ts +66 -8
package/src/sss-strategy.ts
CHANGED
|
@@ -54,7 +54,12 @@ import {
|
|
|
54
54
|
derivePinProof,
|
|
55
55
|
ESCROW_PIN_MAX_ATTEMPTS,
|
|
56
56
|
} from './escrow-pin';
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
canonicalEscrowEmailBrand,
|
|
59
|
+
encryptEscrowBlob,
|
|
60
|
+
generateEscrowKeyPair,
|
|
61
|
+
openEscrowRelease,
|
|
62
|
+
} from './escrow-crypto';
|
|
58
63
|
import { verifyEnclaveAttestation } from './escrow-attestation';
|
|
59
64
|
import {
|
|
60
65
|
createEscrowEnrollmentIntent,
|
|
@@ -69,7 +74,7 @@ import {
|
|
|
69
74
|
} from './escrow-authority';
|
|
70
75
|
import { getEscrowBlobIdentity } from './escrow-enrollment-intent';
|
|
71
76
|
import type { VerifiedEnclaveIdentity, EscrowAuthorityState } from './escrow-authority.types';
|
|
72
|
-
import { EscrowEvidenceError } from './escrow-authority.types';
|
|
77
|
+
import { ESCROW_AUTHORITY_TENANT_PATTERN, EscrowEvidenceError } from './escrow-authority.types';
|
|
73
78
|
import type { EscrowEnvelope } from './escrow-crypto';
|
|
74
79
|
import type { EmailRelayBranding } from './email-relay-crypto';
|
|
75
80
|
|
|
@@ -134,7 +139,36 @@ export interface SSSStorageFunctions {
|
|
|
134
139
|
|
|
135
140
|
export interface SSSStrategyConfig {
|
|
136
141
|
/** Enable automatic enrollment under an explicit enclave trust policy. */
|
|
137
|
-
escrow?: {
|
|
142
|
+
escrow?: {
|
|
143
|
+
enabled: boolean;
|
|
144
|
+
attestation: EscrowAttestationPolicy;
|
|
145
|
+
/**
|
|
146
|
+
* The escrow authority's tenant label: the enclave's measured
|
|
147
|
+
* `ESCROW_AUTHORITY_TENANT`, which it signs into every authority record and
|
|
148
|
+
* requires in every signed enrollment intent. Used for everything the
|
|
149
|
+
* enclave verifies (intent tenant, authority state, hold, code-delivery and
|
|
150
|
+
* persisted recovery expectations). Everything else (X-Tenant-Id, email
|
|
151
|
+
* branding, same-tenant guards) keeps using {@link SSSStrategyConfig.tenantId}.
|
|
152
|
+
*
|
|
153
|
+
* Defaults to `tenantId` (then `'learncard'`). Set it explicitly when this app
|
|
154
|
+
* shares another tenant's single-tenant enclave, e.g. VetPass on LearnCard's
|
|
155
|
+
* (`'learncard'`). Must match `/^[A-Za-z0-9_-]{1,128}$/`.
|
|
156
|
+
*/
|
|
157
|
+
authorityTenant?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Display name sealed into the escrow blob for enclave-sent emails
|
|
160
|
+
* (D21, `recoveryBrand`), e.g. `'VetPass'`. Sealed only when the
|
|
161
|
+
* recovery email and language are (nitro enroll/replace), never on a
|
|
162
|
+
* carry. Unset: the enclave brands emails from its measured tenant label.
|
|
163
|
+
*
|
|
164
|
+
* Set only after every live enclave host runs the brand-aware image: an
|
|
165
|
+
* older image drops the field whenever it re-encrypts the blob.
|
|
166
|
+
* Must pass {@link canonicalEscrowEmailBrand} (ASCII letters, digits,
|
|
167
|
+
* space, `-`, `'`, `&`; 1-32 characters); `createSSSStrategy` throws
|
|
168
|
+
* otherwise.
|
|
169
|
+
*/
|
|
170
|
+
emailBrand?: string;
|
|
171
|
+
};
|
|
138
172
|
|
|
139
173
|
/** Report enrollment failures without logging recovery material. */
|
|
140
174
|
onEscrowError?: (err: unknown) => void;
|
|
@@ -1037,6 +1071,23 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
1037
1071
|
emailBranding,
|
|
1038
1072
|
tenantId,
|
|
1039
1073
|
} = config;
|
|
1074
|
+
// Enclave-verified tenant (see SSSStrategyConfig.escrow.authorityTenant); never
|
|
1075
|
+
// sent as X-Tenant-Id. Defaults keep LearnCard/Scouts behaviour unchanged.
|
|
1076
|
+
const authorityTenant = config.escrow?.authorityTenant ?? tenantId ?? 'learncard';
|
|
1077
|
+
if (config.escrow && !ESCROW_AUTHORITY_TENANT_PATTERN.test(authorityTenant))
|
|
1078
|
+
throw new Error(
|
|
1079
|
+
'Invalid escrow authority tenant: expected 1-128 letters, digits, "_" or "-".'
|
|
1080
|
+
);
|
|
1081
|
+
// D21: strict on write (the enclave and parsers are lenient on read), and
|
|
1082
|
+
// canonicalized once here so every enrollment path seals the same value.
|
|
1083
|
+
const emailBrand =
|
|
1084
|
+
config.escrow?.emailBrand === undefined
|
|
1085
|
+
? undefined
|
|
1086
|
+
: canonicalEscrowEmailBrand(config.escrow.emailBrand);
|
|
1087
|
+
if (config.escrow?.emailBrand !== undefined && emailBrand === undefined)
|
|
1088
|
+
throw new Error(
|
|
1089
|
+
'Invalid escrow email brand: expected 1-32 ASCII letters, digits, spaces, "-", "\'" or "&", starting with a letter.'
|
|
1090
|
+
);
|
|
1040
1091
|
const storage = config.storage || defaultStorage;
|
|
1041
1092
|
let storageGeneration = 0;
|
|
1042
1093
|
const localWrites = new Set<Promise<void>>();
|
|
@@ -1289,7 +1340,7 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
1289
1340
|
if (
|
|
1290
1341
|
!expected ||
|
|
1291
1342
|
expected.holdId !== holdId ||
|
|
1292
|
-
expected.tenant !==
|
|
1343
|
+
expected.tenant !== authorityTenant ||
|
|
1293
1344
|
!expected.did ||
|
|
1294
1345
|
!expected.clientEphemeralPublicKey ||
|
|
1295
1346
|
!expected.blobHash ||
|
|
@@ -1357,7 +1408,7 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
1357
1408
|
const result = await verifyEscrowAuthorityState(
|
|
1358
1409
|
response,
|
|
1359
1410
|
nonce,
|
|
1360
|
-
|
|
1411
|
+
authorityTenant,
|
|
1361
1412
|
did,
|
|
1362
1413
|
identity
|
|
1363
1414
|
);
|
|
@@ -1427,8 +1478,15 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
1427
1478
|
did: primaryDid,
|
|
1428
1479
|
shareVersion,
|
|
1429
1480
|
...(pinMaterial ? { pinVerifier: pinMaterial.pinVerifier } : {}),
|
|
1481
|
+
// D21: the brand is sealed exactly when the contact is, so a
|
|
1482
|
+
// carry (whose target the enclave refuses if it claims one)
|
|
1483
|
+
// never sends it; the enclave inherits the source's brand.
|
|
1430
1484
|
...(authority && recoveryEmail && bindsRecoveryContact
|
|
1431
|
-
? {
|
|
1485
|
+
? {
|
|
1486
|
+
recoveryEmail,
|
|
1487
|
+
recoveryLanguage,
|
|
1488
|
+
...(emailBrand ? { recoveryBrand: emailBrand } : {}),
|
|
1489
|
+
}
|
|
1432
1490
|
: {}),
|
|
1433
1491
|
},
|
|
1434
1492
|
publicKey,
|
|
@@ -3154,7 +3212,7 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
3154
3212
|
const verified = await verifyEscrowHoldRecord(
|
|
3155
3213
|
result.holdRecord,
|
|
3156
3214
|
{
|
|
3157
|
-
tenant:
|
|
3215
|
+
tenant: authorityTenant,
|
|
3158
3216
|
did: expectedDid,
|
|
3159
3217
|
holdId: result.holdId,
|
|
3160
3218
|
clientEphemeralPublicKey: pair.publicKey,
|
|
@@ -3163,7 +3221,7 @@ export function createSSSStrategy(config: SSSStrategyConfig): SSSKeyDerivationSt
|
|
|
3163
3221
|
identity
|
|
3164
3222
|
);
|
|
3165
3223
|
expectations = {
|
|
3166
|
-
tenant:
|
|
3224
|
+
tenant: authorityTenant,
|
|
3167
3225
|
did: expectedDid,
|
|
3168
3226
|
holdId: result.holdId,
|
|
3169
3227
|
clientEphemeralPublicKey: pair.publicKey,
|