scrypt async
This commit is contained in:
parent
3e0c4e5064
commit
8244441929
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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()}
|
||||
|
@ -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({
|
||||
|
Loading…
x
Reference in New Issue
Block a user