imprint

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2026 License: MIT Imports: 1 Imported by: 0

README

Imprint

Imprint is a Go library for device enrollment, certificate renewal, and mutual TLS (mTLS) authentication. It provides a complete device-to-service trust establishment protocol:

  1. Fingerprint - generate a unique, stable identity for a device from hardware attributes
  2. Enroll - prove device authenticity and receive a signed client certificate
  3. Renew - automatically renew certificates before or after expiry via a three-tier fallback
  4. Authenticate - use mTLS for all subsequent communication

Installation

go get github.com/dvstc/imprint

Packages

  • imprint (root) - shared types (EnrollmentRequest, EnrollmentResponse, Enrollment, RenewalRequest, ChallengeRenewalRequest)
  • imprint/fingerprint - hardware fingerprint generation with tiered fallback (hardware, persisted, generated)
  • imprint/client - client-side enrollment, certificate renewal, auto-renewal, certificate inspection, and mTLS configuration (enroll.go, renew.go, cert.go, autorenew.go, tls.go, store.go)
  • imprint/server - server-side enrollment handling, renewal handlers, internal CA, and mTLS verification middleware (handler.go, renew.go, verifier.go, ca.go, store.go)

How It Works

Enrollment
Device                                    Service
  |                                          |
  |-- Generate fingerprint (hardware hash)   |
  |-- Generate ECDSA P-256 keypair + CSR     |
  |                                          |
  |-- POST /enroll  ----------------------->|
  |   { build_secret, fingerprint, csr }     |-- Validate build secret
  |                                          |-- Sign CSR with internal CA
  |<-- { server_id, certificate, ca_cert } --|
  |                                          |
  |-- All future requests via mTLS -------->|
  |   (private key never leaves device)      |
Certificate Renewal (Three-Tier Fallback)
Tier Situation Endpoint Auth mechanism
1 Cert valid, nearing expiry POST /api/v1/renew (mTLS) TLS client cert
2 Cert expired (within 30 days) POST /api/v1/renew/challenge Signature proof + fingerprint
3 Key lost or device rebuilt POST /api/v1/enroll Build secret + fingerprint
Device                                    Service
  |                                          |
  |-- Check cert expiry                      |
  |                                          |
  |  [Tier 1: cert still valid]              |
  |-- POST /renew (mTLS) ----------------->|
  |   { csr }                                |-- Verify mTLS identity
  |                                          |-- Sign new CSR, update serial
  |<-- { server_id, certificate, ca_cert } --|
  |                                          |
  |  [Tier 2: cert expired, within window]   |
  |-- POST /renew/challenge (HTTPS) ------->|
  |   { server_id, fingerprint,              |-- Verify proof signature
  |     expired_cert, csr, proof }           |-- Verify fingerprint + serial
  |<-- { server_id, certificate, ca_cert } --|
  |                                          |
  |  [Tier 3: fallback to re-enrollment]     |
  |-- POST /enroll (HTTPS) ---------------->|
  |   (same as initial enrollment)           |

Usage

Client side (device)
import (
    "github.com/dvstc/imprint/client"
    "github.com/dvstc/imprint/fingerprint"
)

// Generate hardware fingerprint
fp, err := fingerprint.Generate(fingerprint.Options{
    PersistDir: "/data/imprint",
})

// Enroll with the service
resp, err := client.Enroll(ctx, client.EnrollConfig{
    ServiceURL:  "https://updates.example.com",
    BuildSecret: buildSecret,
    Fingerprint: fp.Fingerprint,
    Hostname:    hostname,
    OS:          runtime.GOOS,
    Arch:        runtime.GOARCH,
    StoreDir:    "/etc/myapp/imprint",
})

// Load mTLS config for subsequent requests
tlsCfg, err := client.LoadTLS("/etc/myapp/imprint")
httpClient := &http.Client{
    Transport: &http.Transport{TLSClientConfig: tlsCfg},
}
Certificate Renewal
// Check if renewal is needed
needs, err := client.NeedsRenewal("/etc/myapp/imprint", 30*24*time.Hour)

// Tier 1: mTLS renewal (cert still valid)
resp, err := client.Renew(ctx, client.RenewConfig{
    ServiceURL: "https://updates.example.com",
    StoreDir:   "/etc/myapp/imprint",
})

// Automatic three-tier renewal with fallback
action, err := client.RenewOrReenroll(ctx,
    client.RenewConfig{
        ServiceURL:      "https://updates.example.com",
        StoreDir:        "/etc/myapp/imprint",
        ChallengeWindow: 30 * 24 * time.Hour,
    },
    client.EnrollConfig{
        ServiceURL:  "https://updates.example.com",
        BuildSecret: buildSecret,
        Fingerprint: fp.Fingerprint,
        StoreDir:    "/etc/myapp/imprint",
    },
    30*24*time.Hour, // renewal threshold
)
// action: "renewed", "challenge_renewed", "reenrolled", or "none"
Background Auto-Renewal (Long-Running Services)
// Use ReloadableTLS for automatic cert pickup after renewal
tlsCfg, err := client.ReloadableTLS("/etc/myapp/imprint")
httpClient := &http.Client{
    Transport: &http.Transport{TLSClientConfig: tlsCfg},
}

