Files
quak/src/auth/unwrap.ts
sneak 78fdabe54a Phase 3a green: implement auth.unwrapAuth
The implementation is exactly the decryption chain documented in the
test file: deriveKEK -> decryptBox(masterKey) -> decryptBox(secretKey)
-> decryptSealed(token) -> toBase64URL. Errors from the underlying
crypto primitives propagate; the only added validation is the up-front
check that the response actually contains both keyAttributes and
encryptedToken (caller bug if not).

Also re-exports the auth/unwrap and auth/types public surface from
src/index.ts.

All 38 tests pass; make check and make docker are green.
2026-05-11 00:59:43 -07:00

65 lines
1.9 KiB
TypeScript

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<UnwrapResult> => {
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 };
};