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.
This commit is contained in:
@@ -611,8 +611,10 @@ Phase 3: SRP + auth
|
|||||||
- [ ] `beginLogin(email, password)` returning a `LoginChallenge`
|
- [ ] `beginLogin(email, password)` returning a `LoginChallenge`
|
||||||
- [ ] `requestEmailOTP` and `submitEmailOTP` for accounts without SRP
|
- [ ] `requestEmailOTP` and `submitEmailOTP` for accounts without SRP
|
||||||
- [ ] `submitTOTP(sessionID, code)`
|
- [ ] `submitTOTP(sessionID, code)`
|
||||||
- [ ] `unwrapAuth(response, password)` returning master key, secret key, public
|
- [x] `unwrapAuth(response, password)` returning master key, secret key, public
|
||||||
key, and decrypted token
|
key, and decrypted token (URL-safe-no-padding base64)
|
||||||
|
- [x] `src/auth/types.ts` with `KeyAttributes`, `SRPAttributes`,
|
||||||
|
`AuthorizationResponse`, and `LoginChallenge`
|
||||||
- [ ] Tests against recorded HTTP fixtures
|
- [ ] Tests against recorded HTTP fixtures
|
||||||
|
|
||||||
Phase 4: HTTP client + endpoints
|
Phase 4: HTTP client + endpoints
|
||||||
|
|||||||
@@ -1,17 +1,64 @@
|
|||||||
// Stub: see the README "Development workflow" section for TDD policy.
|
import {
|
||||||
|
decryptBox,
|
||||||
|
decryptSealed,
|
||||||
|
deriveKEK,
|
||||||
|
fromBase64,
|
||||||
|
toBase64URL,
|
||||||
|
} from "../crypto/index.js";
|
||||||
import type { AuthorizationResponse } from "./types.js";
|
import type { AuthorizationResponse } from "./types.js";
|
||||||
|
|
||||||
export interface UnwrapResult {
|
export interface UnwrapResult {
|
||||||
masterKey: Uint8Array;
|
masterKey: Uint8Array;
|
||||||
secretKey: Uint8Array;
|
secretKey: Uint8Array;
|
||||||
publicKey: 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;
|
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 (
|
export const unwrapAuth = async (
|
||||||
_response: AuthorizationResponse,
|
response: AuthorizationResponse,
|
||||||
_password: string,
|
password: string,
|
||||||
): Promise<UnwrapResult> => {
|
): Promise<UnwrapResult> => {
|
||||||
throw new Error("auth.unwrapAuth not implemented");
|
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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1,9 @@
|
|||||||
export const VERSION = "0.0.0";
|
export const VERSION = "0.0.0";
|
||||||
|
|
||||||
|
export { unwrapAuth, type UnwrapResult } from "./auth/unwrap.js";
|
||||||
|
export type {
|
||||||
|
AuthorizationResponse,
|
||||||
|
KeyAttributes,
|
||||||
|
LoginChallenge,
|
||||||
|
SRPAttributes,
|
||||||
|
} from "./auth/types.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user