Last active
January 10, 2025 03:26
-
-
Save KunalKumarSwift/5e5d62eb5316a33293e685bf4b6ccf2c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require("express"); | |
const fetch = require("node-fetch"); | |
const jwt = require("jsonwebtoken"); | |
const app = express(); | |
const port = 9000; | |
// Apple's attestation API endpoint (using development endpoint) | |
const APPLE_ATTESTATION_API = | |
"https://api.development.devicecheck.apple.com/v1/validate_device_token"; | |
// For production use: https://api.devicecheck.apple.com/v1/validate_device_token | |
class AttestationVerifier { | |
constructor() { | |
// Load credentials from environment variables | |
this.appleTeamId = process.env.APPLE_TEAM_ID; | |
this.appleKeyId = process.env.APPLE_KEY_ID; | |
this.applePrivateKey = process.env.APPLE_PRIVATE_KEY; | |
if (!this.appleTeamId || !this.appleKeyId || !this.applePrivateKey) { | |
throw new Error( | |
"Missing required Apple credentials in environment variables" | |
); | |
} | |
} | |
async generateAppleJWT() { | |
const header = { | |
alg: "ES256", | |
kid: this.appleKeyId, | |
}; | |
const payload = { | |
iss: this.appleTeamId, | |
iat: Math.floor(Date.now() / 1000), | |
}; | |
return jwt.sign(payload, this.applePrivateKey, { | |
algorithm: "ES256", | |
header: header, | |
}); | |
} | |
async verifyAttestation(attestationObject, keyId) { | |
try { | |
const jwt = await this.generateAppleJWT(); | |
const response = await fetch(APPLE_ATTESTATION_API, { | |
method: "POST", | |
headers: { | |
Authorization: `Bearer ${jwt}`, | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ | |
attestation: attestationObject, | |
key_id: keyId, | |
}), | |
}); | |
if (!response.ok) { | |
const errorText = await response.text(); | |
throw new Error( | |
`Apple attestation verification failed: ${response.status} - ${errorText}` | |
); | |
} | |
const result = await response.json(); | |
return result.isValid === true; | |
} catch (error) { | |
console.error("Attestation verification error:", error); | |
throw error; | |
} | |
} | |
} | |
// Middleware to parse JSON bodies | |
app.use(express.json()); | |
// Health check endpoint | |
app.get("/health", (req, res) => { | |
res.status(200).json({ status: "healthy" }); | |
}); | |
// Attestation endpoint | |
app.post("/api/attest", async (req, res) => { | |
try { | |
const { keyId, attestationObject } = req.body; | |
// Validate required fields | |
if (!keyId || !attestationObject) { | |
return res.status(400).json({ | |
error: | |
"Missing required fields: keyId and attestationObject are required", | |
}); | |
} | |
// Create verifier instance | |
const verifier = new AttestationVerifier(); | |
// Verify the attestation | |
const isValid = await verifier.verifyAttestation(attestationObject, keyId); | |
if (isValid) { | |
// In a production environment, you would: | |
// 1. Store the verified keyId in your database | |
// 2. Associate it with the user/device | |
// 3. Possibly set an expiration | |
res.status(200).json({ | |
success: true, | |
keyId: keyId, | |
verified: true, | |
}); | |
} else { | |
res.status(401).json({ | |
success: false, | |
error: "Invalid attestation", | |
}); | |
} | |
} catch (error) { | |
console.error("Attestation error:", error); | |
res.status(500).json({ | |
success: false, | |
error: "Attestation verification failed", | |
message: error.message, | |
}); | |
} | |
}); | |
// Start server | |
app.listen(port, () => { | |
console.log(`Attestation server running at http://localhost:${port}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment