aboutsummaryrefslogtreecommitdiff
path: root/packages/exchange-backoffice-ui/src/account.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/exchange-backoffice-ui/src/account.ts')
-rw-r--r--packages/exchange-backoffice-ui/src/account.ts227
1 files changed, 34 insertions, 193 deletions
diff --git a/packages/exchange-backoffice-ui/src/account.ts b/packages/exchange-backoffice-ui/src/account.ts
index 6c3766940..05f0f8984 100644
--- a/packages/exchange-backoffice-ui/src/account.ts
+++ b/packages/exchange-backoffice-ui/src/account.ts
@@ -2,28 +2,17 @@ import {
bytesToString,
createEddsaKeyPair,
decodeCrock,
+ decryptWithDerivedKey,
+ eddsaGetPublic,
encodeCrock,
encryptWithDerivedKey,
getRandomBytesF,
stringToBytes,
} from "@gnu-taler/taler-util";
-/**
- * Create a new session id from which it will
- * be derive the crypto parameters from
- * securing the private key
- *
- * @returns session id as string
- */
-export function createSalt(): string {
- const salt = crypto.getRandomValues(new Uint8Array(8));
- const iv = crypto.getRandomValues(new Uint8Array(12));
- return encodeCrock(salt.buffer) + "-" + encodeCrock(iv.buffer);
-}
-
export interface Account {
- accountId: string;
- secret: CryptoKey;
+ accountId: AccountId;
+ signingKey: SigningKey;
}
/**
@@ -35,25 +24,36 @@ export interface Account {
* @returns
*/
export async function unlockAccount(
- salt: string,
- key: string,
+ account: LockedAccount,
password: string,
): Promise<Account> {
- const rawKey = str2ab(window.atob(key));
+ const rawKey = decodeCrock(account);
+ const rawPassword = stringToBytes(password);
- const privateKey = await recoverWithPassword(rawKey, salt, password);
+ const signingKey = (await decryptWithDerivedKey(
+ rawKey,
+ rawPassword,
+ password,
+ ).catch((e: Error) => {
+ throw new UnwrapKeyError(e.message);
+ })) as SigningKey;
- const publicKey = await getPublicFromPrivate(privateKey);
+ const publicKey = eddsaGetPublic(signingKey);
- const pubRaw = await crypto.subtle.exportKey("spki", publicKey).catch((e) => {
- throw new Error(String(e));
- });
+ const accountId = encodeCrock(publicKey) as AccountId;
- const accountId = btoa(ab2str(pubRaw));
-
- return { accountId, secret: privateKey };
+ return { accountId, signingKey };
}
+declare const opaque_Account: unique symbol;
+export type LockedAccount = string & { [opaque_Account]: true };
+
+declare const opaque_AccountId: unique symbol;
+export type AccountId = string & { [opaque_AccountId]: true };
+
+declare const opaque_SigningKey: unique symbol;
+export type SigningKey = Uint8Array & { [opaque_SigningKey]: true };
+
/**
* Create new account (secured private key) under session
* secured with the given password
@@ -62,9 +62,10 @@ export async function unlockAccount(
* @param password
* @returns
*/
-export async function createNewAccount(password: string) {
+export async function createNewAccount(
+ password: string,
+): Promise<LockedAccount> {
const { eddsaPriv } = createEddsaKeyPair();
- const salt = createSalt();
const key = stringToBytes(password);
@@ -72,178 +73,18 @@ export async function createNewAccount(password: string) {
getRandomBytesF(24),
key,
eddsaPriv,
- salt,
+ password,
);
- const protectedPriv = bytesToString(protectedPrivKey);
-
- return { accountId: protectedPriv, salt };
-}
-
-const rsaAlgorithm: RsaHashedKeyGenParams = {
- name: "RSA-OAEP",
- modulusLength: 2048,
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
- hash: "SHA-256",
-};
-
-async function createPair(): Promise<CryptoKeyPair> {
- const key = await crypto.subtle
- .generateKey(rsaAlgorithm, true, ["encrypt", "decrypt"])
- .catch((e) => {
- throw new Error(String(e));
- });
- return key;
-}
-
-const textEncoder = new TextEncoder();
-
-async function protectWithPassword(
- privateKey: CryptoKey,
- sessionId: string,
- password: string,
-): Promise<ArrayBuffer> {
- const { salt, initVector: iv } = getCryptoParameters(sessionId);
- const passwordAsKey = await crypto.subtle
- .importKey("raw", textEncoder.encode(password), { name: "PBKDF2" }, false, [
- "deriveBits",
- "deriveKey",
- ])
- .catch((e) => {
- throw new Error(String(e));
- });
- const wrappingKey = await crypto.subtle
- .deriveKey(
- {
- name: "PBKDF2",
- salt,
- iterations: 100000,
- hash: "SHA-256",
- },
- passwordAsKey,
- { name: "AES-GCM", length: 256 },
- true,
- ["wrapKey", "unwrapKey"],
- )
- .catch((e) => {
- throw new Error(String(e));
- });
-
- const protectedPrivKey = await crypto.subtle
- .wrapKey("pkcs8", privateKey, wrappingKey, {
- name: "AES-GCM",
- iv,
- })
- .catch((e) => {
- throw new Error(String(e));
- });
- return protectedPrivKey;
-}
-
-async function recoverWithPassword(
- value: ArrayBuffer,
- sessionId: string,
- password: string,
-): Promise<CryptoKey> {
- const { salt, initVector: iv } = getCryptoParameters(sessionId);
-
- const master = await crypto.subtle
- .importKey("raw", textEncoder.encode(password), { name: "PBKDF2" }, false, [
- "deriveBits",
- "deriveKey",
- ])
- .catch((e) => {
- throw new UnwrapKeyError("starting", String(e));
- });
+ const protectedPriv = encodeCrock(protectedPrivKey);
- const unwrappingKey = await crypto.subtle
- .deriveKey(
- {
- name: "PBKDF2",
- salt,
- iterations: 100000,
- hash: "SHA-256",
- },
- master,
- { name: "AES-GCM", length: 256 },
- true,
- ["wrapKey", "unwrapKey"],
- )
- .catch((e) => {
- throw new UnwrapKeyError("deriving", String(e));
- });
-
- const privKey = await crypto.subtle
- .unwrapKey(
- "pkcs8",
- value,
- unwrappingKey,
- {
- name: "AES-GCM",
- iv,
- },
- rsaAlgorithm,
- true,
- ["decrypt"],
- )
- .catch((e) => {
- throw new UnwrapKeyError("unwrapping", String(e));
- });
- return privKey;
+ return protectedPriv as LockedAccount;
}
-type Steps = "starting" | "deriving" | "unwrapping";
export class UnwrapKeyError extends Error {
- public step: Steps;
public cause: string;
- constructor(step: Steps, cause: string) {
- super(`Recovering private key failed on "${step}": ${cause}`);
- this.step = step;
+ constructor(cause: string) {
+ super(`Recovering private key failed on: ${cause}`);
this.cause = cause;
}
}
-
-/**
- * Looks like there is no easy way to do it with the Web Crypto API
- */
-async function getPublicFromPrivate(key: CryptoKey): Promise<CryptoKey> {
- const jwk = await crypto.subtle.exportKey("jwk", key).catch((e) => {
- throw new Error(String(e));
- });
-
- delete jwk.d;
- delete jwk.dp;
- delete jwk.dq;
- delete jwk.q;
- delete jwk.qi;
- jwk.key_ops = ["encrypt"];
-
- return crypto.subtle
- .importKey("jwk", jwk, rsaAlgorithm, true, ["encrypt"])
- .catch((e) => {
- throw new Error(String(e));
- });
-}
-
-function ab2str(buf: ArrayBuffer) {
- return String.fromCharCode.apply(null, Array.from(new Uint8Array(buf)));
-}
-function str2ab(str: string) {
- const buf = new ArrayBuffer(str.length);
- const bufView = new Uint8Array(buf);
- for (let i = 0, strLen = str.length; i < strLen; i++) {
- bufView[i] = str.charCodeAt(i);
- }
- return buf;
-}
-
-function getCryptoParameters(sessionId: string): {
- salt: Uint8Array;
- initVector: Uint8Array;
-} {
- const [saltId, vectorId] = sessionId.split("-");
- return {
- salt: decodeCrock(saltId),
- initVector: decodeCrock(vectorId),
- };
-}