Quick Start
Generate RSA signing key pair
Keys are written to build/package/etc/ssl-pinning/tls/ as prv.pem and pub.pem.
task generate-keys
Configure domains
Edit build/package/etc/ssl-pinning/config.yaml and list the FQDNs to track:
keys:
- fqdn: google.com
- fqdn: mail.google.com
file: google.com.json # group into one response
domainName: mail.google.com
server:
listen: 0.0.0.0:7500
storage:
type: memory # fs | memory | redis | postgres
Run with Docker Compose
Starts the server together with Valkey/Redis and PostgreSQL. RSA keys are auto-generated on first run if missing.
cd docker
docker compose up
Query a key registry file
curl http://localhost:7500/api/v1/google.com.json
How It Works
One goroutine-worker per configured FQDN polls the live TLS certificate continuously.
| # | Step |
|---|---|
1 | On startup, one goroutine-worker is launched per configured fqdn |
2 | Each worker dials {fqdn}:443 every second, extracts the leaf certificate, computes SHA-256 of the public key (SPKI) |
3 | Every dump_interval (default 5 s), all current keys are signed and flushed to the configured storage backend |
4 | HTTP handler for GET /api/v1/{file} reads keys from storage, signs the payload, and returns JSON |
5 | On SIGTERM/SIGINT — graceful shutdown of the HTTP server and all workers |
API
| Method & Path | Description |
|---|---|
GET /api/v1/{file} | Signed JSON with current certificate pins. {file} defaults to {fqdn}.json. |
GET /health/liveness | Liveness probe — returns 200 when the process is running |
GET /health/readiness | Readiness probe — returns 200 when the service is ready to serve traffic |
GET /health/startup | Startup probe — returns 200 after initial key collection completes |
GET /metrics | Prometheus metrics exposed on port 9090 |
Response Format
Each GET /api/v1/{file} response contains a signed payload with one or more pinned keys.
{
"payload": {
"keys": [
{
"domainName": "www.google.com",
"key": "b8tZqtqfv0RVKfwivfzaXuFWFHkYP4ufBhb5esciCwo=",
"fqdn": "google.com",
"expire": 4736419,
"date": "2026-03-24T23:39:22.964574+01:00"
}
]
},
"signature": "BASE64_RSA_SHA512_SIGNATURE"
}
| Field | Description |
|---|---|
domainName | Hostname pattern for SPKI pinning. Defaults to *.{fqdn}. Supports wildcards. |
key | Base64-encoded SHA-256 hash of the certificate's Subject Public Key Info (SPKI). |
fqdn | Fully-qualified domain name the server dialed to extract the key. |
expire | Seconds until certificate expiry at the time of collection. |
date | Timestamp when the key was collected. |
signature | RSA PKCS#1 v1.5 + SHA-512 over the JCS-canonicalized JSON of the payload field. |
Configuration
Config is loaded from config.yaml, environment variables (prefix SSL_PINNING_), and CLI flags — in increasing priority order.
Keys
| Key | Required | Description |
|---|---|---|
fqdn | Yes | Domain to dial at port 443 for TLS certificate extraction |
domainName | No | Pin pattern returned to clients. Defaults to *.{fqdn} |
file | No | Response filename. Defaults to {fqdn}.json. Shared by multiple FQDNs to group them |
Server
| Key | Default | Description |
|---|---|---|
server.listen | 127.0.0.1:7500 | HTTP listen address and port |
server.read_timeout | 5s | Maximum duration for reading a request |
server.write_timeout | 5s | Maximum duration before timing out a response write |
TLS
| Key | Default | Description |
|---|---|---|
tls.dir | {config-path}/tls | Directory containing prv.pem and pub.pem |
tls.dump_interval | 5s | Interval for flushing signed keys to storage |
tls.timeout | 5s | Timeout for TLS dial operations |
Environment variables
SSL_PINNING_SERVER_LISTEN=0.0.0.0:7500
SSL_PINNING_STORAGE_TYPE=postgres
SSL_PINNING_STORAGE_DSN="postgres://user:pass@localhost:5432/db?sslmode=disable"
SSL_PINNING_TLS_DIR=/opt/ssl-pinning/tls
SSL_PINNING_TLS_DUMP_INTERVAL=30s
SSL_PINNING_LOG_LEVEL=debug
Storage Backends
| Type | DSN | Notes |
|---|---|---|
memory | — | Ephemeral in-process store. Best for development and testing. |
fs | — | Writes signed JSON files to dump_dir. Survives restarts. |
redis | redis://:password@host:6379/0 | Shared store for multiple replicas. Key format: file:fqdn:appID. |
postgres | postgres://user:pass@host/db | Durable relational storage. Migrations run automatically on startup. |
Cryptography
| Property | Value |
|---|---|
Signing algorithm | RSA PKCS#1 v1.5 |
Hash function | SHA-512 (signature) · SHA-256 (SPKI pin) |
Canonicalization | JSON Canonicalization Scheme (JCS, RFC 8785) |
Key size | RSA-4096 |
Private key format | PKCS#8 PEM (tls/prv.pem) |
Public key format | PEM (tls/pub.pem) — distribute to clients for signature verification |
Pin type | SPKI SHA-256 (leaf certificate only) |
Prometheus Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
ssl_pinning_errors | Gauge | file | Number of current errors per response file |
ssl_pinning_expire | Gauge | key, fqdn | Seconds until certificate expiry per tracked key |