Help us shape the future of Go! Take the 2026 Go Developer Survey

krl

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2026 License: BSD-3-Clause Imports: 11 Imported by: 0

README

krl

Go Reference License CI

OpenSSH key revocation lists (KRLs) in Go: parse, build and check the binary format of OpenSSH's PROTOCOL.krl, the one sshd loads through RevokedKeys and ssh-keygen -k writes. golang.org/x/crypto/ssh has no support for them. Pure Go, CGO_ENABLED=0.

// A server refusing revoked certificates.
k, err := krl.Parse(data)
if err != nil { ... }
if k.IsRevoked(cert) { /* refuse */ }

// An issuer publishing what it revoked.
b := krl.NewBuilder(version, "revoked certificates")
b.RevokeSerial(caKey, 42)
b.RevokeSerialRange(caKey, 100, 199)
b.RevokeKeyID(nil, "alice") // nil: under any CA
b.RevokeKey(compromisedKey)
data, err := b.Marshal(time.Now())

IsRevoked follows ssh_krl_check_key in OpenSSH's krl.c: a certificate is revoked when its serial or key ID is revoked under the CA that signed it or under any CA, when its CA key is revoked, or when its own key is revoked by blob or by SHA-1 / SHA-256 fingerprint.

Judged by ssh-keygen

The tests do not let the package agree with itself. On the linux and macOS CI lanes, ssh-keygen is the oracle (a missing one fails those lanes):

test what ssh-keygen judges
TestOracleSshKeygenKRL a KRL written by ssh-keygen -k with serials (list, range, bitmap, up to 2^64-1), key IDs, three CAs plus "any CA" (-s none), explicit keys and a revoked CA key: IsRevoked answers like ssh-keygen -Q for 250 certificates (made by ssh-keygen -s) and keys. Builder given the same revocations writes the same bytes.
TestOracleFingerprints sha1:, sha256: and hash: revocations, for keys and their certificates
TestOracleRandomSerials 40 random serial sets written by both: byte-identical, same ssh-keygen -Q -l listing, and the same answers for ~9000 certificates on both sides of every edge
TestOracleMutations every truncation and ~1400 single-byte corruptions of a KRL: Parse refuses exactly what ssh-keygen refuses, and answers like it for the rest
TestOracleSigned a KRL with a signature section: read, signature skipped
TestOracleBitmapOverflow see below

Windows runs the pure-Go tests only, which alone cover 100% of the code. FuzzParse (seeded with KRLs written by ssh-keygen, in testdata/) and FuzzBuilder run on CI too.

One deliberate difference

For many sparse serials close together (1, 3, 5, … 40001), ssh-keygen -k writes a bitmap wider than its own reader accepts and then refuses its own file: Invalid KRL file: bignum is too large (OpenSSH 10.3p1). Parse refuses that file too, as sshd would. Builder starts a new section before a bitmap gets that wide, so what it writes is always readable.

No signatures

OpenSSH never exposed a way to write the KRL signature section and no longer verifies it. Parse reads a KRL that has one, skips it as krl.c does and sets Signed, which says nothing about authenticity. Builder never writes one. A KRL's integrity comes from its transport (HTTPS from an authenticated server) or from a detached SSHSIG signature (ssh-keygen -Y sign), which this package does not handle.

License

BSD-3-Clause.

Documentation

Overview

Package krl reads, writes and checks OpenSSH key revocation lists (KRLs), the binary format of OpenSSH's PROTOCOL.krl that sshd loads through RevokedKeys and that ssh-keygen -k writes and ssh-keygen -Q queries. golang.org/x/crypto/ssh has no support for them.

A KRL revokes certificates by serial number (as lists, ranges or bitmaps) or by key ID, each under one CA key or under any CA, and revokes plain keys by their blob or by the SHA-1 or SHA-256 hash of their blob.

k, err := krl.Parse(data)
if err != nil { ... }
if k.IsRevoked(cert) { refuse the login }

b := krl.NewBuilder(version, "revoked by the bridge")
b.RevokeSerial(caKey, 42)
data, err := b.Marshal(time.Now())

Judged by ssh-keygen

Parse, IsRevoked and Builder follow krl.c, OpenSSH's own implementation, and the tests hold them to it: KRLs written by ssh-keygen -k are parsed here and every certificate and key gets the same answer from IsRevoked as from ssh-keygen -Q; KRLs written by Builder are accepted by ssh-keygen -Q, which gives the same answers again and dumps the same revocations as for the KRL it wrote itself.

Builder chooses between list, range and bitmap encodings with krl.c's cost model, so its output is usually byte for byte what ssh-keygen writes for the same revocations. It departs from it on purpose in one place: ssh-keygen lets a bitmap grow past the largest integer its own reader accepts (2048 bytes), and then cannot read the KRL it wrote ("bignum is too large"), which is what happens to many sparse serials close together. Builder starts a new section instead.

Integrity: no signatures

