Documentation
¶
Overview ¶
Package cardhl is a high-level signer and decryptor for OpenPGP smartcards (YubiKey, Nitrokey, and other OpenPGP-applet cards) over PC/SC.
It wraps the low-level transport (cunicu.li/go-iso7816 + cunicu.li/go-openpgp-card) and the OpenPGP packet layer (github.com/ProtonMail/go-crypto) behind five operations — Sign, Decrypt, SignMIME, DecryptMIME, and ListKeys — with errors that tell a human what to do next ("is pcscd running?", "is the YubiKey plugged in?") instead of leaking raw APDU codes.
Sign produces a detached, ASCII-armored OpenPGP signature over arbitrary bytes (git commit signing, age-plugin-style tooling, etc.).
SignMIME and DecryptMIME handle RFC 3156 PGP/MIME directly: SignMIME takes a raw MIME message and returns a multipart/signed message; DecryptMIME takes a multipart/encrypted message and returns the plaintext, with all MIME parsing and armor handling done inside the library.
Quick start — raw signing ¶
card, err := cardhl.Open()
if err != nil {
log.Fatal(err) // friendly, actionable message
}
defer card.Close()
pub, err := cardhl.LoadPublicKey("key.asc") // signing subkey metadata
if err != nil {
log.Fatal(err)
}
sig, err := card.Sign(payload, pin, pub)
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(sig) // -----BEGIN PGP SIGNATURE-----
Quick start — PGP/MIME ¶
// Sign a raw MIME message and get back a multipart/signed message.
signed, err := card.SignMIME(rawMIMEMessage, pin, pub)
// Decrypt a multipart/encrypted message.
key, err := cardhl.LoadEntity("recipient.asc")
plain, err := card.DecryptMIME(encryptedMIMEMessage, pin, key)
Security model ¶
The private key never leaves the card; signing and decryption happen on the device. The PIN (PW1) is sent to the card to authorize each operation. This library does not cache PINs, touch the filesystem, or talk to gpg-agent.
Index ¶
- Variables
- func LoadEntity(path string) (*pgpcrypto.Entity, error)
- func LoadPublicKey(path string) (*packet.PublicKey, error)
- func ParseEntity(r io.Reader) (*pgpcrypto.Entity, error)
- func ParsePublicKey(r io.Reader) (*packet.PublicKey, error)
- type Card
- func (c *Card) Close() error
- func (c *Card) Decrypt(ciphertext []byte, pin string, key *pgpcrypto.Entity) ([]byte, error)
- func (c *Card) DecryptMIME(payload []byte, pin string, key *pgpcrypto.Entity) ([]byte, error)
- func (c *Card) Info() (*Info, error)
- func (c *Card) ListKeys() []KeyInfo
- func (c *Card) OpenPGP() *openpgp.Card
- func (c *Card) Sign(data []byte, pin string, pub *packet.PublicKey) ([]byte, error)
- func (c *Card) SignMIME(payload []byte, pin string, pub *packet.PublicKey) ([]byte, error)
- type Info
- type KeyInfo
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoPCSC is returned when the PC/SC daemon cannot be reached. On Linux // this almost always means pcscd is not running. ErrNoPCSC = errors.New("cannot connect to PC/SC daemon") // ErrNoCard is returned when no smartcard exposing the OpenPGP applet is // present on any reader. ErrNoCard = errors.New("no OpenPGP smartcard found") // ErrCardInit is returned when a card is present but the OpenPGP applet // could not be initialized. ErrCardInit = errors.New("failed to initialize OpenPGP card") // ErrPIN is returned when PIN (PW1) verification fails. ErrPIN = errors.New("PIN verification failed") // ErrNoKey is returned when the requested key slot (sign/decrypt) holds no // key, or no public-key material could be read for it. ErrNoKey = errors.New("no key in slot") // ErrUnsupportedKey is returned when the key's algorithm is not supported // for the requested operation. ErrUnsupportedKey = errors.New("unsupported key algorithm") // ErrSign is returned when the card refuses or fails a signing operation. ErrSign = errors.New("signing failed") // ErrDecrypt is returned when a message cannot be decrypted with the card's // decryption key. ErrDecrypt = errors.New("decryption failed") // ErrBadKeyFile is returned when a public key file cannot be parsed. ErrBadKeyFile = errors.New("could not parse public key") // ErrMIME is returned when a PGP/MIME message is malformed or cannot be // parsed for a card operation (e.g. missing boundary, wrong media type). ErrMIME = errors.New("invalid PGP/MIME structure") )
Sentinel errors returned by this package. Use errors.Is to match them; the concrete error returned usually wraps one of these with extra context from the card or PC/SC layer.
Functions ¶
func LoadEntity ¶
LoadEntity reads an exported OpenPGP public key from path and returns the first entity. Both ASCII-armored and binary keyrings are accepted.
func LoadPublicKey ¶
LoadPublicKey reads an exported OpenPGP public key from path and returns the signing-capable key (a signing subkey if present, otherwise the primary key). Both ASCII-armored and binary keyrings are accepted.
func ParseEntity ¶
ParseEntity reads an exported OpenPGP public key from r and returns the first entity. Both ASCII-armored and binary keyrings are accepted.
Types ¶
type Card ¶
type Card struct {
// contains filtered or unexported fields
}
Card is a connected OpenPGP smartcard session. It is not safe for concurrent use; serialize calls or open one Card per goroutine. Always Close it.
func Open ¶
Open connects to the first available OpenPGP smartcard via PC/SC.
Errors are actionable: a missing daemon yields ErrNoPCSC, an absent card yields ErrNoCard, and an applet that will not initialize yields ErrCardInit.
func (*Card) Decrypt ¶
Decrypt decrypts an OpenPGP message with the card's decryption key and returns the plaintext.
pin authorizes the DECIPHER operation (PW1 in mode 0x82). key is the message recipient's public key — its encryption subkey identifies which session key the card must unwrap; load it with LoadEntity or ParseEntity.
Only RSA decryption keys are supported. The session-key unwrap for RSA runs through the card's crypto.Decrypter; the symmetric layer is handled in process. ECDH/Curve25519 decryption requires scalar access the card does not expose — use gpg-agent for those keys.
func (*Card) DecryptMIME ¶ added in v0.1.0
DecryptMIME decrypts a RFC 3156 multipart/encrypted MIME message using the card's on-device decryption key. The private key never leaves the hardware.
payload must be a complete multipart/encrypted MIME message. key is the account's public key entity — its encryption subkey is used to locate the correct session-key packet in the ciphertext; load it with LoadEntity or ParseEntity.
Only RSA decryption keys are supported. ECDH/Curve25519 keys require scalar access the card does not expose — use gpg-agent for those.
func (*Card) OpenPGP ¶
OpenPGP exposes the underlying go-openpgp-card handle for advanced use (cardholder data, key generation, PIN management). Most callers do not need it; the high-level Sign, Decrypt, and Info cover the common path.
func (*Card) Sign ¶
Sign returns a detached, ASCII-armored OpenPGP signature over data, produced by the card's signing key.
pin authorizes the signing operation (PW1). pub supplies the metadata that goes into the signature packet — key ID, fingerprint, and public-key algorithm — and must correspond to the key on the card; use LoadPublicKey or ParsePublicKey to obtain it from an exported public key.
The signature covers data verbatim as a binary document (signature type 0x00). Callers building higher-level envelopes (multipart/signed, the git signature format) hash the bytes they want covered and pass them here.
EdDSA, RSA, and ECDSA signing keys are supported.
func (*Card) SignMIME ¶ added in v0.1.0
SignMIME creates a RFC 3156 multipart/signed MIME message from payload using the card's signing key.
payload is a raw MIME message (headers + CRLF + body). The result is a new message with the same transport headers and a multipart/signed body containing the original body as the first part and the detached PGP signature as the second.
pub must be the signing-capable public key corresponding to the card's sign slot — obtained via LoadPublicKey or ParsePublicKey.