Skip to content

Commit

Permalink
Use fixed length for principal digest (#2126)
Browse files Browse the repository at this point in the history
* Use fixed length for principal digest

This updates the way we compute principals to encode the length of the
origins & principals in a fixed-length manner. This avoids a whole class
of collisions that could be crafted intentionally when using the string
representation of the numbers.

* Update src/frontend/src/storage/index.ts

* Add length limit to RP front-end origins

---------

Co-authored-by: Frederik Rothenberger <frederik.rothenberger@dfinity.org>
  • Loading branch information
nmattia and Frederik Rothenberger authored Dec 11, 2023
1 parent e154889 commit dee34c9
Showing 1 changed file with 39 additions and 7 deletions.
46 changes: 39 additions & 7 deletions src/frontend/src/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,27 +274,59 @@ const nowMillis = (): number => {
* possibility of collisions.
*/
const computePrincipalDigest = async ({
origin,
principal: principal_,
origin: origin_,
principal: principalObj,
hasher,
}: {
origin: string;
principal: Principal;
hasher: CryptoKey;
}): Promise<string> => {
// Create a buffer with origin & principal
if (origin_.length > 255) {
// Origins must not be longer than 255 bytes according to RFC1035.
// See also here: https://stackoverflow.com/questions/32290167/what-is-the-maximum-length-of-a-dns-name
// Given that by these limitations above it should not be possible to exceed the limit, we simply throw
// to avoid the associated security issue.
// Note: this is consistent with the backend, that also does not allow origins longer than 255 bytes
// -> there cannot exist an RP for which the VC flow would be meaningful that has an origin longer than 255 bytes
// since sign-in would not work in the first place.
throw new Error("origin too long");
}
// Encode each element & size
const enc = new TextEncoder();
const principal = principal_.toText();
const buff = enc.encode(
origin + origin.length.toString() + principal + principal.length.toString()
);
const principal_ = principalObj.toText();

const origin = enc.encode(origin_);
const originLen = Uint8Array.from([origin_.length]);

const principal = enc.encode(principal_);
const principalLen = Uint8Array.from([principal_.length]);

// Create a buffer with all four elements
const buff = concatUint8Arrays([origin, originLen, principal, principalLen]);

// Create the digest
const digestBytes = await crypto.subtle.sign("HMAC", hasher, buff);
const digest = arrayBufferToBase64(digestBytes);
return digest;
};

// Concat some byte arrays
const concatUint8Arrays = (buffers: Uint8Array[]): Uint8Array => {
// Create a new byte array of the total size
const totSize = buffers.reduce((acc, arr) => acc + arr.length, 0);
const final = new Uint8Array(totSize);

// For each of the arrays to concat: write it to the final array, then bump the write offset
let offset = 0;
buffers.forEach((arr) => {
final.set(arr, offset);
offset += arr.length;
});

return final;
};

/* Generate HMAC key */
const newHMACKey = async (): Promise<CryptoKey> => {
const key = await crypto.subtle.generateKey(
Expand Down

0 comments on commit dee34c9

Please sign in to comment.