diff --git a/packages/backend/src/nest/auth/services/crypto/crypto.service.ts b/packages/backend/src/nest/auth/services/crypto/crypto.service.ts index 54b41a103..a385f93c9 100644 --- a/packages/backend/src/nest/auth/services/crypto/crypto.service.ts +++ b/packages/backend/src/nest/auth/services/crypto/crypto.service.ts @@ -3,7 +3,13 @@ */ import * as bs58 from 'bs58' -import { EncryptedAndSignedPayload, EncryptedPayload, EncryptionScope, EncryptionScopeType } from './types' +import { + DecryptedPayload, + EncryptedAndSignedPayload, + EncryptedPayload, + EncryptionScope, + EncryptionScopeType, +} from './types' import { ChainServiceBase } from '../chainServiceBase' import { SigChain } from '../../sigchain' import { asymmetric, Base58, Keyset, LocalUserContext, Member, SignedEnvelope } from '@localfirst/auth' @@ -96,28 +102,45 @@ class CryptoService extends ChainServiceBase { } } - public decryptAndVerify(encrypted: EncryptedPayload, signature: SignedEnvelope, context: LocalUserContext): any { - const isValid = this.sigChain.team!.verify(signature) - if (!isValid) { + public decryptAndVerify( + encrypted: EncryptedPayload, + signature: SignedEnvelope, + context: LocalUserContext, + failOnInvalid = true + ): DecryptedPayload { + const isValid = this.verifyMessage(signature) + if (!isValid && failOnInvalid) { throw new Error(`Couldn't verify signature on message`) } + let contents: T switch (encrypted.scope.type) { // Symmetrical Encryption Types case EncryptionScopeType.CHANNEL: case EncryptionScopeType.ROLE: case EncryptionScopeType.TEAM: - return this.symDecrypt(encrypted) + contents = this.symDecrypt(encrypted) + break // Asymmetrical Encryption Types case EncryptionScopeType.USER: - return this.asymUserDecrypt(encrypted, signature, context) + contents = this.asymUserDecrypt(encrypted, signature, context) + break // Unknown Type default: throw new Error(`Unknown encryption scope type ${encrypted.scope.type}`) } + + return { + contents, + isValid, + } } - private symDecrypt(encrypted: EncryptedPayload): any { + public verifyMessage(signature: SignedEnvelope): boolean { + return this.sigChain.team!.verify(signature) + } + + private symDecrypt(encrypted: EncryptedPayload): T { if (encrypted.scope.type !== EncryptionScopeType.TEAM && encrypted.scope.name == null) { throw new Error(`Must provide a scope name when encryption scope is set to ${encrypted.scope.type}`) } @@ -129,10 +152,10 @@ class CryptoService extends ChainServiceBase { // you don't need a name on the scope when encrypting but you need one for decrypting because of how LFA searches for keys in lockboxes name: encrypted.scope.type === EncryptionScopeType.TEAM ? EncryptionScopeType.TEAM : encrypted.scope.name!, }, - }) + }) as T } - private asymUserDecrypt(encrypted: EncryptedPayload, signature: SignedEnvelope, context: LocalUserContext): any { + private asymUserDecrypt(encrypted: EncryptedPayload, signature: SignedEnvelope, context: LocalUserContext): T { if (encrypted.scope.name == null) { throw new Error(`Must provide a user ID when encryption scope is set to ${encrypted.scope.type}`) } @@ -145,7 +168,7 @@ class CryptoService extends ChainServiceBase { cipher: encrypted.contents, senderPublicKey: senderKey, recipientSecretKey: recipientKey, - }) + }) as T } } diff --git a/packages/backend/src/nest/auth/services/crypto/types.ts b/packages/backend/src/nest/auth/services/crypto/types.ts index 3e15f0d7e..7c9c0bc49 100644 --- a/packages/backend/src/nest/auth/services/crypto/types.ts +++ b/packages/backend/src/nest/auth/services/crypto/types.ts @@ -25,3 +25,8 @@ export type EncryptedAndSignedPayload = { ts: number username: string } + +export type DecryptedPayload = { + contents: T + isValid: boolean +} diff --git a/packages/backend/src/nest/auth/sigchain.service.ts b/packages/backend/src/nest/auth/sigchain.service.ts index 853293ff3..81c8629eb 100644 --- a/packages/backend/src/nest/auth/sigchain.service.ts +++ b/packages/backend/src/nest/auth/sigchain.service.ts @@ -70,6 +70,7 @@ export class SigChainService implements OnModuleInit { this.setActiveChain(teamName) return true } + return false } diff --git a/packages/backend/src/nest/storage/base.store.ts b/packages/backend/src/nest/storage/base.store.ts index d4b97f926..b41397e94 100644 --- a/packages/backend/src/nest/storage/base.store.ts +++ b/packages/backend/src/nest/storage/base.store.ts @@ -34,8 +34,8 @@ export abstract class KeyValueStoreBase extends StoreBase> abstract getEntry(key?: string): Promise } -export abstract class EventStoreBase extends StoreBase> { +export abstract class EventStoreBase extends StoreBase> { protected store: EventsType | undefined - abstract addEntry(value: V): Promise - abstract getEntries(): Promise + abstract addEntry(value: U): Promise + abstract getEntries(): Promise } diff --git a/packages/backend/src/nest/storage/channels/channel.store.ts b/packages/backend/src/nest/storage/channels/channel.store.ts index dcd08cffb..7423fa929 100644 --- a/packages/backend/src/nest/storage/channels/channel.store.ts +++ b/packages/backend/src/nest/storage/channels/channel.store.ts @@ -15,19 +15,20 @@ import { import { createLogger } from '../../common/logger' import { EventStoreBase } from '../base.store' import { EventsWithStorage } from '../orbitDb/eventsWithStorage' -import { MessagesAccessController } from '../orbitDb/MessagesAccessController' +import { MessagesAccessController } from './messages/orbitdb/MessagesAccessController' import { OrbitDbService } from '../orbitDb/orbitDb.service' import validate from '../../validation/validators' import { MessagesService } from './messages/messages.service' import { DBOptions, StorageEvents } from '../storage.types' import { LocalDbService } from '../../local-db/local-db.service' import { CertificatesStore } from '../certificates/certificates.store' +import { EncryptedMessage } from './messages/messages.types' /** * Manages storage-level logic for a given channel in Quiet */ @Injectable() -export class ChannelStore extends EventStoreBase { +export class ChannelStore extends EventStoreBase { private channelData: PublicChannel private _subscribing: boolean = false @@ -61,12 +62,15 @@ export class ChannelStore extends EventStoreBase { this.logger = createLogger(`storage:channels:channelStore:${this.channelData.name}`) this.logger.info(`Initializing channel store for channel ${this.channelData.name}`) - this.store = await this.orbitDbService.orbitDb.open>(`channels.${this.channelData.id}`, { - type: 'events', - Database: EventsWithStorage(true), - AccessController: MessagesAccessController({ write: ['*'] }), - sync: options.sync, - }) + this.store = await this.orbitDbService.orbitDb.open>( + `channels.${this.channelData.id}`, + { + type: 'events', + Database: EventsWithStorage(), + AccessController: MessagesAccessController({ write: ['*'], messagesService: this.messagesService }), + sync: options.sync, + } + ) this.logger.info('Initialized') return this @@ -96,7 +100,7 @@ export class ChannelStore extends EventStoreBase { this.logger.info('Subscribing to channel ', this.channelData.id) this._subscribing = true - this.getStore().events.on('update', async (entry: LogEntry) => { + this.getStore().events.on('update', async (entry: LogEntry) => { this.logger.info(`${this.channelData.id} database updated`, entry.hash, entry.payload.value?.channelId) let message: ChannelMessage | undefined = undefined if (entry.payload.value == null) { @@ -166,25 +170,28 @@ export class ChannelStore extends EventStoreBase { * * @param message Message to add to the OrbitDB database */ - public async sendMessage(message: ChannelMessage): Promise { + public async sendMessage(message: ChannelMessage): Promise { this.logger.info(`Sending message with ID ${message.id} on channel ${this.channelData.id}`) if (!validate.isMessage(message)) { this.logger.error('Public channel message is invalid') - return + return false } if (message.channelId != this.channelData.id) { this.logger.error( `Could not send message. Message is for channel ID ${message.channelId} which does not match channel ID ${this.channelData.id}` ) - return + return false } try { await this.addEntry(message) + return true } catch (e) { - this.logger.error(`Could not append message (entry not allowed to write to the log). Details: ${e.message}`) + this.logger.error(`Error while sending message`, e) } + + return false } /** @@ -238,16 +245,16 @@ export class ChannelStore extends EventStoreBase { } /** - * Read a list of entries on the OrbitDB event store + * Read a list of entries on the OrbitDB event store and decrypt * * @param ids Optional list of message IDs to filter by * @returns All matching entries on the event store */ - public async getEntries(): Promise - public async getEntries(ids: string[] | undefined): Promise - public async getEntries(ids?: string[] | undefined): Promise { + public async getEntries(): Promise + public async getEntries(ids: string[] | undefined): Promise + public async getEntries(ids?: string[] | undefined): Promise { this.logger.info(`Getting all messages for channel`, this.channelData.id, this.channelData.name) - const messages: ChannelMessage[] = [] + const messages: ConsumedChannelMessage[] = [] for await (const x of this.getStore().iterator()) { if (x.value == null) { @@ -256,10 +263,32 @@ export class ChannelStore extends EventStoreBase { } if (ids == null || ids?.includes(x.value.id)) { - // NOTE: we skipped the verification process when reading many messages in the previous version - // so I'm skipping it here - is that really the correct behavior? - const processedMessage = await this.messagesService.onConsume(x.value, false) - messages.push(processedMessage) + const decryptedMessage = await this.messagesService.onConsume(x.value) + if (decryptedMessage == null) { + continue + } + messages.push(decryptedMessage) + } + } + + return messages + } + + /** + * Read a list of entries on the OrbitDB event store without decrypting + * + * @param ids Optional list of message IDs to filter by + * @returns All matching entries on the event store + */ + public async getEncryptedEntries(): Promise + public async getEncryptedEntries(ids: string[] | undefined): Promise + public async getEncryptedEntries(ids?: string[] | undefined): Promise { + this.logger.info(`Getting all encrypted messages for channel`, this.channelData.id, this.channelData.name) + const messages: EncryptedMessage[] = [] + + for await (const x of this.getStore().iterator()) { + if (ids == null || ids?.includes(x.value.id)) { + messages.push(x.value) } } diff --git a/packages/backend/src/nest/storage/channels/channels.service.spec.ts b/packages/backend/src/nest/storage/channels/channels.service.spec.ts index f92b720da..cfc1b6d05 100644 --- a/packages/backend/src/nest/storage/channels/channels.service.spec.ts +++ b/packages/backend/src/nest/storage/channels/channels.service.spec.ts @@ -38,6 +38,7 @@ import { LocalDbModule } from '../../local-db/local-db.module' import { LocalDbService } from '../../local-db/local-db.service' import { createLogger } from '../../common/logger' import { ChannelsService } from './channels.service' +import { SigChainService } from '../../auth/sigchain.service' const logger = createLogger('channelsService:test') @@ -51,6 +52,7 @@ describe('ChannelsService', () => { let libp2pService: Libp2pService let localDbService: LocalDbService let channelsService: ChannelsService + let sigChainService: SigChainService let peerId: PeerId let store: Store @@ -106,6 +108,7 @@ describe('ChannelsService', () => { localDbService = await module.resolve(LocalDbService) libp2pService = await module.resolve(Libp2pService) ipfsService = await module.resolve(IpfsService) + sigChainService = await module.resolve(SigChainService) const params = await libp2pInstanceParams() peerId = params.peerId.peerId @@ -119,6 +122,8 @@ describe('ChannelsService', () => { await localDbService.setCommunity(community) await localDbService.setCurrentCommunityId(community.id) + await sigChainService.createChain(community.name!, alice.nickname, true) + await storageService.init(peerId) }) @@ -174,10 +179,23 @@ describe('ChannelsService', () => { expect(eventSpy).toHaveBeenCalled() const savedMessages = await channelsService.getMessages(channelio.id) expect(savedMessages?.messages.length).toBe(1) - expect(savedMessages?.messages[0]).toEqual({ ...messageCopy, verified: true }) + expect(savedMessages?.messages[0]).toEqual({ + ...messageCopy, + verified: true, + encSignature: expect.objectContaining({ + author: { + generation: 0, + type: 'USER', + name: sigChainService.getActiveChain().localUserContext.user.userId, + }, + contents: expect.any(String), + signature: expect.any(String), + }), + }) }) - it('is not saved to db if did not pass signature verification', async () => { + // TODO: figure out a good way to spoof the signature + it.skip('is not saved to db if did not pass signature verification', async () => { const aliceMessage = await factory.create['payload']>( 'Message', { diff --git a/packages/backend/src/nest/storage/channels/messages/messages.service.spec.ts b/packages/backend/src/nest/storage/channels/messages/messages.service.spec.ts index e6feb6d3b..5d9dd31bd 100644 --- a/packages/backend/src/nest/storage/channels/messages/messages.service.spec.ts +++ b/packages/backend/src/nest/storage/channels/messages/messages.service.spec.ts @@ -1,7 +1,6 @@ import { jest } from '@jest/globals' import { Test, TestingModule } from '@nestjs/testing' -import { keyFromCertificate, parseCertificate } from '@quiet/identity' import { generateMessageFactoryContentWithId, getFactory, @@ -10,12 +9,16 @@ import { Store, } from '@quiet/state-manager' import { ChannelMessage, Community, Identity, PublicChannel, TestMessage } from '@quiet/types' +import { isBase58 } from 'class-validator' import { FactoryGirl } from 'factory-girl' +import { EncryptionScopeType } from '../../../auth/services/crypto/types' +import { RoleName } from '../../../auth/services/roles/roles' import { SigChainService } from '../../../auth/sigchain.service' import { createLogger } from '../../../common/logger' import { TestModule } from '../../../common/test.module' import { StorageModule } from '../../storage.module' import { MessagesService } from './messages.service' +import { EncryptedMessage } from './messages.types' const logger = createLogger('messagesService:test') @@ -27,7 +30,6 @@ describe('MessagesService', () => { let store: Store let factory: FactoryGirl let alice: Identity - let john: Identity let community: Community let channel: PublicChannel let message: ChannelMessage @@ -39,7 +41,6 @@ describe('MessagesService', () => { community = await factory.create('Community') channel = publicChannels.selectors.publicChannels(store.getState())[0] alice = await factory.create('Identity', { id: community.id, nickname: 'alice' }) - john = await factory.create('Identity', { id: community.id, nickname: 'john' }) message = ( await factory.create('Message', { identity: alice, @@ -56,55 +57,82 @@ describe('MessagesService', () => { }).compile() sigChainService = await module.resolve(SigChainService) + await sigChainService.createChain(community.name!, alice.nickname, true) messagesService = await module.resolve(MessagesService) }) describe('verifyMessage', () => { it('message with valid signature is verified', async () => { - expect(await messagesService.verifyMessage(message)).toBeTruthy() + const encryptedMessage = await messagesService.onSend(message) + expect(messagesService.verifyMessage(encryptedMessage)).toBeTruthy() }) it('message with invalid signature is not verified', async () => { - expect( - await messagesService.verifyMessage({ - ...message, - pubKey: keyFromCertificate(parseCertificate(john.userCertificate!)), + const encryptedMessage = await messagesService.onSend(message) + let err: Error | undefined = undefined + try { + messagesService.verifyMessage({ + ...encryptedMessage, + encSignature: { + ...encryptedMessage.encSignature, + author: { + generation: 1, + name: 'foobar', + type: '', + }, + }, }) - ).toBeFalsy() + } catch (e) { + err = e + } + expect(err).toBeDefined() }) }) - // TODO: https://github.com/TryQuiet/quiet/issues/2631 describe('onSend', () => { - it('does nothing but return the message as-is', async () => { - expect(await messagesService.onSend(message)).toEqual(message) + it('encrypts message correctly', async () => { + const encryptedMessage = await messagesService.onSend(message) + expect(encryptedMessage).toEqual( + expect.objectContaining({ + ...message, + message: expect.objectContaining({ + scope: { + generation: 0, + type: EncryptionScopeType.ROLE, + name: RoleName.MEMBER, + }, + }), + }) + ) + expect(isBase58(encryptedMessage.message.contents)).toBeTruthy() }) }) - // TODO: https://github.com/TryQuiet/quiet/issues/2632 describe('onConsume', () => { - it('runs verifyMessage when verify === true', async () => { - expect(await messagesService.onConsume(message, true)).toEqual({ + it('decrypts an encrypted message correctly', async () => { + const encryptedMessage = await messagesService.onSend(message) + expect(await messagesService.onConsume(encryptedMessage)).toEqual({ ...message, verified: true, + encSignature: encryptedMessage.encSignature, }) }) - it('skips verifyMessage when verify === false', async () => { - const fakePubKey = keyFromCertificate(parseCertificate(john.userCertificate!)) - expect( - await messagesService.onConsume( - { - ...message, - pubKey: fakePubKey, + it('returns undefined when the signature is invalid', async () => { + const encryptedMessage = await messagesService.onSend(message) + const invalidEncryptedMessage: EncryptedMessage = { + ...encryptedMessage, + encSignature: { + ...encryptedMessage.encSignature, + author: { + generation: 1, + name: 'foobar', + type: '', }, - false - ) - ).toEqual({ - ...message, - pubKey: fakePubKey, - verified: true, - }) + }, + } + + expect(await messagesService.onConsume(invalidEncryptedMessage)).toBeUndefined() }) }) }) diff --git a/packages/backend/src/nest/storage/channels/messages/messages.service.ts b/packages/backend/src/nest/storage/channels/messages/messages.service.ts index 642d3a379..0a1b77b3b 100644 --- a/packages/backend/src/nest/storage/channels/messages/messages.service.ts +++ b/packages/backend/src/nest/storage/channels/messages/messages.service.ts @@ -4,12 +4,20 @@ import EventEmitter from 'events' import { getCrypto, ICryptoEngine } from 'pkijs' import { keyObjectFromString, verifySignature } from '@quiet/identity' -import { ChannelMessage, ConsumedChannelMessage, NoCryptoEngineError } from '@quiet/types' +import { + ChannelMessage, + CompoundError, + ConsumedChannelMessage, + EncryptionSignature, + NoCryptoEngineError, +} from '@quiet/types' import { createLogger } from '../../../common/logger' -import { EncryptedAndSignedPayload, EncryptedPayload } from '../../../auth/services/crypto/types' -import { SignedEnvelope } from '3rd-party/auth/packages/auth/dist' +import { EncryptionScopeType } from '../../../auth/services/crypto/types' import { SigChainService } from '../../../auth/sigchain.service' +import { EncryptedMessage } from './messages.types' +import { SignedEnvelope } from '3rd-party/auth/packages/auth/dist' +import { RoleName } from '../../../auth/services/roles/roles' @Injectable() export class MessagesService extends EventEmitter { @@ -29,65 +37,78 @@ export class MessagesService extends EventEmitter { /** * Handle processing of message to be added to OrbitDB and sent to peers * - * NOTE: This will call the encryption method below (https://github.com/TryQuiet/quiet/issues/2631) - * * @param message Message to send * @returns Processed message */ - public async onSend(message: ChannelMessage): Promise { - return message + public async onSend(message: ChannelMessage): Promise { + return this._encryptPublicChannelMessage(message) } /** * Handle processing of message consumed from OrbitDB * - * NOTE: This will call the decryption method below (https://github.com/TryQuiet/quiet/issues/2632) - * * @param message Message consumed from OrbitDB * @returns Processed message */ - public async onConsume(message: ChannelMessage, verify: boolean = true): Promise { - const verified = verify ? await this.verifyMessage(message) : true - return { - ...message, - verified, + public async onConsume(message: EncryptedMessage): Promise { + try { + return this._decryptPublicChannelMessage(message) + } catch (e) { + this.logger.error(`Failed to process message on consume`, e) + return undefined } } /** - * Verify signature on message + * Verify encryption signature on message * * @param message Message to verify * @returns True if message is valid */ - public async verifyMessage(message: ChannelMessage): Promise { + public verifyMessage(message: EncryptedMessage): boolean { try { - const crypto = this.getCrypto() - const signature = stringToArrayBuffer(message.signature) - let cryptoKey = this.publicKeysMap.get(message.pubKey) - - if (!cryptoKey) { - cryptoKey = await keyObjectFromString(message.pubKey, crypto) - this.publicKeysMap.set(message.pubKey, cryptoKey) - } - - return await verifySignature(signature, message.message, cryptoKey) + const chain = this.sigChainService.getActiveChain() + return chain.crypto.verifyMessage(message.encSignature) } catch (e) { - this.logger.error(`Error while verifying signature on message`, e) - return false + throw new CompoundError(`Failed to verify message signature`, e) } } - // TODO: https://github.com/TryQuiet/quiet/issues/2631 - // NOTE: the signature here may not be correct - private async encryptMessage(message: ChannelMessage): Promise { - throw new Error(`MessagesService.encryptMessage is not implemented!`) + private _encryptPublicChannelMessage(rawMessage: ChannelMessage): EncryptedMessage { + try { + const chain = this.sigChainService.getActiveChain() + const encryptedMessage = chain.crypto.encryptAndSign( + rawMessage.message, + { type: EncryptionScopeType.ROLE, name: RoleName.MEMBER }, + chain.localUserContext + ) + return { + ...rawMessage, + encSignature: encryptedMessage.signature, + message: encryptedMessage.encrypted, + } + } catch (e) { + throw new CompoundError(`Failed to encrypt message with error`, e) + } } - // TODO: https://github.com/TryQuiet/quiet/issues/2632 - // NOTE: the signature here may not be correct - private async decryptMessage(encrypted: EncryptedPayload, signature: SignedEnvelope): Promise { - throw new Error(`MessagesService.decryptMessage is not implemented!`) + private _decryptPublicChannelMessage(encryptedMessage: EncryptedMessage): ConsumedChannelMessage { + try { + const chain = this.sigChainService.getActiveChain() + const decryptedMessage = chain.crypto.decryptAndVerify( + encryptedMessage.message, + encryptedMessage.encSignature, + chain.localUserContext, + false + ) + return { + ...encryptedMessage, + message: decryptedMessage.contents, + verified: decryptedMessage.isValid, + } + } catch (e) { + throw new CompoundError(`Failed to decrypt message with error`, e) + } } /** diff --git a/packages/backend/src/nest/storage/channels/messages/messages.types.ts b/packages/backend/src/nest/storage/channels/messages/messages.types.ts new file mode 100644 index 000000000..17d780ccf --- /dev/null +++ b/packages/backend/src/nest/storage/channels/messages/messages.types.ts @@ -0,0 +1,16 @@ +import { SignedEnvelope } from '3rd-party/auth/packages/auth/dist' +import { EncryptedPayload } from '../../../auth/services/crypto/types' + +import { FileMetadata } from '@quiet/types' + +export interface EncryptedMessage { + id: string + type: number + message: EncryptedPayload + createdAt: number + channelId: string + encSignature: SignedEnvelope + signature: string + pubKey: string + media?: FileMetadata +} diff --git a/packages/backend/src/nest/storage/orbitDb/MessagesAccessController.ts b/packages/backend/src/nest/storage/channels/messages/orbitdb/MessagesAccessController.ts similarity index 79% rename from packages/backend/src/nest/storage/orbitDb/MessagesAccessController.ts rename to packages/backend/src/nest/storage/channels/messages/orbitdb/MessagesAccessController.ts index b7d30ac82..bf0b98865 100644 --- a/packages/backend/src/nest/storage/orbitDb/MessagesAccessController.ts +++ b/packages/backend/src/nest/storage/channels/messages/orbitdb/MessagesAccessController.ts @@ -15,12 +15,18 @@ import { getCrypto } from 'pkijs' import { stringToArrayBuffer } from 'pvutils' import { keyObjectFromString, verifySignature } from '@quiet/identity' import { ChannelMessage, NoCryptoEngineError } from '@quiet/types' -import { posixJoin } from './util' +import { posixJoin } from '../../../orbitDb/util' +import { EncryptedMessage } from '../messages.types' +import { SigChainService } from 'packages/backend/src/nest/auth/sigchain.service' +import { MessagesService } from '../messages.service' +import { createLogger } from '../../../../common/logger' const codec = dagCbor const hasher = sha256 const hashStringEncoding = base58btc +const logger = createLogger(`storage:channels:messages:orbitdb:access-control`) + const AccessControlList = async ({ storage, type, @@ -43,7 +49,7 @@ const AccessControlList = async ({ const type = 'messagesaccess' export const MessagesAccessController = - ({ write }: { write: string[] }) => + ({ write, messagesService }: { write: string[]; messagesService: MessagesService }) => async ({ orbitdb, identities, address }: { orbitdb: OrbitDBType; identities: IdentitiesType; address: string }) => { const storage = await ComposedStorage( await LRUStorage({ size: 1000 }), @@ -65,7 +71,7 @@ export const MessagesAccessController = const crypto = getCrypto() const keyMapping: Map = new Map() - const canAppend = async (entry: LogEntry) => { + const canAppend = async (entry: LogEntry) => { if (!crypto) throw new NoCryptoEngineError() const writerIdentity = await identities.getIdentity(entry.identity) @@ -86,16 +92,9 @@ export const MessagesAccessController = if (message) { try { - const signature = stringToArrayBuffer(message.signature) - let cryptoKey = keyMapping.get(message.pubKey) - - if (!cryptoKey) { - cryptoKey = await keyObjectFromString(message.pubKey, crypto) - keyMapping.set(message.pubKey, cryptoKey) - } - - return await verifySignature(signature, message.message, cryptoKey) + return messagesService.verifyMessage(message) } catch (e) { + logger.error(`Couldn't verify signature on message ${message.id}, can't append!`, e) return true } } else { diff --git a/packages/backend/src/nest/storage/orbitDb/orbitDb.service.ts b/packages/backend/src/nest/storage/orbitDb/orbitDb.service.ts index 492c43dcc..0d750e882 100644 --- a/packages/backend/src/nest/storage/orbitDb/orbitDb.service.ts +++ b/packages/backend/src/nest/storage/orbitDb/orbitDb.service.ts @@ -3,7 +3,7 @@ import { ORBIT_DB_DIR } from '../../const' import { createLogger } from '../../common/logger' import { posixJoin } from './util' import { type PeerId } from '@libp2p/interface' -import { MessagesAccessController } from './MessagesAccessController' +import { MessagesAccessController } from '../channels/messages/orbitdb/MessagesAccessController' import { createOrbitDB, type OrbitDBType, diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 59c589fdf..90e9bc9d8 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -337,6 +337,9 @@ export class QuietLogger { if ((param as any).errors != null) { formattedErrors += ` - Errors:\n` formattedErrors += (param as any).errors.map((err: Error) => stringifyError(err)).join('\n') + } else if ((param as any).originalError != null) { + formattedErrors += ` - Original Error:\n` + formattedErrors += stringifyError((param as any).originalError) } return colorizeError(formattedErrors) diff --git a/packages/types/src/channel.ts b/packages/types/src/channel.ts index 05c12c41e..ec8c33f8d 100644 --- a/packages/types/src/channel.ts +++ b/packages/types/src/channel.ts @@ -31,6 +31,14 @@ export interface PublicChannelSubscription { subscribed: boolean } +// NOTE: These are all typed as any because they are all LFA types and I don't wanna import LFA into +// the types package. +export interface EncryptionSignature { + contents: any + signature: any + author: any +} + export interface ChannelMessage { id: string type: number @@ -38,6 +46,7 @@ export interface ChannelMessage { createdAt: number channelId: string signature: string + encSignature?: EncryptionSignature pubKey: string media?: FileMetadata }