certkit

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 30 Imported by: 0

README

go-certkit 🔐

Parse, inspect and convert X.509 certificate/key containers — PEM, DER, PKCS#12, PKCS#7 and JKS/JCEKS — through one normalized Bundle type.

📦 Install

go get github.com/Bugs5382/go-certkit

🚀 Usage

Parse accepts any supported container format and normalizes it to a Bundle: a leaf certificate, an optional private key, an optional intermediate/root chain, and derived metadata (subject, issuer, SANs, validity window, fingerprint, key algorithm/size).

data, err := os.ReadFile("site.p12")
if err != nil {
    log.Fatal(err)
}

bundle, err := certkit.Parse(data, "changeit")
if err != nil {
    log.Fatal(err)
}

fmt.Println(bundle.Meta.Subject, bundle.Meta.NotAfter)

Export reassembles a Bundle into any supported format, optionally re-encrypting it under a new passphrase:

pfx, err := certkit.Export(bundle, certkit.PKCS12, "new-passphrase")
if err != nil {
    log.Fatal(err)
}
🧩 Formats
Format Contains
PKCS12 leaf + key + chain, encrypted
PEMBundle leaf + key + chain, PEM
PEMCertOnly leaf only, PEM
PEMKeyOnly key only, PEM
PEMFullchain leaf + chain, PEM (no key)
DER leaf only, raw ASN.1
PKCS7 leaf + chain, no key (.p7b/.p7c)
JKS Java KeyStore (JKS or JCEKS)

DetectFormat returns a best-effort guess of a blob's format; Parse dispatches on that hint and falls back to trying every parser if the hint is ambiguous or wrong.

🗂️ Multi-entry containers

A JKS/JCEKS keystore or a PKCS#7 bag can hold more than one distinct entry. When that happens, Parse returns *certkit.ErrMultipleEntries, carrying each entry's alias/subject:

bundle, err := certkit.Parse(jksData, "changeit")
var multi *certkit.ErrMultipleEntries
if errors.As(err, &multi) {
    // present multi.Aliases to the caller, then:
    bundle, err = certkit.ParseEntry(jksData, "changeit", multi.Aliases[0])
}
⚠️ Errors
  • ErrWrongPassphrase — the supplied passphrase failed to decrypt the key, PKCS#12 archive or JKS/JCEKS keystore.
  • ErrUnrecognizedFormat — the input didn't match any supported format.
  • ErrNoPrivateKey — a key-bearing export (PKCS12, JKS, PEMBundle, PEMKeyOnly) needs a private key, but the Bundle has none.
  • ErrMultipleEntries{Aliases []string} — see above.
🔭 Observability

Parse, Export and ParseEntry each have a context-aware variant that takes optional logging and tracing. With no options they emit nothing and behave exactly like the plain functions.

logger := golog.NewLogger("my-service") // github.com/Bugs5382/go-log

bundle, err := certkit.ParseContext(ctx, data, "changeit",
    certkit.WithLogger(logger), // structured logs via go-log
    certkit.WithTracing(),      // one span per call via the global tracer
)
  • WithLogger(l) takes a go-log neutral Logger, logging success at debug level and failure at error level with the typed error.
  • WithTracing() starts one span (certkit.Parse, certkit.Export or certkit.ParseEntry) from the global OpenTelemetry TracerProvider, sets an error status and records the error on failure, and always ends the span. It is a no-op until the application installs a provider.

Only non-sensitive attributes are recorded: the format, byte sizes, chain length, whether a private key is present, and public certificate metadata (subject, serial, notAfter). The passphrase, private key bytes and raw input bytes are never logged or attached to a span.

📄 License

MIT — see LICENSE.

Documentation

Overview

