Last active
January 10, 2025 08:12
-
-
Save KunalKumarSwift/43782b59f0c20372aafd8ae4239df642 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 bodyParser = require('body-parser'); | |
const crypto = require('crypto'); | |
const cbor = require('cbor'); | |
const app = express(); | |
app.use(bodyParser.json()); | |
// Apple's App Attest root certificate (replace with actual PEM format certificate) | |
const APPLE_ROOT_CERT_PEM = ` | |
-----BEGIN CERTIFICATE----- | |
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... | |
-----END CERTIFICATE----- | |
`; | |
// In-memory storage for challenges (use a database in production) | |
const challenges = new Map(); | |
/** | |
* Convert a DER-encoded certificate buffer to PEM format. | |
*/ | |
function convertDERtoPEM(derBuffer) { | |
const base64Cert = derBuffer.toString('base64'); | |
const pemCert = `-----BEGIN CERTIFICATE-----\n${base64Cert.match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; | |
return pemCert; | |
} | |
/** | |
* Verify a certificate against its issuer's public key. | |
*/ | |
function verifyCertificate(certPEM, issuerPEM) { | |
const cert = new crypto.X509Certificate(certPEM); | |
const issuer = new crypto.X509Certificate(issuerPEM); | |
// Verify that the certificate is signed by the issuer | |
return cert.verify(issuer.publicKey); | |
} | |
/** | |
* Generate a random challenge and send it to the client. | |
*/ | |
app.get('/generate-challenge', (req, res) => { | |
try { | |
const challenge = crypto.randomBytes(32).toString('base64'); | |
const challengeId = crypto.randomUUID(); | |
challenges.set(challengeId, challenge); | |
res.status(200).json({ challengeId, challenge }); | |
} catch (error) { | |
console.error('Error generating challenge:', error); | |
res.status(500).json({ error: 'Failed to generate challenge' }); | |
} | |
}); | |
/** | |
* Verify the attestation object sent by the client. | |
*/ | |
app.post('/verify-attestation', async (req, res) => { | |
const { keyId, attestation, challengeId } = req.body; | |
try { | |
// Step 1: Retrieve and validate the original challenge | |
const originalChallenge = challenges.get(challengeId); | |
if (!originalChallenge) { | |
return res.status(400).json({ error: 'Invalid or expired challenge' }); | |
} | |
// Step 2: Decode Base64-encoded attestation object | |
const attestationBuffer = Buffer.from(attestation, 'base64'); | |
// Step 3: Parse CBOR-encoded attestation object | |
const decodedAttestation = cbor.decodeAllSync(attestationBuffer)[0]; | |
console.log('Decoded Attestation:', decodedAttestation); | |
const { fmt, attStmt, authData } = decodedAttestation; | |
if (fmt !== 'apple-appattest') { | |
return res.status(400).json({ error: 'Invalid attestation format' }); | |
} | |
// Step 4: Extract certificates from x5c | |
const x5c = attStmt.x5c; | |
if (!x5c || x5c.length < 2) { | |
return res.status(400).json({ error: 'Invalid certificate chain' }); | |
} | |
// Convert DER certificates to PEM format | |
const leafCertPEM = convertDERtoPEM(Buffer.from(x5c[0])); | |
const intermediateCertPEM = convertDERtoPEM(Buffer.from(x5c[1])); | |
const appleRootCertPEM = APPLE_ROOT_CERT_PEM; | |
// Step 5: Verify certificate chain | |
if (!verifyCertificate(intermediateCertPEM, appleRootCertPEM)) { | |
return res.status(400).json({ error: 'Invalid intermediate certificate' }); | |
} | |
if (!verifyCertificate(leafCertPEM, intermediateCertPEM)) { | |
return res.status(400).json({ error: 'Invalid leaf certificate' }); | |
} | |
// Step 6: Validate nonce | |
const clientDataHash = crypto.createHash('sha256').update(originalChallenge).digest(); | |
const compositeData = Buffer.concat([authData, clientDataHash]); | |
const nonce = crypto.createHash('sha256').update(compositeData).digest(); | |
// Extract nonce from credCert extension OID 1.2.840.113635.100.8.2 | |
const leafCert = new crypto.X509Certificate(leafCertPEM); | |
const nonceExtensionOID = '1.2.840.113635.100.8.2'; | |
if (!leafCert.extensions[nonceExtensionOID]) { | |
return res.status(400).json({ error: 'Nonce extension missing' }); | |
} | |
const nonceExtensionValue = Buffer.from(leafCert.extensions[nonceExtensionOID].value); | |
if (!nonce.equals(nonceExtensionValue)) { | |
return res.status(400).json({ error: 'Nonce mismatch' }); | |
} | |
// Step 7: Verify key identifier matches public key hash | |
const publicKeyHash = crypto.createHash('sha256').update(leafCert.publicKey.raw).digest('hex'); | |
if (publicKeyHash !== keyId) { | |
return res.status(400).json({ error: 'Key identifier mismatch' }); | |
} | |
// Clean up used challenge | |
challenges.delete(challengeId); | |
res.status(200).json({ message: 'Attestation verified successfully' }); | |
} catch (error) { | |
console.error('Error verifying attestation:', error); | |
res.status(500).json({ error: 'Internal server error', details: error.message }); | |
} | |
}); | |
// Start server | |
const PORT = process.env.PORT || 3000; | |
app.listen(PORT, () => { | |
console.log(`Server is running on http://localhost:${PORT}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment