scrypt async

This commit is contained in:
Kieran 2023-09-21 22:00:06 +01:00
parent 3e0c4e5064
commit 8244441929
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 44 additions and 38 deletions

View File

@ -5,12 +5,13 @@ import { FormattedMessage, useIntl } from "react-intl";
import useEventPublisher from "Hooks/useEventPublisher";
import { LoginStore, createPublisher, sessionNeedsPin } from "Login";
import { unwrap } from "@snort/shared";
import { EventPublisher, InvalidPinError, PinEncrypted } from "@snort/system";
import { EventPublisher, InvalidPinError, PinEncrypted, PinEncryptedPayload } from "@snort/system";
import { DefaultPowWorker } from "index";
import Modal from "./Modal";
import Spinner from "Icons/Spinner";
const PinLen = 6;
export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: string) => void, onCancel: () => void, subTitle?: ReactNode }) {
export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: string) => Promise<void>, onCancel: () => void, subTitle?: ReactNode }) {
const [pin, setPin] = useState("");
const [error, setError] = useState("");
const { formatMessage } = useIntl();
@ -32,12 +33,14 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
}, [pin]);
useEffect(() => {
setError("");
if (pin.length > 0) {
setError("");
}
if (pin.length === PinLen) {
try {
onResult(pin);
} catch (e) {
onResult(pin).catch(e => {
console.error(e);
setPin("");
if (e instanceof InvalidPinError) {
setError(formatMessage({
defaultMessage: "Incorrect pin"
@ -45,15 +48,19 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
} else if (e instanceof Error) {
setError(e.message);
}
}
})
}
}, [pin]);
const boxes = [];
for (let x = 0; x < PinLen; x++) {
boxes.push(<div className="pin-box flex f-center f-1">
{pin[x]}
</div>)
if (pin.length === PinLen) {
boxes.push(<Spinner className="flex f-center f-1" />);
} else {
for (let x = 0; x < PinLen; x++) {
boxes.push(<div className="pin-box flex f-center f-1">
{pin[x]}
</div>)
}
}
return <Modal id="pin" onClose={() => onCancel()}>
<div className="flex-column g12">
@ -78,9 +85,9 @@ export function LoginUnlock() {
const login = useLogin();
const publisher = useEventPublisher();
function encryptMigration(pin: string) {
async function encryptMigration(pin: string) {
const k = unwrap(login.privateKey);
const newPin = PinEncrypted.create(k, pin);
const newPin = await PinEncrypted.create(k, pin);
const pub = EventPublisher.privateKey(k);
if (login.preferences.pow) {
@ -89,13 +96,15 @@ export function LoginUnlock() {
LoginStore.setPublisher(login.id, pub);
LoginStore.updateSession({
...login,
privateKeyData: newPin.toPayload(),
privateKeyData: newPin,
privateKey: undefined
});
}
function unlockSession(pin: string) {
const pub = createPublisher(login, pin);
async function unlockSession(pin: string) {
const key = new PinEncrypted(unwrap(login.privateKeyData) as PinEncryptedPayload);
await key.decrypt(pin);
const pub = createPublisher(login, key);
if (pub) {
if (login.preferences.pow) {
pub.pow(login.preferences.pow, DefaultPowWorker);

View File

@ -26,7 +26,7 @@ export default function useLoginHandler() {
const hexKey = bech32ToHex(key);
if (hexKey.length === 64) {
if (!pin) throw new PinRequiredError();
LoginStore.loginWithPrivateKey(PinEncrypted.create(hexKey, pin));
LoginStore.loginWithPrivateKey(await PinEncrypted.create(hexKey, pin));
} else {
throw new Error("INVALID PRIVATE KEY");
}
@ -37,13 +37,13 @@ export default function useLoginHandler() {
if (!pin) throw new PinRequiredError();
const ent = generateBip39Entropy(key);
const keyHex = entropyToPrivateKey(ent);
LoginStore.loginWithPrivateKey(PinEncrypted.create(keyHex, pin));
LoginStore.loginWithPrivateKey(await PinEncrypted.create(keyHex, pin));
} else if (key.length === 64) {
if (!hasSubtleCrypto) {
throw new Error(insecureMsg);
}
if (!pin) throw new PinRequiredError();
LoginStore.loginWithPrivateKey(PinEncrypted.create(key, pin));
LoginStore.loginWithPrivateKey(await PinEncrypted.create(key, pin));
}
// public key logins

View File

@ -90,8 +90,7 @@ export async function generateNewLogin(pin: string) {
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
System.BroadcastEvent(ev);
const key = PinEncrypted.create(privateKey, pin);
key.decrypt(pin);
const key = await PinEncrypted.create(privateKey, pin);
LoginStore.loginWithPrivateKey(key, entropy, newRelays);
}
@ -170,23 +169,19 @@ export function sessionNeedsPin(l: LoginSession) {
return l.type === LoginSessionType.PrivateKey || l.type === LoginSessionType.Nip46;
}
export function createPublisher(l: LoginSession, pin?: string) {
export function createPublisher(l: LoginSession, pin?: PinEncrypted) {
switch (l.type) {
case LoginSessionType.PrivateKey: {
if(!pin) throw new PinRequiredError();
const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData));
v.decrypt(pin);
l.privateKeyData = v;
return EventPublisher.privateKey(v.value);
l.privateKeyData = pin;
return EventPublisher.privateKey(pin.value);
}
case LoginSessionType.Nip46: {
if(!pin) throw new PinRequiredError();
const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData));
v.decrypt(pin);
l.privateKeyData = v;
l.privateKeyData = pin;
const relayArgs = (l.remoteSignerRelays ?? []).map(a => `relay=${encodeURIComponent(a)}`);
const inner = new PrivateKeySigner(v.value);
const inner = new PrivateKeySigner(pin.value);
const nip46 = new Nip46Signer(`bunker://${unwrap(l.publicKey)}?${[...relayArgs].join("&")}`, inner);
return new EventPublisher(nip46, unwrap(l.publicKey));
}

View File

@ -300,11 +300,13 @@ export default function LoginPage() {
<AsyncButton onClick={() => setPin(true)}>
<FormattedMessage defaultMessage="Create Account" />
</AsyncButton>
{pin && <PinPrompt onResult={pin => {
{pin && <PinPrompt subTitle={<p>
<FormattedMessage defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open Snort." />
</p>} onResult={async pin => {
if (key) {
doLogin(pin);
await doLogin(pin);
} else {
makeRandomKey(pin);
await makeRandomKey(pin);
}
}} onCancel={() => setPin(false)} />}
{altLogins()}

View File

@ -1,4 +1,4 @@
import { scrypt } from "@noble/hashes/scrypt";
import { scryptAsync } from "@noble/hashes/scrypt";
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from "@noble/hashes/hmac";
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
@ -28,8 +28,8 @@ export class PinEncrypted {
return bytesToHex(this.#decrypted);
}
decrypt(pin: string) {
const key = scrypt(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts);
async decrypt(pin: string) {
const key = await scryptAsync(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts);
const ciphertext = base64.decode(this.#encrypted.ciphertext);
const nonce = base64.decode(this.#encrypted.iv);
const plaintext = xchacha20(key, nonce, ciphertext, new Uint8Array(32));
@ -43,11 +43,11 @@ export class PinEncrypted {
return this.#encrypted;
}
static create(content: string, pin: string) {
static async create(content: string, pin: string) {
const salt = randomBytes(24);
const nonce = randomBytes(24);
const plaintext = hexToBytes(content);
const key = scrypt(pin, salt, PinEncrypted.#opts);
const key = await scryptAsync(pin, salt, PinEncrypted.#opts);
const mac = base64.encode(hmac(sha256, key, plaintext));
const ciphertext = xchacha20(key, nonce, plaintext, new Uint8Array(32));
const ret = new PinEncrypted({