Security
Multi-layered security for DNS-based data distribution
Security Layers
ResolveDB implements defense-in-depth with four distinct security layers:
+--------------------------------------------------------------+
| Layer 4: Query Privacy (DoH/DoT) |
| -> Encrypts DNS queries in transit |
+--------------------------------------------------------------+
| Layer 3: Payload Encryption (AES-256-GCM, X25519) |
| -> Encrypts sensitive data within responses |
+--------------------------------------------------------------+
| Layer 2: Content Integrity (SHA-256, Ed25519) |
| -> Verifies data hasn't been tampered with |
+--------------------------------------------------------------+
| Layer 1: DNSSEC Foundation |
| -> Authenticates DNS infrastructure |
+--------------------------------------------------------------+Layer 1: DNSSEC Foundation
DNSSEC provides cryptographic authentication for DNS responses, preventing cache poisoning and man-in-the-middle attacks.
Features
- ECDSA P-256 KSK - Key Signing Key with wide resolver compatibility
- Ed25519 ZSK - Zone Signing Key with modern cryptography
- Automatic Zone Signing - All records signed with RRSIG at startup
- DS Record Support - Easy registrar integration
Implementation Status
DNSSEC signing is implemented and ready. To enable:
- DS record must be added at the domain registrar
- Enable via
DNSSEC_ENABLED=trueenvironment variable - Provide key paths for persistent keys
Verifying DNSSEC (when enabled)
# Check DNSSEC validation using dig
dig +dnssec resolvedb.net DNSKEY
# Look for DNSKEY and RRSIG records in response
# The "ad" flag indicates Authenticated Data
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0Layer 2: Content Integrity
Beyond DNSSEC, ResolveDB supports application-level integrity verification.
SHA-256 Hash Verification
# Response with integrity hash (first 16+ chars of SHA-256)
v=rdb1;s=ok;t=data;h=a7f3c2e8b9d1f456;d={"config":"value"}// Client verification
async function verifyIntegrity(response) {
const data = response.d;
const expectedHash = response.h; // SHA-256 hash (first 16+ chars)
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
const actualHash = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.substring(0, expectedHash.length); // Compare same length
return actualHash === expectedHash;
}Ed25519 Signatures
For higher assurance, responses can be signed with Ed25519.
# Signed response format
v=rdb1;s=ok;t=data;sig=<base64-signature>;pk=<base64-pubkey>;d=<data>Replay Protection
Authenticated requests require timestamp and nonce to prevent replay attacks:
get.auth-<jwt>.ts-1703865600.nonce-a1b2c3d4.resource.namespace.v1.resolvedb.net| Requirement | Value |
|---|---|
| Timestamp tolerance | 5 seconds |
| Nonce format | 8+ alphanumeric characters |
| Nonce uniqueness | Cryptographically random, unique per request |
Servers reject requests with:
- Timestamp more than 5 seconds from server time
- Duplicate (nonce, timestamp) pairs -> returns
E016(replay detected)
Layer 3: Encryption
For sensitive data, ResolveDB supports end-to-end payload encryption.
Symmetric Encryption (AES-256-GCM)
# Encrypted response format
v=rdb1;s=ok;t=data;e=encrypted;d=<base64-nonce+ciphertext>Structure:
- First 12 bytes: Random nonce (IV)
- Remaining bytes: AES-256-GCM ciphertext + auth tag
Asymmetric Key Exchange (X25519)
For establishing shared secrets with ephemeral keys (Perfect Forward Secrecy):
# Query for encrypted data
get.auth-<jwt>.secret.tenant.v1.resolvedb.net
# Response with server ephemeral public key + encrypted data
v=rdb1;s=ok;t=encrypted;e=aes256gcm;k=<server-ephemeral-pubkey>;d=<encrypted-payload>The k field contains the server's ephemeral X25519 public key (Base64, 32 bytes). Use HKDF-SHA256 to derive the AES-256-GCM key from the shared secret.
JWT Authentication
ResolveDB uses JWT tokens for authenticating requests to protected resources.
Query Format
get.auth-<jwt-token>.resource.namespace.v1.resolvedb.netJWT Token Structure
{
"header": {
"alg": "EdDSA",
"typ": "JWT"
},
"payload": {
"sub": "user-123",
"iss": "resolvedb.io",
"aud": "resolvedb.io",
"tenant": "acme-corp",
"exp": 1703869200,
"iat": 1703865600,
"nbf": 1703865600,
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"scopes": ["read", "write"]
}
}Required Claims: sub, iss, aud, exp, iat, nbf, jti, tenant, scopes
Algorithm Requirements
| Algorithm | Status | Notes |
|---|---|---|
EdDSA (Ed25519) | Required | Primary signing algorithm |
ES256 | Allowed | ECDSA P-256 (legacy) |
RS256 | Allowed | RSA 2048+ (legacy) |
none | Forbidden | No signature - always reject |
HS256/384/512 | Forbidden | Symmetric key confusion risk |
Tokens with forbidden algorithms return E008 (token invalid).
Token Hash References
JWT tokens often exceed the 63-character DNS label limit. Use hash references:
get.auth-h-a1b2c3d4e5f6g7h8i9j0k1l2.resource.namespace.v1.resolvedb.netRegister tokens via the API to receive hash references. See UQRP Protocol for details.
Token Validation
- Signature Verification - Ed25519 with constant-time comparison
- Expiration Check - Token
expvalidated against server time - Scope Validation - Operation checked against token scopes
- Tenant Isolation - Token
tenantmust match query namespace
Cache Exclusion
Authenticated queries are never cached:
fn is_authenticated_query(query: &DNSQuery) -> bool {
query.question.qname.contains(".auth-")
}
// In cache.put(): skip caching for auth queriesLayer 4: Query Privacy
For sensitive queries, use encrypted DNS transports.
Transport Requirements
| Query Type | Transport | Enforcement |
|---|---|---|
auth-* prefix | DoH/DoT required | Returns E014 if plaintext |
user.* namespace | DoH/DoT required | Returns E014 if plaintext |
public.* namespace | DoH/DoT recommended | Warning logged, query processed |
system.* namespace | Plaintext allowed | Health checks over UDP |
DNS-over-HTTPS (DoH)
Endpoint: https://doh.resolvedb.net/dns-query
curl -H "accept: application/dns-json" \
"https://doh.resolvedb.net/dns-query?name=get.weather.public.v1.resolvedb.net&type=TXT"DNS-over-TLS (DoT)
Endpoint: dot.resolvedb.net:853
kdig -d @dot.resolvedb.net +tls-ca +tls-hostname=dot.resolvedb.net \
get.weather.public.v1.resolvedb.net TXTBest Practices
- Always Enable DNSSEC Validation - Check for AD flag in responses
- Use Short-Lived JWT Tokens - 15-60 minute expiration
- Encrypt Sensitive Payloads - Use AES-256-GCM for defense-in-depth
- Use DoH/DoT for Authenticated Queries - Protect tokens in transit
- Verify Content Signatures - Don't rely solely on transport security
- Rotate Keys Regularly - JWT signing keys quarterly, encryption keys annually
Next Steps
- GeoIP Lookup - Predictable geographic IP lookups with explicit parameters
- Caching & TTL - Optimize cache performance