[web] Use our standard crypto layer in accounts app (#2021)

This commit is contained in:
Manav Rathi
2024-06-05 20:28:11 +05:30
committed by GitHub
7 changed files with 57 additions and 40 deletions

View File

@@ -7,6 +7,7 @@ import {
import EnteButton from "@ente/shared/components/EnteButton";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import { fromB64URLSafeNoPadding } from "@ente/shared/crypto/internal/libsodium";
import HTTPService from "@ente/shared/network/HTTPService";
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
import InfoIcon from "@mui/icons-material/Info";
@@ -125,6 +126,7 @@ const PasskeysFlow = () => {
const encodedResponse = _sodium.to_base64(JSON.stringify(finishData));
// TODO-PK: Shouldn't this be URL encoded?
window.location.href = `${redirect}?response=${encodedResponse}`;
};
@@ -144,20 +146,16 @@ const PasskeysFlow = () => {
publicKey: any,
timeoutMillis: number = 60000, // Default timeout of 60 seconds
): Promise<Credential | null> => {
publicKey.challenge = _sodium.from_base64(
publicKey.challenge = await fromB64URLSafeNoPadding(
publicKey.challenge,
_sodium.base64_variants.URLSAFE_NO_PADDING,
);
publicKey.allowCredentials?.forEach(function (listItem: any) {
listItem.id = _sodium.from_base64(
listItem.id,
_sodium.base64_variants.URLSAFE_NO_PADDING,
);
for (const listItem of publicKey.allowCredentials ?? []) {
listItem.id = await fromB64URLSafeNoPadding(listItem.id);
// note: we are orverwriting the transports array with all possible values.
// This is because the browser will only prompt the user for the transport that is available.
// Warning: In case of invalid transport value, the webauthn will fail on Safari & iOS browsers
listItem.transports = ["usb", "nfc", "ble", "internal"];
});
}
publicKey.timeout = timeoutMillis;
const publicKeyCredentialCreationOptions: CredentialRequestOptions = {
publicKey: publicKey,

View File

@@ -101,6 +101,7 @@ const Passkeys = () => {
const options = response.options;
// TODO-PK: The types don't match.
options.publicKey.challenge = _sodium.from_base64(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@@ -1,8 +1,8 @@
import log from "@/next/log";
import { toB64URLSafeNoPadding } from "@ente/shared/crypto/internal/libsodium";
import HTTPService from "@ente/shared/network/HTTPService";
import { getEndpoint } from "@ente/shared/network/api";
import { getToken } from "@ente/shared/storage/localStorage/helpers";
import _sodium from "libsodium-wrappers";
const ENDPOINT = getEndpoint();
@@ -87,17 +87,15 @@ export const finishPasskeyRegistration = async (
sessionId: string,
) => {
try {
const attestationObjectB64 = _sodium.to_base64(
const attestationObjectB64 = await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.attestationObject),
_sodium.base64_variants.URLSAFE_NO_PADDING,
);
const clientDataJSONB64 = _sodium.to_base64(
const clientDataJSONB64 = await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.clientDataJSON),
_sodium.base64_variants.URLSAFE_NO_PADDING,
);
const token = getToken();
@@ -168,29 +166,25 @@ export const finishPasskeyAuthentication = async (
rawId: credential.id,
type: credential.type,
response: {
authenticatorData: _sodium.to_base64(
authenticatorData: await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.authenticatorData),
_sodium.base64_variants.URLSAFE_NO_PADDING,
),
clientDataJSON: _sodium.to_base64(
clientDataJSON: await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.clientDataJSON),
_sodium.base64_variants.URLSAFE_NO_PADDING,
),
signature: _sodium.to_base64(
signature: await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.signature),
_sodium.base64_variants.URLSAFE_NO_PADDING,
),
userHandle: _sodium.to_base64(
userHandle: await toB64URLSafeNoPadding(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new Uint8Array(credential.response.userHandle),
_sodium.base64_variants.URLSAFE_NO_PADDING,
),
},
},

View File

@@ -1,8 +1,10 @@
import log from "@/next/log";
import { wait } from "@/utils/promise";
import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium";
import {
boxSealOpen,
generateKeyPair,
} from "@ente/shared/crypto/internal/libsodium";
import castGateway from "@ente/shared/network/cast";
import _sodium from "libsodium-wrappers";
export interface Registration {
/** A pairing code shown on the screen. A client can use this to connect. */
@@ -75,9 +77,8 @@ export interface Registration {
*/
export const register = async (): Promise<Registration> => {
// Generate keypair.
const keypair = await generateKeyPair();
const publicKeyB64 = await toB64(keypair.publicKey);
const privateKeyB64 = await toB64(keypair.privateKey);
const { publicKey: publicKeyB64, privateKey: privateKeyB64 } =
await generateKeyPair();
// Register keypair with museum to get a pairing code.
let pairingCode: string;
@@ -127,8 +128,3 @@ export const getCastData = async (registration: Registration) => {
return JSON.parse(atob(decryptedCastData));
};
const generateKeyPair = async () => {
await _sodium.ready;
return _sodium.crypto_box_keypair();
};

View File

@@ -34,7 +34,7 @@ export async function decryptAndStoreToken(
const decryptedTokenBytes = await cryptoWorker.fromB64(
urlUnsafeB64DecryptedToken,
);
decryptedToken = await cryptoWorker.toURLSafeB64(decryptedTokenBytes);
decryptedToken = await cryptoWorker.toB64URLSafe(decryptedTokenBytes);
setData(LS_KEYS.USER, {
...user,
token: decryptedToken,

View File

@@ -195,8 +195,8 @@ export class DedicatedCryptoWorker {
return libsodium.toB64(data);
}
async toURLSafeB64(data: Uint8Array) {
return libsodium.toURLSafeB64(data);
async toB64URLSafe(data: Uint8Array) {
return libsodium.toB64URLSafe(data);
}
async fromB64(string: string) {

View File

@@ -340,14 +340,18 @@ export async function generateSaltToDeriveKey() {
return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
}
export async function generateKeyPair() {
/**
* Generate a new public/private keypair, and return their Base64
* representations.
*/
export const generateKeyPair = async () => {
await sodium.ready;
const keyPair: sodium.KeyPair = sodium.crypto_box_keypair();
const keyPair = sodium.crypto_box_keypair();
return {
privateKey: await toB64(keyPair.privateKey),
publicKey: await toB64(keyPair.publicKey),
privateKey: await toB64(keyPair.privateKey),
};
}
};
export async function boxSealOpen(
input: string,
@@ -398,10 +402,34 @@ export async function toB64(input: Uint8Array) {
return sodium.to_base64(input, sodium.base64_variants.ORIGINAL);
}
export async function toURLSafeB64(input: Uint8Array) {
/** Convert a {@link Uint8Array} to a URL safe Base64 encoded string. */
export const toB64URLSafe = async (input: Uint8Array) => {
await sodium.ready;
return sodium.to_base64(input, sodium.base64_variants.URLSAFE);
}
};
/**
* Convert a {@link Uint8Array} to a URL safe Base64 encoded string.
*
* This differs from {@link toB64URLSafe} in that it does not append any
* trailing padding character(s) "=" to make the resultant string's length be an
* integer multiple of 4.
*/
export const toB64URLSafeNoPadding = async (input: Uint8Array) => {
await sodium.ready;
return sodium.to_base64(input, sodium.base64_variants.URLSAFE_NO_PADDING);
};
/**
* Convert a Base64 encoded string to a {@link Uint8Array}.
*
* This is the converse of {@link toB64URLSafeNoPadding}, and does not expect
* its input string's length to be a an integer multiple of 4.
*/
export const fromB64URLSafeNoPadding = async (input: string) => {
await sodium.ready;
return sodium.from_base64(input, sodium.base64_variants.URLSAFE_NO_PADDING);
};
export async function fromUTF8(input: string) {
await sodium.ready;