← Back to Research

Current Infrastructure Leverage

Existing Components We Can Use

  1. LDAP (ldap/) - Store user public keys alongside user data
  2. Postfix Relays (postfix-smtp/) - Implement verification policy service
  3. Flask API (app/) - Key management endpoints
  4. PostgreSQL - Revocation list, audit logs
  5. DNS Management - Publish user keys in TXT records

Integration Steps

1. LDAP Schema Extension

Location: ldap/bootstrap/01-users.ldif

Add attributes:

# Add to existing schema
dn: cn=authroute,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: authroute
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.1
  NAME 'authRoutePrimaryKey'
  DESC 'User primary public key for authenticated routing'
  EQUALITY caseIgnoreMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.2
  NAME 'authRouteSubKey'
  DESC 'User subkey for authenticated routing'
  EQUALITY caseIgnoreMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.3
  NAME 'authRouteKeyExpiry'
  DESC 'Subkey expiration timestamp'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE )

Migration Script: scripts/add_authroute_ldap_schema.sh

2. Database Schema

Migration: migrations/add_authenticated_routing.sql

-- Store user key metadata
CREATE TABLE authroute_keys (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    key_type VARCHAR(20) NOT NULL, -- 'primary' or 'subkey'
    key_id VARCHAR(64) UNIQUE NOT NULL,
    public_key TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    expires_at TIMESTAMP,
    revoked_at TIMESTAMP,
    revoked_reason TEXT
);

CREATE INDEX idx_authroute_keys_user ON authroute_keys(user_id);
CREATE INDEX idx_authroute_keys_active ON authroute_keys(key_id)
    WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW());

-- Audit trail for key operations
CREATE TABLE authroute_key_events (
    id SERIAL PRIMARY KEY,
    key_id VARCHAR(64) REFERENCES authroute_keys(key_id),
    event_type VARCHAR(50) NOT NULL, -- 'created', 'used', 'rotated', 'revoked'
    event_data JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Verification results for compliance
CREATE TABLE authroute_verifications (
    id SERIAL PRIMARY KEY,
    message_id VARCHAR(255),
    sender_email VARCHAR(255),
    key_id VARCHAR(64),
    verification_result VARCHAR(20), -- 'pass', 'fail', 'none'
    failure_reason TEXT,
    verified_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_authroute_verif_msg ON authroute_verifications(message_id);
CREATE INDEX idx_authroute_verif_time ON authroute_verifications(verified_at);

3. Flask API Extensions

New Module: app/authroute_api.py

from flask import Blueprint, request, jsonify
from app.models import db, AuthRouteKey
from app.api_auth import require_api_key
import nacl.signing
import nacl.encoding

authroute_bp = Blueprint('authroute', __name__)

@authroute_bp.route('/api/v1/authroute/keys', methods=['GET'])
@require_api_key
def get_user_keys():
    """Get user's current keys"""
    user = request.current_user
    keys = AuthRouteKey.query.filter_by(
        user_id=user.id,
        revoked_at=None
    ).all()
    return jsonify([k.to_dict() for k in keys])

@authroute_bp.route('/api/v1/authroute/keys/generate', methods=['POST'])
@require_api_key
def generate_subkey():
    """Generate new subkey (client provides public key)"""
    user = request.current_user
    data = request.json

    # Validate signature from primary key
    if not verify_primary_signature(user, data):
        return jsonify({'error': 'Invalid primary key signature'}), 403

    # Store subkey
    subkey = AuthRouteKey(
        user_id=user.id,
        key_type='subkey',
        key_id=data['key_id'],
        public_key=data['public_key'],
        expires_at=data['expires_at']
    )
    db.session.add(subkey)
    db.session.commit()

    # Publish to DNS
    publish_key_to_dns(user.email, subkey)

    return jsonify(subkey.to_dict()), 201

@authroute_bp.route('/api/v1/authroute/keys/<key_id>/revoke', methods=['POST'])
@require_api_key
def revoke_key(key_id):
    """Revoke a key"""
    user = request.current_user
    key = AuthRouteKey.query.filter_by(
        key_id=key_id,
        user_id=user.id
    ).first_or_404()

    key.revoked_at = datetime.now()
    key.revoked_reason = request.json.get('reason', 'User requested')
    db.session.commit()

    # Update revocation list
    update_revocation_list()

    return jsonify({'status': 'revoked'})

@authroute_bp.route('/.well-known/authroute-revocations', methods=['GET'])
def get_revocations():
    """Public endpoint for revocation list"""
    revoked = AuthRouteKey.query.filter(
        AuthRouteKey.revoked_at.isnot(None)
    ).all()
    return jsonify({
        'version': '1.0',
        'updated_at': datetime.now().isoformat(),
        'revocations': [
            {
                'key_id': k.key_id,
                'revoked_at': k.revoked_at.isoformat(),
                'reason': k.revoked_reason
            }
            for k in revoked
        ]
    })

Register in: app/__init__.py

from app.authroute_api import authroute_bp
app.register_blueprint(authroute_bp)

4. Postfix Integration

New File: postfix-smtp/authroute-policy.py

#!/usr/bin/env python3
"""
Postfix policy service for authenticated routing verification
"""
import sys
import socket
import json
import requests
from nacl.signing import VerifyKey
from nacl.encoding import Base64Encoder

def verify_auth_proof(sender, auth_proof):
    """Verify AUTH-PROOF from MAIL FROM"""
    try:
        # Parse proof
        proof = json.loads(base64.b64decode(auth_proof))

        # Fetch public key from DNS or API
        public_key = fetch_public_key(sender, proof['key_id'])
        if not public_key:
            return 'DUNNO', 'Key not found'

        # Check revocation
        if is_revoked(proof['key_id']):
            return 'REJECT', 'Key revoked'

        # Verify signature
        verify_key = VerifyKey(public_key, encoder=Base64Encoder)
        message = f"{proof['timestamp']}:{proof['message_id']}:{sender}"

        try:
            verify_key.verify(message.encode(),
                            base64.b64decode(proof['signature']))
        except:
            return 'REJECT', 'Invalid signature'

        # Check timestamp (5 min window)
        if abs(time.time() - proof['timestamp']) > 300:
            return 'REJECT', 'Timestamp out of range'

        return 'PREPEND', f'Authenticated-Routing: pass key-id={proof["key_id"]}'

    except Exception as e:
        return 'DUNNO', f'Verification error: {str(e)}'

# Socket server for Postfix policy protocol
def policy_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 10040))
    sock.listen(5)

    while True:
        conn, addr = sock.accept()
        handle_policy_request(conn)

