@blamejs/pki 0.8.20 → 0.8.21
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 +17 -0
- package/README.md +1 -1
- package/lib/lint.js +341 -1
- package/lib/oid.js +7 -0
- package/lib/schema-pkix.js +4 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.8.21 — 2026-09-24
|
|
8
|
+
|
|
9
|
+
A qualified certificate is linted against the statements it carries, not just their syntax.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `pki.lint.certificate(der, { profile: "etsi-qc" })` runs the ETSI EN 319 412-5 v2.4.1 profile, and the rows reach a certificate unprompted because carrying the extension is what the clause governs.
|
|
14
|
+
- The qcStatements extension must not be marked critical, and a certificate names one and only one of the three purposes the clause defines: electronic signature, electronic seal and web site authentication. The clause limits the certificate, so the purposes are counted across every QcType statement it carries and two statements naming one each break it as surely as one naming two. The syntax admits identifiers beyond those three, so one of them is not a second purpose and is not counted.
|
|
15
|
+
- A QcPDS statement must provide an https URL. The clause asks for one as a minimum, so a plain URL beside a secure one satisfies it. Under the requirements an EU qualified certificate carries, that statement must also name a document in English and must not name more than one per language.
|
|
16
|
+
- The semantics identifiers of ETSI EN 319 412-1 v1.5.1 are read, and a subject attribute they govern is held to the structure they require: a three-character identity type reference, a two-character country code, a hyphen-minus and an identifier. A natural person identifier governs the subject serialNumber and admits PAS, IDC, PNO, TAX and TIN; a legal person identifier governs the subject organizationIdentifier and admits VAT, NTR, PSD and LEI. An LEI value must carry the country code XG. A TAX reference is reported as deprecated, which the standard states at a lower strength than the rest, so the finding is a warning.
|
|
17
|
+
- Either identifier may instead name a locally defined reference, two characters and a colon, and one of those requires a nameRegistrationAuthorities element carrying a uniformResourceIdentifier, since that is what makes the local scheme resolvable.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- The four semantics identifiers of ETSI EN 319 412-1 section 5.1.2 resolve through `pki.oid`, and a decoded SemanticsInformation carries `semanticsIdentifierName` beside the identifier, the way a QcType carries `typeNames` and a QcIdentMethod `methodNames`. It is null for an identifier the registry does not name.
|
|
22
|
+
- Every statement of a kind is graded, not the first of that kind. RFC 3739 section 3.2.6 puts no cardinality on QCStatements, so a certificate may carry two disclosure statements or two semantics statements, and the order they sit in the extension decides nothing.
|
|
23
|
+
|
|
7
24
|
## v0.8.20 — 2026-09-24
|
|
8
25
|
|
|
9
26
|
An extended validation code signing certificate is held to the subject its own section requires.
|
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ comment blocks, is at [pkijs.com](https://pkijs.com).
|
|
|
290
290
|
| `pki.sigstore` | Offline verifier for a Sigstore bundle, the artifact `npm publish --provenance` produces and the registry serves. `verifyBundle` composes five fail-closed legs against caller-pinned trust (Fulcio CA roots and Rekor log keys, never trusted from the bundle): the DSSE signature over its PAE preimage under the Fulcio leaf key, the ephemeral Fulcio certificate chain validated as of the Rekor log time, the RFC 9162 inclusion proof folded to a Rekor-signed tree root, the log entry bound to this exact signature, and the in-toto SLSA subject digest the caller confirms against the published artifact. It reuses the X.509 parser, the RFC 5280 path validator, and the Merkle verifier; the net-new codecs are the DSSE PAE byte-builder and a fail-closed JSON reader. `verified: true` says the artifact was signed and logged, not that a party you trust signed it — Fulcio issues to anyone who completes an OIDC flow, so who signed is decided only by `opts.identity`, and `identityChecked` reports which of its fields were compared. An identity policy naming no field, or a field name that is not one of the three, is refused rather than satisfied — either would accept every signer while reading as a policy in force. A sixth leg is opt-in: pinning the certificate-transparency logs in `opts.ctLogs` checks the receipt Fulcio embeds in the certificate it issues (RFC 6962 §3.2), verified over the certificate as it stood before the receipt was added and under the key of the log that issued it, so the verdict says the signing certificate was public when it was issued rather than handed out quietly. At least one receipt must verify against a pinned log, the verdict reports `sctChecked` and `validScts`, and an empty log set is refused rather than read as a policy that checks nothing. Both content arms are read: a DSSE-wrapped in-toto attestation, and a message signature over an artifact's own bytes, which `cosign sign-blob` produces. For that arm `opts.artifact` supplies the bytes, and they are held to the hash the Rekor `hashedrekord` entry records before the signature is checked over them; the `messageDigest` the bundle carries is covered by no signature, so it is compared against the computed digest and never used in its place. `contentType` names the arm and the verdict has one shape for both — `pae`, `parseBundle`, `verifyBundle` |
|
|
291
291
|
| `pki.inspect` | Human-readable inspection, the pure-JS equivalent of `openssl x509/crl/req/cms -text`. `certificate(pem \| der \| parsed)` renders an OpenSSL-style report: version, serial, signature algorithm, issuer and subject distinguished names, validity, public-key details (curve or modulus size plus the raw point or modulus), every decoded extension with its critical flag, and the signature. `crl`, `csr`, and `cms` render the other formats the same way — a CRL like `openssl crl -text`, a CSR like `openssl req -text`, and a CMS message like `openssl cms -cmsout -print`, with a stable summary for a non-SignedData ContentInfo — and `any(input)` detects the format and routes to the right report. Built over the strict parsers and the two-way OID registry with one set of field renderers, it names extension and algorithm OIDs an OpenSSL build shows only as raw bytes. No OpenSSL dependency, and the format is stable and OpenSSL-familiar rather than pinned to one OpenSSL version. A certificate policy's user notice renders as text, both its explicit text and a notice reference with the notice numbers that identify it, rather than hex, and a malformed part falls back to a hex dump rather than throwing — `certificate`, `crl`, `csr`, `cms`, `any` |
|
|
292
292
|
| `pki.webauthn` | WebAuthn and passkey verification, both halves: offline trust evaluation of a W3C WebAuthn (Level 3) registration, and signature verification of the assertion every login returns. `parseAttestationObject(bytes)` decodes the CBOR attestation object, authenticatorData, and COSE credential key over the strict `pki.cbor` codec; `parseAuthenticatorData(bytes)` reads the bare form an assertion carries through the same parser; `parseClientData(bytes, opts)` decodes the `clientDataJSON` no signature check looks inside, through the shared JSON guard since these are attacker-chosen bytes, returning the challenge decoded so a caller compares bytes rather than spellings, and checking the ceremony type, challenge, and origin when the relying party supplies what it issued. `verifyAssertion(input)` verifies an assertion signature over `authenticatorData \|\| SHA-256(clientDataJSON)` — raw bytes, no COSE_Sign1, an ES256 signature in ASN.1 DER — and applies the §7.2 step 21 counter rule when a stored `previousSignCount` is given, so a counter that fails to advance is refused as a cloned authenticator. `verify(attestationObject, clientDataHash, opts)` checks the attestation-statement signature and each format's structural bindings for packed, tpm, android-key, apple, fido-u2f, and none: the x5c leaf key, the apple nonce, the tpm `certInfo` Name and `extraData` over the `pubArea`, the android `KeyDescription`, and the fido-u2f `verificationData`. It binds the credential public key to each attestation, through the signed authenticatorData for packed and fido-u2f or a cert or `pubArea`-key equality check for android-key, apple, and tpm, and enforces each leaf's certificate requirements. The credential-key check covers the full WebAuthn COSE algorithm set — ES256/384/512, RS256/384/512, PS256/384/512, EdDSA (Ed25519), and the RFC 9864 fully-specified identifiers ESP256/384/512, Ed25519, and Ed448 — validating the public-key point on its curve, rejecting the compressed EC point form, and enforcing a minimally encoded DER ECDSA signature. The verdict field is `attestationVerified`, and `signatureVerified` for an assertion, rather than a bare `verified`, because a sound statement is a different claim from an acceptable ceremony: an attestation naming another origin's RP ID with user presence clear is perfectly sound and must not be registered. Pass `expectedRpId`, `requireUserPresence`, `requireUserVerification`, or `allowedAlgorithms` and those are checked, with `bindingChecked` reporting which ran, so a check that passed can be told from one that never happened. Pass `opts.clientDataJSON` instead of the digest and the client data is read here too: the ceremony type is checked unconditionally, since a login response replayed into a registration is what that rule stops, and the challenge, origin, and top-level origin are checked against what the relying party issued, with `clientData.checked` reporting which comparisons ran. A registration verdict also carries the `credentialId`, `credentialPublicKey`, and initial `signCount` a later login needs. A credential key declaring COSE algorithm `-65535` (RSASSA-PKCS1-v1_5 with SHA-1) is refused unless `allowedAlgorithms` names it, since every signature that credential ever makes would use SHA-1. Anchoring the trust path has two routes: `opts.metadata` resolves the roots the authenticator's own model registered, and `opts.rootCertificates` pins roots directly, which is what anchors the formats FIDO MDS does not cover, Apple's authenticators and the Google hardware-attestation roots among them. `metadata` governs when both are given, and `anchoredTo` names every route that anchored the path, joined with `+` when more than one did: `"metadata"`, `"rootCertificates"`, and `"safetyNetRoots"` for the android-safetynet chain, which anchors through the roots that format requires whether or not either other route was asked for. It is `null` only when nothing anchored the path. `verifyMetadataBlob(blob, opts)` reads a FIDO Metadata Service (MDS v3) BLOB, the signed catalogue of registered authenticator models, verifying its JWS and chaining its signer to an operator-supplied FIDO root before the payload is parsed, with sequence-number rollback and `nextUpdate` freshness checks. Passing the result as `opts.metadata` to `verify` resolves the authenticator's registered attestation roots from its identifier and requires the trust path to fully validate to one of them, refusing an unlisted or revoked model. `metadataAnchors(entry, opts)` applies the same status gate for a caller anchoring the path themselves: a model the catalogue has disqualified registers no anchors. Both of the catalogue's key spaces are covered: an aaguid, and the attestation-certificate key identifiers a U2F authenticator is listed under instead. No FIDO root is bundled, there is no trust-on-first-use, and retrieving the BLOB is out of scope. Fail-closed with typed `webauthn/*` errors — `parseAttestationObject`, `verify`, `verifyMetadataBlob`, `metadataFor`, `metadataAnchors` |
|
|
293
|
-
| `pki.lint` | Certificate linting, the zlint or pkilint of JavaScript. `certificate(pem \| der \| parsed, opts)` walks a parsed certificate and emits graded advisory findings, each with a stable id, a severity (`fatal`, `error`, `warn`, `notice`), a source, a spec-clause citation, and a message, against the RFC 5280 profile, the post-quantum certificate profiles, and a representative CA/Browser Forum TLS BR subset: serial sign and size, validity ordering and the SC081v3 reducing validity schedule, keyCertSign coherence, extension criticality (basicConstraints, nameConstraints, policyConstraints and inhibitAnyPolicy must be critical, and keyUsage should be), nameConstraints CA-scope, unknown critical extensions, empty-subject SAN, SKI and AKI presence including the end-entity subjectKeyIdentifier, the six extensions a conforming CA must mark non-critical (reported by clause rather than as unrecognized) and the two it should (issuerAltName, cRLDistributionPoints), a policy mapping to or from anyPolicy, a unique identifier a conforming CA must not generate, a critical subjectAltName beside a non-empty subject, a critical extKeyUsage naming anyExtendedKeyUsage, a userNotice noticeRef, SAN required and CN-in-SAN, dNSName syntax, serverAuth EKU, weak keys, the CA/Browser Forum subscriber certificate profile of §7.1.2.7 (the extension presence and criticality table, the authorityInformationAccess accessMethod and accessLocation tables, basicConstraints cA and pathLenConstraint, certificatePolicies against anyPolicy and the exactly-one reserved identifier rule and the permitted-qualifier table, the forbidden extended key purposes, the two Key Usage tables that differ by key type, and the subjectAltName GeneralName table with its criticality rule in both directions), the CA/Browser Forum S/MIME BR subscriber profile of §7.1.2.3 (a) through (n) as `cabf-smime`, whose requirements follow the generation a certificate declares through its reserved policy identifier (the distribution-point and accessMethod URI scheme rules, the policy qualifier rules for an id-qt-cps URL and an id-qt-unotice explicitText and noticeRef, the key-usage table for the certificate's own key algorithm including a bit set past the nine named positions, the extended key purposes forbidden at every generation and the ones Strict alone forbids, the authorityKeyIdentifier field rules, a required mailbox in the subjectAltName as an rfc822Name or an id-on-SmtpUTF8Mailbox otherName, the criticality each extension must carry, the subjectDirectoryAttributes and Adobe extensions the Strict generation prohibits, the Legal Entity Identifier extensions keyed on the certificate type rather than the generation, and the Legacy generation retired on 15 July 2025), the CA/Browser Forum Code Signing BR subscriber profile of §7.1.2.3 as `cabf-cs`, whose rows follow the kind a certificate declares through one of the three reserved identifiers of §7.1.6.1 (the extension presence and criticality rules at the strengths this document states them, a HTTP URL required in the distribution points and in each accessMethod location, a critical keyUsage asserting digitalSignature with `keyCertSign` and `cRLSign` forbidden and every other bit discouraged, the extended key purpose each kind must carry with the critical extKeyUsage a timestamp certificate needs, the §7.1.6.4 exactly-one-identifier rule gated to 15 September 2026, the §6.3.2 ceilings of 39 months, 460 days and 135 months, and the §6.1.5.2 floors of a 3072-bit RSA modulus, NIST P-256, P-384 or P-521, or DSA parameters of 2048 bits with a 224- or 256-bit modulus length, and the §7.1.4.2 subject distinguished name rows: a required commonName, a prohibited domainComponent, a subject attribute made only of the metadata characters the clause names, a required organizationName and two-letter countryName, one of localityName and stateOrProvinceName, and on an EV certificate a businessCategory from the four strings the clause names, a jurisdictionCountryName and a serialNumber), the post-quantum key-usage rules binding a certificate's keyUsage bits to its subject key type (ML-KEM under RFC 9935 §5, ML-DSA under RFC 9881 §5, and SLH-DSA under RFC 9909 §6 across both its pure and prehash identifiers) together with the ML-KEM encapsulation-key encoding and size, and the §4.2.1.4 certificate-policy user-notice rules (a VisibleString or BMPString explicitText, a notice past 200 characters, an empty notice, control characters, and a non-NFC UTF8String notice, each at the strength the clause states). Alone among these entries the data path never throws: hostile bytes return a `fatal` `lint/unparseable` finding carrying the strict parser's code, so a whole directory lints without a try/catch, and only config-time misuse throws a typed `LintError` — `certificate`, `crl` (the RFC 5280 §5 revocation-list profile: nextUpdate presence, update ordering, cRLNumber presence and its 20-octet ceiling, authorityKeyIdentifier presence, the criticality each profiled CRL and entry extension must carry, freshestCRL in a delta CRL, and unrecognized critical extensions), `ocsp` (the RFC 6960 response profile: an empty signature, each SingleResponse's update ordering and its relation to producedAt, an empty `certs` field, the extended revoked definition and the fixed shape of a revoked answer for a non-issued certificate, the list each extension belongs in, the criticality and value syntax of the CRL entry extensions a SingleResponse may carry, unrecognized critical extensions, the reasons unspecified and removeFromCRL, and two answers for one CertID; the RFC 5019 lightweight rows run on request), `csr` (the artifact a CA reads before it issues: an empty subject naming no identity, an empty subject whose subjectAltName is not critical, a request for an extension the issuer determines, a request asking for a CA certificate, a broken signing digest, and a challengePassword traveling with the request; `opts.profile: "cabf-tls"` adds the certificate profile's own commonName-in-SAN, dNSName syntax, SAN-required and key-strength rows, applied to what the request asks for, and they run only when named because a request carries no extKeyUsage. It does not check the request's signature, which is a cryptographic operation `pki.csr.verify` owns), `rules`, `profiles` |
|
|
293
|
+
| `pki.lint` | Certificate linting, the zlint or pkilint of JavaScript. `certificate(pem \| der \| parsed, opts)` walks a parsed certificate and emits graded advisory findings, each with a stable id, a severity (`fatal`, `error`, `warn`, `notice`), a source, a spec-clause citation, and a message, against the RFC 5280 profile, the post-quantum certificate profiles, and a representative CA/Browser Forum TLS BR subset: serial sign and size, validity ordering and the SC081v3 reducing validity schedule, keyCertSign coherence, extension criticality (basicConstraints, nameConstraints, policyConstraints and inhibitAnyPolicy must be critical, and keyUsage should be), nameConstraints CA-scope, unknown critical extensions, empty-subject SAN, SKI and AKI presence including the end-entity subjectKeyIdentifier, the six extensions a conforming CA must mark non-critical (reported by clause rather than as unrecognized) and the two it should (issuerAltName, cRLDistributionPoints), a policy mapping to or from anyPolicy, a unique identifier a conforming CA must not generate, a critical subjectAltName beside a non-empty subject, a critical extKeyUsage naming anyExtendedKeyUsage, a userNotice noticeRef, SAN required and CN-in-SAN, dNSName syntax, serverAuth EKU, weak keys, the CA/Browser Forum subscriber certificate profile of §7.1.2.7 (the extension presence and criticality table, the authorityInformationAccess accessMethod and accessLocation tables, basicConstraints cA and pathLenConstraint, certificatePolicies against anyPolicy and the exactly-one reserved identifier rule and the permitted-qualifier table, the forbidden extended key purposes, the two Key Usage tables that differ by key type, and the subjectAltName GeneralName table with its criticality rule in both directions), the CA/Browser Forum S/MIME BR subscriber profile of §7.1.2.3 (a) through (n) as `cabf-smime`, whose requirements follow the generation a certificate declares through its reserved policy identifier (the distribution-point and accessMethod URI scheme rules, the policy qualifier rules for an id-qt-cps URL and an id-qt-unotice explicitText and noticeRef, the key-usage table for the certificate's own key algorithm including a bit set past the nine named positions, the extended key purposes forbidden at every generation and the ones Strict alone forbids, the authorityKeyIdentifier field rules, a required mailbox in the subjectAltName as an rfc822Name or an id-on-SmtpUTF8Mailbox otherName, the criticality each extension must carry, the subjectDirectoryAttributes and Adobe extensions the Strict generation prohibits, the Legal Entity Identifier extensions keyed on the certificate type rather than the generation, and the Legacy generation retired on 15 July 2025), the CA/Browser Forum Code Signing BR subscriber profile of §7.1.2.3 as `cabf-cs`, whose rows follow the kind a certificate declares through one of the three reserved identifiers of §7.1.6.1 (the extension presence and criticality rules at the strengths this document states them, a HTTP URL required in the distribution points and in each accessMethod location, a critical keyUsage asserting digitalSignature with `keyCertSign` and `cRLSign` forbidden and every other bit discouraged, the extended key purpose each kind must carry with the critical extKeyUsage a timestamp certificate needs, the §7.1.6.4 exactly-one-identifier rule gated to 15 September 2026, the §6.3.2 ceilings of 39 months, 460 days and 135 months, and the §6.1.5.2 floors of a 3072-bit RSA modulus, NIST P-256, P-384 or P-521, or DSA parameters of 2048 bits with a 224- or 256-bit modulus length, and the §7.1.4.2 subject distinguished name rows: a required commonName, a prohibited domainComponent, a subject attribute made only of the metadata characters the clause names, a required organizationName and two-letter countryName, one of localityName and stateOrProvinceName, and on an EV certificate a businessCategory from the four strings the clause names, a jurisdictionCountryName and a serialNumber), the ETSI qualified-certificate profile of EN 319 412-5 as `etsi-qc` (a non-critical qcStatements extension, a QcType naming one and only one of the three purposes the clause defines, a QcPDS providing an https URL and, on an EU qualified certificate, a document in English and no more than one per language, plus the EN 319 412-1 semantics identifiers holding a subject serialNumber or organizationIdentifier to a three-character identity type reference, a two-character country code, a hyphen-minus and an identifier, with XG required of an LEI value and a name registration authority URI required of a locally defined reference), the post-quantum key-usage rules binding a certificate's keyUsage bits to its subject key type (ML-KEM under RFC 9935 §5, ML-DSA under RFC 9881 §5, and SLH-DSA under RFC 9909 §6 across both its pure and prehash identifiers) together with the ML-KEM encapsulation-key encoding and size, and the §4.2.1.4 certificate-policy user-notice rules (a VisibleString or BMPString explicitText, a notice past 200 characters, an empty notice, control characters, and a non-NFC UTF8String notice, each at the strength the clause states). Alone among these entries the data path never throws: hostile bytes return a `fatal` `lint/unparseable` finding carrying the strict parser's code, so a whole directory lints without a try/catch, and only config-time misuse throws a typed `LintError` — `certificate`, `crl` (the RFC 5280 §5 revocation-list profile: nextUpdate presence, update ordering, cRLNumber presence and its 20-octet ceiling, authorityKeyIdentifier presence, the criticality each profiled CRL and entry extension must carry, freshestCRL in a delta CRL, and unrecognized critical extensions), `ocsp` (the RFC 6960 response profile: an empty signature, each SingleResponse's update ordering and its relation to producedAt, an empty `certs` field, the extended revoked definition and the fixed shape of a revoked answer for a non-issued certificate, the list each extension belongs in, the criticality and value syntax of the CRL entry extensions a SingleResponse may carry, unrecognized critical extensions, the reasons unspecified and removeFromCRL, and two answers for one CertID; the RFC 5019 lightweight rows run on request), `csr` (the artifact a CA reads before it issues: an empty subject naming no identity, an empty subject whose subjectAltName is not critical, a request for an extension the issuer determines, a request asking for a CA certificate, a broken signing digest, and a challengePassword traveling with the request; `opts.profile: "cabf-tls"` adds the certificate profile's own commonName-in-SAN, dNSName syntax, SAN-required and key-strength rows, applied to what the request asks for, and they run only when named because a request carries no extKeyUsage. It does not check the request's signature, which is a cryptographic operation `pki.csr.verify` owns), `rules`, `profiles` |
|
|
294
294
|
| `pki.C` / `pki.constants` | Version-stable constants: the functional scale helpers `C.TIME.*` and `C.BYTES.*`, the codec `LIMITS`, and `version` |
|
|
295
295
|
| `pki.errors` | The `PkiError` taxonomy — `defineClass` plus `ConstantsError`, `Asn1Error`, `OidError`, `PemError`, `CertificateError`, `CrlError`, `CsrError`, `Pkcs8Error`, `CmsError`, `OcspError`, `TspError`, `AttrCertError`, `CrmfError`, `Pkcs12Error`, `CmpError`, `PathError`, `CtError`, `JoseError`, `AcmeError`, `WebauthnError`, and `LintError`, each carrying a stable `code` in `domain/reason` form |
|
|
296
296
|
| `pki` CLI | `pki version`, `pki oid <dotted\|name>`, `pki parse <cert>`, `pki inspect <cert>`, `pki lint <cert\|crl\|ocsp-response>`, `pki convert <file> --to der\|pem`, `pki verify <cert>... --anchor <cert>`, `pki sign <file> --cert <c> --key <k>` |
|
package/lib/lint.js
CHANGED
|
@@ -1602,6 +1602,345 @@ var CABF_SMIME_RULES = [
|
|
|
1602
1602
|
},
|
|
1603
1603
|
];
|
|
1604
1604
|
|
|
1605
|
+
/** @internal The three purposes ETSI EN 319 412-5 clause 4.2.3 names, of which a QcType declares
|
|
1606
|
+
* "one and only one". The ASN.1 carries an extension marker, so an identifier outside the three
|
|
1607
|
+
* is admitted and is not one of the purposes being counted. */
|
|
1608
|
+
var QC_TYPE_PURPOSES = ["qctEsign", "qctEseal", "qctWeb"].map(function (n) { return oid.byName(n); });
|
|
1609
|
+
/** @internal The identity type references EN 319 412-1 sec. 5.1.3 and 5.1.4 define, each three
|
|
1610
|
+
* characters. A reference may instead be the locally defined form, two characters and a colon,
|
|
1611
|
+
* which is three characters as well, so the two forms differ in which references are admitted and
|
|
1612
|
+
* not in where the country code begins. The clause's own examples read "PASSK-P3000180" and
|
|
1613
|
+
* "EI:SE-200007292386". */
|
|
1614
|
+
var QC_NATURAL_TYPES = ["PAS", "IDC", "PNO", "TAX", "TIN"];
|
|
1615
|
+
var QC_LEGAL_TYPES = ["VAT", "NTR", "PSD", "LEI"];
|
|
1616
|
+
/** @internal "The value 'TAX' is deprecated. The value 'TIN' should be used instead."
|
|
1617
|
+
* (NAT-5.1.3-04), a SHOULD, so the row that reports it is graded below the structural ones. */
|
|
1618
|
+
var QC_DEPRECATED_NATURAL_TYPE = "TAX";
|
|
1619
|
+
/** @internal "LEI ... The 2 character ISO 3166-1 country code shall be set to 'XG'"
|
|
1620
|
+
* (LEG-5.1.4-03). */
|
|
1621
|
+
var QC_LEI_COUNTRY = "XG";
|
|
1622
|
+
|
|
1623
|
+
/** @internal An identity type reference as EN 319 412-1 writes it, with the rest of the value, or
|
|
1624
|
+
* null where the value takes neither form. The locally defined form is two characters and a
|
|
1625
|
+
* colon; every defined reference is three characters. Both are followed by a two-character
|
|
1626
|
+
* country code and a hyphen-minus. */
|
|
1627
|
+
function _qcIdentityParts(value) {
|
|
1628
|
+
if (typeof value !== "string") return null;
|
|
1629
|
+
var localForm = value.length > 2 && value.charCodeAt(2) === 58;
|
|
1630
|
+
var ref = value.slice(0, 3);
|
|
1631
|
+
var rest = value.slice(3);
|
|
1632
|
+
if (rest.length < 3 || rest.charCodeAt(2) !== 45) return null;
|
|
1633
|
+
var country = rest.slice(0, 2);
|
|
1634
|
+
for (var i = 0; i < 2; i++) {
|
|
1635
|
+
var c = country.charCodeAt(i);
|
|
1636
|
+
if ((c < 65 || c > 90) && (c < 97 || c > 122)) return null;
|
|
1637
|
+
}
|
|
1638
|
+
if (rest.length <= 3) return null;
|
|
1639
|
+
return { reference: ref, local: localForm, country: country, identifier: rest.slice(3) };
|
|
1640
|
+
}
|
|
1641
|
+
/** @internal Whether an identity type reference is one the clause defines, in either form. */
|
|
1642
|
+
function _qcKnownReference(parts, defined) {
|
|
1643
|
+
if (parts === null) return false;
|
|
1644
|
+
if (parts.local) return true;
|
|
1645
|
+
return defined.indexOf(parts.reference) !== -1;
|
|
1646
|
+
}
|
|
1647
|
+
/** @internal EVERY SemanticsInformation the certificate carries that names an identifier. RFC 3739
|
|
1648
|
+
* sec. 3.2.6 puts no cardinality on QCStatements, so a certificate may carry more than one, and
|
|
1649
|
+
* reading only the first would let their order in the extension suppress a rule. Both statement
|
|
1650
|
+
* versions carry the same syntax. */
|
|
1651
|
+
function _qcSemanticsAll(ctx) {
|
|
1652
|
+
var out = [];
|
|
1653
|
+
var d = ctx.decode("qcStatements");
|
|
1654
|
+
if (!d || !Array.isArray(d.value)) return out;
|
|
1655
|
+
var v1 = oid.byName("qcsPkixQCSyntaxV1"), v2 = oid.byName("qcsPkixQCSyntaxV2");
|
|
1656
|
+
for (var i = 0; i < d.value.length; i++) {
|
|
1657
|
+
var st = d.value[i];
|
|
1658
|
+
if (st.statementId !== v1 && st.statementId !== v2) continue;
|
|
1659
|
+
if (st.info && st.info.semanticsIdentifier) out.push(st.info);
|
|
1660
|
+
}
|
|
1661
|
+
return out;
|
|
1662
|
+
}
|
|
1663
|
+
/** @internal The decoded info of EVERY statement of one name, for the same reason. */
|
|
1664
|
+
function _qcStatementInfos(ctx, name) {
|
|
1665
|
+
var out = [];
|
|
1666
|
+
var d = ctx.decode("qcStatements");
|
|
1667
|
+
if (!d || !Array.isArray(d.value)) return out;
|
|
1668
|
+
var dotted = oid.byName(name);
|
|
1669
|
+
for (var i = 0; i < d.value.length; i++) {
|
|
1670
|
+
if (d.value[i].statementId === dotted) out.push(d.value[i].info === null ? {} : d.value[i].info);
|
|
1671
|
+
}
|
|
1672
|
+
return out;
|
|
1673
|
+
}
|
|
1674
|
+
/** @internal The SemanticsInformation where it names one of the two identifiers EN 319 412-1
|
|
1675
|
+
* defines, or null. The requirements of sec. 5.1.3 and 5.1.4 each sit under their own identifier,
|
|
1676
|
+
* so a certificate naming a private one is held to neither. */
|
|
1677
|
+
/** @internal Whether ANY qcsPkixQCSyntax statement names a given semantics identifier. */
|
|
1678
|
+
function _qcNamesSemantics(ctx, name) {
|
|
1679
|
+
var dotted = oid.byName(name);
|
|
1680
|
+
return _qcSemanticsAll(ctx).some(function (info) { return info.semanticsIdentifier === dotted; });
|
|
1681
|
+
}
|
|
1682
|
+
function _qcEtsiSemanticsAll(ctx) {
|
|
1683
|
+
var nat = oid.byName("semanticsId-Natural"), leg = oid.byName("semanticsId-Legal");
|
|
1684
|
+
return _qcSemanticsAll(ctx).filter(function (info) {
|
|
1685
|
+
return info.semanticsIdentifier === nat || info.semanticsIdentifier === leg;
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
/** @internal Whether a subject attribute names the locally defined identity type reference form,
|
|
1689
|
+
* which is what conditions the name registration authority requirements. */
|
|
1690
|
+
function _qcHasLocalReference(cert, attr) {
|
|
1691
|
+
var values = _subjectValuesOf(cert, attr);
|
|
1692
|
+
for (var i = 0; i < values.length; i++) {
|
|
1693
|
+
var parts = _qcIdentityParts(values[i]);
|
|
1694
|
+
if (parts !== null && parts.local) return true;
|
|
1695
|
+
}
|
|
1696
|
+
return false;
|
|
1697
|
+
}
|
|
1698
|
+
function _qcHasStatement(ctx, name) {
|
|
1699
|
+
var d = ctx.decode("qcStatements");
|
|
1700
|
+
if (!d || !Array.isArray(d.value)) return false;
|
|
1701
|
+
var dotted = oid.byName(name);
|
|
1702
|
+
for (var i = 0; i < d.value.length; i++) {
|
|
1703
|
+
if (d.value[i].statementId === dotted) return true;
|
|
1704
|
+
}
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
/** @internal A certificate carrying the extension at all, which is what clause 4 governs. */
|
|
1708
|
+
function _isQc(cert, ctx) { return !!ctx.raw("qcStatements"); }
|
|
1709
|
+
/** @internal An EU qualified certificate, which is what clause 5 governs. Table 1A reads it off
|
|
1710
|
+
* the bytes: QcCompliance says the certificate is qualified, and QcCClegislation names the
|
|
1711
|
+
* country or countries whose framework it is qualified under, so with that statement present the
|
|
1712
|
+
* certificate claims a framework other than the Regulation and clause 5 does not bind it. */
|
|
1713
|
+
function _isEuQc(cert, ctx) {
|
|
1714
|
+
return _isQc(cert, ctx) && _qcHasStatement(ctx, "qcCompliance") &&
|
|
1715
|
+
!_qcHasStatement(ctx, "qcCClegislation");
|
|
1716
|
+
}
|
|
1717
|
+
/** @internal Whether the name registration authorities carry a uniformResourceIdentifier, which
|
|
1718
|
+
* NAT-5.1.3-06 and LEG-5.1.4-05 both require of them. A decoded GeneralName names its form in
|
|
1719
|
+
* `tagNumber`; the `tag` an accessDescription carries is that record rebuilt by its own decoder,
|
|
1720
|
+
* and these are the plain form. */
|
|
1721
|
+
function _qcHasUriAuthority(info) {
|
|
1722
|
+
var list = info && info.nameRegistrationAuthorities;
|
|
1723
|
+
if (!Array.isArray(list)) return false;
|
|
1724
|
+
for (var i = 0; i < list.length; i++) {
|
|
1725
|
+
if (list[i] && list[i].tagNumber === 6) return true;
|
|
1726
|
+
}
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/** @internal The ETSI qualified-certificate profile, from EN 319 412-5 V2.4.1 and the semantics
|
|
1731
|
+
* identifiers of EN 319 412-1 V1.5.1. Clause 4 governs any certificate carrying the extension and
|
|
1732
|
+
* clause 5 an EU qualified one, so the two scopes are separate predicates. */
|
|
1733
|
+
var ETSI_QC_RULES = [
|
|
1734
|
+
{
|
|
1735
|
+
id: "lint/etsi-qc/qc-statements-critical", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-5 QCS-4.1-02",
|
|
1736
|
+
message: "the qcStatements extension must not be marked critical",
|
|
1737
|
+
appliesTo: _isQc,
|
|
1738
|
+
check: function (cert, ctx) {
|
|
1739
|
+
var e = ctx.raw("qcStatements");
|
|
1740
|
+
return (e && e.critical === true) ? true : null;
|
|
1741
|
+
},
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
id: "lint/etsi-qc/qc-type-multiple-purposes", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-5 4.2.3",
|
|
1745
|
+
/** @internal "declares that a CERTIFICATE is issued as one and only one of the purposes of
|
|
1746
|
+
* electronic signature, electronic seal or web site authentication", so the limit is on the
|
|
1747
|
+
* certificate and the purposes are counted across every QcType statement it carries: two
|
|
1748
|
+
* statements naming one purpose each break the same rule as one naming two. The QcType ASN.1
|
|
1749
|
+
* carries an extension marker, so an identifier outside the three is admitted and is not one
|
|
1750
|
+
* of the purposes being counted. */
|
|
1751
|
+
message: "the certificate names more than one of the three purposes this clause defines",
|
|
1752
|
+
appliesTo: _isQc,
|
|
1753
|
+
check: function (cert, ctx) {
|
|
1754
|
+
var named = [];
|
|
1755
|
+
_qcStatementInfos(ctx, "qcType").forEach(function (info) {
|
|
1756
|
+
if (!Array.isArray(info.types)) return;
|
|
1757
|
+
info.types.forEach(function (d) {
|
|
1758
|
+
if (QC_TYPE_PURPOSES.indexOf(d) !== -1 && named.indexOf(d) === -1) named.push(d);
|
|
1759
|
+
});
|
|
1760
|
+
});
|
|
1761
|
+
return named.length > 1 ? { context: { purposes: named } } : null;
|
|
1762
|
+
},
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
id: "lint/etsi-qc/qc-pds-no-https-url", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-5 QCS-4.3.4-03",
|
|
1766
|
+
/** @internal "As a minimum, a URL to a PDS provided in this statement shall use the https
|
|
1767
|
+
* scheme", which is the at-least-one reading: reporting each URL that is not https would fail
|
|
1768
|
+
* a statement the clause admits. */
|
|
1769
|
+
message: "a QcPDS statement provides no https URL",
|
|
1770
|
+
appliesTo: function (cert, ctx) { return _isQc(cert, ctx) && _qcHasStatement(ctx, "qcPDS"); },
|
|
1771
|
+
check: function (cert, ctx) {
|
|
1772
|
+
var out = [];
|
|
1773
|
+
_qcStatementInfos(ctx, "qcPDS").forEach(function (info) {
|
|
1774
|
+
if (!Array.isArray(info.locations)) return;
|
|
1775
|
+
var https = info.locations.filter(function (l) {
|
|
1776
|
+
return pkiBuild.httpUrlScheme(l && l.url) === "https";
|
|
1777
|
+
});
|
|
1778
|
+
if (!https.length) out.push({ context: { locations: info.locations.length } });
|
|
1779
|
+
});
|
|
1780
|
+
return out.length ? out : null;
|
|
1781
|
+
},
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
id: "lint/etsi-qc/qc-pds-no-english", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-5 table 2",
|
|
1785
|
+
/** @internal "it shall provide at least one URL to a PDS in English", an additional
|
|
1786
|
+
* requirement Table 2 states on the QcPDS statement, so it binds an EU qualified certificate
|
|
1787
|
+
* and no other. The language is an ISO 639-1 code (QCS-4.3.4-01), where English is "en". */
|
|
1788
|
+
message: "an EU qualified certificate's QcPDS statement names no document in English",
|
|
1789
|
+
appliesTo: function (cert, ctx) { return _isEuQc(cert, ctx) && _qcHasStatement(ctx, "qcPDS"); },
|
|
1790
|
+
check: function (cert, ctx) {
|
|
1791
|
+
var out = [];
|
|
1792
|
+
_qcStatementInfos(ctx, "qcPDS").forEach(function (info) {
|
|
1793
|
+
if (!Array.isArray(info.locations)) return;
|
|
1794
|
+
var english = info.locations.filter(function (l) {
|
|
1795
|
+
return typeof l.language === "string" && l.language.toLowerCase() === "en";
|
|
1796
|
+
});
|
|
1797
|
+
if (!english.length) {
|
|
1798
|
+
out.push({ context: { languages: info.locations.map(function (l) { return l.language; }) } });
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
return out.length ? out : null;
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
id: "lint/etsi-qc/qc-pds-duplicate-language", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-5 table 2",
|
|
1806
|
+
/** @internal "it shall not reference more than one PDS per language", the other additional
|
|
1807
|
+
* requirement Table 2 states on the same statement. */
|
|
1808
|
+
message: "an EU qualified certificate's QcPDS statement names more than one document in a language",
|
|
1809
|
+
appliesTo: function (cert, ctx) { return _isEuQc(cert, ctx) && _qcHasStatement(ctx, "qcPDS"); },
|
|
1810
|
+
check: function (cert, ctx) {
|
|
1811
|
+
var out = [];
|
|
1812
|
+
_qcStatementInfos(ctx, "qcPDS").forEach(function (info) {
|
|
1813
|
+
if (!Array.isArray(info.locations)) return;
|
|
1814
|
+
var seen = Object.create(null);
|
|
1815
|
+
info.locations.forEach(function (l) {
|
|
1816
|
+
if (typeof l.language !== "string") return;
|
|
1817
|
+
var key = l.language.toLowerCase();
|
|
1818
|
+
if (seen[key]) { out.push({ context: { language: l.language } }); return; }
|
|
1819
|
+
seen[key] = true;
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
return out.length ? out : null;
|
|
1823
|
+
},
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
id: "lint/etsi-qc/qc-natural-serial-number-structure", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-1 NAT-5.1.3-02 and NAT-5.1.3-03",
|
|
1827
|
+
/** @internal "When the natural person semantics identifier is included, any present
|
|
1828
|
+
* serialNumber attribute in the subject field shall contain information using the following
|
|
1829
|
+
* structure": a three character identity type reference, a two character country code, a
|
|
1830
|
+
* hyphen-minus, and an identifier. The reference is one the clause defines or the locally
|
|
1831
|
+
* defined form of two characters and a colon. */
|
|
1832
|
+
message: "a subject serialNumber does not take the structure the natural person semantics identifier requires",
|
|
1833
|
+
appliesTo: function (cert, ctx) {
|
|
1834
|
+
return _isQc(cert, ctx) && _qcNamesSemantics(ctx, "semanticsId-Natural") &&
|
|
1835
|
+
_subjectValuesOf(cert, "serialNumber").length > 0;
|
|
1836
|
+
},
|
|
1837
|
+
check: function (cert) {
|
|
1838
|
+
var out = [];
|
|
1839
|
+
_subjectValuesOf(cert, "serialNumber").forEach(function (v) {
|
|
1840
|
+
var parts = _qcIdentityParts(v);
|
|
1841
|
+
if (!_qcKnownReference(parts, QC_NATURAL_TYPES)) out.push({ context: { serialNumber: v } });
|
|
1842
|
+
});
|
|
1843
|
+
return out.length ? out : null;
|
|
1844
|
+
},
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
id: "lint/etsi-qc/qc-natural-identity-type-deprecated", severity: "warn", source: "etsi-qc", citation: "ETSI EN 319 412-1 NAT-5.1.3-04",
|
|
1848
|
+
message: "the TAX identity type reference is deprecated and TIN should be used instead",
|
|
1849
|
+
appliesTo: function (cert, ctx) {
|
|
1850
|
+
return _isQc(cert, ctx) && _qcNamesSemantics(ctx, "semanticsId-Natural");
|
|
1851
|
+
},
|
|
1852
|
+
check: function (cert) {
|
|
1853
|
+
var out = [];
|
|
1854
|
+
_subjectValuesOf(cert, "serialNumber").forEach(function (v) {
|
|
1855
|
+
var parts = _qcIdentityParts(v);
|
|
1856
|
+
if (parts && !parts.local && parts.reference === QC_DEPRECATED_NATURAL_TYPE) {
|
|
1857
|
+
out.push({ context: { serialNumber: v } });
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
return out.length ? out : null;
|
|
1861
|
+
},
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
id: "lint/etsi-qc/qc-legal-organization-identifier-structure", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-1 LEG-5.1.4-02 and LEG-5.1.4-03",
|
|
1865
|
+
message: "a subject organizationIdentifier does not take the structure the legal person semantics identifier requires",
|
|
1866
|
+
appliesTo: function (cert, ctx) {
|
|
1867
|
+
return _isQc(cert, ctx) && _qcNamesSemantics(ctx, "semanticsId-Legal") &&
|
|
1868
|
+
_subjectValuesOf(cert, "organizationIdentifier").length > 0;
|
|
1869
|
+
},
|
|
1870
|
+
check: function (cert) {
|
|
1871
|
+
var out = [];
|
|
1872
|
+
_subjectValuesOf(cert, "organizationIdentifier").forEach(function (v) {
|
|
1873
|
+
var parts = _qcIdentityParts(v);
|
|
1874
|
+
if (!_qcKnownReference(parts, QC_LEGAL_TYPES)) out.push({ context: { organizationIdentifier: v } });
|
|
1875
|
+
});
|
|
1876
|
+
return out.length ? out : null;
|
|
1877
|
+
},
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
id: "lint/etsi-qc/qc-legal-lei-country-not-xg", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-1 LEG-5.1.4-03",
|
|
1881
|
+
/** @internal "LEI for a global Legal Entity Identifier as specified in ISO 17442. The 2
|
|
1882
|
+
* character ISO 3166-1 country code shall be set to 'XG'", the one reference whose country
|
|
1883
|
+
* code the clause fixes. */
|
|
1884
|
+
message: "an LEI organizationIdentifier must carry the country code XG",
|
|
1885
|
+
appliesTo: function (cert, ctx) {
|
|
1886
|
+
return _isQc(cert, ctx) && _qcNamesSemantics(ctx, "semanticsId-Legal");
|
|
1887
|
+
},
|
|
1888
|
+
check: function (cert) {
|
|
1889
|
+
var out = [];
|
|
1890
|
+
_subjectValuesOf(cert, "organizationIdentifier").forEach(function (v) {
|
|
1891
|
+
var parts = _qcIdentityParts(v);
|
|
1892
|
+
if (!parts || parts.local || parts.reference !== "LEI") return;
|
|
1893
|
+
if (parts.country !== QC_LEI_COUNTRY) out.push({ context: { country: parts.country } });
|
|
1894
|
+
});
|
|
1895
|
+
return out.length ? out : null;
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
id: "lint/etsi-qc/qc-semantics-registration-authority-missing", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-1 NAT-5.1.3-05 and LEG-5.1.4-05",
|
|
1900
|
+
/** @internal "When a locally defined identity type reference is provided (two characters
|
|
1901
|
+
* followed by ':'), the nameRegistrationAuthorities element of SemanticsInformation shall be
|
|
1902
|
+
* present." The same sentence appears under each semantics identifier, so one row answers for
|
|
1903
|
+
* both and reads whichever attribute the identifier governs. */
|
|
1904
|
+
message: "a locally defined identity type reference requires a nameRegistrationAuthorities element",
|
|
1905
|
+
appliesTo: function (cert, ctx) { return _isQc(cert, ctx) && _qcEtsiSemanticsAll(ctx).length > 0; },
|
|
1906
|
+
check: function (cert, ctx) {
|
|
1907
|
+
var out = [];
|
|
1908
|
+
_qcEtsiSemanticsAll(ctx).forEach(function (info) {
|
|
1909
|
+
var attr = info.semanticsIdentifier === oid.byName("semanticsId-Legal")
|
|
1910
|
+
? "organizationIdentifier" : "serialNumber";
|
|
1911
|
+
if (!_qcHasLocalReference(cert, attr)) return;
|
|
1912
|
+
var list = info.nameRegistrationAuthorities;
|
|
1913
|
+
if (!Array.isArray(list) || !list.length) out.push({ context: { attribute: attr } });
|
|
1914
|
+
});
|
|
1915
|
+
return out.length ? out : null;
|
|
1916
|
+
},
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
id: "lint/etsi-qc/qc-semantics-registration-authority-not-uri", severity: "error", source: "etsi-qc", citation: "ETSI EN 319 412-1 NAT-5.1.3-06 and LEG-5.1.4-05",
|
|
1920
|
+
/** @internal It "shall contain at least a uniformResourceIdentifier generalName", which is what
|
|
1921
|
+
* makes a locally defined reference resolvable. The two clauses state it differently and the
|
|
1922
|
+
* rows follow them: NAT-5.1.3-06 states it of the element itself, so it binds wherever a
|
|
1923
|
+
* natural person identifier carries one, where LEG-5.1.4-05 states it inside the sentence
|
|
1924
|
+
* about a locally defined reference and binds only there. Both sit under their own semantics
|
|
1925
|
+
* identifier, so a certificate naming a private one is held to neither. */
|
|
1926
|
+
message: "a nameRegistrationAuthorities element must carry a uniformResourceIdentifier",
|
|
1927
|
+
appliesTo: function (cert, ctx) { return _isQc(cert, ctx) && _qcEtsiSemanticsAll(ctx).length > 0; },
|
|
1928
|
+
check: function (cert, ctx) {
|
|
1929
|
+
var out = [];
|
|
1930
|
+
_qcEtsiSemanticsAll(ctx).forEach(function (info) {
|
|
1931
|
+
var list = info.nameRegistrationAuthorities;
|
|
1932
|
+
if (!Array.isArray(list) || !list.length) return;
|
|
1933
|
+
var natural = info.semanticsIdentifier === oid.byName("semanticsId-Natural");
|
|
1934
|
+
if (!natural && !_qcHasLocalReference(cert, "organizationIdentifier")) return;
|
|
1935
|
+
if (!_qcHasUriAuthority(info)) {
|
|
1936
|
+
out.push({ context: { semanticsIdentifier: oid.name(info.semanticsIdentifier) } });
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
return out.length ? out : null;
|
|
1940
|
+
},
|
|
1941
|
+
},
|
|
1942
|
+
];
|
|
1943
|
+
|
|
1605
1944
|
/** @internal The CA/Browser Forum Code Signing BR v3.11.0 subscriber profile. Every severity is
|
|
1606
1945
|
* graded against the clause the row cites, never against the same-named row of another profile:
|
|
1607
1946
|
* this document says MUST where the S/MIME one says SHOULD for cRLDistributionPoints criticality,
|
|
@@ -3702,9 +4041,10 @@ var PROFILES = Object.assign(Object.create(null), {
|
|
|
3702
4041
|
"cabf-tls": CABF_TLS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
3703
4042
|
"cabf-smime": CABF_SMIME_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
3704
4043
|
"cabf-cs": CABF_CS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
4044
|
+
"etsi-qc": ETSI_QC_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
3705
4045
|
});
|
|
3706
4046
|
var ALL_RULES = RFC5280_RULES.concat(RFC9881_RULES).concat(RFC9909_RULES).concat(RFC9935_RULES)
|
|
3707
|
-
.concat(CABF_TLS_RULES).concat(CABF_SMIME_RULES).concat(CABF_CS_RULES);
|
|
4047
|
+
.concat(CABF_TLS_RULES).concat(CABF_SMIME_RULES).concat(CABF_CS_RULES).concat(ETSI_QC_RULES);
|
|
3708
4048
|
|
|
3709
4049
|
var CABF_TLS_CSR_RULES = CABF_TLS_RULES.filter(function (r) {
|
|
3710
4050
|
return CABF_TLS_CSR_RULE_IDS.indexOf(r.id) !== -1;
|
package/lib/oid.js
CHANGED
|
@@ -83,6 +83,13 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
83
83
|
|
|
84
84
|
etsiQcType: { base: [0, 4, 0, 1862, 1, 6], of: { qctEsign: 1, qctEseal: 2, qctWeb: 3 } },
|
|
85
85
|
|
|
86
|
+
/** @internal The semantics identifiers of ETSI EN 319 412-1 sec. 5.1.2, which a
|
|
87
|
+
* SemanticsInformation names to say how a subject serialNumber or organizationIdentifier is to
|
|
88
|
+
* be read. */
|
|
89
|
+
etsiQcsSemantics: { base: [0, 4, 0, 194121, 1], of: {
|
|
90
|
+
"semanticsId-Natural": 1, "semanticsId-Legal": 2,
|
|
91
|
+
"semanticsId-eIDASNatural": 3, "semanticsId-eIDASLegal": 4 } },
|
|
92
|
+
|
|
86
93
|
msEnrollment: { base: [1, 3, 6, 1, 4, 1, 311, 21], of: {
|
|
87
94
|
msCaVersion: 1, msPreviousCertHash: 2, msCertificateTemplate: 7, msApplicationPolicies: 10 } },
|
|
88
95
|
|
package/lib/schema-pkix.js
CHANGED
|
@@ -1990,7 +1990,10 @@ function certExtensionDecoders(ns) {
|
|
|
1990
1990
|
i++;
|
|
1991
1991
|
}
|
|
1992
1992
|
if (i !== node.children.length) throw ns.E(C, "SemanticsInformation has unexpected trailing fields");
|
|
1993
|
-
|
|
1993
|
+
/** @internal The identifier resolves to its registered name the way QcType's and
|
|
1994
|
+
* QcIdentMethod's values do, and stays null for one the registry does not name. */
|
|
1995
|
+
return { semanticsIdentifier: sid, semanticsIdentifierName: sid === null ? null : (ns.oid.name(sid) || null),
|
|
1996
|
+
nameRegistrationAuthorities: nras };
|
|
1994
1997
|
}
|
|
1995
1998
|
var qcInfoByOid = _create(null);
|
|
1996
1999
|
qcInfoByOid[O("qcCompliance")] = _qcPresenceOnly("QcCompliance");
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:0f0cede4-1bf3-480e-818c-d982a201c398",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-24T12:14:29.178Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.21",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.21",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.21",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.21",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|