import { decryptBox, decryptSealed, deriveKEK, fromBase64, toBase64URL, } from "../crypto/index.js"; import type { AuthorizationResponse } from "./types.js"; export interface UnwrapResult { masterKey: Uint8Array; secretKey: Uint8Array; publicKey: Uint8Array; // URL-safe-no-padding base64 of the decrypted token bytes; this is the // exact value to put in the X-Auth-Token header on subsequent calls. token: string; } // Given a successful AuthorizationResponse and the user's password, // derive the KEK, decrypt the master key, decrypt the secret key, and // open the sealed auth token. See test/auth/unwrap.test.ts for the // complete protocol description. export const unwrapAuth = async ( response: AuthorizationResponse, password: string, ): Promise => { if (!response.keyAttributes) { throw new Error( "unwrapAuth: response.keyAttributes is required (login may be incomplete: TOTP or passkey pending)", ); } if (!response.encryptedToken) { throw new Error( "unwrapAuth: response.encryptedToken is required (login may be incomplete: TOTP or passkey pending)", ); } const ka = response.keyAttributes; const kek = await deriveKEK( password, fromBase64(ka.kekSalt), ka.opsLimit, ka.memLimit, ); const masterKey = decryptBox( fromBase64(ka.encryptedKey), fromBase64(ka.keyDecryptionNonce), kek, ); const secretKey = decryptBox( fromBase64(ka.encryptedSecretKey), fromBase64(ka.secretKeyDecryptionNonce), masterKey, ); const publicKey = fromBase64(ka.publicKey); const tokenBytes = decryptSealed( fromBase64(response.encryptedToken), publicKey, secretKey, ); const token = toBase64URL(tokenBytes); return { masterKey, secretKey, publicKey, token }; };