if __name__ == '__main__':
    policy_server()

Postfix Config: postfix-smtp/relay-a/main.cf

smtpd_recipient_restrictions =
    ...
    check_policy_service inet:127.0.0.1:10040
    ...

5. Docker Compose Integration

Update: docker-compose.yml

services:
  authroute-policy:
    build: ./postfix-smtp/authroute-policy
    container_name: authroute-policy
    networks:
      - msgs-network
    environment:
      - API_URL=http://flask-app:5000
      - DNS_SERVER=1.1.1.1
    depends_on:
      - flask-app
      - postgres

  # Update postfix services
  postfix-relay-a:
    depends_on:
      - authroute-policy

6. DNS Management

Script: scripts/publish_authroute_dns.sh

#!/bin/bash
# Publish user public keys to DNS

USER_EMAIL=$1
KEY_ID=$2
PUBLIC_KEY=$3

# Extract user and domain
USER=$(echo $USER_EMAIL | cut -d@ -f1)
DOMAIN=$(echo $USER_EMAIL | cut -d@ -f2)

# Create DNS record
cat > /tmp/authroute-${KEY_ID}.dns <<EOF
_authroute.${USER}._domainkey.${DOMAIN}. 300 IN TXT (
  "v=AUTHROUTE1; "
  "k=ed25519; "
  "p=${PUBLIC_KEY}; "
  "t=s; "
)
EOF

# Apply via your DNS provider API
# This depends on your DNS setup (Cloudflare, Route53, etc.)

Testing Plan

Phase 1: Unit Tests

# Test key generation
python -m pytest tests/test_authroute_keys.py

# Test verification logic
python -m pytest tests/test_authroute_verify.py

Phase 2: Integration Tests

# Test full flow: generate key -> sign -> verify
./test_authroute_flow.sh user@msgs.global

# Test revocation
./test_authroute_revocation.sh

Phase 3: Load Testing

# Verify performance impact
./load_test_authroute.sh 1000  # 1000 msg/sec

Rollout Strategy

Week 1-2: Infrastructure

  • [ ] Add LDAP schema
  • [ ] Create database tables
  • [ ] Deploy revocation list endpoint

Week 3-4: API & Policy Service

  • [ ] Implement Flask API endpoints
  • [ ] Build Postfix policy service
  • [ ] DNS automation scripts

Week 5-6: Testing

  • [ ] Internal testing with test accounts
  • [ ] Performance validation
  • [ ] Security audit

Week 7-8: Gradual Rollout

  • [ ] Beta users (opt-in)
  • [ ] Monitor verification metrics
  • [ ] Collect feedback

Week 9+: Full Deployment

  • [ ] Enable for all users
  • [ ] Document in API docs
  • [ ] Publish specification

Success Metrics

  1. Adoption: % of users with keys generated
  2. Verification Rate: % of inbound mail with valid AUTH-PROOF
  3. Performance: Verification latency <100ms p99
  4. Security: Zero key compromises, revocation response <5min
  5. Compatibility: Zero delivery failures due to auth-routing

Documentation Updates

API Documentation

File: docs/API_AUTHENTICATION.md - Add authroute endpoints - Document key management flow - Provide client examples

User Documentation

New File: docs/AUTHENTICATED_ROUTING.md - What is authenticated routing? - How to generate keys - Integration with email clients - Troubleshooting guide

Admin Documentation

New File: docs/AUTHROUTE_ADMIN.md - Policy configuration - DNS setup - Monitoring and alerts - Emergency revocation procedures