gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (51d54fdd -> 6c5d32be)


From: gnunet
Subject: [taler-wallet-core] branch master updated (51d54fdd -> 6c5d32be)
Date: Tue, 19 Oct 2021 23:26:35 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository wallet-core.

    from 51d54fdd fixed missing styles
     new 5dc00893 anastasis-core: question hashing and policy expiration
     new 6c5d32be anastasis-core: compatible secret upload

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/anastasis-core/package.json               |   1 +
 packages/anastasis-core/src/crypto.test.ts         |   1 +
 packages/anastasis-core/src/crypto.ts              |  26 +++--
 packages/anastasis-core/src/index.ts               | 120 ++++++++++++++++-----
 packages/anastasis-core/src/reducer-types.ts       |  13 ++-
 .../src/hooks/use-anastasis-reducer.ts             |   2 +-
 .../src/pages/home/SecretEditorScreen.tsx          |  23 ++--
 pnpm-lock.yaml                                     |   2 +
 8 files changed, 135 insertions(+), 53 deletions(-)

diff --git a/packages/anastasis-core/package.json 
b/packages/anastasis-core/package.json
index f4b611ed..8dbef2d4 100644
--- a/packages/anastasis-core/package.json
+++ b/packages/anastasis-core/package.json
@@ -23,6 +23,7 @@
   "dependencies": {
     "@gnu-taler/taler-util": "workspace:^0.8.3",
     "fetch-ponyfill": "^7.1.0",
+    "fflate": "^0.6.0",
     "hash-wasm": "^4.9.0",
     "node-fetch": "^3.0.0"
   },
