Skip to content

Commit

Permalink
Hybrid asymmetric/symmetric encryption for streams (not enabled) (#2016)
Browse files Browse the repository at this point in the history
We're getting lots of groups wanting to expand their towns. This should
allow us to have much bigger towns.

just pass around a small number of aes keys instead of N asymmetric keys
per stream

this sets the default to the new decryption, probably want to run tests
against both for the time being
  • Loading branch information
texuf authored Jan 17, 2025
1 parent 33c96f6 commit a684f34
Show file tree
Hide file tree
Showing 19 changed files with 1,411 additions and 720 deletions.
1,434 changes: 733 additions & 701 deletions core/node/protocol/protocol.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/encryption/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface IGroupEncryptionClient {
algorithm: GroupEncryptionAlgorithmId,
): Promise<void>
getDevicesInStream(streamId: string): Promise<UserDeviceCollection>
getMiniblockInfo(streamId: string): Promise<{ miniblockNum: bigint; miniblockHash: Uint8Array }>
}

export interface IDecryptionParams {
Expand Down
48 changes: 48 additions & 0 deletions packages/encryption/src/cryptoAesGcm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export async function generateNewAesGcmKey(): Promise<CryptoKey> {
return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'])
}

export async function exportAesGsmKeyBytes(key: CryptoKey): Promise<Uint8Array> {
const exportedKey = await crypto.subtle.exportKey('raw', key)
return new Uint8Array(exportedKey)
}

export async function importAesGsmKeyBytes(key: Uint8Array): Promise<CryptoKey> {
return crypto.subtle.importKey('raw', key, 'AES-GCM', true, ['encrypt', 'decrypt'])
}

export async function encryptAesGcm(
key: CryptoKey,
data: Uint8Array,
): Promise<{ ciphertext: Uint8Array; iv: Uint8Array }> {
// If data is empty, it's obvious what the message is from the result length.
if (data.length === 0) {
throw new Error('Data to encrypt cannot be empty')
}
const iv = crypto.getRandomValues(new Uint8Array(12))
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
data,
)
return { ciphertext: new Uint8Array(encrypted), iv }
}

export async function decryptAesGcm(
key: CryptoKey,
ciphertext: Uint8Array,
iv: Uint8Array,
): Promise<Uint8Array> {
if (iv.length !== 12) {
throw new Error('IV must be 12 bytes')
}
if (ciphertext.length < 17) {
throw new Error('Ciphertext can not be this short')
}
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
ciphertext,
)
return new Uint8Array(decrypted)
}
29 changes: 29 additions & 0 deletions packages/encryption/src/cryptoStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AccountRecord,
ExtendedInboundGroupSessionData,
GroupSessionRecord,
HybridGroupSessionRecord,
UserDeviceRecord,
} from './storeTypes'
import Dexie, { Table } from 'dexie'
Expand All @@ -17,6 +18,7 @@ export class CryptoStore extends Dexie {
account!: Table<AccountRecord>
outboundGroupSessions!: Table<GroupSessionRecord>
inboundGroupSessions!: Table<ExtendedInboundGroupSessionData>
hybridGroupSessions!: Table<HybridGroupSessionRecord>
devices!: Table<UserDeviceRecord>
userId: string

Expand All @@ -27,6 +29,7 @@ export class CryptoStore extends Dexie {
account: 'id',
inboundGroupSessions: '[streamId+sessionId]',
outboundGroupSessions: 'streamId',
hybridGroupSessions: '[streamId+sessionId],streamId',
devices: '[userId+deviceKey],expirationTimestamp',
})
}
Expand Down Expand Up @@ -86,10 +89,26 @@ export class CryptoStore extends Dexie {
return await this.inboundGroupSessions.get({ sessionId, streamId })
}

async getHybridGroupSession(
streamId: string,
sessionId: string,
): Promise<HybridGroupSessionRecord | undefined> {
return await this.hybridGroupSessions.get({ streamId, sessionId })
}

async getHybridGroupSessionsForStream(streamId: string): Promise<HybridGroupSessionRecord[]> {
const sessions = await this.hybridGroupSessions.where({ streamId }).toArray()
return sessions
}

async getAllEndToEndInboundGroupSessions(): Promise<ExtendedInboundGroupSessionData[]> {
return await this.inboundGroupSessions.toArray()
}

async getAllHybridGroupSessions(): Promise<HybridGroupSessionRecord[]> {
return await this.hybridGroupSessions.toArray()
}

async storeEndToEndInboundGroupSession(
streamId: string,
sessionId: string,
Expand All @@ -98,11 +117,20 @@ export class CryptoStore extends Dexie {
await this.inboundGroupSessions.put({ streamId, sessionId, ...sessionData })
}

async storeHybridGroupSession(sessionData: HybridGroupSessionRecord): Promise<void> {
await this.hybridGroupSessions.put({ ...sessionData })
}

async getInboundGroupSessionIds(streamId: string): Promise<string[]> {
const sessions = await this.inboundGroupSessions.where({ streamId }).toArray()
return sessions.map((s) => s.sessionId)
}

async getHybridGroupSessionIds(streamId: string): Promise<string[]> {
const sessions = await this.hybridGroupSessions.where({ streamId }).toArray()
return sessions.map((s) => s.sessionId)
}

async withAccountTx<T>(fn: () => Promise<T>): Promise<T> {
return await this.transaction('rw', this.account, fn)
}
Expand All @@ -112,6 +140,7 @@ export class CryptoStore extends Dexie {
'rw',
this.outboundGroupSessions,
this.inboundGroupSessions,
this.hybridGroupSessions, // aellis this should be in its own transaction but tests were failing otherwise
fn,
)
}
Expand Down
5 changes: 4 additions & 1 deletion packages/encryption/src/decryptionExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,10 @@ export abstract class BaseDecryptionExtensions {
})
if (sessionNotFound) {
const streamId = item.streamId
const sessionId = item.encryptedData.sessionId
const sessionId =
item.encryptedData.sessionId && item.encryptedData.sessionId.length > 0
? item.encryptedData.sessionId
: bin_toHexString(item.encryptedData.sessionIdBytes)
if (!this.decryptionFailures[streamId]) {
this.decryptionFailures[streamId] = { [sessionId]: [item] }
} else if (!this.decryptionFailures[streamId][sessionId]) {
Expand Down
Loading

0 comments on commit a684f34

Please sign in to comment.