Challenge Generation API
Quick Start
Advanced Features
Challenge Generation
Understand how Grantiva generates and manages cryptographic challenges for secure attestation.
Overview
Challenges are cryptographically secure random values used to prevent replay attacks during the attestation process. Each challenge is unique and can only be used once within a specific time window.
Important Note
With Grantiva's SDK, challenge generation is handled automatically. You don't need to manually request challenges - the SDK manages this for you. This documentation is provided for transparency and custom implementations.
How Challenges Work
- Generation: A cryptographically secure random value is generated
- Storage: The challenge is stored with an expiration timestamp
- Usage: The client includes the challenge in the attestation request
- Validation: The server verifies the challenge hasn't been used and isn't expired
- Invalidation: Once used, the challenge is marked as consumed
Challenge API Endpoint
/api/v1/attestation/challenge
Generate a new challenge for attestation. No authentication required as tenant identification happens during attestation validation.
Request
curl https://api.grantiva.com/api/v1/attestation/challenge
Response
{
"challenge": "YmFzZTY0X2VuY29kZWRfcmFuZG9tX2RhdGE=",
"expiresAt": "2024-01-15T12:05:00Z",
"expiresIn": 300
}
Response Fields
challenge
- Base64 encoded random data (32 bytes)expiresAt
- ISO 8601 timestamp when challenge expiresexpiresIn
- Seconds until expiration (default: 300)
SDK Implementation
The Grantiva SDK handles challenge generation automatically. Here's what happens behind the scenes:
// This is handled internally by the SDK
class GrantivaInternal {
func generateChallenge() async throws -> Challenge {
let url = URL(string: "https://api.grantiva.com/api/v1/attestation/challenge")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(Challenge.self, from: data)
}
func attestWithChallenge(_ challenge: String) async throws -> Data {
let service = DCAppAttestService.shared
let keyId = try await service.generateKey()
// Create client data hash from challenge
let clientData = Data(challenge.utf8)
let hash = SHA256.hash(data: clientData)
// Generate attestation
return try await service.attestKey(keyId, clientDataHash: hash)
}
}
Challenge Security
Cryptographic Properties
- Randomness: 256 bits of cryptographically secure random data
- Uniqueness: Guaranteed unique across all tenants
- Single-use: Each challenge can only be used once
- Time-bound: Expires after 5 minutes by default
Replay Attack Prevention
// Server-side validation
async function validateChallenge(challenge: string, tenantId: string) {
// Check if challenge exists and hasn't been used
const storedChallenge = await db.challenges.findOne({
value: challenge,
tenantId: tenantId,
used: false,
expiresAt: { $gt: new Date() }
});
if (!storedChallenge) {
throw new Error('Invalid or expired challenge');
}
// Mark as used
await db.challenges.updateOne(
{ _id: storedChallenge._id },
{ $set: { used: true, usedAt: new Date() } }
);
return true;
}
Custom Implementation
If you're building a custom client, here's how to properly use challenges:
// Custom attestation implementation
class CustomAttestationClient {
let apiBase = "https://api.grantiva.com/api/v1"
func performAttestation() async throws -> AttestationResult {
// Step 1: Get challenge
let challengeURL = URL(string: "\(apiBase)/attestation/challenge")!
let (challengeData, _) = try await URLSession.shared.data(from: challengeURL)
let challenge = try JSONDecoder().decode(ChallengeResponse.self, from: challengeData)
// Step 2: Generate attestation with challenge
let service = DCAppAttestService.shared
let keyId = try await service.generateKey()
// Create hash of challenge
let clientData = Data(challenge.challenge.utf8)
let hash = SHA256.hash(data: clientData)
// Generate attestation
let attestation = try await service.attestKey(keyId, clientDataHash: hash)
// Step 3: Submit attestation for validation
var request = URLRequest(url: URL(string: "\(apiBase)/attestation/validate")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let payload = AttestationRequest(
keyId: keyId,
attestation: attestation.base64EncodedString(),
challenge: challenge.challenge
)
request.httpBody = try JSONEncoder().encode(payload)
let (resultData, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(AttestationResult.self, from: resultData)
}
}
Challenge Configuration
Challenge behavior can be customized per tenant (Enterprise tier and above):
Setting | Default | Range |
---|---|---|
Challenge Expiration | 300 seconds | 60-3600 seconds |
Challenge Length | 32 bytes | 16-64 bytes |
Rate Limiting | 100/minute | 10-1000/minute |
Error Handling
// 429 Too Many Requests
{
"error": "rate_limit_exceeded",
"message": "Challenge generation rate limit exceeded",
"retryAfter": 60
}
// 503 Service Unavailable
{
"error": "service_unavailable",
"message": "Challenge generation temporarily unavailable"
}
Best Practices
- Use the SDK's automatic challenge handling when possible
- Don't cache or reuse challenges
- Complete attestation promptly after challenge generation
- Implement exponential backoff for retries
- Monitor challenge expiration in your error logs