@learncard/sss-key-manager 0.2.6 → 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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/sss-key-manager.cjs.development.js +776 -89
- 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 +778 -89
- 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/dist/types.d.ts +28 -0
- package/dist/types.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 +14 -2
- package/src/sss-authority.test.ts +392 -32
- package/src/sss-strategy.test.ts +238 -0
- package/src/sss-strategy.ts +245 -104
- package/src/types.ts +29 -0
package/src/escrow-crypto.ts
CHANGED
|
@@ -35,7 +35,11 @@ export const RECOVERY_LANGUAGES = ['en', 'es', 'fr', 'ar'] as const;
|
|
|
35
35
|
/** Recovery share bound to an identity and a positive share version.
|
|
36
36
|
* `recoveryEmail`/`recoveryLanguage`: bound by the user's own device at
|
|
37
37
|
* enrollment, never by lca-api. Always both-present or both-absent; changing
|
|
38
|
-
* the address requires a fresh `replace` enrollment, not an edit.
|
|
38
|
+
* the address requires a fresh `replace` enrollment, not an edit.
|
|
39
|
+
* `recoveryBrand` (D21): display name for enclave-sent recovery emails, sealed
|
|
40
|
+
* by the owner's device at enrollment alongside the contact. Not in the signed
|
|
41
|
+
* intent; canonical per `canonicalEscrowEmailBrand`, and silently dropped
|
|
42
|
+
* (never an error) when invalid so recovery is never refused over display text. */
|
|
39
43
|
export interface EscrowBlobPlaintext {
|
|
40
44
|
version: typeof ESCROW_ENVELOPE_VERSION;
|
|
41
45
|
recoveryShare: string;
|
|
@@ -44,6 +48,7 @@ export interface EscrowBlobPlaintext {
|
|
|
44
48
|
pinVerifier?: string;
|
|
45
49
|
recoveryEmail?: string;
|
|
46
50
|
recoveryLanguage?: string;
|
|
51
|
+
recoveryBrand?: string;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** Released recovery share additionally bound to its recovery hold. */
|
|
@@ -51,6 +56,38 @@ export interface EscrowReleasePlaintext extends EscrowBlobPlaintext {
|
|
|
51
56
|
holdId: string;
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
/** D21: longest canonical email brand, in characters (ASCII only, so also bytes). */
|
|
60
|
+
export const MAX_ESCROW_EMAIL_BRAND_LENGTH = 32;
|
|
61
|
+
|
|
62
|
+
/** D21: most digits an escrow email brand may contain, in total (not per run). */
|
|
63
|
+
export const MAX_ESCROW_EMAIL_BRAND_DIGITS = 3;
|
|
64
|
+
|
|
65
|
+
/** D21: canonical form of an owner-sealed escrow email brand, or `undefined` if
|
|
66
|
+
* it is not acceptable display text. Mirrors the enclave's `canonical_brand`
|
|
67
|
+
* (crypto.rs) exactly; both are pinned by escrow-email-brand-contract-v1.json.
|
|
68
|
+
* Trims and collapses runs of ASCII space only (every other whitespace or
|
|
69
|
+
* control character rejects); allows only `A-Z a-z 0-9`, space, `-`, `'`, `&`;
|
|
70
|
+
* must start with a letter, be 1-32 characters, contain at most 3 digits in total
|
|
71
|
+
* (not per run, so `Code 123 456 78` rejects) and no `http`/`www` in any case. ASCII-only by design: no URL, address, bidi or
|
|
72
|
+
* homoglyph can be formed, and no RFC 2047 encoding is ever needed. */
|
|
73
|
+
export const canonicalEscrowEmailBrand = (raw: unknown): string | undefined => {
|
|
74
|
+
if (typeof raw !== 'string') return undefined;
|
|
75
|
+
const brand = raw
|
|
76
|
+
.split(' ')
|
|
77
|
+
.filter(word => word.length > 0)
|
|
78
|
+
.join(' ');
|
|
79
|
+
if (
|
|
80
|
+
brand.length === 0 ||
|
|
81
|
+
brand.length > MAX_ESCROW_EMAIL_BRAND_LENGTH ||
|
|
82
|
+
!/^[A-Za-z][A-Za-z0-9 '&-]*$/.test(brand) ||
|
|
83
|
+
(brand.match(/[0-9]/g)?.length ?? 0) > MAX_ESCROW_EMAIL_BRAND_DIGITS ||
|
|
84
|
+
/http|www/i.test(brand)
|
|
85
|
+
) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
return brand;
|
|
89
|
+
};
|
|
90
|
+
|
|
54
91
|
const encodeUtf8 = (value: string): Uint8Array<ArrayBuffer> => {
|
|
55
92
|
const bytes = new TextEncoder().encode(value);
|
|
56
93
|
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
@@ -142,6 +179,10 @@ export const parseEscrowBlobPlaintext = (value: unknown): EscrowBlobPlaintext =>
|
|
|
142
179
|
}
|
|
143
180
|
recoveryContact = { recoveryEmail: email, recoveryLanguage: language };
|
|
144
181
|
}
|
|
182
|
+
// D21: lenient on read, exactly like the enclave's `normalize`: an invalid
|
|
183
|
+
// or mistyped brand is omitted, never an error. Independent of the contact
|
|
184
|
+
// (kept but inert without one).
|
|
185
|
+
const recoveryBrand = canonicalEscrowEmailBrand(value.recoveryBrand);
|
|
145
186
|
return {
|
|
146
187
|
version: ESCROW_ENVELOPE_VERSION,
|
|
147
188
|
recoveryShare: requireString(value, 'recoveryShare', {
|
|
@@ -161,6 +202,7 @@ export const parseEscrowBlobPlaintext = (value: unknown): EscrowBlobPlaintext =>
|
|
|
161
202
|
}
|
|
162
203
|
: {}),
|
|
163
204
|
...recoveryContact,
|
|
205
|
+
...(recoveryBrand !== undefined ? { recoveryBrand } : {}),
|
|
164
206
|
};
|
|
165
207
|
};
|
|
166
208
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ExtendedPoint, getPublicKeyAsync, signAsync } from '@noble/ed25519';
|
|
2
2
|
import { base64ToBuffer, bufferToBase64, bytesToHex, hexToBytes } from './crypto';
|
|
3
3
|
import { parseEscrowEnvelope } from './escrow-crypto';
|
|
4
|
+
import { ESCROW_AUTHORITY_TENANT_PATTERN } from './escrow-authority.types';
|
|
4
5
|
import type { EscrowEnvelope } from './escrow-crypto';
|
|
5
6
|
import type {
|
|
6
7
|
EscrowEnrollmentIntent,
|
|
@@ -101,7 +102,7 @@ export const encodeEscrowEnrollmentIntent = (
|
|
|
101
102
|
FIELDS.every(field => Object.prototype.hasOwnProperty.call(intent, field))
|
|
102
103
|
);
|
|
103
104
|
requireIntent(intent.version === 2 && typeof intent.allowRewrap === 'boolean');
|
|
104
|
-
requireIntent(matches(intent.tenant,
|
|
105
|
+
requireIntent(matches(intent.tenant, ESCROW_AUTHORITY_TENANT_PATTERN));
|
|
105
106
|
requireIntent(matches(intent.authorityGeneration, /^[A-Za-z0-9_-]{1,128}$/));
|
|
106
107
|
requireIntent(matches(intent.requestId, /^[A-Za-z0-9._-]{1,112}$/));
|
|
107
108
|
didPublicKey(intent.did);
|
package/src/index.ts
CHANGED
|
@@ -68,6 +68,9 @@ export {
|
|
|
68
68
|
parseEscrowEnvelope,
|
|
69
69
|
parseEscrowBlobPlaintext,
|
|
70
70
|
parseEscrowReleasePlaintext,
|
|
71
|
+
canonicalEscrowEmailBrand,
|
|
72
|
+
MAX_ESCROW_EMAIL_BRAND_LENGTH,
|
|
73
|
+
MAX_ESCROW_EMAIL_BRAND_DIGITS,
|
|
71
74
|
generateEscrowKeyPair,
|
|
72
75
|
encryptEscrowBlob,
|
|
73
76
|
decryptEscrowBlob,
|
|
@@ -87,7 +90,11 @@ export type {
|
|
|
87
90
|
SignedEscrowEnrollmentIntent,
|
|
88
91
|
} from './escrow-enrollment-intent.types';
|
|
89
92
|
export { verifyEnclaveAttestation } from './escrow-attestation';
|
|
90
|
-
export {
|
|
93
|
+
export {
|
|
94
|
+
EscrowEvidenceError,
|
|
95
|
+
EscrowAuthorityTenantMismatchError,
|
|
96
|
+
ESCROW_AUTHORITY_TENANT_PATTERN,
|
|
97
|
+
} from './escrow-authority.types';
|
|
91
98
|
export type { HistoricalEscrowEvidence } from './escrow-authority.types';
|
|
92
99
|
export type { EscrowRecoveryExpectations } from '@learncard/types';
|
|
93
100
|
export {
|
|
@@ -108,7 +115,12 @@ export type {
|
|
|
108
115
|
} from './escrow-authority.types';
|
|
109
116
|
export { verifyNitroAttestationDocument, NitroAttestationError } from './escrow-nitro-attestation';
|
|
110
117
|
export type { NitroAttestationReason } from './escrow-nitro-attestation';
|
|
111
|
-
export type {
|
|
118
|
+
export type {
|
|
119
|
+
EscrowAttestationPolicy,
|
|
120
|
+
EscrowHoldStatus,
|
|
121
|
+
EscrowPinAttemptContext,
|
|
122
|
+
EscrowRecoveryStart,
|
|
123
|
+
} from './types';
|
|
112
124
|
export type {
|
|
113
125
|
EscrowEnrollmentOptions,
|
|
114
126
|
EscrowEnrollmentState,
|