The format once had a signature section. OpenSSH never exposed a way to write it, has stopped verifying it, and recommends a detached SSHSIG signature (ssh-keygen -Y sign) instead. Parse still reads a KRL that has one, for old files, skips it as krl.c does and sets KRL.Signed; the signature is NOT verified, so Signed says nothing about authenticity. Builder never writes one. A KRL's integrity must come from how it is transported, such as HTTPS from a server the reader authenticates, or from a detached SSHSIG signature checked by the caller; this package does neither.

Differences from OpenSSH

Keys are parsed with golang.org/x/crypto/ssh, not OpenSSH's sshkey, so a CA key in a certificate section is refused or accepted by that parser. The two agree on every key type OpenSSH supports, with small exceptions: x/crypto still parses ssh-dss keys, which OpenSSH 10 no longer does, and does not refuse RSA keys shorter than 1024 bits.

Like OpenSSH, Parse allocates in proportion to what the KRL revokes, so a KRL should come from a source trusted not to send an enormous one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder accumulates revocations and encodes them as a KRL that ssh-keygen and sshd accept. It never writes a signature section. The Revoke methods record the first invalid request and Marshal returns it. A Builder is not safe for concurrent use.

func NewBuilder

func NewBuilder(version uint64, comment string) *Builder

NewBuilder returns a Builder for a KRL with this krl_version and comment.

func (*Builder) Marshal

func (b *Builder) Marshal(now time.Time) ([]byte, error)

Marshal encodes the KRL with now as its generation date. The output depends only on the revocations and on the order in which CAs were first named. Serials are encoded as lists, ranges and bitmaps chosen by the cost model of krl.c, except that a bitmap is never let grow past what OpenSSH can read back (ssh-keygen itself does not stop there).

func (*Builder) RevokeKey

func (b *Builder) RevokeKey(key ssh.PublicKey)

RevokeKey revokes a key the way ssh-keygen -k does for a public key line of its specification (ssh_krl_revoke_key): a plain key is listed as an explicit key, which also revokes every certificate made for it and, when it is a CA key, every certificate it signed; a certificate is revoked by its serial under the CA that signed it, or by its key ID when its serial is 0.

func (*Builder) RevokeKeyID

func (b *Builder) RevokeKeyID(ca ssh.PublicKey, id string)

RevokeKeyID revokes the certificates with this key ID signed by ca, or by any CA when ca is nil. An ID cannot contain a NUL.

func (*Builder) RevokeSerial

func (b *Builder) RevokeSerial(ca ssh.PublicKey, serial uint64)

RevokeSerial revokes the certificate with this serial signed by ca, or by any CA when ca is nil. Serial 0 cannot be revoked: it is what a CA writes when it names no serial, and OpenSSH refuses it.

func (*Builder) RevokeSerialRange

func (b *Builder) RevokeSerialRange(ca ssh.PublicKey, lo, hi uint64)

RevokeSerialRange revokes the certificates with serials lo through hi, both included, signed by ca, or by any CA when ca is nil. lo must not be 0 nor greater than hi.

type KRL

type KRL struct {
	// Version is krl_version, a number that grows each time the list
	// changes.
	Version uint64
	// GeneratedDate is when the list was generated, in UTC.
	GeneratedDate time.Time
	// Flags is the header's flags word. PROTOCOL.krl defines none.
	Flags uint64
	// Comment is the free-form comment of the header.
	Comment string
	// Signed reports that the list carried a signature section. The
	// signature is skipped, never verified: see the package documentation.
	Signed bool
	// contains filtered or unexported fields
}

KRL is a parsed key revocation list. The zero value revokes nothing. A KRL is not modified by IsRevoked and may be shared between goroutines.

func Parse

func Parse(b []byte) (*KRL, error)

Parse decodes a KRL in the format of OpenSSH's PROTOCOL.krl, refusing what ssh-keygen and sshd refuse: a bad magic or format version, truncated or trailing data, an unknown section or certificate subsection, an unknown extension marked critical, a CA key OpenSSH cannot parse, serial 0, an inverted serial range, a bitmap larger than OpenSSH reads or one that wraps past 2^64-1. Unknown extensions not marked critical are ignored. A signature section is skipped and recorded in Signed.

func (*KRL) IsRevoked

func (k *KRL) IsRevoked(key ssh.PublicKey) bool

IsRevoked reports whether key is revoked by k, with the semantics of ssh_krl_check_key in OpenSSH's krl.c. A plain key is revoked when its SHA-1 or SHA-256 fingerprint or its blob is listed. A certificate (*ssh.Certificate) is revoked when its public key is revoked that way, when its key ID or its serial is revoked under the CA that signed it or under any CA, or when that CA key is itself revoked. Serial 0, what a CA writes when it names none, is never revoked by serial.

Key IDs are compared as C strings, as OpenSSH does: an ID stops at its first NUL. OpenSSH refuses a certificate whose ID has an embedded NUL; golang.org/x/crypto/ssh does not, so such a certificate is judged by the part before the NUL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL