diff --git a/frontend/app/api/verify-amd/route.ts b/frontend/app/api/verify-amd/route.ts new file mode 100644 index 0000000..3fffd30 --- /dev/null +++ b/frontend/app/api/verify-amd/route.ts @@ -0,0 +1,60 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const body = await req.json().catch(() => null); + const report = body?.report; + + if (typeof report !== "string" || report.trim().length === 0) { + return NextResponse.json( + { success: false, error: "Invalid AMD attestation report provided" }, + { status: 400 } + ); + } + + const upstreamRes = await fetch( + "https://nilcc-verifier.nillion.network/v1/attestations/verify-amd", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ report }), + } + ); + + const contentType = upstreamRes.headers.get("content-type") || ""; + + if (contentType.includes("application/json")) { + const json = await upstreamRes.json().catch(() => ({})); + // Treat any 2xx response from the verifier as success. + return NextResponse.json( + { + success: upstreamRes.ok, + ...json, + }, + { status: upstreamRes.status } + ); + } else { + const text = await upstreamRes.text(); + if (!upstreamRes.ok) { + return NextResponse.json( + { success: false, error: text || "AMD attestation verification failed" }, + { status: upstreamRes.status } + ); + } + + return new NextResponse(text, { + status: upstreamRes.status, + headers: { "Content-Type": "text/plain" }, + }); + } + } catch (err) { + console.error("AMD verifier Next.js API error:", err); + return NextResponse.json( + { success: false, error: "Failed to reach AMD verifier" }, + { status: 502 } + ); + } +} + diff --git a/frontend/app/api/verify-attestation/route.ts b/frontend/app/api/verify-attestation/route.ts new file mode 100644 index 0000000..28710ba --- /dev/null +++ b/frontend/app/api/verify-attestation/route.ts @@ -0,0 +1,43 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const body = await req.json().catch(() => null); + const { hex, type } = body || {}; + + if (typeof hex !== "string" || hex.trim().length === 0) { + return NextResponse.json( + { error: "Invalid attestation quote provided" }, + { status: 400 } + ); + } + + const verifyRes = await fetch( + "https://cloud-api.phala.com/proofofcloud/attestations/verify", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ hex, type: type || "intel" }), + } + ); + + const verifyData = await verifyRes.json().catch(() => ({})); + + if (!verifyRes.ok) { + return NextResponse.json( + { error: verifyData.error || verifyData.detail || verifyData.message || "Verification failed" }, + { status: verifyRes.status } + ); + } + + return NextResponse.json(verifyData, { status: 200 }); + } catch (err) { + console.error("Attestation verification API error:", err); + return NextResponse.json( + { error: "Failed to reach verification service" }, + { status: 502 } + ); + } +} diff --git a/frontend/app/api/view-attestation/[checksum]/route.ts b/frontend/app/api/view-attestation/[checksum]/route.ts new file mode 100644 index 0000000..c1b6469 --- /dev/null +++ b/frontend/app/api/view-attestation/[checksum]/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ checksum: string }> } +) { + try { + const { checksum } = await params; + + if (!checksum) { + return NextResponse.json( + { error: "Checksum is required" }, + { status: 400 } + ); + } + + const viewRes = await fetch( + `https://cloud-api.phala.com/proofofcloud/attestations/view/${checksum}` + ); + + const viewData = await viewRes.json().catch(() => ({})); + + if (!viewRes.ok) { + return NextResponse.json( + { error: viewData.error || viewData.detail || "Failed to retrieve verification details" }, + { status: viewRes.status } + ); + } + + return NextResponse.json(viewData, { status: 200 }); + } catch (err) { + console.error("View attestation API error:", err); + return NextResponse.json( + { error: "Failed to reach verification service" }, + { status: 502 } + ); + } +} diff --git a/frontend/components/verification-form.tsx b/frontend/components/verification-form.tsx index 126bcb9..4d0aeef 100644 --- a/frontend/components/verification-form.tsx +++ b/frontend/components/verification-form.tsx @@ -18,6 +18,7 @@ export function VerificationFormWithLabel() { uploadedAt?: string; error?: string; } | null>(null); + const [attestationType, setAttestationType] = useState<'intel' | 'amd'>('intel'); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -35,57 +36,86 @@ export function VerificationFormWithLabel() { setResult(null); try { - // Normalize to hex (remove 0x prefix if present, remove whitespace) - let quoteHex = attestation.trim().replace(/^0x/i, '').replace(/\s+/g, ''); - - // Check if it's potentially base64 and convert - if (!/^[0-9a-fA-F]+$/.test(quoteHex)) { - try { - // Attempt base64 to hex conversion - const binary = atob(quoteHex); - quoteHex = Array.from(binary, (char) => - char.charCodeAt(0).toString(16).padStart(2, '0') - ).join(''); - } catch { - throw new Error("Invalid attestation format. Please provide hex or base64 encoded quote."); + if (attestationType === 'intel') { + // Intel flow: normalize to hex and use Phala Proof of Cloud API + let quoteHex = attestation.trim().replace(/^0x/i, '').replace(/\s+/g, ''); + + if (!/^[0-9a-fA-F]+$/.test(quoteHex)) { + try { + const binary = atob(quoteHex); + quoteHex = Array.from(binary, (char) => + char.charCodeAt(0).toString(16).padStart(2, '0') + ).join(''); + } catch { + throw new Error("Invalid attestation format. Please provide hex or base64 encoded quote."); + } } - } - // Step 1: Submit attestation to get checksum - const verifyResponse = await fetch("https://cloud-api.phala.com/proofofcloud/attestations/verify", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ hex: quoteHex }), - }); + // Use local API proxy to avoid CORS issues + const verifyResponse = await fetch("/api/verify-attestation", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ hex: quoteHex, type: attestationType }), + }); - const verifyData = await verifyResponse.json(); + const verifyData = await verifyResponse.json(); - if (!verifyResponse.ok) { - throw new Error(verifyData.error || verifyData.message || "Verification failed"); - } + if (!verifyResponse.ok) { + throw new Error(verifyData.error || verifyData.message || "Verification failed"); + } - const checksum = verifyData.checksum; - if (!checksum) { - throw new Error("No checksum returned from verification service"); - } + const checksum = verifyData.checksum; + if (!checksum) { + throw new Error("No checksum returned from verification service"); + } - // Step 2: Fetch full verification result using checksum - const viewResponse = await fetch(`https://cloud-api.phala.com/proofofcloud/attestations/view/${checksum}`); + const viewResponse = await fetch(`/api/view-attestation/${checksum}`); - if (!viewResponse.ok) { - throw new Error("Failed to retrieve verification details"); - } + if (!viewResponse.ok) { + throw new Error("Failed to retrieve verification details"); + } - const viewData = await viewResponse.json(); + const viewData = await viewResponse.json(); - setResult({ - verified: viewData.verified || false, - proofOfCloud: viewData.proof_of_cloud || false, - checksum: checksum, - uploadedAt: viewData.uploaded_at, - }); + setResult({ + verified: viewData.verified || false, + proofOfCloud: viewData.proof_of_cloud || false, + checksum: checksum, + uploadedAt: viewData.uploaded_at, + }); + } else { + // AMD flow: send raw report to Nillion verifier + const report = attestation.trim(); + + const verifierResponse = await fetch('/api/verify-amd', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ report }), + }); + + const verifierData = await verifierResponse.json().catch(() => ({})); + + const ok = verifierResponse.ok && (verifierData as any)?.success === true; + + if (!ok) { + throw new Error( + (verifierData as any)?.error || + (verifierData as any)?.message || + "AMD attestation verification failed" + ); + } + + setResult({ + verified: true, + proofOfCloud: false, + checksum: undefined, + uploadedAt: undefined, + }); + } } catch (error) { console.error("Verification error:", error); setResult({ @@ -281,6 +311,31 @@ export function VerificationFormWithLabel() { {/* Form */}
+
+ Platform + + +