From b1034801d124b53cbb683e4a430ac00c7979bca1 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 18 Oct 2021 19:18:34 +0200 Subject: reducer implementation WIP --- packages/anastasis-core/src/crypto.ts | 152 +++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 12 deletions(-) (limited to 'packages/anastasis-core/src/crypto.ts') diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index c20d323a7..5da3a4cce 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -1,15 +1,44 @@ import { + bytesToString, canonicalJson, decodeCrock, encodeCrock, + getRandomBytes, + kdf, + secretbox, stringToBytes, } from "@gnu-taler/taler-util"; import { argon2id } from "hash-wasm"; +export type Flavor = T & { _flavor?: FlavorT }; +export type FlavorP = T & { + _flavor?: FlavorT; + _size?: S; +}; + +export type UserIdentifier = Flavor; +export type ServerSalt = Flavor; +export type PolicySalt = Flavor; +export type PolicyKey = FlavorP; +export type KeyShare = Flavor; +export type EncryptedKeyShare = Flavor; +export type EncryptedTruth = Flavor; +export type EncryptedCoreSecret = Flavor; +export type EncryptedMasterKey = Flavor; +/** + * Truth key, found in the recovery document. + */ +export type TruthKey = Flavor; +export type EncryptionNonce = Flavor; +export type OpaqueData = Flavor; + +const nonceSize = 24; +const masterKeySize = 64; + export async function userIdentifierDerive( idData: any, - serverSalt: string, -): Promise { + serverSalt: ServerSalt, +): Promise { const canonIdData = canonicalJson(idData); const hashInput = stringToBytes(canonIdData); const result = await argon2id({ @@ -24,15 +53,114 @@ export async function userIdentifierDerive( return encodeCrock(result); } -// interface Keypair { -// pub: string; -// priv: string; -// } +function taConcat(chunks: Uint8Array[]): Uint8Array { + let payloadLen = 0; + for (const c of chunks) { + payloadLen += c.byteLength; + } + const buf = new ArrayBuffer(payloadLen); + const u8buf = new Uint8Array(buf); + let p = 0; + for (const c of chunks) { + u8buf.set(c, p); + p += c.byteLength; + } + return u8buf; +} -// async function accountKeypairDerive(): Promise {} +export async function policyKeyDerive( + keyShares: KeyShare[], + policySalt: PolicySalt, +): Promise { + const chunks = keyShares.map((x) => decodeCrock(x)); + const polKey = kdf( + 64, + taConcat(chunks), + decodeCrock(policySalt), + new Uint8Array(0), + ); + return encodeCrock(polKey); +} + +async function deriveKey( + keySeed: OpaqueData, + nonce: EncryptionNonce, + salt: string, +): Promise { + return kdf(32, decodeCrock(keySeed), stringToBytes(salt), decodeCrock(nonce)); +} + +async function anastasisEncrypt( + nonce: EncryptionNonce, + keySeed: OpaqueData, + plaintext: OpaqueData, + salt: string, +): Promise { + const key = await deriveKey(keySeed, nonce, salt); + const nonceBuf = decodeCrock(nonce); + const cipherText = secretbox(decodeCrock(plaintext), decodeCrock(nonce), key); + return encodeCrock(taConcat([nonceBuf, cipherText])); +} -// async function secureAnswerHash( -// answer: string, -// truthUuid: string, -// questionSalt: string, -// ): Promise {} +const asOpaque = (x: string): OpaqueData => x; +const asEncryptedKeyShare = (x: OpaqueData): EncryptedKeyShare => x as string; +const asEncryptedTruth = (x: OpaqueData): EncryptedTruth => x as string; + +export async function encryptKeyshare( + keyShare: KeyShare, + userId: UserIdentifier, + answerSalt?: string, +): Promise { + const s = answerSalt ?? "eks"; + const nonce = encodeCrock(getRandomBytes(24)); + return asEncryptedKeyShare( + await anastasisEncrypt(nonce, asOpaque(userId), asOpaque(keyShare), s), + ); +} + +export async function encryptTruth( + nonce: EncryptionNonce, + truthEncKey: TruthKey, + truth: OpaqueData, +): Promise { + const salt = "ect"; + return asEncryptedTruth( + await anastasisEncrypt(nonce, asOpaque(truthEncKey), truth, salt), + ); +} + +export interface CoreSecretEncResult { + encCoreSecret: EncryptedCoreSecret; + encMasterKeys: EncryptedMasterKey[]; +} + +export async function coreSecretEncrypt( + policyKeys: PolicyKey[], + coreSecret: OpaqueData, +): Promise { + const masterKey = getRandomBytes(masterKeySize); + const nonce = encodeCrock(getRandomBytes(nonceSize)); + const coreSecretEncSalt = "cse"; + const masterKeyEncSalt = "emk"; + const encCoreSecret = (await anastasisEncrypt( + nonce, + encodeCrock(masterKey), + coreSecret, + coreSecretEncSalt, + )) as string; + const encMasterKeys: EncryptedMasterKey[] = []; + for (let i = 0; i < policyKeys.length; i++) { + const polNonce = encodeCrock(getRandomBytes(nonceSize)); + const encMasterKey = await anastasisEncrypt( + polNonce, + asOpaque(policyKeys[i]), + encodeCrock(masterKey), + masterKeyEncSalt, + ); + encMasterKeys.push(encMasterKey as string); + } + return { + encCoreSecret, + encMasterKeys, + }; +} -- cgit v1.2.3