Rolling main documentation
Built from commit f80b604a992ba1144e6c45229a8165c81e52dae0. Content may describe unreleased behavior.
Request Signing
Learn how to implement cryptographic request signing using Ed25519 keys for secure communication with Trusted Server.
Overview
Trusted Server provides Ed25519-based request signing capabilities to ensure authenticity and integrity of HTTP requests. This feature uses:
- Ed25519 signatures - Modern, fast, and secure elliptic curve cryptography
- JWKS (JSON Web Key Set) - Standard format for public key distribution
- Fastly storage - Private keys in Secret Store, public keys in Config Store
- Automatic key rotation - Date-based key identifiers and rotation support
Architecture
Key Storage
Keys are stored in two separate Fastly stores:
Secret Store (signing_keys):
- Private signing keys (Ed25519 private keys)
- Base64-encoded 32-byte keys
- Only accessible to the edge application
Config Store (jwks_store):
- Public verification keys (JWK format)
- Current key identifier (
current-kid) - Active key identifiers (
active-kids) - Public JWKS documents
Key Components
┌─────────────────────┐
│ RequestSigner │ ← Signs outgoing requests
│ - Loads private key│
│ - Signs payload │
└─────────────────────┘
┌─────────────────────┐
│ verify_signature() │ ← Verifies incoming requests
│ - Loads public key │
│ - Validates sig │
└─────────────────────┘
┌─────────────────────┐
│ JWKS Endpoint │ ← Publishes public keys
│ - Discovery doc │
│ - Active keys only │
└─────────────────────┘Signing Requests
Basic Usage
use trusted_server_core::request_signing::RequestSigner;
// Initialize signer (loads current key from config)
let signer = RequestSigner::from_config()?;
// Sign a payload
let payload = "request data to sign";
let signature = signer.sign(payload)?;
// Signature is base64url-encoded
println!("Signature: {}", signature);
println!("Key ID: {}", signer.kid);Signing HTTP Requests
Include the signature and key ID in request headers:
// Sign the request body
let body = serde_json::to_string(&request_data)?;
let signature = signer.sign(body.as_bytes())?;
// Add headers
request.set_header("X-Signature", signature);
request.set_header("X-Signature-Kid", signer.kid);Recommended Headers
X-Signature- Base64url-encoded signatureX-Signature-Kid- Key identifier used for signingX-Signature-Timestamp- Unix timestamp (recommended for replay protection)
Verifying Signatures
Basic Verification
use trusted_server_core::request_signing::verify_signature;
// Extract signature and kid from request
let signature = request.get_header("X-Signature")?;
let kid = request.get_header("X-Signature-Kid")?;
// Verify the signature
let payload = request.get_body_bytes();
let is_valid = verify_signature(payload, signature, kid)?;
if !is_valid {
return Err("Invalid signature");
}Verification Endpoint
Trusted Server provides a built-in endpoint for testing signatures:
Endpoint: POST /verify-signature
Request:
{
"payload": "message to verify",
"signature": "base64url-encoded-signature",
"kid": "ts-2024-01-15"
}Response:
{
"verified": true,
"kid": "ts-2024-01-15",
"message": "Signature verified successfully",
"error": null
}Discovery Endpoint
Public keys are published via a standardized discovery endpoint following IAB patterns.
Accessing the Discovery Document
Endpoint: GET /.well-known/trusted-server.json
Response:
{
"version": "1.0",
"jwks": {
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url-encoded-public-key",
"kid": "ts-2024-01-15",
"alg": "EdDSA"
}
]
}
}Using Discovery for Verification
Partners can fetch and cache your public keys:
// Fetch discovery document
const discovery = await fetch(
'https://your-domain/.well-known/trusted-server.json'
).then((r) => r.json())
// Extract JWKS
const jwks = discovery.jwks
// Verify signatures using JWKS
const publicKey = jwks.keys.find((k) => k.kid === signatureKid)Key Format
Ed25519 Keys
Private Key:
- 32 bytes
- Stored base64-encoded in Secret Store
- Never exposed via API
Public Key:
- 32 bytes
- Stored as JWK in Config Store
- Published in discovery endpoint (
/.well-known/trusted-server.json)
JWK Format
{
"kty": "OKP", // Key type: Octet Key Pair
"crv": "Ed25519", // Curve: Ed25519
"x": "public_key_b64", // Public key (base64url)
"kid": "ts-2024-01-15", // Key identifier
"alg": "EdDSA" // Algorithm: EdDSA
}Configuration
Configure request signing in trusted-server.toml:
[request_signing]
config_store_id = "jwks_store"
secret_store_id = "signing_keys"Fastly Config Store Setup
Create the Config Store in Fastly:
# Create config store
fastly config-store create --name=jwks_store
# Create secret store
fastly secret-store create --name=signing_keysLink stores to your service in fastly.toml:
[local_server.config_stores]
[local_server.config_stores.jwks_store]
file = "test-data/jwks_store.json"
[local_server.secret_stores]
[local_server.secret_stores.signing_keys]
file = "test-data/signing_keys.json"Security Best Practices
1. Protect Private Keys
- Store private keys only in Fastly Secret Store
- Never log or expose private keys
- Rotate keys regularly (see Key Rotation)
2. Validate Signatures
Always verify:
- Signature authenticity
- Key ID exists in active keys
- Timestamp freshness (if using timestamps)
3. Key Lifecycle
- Generate new keys periodically
- Keep previous key active during transition
- Deactivate old keys after grace period
- Delete deprecated keys
4. Transport Security
- Always use HTTPS
- Consider additional authentication (API keys, mutual TLS)
- Implement rate limiting on verification endpoints
Error Handling
Common errors and solutions:
Invalid key length:
Error: Invalid key length (expected 32 bytes for Ed25519)- Ensure key is exactly 32 bytes
- Check base64 encoding is correct
Missing key:
Error: Key not found: ts-2024-01-15- Verify kid exists in Config Store
- Check active-kids list includes the kid
Signature verification failed:
verified: false- Ensure payload matches exactly (no modifications)
- Verify correct kid is used
- Check signature encoding (base64url vs standard)
Testing
Unit Tests
Test signing and verification:
#[test]
fn test_sign_and_verify() {
let payload = b"test message";
let signer = RequestSigner::from_config().unwrap();
let signature = signer.sign(payload).unwrap();
// Verify the signature
let verified = verify_signature(payload, &signature, &signer.kid).unwrap();
assert!(verified);
}Integration Testing
Use the verification endpoint:
# Sign a payload
SIGNATURE=$(sign-payload "test message")
# Verify via API
curl -X POST https://your-domain/verify-signature \
-H "Content-Type: application/json" \
-d '{
"payload": "test message",
"signature": "'$SIGNATURE'",
"kid": "ts-2024-01-15"
}'Next Steps
- Learn about Key Rotation for managing key lifecycle
- Review Architecture for system design
- Configure Testing for your deployment