Skip to content

picocert: Minimal Certificate Library

picocert is a minimal certificate library for handling compact X.509-like certificates, designed for embedded systems. It provides certificate chain validation and data signature verification using ECDSA (P-256) and SHA-256.

The library consists of:

  • C Library: Core certificate validation and signature verification (header-only picocert.h)
  • Go Package: Certificate management tools and CLI (pkg/picocert, cmd/picocert)
  • CLI Tool: Command-line interface for certificate operations
  • PKI Scripts: Helper scripts for setting up certificate hierarchies

Certificate Structure in Memory

Certificates are represented by the picocert_t struct (see picocert.h):

typedef struct {
  uint8_t version;
  char issuer[PICOCERT_MAX_NAME_LEN];
  char subject[PICOCERT_MAX_NAME_LEN];
  uint64_t valid_from;  // Any monotonic timestamp, e.g. a unix timestamp or counter
  uint64_t valid_to;    // Ditto
  picocert_curve_t curve; // Only PICOCERT_P256 supported
  picocert_hash_t hash;   // Only PICOCERT_SHA256 supported
  uint32_t reserved;      // Must be zero
  uint8_t public_key[PICOCERT_MAX_PUBKEY_LEN]; // Uncompressed ECC public key
  uint8_t signature[PICOCERT_MAX_SIG_LEN];     // ECDSA signature
} PACKED picocert_t;

Field layout:

Field Size (bytes) Description
version 1 Certificate version
issuer 32 Issuer name (null-terminated)
subject 32 Subject name (null-terminated)
valid_from 8 Start of validity
valid_to 8 End of validity
curve 1 Curve ID (0 = P-256)
hash 1 Hash ID (0 = SHA-256)
reserved 4 Reserved, must be zero
public_key 65 Uncompressed ECC public key (SEC1)
signature 64 ECDSA signature raw (r||s)

Total size: 184 bytes (packed)

C Library

The C library is header-only and uses a context for dependency injection of function pointers.

API

The main API functions are:

// Initialize context with crypto functions
static picocert_err_t picocert_init_context(picocert_context_t* ctx,
                                            picocert_hash_fn_t hash_fn,
                                            picocert_ecc_verify_fn_t ecc_verify_fn,
                                            picocert_time_fn_t time_fn);

// Verify a hash with certificate chain validation
static picocert_err_t picocert_verify_hash_and_validate_chain(picocert_context_t* ctx,
                                                             const picocert_t* cert_chain,
                                                             const uint32_t chain_len,
                                                             const picocert_t* trust_anchor,
                                                             const uint8_t hash[PICOCERT_HASH_SHA256_DIGEST_SIZE],
                                                             const uint8_t signature[PICOCERT_ECC_SIG_SIZE]);

// Verify hash with a single certificate (no chain validation)
static picocert_err_t picocert_verify_hash(picocert_context_t* ctx,
                                           const picocert_t* cert,
                                           const uint8_t hash[PICOCERT_HASH_SHA256_DIGEST_SIZE],
                                           const uint8_t signature[PICOCERT_ECC_SIG_SIZE]);

// Validate certificate chain (without data verification)
static picocert_err_t picocert_validate_cert_chain(picocert_context_t* ctx,
                                                   const picocert_t* cert_chain,
                                                   const uint32_t chain_len,
                                                   const picocert_t* trust_anchor);

// Validate a single certificate against its issuer
static picocert_err_t picocert_validate_cert(picocert_context_t* ctx,
                                             const picocert_t* issuer,
                                             const picocert_t* subject);

Example

#include "picocert.h"

// Example hash function (you must provide your own)
bool my_sha256_hash(const uint8_t* data, uint32_t data_len,
                    uint8_t* digest, uint32_t digest_len) {
    return true;
}

// Example ECC verification function (you must provide your own)
bool my_ecc_verify(const uint8_t* key, size_t key_size,
                   const uint8_t* hash, uint32_t hash_len,
                   const uint8_t* signature) {
    return true;
}

// Time callback for certificate validity checking
uint64_t get_current_time(void) {
    return (uint64_t)time(NULL);
}

picocert_context_t ctx;
picocert_init_context(&ctx, my_sha256_hash, my_ecc_verify, get_current_time);