Package certkit parses and assembles X.509 certificate material across the common container formats (PEM, DER, PKCS#12, PKCS#7, JKS/JCEKS).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrWrongPassphrase is returned when the supplied passphrase fails to
	// decrypt an encrypted private key, PKCS#12 archive or JKS/JCEKS
	// keystore.
	ErrWrongPassphrase = errors.New("certkit: wrong passphrase")

	// ErrUnrecognizedFormat is returned when the input does not match any
	// supported container format.
	ErrUnrecognizedFormat = errors.New("certkit: unrecognized format")

	// ErrNoPrivateKey is returned when a caller requests a key-bearing
	// export (e.g. PKCS#12, JKS) from a Bundle that has no private key.
	ErrNoPrivateKey = errors.New("certkit: no private key")
)

Sentinel errors returned by Parse, ParseEntry and Export.

Functions

func Export

func Export(b Bundle, f Format, newPassphrase string) ([]byte, error)

Export assembles a Bundle into the given container Format, returning the encoded bytes. newPassphrase protects the output for formats that support encryption: PKCS#12 and JKS always, and the private key of PEMBundle and PEMKeyOnly (emitted as an "ENCRYPTED PRIVATE KEY" block when a passphrase is supplied, plaintext PKCS#8 otherwise). It is ignored by the cert-only PEM/DER/PKCS#7 formats, which carry no key.

Exporting a key-bearing format (PKCS12, JKS, PEMBundle, PEMKeyOnly) from a Bundle with no private key returns ErrNoPrivateKey.

Export is the observability-free wrapper over ExportContext.

func ExportContext

func ExportContext(ctx context.Context, b Bundle, f Format, newPassphrase string, opts ...Option) ([]byte, error)

ExportContext is Export with optional, opt-in observability. With no opts it behaves exactly like Export and emits nothing. WithLogger and WithTracing enable structured logging and an OpenTelemetry span ("certkit.Export"); neither ever records newPassphrase, key material, or certificate bytes.

Types

type Bundle

type Bundle struct {
	LeafPEM  []byte
	KeyPEM   []byte
	ChainPEM [][]byte
	Meta     Meta
}

Bundle is the normalized, in-memory representation of a parsed certificate/key container: a leaf certificate, its optional private key, an optional chain of intermediate/root certificates, and derived metadata. LeafPEM, KeyPEM and each entry of ChainPEM are PEM-encoded blocks.

func GenerateSelfSigned added in v1.1.0

func GenerateSelfSigned(opts GenOpts) (Bundle, error)

GenerateSelfSigned creates a fresh RSA keypair and a self-signed leaf certificate for it, returning both as a Bundle. The returned Bundle has no ChainPEM, since a self-signed leaf is its own trust anchor.

The certificate is suited to a TLS/signing service leaf: it carries DigitalSignature and KeyEncipherment key usage, ExtKeyUsageServerAuth and ExtKeyUsageClientAuth extended usages, and is not a CA.

func Parse

func Parse(data []byte, passphrase string) (Bundle, error)

Parse decodes data (in any supported container format) into a Bundle. passphrase is used to decrypt an encrypted private key, PKCS#12 archive or JKS/JCEKS keystore; pass "" when the input is not encrypted.

