diff --git a/package.json b/package.json index d499514d..06308103 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@formkit/auto-animate": "^0.7.0", "@headlessui/react": "^1.5.0", "@movie-web/providers": "^1.0.4", + "@noble/hashes": "^1.3.2", "@react-spring/web": "^9.7.1", "@scure/bip39": "^1.2.1", "@sozialhelden/ietf-language-tags": "^5.4.2", @@ -33,7 +34,6 @@ "react-use": "^17.4.0", "slugify": "^1.6.6", "subsrt-ts": "^2.1.1", - "universal-base64url": "^1.1.0", "unzipit": "^1.4.3", "zustand": "^4.3.9" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94e4a7eb..e84a4c83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: '@movie-web/providers': specifier: ^1.0.4 version: 1.0.4 + '@noble/hashes': + specifier: ^1.3.2 + version: 1.3.2 '@react-spring/web': specifier: ^9.7.1 version: 9.7.3(react-dom@17.0.2)(react@17.0.2) @@ -98,9 +101,6 @@ dependencies: subsrt-ts: specifier: ^2.1.1 version: 2.1.1 - universal-base64url: - specifier: ^1.1.0 - version: 1.1.0 unzipit: specifier: ^1.4.3 version: 1.4.3 @@ -6150,16 +6150,6 @@ packages: crypto-random-string: 2.0.0 dev: true - /universal-base64@2.1.0: - resolution: {integrity: sha512-WeOkACVnIXJZr/qlv7++Rl1zuZOHN96v2yS5oleUuv8eJOs5j9M5U3xQEIoWqn1OzIuIcgw0fswxWnUVGDfW6g==} - dev: false - - /universal-base64url@1.1.0: - resolution: {integrity: sha512-qWv2+8KCaAWdpqqXwU8W0Yj9pflYDXP37/a3kec6Y4Je7bYzgIfxEVRjZWeLR67be7iot1lGCy5Nuo+xB0fojA==} - dependencies: - universal-base64: 2.1.0 - dev: false - /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} diff --git a/src/backend/accounts/crypto.ts b/src/backend/accounts/crypto.ts index 8ab25163..31642199 100644 --- a/src/backend/accounts/crypto.ts +++ b/src/backend/accounts/crypto.ts @@ -1,13 +1,14 @@ +import { pbkdf2Async } from "@noble/hashes/pbkdf2"; +import { sha256 } from "@noble/hashes/sha256"; import { generateMnemonic } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english"; import forge from "node-forge"; -import { encode } from "universal-base64url"; async function seedFromMnemonic(mnemonic: string) { - const md = forge.md.sha256.create(); - md.update(mnemonic); - // TODO this is probably not correct - return md.digest().toHex(); + return pbkdf2Async(sha256, mnemonic, "mnemonic", { + c: 2048, + dkLen: 32, + }); } export async function keysFromMenmonic(mnemonic: string) { @@ -28,13 +29,19 @@ export function genMnemonic(): string { } export async function signCode( - _code: string, - _privateKey: forge.pki.ed25519.NativeBuffer -): Promise { - // TODO add real signature - return new Uint8Array(); + code: string, + privateKey: forge.pki.ed25519.NativeBuffer +): Promise { + return forge.pki.ed25519.sign({ + encoding: "utf8", + message: code, + privateKey, + }); } export function bytesToBase64Url(bytes: Uint8Array): string { - return encode(String.fromCodePoint(...bytes)); + return btoa(String.fromCodePoint(...bytes)) + .replace(/\//g, "_") + .replace(/\+/g, "-") + .replace(/=+$/, ""); } diff --git a/src/backend/accounts/register.ts b/src/backend/accounts/register.ts index 93f2794d..507d4cf9 100644 --- a/src/backend/accounts/register.ts +++ b/src/backend/accounts/register.ts @@ -1,7 +1,11 @@ import { ofetch } from "ofetch"; import { SessionResponse, UserResponse } from "@/backend/accounts/auth"; -import { keysFromMenmonic, signCode } from "@/backend/accounts/crypto"; +import { + bytesToBase64Url, + keysFromMenmonic as keysFromMnemonic, + signCode, +} from "@/backend/accounts/crypto"; export interface ChallengeTokenResponse { challenge: string; @@ -55,10 +59,10 @@ export async function registerAccount( } export async function signChallenge(mnemonic: string, challengeCode: string) { - const keys = await keysFromMenmonic(mnemonic); + const keys = await keysFromMnemonic(mnemonic); const signature = await signCode(challengeCode, keys.privateKey); return { - publicKey: keys.publicKey, - signature, + publicKey: bytesToBase64Url(keys.publicKey), + signature: bytesToBase64Url(signature), }; } diff --git a/src/pages/parts/auth/VerifyPassphrasePart.tsx b/src/pages/parts/auth/VerifyPassphrasePart.tsx index 9b5b2222..8d7f5698 100644 --- a/src/pages/parts/auth/VerifyPassphrasePart.tsx +++ b/src/pages/parts/auth/VerifyPassphrasePart.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useAsyncFn } from "react-use"; -import { bytesToBase64Url } from "@/backend/accounts/crypto"; import { getRegisterChallengeToken, registerAccount, @@ -37,9 +36,9 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) { const registerResult = await registerAccount(url, { challenge: { code: challenge, - signature: bytesToBase64Url(keys.signature), + signature: keys.signature, }, - publicKey: bytesToBase64Url(keys.publicKey), + publicKey: keys.publicKey, device: props.profile.device, profile: props.profile.profile, });