Skip to content

Instantly share code, notes, and snippets.

@KunalKumarSwift
Last active January 10, 2025 03:26
Show Gist options
  • Save KunalKumarSwift/5e5d62eb5316a33293e685bf4b6ccf2c to your computer and use it in GitHub Desktop.
Save KunalKumarSwift/5e5d62eb5316a33293e685bf4b6ccf2c to your computer and use it in GitHub Desktop.
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