Authenticated Requests

To carry out an unauthenticated request, 4 headers are required:

  1. Authentication Header - X-Encrypted-Session or X-Encrypted-WebAuthn-Signature or X-Encrypted-Key-Signature

  2. X-Encrypted-Key

  3. X-Encrypted-User

  4. X-Scope-Id

X-Encrypted-Session is obtained when you authenticate with the Authenticating with API endpoint. This is used by enclave to verify your requests. Other 2 types of headers are obtained by signing the user details object directly using your auth method (passkeys or private key)

To setup an X-Encrypted-Key you need to generate a random AES Key, and then RSA encrypt it with the secure enclaves public key, we'll use TypeScript for the example below.

Here we setup a cryptoObj that can work in both browser and server environments and then a function to generate an AES Key.

const cryptoObj = typeof window !== "undefined" ? window.crypto : crypto;

export const generateAesKey = async () => {
  const cryptoKey = await cryptoObj.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"]
  );
  return await cryptoObj.subtle.exportKey("raw", cryptoKey);
};

Next we setup two function an encrypt function, which we then use in our rsaEncrypt function.

const encrypt = async (algo, key, data) => {
  try {
    const encryptedResult = await cryptoObj.subtle.encrypt(algo, key, data);
    return encryptedResult;
  } catch (error) {
    console.error("Encryption error:", error);
    throw error;
  }
};

const rsaEncrypt = async (
  plainText: string,
  encryptionKey: BufferSource,
  keyFormat: "spki" = "spki",
  hashName: "SHA-256" = "SHA-256"
) => {
  if (!encryptionKey) {
    throw Error("Encryption key not initialized");
  }

  const encoder = new TextEncoder();
  const data = encoder.encode(plainText);

  let cryptoKey: CryptoKey;
  let encrypted: ArrayBuffer;

  try {
    cryptoKey = await importKey(
      keyFormat,
      encryptionKey,
      { name: "RSA-OAEP", hash: { name: hashName } },
      ["encrypt"]
    );
    encrypted = await encrypt({ name: "RSA-OAEP" }, cryptoKey, data);
    return btoa(String.fromCharCode.apply(null, new Uint8Array(encrypted)));
  } catch (error) {
    console.error("RSA-OAEP Encryption error:", error);
    throw error;
  }
};


export const aesEncrypt = async (
  plainText: string,
  encryptionKey: BufferSource,
  keyFormat: "raw" = "raw",
  keyLength: number = 256
) => {
  if (!encryptionKey) {
    throw Error("Encryption key not initialized");
  }

  const encoder = new TextEncoder();
  const data = encoder.encode(plainText);
  let cryptoKey: CryptoKey;
  let encrypted: ArrayBuffer;

  try {
    cryptoKey = await importKey(keyFormat, encryptionKey, { name: "AES-GCM", length: keyLength }, [
      "encrypt",
    ]);

    const iv = cryptoObj.getRandomValues(new Uint8Array(12)); // Initialization vector

    encrypted = await encrypt({ name: "AES-GCM", iv }, cryptoKey, data);

    const combined = new Uint8Array(iv.length + encrypted.byteLength);
    combined.set(iv, 0);
    combined.set(new Uint8Array(encrypted), iv.length);

    return btoa(String.fromCharCode.apply(null, combined));
  } catch (error) {
    console.error("AES-GCM Encryption error:", error);
    throw error;
  }
};

With these function setup, we are able to generate our AES key, and then RSA Encrypt it using the public key of the secure enclave.

const enclavePublicKey = 
  "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvQOa1gkatuN6KjaS4KEWsVZAN9i4Cf0j9jlmBW5RwCJ3Bxo32McP7axt4Ev6sMWM24lpCgXgu68S9KBYRcrcEB6dRcaupFGd+ER7M518fiJ0VtCZ+XRnmwn9fqEvotp9DPZOysJkUQ60kugCRKwNvfZzAFcDiubwiqsUY2sCm943a/u9Hym51SEetG+ZFPJZFOBqwRSGkOgGZ+9Ac7ITE+bWLCZk9DlzRu+BIoDOFzXZIn+/0a0X8BnLtRY4g50aew4J+4OllQagBbhYnPMvYExYIEUx6bdjQicw0Js6s2pHr+SFAX23kQtbVOVxb5+KEGp1d+6Q4Gx7FBoyWI5qPQIDAQAB";

const aesKey = await generateAesKey();
const aesKeyBytes = new Uint8Array(aesKey);
const aesKeyString = btoa(String.fromCharCode(...aesKeyBytes));
const encryptedAesKey = await rsaEncrypt(aesKeyString, enclavePublicKey);
const userDetails = {
    username: "john_doe",
    userDisplayName: "john_doe_crypto",
};
const encryptedUser = await aesEncrypt(JSON.stringify(userDetails), aesKey);

Now you have your encryptedAesKey and encryptedUser you can use this value as the value in the request header as X-Encrypted-Key . With X-Scope-Id, X-Encrypted-User , X-Encrypted-Session and X-Encrypted-Key setup, you can now start interacting with Passport API to sign messages, transactions and everythig else!

Last updated