@typedstandards/verify-core 0.11.0 → 0.12.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/CHANGELOG.md +28 -0
- package/dist/rfc3161.d.ts +5 -3
- package/dist/rfc3161.js +7 -2
- package/dist/x509.d.ts +3 -2
- package/dist/x509.js +13 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,34 @@ references are to `npstorey/civic-ai-tools-website` (#119 is the offline-crypto
|
|
|
6
6
|
hardening arc; #116 is the standalone-verifier arc this package was extracted
|
|
7
7
|
in).
|
|
8
8
|
|
|
9
|
+
## 0.12.0 — 2026-09-24
|
|
10
|
+
|
|
11
|
+
The certificate-chain split (typedstandards#100, shipped in #106). **A minor bump** — `ChainFailReason`
|
|
12
|
+
and `Rfc3161FailReason` each gain a member, so a consumer with an exhaustive `switch` or `Record` over
|
|
13
|
+
either needs the new case.
|
|
14
|
+
|
|
15
|
+
- **A chain link signed with an algorithm the validator does not implement is no longer reported as an
|
|
16
|
+
invalid signature.** `verifyCertChainToAnchor` checks each link's signature algorithm at the step
|
|
17
|
+
where it verifies the link's signature:
|
|
18
|
+
- an algorithm it does not implement (anything but RSASSA-PKCS1-v1_5 with SHA-256, SHA-384 or
|
|
19
|
+
SHA-512; an ECDSA-signed intermediate, say) reads the new reason `link_algorithm_unsupported`. It
|
|
20
|
+
used to read `link_signature_invalid`;
|
|
21
|
+
- an RSA link whose signature does not verify keeps `link_signature_invalid`;
|
|
22
|
+
- no other reason changes order, and `verifyCertSignatureRsa` is unchanged.
|
|
23
|
+
- **`verifyRfc3161Timestamp` reports that link as `chain_algorithm_unsupported`**, a new reason, where
|
|
24
|
+
it used to report `chain_signature_invalid`. `RFC3161_FAIL_REASONS` holds fourteen codes.
|
|
25
|
+
`chain_signature_invalid` now means only a link whose signature does not verify: a fault in the
|
|
26
|
+
token, not a limit of the verifier. `verified` is unchanged, and no request is added.
|
|
27
|
+
- **On typedstandards.org** (the site's verifier, live since #106; not part of this package's API):
|
|
28
|
+
- **a timestamp whose chain has a link with an invalid signature (`chain_signature_invalid`) now
|
|
29
|
+
fails the package, where it was a caveat.** `chain_algorithm_unsupported` is a caveat, as the
|
|
30
|
+
verifier's other policy limits are, and the #7 row reads amber for every caveat-class reason
|
|
31
|
+
(typedstandards#104);
|
|
32
|
+
- **bundle mode requests nothing.** A fully offline run gives `verifyRecord` a fetcher that sends no
|
|
33
|
+
request, so a file the package references is not fetched and reads "not checked offline", and no
|
|
34
|
+
transparency-log lookup is sent (typedstandards#105). A consumer that wants the same passes such a
|
|
35
|
+
fetcher as `VerifyDeps.fetch`.
|
|
36
|
+
|
|
9
37
|
## 0.11.0 — 2026-09-22
|
|
10
38
|
|
|
11
39
|
The verifier follow-ons (anchored at `typedstandards#98`): specification check #6, the ratified
|
package/dist/rfc3161.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface TsaRootAnchor {
|
|
|
10
10
|
rootKeyDer: string;
|
|
11
11
|
}
|
|
12
12
|
export declare const FREETSA_ROOT_ANCHORS: readonly TsaRootAnchor[];
|
|
13
|
-
export declare const RFC3161_FAIL_REASONS: readonly ["parse_error", "unexpected_algorithm", "no_message_digest", "content_not_bound", "imprint_mismatch", "no_signing_cert", "eku_not_timestamping", "genTime_outside_validity", "chain_incomplete", "chain_signature_invalid", "chain_outside_validity", "untrusted_root", "signature_invalid"];
|
|
13
|
+
export declare const RFC3161_FAIL_REASONS: readonly ["parse_error", "unexpected_algorithm", "no_message_digest", "content_not_bound", "imprint_mismatch", "no_signing_cert", "eku_not_timestamping", "genTime_outside_validity", "chain_incomplete", "chain_signature_invalid", "chain_algorithm_unsupported", "chain_outside_validity", "untrusted_root", "signature_invalid"];
|
|
14
14
|
export type Rfc3161FailReason = (typeof RFC3161_FAIL_REASONS)[number];
|
|
15
15
|
export interface Rfc3161VerifyResult {
|
|
16
16
|
/** All checks passed: a chain-trusted TSA signed this package hash at genTime. */
|
|
@@ -46,8 +46,10 @@ export interface Rfc3161VerifyResult {
|
|
|
46
46
|
* own faults come first: `genTime_outside_validity` (the signing cert only), then
|
|
47
47
|
* `unexpected_algorithm` (a signing-cert key that is not P-384), then
|
|
48
48
|
* `signature_invalid`, then the chain's reason — `untrusted_root`,
|
|
49
|
-
* `chain_incomplete`, `chain_signature_invalid
|
|
50
|
-
*
|
|
49
|
+
* `chain_incomplete`, `chain_signature_invalid` (a link whose RSA signature does not
|
|
50
|
+
* verify), `chain_algorithm_unsupported` (a link signed with an algorithm the chain
|
|
51
|
+
* validator does not implement), or `chain_outside_validity` (an intermediate or
|
|
52
|
+
* root not valid at genTime). So a chain reason is reported only for
|
|
51
53
|
* a token whose TSA signature verifies and whose genTime is within the signing cert's
|
|
52
54
|
* validity, and each reason names one kind of fault.
|
|
53
55
|
*/
|
package/dist/rfc3161.js
CHANGED
|
@@ -66,6 +66,7 @@ export const RFC3161_FAIL_REASONS = Object.freeze([
|
|
|
66
66
|
'genTime_outside_validity',
|
|
67
67
|
'chain_incomplete',
|
|
68
68
|
'chain_signature_invalid',
|
|
69
|
+
'chain_algorithm_unsupported',
|
|
69
70
|
'chain_outside_validity',
|
|
70
71
|
'untrusted_root',
|
|
71
72
|
'signature_invalid',
|
|
@@ -152,8 +153,10 @@ function parseTstInfo(tst) {
|
|
|
152
153
|
}
|
|
153
154
|
// Map the X.509 chain verifier's precise reason onto this module's vocabulary. The
|
|
154
155
|
// strict-RFC structural/policy rejections (#119 P4) collapse to `chain_incomplete`
|
|
155
|
-
// (the chain could not be trusted)
|
|
156
|
-
// `chain_signature_invalid
|
|
156
|
+
// (the chain could not be trusted), only a real RSA link failure surfaces as
|
|
157
|
+
// `chain_signature_invalid`, and a link signed with an algorithm the validator does
|
|
158
|
+
// not implement is `chain_algorithm_unsupported` (#100); the chain layer keeps the
|
|
159
|
+
// fine-grained reason. The
|
|
157
160
|
// chain's validity failure is `chain_outside_validity`: `verifyRfc3161Timestamp`
|
|
158
161
|
// reports a signing cert not valid at genTime before the chain's reason, so here it
|
|
159
162
|
// can only be an intermediate or the root (typedstandards#94).
|
|
@@ -165,6 +168,8 @@ function toRfc3161ChainReason(reason) {
|
|
|
165
168
|
return 'untrusted_root';
|
|
166
169
|
case 'link_signature_invalid':
|
|
167
170
|
return 'chain_signature_invalid';
|
|
171
|
+
case 'link_algorithm_unsupported':
|
|
172
|
+
return 'chain_algorithm_unsupported';
|
|
168
173
|
case 'unsupported_critical_extension':
|
|
169
174
|
case 'algorithm_mismatch':
|
|
170
175
|
case 'issuer_not_found':
|
package/dist/x509.d.ts
CHANGED
|
@@ -40,7 +40,8 @@ export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
|
40
40
|
* Verify `cert.signature` over `cert.tbsBytes` using `issuerSpkiDer` (RSA), via
|
|
41
41
|
* WebCrypto (`globalThis.crypto.subtle` — a browser/Node global, not a `node:`
|
|
42
42
|
* import, so verify-core stays browser-safe). Returns false for an unsupported
|
|
43
|
-
* algorithm or any verification error
|
|
43
|
+
* algorithm or any verification error; `verifyCertChainToAnchor` tells the two
|
|
44
|
+
* apart before it calls this.
|
|
44
45
|
*/
|
|
45
46
|
export declare function verifyCertSignatureRsa(cert: X509Cert, issuerSpkiDer: Uint8Array): Promise<boolean>;
|
|
46
47
|
/** A pinned trust anchor: a root whose public key is trusted by value. */
|
|
@@ -49,7 +50,7 @@ export interface ChainAnchor {
|
|
|
49
50
|
/** Root public key, base64 SPKI DER. */
|
|
50
51
|
rootKeyDer: string;
|
|
51
52
|
}
|
|
52
|
-
export type ChainFailReason = 'unsupported_critical_extension' | 'algorithm_mismatch' | 'genTime_outside_validity' | 'issuer_not_found' | 'issuer_not_ca' | 'path_len_exceeded' | 'link_signature_invalid' | 'untrusted_root';
|
|
53
|
+
export type ChainFailReason = 'unsupported_critical_extension' | 'algorithm_mismatch' | 'genTime_outside_validity' | 'issuer_not_found' | 'issuer_not_ca' | 'path_len_exceeded' | 'link_signature_invalid' | 'link_algorithm_unsupported' | 'untrusted_root';
|
|
53
54
|
export interface ChainResult {
|
|
54
55
|
/** The path links to a pinned anchor and every strict check passed. */
|
|
55
56
|
ok: boolean;
|
package/dist/x509.js
CHANGED
|
@@ -148,11 +148,18 @@ const RSA_SIG_HASH = {
|
|
|
148
148
|
'1.2.840.113549.1.1.12': 'SHA-384',
|
|
149
149
|
'1.2.840.113549.1.1.13': 'SHA-512',
|
|
150
150
|
};
|
|
151
|
+
/** Whether `oid` is a certificate signature algorithm `verifyCertSignatureRsa`
|
|
152
|
+
* implements. The chain validator reports a link signed with any other algorithm as
|
|
153
|
+
* `link_algorithm_unsupported`, not as a signature that does not verify (#100). */
|
|
154
|
+
function isImplementedCertSignatureAlgorithm(oid) {
|
|
155
|
+
return Object.prototype.hasOwnProperty.call(RSA_SIG_HASH, oid);
|
|
156
|
+
}
|
|
151
157
|
/**
|
|
152
158
|
* Verify `cert.signature` over `cert.tbsBytes` using `issuerSpkiDer` (RSA), via
|
|
153
159
|
* WebCrypto (`globalThis.crypto.subtle` — a browser/Node global, not a `node:`
|
|
154
160
|
* import, so verify-core stays browser-safe). Returns false for an unsupported
|
|
155
|
-
* algorithm or any verification error
|
|
161
|
+
* algorithm or any verification error; `verifyCertChainToAnchor` tells the two
|
|
162
|
+
* apart before it calls this.
|
|
156
163
|
*/
|
|
157
164
|
export async function verifyCertSignatureRsa(cert, issuerSpkiDer) {
|
|
158
165
|
const hash = RSA_SIG_HASH[cert.sigAlgOid];
|
|
@@ -207,6 +214,11 @@ export async function verifyCertChainToAnchor(certs, leaf, anchors, atTime) {
|
|
|
207
214
|
if (issuer.pathLen !== null && depth > issuer.pathLen) {
|
|
208
215
|
return { ok: false, reason: 'path_len_exceeded' }; // (c)
|
|
209
216
|
}
|
|
217
|
+
// The algorithm is checked where the signature is, so the reasons before it read
|
|
218
|
+
// as they did; a link this validator cannot check is not one that fails (#100).
|
|
219
|
+
if (!isImplementedCertSignatureAlgorithm(current.sigAlgOid)) {
|
|
220
|
+
return { ok: false, reason: 'link_algorithm_unsupported' };
|
|
221
|
+
}
|
|
210
222
|
if (!(await verifyCertSignatureRsa(current, issuer.spkiDer))) {
|
|
211
223
|
return { ok: false, reason: 'link_signature_invalid' };
|
|
212
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typedstandards/verify-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Portable, browser-safe verification core for Typed Standards record packages — the §9.2 check suite, single-sourced so it verifies identically on a server and in the browser.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nathan Storey",
|