npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

csrkit

v0.1.0

Published

build pkcs#10 certificate signing requests on webcrypto, zero deps

Readme

csrkit

build pkcs#10 certificate signing requests on webcrypto. zero deps.

npm i csrkit

why

to get a certificate you have to hand the ca a csr, and generating one in javascript means either shelling out to openssl or pulling in a pki library with ten dependencies. neither works on cloudflare workers, deno deploy or anywhere else without node builtins — which is exactly where people now want to mint their own certificates.

a csr is a small der structure and one signature. crypto.subtle already does the signature, so the only real work is the encoding.

use

import { createCsr, generateKeys, privateKeyPem } from 'csrkit'

const keys = await generateKeys()          // p-256 by default

const csr = await createCsr({
  names: ['example.com', 'www.example.com', '*.api.example.com'],
  subject: { O: 'Kokomo Games', C: 'RW' },
  keys
})

csr.pem         // -----BEGIN CERTIFICATE REQUEST-----
csr.der         // Uint8Array
csr.base64url   // what acme's finalize endpoint wants

await privateKeyPem(keys)   // keep this next to the certificate

every name goes into the subjectAltName, which is the only place modern clients look. the common name is set to the first name as well, for anything old that still reads it.

algorithms

await generateKeys('P-256')     // default
await generateKeys('P-384')
await generateKeys('RSA-2048')
await generateKeys('RSA-4096')

or bring your own CryptoKeyPair from crypto.subtle.generateKey. ECDSA signatures are re-encoded from webcrypto's r||s into the der SEQUENCE that x.509 requires — getting that wrong is the usual reason a hand rolled csr is rejected.

for acme

const csr = await createCsr({ names: order.identifiers.map((i) => i.value), keys })
await fetch(order.finalize, { /* jws */ body: JSON.stringify({ csr: csr.base64url }) })

base64url is already unpadded and url safe, which is the exact shape rfc 8555 asks for.

names are checked before anything is signed

a csr with a bad name is rejected by the ca after a round trip, so it is cheaper to catch it here. these all throw CsrError with code BAD_NAME:

  • an empty name, or one over 253 characters
  • a label over 63 characters, or an empty label from a double dot
  • whitespace or a null byte
  • non ascii — punycode it first
  • a partial wildcard like part*.example.com
  • a wildcard that is not the leftmost label

*.example.com is fine.

correctness

21 tests, and the ones that count run the output through openssl:

openssl req -in out.csr -noout -verify
Certificate request self-signature verify OK

that is asserted for p-256, p-384 and rsa-2048, along with the subject, the signature algorithm, and every subjectAltName appearing in openssl req -text. one test flips a byte in the signature and asserts openssl then rejects it, so the check is proving something. the exported private key is read back by openssl pkey.

api

  • createCsr({ names, subject?, keys }){ der, pem, base64url }
  • generateKeys(algorithm?)CryptoKeyPair
  • privateKeyPem(keys) → pkcs#8 pem
  • toPem(bytes, label) / toBase64 / toBase64Url

subject fields: CN, C, ST, L, O, OU, E.

runtime

needs global crypto.subtle, so node 20+, or any of workers, deno and bun.

license

MIT