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 useEventPublisher from "Hooks/useEventPublisher";
|
||||||
import { LoginStore, createPublisher, sessionNeedsPin } from "Login";
|
import { LoginStore, createPublisher, sessionNeedsPin } from "Login";
|
||||||
import { unwrap } from "@snort/shared";
|
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 { DefaultPowWorker } from "index";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
|
import Spinner from "Icons/Spinner";
|
||||||
|
|
||||||
const PinLen = 6;
|
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 [pin, setPin] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@ -32,12 +33,14 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
|
|||||||
}, [pin]);
|
}, [pin]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError("");
|
if (pin.length > 0) {
|
||||||
|
setError("");
|
||||||
|
}
|
||||||
|
|
||||||
if (pin.length === PinLen) {
|
if (pin.length === PinLen) {
|
||||||
try {
|
onResult(pin).catch(e => {
|
||||||
onResult(pin);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
setPin("");
|
||||||
if (e instanceof InvalidPinError) {
|
if (e instanceof InvalidPinError) {
|
||||||
setError(formatMessage({
|
setError(formatMessage({
|
||||||
defaultMessage: "Incorrect pin"
|
defaultMessage: "Incorrect pin"
|
||||||
@ -45,15 +48,19 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
|
|||||||
} else if (e instanceof Error) {
|
} else if (e instanceof Error) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}, [pin]);
|
}, [pin]);
|
||||||
|
|
||||||
const boxes = [];
|
const boxes = [];
|
||||||
for (let x = 0; x < PinLen; x++) {
|
if (pin.length === PinLen) {
|
||||||
boxes.push(<div className="pin-box flex f-center f-1">
|
boxes.push(<Spinner className="flex f-center f-1" />);
|
||||||
{pin[x]}
|
} else {
|
||||||
</div>)
|
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()}>
|
return <Modal id="pin" onClose={() => onCancel()}>
|
||||||
<div className="flex-column g12">
|
<div className="flex-column g12">
|
||||||
@ -78,9 +85,9 @@ export function LoginUnlock() {
|
|||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
|
|
||||||
function encryptMigration(pin: string) {
|
async function encryptMigration(pin: string) {
|
||||||
const k = unwrap(login.privateKey);
|
const k = unwrap(login.privateKey);
|
||||||
const newPin = PinEncrypted.create(k, pin);
|
const newPin = await PinEncrypted.create(k, pin);
|
||||||
|
|
||||||
const pub = EventPublisher.privateKey(k);
|
const pub = EventPublisher.privateKey(k);
|
||||||
if (login.preferences.pow) {
|
if (login.preferences.pow) {
|
||||||
@ -89,13 +96,15 @@ export function LoginUnlock() {
|
|||||||
LoginStore.setPublisher(login.id, pub);
|
LoginStore.setPublisher(login.id, pub);
|
||||||
LoginStore.updateSession({
|
LoginStore.updateSession({
|
||||||
...login,
|
...login,
|
||||||
privateKeyData: newPin.toPayload(),
|
privateKeyData: newPin,
|
||||||
privateKey: undefined
|
privateKey: undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlockSession(pin: string) {
|
async function unlockSession(pin: string) {
|
||||||
const pub = createPublisher(login, pin);
|
const key = new PinEncrypted(unwrap(login.privateKeyData) as PinEncryptedPayload);
|
||||||
|
await key.decrypt(pin);
|
||||||
|
const pub = createPublisher(login, key);
|
||||||
if (pub) {
|
if (pub) {
|
||||||
if (login.preferences.pow) {
|
if (login.preferences.pow) {
|
||||||
pub.pow(login.preferences.pow, DefaultPowWorker);
|
pub.pow(login.preferences.pow, DefaultPowWorker);
|
||||||
|
@ -26,7 +26,7 @@ export default function useLoginHandler() {
|
|||||||
const hexKey = bech32ToHex(key);
|
const hexKey = bech32ToHex(key);
|
||||||
if (hexKey.length === 64) {
|
if (hexKey.length === 64) {
|
||||||
if (!pin) throw new PinRequiredError();
|
if (!pin) throw new PinRequiredError();
|
||||||
LoginStore.loginWithPrivateKey(PinEncrypted.create(hexKey, pin));
|
LoginStore.loginWithPrivateKey(await PinEncrypted.create(hexKey, pin));
|
||||||
} else {
|
} else {
|
||||||
throw new Error("INVALID PRIVATE KEY");
|
throw new Error("INVALID PRIVATE KEY");
|
||||||
}
|
}
|
||||||
@ -37,13 +37,13 @@ export default function useLoginHandler() {
|
|||||||
if (!pin) throw new PinRequiredError();
|
if (!pin) throw new PinRequiredError();
|
||||||
const ent = generateBip39Entropy(key);
|
const ent = generateBip39Entropy(key);
|
||||||
const keyHex = entropyToPrivateKey(ent);
|
const keyHex = entropyToPrivateKey(ent);
|
||||||
LoginStore.loginWithPrivateKey(PinEncrypted.create(keyHex, pin));
|
LoginStore.loginWithPrivateKey(await PinEncrypted.create(keyHex, pin));
|
||||||
} else if (key.length === 64) {
|
} else if (key.length === 64) {
|
||||||
if (!hasSubtleCrypto) {
|
if (!hasSubtleCrypto) {
|
||||||
throw new Error(insecureMsg);
|
throw new Error(insecureMsg);
|
||||||
}
|
}
|
||||||
if (!pin) throw new PinRequiredError();
|
if (!pin) throw new PinRequiredError();
|
||||||
LoginStore.loginWithPrivateKey(PinEncrypted.create(key, pin));
|
LoginStore.loginWithPrivateKey(await PinEncrypted.create(key, pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
// public key logins
|
// public key logins
|
||||||
|
@ -90,8 +90,7 @@ export async function generateNewLogin(pin: string) {
|
|||||||
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
|
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
|
||||||
System.BroadcastEvent(ev);
|
System.BroadcastEvent(ev);
|
||||||
|
|
||||||
const key = PinEncrypted.create(privateKey, pin);
|
const key = await PinEncrypted.create(privateKey, pin);
|
||||||
key.decrypt(pin);
|
|
||||||
LoginStore.loginWithPrivateKey(key, entropy, newRelays);
|
LoginStore.loginWithPrivateKey(key, entropy, newRelays);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,23 +169,19 @@ export function sessionNeedsPin(l: LoginSession) {
|
|||||||
return l.type === LoginSessionType.PrivateKey || l.type === LoginSessionType.Nip46;
|
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) {
|
switch (l.type) {
|
||||||
case LoginSessionType.PrivateKey: {
|
case LoginSessionType.PrivateKey: {
|
||||||
if(!pin) throw new PinRequiredError();
|
if(!pin) throw new PinRequiredError();
|
||||||
const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData));
|
l.privateKeyData = pin;
|
||||||
v.decrypt(pin);
|
return EventPublisher.privateKey(pin.value);
|
||||||
l.privateKeyData = v;
|
|
||||||
return EventPublisher.privateKey(v.value);
|
|
||||||
}
|
}
|
||||||
case LoginSessionType.Nip46: {
|
case LoginSessionType.Nip46: {
|
||||||
if(!pin) throw new PinRequiredError();
|
if(!pin) throw new PinRequiredError();
|
||||||
const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData));
|
l.privateKeyData = pin;
|
||||||
v.decrypt(pin);
|
|
||||||
l.privateKeyData = v;
|
|
||||||
|
|
||||||
const relayArgs = (l.remoteSignerRelays ?? []).map(a => `relay=${encodeURIComponent(a)}`);
|
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);
|
const nip46 = new Nip46Signer(`bunker://${unwrap(l.publicKey)}?${[...relayArgs].join("&")}`, inner);
|
||||||
return new EventPublisher(nip46, unwrap(l.publicKey));
|
return new EventPublisher(nip46, unwrap(l.publicKey));
|
||||||
}
|
}
|
||||||
|
@ -300,11 +300,13 @@ export default function LoginPage() {
|
|||||||
<AsyncButton onClick={() => setPin(true)}>
|
<AsyncButton onClick={() => setPin(true)}>
|
||||||
<FormattedMessage defaultMessage="Create Account" />
|
<FormattedMessage defaultMessage="Create Account" />
|
||||||
</AsyncButton>
|
</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) {
|
if (key) {
|
||||||
doLogin(pin);
|
await doLogin(pin);
|
||||||
} else {
|
} else {
|
||||||
makeRandomKey(pin);
|
await makeRandomKey(pin);
|
||||||
}
|
}
|
||||||
}} onCancel={() => setPin(false)} />}
|
}} onCancel={() => setPin(false)} />}
|
||||||
{altLogins()}
|
{altLogins()}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { scrypt } from "@noble/hashes/scrypt";
|
import { scryptAsync } from "@noble/hashes/scrypt";
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { hmac } from "@noble/hashes/hmac";
|
import { hmac } from "@noble/hashes/hmac";
|
||||||
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
|
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
|
||||||
@ -28,8 +28,8 @@ export class PinEncrypted {
|
|||||||
return bytesToHex(this.#decrypted);
|
return bytesToHex(this.#decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypt(pin: string) {
|
async decrypt(pin: string) {
|
||||||
const key = scrypt(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts);
|
const key = await scryptAsync(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts);
|
||||||
const ciphertext = base64.decode(this.#encrypted.ciphertext);
|
const ciphertext = base64.decode(this.#encrypted.ciphertext);
|
||||||
const nonce = base64.decode(this.#encrypted.iv);
|
const nonce = base64.decode(this.#encrypted.iv);
|
||||||
const plaintext = xchacha20(key, nonce, ciphertext, new Uint8Array(32));
|
const plaintext = xchacha20(key, nonce, ciphertext, new Uint8Array(32));
|
||||||
@ -43,11 +43,11 @@ export class PinEncrypted {
|
|||||||
return this.#encrypted;
|
return this.#encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(content: string, pin: string) {
|
static async create(content: string, pin: string) {
|
||||||
const salt = randomBytes(24);
|
const salt = randomBytes(24);
|
||||||
const nonce = randomBytes(24);
|
const nonce = randomBytes(24);
|
||||||
const plaintext = hexToBytes(content);
|
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 mac = base64.encode(hmac(sha256, key, plaintext));
|
||||||
const ciphertext = xchacha20(key, nonce, plaintext, new Uint8Array(32));
|
const ciphertext = xchacha20(key, nonce, plaintext, new Uint8Array(32));
|
||||||
const ret = new PinEncrypted({
|
const ret = new PinEncrypted({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user