Skip to content

Read a certificate

Recover the parameters a message's key was derived from, with the certificate's subkey binding verified rather than assumed.

recipient, err := certificate.Parse(der)
if err != nil {
    return err
}

recipient carries the encryption subkey's fingerprint, its key id, and the KDFParams that SessionKey needs. Everything except CoordinateBytes, which is a property of the curve as your key service reports it rather than something the certificate states:

params := recipient.KDF
params.CoordinateBytes = deriver.CoordinateBytes()

Both encodings

Parse takes binary packets. A certificate published on a web page is armoured, so dearmour it first — armor.Decode from go-crypto, or whatever your application already uses. The core does not carry an armour codec for one caller.

Why it verifies

Anyone can append a public subkey packet to a certificate they do not own. Nothing about the format prevents it; what prevents it being believed is the binding signature the primary makes over the subkey.

A parser that skips that check will happily hand back an attacker's key, and a sender directed at it encrypts to the attacker while believing it is talking to the certificate's owner. So Parse verifies the binding against the primary before returning anything, and refuses a subkey that has none.

This matters most when the certificate did not come from you — fetched from WKD, pasted into a ticket, downloaded from a page over a connection you did not pin.

What it does not check

  • Identity self-certifications. This reads a certificate to find out how to decrypt; the user ID plays no part in the derivation. If you need to know that a name belongs to a key, that is a web-of-trust question and this is not the tool for it.
  • Expiry and revocation. Neither is represented here. A certificate this package reads is one you are decrypting against, and a revoked key you still hold still opens old messages.
  • Anything but ECDH subkeys. They are the only ones this module can derive against, so others are ignored and a certificate with none is refused.

Errors

Sentinel Means
encryption.ErrMalformed Not a certificate, truncated, more than one certificate, or no ECDH subkey
encryption.ErrUnsupported A version or algorithm this package does not implement — a v6 key, or a non-RSA primary
encryption.ErrIntegrity A binding signature is present and does not verify

Walking the packets yourself

If you need something Parse does not return, encryption.ParsePacket reads one packet and hands back the rest, so a sequence is an ordinary loop:

for rest := der; len(rest) > 0; {
    pkt, err := encryption.ParsePacket(rest)
    if err != nil {
        return err
    }

    // pkt.Tag, pkt.Body …

    rest = pkt.Rest
}

Both header forms are handled — the RFC 9580 form a modern implementation writes, and the legacy CTB that older senders still emit. Partial and indeterminate lengths are refused rather than guessed at.