diff --git a/packages/anastasis-core/src/crypto.test.ts 
b/packages/anastasis-core/src/crypto.test.ts
index e535b7b2..1c255014 100644
--- a/packages/anastasis-core/src/crypto.test.ts
+++ b/packages/anastasis-core/src/crypto.test.ts
@@ -1,6 +1,7 @@
 import test from "ava";
 import {
   accountKeypairDerive,
+  encryptKeyshare,
   encryptTruth,
   policyKeyDerive,
   secureAnswerHash,
diff --git a/packages/anastasis-core/src/crypto.ts 
b/packages/anastasis-core/src/crypto.ts
index a5594288..63de795b 100644
--- a/packages/anastasis-core/src/crypto.ts
+++ b/packages/anastasis-core/src/crypto.ts
@@ -10,6 +10,7 @@ import {
   crypto_sign_keyPair_fromSeed,
   stringToBytes,
 } from "@gnu-taler/taler-util";
+import { gzipSync } from "fflate";
 import { argon2id } from "hash-wasm";
 
 export type Flavor<T, FlavorT extends string> = T & {
@@ -33,7 +34,10 @@ export type EddsaPublicKey = Flavor<string, 
"EddsaPublicKey">;
 export type EddsaPrivateKey = Flavor<string, "EddsaPrivateKey">;
 export type TruthUuid = Flavor<string, "TruthUuid">;
 export type SecureAnswerHash = Flavor<string, "SecureAnswerHash">;
-export type QuestionSalt = Flavor<string, "QuestionSalt">;
+/**
+ * Truth-specific randomness, also called question salt sometimes.
+ */
+export type TruthSalt = Flavor<string, "TruthSalt">;
 /**
  * Truth key, found in the recovery document.
  */
@@ -81,21 +85,25 @@ export function accountKeypairDerive(userId: 
UserIdentifier): AccountKeyPair {
   };
 }
 
+/**
+ * Encrypt the recovery document.
+ * 
+ * The caller should first compress the recovery doc.
+ */
 export async function encryptRecoveryDocument(
   userId: UserIdentifier,
-  recoveryDoc: any,
+  recoveryDocData: OpaqueData,
 ): Promise<OpaqueData> {
-  const plaintext = stringToBytes(JSON.stringify(recoveryDoc));
   const nonce = encodeCrock(getRandomBytes(nonceSize));
   return anastasisEncrypt(
     nonce,
     asOpaque(userId),
-    encodeCrock(plaintext),
+    recoveryDocData,
     "erd",
   );
 }
 
-function taConcat(chunks: Uint8Array[]): Uint8Array {
+export function typedArrayConcat(chunks: Uint8Array[]): Uint8Array {
   let payloadLen = 0;
   for (const c of chunks) {
     payloadLen += c.byteLength;
@@ -117,7 +125,7 @@ export async function policyKeyDerive(
   const chunks = keyShares.map((x) => decodeCrock(x));
   const polKey = kdfKw({
     outputLength: 64,
-    ikm: taConcat(chunks),
+    ikm: typedArrayConcat(chunks),
     salt: decodeCrock(policySalt),
     info: stringToBytes("anastasis-policy-key-derive"),
   });
@@ -147,10 +155,10 @@ async function anastasisEncrypt(
   const key = await deriveKey(keySeed, nonce, salt);
   const nonceBuf = decodeCrock(nonce);
   const cipherText = secretbox(decodeCrock(plaintext), decodeCrock(nonce), 
key);
-  return encodeCrock(taConcat([nonceBuf, cipherText]));
+  return encodeCrock(typedArrayConcat([nonceBuf, cipherText]));
 }
 
-const asOpaque = (x: string): OpaqueData => x;
+export const asOpaque = (x: string): OpaqueData => x;
 const asEncryptedKeyShare = (x: OpaqueData): EncryptedKeyShare => x as string;
 const asEncryptedTruth = (x: OpaqueData): EncryptedTruth => x as string;
 
@@ -216,7 +224,7 @@ export async function coreSecretEncrypt(
 export async function secureAnswerHash(
   answer: string,
   truthUuid: TruthUuid,
-  questionSalt: QuestionSalt,
+  questionSalt: TruthSalt,
 ): Promise<SecureAnswerHash> {
   const powResult = await argon2id({
     hashLength: 64,
diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index 7206f912..2909cf61 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -1,14 +1,16 @@
 import {
   AmountString,
   buildSigPS,
-  codecForGetExchangeWithdrawalInfo,
+  bytesToString,
   decodeCrock,
   eddsaSign,
   encodeCrock,
   getRandomBytes,
   hash,
+  stringToBytes,
   TalerErrorCode,
   TalerSignaturePurpose,
+  Timestamp,
 } from "@gnu-taler/taler-util";
 import { anastasisData } from "./anastasis-data.js";
 import {
@@ -42,16 +44,24 @@ import {
 import fetchPonyfill from "fetch-ponyfill";
 import {
   accountKeypairDerive,
+  asOpaque,
   coreSecretEncrypt,
   encryptKeyshare,
   encryptRecoveryDocument,
   encryptTruth,
+  OpaqueData,
   PolicyKey,
   policyKeyDerive,
   PolicySalt,
+  TruthSalt,
+  secureAnswerHash,
+  TruthKey,
+  TruthUuid,
   UserIdentifier,
   userIdentifierDerive,
+  typedArrayConcat,
 } from "./crypto.js";
+import { zlibSync } from "fflate";
 
 const { fetch, Request, Response, Headers } = fetchPonyfill({});
 
@@ -86,7 +96,7 @@ interface DecryptionPolicy {
   /**
    * List of escrow methods identified by their UUID.
    */
-  uuid: string[];
+  uuids: string[];
 }
 
 interface EscrowMethod {
@@ -100,16 +110,18 @@ interface EscrowMethod {
    */
   escrow_type: string;
 
-  // UUID of the escrow method (see /truth/ API below).
+  // UUID of the escrow method.
   // 16 bytes base32-crock encoded.
-  uuid: string;
+  uuid: TruthUuid;
 
   // Key used to encrypt the Truth this EscrowMethod is related to.
   // Client has to provide this key to the server when using /truth/.
-  truth_key: string;
+  truth_key: TruthKey;
 
-  // Salt used to encrypt the truth on the Anastasis server.
-  salt: string;
+  /**
+   * Salt to hash the security question answer if applicable.
+   */
+  truth_salt: TruthSalt;
 
   // Salt from the provider to derive the user ID
   // at this provider.
@@ -117,11 +129,6 @@ interface EscrowMethod {
 
   // The instructions to give to the user (i.e. the security question
   // if this is challenge-response).
-  // (Q: as string in base32 encoding?)
-  // (Q: what is the mime-type of this value?)
-  //
-  // The plaintext challenge is not revealed to the
-  // Anastasis server.
   instructions: string;
 }
 
@@ -388,7 +395,46 @@ interface TruthMetaData {
   /**
    * Truth-specific salt.
    */
-  salt: string;
+  truth_salt: string;
+}
+
+async function getTruthValue(
+  authMethod: AuthMethod,
+  truthUuid: string,
+  questionSalt: TruthSalt,
+): Promise<OpaqueData> {
+  switch (authMethod.type) {
+    case "question": {
+      return asOpaque(
+        await secureAnswerHash(
+          bytesToString(decodeCrock(authMethod.challenge)),
+          truthUuid,
+          questionSalt,
+        ),
+      );
+    }
+    case "sms":
+    case "email":
+    case "totp":
+    case "iban":
+      return encodeCrock(stringToBytes(authMethod.type));
+    default:
+      throw Error("unknown auth type");
+  }
+}
+
+/**
+ * Compress the recovery document and add a size header.
+ */
+async function compressRecoveryDoc(rd: any): Promise<Uint8Array> {
+  console.log("recovery document", rd);
+  const docBytes = stringToBytes(JSON.stringify(rd));
+  console.log("plain doc length", docBytes.length);
+  const sizeHeaderBuf = new ArrayBuffer(4);
+  const dvbuf = new DataView(sizeHeaderBuf);
+  dvbuf.setUint32(0, docBytes.length, false);
+  const zippedDoc = zlibSync(docBytes);
+  return typedArrayConcat([new Uint8Array(sizeHeaderBuf), zippedDoc]);
 }
 
 async function uploadSecret(
@@ -396,7 +442,9 @@ async function uploadSecret(
 ): Promise<ReducerStateBackup | ReducerStateError> {
   const policies = state.policies!;
   const secretName = state.secret_name!;
-  const coreSecret = state.core_secret?.value!;
+  const coreSecret: OpaqueData = encodeCrock(
+    stringToBytes(JSON.stringify(state.core_secret!)),
+  );
   // Truth key is `${methodIndex}/${providerUrl}`
   const truthMetadataMap: Record<string, TruthMetaData> = {};
 
@@ -412,8 +460,8 @@ async function uploadSecret(
     const methUuids: string[] = [];
     for (let methIndex = 0; methIndex < pol.methods.length; methIndex++) {
       const meth = pol.methods[methIndex];
-      const truthKey = `${meth.authentication_method}:${meth.provider}`;
-      if (truthMetadataMap[truthKey]) {
+      const truthReference = `${meth.authentication_method}:${meth.provider}`;
+      if (truthMetadataMap[truthReference]) {
         continue;
       }
       const keyShare = encodeCrock(getRandomBytes(32));
@@ -421,16 +469,17 @@ async function uploadSecret(
       const tm: TruthMetaData = {
         key_share: keyShare,
         nonce: encodeCrock(getRandomBytes(24)),
-        salt: encodeCrock(getRandomBytes(16)),
-        truth_key: encodeCrock(getRandomBytes(32)),
+        truth_salt: encodeCrock(getRandomBytes(16)),
+        truth_key: encodeCrock(getRandomBytes(64)),
         uuid: encodeCrock(getRandomBytes(32)),
         pol_method_index: methIndex,
         policy_index: policyIndex,
       };
       methUuids.push(tm.uuid);
-      truthMetadataMap[truthKey] = tm;
+      truthMetadataMap[truthReference] = tm;
     }
     const policyKey = await policyKeyDerive(keyShares, policySalt);
+    policyUuids.push(methUuids);
     policyKeys.push(policyKey);
     policySalts.push(policySalt);
   }
@@ -459,13 +508,20 @@ async function uploadSecret(
     const provider = state.authentication_providers![
       meth.provider
     ] as AuthenticationProviderStatusOk;
+    const truthValue = await getTruthValue(authMethod, tm.uuid, tm.truth_salt);
     const encryptedTruth = await encryptTruth(
       tm.nonce,
       tm.truth_key,
-      authMethod.challenge,
+      truthValue,
     );
     const uid = uidMap[meth.provider];
-    const encryptedKeyShare = await encryptKeyshare(tm.key_share, uid, 
tm.salt);
+    const encryptedKeyShare = await encryptKeyshare(
+      tm.key_share,
+      uid,
+      authMethod.type === "question"
+        ? bytesToString(decodeCrock(authMethod.challenge))
+        : undefined,
+    );
     console.log(
       "encrypted key share len",
       decodeCrock(encryptedKeyShare).length,
@@ -496,7 +552,7 @@ async function uploadSecret(
       escrow_type: authMethod.type,
       instructions: authMethod.instructions,
       provider_salt: provider.salt,
-      salt: tm.salt,
+      truth_salt: tm.truth_salt,
       truth_key: tm.truth_key,
       url: meth.provider,
       uuid: tm.uuid,
@@ -514,7 +570,7 @@ async function uploadSecret(
     policies: policies.map((x, i) => {
       return {
         master_key: csr.encMasterKeys[i],
-        uuid: policyUuids[i],
+        uuids: policyUuids[i],
         salt: policySalts[i],
       };
     }),
@@ -525,7 +581,12 @@ async function uploadSecret(
   for (const prov of state.policy_providers!) {
     const uid = uidMap[prov.provider_url];
     const acctKeypair = accountKeypairDerive(uid);
-    const encRecoveryDoc = await encryptRecoveryDocument(uid, rd);
+    const zippedDoc = await compressRecoveryDoc(rd);
+    console.log("zipped doc", zippedDoc);
+    const encRecoveryDoc = await encryptRecoveryDocument(
+      uid,
+      encodeCrock(zippedDoc),
+    );
     const bodyHash = hash(decodeCrock(encRecoveryDoc));
     const sigPS = buildSigPS(TalerSignaturePurpose.ANASTASIS_POLICY_UPLOAD)
       .put(bodyHash)
@@ -549,14 +610,19 @@ async function uploadSecret(
       };
     }
     let policyVersion = 0;
-    console.log(resp);
-    console.log(resp.headers);
-    console.log(resp.headers.get("Anastasis-Version"));
+    let policyExpiration: Timestamp = { t_ms: 0 };
     try {
       policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
     } catch (e) {}
+    try {
+      policyExpiration = {
+        t_ms:
+          1000 * Number(resp.headers.get("Anastasis-Policy-Expiration") ?? 
"0"),
+      };
+    } catch (e) {}
     successDetails[prov.provider_url] = {
       policy_version: policyVersion,
+      policy_expiration: policyExpiration,
     };
   }
 
diff --git a/packages/anastasis-core/src/reducer-types.ts 
b/packages/anastasis-core/src/reducer-types.ts
index 1f4a2abe..44761ea0 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -1,4 +1,4 @@
-import { Duration } from "@gnu-taler/taler-util";
+import { Duration, Timestamp } from "@gnu-taler/taler-util";
 
 export type ReducerState =
   | ReducerStateBackup
@@ -30,9 +30,15 @@ export interface PolicyProvider {
 export interface SuccessDetails {
   [provider_url: string]: {
     policy_version: number;
+    policy_expiration: Timestamp;
   };
 }
 
+export interface CoreSecret {
+  mime: string;
+  value: string;
+}
+
 export interface ReducerStateBackup {
   recovery_state?: undefined;
   backup_state: BackupStates;
@@ -60,10 +66,7 @@ export interface ReducerStateBackup {
     provider: string;
   }[];
 
-  core_secret?: {
-    mime: string;
-    value: string;
-  };
+  core_secret?: CoreSecret;
 
   expiration?: Duration;
 }
diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts 
b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
index 72424e82..4a242a2e 100644
--- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
+++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
@@ -3,7 +3,7 @@ import { BackupStates, getBackupStartState, 
getRecoveryStartState, RecoveryState
 import { useState } from "preact/hooks";
 
 const reducerBaseUrl = "http://localhost:5000/";;
-const remoteReducer = true;
+const remoteReducer = false;
 
 interface AnastasisState {
   reducerState: ReducerState | undefined;
diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx 
b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
index 2963930f..086d4921 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
@@ -1,20 +1,19 @@
 /* eslint-disable @typescript-eslint/camelcase */
-import {
-  encodeCrock,
-  stringToBytes
-} from "@gnu-taler/taler-util";
+import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { BackupReducerProps, AnastasisClientFrame, LabeledInput } from 
"./index";
+import {
+  BackupReducerProps,
+  AnastasisClientFrame,
+  LabeledInput,
+} from "./index";
 
 export function SecretEditorScreen(props: BackupReducerProps): VNode {
   const { reducer } = props;
   const [secretName, setSecretName] = useState(
-    props.backupState.secret_name ?? ""
-  );
-  const [secretValue, setSecretValue] = useState(
-    props.backupState.core_secret?.value ?? "" ?? ""
+    props.backupState.secret_name ?? "",
   );
+  const [secretValue, setSecretValue] = useState("");
   const secretNext = (): void => {
     reducer.runTransaction(async (tx) => {
       await tx.transition("enter_secret_name", {
@@ -41,12 +40,14 @@ export function SecretEditorScreen(props: 
BackupReducerProps): VNode {
         <LabeledInput
           label="Secret Name:"
           grabFocus
-          bind={[secretName, setSecretName]} />
+          bind={[secretName, setSecretName]}
+        />
       </div>
       <div>
         <LabeledInput
           label="Secret Value:"
-          bind={[secretValue, setSecretValue]} />
+          bind={[secretValue, setSecretValue]}
+        />
       </div>
     </AnastasisClientFrame>
   );
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 30b9e8d0..f5f77e57 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,12 +17,14 @@ importers:
       '@gnu-taler/taler-util': workspace:^0.8.3
       ava: ^3.15.0
       fetch-ponyfill: ^7.1.0
+      fflate: ^0.6.0
       hash-wasm: ^4.9.0
       node-fetch: ^3.0.0
       typescript: ^4.4.3
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
       fetch-ponyfill: 7.1.0
+      fflate: 0.6.0
       hash-wasm: 4.9.0
       node-fetch: 3.0.0
     devDependencies:

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]