picocert_t certs[2]; // [0] = leaf, [1] = root supplied with the payload
picocert_t trusted_root; // Load this independently from trusted storage.
// ... fill certs, load trusted_root, hash the data, and obtain signature ...
uint8_t data_hash[PICOCERT_HASH_SHA256_DIGEST_SIZE] = { ... };
uint8_t signature[PICOCERT_ECC_SIG_SIZE] = { ... };

picocert_err_t err = picocert_verify_hash_and_validate_chain(
    &ctx, certs, 2, &trusted_root, data_hash, signature);
if (err == PICOCERT_OK) {
    // Data is valid and cert chain is trusted
} else {
    // Handle error
}

Error Codes

Error Code Value Meaning
PICOCERT_OK 0 Success
PICOCERT_ERR_INVALID 1 Invalid argument
PICOCERT_ERR_EXPIRED 2 Certificate expired/not yet valid
PICOCERT_ERR_SIGNATURE 3 Signature verification failed
PICOCERT_ERR_ISSUER 4 Issuer/subject mismatch
PICOCERT_ERR_VERSION 5 Version mismatch
PICOCERT_ERR_RESERVED 6 Reserved field nonzero
PICOCERT_ERR_NOT_SELF_SIGNED 7 Root not self-signed
PICOCERT_ERR_INVALID_FORMAT 8 Invalid certificate format
PICOCERT_ERR_CONTEXT_NOT_INITIALIZED 9 Context not properly initialized
PICOCERT_ERR_HASH_FAILED 10 Hash computation failed
PICOCERT_ERR_UNSUPPORTED_CURVE 11 Unsupported curve
PICOCERT_ERR_UNSUPPORTED_HASH 12 Unsupported hash algorithm
PICOCERT_ERR_INVALID_VALIDITY_PERIOD 13 Invalid validity period
PICOCERT_ERR_CHAIN_TOO_LONG 14 Certificate chain too long
PICOCERT_ERR_UNTRUSTED_ROOT 15 Chain does not end at trusted root
PICOCERT_ERR_UNKNOWN 255 Unknown error

CLI Tool

The picocert command-line tool provides certificate management functionality:

Installation

cd cmd/picocert
go build -o picocert

Commands

Issue Certificates

# Create a self-signed root certificate
picocert issue --subject "MyRoot" --output my-root \
  --validity_in_days 3650 --self_signed

# Create a certificate signed by an issuer
picocert issue --subject "MyLeaf" --output my-leaf --validity_in_days 365 \
  --issuer my-root.pct --issuer_key my-root.priv.der

--subject is certificate metadata. --output is the filesystem path prefix; the command creates <output>.pct and <output>.priv.der without replacing existing files.

Sign Binary Files

# Sign a binary file
picocert sign --key private.priv.der --binary firmware.bin --output firmware.sig

# Sign and print signature to stdout
picocert sign --key private.priv.der --binary firmware.bin

Verify Signatures

# Verify a signed binary and its certificate chain against a pinned root
picocert verify --cert firmware-leaf.pct \
  --intermediate firmware-intermediate.pct \
  --root /trusted/firmware-root.pct \
  --binary firmware.bin --signature firmware.sig

Pass --intermediate more than once for deeper chains, ordered from the leaf's issuer toward the root. The --root file must come from trusted storage rather than from the same untrusted source as the binary and certificate chain. The CLI checks every certificate's validity period as part of verification.

Global Flags

  • --quiet, -q: Suppress output messages

Setting Up a Three-Tier PKI

Use the included script to set up a PKI hierarchy:

# Set up a three-tier PKI for firmware signing
./three-tier-pki.sh ./cmd/picocert/picocert firmware

# This creates:
# - firmware-root.pct and firmware-root.priv.der (Root CA, 10-year validity)
# - firmware-intermediate.pct and firmware-intermediate.priv.der (Intermediate CA, 6-year validity)
# - firmware-leaf.pct and firmware-leaf.priv.der (Leaf certificate, 4-year validity)

⚠️ Warning: The script uses long validity periods. Consider your own needs for production use.

Go Package

The Go package github.com/block/picocert/pkg/picocert provides high-level certificate operations.

