Skip to content

block/picocert

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 uint8_t hash[HASH_SHA256_DIGEST_SIZE],
                                                             const uint8_t signature[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[HASH_SHA256_DIGEST_SIZE],
                                           const uint8_t signature[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);

// 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
// ... fill certs, hash the data, and obtain signature ...
uint8_t data_hash[HASH_SHA256_DIGEST_SIZE] = { ... };
uint8_t signature[ECC_SIG_SIZE] = { ... };

picocert_err_t err = picocert_verify_hash_and_validate_chain(&ctx, certs, 2, 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_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" --validity_in_days 3650 --self_signed

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

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
picocert verify --cert certificate.pct --binary firmware.bin --signature firmware.sig

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/priv.der (Root CA, 10-year validity)
# - firmware-intermediate.pct/priv.der (Intermediate CA, 6-year validity)
# - firmware-leaf.pct/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
}

Certificate Chain Validation

// Validate a certificate chain
chain := []picocert.Certificate{leafCert, intermediateCert, rootCert}

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

// Verify data against the chain
err = picocert.VerifyAndValidateChain(chain, 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
)

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.

File Formats

  • Certificates: Binary format (.pct extension), 184 bytes each
  • Private Keys: PKCS#8 DER format (.priv.der extension)
  • Signatures: Raw binary, 64 bytes (r||s format)

Security Considerations

  • Private keys should be stored securely and never transmitted in plaintext
  • Consider using hardware security modules (HSMs) for root CA key storage
  • Implement proper key rotation and certificate renewal procedures
  • Validate certificate chains completely before trusting signatures
  • 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

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •