Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

export remaining functions and types for maple #5

Merged
merged 3 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opensecret/react",
"version": "0.1.7",
"version": "0.1.8",
"license": "MIT",
"type": "module",
"files": [
Expand Down
28 changes: 28 additions & 0 deletions src/lib/attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,37 @@ export async function authenticate(
}
}

// For localhost we get a fake document and we only need the public key
const FakeAttestationDocumentSchema = z.object({
public_key: z.nullable(z.instanceof(Uint8Array))
});

type FakeAttestationDocument = z.infer<typeof FakeAttestationDocumentSchema>;

async function fakeAuthenticate(
attestationDocumentBase64: string
): Promise<FakeAttestationDocument> {
const attestationDocumentBuffer = decode(attestationDocumentBase64);
const cborAttestationDocument: Uint8Array[] = cbor.decode(attestationDocumentBuffer);
const payload = cborAttestationDocument[2];
const payloadDecoded = cbor.decode(payload);
const zodParsed = await FakeAttestationDocumentSchema.parse(payloadDecoded);
return zodParsed;
}

export async function verifyAttestation(nonce: string): Promise<AttestationDocument> {
try {
const attestationDocumentBase64 = await fetchAttestationDocument(nonce);

// With a local backend we get a fake attestation document, so we'll just pretend to authenticate it
const API_URL = import.meta.env.VITE_OPEN_SECRET_API_URL;
if (API_URL === "http://127.0.0.1:3000" || API_URL === "http://localhost:3000" || API_URL === "http://0.0.0.0:3000" ) {
console.log("DEV MODE: Using fake attestation document");
const fakeDocument = await fakeAuthenticate(attestationDocumentBase64);
return fakeDocument as AttestationDocument;
}

// The real thing!
const verifiedDocument = await authenticate(attestationDocumentBase64, awsRootCertDer, nonce);
return verifiedDocument;
} catch (error) {
Expand Down
116 changes: 116 additions & 0 deletions src/lib/attestationForView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { encode } from "@stablelib/base64";
import { type AttestationDocument } from "./attestation";
import awsRootCertDer from "../assets/aws_root.der";
import { X509Certificate } from "@peculiar/x509";

export const AWS_ROOT_CERT_DER = awsRootCertDer;

export const EXPECTED_ROOT_CERT_HASH = "641a0321a3e244efe456463195d606317ed7cdcc3c1756e09893f3c68f79bb5b";

export const VALID_PCR0_VALUES = [
"eeddbb58f57c38894d6d5af5e575fbe791c5bf3bbcfb5df8da8cfcf0c2e1da1913108e6a762112444740b88c163d7f4b",
"74ed417f88cb0ca76c4a3d10f278bd010f1d3f95eafb254d4732511bb50e404507a4049b779c5230137e4091a5582271",
"9043fcab93b972d3c14ad2dc8fa78ca7ad374fc937c02435681772a003f7a72876bc4d578089b5c4cf3fe9b480f1aabb",
"52c3595b151d93d8b159c257301bfd5aa6f49210de0c55a6cd6df5ebeee44e4206cab950500f5d188f7fa14e6d900b75",
"91cb67311e910cce68cd5b7d0de77aa40610d87c6681439b44c46c3ff786ae643956ab2c812478a1da8745b259f07a45",
"859065ac81b81d3735130ba08b8af72a7256b603fefb74faabae25ed28cca6edcaa7c10ea32b5948d675c18a9b0f2b1d",
"acd82a7d3943e23e95a9dc3ce0b0107ea358d6287f9e3afa245622f7c7e3e0a66142a928b6efcc02f594a95366d3a99d"
];

export const VALID_PCR0_VALUES_DEV = [
"62c0407056217a4c10764ed9045694c29fa93255d3cc04c2f989cdd9a1f8050c8b169714c71f1118ebce2fcc9951d1a9",
"cb95519905443f9f66f05f63c548b61ad1561a27fd5717b69285861aaea3c3063fe12a2571773b67fea3c6c11b4d8ec6",
"deb5895831b5e4286f5a2dcf5e9c27383821446f8df2b465f141d10743599be20ba3bb381ce063bf7139cc89f7f61d4c",
"70ba26c6af1ec3b57ce80e1adcc0ee96d70224d4c7a078f427895cdf68e1c30f09b5ac4c456588d872f3f21ff77c036b",
"669404ea71435b8f498b48db7816a5c2ab1d258b1a77685b11d84d15a73189504d79c4dee13a658de9f4a0cbfc39cfe8",
"a791bf92c25ffdfd372660e460a0e238c6778c090672df6509ae4bc065cf8668b6baac6b6a11d554af53ee0ff0172ad5",
"c4285443b87b9b12a6cea3bef1064ec060f652b235a297095975af8f134e5ed65f92d70d4616fdec80af9dff48bb9f35"
];

export type ParsedAttestationView = {
moduleId: string;
publicKey: string | null;
timestamp: string;
digest: string;
pcrs: Array<{
id: number;
value: string;
}>;
certificates: Array<{
subject: string;
notBefore: string;
notAfter: string;
pem: string;
isRoot: boolean;
}>;
userData: string | null;
nonce: string | null;
cert0hash: string;
};

function toHexString(data: Uint8Array | number[]): string {
return Array.from(data)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}

async function calculateCertHash(data: ArrayBuffer): Promise<string> {
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return toHexString(new Uint8Array(hashBuffer));
}

export async function parseAttestationForView(
document: AttestationDocument,
cabundle: Uint8Array[]
): Promise<ParsedAttestationView> {
// Add logging to see what we're getting
console.log("Raw timestamp:", document.timestamp);
console.log("Date object:", new Date(document.timestamp));

// Convert PCRs to hex strings and filter out all-zero values
const pcrs = Array.from(document.pcrs.entries())
.map(([key, value]) => ({
id: key,
value: toHexString(value)
}))
.filter((pcr) => !pcr.value.match(/^0+$/));

// Parse certificates - cabundle first, then leaf certificate
const certificates = [...cabundle, document.certificate].map((certBytes) => {
const cert = new X509Certificate(certBytes);
return {
subject: cert.subject,
notBefore: cert.notBefore.toLocaleString(),
notAfter: cert.notAfter.toLocaleString(),
pem: cert.toString("pem"),
isRoot: cert.subject === "C=US, O=Amazon, OU=AWS, CN=aws.nitro-enclaves"
};
});

// Parse userData and nonce if they exist
const decoder = new TextDecoder();

// Calculate cert0 hash
const cert0 = new X509Certificate(cabundle[0]);
const cert0hash = await calculateCertHash(cert0.rawData);

return {
moduleId: document.module_id,
publicKey: document.public_key ? encode(document.public_key) : null,
timestamp: new Date(document.timestamp).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
}),
digest: document.digest,
pcrs,
certificates,
userData: document.user_data ? decoder.decode(document.user_data) : null,
nonce: document.nonce ? decoder.decode(document.nonce) : null,
cert0hash
};
}
14 changes: 14 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ export { useOpenSecret } from "./context";

// Export key functions that might be useful
export { setApiUrl } from "./api";

// Export getAttestation
export { getAttestation } from "./getAttestation";

export { authenticate } from "./attestation";
export type { AttestationDocument } from "./attestation";

// Export attestationForView
export { parseAttestationForView } from "./attestationForView";
export type { ParsedAttestationView } from "./attestationForView";
export { EXPECTED_ROOT_CERT_HASH, VALID_PCR0_VALUES, VALID_PCR0_VALUES_DEV, AWS_ROOT_CERT_DER } from "./attestationForView";

// Export crypto stuff
export { generateSecureSecret, hashSecret } from "./crypto";
18 changes: 12 additions & 6 deletions src/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export type OpenSecretContextType = {
refetchUser: () => Promise<void>;
changePassword: typeof api.changePassword;
refreshAccessToken: typeof api.refreshToken;
requestPasswordReset: typeof api.requestPasswordReset;
confirmPasswordReset: typeof api.confirmPasswordReset;
initiateGitHubAuth: (inviteCode: string) => Promise<api.GithubAuthResponse>;
handleGitHubCallback: (code: string, state: string, inviteCode: string) => Promise<void>;
initiateGoogleAuth: (inviteCode: string) => Promise<api.GoogleAuthResponse>;
Expand Down Expand Up @@ -180,23 +182,25 @@ export const OpenSecretContext = createContext<OpenSecretContextType>({
loading: true,
user: undefined
},
signIn: async () => {},
signUp: async () => {},
signOut: async () => {},
signIn: async () => { },
signUp: async () => { },
signOut: async () => { },
get: api.fetchGet,
put: api.fetchPut,
list: api.fetchList,
del: api.fetchDelete,
verifyEmail: api.verifyEmail,
requestNewVerificationCode: api.requestNewVerificationCode,
requestNewVerificationEmail: api.requestNewVerificationCode,
refetchUser: async () => {},
refetchUser: async () => { },
changePassword: api.changePassword,
refreshAccessToken: api.refreshToken,
requestPasswordReset: api.requestPasswordReset,
confirmPasswordReset: api.confirmPasswordReset,
initiateGitHubAuth: async () => ({ auth_url: "", csrf_token: "" }),
handleGitHubCallback: async () => {},
handleGitHubCallback: async () => { },
initiateGoogleAuth: async () => ({ auth_url: "", csrf_token: "" }),
handleGoogleCallback: async () => {},
handleGoogleCallback: async () => { },
getPrivateKey: api.fetchPrivateKey,
getPublicKey: api.fetchPublicKey,
signMessage: api.signMessage,
Expand Down Expand Up @@ -397,6 +401,8 @@ export function OpenSecretProvider({
requestNewVerificationEmail: api.requestNewVerificationCode,
changePassword: api.changePassword,
refreshAccessToken: api.refreshToken,
requestPasswordReset: api.requestPasswordReset,
confirmPasswordReset: api.confirmPasswordReset,
initiateGitHubAuth,
handleGitHubCallback,
initiateGoogleAuth,
Expand Down