Skip to content

Commit

Permalink
Separate principal and origin information in IndexedDB storage (#2578)
Browse files Browse the repository at this point in the history
* Separate principal and origin information in IndexedDB storage

This PR introduces the storage layout v4 which keeps separate digests
for principals and origins. That way it will be possible in the future
to determine whether a specific principal was last derived for a particular
origin.

* Address review input

* Formatting

* Fix rename mistake
  • Loading branch information
Frederik Rothenberger authored Aug 29, 2024
1 parent 1bc38a5 commit 4bae280
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 102 deletions.
1 change: 0 additions & 1 deletion src/frontend/src/flows/authorize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ const authenticate = async (
let autoSelectionIdentity = undefined;
if (nonNullish(authContext.authRequest.autoSelectionPrincipal)) {
autoSelectionIdentity = await getAnchorByPrincipal({
origin: authContext.requestOrigin,
principal: authContext.authRequest.autoSelectionPrincipal,
});
}
Expand Down
6 changes: 1 addition & 5 deletions src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,7 @@ const verifyCredentials = async ({
return abortedCredentials({ reason: "auth_failed_issuer" });
}

const userNumber_ = await getAnchorByPrincipal({
origin:
rpOrigin_ /* NOTE: the storage uses the request origin, not the derivation origin */,
principal: givenP_RP,
});
const userNumber_ = await getAnchorByPrincipal({ principal: givenP_RP });

// Ask user to confirm the verification of credentials
const allowed = await allowCredentials({
Expand Down
111 changes: 77 additions & 34 deletions src/frontend/src/storage/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
keys as idbKeys,
set as idbSet,
} from "idb-keyval";
import { expect } from "vitest";
import {
MAX_SAVED_ANCHORS,
MAX_SAVED_PRINCIPALS,
Expand Down Expand Up @@ -87,15 +88,15 @@ test(
},
indexeddb: {
after: (storage) => {
// Written to V3
// Written to V4
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const storageV3: any = storage["ii-storage-v3"];
expect(storageV3).toBeTypeOf("object");
const storageV4: any = storage["ii-storage-v4"];
expect(storageV4).toBeTypeOf("object");

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anchorsV3: any = storageV3["anchors"];
expect(anchorsV3).toBeTypeOf("object");
expect(anchorsV3["123456"]).toBeDefined();
const anchorsV4: any = storageV4["anchors"];
expect(anchorsV4).toBeTypeOf("object");
expect(anchorsV4["123456"]).toBeDefined();
},
},
}
Expand Down Expand Up @@ -146,7 +147,46 @@ test(
);

test(
"anchors are also written to V2",
"V3 anchors are migrated",
withStorage(
async () => {
expect(await getAnchors()).toContain(BigInt(10000));
expect(await getAnchors()).toContain(BigInt(10001));
expect(await getAnchors()).toContain(BigInt(10003));
},
{
indexeddb: {
before: {
/* V3 layout */
"ii-storage-v3": {
anchors: {
"10000": { lastUsedTimestamp: 0, knownPrincipals: [] },
"10001": { lastUsedTimestamp: 0, knownPrincipals: [] },
"10003": { lastUsedTimestamp: 0, knownPrincipals: [] },
},
hasher: await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-512" },
false /* not extractable */,
["sign"] /* only used to "sign" (e.g. produce a digest ) */
),
},
},
after: (storage) => {
// Written to V4
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const storageV4: any = storage["ii-storage-v4"];
expect(storageV4).toBeTypeOf("object");
expect(storageV4.anchors["10000"]).toBeDefined();
expect(storageV4.anchors["10001"]).toBeDefined();
expect(storageV4.anchors["10003"]).toBeDefined();
},
},
}
)
);

test(
"anchors are also written to V3",
withStorage(
async () => {
await setAnchorUsed(BigInt(10000));
Expand All @@ -156,13 +196,13 @@ test(
{
indexeddb: {
after: (storage) => {
// Written to V2
// Written to V3
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anchorsV2: any = storage["anchors"];
expect(anchorsV2).toBeTypeOf("object");
expect(anchorsV2["10000"]).toBeDefined();
expect(anchorsV2["10001"]).toBeDefined();
expect(anchorsV2["10003"]).toBeDefined();
const storageV3: any = storage["ii-storage-v3"];
expect(storageV3).toBeTypeOf("object");
expect(storageV3.anchors["10000"]).toBeDefined();
expect(storageV3.anchors["10001"]).toBeDefined();
expect(storageV3.anchors["10003"]).toBeDefined();
},
},
}
Expand Down Expand Up @@ -202,14 +242,7 @@ test(
principal,
});

const otherOrigin = "https://other.com";
expect(
await getAnchorByPrincipal({ origin: otherOrigin, principal })
).not.toBeDefined();

expect(await getAnchorByPrincipal({ origin, principal })).toBe(
BigInt(10000)
);
expect(await getAnchorByPrincipal({ principal })).toBe(BigInt(10000));
})
);

Expand All @@ -234,37 +267,47 @@ test(
"old principals are dropped",
withStorage(async () => {
const userNumber = BigInt(10000);
const principal = Principal.fromText("2vxsx-fae");
const oldOrigin = "https://old.com";
const veryOldOrigin = "https://very.old.com";
const principal = Principal.fromText(
"hawxh-fq2bo-p5sh7-mmgol-l3vtr-f72w2-q335t-dcbni-2n25p-xhusp-fqe"
);
const oldPrincipal = Principal.fromText(
"lrf2i-zba54-pygwt-tbi75-zvlz4-7gfhh-ylcrq-2zh73-6brgn-45jy5-cae"
);
const veryOldPrincipal = Principal.fromText(
"t6s4t-dzahw-w2cqi-csimw-vn4xf-fjte4-doj3g-gyjxt-6v2uk-cjeft-eae"
);
const origin = "https://origin.com";
vi.useFakeTimers().setSystemTime(new Date(0));
await setKnownPrincipal({ userNumber, principal, origin: veryOldOrigin });
await setKnownPrincipal({
userNumber,
principal: veryOldPrincipal,
origin,
});
vi.useFakeTimers().setSystemTime(new Date(1));
await setKnownPrincipal({ userNumber, principal, origin: oldOrigin });
await setKnownPrincipal({ userNumber, principal: oldPrincipal, origin });
let date = 2;
vi.useFakeTimers().setSystemTime(new Date(date));
for (let i = 0; i < MAX_SAVED_PRINCIPALS; i++) {
date++;
vi.useFakeTimers().setSystemTime(new Date(date));
await setKnownPrincipal({
userNumber,
principal,
principal: Principal.fromUint8Array(
window.crypto.getRandomValues(new Uint8Array(29))
),
origin: `https://new${i}.com`,
});
}
date++;
vi.useFakeTimers().setSystemTime(new Date(date));
const newOrigin = "https://new.com";
await setKnownPrincipal({ userNumber, principal, origin: newOrigin });
await setKnownPrincipal({ userNumber, principal, origin });
expect(
await getAnchorByPrincipal({ principal, origin: veryOldOrigin })
await getAnchorByPrincipal({ principal: veryOldPrincipal })
).not.toBeDefined();
expect(
await getAnchorByPrincipal({ principal, origin: oldOrigin })
await getAnchorByPrincipal({ principal: oldPrincipal })
).not.toBeDefined();
expect(
await getAnchorByPrincipal({ principal, origin: newOrigin })
).toBeDefined();
expect(await getAnchorByPrincipal({ principal })).toBeDefined();
vi.useRealTimers();
})
);
Expand Down
Loading

0 comments on commit 4bae280

Please sign in to comment.