@synoi/sraid 0.2.0 → 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 +51 -6
- package/dist/attestation.d.ts +8 -49
- package/dist/attestation.d.ts.map +1 -1
- package/dist/attestation.js +15 -46
- package/dist/attestation.js.map +1 -1
- package/dist/internal/attestation-core.d.ts +85 -0
- package/dist/internal/attestation-core.d.ts.map +1 -0
- package/dist/internal/attestation-core.js +78 -0
- package/dist/internal/attestation-core.js.map +1 -0
- package/dist/internal/base64-browser.d.ts +31 -0
- package/dist/internal/base64-browser.d.ts.map +1 -0
- package/dist/internal/base64-browser.js +39 -0
- package/dist/internal/base64-browser.js.map +1 -0
- package/dist/internal/base64-validate.d.ts +19 -0
- package/dist/internal/base64-validate.d.ts.map +1 -0
- package/dist/internal/base64-validate.js +33 -0
- package/dist/internal/base64-validate.js.map +1 -0
- package/dist/internal/base64.d.ts +7 -5
- package/dist/internal/base64.d.ts.map +1 -1
- package/dist/internal/base64.js +7 -19
- package/dist/internal/base64.js.map +1 -1
- package/dist/internal/content-core.d.ts +68 -0
- package/dist/internal/content-core.d.ts.map +1 -0
- package/dist/internal/content-core.js +87 -0
- package/dist/internal/content-core.js.map +1 -0
- package/dist/internal/ed25519-browser.d.ts +33 -0
- package/dist/internal/ed25519-browser.d.ts.map +1 -0
- package/dist/internal/ed25519-browser.js +79 -0
- package/dist/internal/ed25519-browser.js.map +1 -0
- package/dist/internal/mldsa-browser.d.ts +27 -0
- package/dist/internal/mldsa-browser.d.ts.map +1 -0
- package/dist/internal/mldsa-browser.js +35 -0
- package/dist/internal/mldsa-browser.js.map +1 -0
- package/dist/internal/sha256-browser.d.ts +17 -0
- package/dist/internal/sha256-browser.d.ts.map +1 -0
- package/dist/internal/sha256-browser.js +27 -0
- package/dist/internal/sha256-browser.js.map +1 -0
- package/dist/oid.d.ts +2 -52
- package/dist/oid.d.ts.map +1 -1
- package/dist/oid.js +9 -71
- package/dist/oid.js.map +1 -1
- package/dist/verify-browser.d.ts +80 -0
- package/dist/verify-browser.d.ts.map +1 -0
- package/dist/verify-browser.js +157 -0
- package/dist/verify-browser.js.map +1 -0
- package/package.json +21 -2
- package/src/attestation.ts +29 -88
- package/src/internal/attestation-core.ts +122 -0
- package/src/internal/base64-browser.ts +39 -0
- package/src/internal/base64-validate.ts +31 -0
- package/src/internal/base64.ts +7 -16
- package/src/internal/content-core.ts +88 -0
- package/src/internal/ed25519-browser.ts +85 -0
- package/src/internal/mldsa-browser.ts +39 -0
- package/src/internal/sha256-browser.ts +28 -0
- package/src/oid.ts +10 -73
- package/src/verify-browser.ts +185 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @synoi/sraid
|
|
2
2
|
|
|
3
3
|
Reference TypeScript implementation of **SRAID (Self-Routing Addressable
|
|
4
|
-
Identity Data)
|
|
4
|
+
Identity Data)**, L0 of the SynOI SRAID Stack. MIT-licensed.
|
|
5
5
|
|
|
6
6
|
SRAID defines:
|
|
7
7
|
|
|
@@ -12,8 +12,8 @@ SRAID defines:
|
|
|
12
12
|
- an **L3 Merkle-DAG lineage** layer (`prev` edge + typed `links[]`), hashed into the OID so a head id commits its whole reachable history, and
|
|
13
13
|
- a supersession record, the **SRO** (Supersession Record Object), that links a successor CDRO to its predecessor.
|
|
14
14
|
|
|
15
|
-
The three legacy supersession mechanisms
|
|
16
|
-
string, the standalone SRO, and the `prev` edge
|
|
15
|
+
The three legacy supersession mechanisms, the self-asserted `supersedes`
|
|
16
|
+
string, the standalone SRO, and the `prev` edge, are unified by the lineage
|
|
17
17
|
helpers (`lineageLinks`, `supersededOids`, `latestWins`). `latestWins` resolves
|
|
18
18
|
a set of versions to its single head by following the identity-bound `prev`/
|
|
19
19
|
`links` edges, giving a verifier a **latest-wins / monotone** rule instead of an
|
|
@@ -24,8 +24,8 @@ transparency-log property (DESIGN, not yet deployed). The `set_complete` field
|
|
|
24
24
|
on `LatestWinsResult` is the local signal that lets a caller detect an
|
|
25
25
|
incomplete (potentially withheld) set.
|
|
26
26
|
|
|
27
|
-
Higher layers
|
|
28
|
-
|
|
27
|
+
Higher layers are built on SRAID objects: Vault/Resolver at L1, Inference
|
|
28
|
+
Broker/Resonance at L2, GAP at L3. This package is intentionally tiny and has no
|
|
29
29
|
storage, no HTTP surface, and no governance logic.
|
|
30
30
|
|
|
31
31
|
## Install
|
|
@@ -113,6 +113,51 @@ interface SRO { /* supersession record */ }
|
|
|
113
113
|
interface SignatureEnvelope { ed25519: string; ml_dsa_65: string; signer_kid: string }
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
## Browser, Chrome extension, service worker
|
|
117
|
+
|
|
118
|
+
The default (`.`) entry statically imports `node:crypto` in three places
|
|
119
|
+
(`ed25519.ts`, `mldsa.ts`, `oid.ts`), so `import '@synoi/sraid'` breaks any
|
|
120
|
+
browser bundle. Import the `./verify-browser` subpath instead. It carries exactly
|
|
121
|
+
what a v2 hybrid DSSE receipt verifier needs, with no static `node:crypto`
|
|
122
|
+
anywhere in its graph.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { verifyAttestation, canonicalize, cdroContentCore } from '@synoi/sraid/verify-browser'
|
|
126
|
+
|
|
127
|
+
const v = await verifyAttestation({
|
|
128
|
+
envelope: receipt.attestation,
|
|
129
|
+
ed25519_pub, // raw 32 bytes
|
|
130
|
+
ml_dsa_pub, // raw 1952 bytes
|
|
131
|
+
expectedPayloadType: 'application/vnd.synoi.gap+json',
|
|
132
|
+
})
|
|
133
|
+
// { valid: true, reasons: [] } only when BOTH signatures verify over the PAE
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// The subpath surface. Four of these are ASYNC where the node entry is sync,
|
|
138
|
+
// because WebCrypto is Promise-based. Everything else is shared byte-for-byte
|
|
139
|
+
// with the node entry, so the two agree on the same input.
|
|
140
|
+
verifyAttestation(input): Promise<{ valid: boolean; reasons: string[] }> // ASYNC
|
|
141
|
+
cdroOid(cdro: unknown): Promise<string> // ASYNC
|
|
142
|
+
oidOf(canonical: unknown): Promise<string> // ASYNC
|
|
143
|
+
oidOfCanonical(canonical: string | Uint8Array): Promise<string> // ASYNC
|
|
144
|
+
|
|
145
|
+
canonicalize(value: unknown): string // pure, identical to the node entry
|
|
146
|
+
cdroContentCore(cdro: unknown): Record<string, unknown>
|
|
147
|
+
pae(payloadType: string, payload: string | Uint8Array): Uint8Array
|
|
148
|
+
CDRO_ENVELOPE_FIELDS: readonly string[]
|
|
149
|
+
ALG_ED25519, ALG_ML_DSA_65
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Crypto backends, since browsers have no native ML-DSA and no `node:crypto`:
|
|
153
|
+
Ed25519 verify on WebCrypto (RFC 8032 cofactored, matching the node path) with a
|
|
154
|
+
`@noble/curves` fallback below the WebCrypto-Ed25519 support floor; ML-DSA-65
|
|
155
|
+
verify on `@noble/post-quantum`; SHA-256 on WebCrypto `subtle.digest`.
|
|
156
|
+
|
|
157
|
+
The node default entry is unchanged and keeps its synchronous `node:crypto` fast
|
|
158
|
+
paths. This subpath is purely additive. Added in 0.3.0; earlier versions export
|
|
159
|
+
only `.` and `./canonicalize`.
|
|
160
|
+
|
|
116
161
|
## Canonical form
|
|
117
162
|
|
|
118
163
|
The canonical form is recursive JCS-lite JSON:
|
|
@@ -146,4 +191,4 @@ in this package returns `valid: true` only when BOTH succeed.
|
|
|
146
191
|
|
|
147
192
|
## License
|
|
148
193
|
|
|
149
|
-
MIT
|
|
194
|
+
MIT, see [`LICENSE`](./LICENSE).
|
package/dist/attestation.d.ts
CHANGED
|
@@ -30,57 +30,16 @@
|
|
|
30
30
|
* Identity stability: signatures live in a detached `signatures[]` array and
|
|
31
31
|
* are NEVER part of the OID hash input (see `cdroContentCore` in oid.ts), so
|
|
32
32
|
* adding, rotating, or replacing a signature never changes the object's OID.
|
|
33
|
-
*/
|
|
34
|
-
import type { AttestationEnvelope } from './types.js';
|
|
35
|
-
/** The single supported algorithm identifiers for the JSON profile. */
|
|
36
|
-
export declare const ALG_ED25519 = "ed25519";
|
|
37
|
-
export declare const ALG_ML_DSA_65 = "ml-dsa-65";
|
|
38
|
-
/**
|
|
39
|
-
* The DSSE Pre-Authentication Encoding (PAE).
|
|
40
|
-
*
|
|
41
|
-
* Per the DSSE spec:
|
|
42
|
-
*
|
|
43
|
-
* PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
|
|
44
|
-
*
|
|
45
|
-
* where:
|
|
46
|
-
* - SP is a single ASCII space (0x20),
|
|
47
|
-
* - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
|
|
48
|
-
* - type and body are the raw UTF-8 bytes (NOT base64).
|
|
49
33
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
34
|
+
* NODE ENTRY: this module verifies via node:crypto (Ed25519 native, ML-DSA-65
|
|
35
|
+
* native-with-@noble-fallback) and is SYNCHRONOUS. The PAE, algorithm ids,
|
|
36
|
+
* types, AND-policy lookup, and envelope-shape predicate are shared with the
|
|
37
|
+
* browser verify surface via ./internal/attestation-core.ts (pure, no
|
|
38
|
+
* node:crypto); only the verify orchestration below is node-specific. The
|
|
39
|
+
* browser-safe async equivalent lives in ./verify-browser.ts.
|
|
53
40
|
*/
|
|
54
|
-
|
|
55
|
-
export
|
|
56
|
-
/**
|
|
57
|
-
* The DSSE attestation envelope to verify. Its `payloadType` is bound into
|
|
58
|
-
* the PAE; its `payload` is the canonical UTF-8 string that was signed.
|
|
59
|
-
*/
|
|
60
|
-
envelope: AttestationEnvelope;
|
|
61
|
-
/** Raw 32-byte Ed25519 public key. */
|
|
62
|
-
ed25519_pub: Uint8Array;
|
|
63
|
-
/** Raw ML-DSA-65 public key bytes. */
|
|
64
|
-
ml_dsa_pub: Uint8Array;
|
|
65
|
-
/**
|
|
66
|
-
* Optional. If supplied, the verifier asserts the envelope's `payloadType`
|
|
67
|
-
* equals this value before verifying signatures — an explicit type-pinning
|
|
68
|
-
* check on top of the structural PAE binding. A mismatch fails with
|
|
69
|
-
* `payload-type-mismatch`.
|
|
70
|
-
*/
|
|
71
|
-
expectedPayloadType?: string;
|
|
72
|
-
}
|
|
73
|
-
export interface VerifyAttestationResult {
|
|
74
|
-
/** True only when BOTH required signatures verified over the same PAE. */
|
|
75
|
-
valid: boolean;
|
|
76
|
-
/**
|
|
77
|
-
* Human-readable failure reasons. Empty when valid. Possible values:
|
|
78
|
-
* 'envelope-malformed', 'payload-type-mismatch', 'missing-ed25519',
|
|
79
|
-
* 'missing-ml-dsa-65', 'ed25519-malformed', 'ml-dsa-malformed',
|
|
80
|
-
* 'ed25519-invalid', 'ml-dsa-invalid'.
|
|
81
|
-
*/
|
|
82
|
-
reasons: string[];
|
|
83
|
-
}
|
|
41
|
+
import { ALG_ED25519, ALG_ML_DSA_65, pae, type VerifyAttestationInput, type VerifyAttestationResult } from './internal/attestation-core.js';
|
|
42
|
+
export { ALG_ED25519, ALG_ML_DSA_65, pae, type VerifyAttestationInput, type VerifyAttestationResult, };
|
|
84
43
|
/**
|
|
85
44
|
* Verify a hybrid DSSE attestation envelope. Returns `valid: true` only when
|
|
86
45
|
* the envelope carries BOTH an `ed25519` and an `ml-dsa-65` signature and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,EACL,WAAW,EACX,aAAa,EAGb,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC7B,MAAM,gCAAgC,CAAA;AAIvC,OAAO,EACL,WAAW,EACX,aAAa,EACb,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,uBAAuB,CA+DxF"}
|
package/dist/attestation.js
CHANGED
|
@@ -30,39 +30,21 @@
|
|
|
30
30
|
* Identity stability: signatures live in a detached `signatures[]` array and
|
|
31
31
|
* are NEVER part of the OID hash input (see `cdroContentCore` in oid.ts), so
|
|
32
32
|
* adding, rotating, or replacing a signature never changes the object's OID.
|
|
33
|
+
*
|
|
34
|
+
* NODE ENTRY: this module verifies via node:crypto (Ed25519 native, ML-DSA-65
|
|
35
|
+
* native-with-@noble-fallback) and is SYNCHRONOUS. The PAE, algorithm ids,
|
|
36
|
+
* types, AND-policy lookup, and envelope-shape predicate are shared with the
|
|
37
|
+
* browser verify surface via ./internal/attestation-core.ts (pure, no
|
|
38
|
+
* node:crypto); only the verify orchestration below is node-specific. The
|
|
39
|
+
* browser-safe async equivalent lives in ./verify-browser.ts.
|
|
33
40
|
*/
|
|
34
41
|
import { verifyEd25519 } from './ed25519.js';
|
|
35
42
|
import { decodeBase64Strict } from './internal/base64.js';
|
|
36
43
|
import { verifyMlDsa65 } from './mldsa.js';
|
|
37
|
-
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* The DSSE Pre-Authentication Encoding (PAE).
|
|
42
|
-
*
|
|
43
|
-
* Per the DSSE spec:
|
|
44
|
-
*
|
|
45
|
-
* PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
|
|
46
|
-
*
|
|
47
|
-
* where:
|
|
48
|
-
* - SP is a single ASCII space (0x20),
|
|
49
|
-
* - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
|
|
50
|
-
* - type and body are the raw UTF-8 bytes (NOT base64).
|
|
51
|
-
*
|
|
52
|
-
* Binding the payloadType (and both lengths) into the signed bytes is what
|
|
53
|
-
* makes a signature non-transferable across object types. Returns the raw
|
|
54
|
-
* bytes to be signed/verified.
|
|
55
|
-
*/
|
|
56
|
-
export function pae(payloadType, payload) {
|
|
57
|
-
const enc = new TextEncoder();
|
|
58
|
-
const typeBytes = enc.encode(payloadType);
|
|
59
|
-
const bodyBytes = typeof payload === 'string' ? enc.encode(payload) : payload;
|
|
60
|
-
const prefix = enc.encode(`DSSEv1 ${typeBytes.length} ${payloadType} ${bodyBytes.length} `);
|
|
61
|
-
const out = new Uint8Array(prefix.length + bodyBytes.length);
|
|
62
|
-
out.set(prefix, 0);
|
|
63
|
-
out.set(bodyBytes, prefix.length);
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
44
|
+
import { ALG_ED25519, ALG_ML_DSA_65, findSig, isWellFormedEnvelope, pae, } from './internal/attestation-core.js';
|
|
45
|
+
// Re-export the crypto-agnostic core so the public surface is unchanged:
|
|
46
|
+
// `import { pae, ALG_ED25519, ALG_ML_DSA_65 } from '@synoi/sraid'` still works.
|
|
47
|
+
export { ALG_ED25519, ALG_ML_DSA_65, pae, };
|
|
66
48
|
/**
|
|
67
49
|
* Verify a hybrid DSSE attestation envelope. Returns `valid: true` only when
|
|
68
50
|
* the envelope carries BOTH an `ed25519` and an `ml-dsa-65` signature and
|
|
@@ -73,12 +55,7 @@ export function pae(payloadType, payload) {
|
|
|
73
55
|
export function verifyAttestation(input) {
|
|
74
56
|
const reasons = [];
|
|
75
57
|
const env = input.envelope;
|
|
76
|
-
if (env
|
|
77
|
-
typeof env !== 'object' ||
|
|
78
|
-
typeof env.payloadType !== 'string' ||
|
|
79
|
-
env.payloadType.length === 0 ||
|
|
80
|
-
typeof env.payload !== 'string' ||
|
|
81
|
-
!Array.isArray(env.signatures)) {
|
|
58
|
+
if (!isWellFormedEnvelope(env)) {
|
|
82
59
|
return { valid: false, reasons: ['envelope-malformed'] };
|
|
83
60
|
}
|
|
84
61
|
if (input.expectedPayloadType !== undefined &&
|
|
@@ -137,18 +114,10 @@ export function verifyAttestation(input) {
|
|
|
137
114
|
return { valid: edOk && mlOk && !!edEntry && !!mlEntry, reasons };
|
|
138
115
|
}
|
|
139
116
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
140
|
-
function findSig(sigs, alg) {
|
|
141
|
-
for (const s of sigs) {
|
|
142
|
-
if (s && typeof s === 'object' && s.alg === alg && typeof s.sig === 'string') {
|
|
143
|
-
return s;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return undefined;
|
|
147
|
-
}
|
|
148
117
|
// Strict standard base64. Throws Error('base64-malformed') on any deviation
|
|
149
|
-
// (illegal char, bad length, bad padding) rather than silently truncating.
|
|
150
|
-
//
|
|
151
|
-
//
|
|
118
|
+
// (illegal char, bad length, bad padding) rather than silently truncating. The
|
|
119
|
+
// call sites wrap this in try/catch and map the throw to a *-malformed reason,
|
|
120
|
+
// so verifyAttestation never throws.
|
|
152
121
|
function fromBase64(s) {
|
|
153
122
|
return decodeBase64Strict(s);
|
|
154
123
|
}
|
package/dist/attestation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/attestation-core.ts
|
|
3
|
+
*
|
|
4
|
+
* The crypto-agnostic core of the L2 DSSE attestation profile: the PAE
|
|
5
|
+
* encoding, the algorithm identifiers, the public verify input/result types,
|
|
6
|
+
* the both-required signature lookup, and the envelope-shape predicate.
|
|
7
|
+
*
|
|
8
|
+
* This is a PURE module — no crypto, no node:crypto, no Buffer — so the same
|
|
9
|
+
* PAE bytes, AND-policy shape, and type contract are shared byte-for-byte by
|
|
10
|
+
* BOTH the node default entry (attestation.ts, node:crypto verifiers) and the
|
|
11
|
+
* browser verify surface (verify-browser.ts, WebCrypto + @noble verifiers).
|
|
12
|
+
* Divergent PAE or shape logic between the two would be a signature-validity
|
|
13
|
+
* hazard; keeping it in one place removes that risk.
|
|
14
|
+
*
|
|
15
|
+
* The verify ORCHESTRATION (decode each sig, run both verifiers, gate on the
|
|
16
|
+
* AND policy) is intentionally NOT here: the node path is synchronous
|
|
17
|
+
* (node:crypto verify) and the browser path is asynchronous (WebCrypto verify),
|
|
18
|
+
* so a single shared body cannot serve both. Each entry keeps its own thin
|
|
19
|
+
* orchestration and reuses these leaf helpers.
|
|
20
|
+
*/
|
|
21
|
+
import type { AttestationEnvelope, AttestationSignature } from '../types.js';
|
|
22
|
+
/** The single supported algorithm identifiers for the JSON profile. */
|
|
23
|
+
export declare const ALG_ED25519 = "ed25519";
|
|
24
|
+
export declare const ALG_ML_DSA_65 = "ml-dsa-65";
|
|
25
|
+
/**
|
|
26
|
+
* The DSSE Pre-Authentication Encoding (PAE).
|
|
27
|
+
*
|
|
28
|
+
* Per the DSSE spec:
|
|
29
|
+
*
|
|
30
|
+
* PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
|
|
31
|
+
*
|
|
32
|
+
* where:
|
|
33
|
+
* - SP is a single ASCII space (0x20),
|
|
34
|
+
* - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
|
|
35
|
+
* - type and body are the raw UTF-8 bytes (NOT base64).
|
|
36
|
+
*
|
|
37
|
+
* Binding the payloadType (and both lengths) into the signed bytes is what
|
|
38
|
+
* makes a signature non-transferable across object types. Returns the raw
|
|
39
|
+
* bytes to be signed/verified.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pae(payloadType: string, payload: string | Uint8Array): Uint8Array;
|
|
42
|
+
export interface VerifyAttestationInput {
|
|
43
|
+
/**
|
|
44
|
+
* The DSSE attestation envelope to verify. Its `payloadType` is bound into
|
|
45
|
+
* the PAE; its `payload` is the canonical UTF-8 string that was signed.
|
|
46
|
+
*/
|
|
47
|
+
envelope: AttestationEnvelope;
|
|
48
|
+
/** Raw 32-byte Ed25519 public key. */
|
|
49
|
+
ed25519_pub: Uint8Array;
|
|
50
|
+
/** Raw ML-DSA-65 public key bytes. */
|
|
51
|
+
ml_dsa_pub: Uint8Array;
|
|
52
|
+
/**
|
|
53
|
+
* Optional. If supplied, the verifier asserts the envelope's `payloadType`
|
|
54
|
+
* equals this value before verifying signatures — an explicit type-pinning
|
|
55
|
+
* check on top of the structural PAE binding. A mismatch fails with
|
|
56
|
+
* `payload-type-mismatch`.
|
|
57
|
+
*/
|
|
58
|
+
expectedPayloadType?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface VerifyAttestationResult {
|
|
61
|
+
/** True only when BOTH required signatures verified over the same PAE. */
|
|
62
|
+
valid: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Human-readable failure reasons. Empty when valid. Possible values:
|
|
65
|
+
* 'envelope-malformed', 'payload-type-mismatch', 'missing-ed25519',
|
|
66
|
+
* 'missing-ml-dsa-65', 'ed25519-malformed', 'ml-dsa-malformed',
|
|
67
|
+
* 'ed25519-invalid', 'ml-dsa-invalid'.
|
|
68
|
+
*/
|
|
69
|
+
reasons: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Structural well-formedness of an attestation envelope: a non-null object with
|
|
73
|
+
* a non-empty string `payloadType`, a string `payload`, and an array
|
|
74
|
+
* `signatures`. A false result maps to the `envelope-malformed` reason. This is
|
|
75
|
+
* the SINGLE definition of envelope shape, shared by the node and browser
|
|
76
|
+
* verifiers so they reject the same inputs.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isWellFormedEnvelope(env: unknown): env is AttestationEnvelope;
|
|
79
|
+
/**
|
|
80
|
+
* Find the first well-formed signature entry for `alg` (a `{ alg, sig }` object
|
|
81
|
+
* whose `sig` is a string). Returns undefined when absent; the AND policy in
|
|
82
|
+
* each verifier turns a missing pair member into a `missing-*` reason.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findSig(sigs: readonly AttestationSignature[], alg: string): AttestationSignature | undefined;
|
|
85
|
+
//# sourceMappingURL=attestation-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestation-core.d.ts","sourceRoot":"","sources":["../../src/internal/attestation-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE5E,uEAAuE;AACvE,eAAO,MAAM,WAAW,YAAY,CAAA;AACpC,eAAO,MAAM,aAAa,cAAc,CAAA;AAExC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAYjF;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,sCAAsC;IACtC,WAAW,EAAE,UAAU,CAAA;IACvB,sCAAsC;IACtC,UAAU,EAAE,UAAU,CAAA;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAA;IACd;;;;;OAKG;IACH,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,mBAAmB,CAS7E;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,SAAS,oBAAoB,EAAE,EACrC,GAAG,EAAE,MAAM,GACV,oBAAoB,GAAG,SAAS,CAOlC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/attestation-core.ts
|
|
3
|
+
*
|
|
4
|
+
* The crypto-agnostic core of the L2 DSSE attestation profile: the PAE
|
|
5
|
+
* encoding, the algorithm identifiers, the public verify input/result types,
|
|
6
|
+
* the both-required signature lookup, and the envelope-shape predicate.
|
|
7
|
+
*
|
|
8
|
+
* This is a PURE module — no crypto, no node:crypto, no Buffer — so the same
|
|
9
|
+
* PAE bytes, AND-policy shape, and type contract are shared byte-for-byte by
|
|
10
|
+
* BOTH the node default entry (attestation.ts, node:crypto verifiers) and the
|
|
11
|
+
* browser verify surface (verify-browser.ts, WebCrypto + @noble verifiers).
|
|
12
|
+
* Divergent PAE or shape logic between the two would be a signature-validity
|
|
13
|
+
* hazard; keeping it in one place removes that risk.
|
|
14
|
+
*
|
|
15
|
+
* The verify ORCHESTRATION (decode each sig, run both verifiers, gate on the
|
|
16
|
+
* AND policy) is intentionally NOT here: the node path is synchronous
|
|
17
|
+
* (node:crypto verify) and the browser path is asynchronous (WebCrypto verify),
|
|
18
|
+
* so a single shared body cannot serve both. Each entry keeps its own thin
|
|
19
|
+
* orchestration and reuses these leaf helpers.
|
|
20
|
+
*/
|
|
21
|
+
/** The single supported algorithm identifiers for the JSON profile. */
|
|
22
|
+
export const ALG_ED25519 = 'ed25519';
|
|
23
|
+
export const ALG_ML_DSA_65 = 'ml-dsa-65';
|
|
24
|
+
/**
|
|
25
|
+
* The DSSE Pre-Authentication Encoding (PAE).
|
|
26
|
+
*
|
|
27
|
+
* Per the DSSE spec:
|
|
28
|
+
*
|
|
29
|
+
* PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
|
|
30
|
+
*
|
|
31
|
+
* where:
|
|
32
|
+
* - SP is a single ASCII space (0x20),
|
|
33
|
+
* - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
|
|
34
|
+
* - type and body are the raw UTF-8 bytes (NOT base64).
|
|
35
|
+
*
|
|
36
|
+
* Binding the payloadType (and both lengths) into the signed bytes is what
|
|
37
|
+
* makes a signature non-transferable across object types. Returns the raw
|
|
38
|
+
* bytes to be signed/verified.
|
|
39
|
+
*/
|
|
40
|
+
export function pae(payloadType, payload) {
|
|
41
|
+
const enc = new TextEncoder();
|
|
42
|
+
const typeBytes = enc.encode(payloadType);
|
|
43
|
+
const bodyBytes = typeof payload === 'string' ? enc.encode(payload) : payload;
|
|
44
|
+
const prefix = enc.encode(`DSSEv1 ${typeBytes.length} ${payloadType} ${bodyBytes.length} `);
|
|
45
|
+
const out = new Uint8Array(prefix.length + bodyBytes.length);
|
|
46
|
+
out.set(prefix, 0);
|
|
47
|
+
out.set(bodyBytes, prefix.length);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Structural well-formedness of an attestation envelope: a non-null object with
|
|
52
|
+
* a non-empty string `payloadType`, a string `payload`, and an array
|
|
53
|
+
* `signatures`. A false result maps to the `envelope-malformed` reason. This is
|
|
54
|
+
* the SINGLE definition of envelope shape, shared by the node and browser
|
|
55
|
+
* verifiers so they reject the same inputs.
|
|
56
|
+
*/
|
|
57
|
+
export function isWellFormedEnvelope(env) {
|
|
58
|
+
return (env !== null &&
|
|
59
|
+
typeof env === 'object' &&
|
|
60
|
+
typeof env.payloadType === 'string' &&
|
|
61
|
+
env.payloadType.length > 0 &&
|
|
62
|
+
typeof env.payload === 'string' &&
|
|
63
|
+
Array.isArray(env.signatures));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Find the first well-formed signature entry for `alg` (a `{ alg, sig }` object
|
|
67
|
+
* whose `sig` is a string). Returns undefined when absent; the AND policy in
|
|
68
|
+
* each verifier turns a missing pair member into a `missing-*` reason.
|
|
69
|
+
*/
|
|
70
|
+
export function findSig(sigs, alg) {
|
|
71
|
+
for (const s of sigs) {
|
|
72
|
+
if (s && typeof s === 'object' && s.alg === alg && typeof s.sig === 'string') {
|
|
73
|
+
return s;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=attestation-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestation-core.js","sourceRoot":"","sources":["../../src/internal/attestation-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAA;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAA;AAExC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,GAAG,CAAC,WAAmB,EAAE,OAA4B;IACnE,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAA;IAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IAE7E,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CACvB,UAAU,SAAS,CAAC,MAAM,IAAI,WAAW,IAAI,SAAS,CAAC,MAAM,GAAG,CACjE,CAAA;IACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClB,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACjC,OAAO,GAAG,CAAA;AACZ,CAAC;AAiCD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,OAAO,CACL,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAQ,GAA2B,CAAC,WAAW,KAAK,QAAQ;QAC3D,GAA2B,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QACnD,OAAQ,GAA2B,CAAC,OAAO,KAAK,QAAQ;QACxD,KAAK,CAAC,OAAO,CAAE,GAA2B,CAAC,UAAU,CAAC,CACvD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CACrB,IAAqC,EACrC,GAAW;IAEX,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC7E,OAAO,CAAC,CAAA;QACV,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/base64-browser.ts
|
|
3
|
+
*
|
|
4
|
+
* Strict standard-base64 (RFC 4648 §4, with `=` padding) decode for the
|
|
5
|
+
* BROWSER verify surface.
|
|
6
|
+
*
|
|
7
|
+
* INTERNAL: not exported from any public entry.
|
|
8
|
+
*
|
|
9
|
+
* Why a separate decoder: the node decoder (./base64.ts `decodeBase64Strict`)
|
|
10
|
+
* finishes with `Buffer.from(s, 'base64')`. `Buffer` is a NODE global that does
|
|
11
|
+
* not exist in a browser / service-worker / Chrome-extension context — and,
|
|
12
|
+
* crucially, it is NOT a `node:` specifier, so an esbuild `node:`-reference scan
|
|
13
|
+
* would NOT catch it. A `Buffer`-based decode would bundle clean yet throw
|
|
14
|
+
* `ReferenceError: Buffer is not defined` at runtime. This decoder is
|
|
15
|
+
* Buffer-free: it reuses the pure `assertBase64` validation (regex/length/
|
|
16
|
+
* padding only, from ./base64-validate.js — NOT ./base64.js, whose graph pulls
|
|
17
|
+
* in the `Buffer` decode) and decodes via the standard `atob` global, which IS
|
|
18
|
+
* available in browsers, dedicated/shared workers, and MV3 extension service
|
|
19
|
+
* workers.
|
|
20
|
+
*
|
|
21
|
+
* Same strict contract as the node path: throws Error('base64-malformed') on
|
|
22
|
+
* any deviation (illegal char, bad length, bad padding) rather than silently
|
|
23
|
+
* truncating, so a malformed signature is rejected loudly, never decoded to a
|
|
24
|
+
* short prefix that then "verifies" against something unintended.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Strictly decode standard base64 to raw bytes using only browser-safe globals.
|
|
28
|
+
* Throws Error('base64-malformed') on any non-conforming input.
|
|
29
|
+
*/
|
|
30
|
+
export declare function decodeBase64StrictBrowser(s: string): Uint8Array;
|
|
31
|
+
//# sourceMappingURL=base64-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64-browser.d.ts","sourceRoot":"","sources":["../../src/internal/base64-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAM/D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/base64-browser.ts
|
|
3
|
+
*
|
|
4
|
+
* Strict standard-base64 (RFC 4648 §4, with `=` padding) decode for the
|
|
5
|
+
* BROWSER verify surface.
|
|
6
|
+
*
|
|
7
|
+
* INTERNAL: not exported from any public entry.
|
|
8
|
+
*
|
|
9
|
+
* Why a separate decoder: the node decoder (./base64.ts `decodeBase64Strict`)
|
|
10
|
+
* finishes with `Buffer.from(s, 'base64')`. `Buffer` is a NODE global that does
|
|
11
|
+
* not exist in a browser / service-worker / Chrome-extension context — and,
|
|
12
|
+
* crucially, it is NOT a `node:` specifier, so an esbuild `node:`-reference scan
|
|
13
|
+
* would NOT catch it. A `Buffer`-based decode would bundle clean yet throw
|
|
14
|
+
* `ReferenceError: Buffer is not defined` at runtime. This decoder is
|
|
15
|
+
* Buffer-free: it reuses the pure `assertBase64` validation (regex/length/
|
|
16
|
+
* padding only, from ./base64-validate.js — NOT ./base64.js, whose graph pulls
|
|
17
|
+
* in the `Buffer` decode) and decodes via the standard `atob` global, which IS
|
|
18
|
+
* available in browsers, dedicated/shared workers, and MV3 extension service
|
|
19
|
+
* workers.
|
|
20
|
+
*
|
|
21
|
+
* Same strict contract as the node path: throws Error('base64-malformed') on
|
|
22
|
+
* any deviation (illegal char, bad length, bad padding) rather than silently
|
|
23
|
+
* truncating, so a malformed signature is rejected loudly, never decoded to a
|
|
24
|
+
* short prefix that then "verifies" against something unintended.
|
|
25
|
+
*/
|
|
26
|
+
import { assertBase64 } from './base64-validate.js';
|
|
27
|
+
/**
|
|
28
|
+
* Strictly decode standard base64 to raw bytes using only browser-safe globals.
|
|
29
|
+
* Throws Error('base64-malformed') on any non-conforming input.
|
|
30
|
+
*/
|
|
31
|
+
export function decodeBase64StrictBrowser(s) {
|
|
32
|
+
assertBase64(s);
|
|
33
|
+
const bin = atob(s);
|
|
34
|
+
const out = new Uint8Array(bin.length);
|
|
35
|
+
for (let i = 0; i < bin.length; i++)
|
|
36
|
+
out[i] = bin.charCodeAt(i);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=base64-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64-browser.js","sourceRoot":"","sources":["../../src/internal/base64-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,CAAS;IACjD,YAAY,CAAC,CAAC,CAAC,CAAA;IACf,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACnB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAC/D,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/base64-validate.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure strict standard-base64 (RFC 4648 §4, with `=` padding) VALIDATION.
|
|
5
|
+
*
|
|
6
|
+
* INTERNAL: not exported from any public entry.
|
|
7
|
+
*
|
|
8
|
+
* This is the Buffer-free, node:crypto-free half of base64 handling, split out
|
|
9
|
+
* so the browser decoder (./base64-browser.ts) can validate without importing
|
|
10
|
+
* ./base64.ts (whose `decodeBase64Strict` uses the node `Buffer` global — safe
|
|
11
|
+
* in node, a runtime `ReferenceError` in a browser). The node decoder re-uses
|
|
12
|
+
* this same validator, so both paths reject exactly the same inputs.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Validate strict standard base64. Throws Error('base64-malformed') on any
|
|
16
|
+
* non-conforming input.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertBase64(s: string): void;
|
|
19
|
+
//# sourceMappingURL=base64-validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64-validate.d.ts","sourceRoot":"","sources":["../../src/internal/base64-validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5C"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — internal/base64-validate.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure strict standard-base64 (RFC 4648 §4, with `=` padding) VALIDATION.
|
|
5
|
+
*
|
|
6
|
+
* INTERNAL: not exported from any public entry.
|
|
7
|
+
*
|
|
8
|
+
* This is the Buffer-free, node:crypto-free half of base64 handling, split out
|
|
9
|
+
* so the browser decoder (./base64-browser.ts) can validate without importing
|
|
10
|
+
* ./base64.ts (whose `decodeBase64Strict` uses the node `Buffer` global — safe
|
|
11
|
+
* in node, a runtime `ReferenceError` in a browser). The node decoder re-uses
|
|
12
|
+
* this same validator, so both paths reject exactly the same inputs.
|
|
13
|
+
*/
|
|
14
|
+
// Standard base64 alphabet; `=` only as 1-2 trailing pad chars. Total length
|
|
15
|
+
// must be a multiple of 4.
|
|
16
|
+
const STD_B64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
17
|
+
/**
|
|
18
|
+
* Validate strict standard base64. Throws Error('base64-malformed') on any
|
|
19
|
+
* non-conforming input.
|
|
20
|
+
*/
|
|
21
|
+
export function assertBase64(s) {
|
|
22
|
+
if (typeof s !== 'string')
|
|
23
|
+
throw new Error('base64-malformed');
|
|
24
|
+
if (s.length % 4 !== 0)
|
|
25
|
+
throw new Error('base64-malformed');
|
|
26
|
+
if (!STD_B64.test(s))
|
|
27
|
+
throw new Error('base64-malformed');
|
|
28
|
+
// `=` may appear only in the final 1-2 positions. The regex `={0,2}` anchored
|
|
29
|
+
// at end already guarantees pad chars are contiguous and trailing, but a pad
|
|
30
|
+
// char earlier in the body would have been rejected by the alphabet class —
|
|
31
|
+
// so an in-body `=` cannot pass STD_B64. No extra check needed.
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=base64-validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64-validate.js","sourceRoot":"","sources":["../../src/internal/base64-validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,2BAA2B;AAC3B,MAAM,OAAO,GAAG,wBAAwB,CAAA;AAExC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC9D,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACzD,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,gEAAgE;AAClE,CAAC"}
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Canonical inputs carry no surrounding whitespace, so no trim is applied;
|
|
16
16
|
* leading/trailing whitespace is itself a rejection.
|
|
17
|
+
*
|
|
18
|
+
* The strict VALIDATION (alphabet/length/padding) lives in the pure, Buffer-
|
|
19
|
+
* free ./base64-validate.ts so the browser decoder can share it; this module
|
|
20
|
+
* adds the node `Buffer`-based decode on top and re-exports `assertBase64` so
|
|
21
|
+
* existing importers of `./base64.js` are unchanged.
|
|
17
22
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* non-conforming input.
|
|
21
|
-
*/
|
|
22
|
-
export declare function assertBase64(s: string): void;
|
|
23
|
+
import { assertBase64 } from './base64-validate.js';
|
|
24
|
+
export { assertBase64 };
|
|
23
25
|
/**
|
|
24
26
|
* Strictly decode standard base64 to raw bytes. Throws
|
|
25
27
|
* Error('base64-malformed') on any non-conforming input.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/internal/base64.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/internal/base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,CAAA;AAEvB;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAGxD"}
|
package/dist/internal/base64.js
CHANGED
|
@@ -14,26 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Canonical inputs carry no surrounding whitespace, so no trim is applied;
|
|
16
16
|
* leading/trailing whitespace is itself a rejection.
|
|
17
|
+
*
|
|
18
|
+
* The strict VALIDATION (alphabet/length/padding) lives in the pure, Buffer-
|
|
19
|
+
* free ./base64-validate.ts so the browser decoder can share it; this module
|
|
20
|
+
* adds the node `Buffer`-based decode on top and re-exports `assertBase64` so
|
|
21
|
+
* existing importers of `./base64.js` are unchanged.
|
|
17
22
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const STD_B64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
21
|
-
/**
|
|
22
|
-
* Validate strict standard base64. Throws Error('base64-malformed') on any
|
|
23
|
-
* non-conforming input.
|
|
24
|
-
*/
|
|
25
|
-
export function assertBase64(s) {
|
|
26
|
-
if (typeof s !== 'string')
|
|
27
|
-
throw new Error('base64-malformed');
|
|
28
|
-
if (s.length % 4 !== 0)
|
|
29
|
-
throw new Error('base64-malformed');
|
|
30
|
-
if (!STD_B64.test(s))
|
|
31
|
-
throw new Error('base64-malformed');
|
|
32
|
-
// `=` may appear only in the final 1-2 positions. The regex `={0,2}` anchored
|
|
33
|
-
// at end already guarantees pad chars are contiguous and trailing, but a pad
|
|
34
|
-
// char earlier in the body would have been rejected by the alphabet class —
|
|
35
|
-
// so an in-body `=` cannot pass STD_B64. No extra check needed.
|
|
36
|
-
}
|
|
23
|
+
import { assertBase64 } from './base64-validate.js';
|
|
24
|
+
export { assertBase64 };
|
|
37
25
|
/**
|
|
38
26
|
* Strictly decode standard base64 to raw bytes. Throws
|
|
39
27
|
* Error('base64-malformed') on any non-conforming input.
|