Go Installation

go get github.com/block/picocert/pkg/picocert

Usage

Certificate Issuance

package main

import (
    "time"
    "github.com/block/picocert/pkg/picocert"
)

func main() {
    // Issue a self-signed certificate
    validFrom := uint64(time.Now().Unix())
    validTo := validFrom + 365*24*60*60 // 1 year

    cert, err := picocert.Issue(nil, "MyDevice", validFrom, validTo)
    if err != nil {
        panic(err)
    }

    // cert.Cert contains the certificate
    // cert.PrivateKey contains the PKCS#8 encoded private key
}

Certificate Signing

// Issue a certificate signed by an issuer
issuerCert := &picocert.CertificateWithKey{
    Cert:       issuerCertificate,
    PrivateKey: issuerPrivateKeyBytes,
}

signedCert, err := picocert.Issue(issuerCert, "SubjectName", validFrom, validTo)
if err != nil {
    panic(err)
}

Data Signing and Verification

// Sign data
privateKey, err := picocert.ParsePrivateKey(privateKeyBytes)
if err != nil {
    panic(err)
}

signature, err := picocert.Sign(privateKey, data)
if err != nil {
    panic(err)
}

// Verify signature
err = picocert.Verify(&certificate, data, signature)
if err != nil {
    // Verification failed
}

Verify only checks that the supplied certificate's public key produced the signature. It does not authenticate that certificate or check its validity period. Use VerifyAndValidateChain with an independently provisioned root for authentication.

Certificate Chain Validation

// Validate a certificate chain against a root loaded from trusted storage.
chain := []picocert.Certificate{leafCert, intermediateCert, rootCert}
trustedRoot := loadPinnedRoot()

err := picocert.ValidateCertChain(chain, &trustedRoot)
if err != nil {
    // Chain validation failed
}

// Verify data against the chain
err = picocert.VerifyAndValidateChain(chain, &trustedRoot, data, signature)
if err != nil {
    // Verification or validation failed
}

Parsing Certificates and Keys

// Parse certificate from bytes
cert, err := picocert.ParseCertificate(certBytes)
if err != nil {
    panic(err)
}

// Parse private key from PKCS#8 format
privateKey, err := picocert.ParsePrivateKey(keyBytes)
if err != nil {
    panic(err)
}

// Convert certificate to bytes
certBytes := cert.ToBytes()

Go Package Types

type Certificate struct {
    Version   uint8
    Issuer    [32]byte
    Subject   [32]byte
    ValidFrom uint64
    ValidTo   uint64
    Curve     Curve
    Hash      Hash
    Reserved  uint32
    PubKey    [65]byte
    Signature [64]byte
}

type CertificateWithKey struct {
    Cert       Certificate
    PrivateKey []byte  // PKCS#8 encoded
}

// Constants
const (
    P256 Curve = 0       // ECDSA P-256
    Sha256 Hash = 0      // SHA-256
    MaxChainLen = 10
)

Notes

  • Only ECDSA P-256 and SHA-256 are supported.
  • All fields are packed; no padding.
  • Names are fixed-length, null-terminated strings.
  • Public key is uncompressed (0x04 | X | Y, 65 bytes).
  • Signature is raw ECDSA (r||s, 64 bytes).
  • Timestamps are Unix epoch seconds.
  • Certificate data is stored in little-endian format for embedded compatibility.
  • The C and Go validators both accept at most 10 certificates in a chain.

File Formats

  • Certificates: Binary format (.pct extension), 184 bytes each
  • Private Keys: Unencrypted PKCS#8 DER format (.priv.der extension), created with owner-only permissions (0600)
  • Signatures: Raw binary, 64 bytes (r||s format)

Security Considerations

  • Private keys are not encrypted. Store them securely and never transmit them in plaintext.
  • Consider using hardware security modules (HSMs) for root CA key storage
  • Implement proper key rotation and certificate renewal procedures
  • Provision trust anchors independently from untrusted certificate chains, and pass the expected root to every chain-validation call
  • Use appropriate validity periods for your security requirements
  • Provide secure implementations of the required cryptographic functions
  • The library enforces a maximum chain length to prevent DoS

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages