@blamejs/pki 0.4.15 → 0.5.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/MIGRATING.md +2 -2
  3. package/README.md +142 -137
  4. package/index.js +4 -0
  5. package/lib/acme.js +73 -1
  6. package/lib/asn1-der.js +2 -0
  7. package/lib/attrcert-sign.js +4 -0
  8. package/lib/cbor-det.js +32 -16
  9. package/lib/cmc-build.js +880 -0
  10. package/lib/cmc-verify.js +657 -0
  11. package/lib/cmp-build.js +8 -7
  12. package/lib/cmp-verify.js +11 -1
  13. package/lib/cms-sign.js +170 -8
  14. package/lib/cms-verify.js +80 -14
  15. package/lib/crl-sign.js +22 -0
  16. package/lib/crmf-sign.js +5 -2
  17. package/lib/csr-sign.js +3 -0
  18. package/lib/ct.js +72 -0
  19. package/lib/est.js +828 -32
  20. package/lib/framework-error.js +13 -0
  21. package/lib/guard-bytes.js +37 -1
  22. package/lib/guard-range.js +23 -1
  23. package/lib/http-transport.js +9 -3
  24. package/lib/inspect.js +28 -5
  25. package/lib/jose.js +15 -0
  26. package/lib/lint.js +4 -0
  27. package/lib/merkle.js +5 -5
  28. package/lib/ocsp.js +139 -11
  29. package/lib/oid.js +69 -1
  30. package/lib/path-validate.js +27 -4
  31. package/lib/pkcs12-build.js +12 -0
  32. package/lib/schema-all.js +19 -1
  33. package/lib/schema-attrcert.js +27 -0
  34. package/lib/schema-c509.js +6 -0
  35. package/lib/schema-cmc.js +791 -0
  36. package/lib/schema-cmp.js +25 -0
  37. package/lib/schema-cms.js +17 -1
  38. package/lib/schema-crl.js +23 -1
  39. package/lib/schema-crmf.js +13 -0
  40. package/lib/schema-csr.js +11 -0
  41. package/lib/schema-csrattrs.js +6 -0
  42. package/lib/schema-engine.js +6 -2
  43. package/lib/schema-ocsp.js +41 -0
  44. package/lib/schema-pkcs12.js +16 -0
  45. package/lib/schema-pkcs8.js +8 -0
  46. package/lib/schema-smime.js +4 -4
  47. package/lib/schema-tsp.js +32 -1
  48. package/lib/schema-x509.js +14 -1
  49. package/lib/shbs.js +12 -4
  50. package/lib/sigstore.js +4 -0
  51. package/lib/smime.js +28 -7
  52. package/lib/tls-cert-compress.js +15 -3
  53. package/lib/trust.js +27 -4
  54. package/lib/tsp-sign.js +41 -6
  55. package/lib/vendor/README.md +19 -19
  56. package/lib/webauthn.js +895 -26
  57. package/lib/x509-sign.js +3 -0
  58. package/package.json +1 -1
  59. package/sbom.cdx.json +6 -6
package/lib/oid.js CHANGED
@@ -132,7 +132,65 @@ var FAMILIES = {
132
132
  // name->OID reverse stays unambiguous next to the id-kp "timeStamping" purpose (a distinct arc).
133
133
  adAccess: { base: [1, 3, 6, 1, 5, 5, 7, 48], of: { ocsp: 1, caIssuers: 2,
134
134
  "id-ad-timeStamping": 3, "id-ad-caRepository": 5, "id-ad-rpkiManifest": 10,
135
- "id-ad-signedObject": 11, "id-ad-rpkiNotify": 13 } },
135
+ "id-ad-signedObject": 11, "id-ad-rpkiNotify": 13, "id-ad-cmc": 12 } },
136
+
137
+ // CMC control attributes -- id-cmc ::= {id-pkix 7} (RFC 5272 sec. 6, extended by RFC 6402
138
+ // sec. 2.2/2.8). The whole registry is declared, not just the controls used today: a control
139
+ // this side does not act on must still be RECOGNIZED to be reported by name, and a decoder that
140
+ // meets an unknown OID cannot tell "control this build ignores" from "attacker-supplied filler".
141
+ //
142
+ // The gaps are the specification's own: 12/13/14 (identityProof/popLinkRandom/popLinkWitness's
143
+ // superseded siblings are retained below where RFC 6402 kept them) and 20 are unassigned in
144
+ // RFC 5272 Appendix A. V2 forms exist because RFC 6402 replaced the originals to fix a hash
145
+ // agility gap -- BOTH are registered, since a v1 message is still legal to receive.
146
+ idCmc: { base: [1, 3, 6, 1, 5, 5, 7, 7], of: {
147
+ "id-cmc-statusInfo": 1, "id-cmc-identification": 2, "id-cmc-identityProof": 3,
148
+ "id-cmc-dataReturn": 4, "id-cmc-transactionId": 5, "id-cmc-senderNonce": 6,
149
+ "id-cmc-recipientNonce": 7, "id-cmc-addExtensions": 8, "id-cmc-encryptedPOP": 9,
150
+ "id-cmc-decryptedPOP": 10, "id-cmc-lraPOPWitness": 11,
151
+ "id-cmc-getCert": 15, "id-cmc-getCRL": 16, "id-cmc-revokeRequest": 17,
152
+ "id-cmc-regInfo": 18, "id-cmc-responseInfo": 19,
153
+ "id-cmc-queryPending": 21, "id-cmc-popLinkRandom": 22, "id-cmc-popLinkWitness": 23,
154
+ "id-cmc-confirmCertAcceptance": 24, "id-cmc-statusInfoV2": 25, "id-cmc-trustedAnchors": 26,
155
+ "id-cmc-authData": 27, "id-cmc-batchRequests": 28, "id-cmc-batchResponses": 29,
156
+ "id-cmc-publishCert": 30, "id-cmc-modCertTemplate": 31, "id-cmc-controlProcessed": 32,
157
+ // ---------------------------------------------------------------------
158
+ // 33 / 34 -- the specification assigns these two OIDs BOTH ways, and no
159
+ // erratum resolves it. Counted across every place either document states an
160
+ // assignment, it is not an even split:
161
+ //
162
+ // identityProofV2 = 34, popLinkWitnessV2 = 33
163
+ // rfc5272.txt:1494 / :1504 the sec. 6 control summary table
164
+ // rfc5272.txt:1846 sec. 6.2.1 body
165
+ // rfc5272.txt:2037 sec. 6.3.1.1 body
166
+ // rfc5272.txt:4041 / :4049 RFC 5272's own ASN.1 module
167
+ // rfc6402.txt:1228 / :1245 RFC 6402 Appendix A.1 (1988 module)
168
+ //
169
+ // identityProofV2 = 33, popLinkWitnessV2 = 34
170
+ // rfc6402.txt:1938 / :1949 RFC 6402 Appendix A.2 (2008 module) ONLY
171
+ //
172
+ // The rows below therefore follow the base specification's normative body
173
+ // text and its module, which RFC 6402's own 1988 module agrees with. The
174
+ // 2008 module is the lone outlier and is treated as its typo.
175
+ //
176
+ // This binds what the toolkit EMITS, and it is load-bearing because the two
177
+ // controls cannot be told apart by SHAPE: both are SEQUENCE {
178
+ // AlgorithmIdentifier, AlgorithmIdentifier, OCTET STRING } (rfc6402.txt:1238,
179
+ // :1246), so the OID is the only discriminator. Emitting the A.2 reading
180
+ // would hand a peer built on RFC 5272 an Identity Proof it reads as a POP
181
+ // Link Witness. A CONSUMER must still not infer meaning from these names
182
+ // alone -- a peer built against A.2 means the other one, and nothing on the
183
+ // wire says which module it read.
184
+ "id-cmc-identityProofV2": 34, "id-cmc-popLinkWitnessV2": 33,
185
+ "id-cmc-raIdentityWitness": 35, "id-cmc-changeSubjectName": 36, "id-cmc-responseBody": 37,
186
+ } },
187
+
188
+ // CMC content types -- id-cct ::= {id-pkix 12} (RFC 5272 Appendix A). The eContentType a CMC
189
+ // message rides under inside its CMS SignedData wrapper. RFC 5272 Appendix B repeatedly writes
190
+ // "id-ct-PKIData"; that is erratum 4775 (Verified) and the correct prefix is id-cct-.
191
+ idCct: { base: [1, 3, 6, 1, 5, 5, 7, 12], of: {
192
+ "id-cct-PKIData": 2, "id-cct-PKIResponse": 3,
193
+ } },
136
194
 
137
195
  // id-on -- RFC 5280 sec. 4.2.1.6 otherName type-ids the C509 sec. 8.13 registry gives their own
138
196
  // negative ints: id-on-hardwareModuleName (RFC 4108), id-on-SmtpUTF8Mailbox (RFC 9598),
@@ -312,6 +370,11 @@ var FAMILIES = {
312
370
  // identifiers (RFC 9802 sec. 4). HSS/LMS additionally has the SMIME
313
371
  // id-alg-hss-lms-hashsig OID above (RFC 9708 / RFC 9802 share it).
314
372
  pkixAlg: { base: [1, 3, 6, 1, 5, 5, 7, 6], of: {
373
+ // id-alg-noSignature (RFC 6402 sec. 2.4, {id-pkix id-alg(6) 2}): the CMC
374
+ // "signature" algorithm for a request whose signer has no key yet -- the
375
+ // SignerInfo carries a MAC-based proof instead. Registered so a decoder can
376
+ // NAME it and refuse it deliberately, rather than meeting an unknown OID.
377
+ "id-alg-noSignature": 2,
315
378
  "id-alg-xmss-hashsig": 34, "id-alg-xmssmt-hashsig": 35,
316
379
  // Composite ML-DSA signature algorithms (draft-ietf-lamps-pq-composite-sigs
317
380
  // sec. 6): a PQ ML-DSA paired with a traditional RSA / ECDSA / EdDSA so the
@@ -348,6 +411,11 @@ var FAMILIES = {
348
411
  receiptRequest: 1, eSSSecurityLabel: 2, mlExpansionHistory: 3, contentHints: 4,
349
412
  msgSigDigest: 5, contentIdentifier: 7, equivalentLabels: 9, contentReference: 10,
350
413
  signingCertificate: 12, timeStampToken: 14, decryptKeyID: 37, signingCertificateV2: 47,
414
+ // id-aa-cmc-unsignedData (RFC 6402 sec. 2.7, {id-aa 34}): the CMC unsigned
415
+ // attribute that carries body parts too large to sit inside the signed
416
+ // PKIData -- unsigned by design, so a consumer must treat its contents as
417
+ // untrusted data rather than as part of the authenticated message.
418
+ cmcUnsignedData: 34,
351
419
  asymmDecryptKeyID: 54, certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
352
420
 
353
421
  // ANSI X9.62 EC public key, named curve, and ECDSA signatures.
@@ -1107,7 +1107,7 @@ function validateCriticalExtensionStructure(cert) {
1107
1107
  * @primitive pki.path.validate
1108
1108
  * @signature pki.path.validate(path, opts) -> Promise<result>
1109
1109
  * @since 0.1.16
1110
- * @status experimental
1110
+ * @status stable
1111
1111
  * @spec RFC 5280
1112
1112
  * @related pki.schema.x509.parse, pki.path.crlChecker
1113
1113
  *
@@ -1134,6 +1134,10 @@ function validateCriticalExtensionStructure(cert) {
1134
1134
  * throws a typed `PathError`.
1135
1135
  *
1136
1136
  * @example
1137
+ * var pair = await pki.key.generate("Ed25519");
1138
+ * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
1139
+ * notBefore: new Date("2019-01-01T00:00:00Z"), notAfter: new Date("2029-01-01T00:00:00Z") },
1140
+ * { key: await pki.key.export(pair.privateKey) });
1137
1141
  * var cert = pki.schema.x509.parse(der);
1138
1142
  * var res = await pki.path.validate([cert], {
1139
1143
  * time: new Date("2020-01-01T00:00:00Z"),
@@ -1769,7 +1773,7 @@ function selectDelta(candidates) {
1769
1773
  * @primitive pki.path.crlChecker
1770
1774
  * @signature pki.path.crlChecker(crls, opts?) -> RevocationChecker
1771
1775
  * @since 0.1.16
1772
- * @status experimental
1776
+ * @status stable
1773
1777
  * @spec RFC 5280
1774
1778
  * @related pki.path.validate, pki.schema.crl.parse
1775
1779
  *
@@ -2212,7 +2216,7 @@ cmpSession.setEngine({ build: build, validate: validate, toAnchor: toAnchor, coe
2212
2216
  * @primitive pki.path.ocspChecker
2213
2217
  * @signature pki.path.ocspChecker(responses) -> RevocationChecker
2214
2218
  * @since 0.1.32
2215
- * @status experimental
2219
+ * @status stable
2216
2220
  * @spec RFC 6960
2217
2221
  * @related pki.path.validate, pki.schema.ocsp.parseResponse, pki.path.crlChecker
2218
2222
  *
@@ -2291,7 +2295,7 @@ function ocspChecker(responses) {
2291
2295
  * @primitive pki.path.verifyOcspResponse
2292
2296
  * @signature pki.path.verifyOcspResponse(parsedResponse, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, reason }>
2293
2297
  * @since 0.2.22
2294
- * @status experimental
2298
+ * @status stable
2295
2299
  * @spec RFC 6960
2296
2300
  * @related pki.ocsp.verify, pki.path.ocspChecker
2297
2301
  *
@@ -2318,6 +2322,20 @@ function ocspChecker(responses) {
2318
2322
  * fault surfaces as the parser's typed `ocsp/*` / `asn1/*` error.
2319
2323
  *
2320
2324
  * @example
2325
+ * var ca = await pki.key.generate("Ed25519");
2326
+ * var caKey = await pki.key.export(ca.privateKey);
2327
+ * var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
2328
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
2329
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } },
2330
+ * { key: caKey });
2331
+ * var leaf = await pki.key.generate("Ed25519");
2332
+ * var leafDer = await pki.x509.sign({ subject: "leaf.example", subjectPublicKey: await pki.key.export(leaf.publicKey),
2333
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
2334
+ * { cert: caDer, key: caKey });
2335
+ * var der = await pki.ocsp.sign(
2336
+ * { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
2337
+ * { cert: caDer, key: caKey });
2338
+ * var cert = pki.schema.x509.parse(leafDer), issuerCert = pki.schema.x509.parse(caDer);
2321
2339
  * var resp = pki.schema.ocsp.parseResponse(der);
2322
2340
  * var v = await pki.path.verifyOcspResponse(resp, cert, issuerCert, new Date());
2323
2341
  * v.status; // "good" | "revoked" | "unknown"
@@ -2674,6 +2692,11 @@ async function _fetchAiaIssuers(current, aia) {
2674
2692
  * @opts maxResponseBytes Per-fetch response size cap, forwarded to the transport (tightenable downward only).
2675
2693
  * @opts (validate options) Every `pki.path.validate` option (`requiredEku`, `revocationChecker`, `checkPurpose`, the initial policy inputs, ...) is forwarded unchanged.
2676
2694
  * @example
2695
+ * var pair = await pki.key.generate("Ed25519");
2696
+ * var pemString = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(pair.publicKey),
2697
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
2698
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } },
2699
+ * { key: await pki.key.export(pair.privateKey) }, { pem: true });
2677
2700
  * var result = await pki.path.build(pemString, {
2678
2701
  * candidates: [], // untrusted intermediates (the openssl -untrusted set)
2679
2702
  * trustAnchors: [pemString], // a self-signed root, or a { name, publicKey, algorithm } tuple
@@ -469,6 +469,10 @@ function _normalizeSpec(spec, opts) {
469
469
  * - `recipientCerts` -- (public-key privacy convenience) `[certDer|PEM, ...]`; the convenience-form cert + key are placed in one recipient-enveloped safe (a plaintext keyBag). For the full form, set per-`safeContents` `recipients: [{ cert }, ...]` -- CERTIFICATE recipients only (RSA-OAEP / ECDH / X25519 / X448 / ML-KEM, dispatched off the cert key); a password or KEK recipient is not public-key privacy and is rejected -- with an optional `contentEncryptionAlgorithm` (`aes-128|192|256-cbc`, default 256; GCM/AEAD rejected). `encrypt` (password) and `recipients` (public-key) on the same safe is rejected; privacy is independent of integrity.
470
470
  * - `pem` (boolean) -- return a PEM `PKCS12` string instead of DER.
471
471
  * @example
472
+ * var pair = await pki.key.generate("Ed25519");
473
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
474
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
475
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
472
476
  * var p12 = await pki.pkcs12.build({ safeContents: [{ bags: [
473
477
  * { type: 'cert', cert: signerCertDer },
474
478
  * { type: 'shroudedKey', key: signerKeyPkcs8, encrypt: { password: 'changeit' } } ] }] },
@@ -526,6 +530,10 @@ async function build(spec, opts) {
526
530
  * or public-key-integrity store, or an unsupported MAC algorithm (never a falsy verdict standing in for an error).
527
531
  *
528
532
  * @example
533
+ * var pair = await pki.key.generate("Ed25519");
534
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
535
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
536
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
529
537
  * var p12 = await pki.pkcs12.build({ key: signerKeyPkcs8, cert: signerCertDer }, { password: 'changeit' });
530
538
  * var ok = await pki.pkcs12.verifyMac(p12, 'changeit');
531
539
  */
@@ -614,6 +622,10 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
614
622
  * - `keys` (string) -- `der` (default) or `crypto` (also `pki.key.import` each private key to a CryptoKey).
615
623
  * - `importAlgorithm` -- forwarded to `pki.key.import` for the ambiguous RSA / EC arms when `keys: crypto`.
616
624
  * @example
625
+ * var pair = await pki.key.generate("Ed25519");
626
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
627
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
628
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
617
629
  * var p12 = await pki.pkcs12.build({ key: signerKeyPkcs8, cert: signerCertDer }, { password: 'changeit' });
618
630
  * var store = await pki.pkcs12.open(p12, 'changeit');
619
631
  * var keyDer = store.keys[0].pkcs8, certDer = store.certs[0].cert;
package/lib/schema-all.js CHANGED
@@ -50,6 +50,15 @@ var attrcert = require("./schema-attrcert");
50
50
  // SEQUENCE) so it is deliberately absent from FORMATS / the detect-and-route
51
51
  // `parse`; it is reached only by explicit OID dispatch on a CMS attribute.
52
52
  var smime = require("./schema-smime");
53
+ // cmc is likewise a COMPANION decoder, for the same reason arrived at from the
54
+ // other direction: a CMC message's outer DER root IS a CMS ContentInfo, which
55
+ // `cms.matches` already claims below. A `cmc` FORMATS row would either shadow CMS
56
+ // or be shadowed by it, and there is no structurally disjoint root to detect --
57
+ // the discriminator is the encapsulated content type, one layer in. So it is
58
+ // reached by explicit call, or by dispatching on
59
+ // `pki.schema.cms.parse(der).encapContentInfo.eContentType`, and exports no
60
+ // `matches` for nobody to call.
61
+ var cmc = require("./schema-cmc");
53
62
  var frameworkError = require("./framework-error");
54
63
 
55
64
  var SchemaError = frameworkError.SchemaError;
@@ -276,6 +285,10 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
276
285
  * errors of the matched format propagate unchanged.
277
286
  *
278
287
  * @example
288
+ * var pair = await pki.key.generate("Ed25519");
289
+ * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
290
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
291
+ * { key: await pki.key.export(pair.privateKey) });
279
292
  * var parsed = pki.schema.parse(der); // cert -> the pki.schema.x509 shape
280
293
  */
281
294
  function parse(input) {
@@ -294,7 +307,7 @@ function parse(input) {
294
307
  * @primitive pki.schema.detectFormat
295
308
  * @signature pki.schema.detectFormat(input) -> string | null
296
309
  * @since 0.3.8
297
- * @status experimental
310
+ * @status stable
298
311
  * @spec RFC 5280
299
312
  * @related pki.schema.parse, pki.schema.all
300
313
  *
@@ -307,6 +320,10 @@ function parse(input) {
307
320
  * same coercion / decode error `parse` throws.
308
321
  *
309
322
  * @example
323
+ * var pair = await pki.key.generate("Ed25519");
324
+ * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
325
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
326
+ * { key: await pki.key.export(pair.privateKey) });
310
327
  * pki.schema.detectFormat(der); // "x509" | "crl" | "csr" | "cms" | ... | null
311
328
  */
312
329
  function detectFormat(input) {
@@ -339,6 +356,7 @@ module.exports = {
339
356
  csrattrs: { parse: csrattrs.parse },
340
357
  attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode },
341
358
  smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
359
+ cmc: { parse: cmc.parse, parsePkiData: cmc.parsePkiData, parsePkiResponse: cmc.parsePkiResponse },
342
360
  all: all,
343
361
  parse: parse,
344
362
  detectFormat: detectFormat,
@@ -340,6 +340,15 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
340
340
  * and `Asn1Error` when the underlying DER is malformed.
341
341
  *
342
342
  * @example
343
+ * var pair = await pki.key.generate("Ed25519");
344
+ * var key = await pki.key.export(pair.privateKey);
345
+ * var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
346
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
347
+ * var der = await pki.attrcert.sign(
348
+ * { holder: { entityName: { directoryName: "CN=Alice" } },
349
+ * notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
350
+ * attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
351
+ * { cert: aaCert, key: key });
343
352
  * var ac = pki.schema.attrcert.parse(der);
344
353
  * ac.attributes[0].name; // "role"
345
354
  * ac.validity.notAfterTime;// Date
@@ -383,6 +392,15 @@ function parseV1(input) {
383
392
  * `PemError` on a missing / mismatched envelope or a non-base64 body.
384
393
  *
385
394
  * @example
395
+ * var pair = await pki.key.generate("Ed25519");
396
+ * var key = await pki.key.export(pair.privateKey);
397
+ * var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
398
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
399
+ * var pemText = await pki.attrcert.sign(
400
+ * { holder: { entityName: { directoryName: "CN=Alice" } },
401
+ * notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
402
+ * attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
403
+ * { cert: aaCert, key: key }, { pem: true });
386
404
  * var der = pki.schema.attrcert.pemDecode(pemText);
387
405
  */
388
406
  function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? null : (label || "ATTRIBUTE CERTIFICATE"), PemError); }
@@ -399,6 +417,15 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? n
399
417
  * same default `pemDecode` reads). Throws `PemError` on a malformed label.
400
418
  *
401
419
  * @example
420
+ * var pair = await pki.key.generate("Ed25519");
421
+ * var key = await pki.key.export(pair.privateKey);
422
+ * var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
423
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
424
+ * var der = await pki.attrcert.sign(
425
+ * { holder: { entityName: { directoryName: "CN=Alice" } },
426
+ * notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
427
+ * attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
428
+ * { cert: aaCert, key: key });
402
429
  * var pem = pki.schema.attrcert.pemEncode(der);
403
430
  */
404
431
  function pemEncode(der, label) { return pkix.pemEncode(der, label || "ATTRIBUTE CERTIFICATE", PemError); }
@@ -2327,6 +2327,12 @@ function _encodeC509Array(r) {
2327
2327
  * only for the DER -> type-3 path; ignored when re-emitting a parse result.
2328
2328
  *
2329
2329
  * @example
2330
+ * // the type-3 (natively signed) encoding covers ECDSA-signed X.509 v3 certificates
2331
+ * var pair = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
2332
+ * var signerCertDer = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
2333
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
2334
+ * extensions: { keyUsage: ["digitalSignature"] } },
2335
+ * { key: await pki.key.export(pair.privateKey) });
2330
2336
  * var cbor = pki.schema.c509.encode(signerCertDer); // a DER cert -> a compact type-3 C509
2331
2337
  * pki.schema.c509.parse(cbor).certificateType; // 3
2332
2338
  */