@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 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`, or `chain_outside_validity` (an
50
- * intermediate or root not valid at genTime). So a chain reason is reported only for
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) and only a real RSA link failure surfaces as
156
- // `chain_signature_invalid`; the chain layer keeps the fine-grained reason. The
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.11.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",