If the container holds more than one distinct entry (e.g. a multi-alias JKS/JCEKS keystore, or a PKCS#7 bag with multiple leaf-like certificates) Parse returns *ErrMultipleEntries carrying the entries' aliases/subjects; use ParseEntry (JKS/JCEKS) to select one.

Parse is the observability-free wrapper over ParseContext; see it for the optional logging/tracing variant.

func ParseContext

func ParseContext(ctx context.Context, data []byte, passphrase string, opts ...Option) (Bundle, error)

ParseContext is Parse with optional, opt-in observability. With no opts it behaves exactly like Parse and emits nothing. WithLogger and WithTracing enable structured logging and an OpenTelemetry span ("certkit.Parse") respectively; neither ever records the passphrase, key material, or raw input bytes.

func ParseEntry

func ParseEntry(data []byte, passphrase, alias string) (Bundle, error)

ParseEntry decodes the named alias of a JKS/JCEKS keystore into a Bundle. Use it after Parse reports *ErrMultipleEntries for a multi-alias keystore. It is the observability-free wrapper over ParseEntryContext.

func ParseEntryContext

func ParseEntryContext(ctx context.Context, data []byte, passphrase, alias string, opts ...Option) (Bundle, error)

ParseEntryContext is ParseEntry with optional, opt-in observability (span "certkit.ParseEntry"). See ParseContext for the option semantics; the alias name, passphrase, and key material are never logged or placed on the span.

func Rotate added in v1.1.0

func Rotate(current Bundle, opts GenOpts) (Bundle, error)

Rotate generates a fresh keypair and self-signed certificate, returning it as a brand-new Bundle for graceful-overlap rotation: callers keep serving current while next is distributed, then cut over. Rotate never mutates current and never reuses its private key.

If opts is the zero value, CommonName and DNSNames are derived from current's metadata so a caller can rotate without restating them.

type ErrMultipleEntries

type ErrMultipleEntries struct {
	Aliases []string
}

ErrMultipleEntries is returned by Parse when a container holds more than one distinct end-entity entry (e.g. a multi-alias JKS/JCEKS keystore or a PKCS#7 bag with more than one leaf-like certificate) and the caller must pick one explicitly -- via ParseEntry for JKS/JCEKS.

func (*ErrMultipleEntries) Error

func (e *ErrMultipleEntries) Error() string

Error implements the error interface.

type Format

type Format int

Format identifies a certificate/key container format.

const (
	PKCS12 Format = iota
	PEMBundle
	PEMCertOnly
	PEMKeyOnly
	PEMFullchain
	DER
	PKCS7
	JKS
)

Supported container formats.

const Unknown Format = Format(-1)

Unknown is returned by DetectFormat when the input cannot be classified.

func DetectFormat

func DetectFormat(data []byte) Format

DetectFormat returns a best-effort guess of the container format of data. It returns Unknown if no format could be identified. Parse does not rely on this being authoritative -- it dispatches on the hint but falls back to trying other parsers.

func (Format) String

func (f Format) String() string

String returns the canonical short name of a Format (e.g. "pem", "pkcs12"), or "unknown" for Unknown. The names match the format strings used across the certkit surface.

type GenOpts added in v1.1.0

type GenOpts struct {
	// CommonName is the certificate Subject's common name. Required.
	CommonName string
	// DNSNames populates the certificate's Subject Alternative Names.
	DNSNames []string
	// TTL is the certificate lifetime, measured from the time of
	// generation. A zero value defaults to 365 days.
	TTL time.Duration
	// KeyBits is the RSA modulus size in bits. A zero value defaults to
	// 2048.
	KeyBits int
}

GenOpts configures GenerateSelfSigned and Rotate.

type Meta

type Meta struct {
	Subject           string
	Issuer            string
	SANs              []string // DNS names + IP addresses
	NotBefore         time.Time
	NotAfter          time.Time
	SerialNumber      string
	FingerprintSHA256 string
	KeyAlgorithm      string
	KeyBits           int
	IsCA              bool
}

Meta holds certificate metadata derived from a Bundle's leaf certificate.

type Option

type Option func(*obsConfig)

Option configures the optional logging/tracing of a context-aware call.

func WithLogger

func WithLogger(l golog.Logger) Option

WithLogger injects a go-log neutral Logger. Success is logged at debug level and failure at error level (with the typed error); both carry only non-sensitive attributes.

func WithTracing

func WithTracing() Option

WithTracing enables a single OpenTelemetry span around the operation, started from the global TracerProvider. It is a no-op unless the application has installed a provider.

Directories

Path Synopsis
Command example demonstrates parsing a PEM certificate bundle into a certkit.Bundle, printing its metadata, and converting it to another container format.
Command example demonstrates parsing a PEM certificate bundle into a certkit.Bundle, printing its metadata, and converting it to another container format.

Jump to

Keyboard shortcuts

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