-
Notifications
You must be signed in to change notification settings - Fork 8
Enhanced request signing with domain verification (v1.1) #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Implements cryptographic signing of OpenRTB requests that includes publisher domain verification and replay protection. The signed payload now includes: - Key ID (kid) - Request host - Request scheme - Request ID - Unix timestamp This prevents request tampering and domain spoofing by ensuring the signature is bound to the originating publisher domain. Changes: - Add version and ts fields to TrustedServerExt - Add SigningParams struct and sign_request() method - Update PrebidAuctionProvider to use enhanced signing - Add comprehensive tests for payload construction and signing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty good, a couple nit picks.
Opening an issue on mocktioneer to verify new signature payload: stackpop/mocktioneer#30
deployed test site with this (verification broken until mocktioneer updated) but the TS side is working.
| request_scheme, | ||
| timestamp: std::time::SystemTime::now() | ||
| .duration_since(std::time::UNIX_EPOCH) | ||
| .map(|d| d.as_secs()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use milliseconds to stay in line with the rest of the openrtb spec.
| let signature = signer.sign(id.as_bytes())?; | ||
| let params = SigningParams::new( | ||
| id.to_string(), | ||
| request_host.clone(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to clone here if you use params.request_host and params.request_scheme on lines 544-545
Enhanced Request Signing (v1.1) - Architecture DiagramRequest Signing FlowsequenceDiagram
participant Client as Publisher<br/>Website
participant TS as Trusted Server
participant Signer as Request<br/>Signer
participant SSP as Prebid SSP/<br/>Ad Exchange
Client->>TS: Ad Request
Note over TS: Extract request metadata
TS->>TS: Parse request_host<br/>(e.g., "publisher.com")
TS->>TS: Parse request_scheme<br/>(e.g., "https")
TS->>TS: Generate request_id<br/>(e.g., "auction-123")
TS->>Signer: Create SigningParams
Note over Signer: SigningParams {<br/> request_id: "auction-123"<br/> request_host: "publisher.com"<br/> request_scheme: "https"<br/> timestamp: 1738527600<br/>}
Signer->>Signer: Build canonical payload
Note over Signer: Format:<br/>"kid:host:scheme:id:ts"<br/><br/>Example:<br/>"ts-2026-01-A:publisher.com:https:auction-123:1738527600"
Signer->>Signer: Sign with Ed25519 private key
Signer->>Signer: Base64url encode signature
Signer-->>TS: Return signature
TS->>TS: Build OpenRTB request with ext.trusted_server
Note over TS: {<br/> "version": "1.1",<br/> "kid": "ts-2026-01-A",<br/> "request_host": "publisher.com",<br/> "request_scheme": "https",<br/> "ts": 1738527600,<br/> "signature": "base64..."<br/>}
TS->>SSP: POST /openrtb2/auction
SSP->>SSP: Verify signature
Note over SSP: 1. Reconstruct payload<br/>2. Fetch public key by kid<br/>3. Verify Ed25519 signature<br/>4. Check timestamp freshness<br/>5. Validate domain binding
alt Valid Signature
SSP-->>TS: 200 OK with bids
TS-->>Client: Return ad
else Invalid Signature
SSP-->>TS: 403 Forbidden
Note over SSP: Reject if:<br/>• Signature invalid<br/>• Timestamp too old<br/>• Domain mismatch<br/>• Missing fields
TS-->>Client: Error response
end
Security Propertiesflowchart TB
subgraph Input["Request Inputs"]
A1[Request ID]
A2[Publisher Host]
A3[Request Scheme]
A4[Timestamp]
A5[Key ID]
end
subgraph Signing["Canonical Payload Construction"]
B1["Build payload string:<br/>kid:host:scheme:id:ts"]
end
subgraph Crypto["Cryptographic Signing"]
C1[Ed25519 Private Key]
C2[Sign payload]
C3[Base64url encode]
end
subgraph Output["Signed Request Extension"]
D1[version: '1.1']
D2[kid]
D3[request_host]
D4[request_scheme]
D5[ts]
D6[signature]
end
subgraph Protection["Security Guarantees"]
E1[Domain Binding]
E2[Replay Protection]
E3[Tampering Detection]
E4[Request Authenticity]
end
Input --> Signing
Signing --> Crypto
C1 --> C2
C2 --> C3
Crypto --> Output
Output --> Protection
Attack Preventionflowchart LR
subgraph Threats["Attack Vectors"]
T1[Domain Spoofing]
T2[Request Replay]
T3[Payload Tampering]
T4[MITM Attacks]
end
subgraph v10["v1.0 Signing<br/>(Only ID)"]
V1[❌ Vulnerable to<br/>domain spoofing]
V2[❌ No replay<br/>protection]
V3[✅ Tampering<br/>detection]
end
subgraph v11["v1.1 Enhanced Signing<br/>(ID + Host + Scheme + Timestamp)"]
N1[✅ Domain binding<br/>prevents spoofing]
N2[✅ Timestamp prevents<br/>replay attacks]
N3[✅ Enhanced tampering<br/>detection]
N4[✅ Scheme validation<br/>prevents downgrade]
end
T1 --> V1
T1 --> N1
T2 --> V2
T2 --> N2
T3 --> V3
T3 --> N3
T4 --> N4
Payload Format EvolutionVersion 1.0 (Legacy)Version 1.1 (Enhanced)Verification Process (SSP/Exchange Side)flowchart TD
Start([Receive OpenRTB Request]) --> Extract[Extract ext.trusted_server]
Extract --> CheckVersion{version == '1.1'?}
CheckVersion -->|No| Legacy[Use legacy verification]
CheckVersion -->|Yes| CheckFields{All fields present?}
CheckFields -->|Missing| Reject1[❌ Reject: Missing fields]
CheckFields -->|Present| BuildPayload[Reconstruct payload:<br/>kid:host:scheme:id:ts]
BuildPayload --> FetchKey[Fetch Ed25519 public key<br/>using kid]
FetchKey --> KeyFound{Key exists?}
KeyFound -->|No| Reject2[❌ Reject: Unknown key]
KeyFound -->|Yes| VerifySig[Verify Ed25519 signature]
VerifySig --> SigValid{Signature valid?}
SigValid -->|No| Reject3[❌ Reject: Invalid signature]
SigValid -->|Yes| CheckTime[Check timestamp freshness]
CheckTime --> TimeValid{ts within window?<br/>e.g., ±5 minutes}
TimeValid -->|No| Reject4[❌ Reject: Timestamp too old/new]
TimeValid -->|Yes| CheckDomain{request_host matches<br/>expected publisher?}
CheckDomain -->|No| Reject5[❌ Reject: Domain mismatch]
CheckDomain -->|Yes| CheckScheme{request_scheme == 'https'?}
CheckScheme -->|No| Reject6[❌ Reject: Invalid scheme]
CheckScheme -->|Yes| Accept[✅ Accept Request]
Reject1 --> End([Return 403])
Reject2 --> End
Reject3 --> End
Reject4 --> End
Reject5 --> End
Reject6 --> End
Accept --> Process[Process auction]
Key Benefits
Implementation Notes
|
Summary
kid:request_host:request_scheme:id:tsversion("1.1") andts(Unix timestamp) fields toext.trusted_serverThis prevents request tampering and domain spoofing by ensuring the signature is cryptographically bound to the originating publisher domain.
Changes
versionandtsfields toTrustedServerExtSigningParamsstruct,SIGNING_VERSIONconstant, andsign_request()methodPrebidAuctionProviderandenhance_openrtb_requestto use enhanced signingOutput Format
{ "ext": { "trusted_server": { "version": "1.1", "kid": "ts-2026-01-A", "request_host": "publisher.com", "request_scheme": "https", "ts": 1738527600, "signature": "base64-encoded-ed25519-signature" } } }Closes #274
Closes #216
Test plan
cargo build --release)cargo clippy)