diff --git a/src/crypto/ecies.ts b/src/crypto/ecies.ts index 73fed0e9..056d325a 100644 --- a/src/crypto/ecies.ts +++ b/src/crypto/ecies.ts @@ -88,7 +88,7 @@ function getAes( const aesCbcEncrypt = getAes('encrypt') const aesCbcDecrypt = getAes('decrypt') -async function hmacSha256Sign(key: Buffer, msg: Buffer) { +export async function hmacSha256Sign(key: Buffer, msg: Buffer) { const newKey = await subtle.importKey( 'raw', key, diff --git a/src/crypto/encryption.ts b/src/crypto/encryption.ts index 0e9460ac..6c0fd67e 100644 --- a/src/crypto/encryption.ts +++ b/src/crypto/encryption.ts @@ -71,7 +71,11 @@ function aesGcmParams( // Derive AES-256-GCM key from a shared secret and salt. // Returns crypto.CryptoKey suitable for the encrypt/decrypt API -async function hkdf(secret: Uint8Array, salt: Uint8Array): Promise { +export async function hkdf( + secret: Uint8Array, + salt: Uint8Array, + extractable = false +): Promise { const key = await crypto.subtle.importKey('raw', secret, 'HKDF', false, [ 'deriveKey', ]) @@ -79,7 +83,7 @@ async function hkdf(secret: Uint8Array, salt: Uint8Array): Promise { { name: 'HKDF', hash: 'SHA-256', salt, info: hkdfNoInfo }, key, { name: 'AES-GCM', length: 256 }, - false, + extractable, ['encrypt', 'decrypt'] ) } diff --git a/src/keystore/InMemoryKeystore.ts b/src/keystore/InMemoryKeystore.ts index 15663705..fd59a28b 100644 --- a/src/keystore/InMemoryKeystore.ts +++ b/src/keystore/InMemoryKeystore.ts @@ -23,10 +23,14 @@ import { getKeyMaterial, topicDataToConversationReference, } from './utils' -import { nsToDate } from '../utils' +import { nsToDate, buildDirectMessageTopicV2 } from '../utils' import InviteStore from './InviteStore' import { Persistence } from './persistence' import LocalAuthenticator from '../authn/LocalAuthenticator' +import { sha256 } from 'ethers/lib/utils' +import { hmacSha256Sign } from '../crypto/ecies' +import { KDFSaltSize } from '../crypto/Ciphertext' +import { hkdf, crypto } from '../crypto/encryption' const { ErrorCode } = keystore export default class InMemoryKeystore implements Keystore { @@ -248,9 +252,43 @@ export default class InMemoryKeystore implements Keystore { 'missing recipient' ) } - const invitation = InvitationV1.createRandom(req.context) + // const invitation = InvitationV1.createRandom(req.context) const created = nsToDate(req.createdNs) const recipient = toSignedPublicKeyBundle(req.recipient) + + const secret = await this.v2Keys.sharedSecret( + recipient, + this.v2Keys.getCurrentPreKey().publicKey, + false + ) + + const msg = new TextEncoder().encode( + JSON.stringify({ + conversationId: req.context?.conversationId || '', + participants: [ + this.accountAddress, + await recipient.walletSignatureAddress(), + ].sort(), + }) + ) + + const topic = sha256( + await hmacSha256Sign(Buffer.from(secret), Buffer.from(msg)) + ) + + const salt = getRandomValues(new Uint8Array(KDFSaltSize)) + const derivedKey = await hkdf(secret, salt, true) + + const keyMaterial = new Uint8Array( + await crypto.subtle.exportKey('raw', derivedKey) + ) + + const invitation = new InvitationV1({ + topic: buildDirectMessageTopicV2(topic), + aes256GcmHkdfSha256: { keyMaterial }, + context: req.context, + }) + const sealed = await SealedInvitation.createV1({ sender: this.v2Keys, recipient, diff --git a/test/keystore/InMemoryKeystore.test.ts b/test/keystore/InMemoryKeystore.test.ts index 777edcae..6ba0f38a 100644 --- a/test/keystore/InMemoryKeystore.test.ts +++ b/test/keystore/InMemoryKeystore.test.ts @@ -457,7 +457,13 @@ describe('InMemoryKeystore', () => { const shuffled = [...timestamps].sort(() => Math.random() - 0.5) await Promise.all( - shuffled.map((createdAt) => { + shuffled.map(async (createdAt) => { + let keys = await PrivateKeyBundleV1.generate(newWallet()) + + const recipient = SignedPublicKeyBundle.fromLegacyBundle( + keys.getPublicKeyBundle() + ) + return aliceKeystore.createInvite({ recipient, createdNs: dateToNs(createdAt),