// Start background auto-renewer
renewer := client.NewAutoRenewer(client.AutoRenewerConfig{
    RenewConfig:   client.RenewConfig{
        ServiceURL: "https://updates.example.com",
        StoreDir:   "/etc/myapp/imprint",
    },
    EnrollConfig:  enrollCfg,
    CheckInterval: 24 * time.Hour,
    Threshold:     30 * 24 * time.Hour,
    OnRenew:       func(action string) { log.Printf("renewed: %s", action) },
    OnError:       func(err error) { log.Printf("renewal error: %v", err) },
})

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go renewer.Start(ctx) // blocks until context is cancelled
Server side (service)
import "github.com/dvstc/imprint/server"

// Initialize CA
ca, err := server.NewCA(server.CAConfig{
    CertDir:      "./imprint-ca",
    Organization: "My Service",
})

// Public endpoints (no mTLS required)
mux.Handle("POST /api/v1/enroll", server.NewEnrollHandler(server.EnrollConfig{
    CA:           ca,
    Store:        myStore, // implements server.Store
    BuildSecrets: []string{secret},
    Mode:         imprint.ModeAuto,
}))
mux.Handle("POST /api/v1/renew/challenge", server.NewChallengeRenewHandler(server.ChallengeRenewConfig{
    CA:              ca,
    Store:           myStore,
    ChallengeWindow: 30 * 24 * time.Hour,
}))

// mTLS-protected endpoints
mux.Handle("POST /api/v1/renew", server.RequireMTLS(myStore, server.NewRenewHandler(server.RenewConfig{
    CA:    ca,
    Store: myStore,
})))
mux.Handle("GET /api/v1/data", server.RequireMTLS(myStore, dataHandler))

Cross-Language Compatibility

Imprint is protocol-first. The enrollment flow uses standard HTTP+JSON and X.509 certificates. Any language that can make HTTPS requests and handle PEM certificates can implement a compatible client. See DESIGN.md for the full protocol specification, fingerprint computation spec, and cross-language integration approaches.

License

MIT - see LICENSE.

Documentation

Index

Constants

View Source
const (
	StatusActive  = "active"
	StatusRevoked = "revoked"
	StatusPending = "pending"
)

Enrollment status constants.

Variables

This section is empty.

Functions

This section is empty.

Types

type ChallengeRenewalRequest

type ChallengeRenewalRequest struct {
	ServerID    string `json:"server_id"`
	Fingerprint string `json:"fingerprint"`
	ExpiredCert string `json:"expired_cert"` // PEM-encoded expired client certificate
	CSR         string `json:"csr"`          // PEM-encoded new CSR
	Proof       string `json:"proof"`        // base64-encoded signature over SHA256 digest
}

ChallengeRenewalRequest is sent by a device with an expired certificate to prove its identity via signature proof and obtain a fresh certificate (Tier 2 renewal). The proof is computed over SHA256(server_id + "\n" + fingerprint + "\n" + csr), signed with the old private key.

type EnrollMode

type EnrollMode int

EnrollMode controls how the server handles new enrollment requests.

const (
	ModeAuto     EnrollMode = iota // any valid build secret is immediately enrolled
	ModeToken                      // requires a pre-generated enrollment token (future)
	ModeApproval                   // enrollment is queued for admin approval (future)
)

type Enrollment

type Enrollment struct {
	ServerID     string    `json:"server_id"`
	Fingerprint  string    `json:"fingerprint"`
	Hostname     string    `json:"hostname"`
	OS           string    `json:"os"`
	Arch         string    `json:"arch"`
	SerialNumber string    `json:"serial_number"` // certificate serial (hex), for revocation
	EnrolledAt   time.Time `json:"enrolled_at"`
	RenewedAt    time.Time `json:"renewed_at,omitempty"` // zero value = never renewed
	LastSeenAt   time.Time `json:"last_seen_at"`
	LastIP       string    `json:"last_ip"`
	Status       string    `json:"status"` // "active", "revoked", "pending"
}

Enrollment represents a registered device in the enrollment store.

type EnrollmentRequest

type EnrollmentRequest struct {
	BuildSecret string `json:"build_secret"`
	Fingerprint string `json:"fingerprint"`
	Hostname    string `json:"hostname"`
	OS          string `json:"os"`
	Arch        string `json:"arch"`
	CSR         string `json:"csr"` // PEM-encoded PKCS#10 certificate signing request
}

EnrollmentRequest is sent by a device to enroll with a service. The build secret proves the software is genuine; the fingerprint uniquely identifies the machine; the CSR lets the service issue a client certificate without ever seeing the device's private key.

type EnrollmentResponse

type EnrollmentResponse struct {
	ServerID      string `json:"server_id"`
	Certificate   string `json:"certificate"`    // PEM-encoded signed client certificate
	CACertificate string `json:"ca_certificate"` // PEM-encoded CA certificate
}

EnrollmentResponse is returned by the service after a successful enrollment. The certificate is signed by the service's internal CA and can be used for mTLS on all subsequent requests.

type RenewalRequest

type RenewalRequest struct {
	CSR string `json:"csr"` // PEM-encoded PKCS#10 certificate signing request
}

RenewalRequest is sent by a device with a valid (not-yet-expired) certificate to obtain a fresh certificate via mTLS (Tier 1 renewal).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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