Certification requests

The PKCS#10 certification-request producing side. pki.csr.sign builds a CertificationRequestInfo, signs it with the subject's own private key (proof of possession, since a CSR has no issuer), and emits a CertificationRequest (RFC 2986) that pki.schema.csr.parse, OpenSSL, and a CA enrollment pipeline all accept. Requested v3 extensions ride in a PKCS#9 extensionRequest attribute (RFC 2985) a CA copies into the issued certificate. Parsing lives at pki.schema.csr.parse.

pki.csr.sign

since 0.3.1 stable
pki.csr.sign(spec, key, opts?) -> Promise<Buffer|string>

Build, sign, and DER-encode a PKCS#10 certification request. spec describes the request: subject (a common-name string, an array of RDNs, or raw Name DER; MAY be empty), subjectPublicKey (the SPKI DER of the key being certified), and optional extensionRequest (requested v3 extensions, as an object of subjectAltName / keyUsage / extendedKeyUsage / basicConstraints / certificatePolicies / subjectKeyIdentifier, or an array of pre-encoded Extension DER) and challengePassword. key (or { key }) is the subject's own PKCS#8 private key / WebCrypto CryptoKey, so the request is self-signed to prove possession of the private half of subjectPublicKey, and that proof is verified before the request is returned. The signature algorithm is resolved from the subject key (RSA PKCS#1 v1.5 or PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm). Returns DER, or a PEM CERTIFICATE REQUEST with opts.pem. Malformed input throws a typed CsrError; where the spec carries raw DER (a Name Buffer, a pre-encoded requested Extension or Attribute) a malformed leaf inside those bytes throws Asn1Error instead. Certificate-request parsing is pki.schema.csr.parse.

Options

- `pem` (boolean) -- return a PEM `CERTIFICATE REQUEST` string instead of DER.
- `pss` (boolean) -- sign an RSA key with RSASSA-PSS instead of PKCS#1 v1.5.
- `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var signerSpki = await pki.key.export(pair.publicKey);
  var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
  var req = await pki.csr.sign(
    { subject: "req.example.com", subjectPublicKey: signerSpki,
      extensionRequest: { subjectAltName: [{ dNSName: "req.example.com" }] } },
    { key: signerKeyPkcs8 });
  pki.schema.csr.parse(req).subject.dn;   // "CN=req.example.com"
}
example();

References

pki.csr.verify

since 0.5.13 stable
pki.csr.verify(request) -> Promise<{ verified, subject, subjectPublicKeyInfo, attributes, certificationRequestInfoBytes }>

Verify a certification request's signature over its exact parsed certificationRequestInfo bytes. request is a DER Buffer, a PEM string, or a parsed request. A CSR carries no issuer: the verifying key is the subjectPKInfo inside the signed preimage, so this is the proof of possession openssl req -verify checks, and a CA that issues without it certifies a key the requester may not hold.

The result carries verified alongside the subject, subjectPublicKeyInfo, attributes and certificationRequestInfoBytes that were verified, all re-derived from the request's own bytes. Issue from those rather than from the argument: a request normalized in place before verifying leaves the caller holding edited fields, and a bare boolean would answer about the signed bytes while the certificate got built from the edits.

What true establishes is bounded, and the bound is the point. It says the producer held the private half of the key inside this request, over bytes that include the subject name and every requested extension, so none of them were altered after signing. It says nothing about who the producer is: the key is self-asserted, the name is self-asserted, and a requester free to choose both can prove possession of a key they generated a moment ago under any name they like. Binding that name to an identity is the enrollment protocol's job -- pki.est, pki.cmc, pki.cmp, or an out-of-band check -- and remains one after this returns true.

Verification composes the one path-validation signature engine, with the same algorithm-confusion (RFC 9814 sec. 4 key-OID == sig-OID) and EdDSA low-order-point gates, rather than the self-check this module's signing side runs over a key the caller already controls. It fails closed to false on any import or verification fault; malformed input throws a typed CsrError.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var spki = await pki.key.export(pair.publicKey);
  var pkcs8 = await pki.key.export(pair.privateKey);
  // A bare string is the commonName VALUE, so this asks for CN=device-42.
  var req = await pki.csr.sign({ subject: "device-42", subjectPublicKey: spki }, { key: pkcs8 });
  var r = await pki.csr.verify(req);
  // Issue from r.subject / r.subjectPublicKeyInfo / r.attributes, which are the verified fields.
  var issued = r.verified
    ? await pki.x509.sign({ subject: r.subject.dn, subjectPublicKey: r.subjectPublicKeyInfo.bytes,
        notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2027-01-01T00:00:00Z") },
      { key: pkcs8 })
    : null;
}
example();

References