7 } from '@proton/crypto'
8 import { CryptoProxy } from '@proton/crypto'
9 import { SignedPlaintextContent } from '@proton/docs-proto'
11 encryptDataWith16ByteIV as gcmEncryptWith16ByteIV,
12 decryptData as gcmDecrypt,
13 } from '@proton/crypto/lib/subtle/aesGcm'
14 import mergeUint8Arrays from '@proton/utils/mergeUint8Arrays'
15 import { stringToUtf8Array } from '@proton/crypto/lib/utils'
16 import type { EncryptionContext } from './EncryptionContext'
17 import { deriveGcmKey } from '../../Crypto/deriveGcmKey'
18 import { HKDF_SALT_SIZE } from '../../Crypto/Constants'
19 import { Result } from '@proton/docs-shared'
20 import type { DriveCompatWrapper } from '@proton/drive-store/lib/DriveCompatWrapper'
22 export class EncryptionService<C extends EncryptionContext> {
25 private driveCompat: DriveCompatWrapper,
27 this.context = context
30 private getContext(associatedData: string) {
31 return `docs.${this.context}.${associatedData}`
34 public async signAndEncryptData(
36 associatedData: string,
37 sessionKey: SessionKey,
38 signingKey: PrivateKeyReference,
39 ): Promise<Result<Uint8Array>> {
41 const contextString = this.getContext(associatedData)
42 const contextBytes = stringToUtf8Array(contextString)
43 const signature = await CryptoProxy.signMessage({
45 signingKeys: signingKey,
46 context: { value: contextString, critical: true },
50 const contentToEncrypt = new SignedPlaintextContent({
55 const hkdfSalt = crypto.getRandomValues(new Uint8Array(HKDF_SALT_SIZE))
56 const key = await deriveGcmKey(sessionKey, hkdfSalt, contextBytes)
57 const ciphertext = await gcmEncryptWith16ByteIV(key, contentToEncrypt.serializeBinary(), contextBytes)
58 return Result.ok(mergeUint8Arrays([hkdfSalt, ciphertext]))
60 return Result.fail(`Failed to sign and encrypt data ${error}`)
64 public async encryptAnonymousData(
66 associatedData: string,
67 sessionKey: SessionKey,
68 ): Promise<Result<Uint8Array>> {
70 const contextString = this.getContext(associatedData)
71 const contextBytes = stringToUtf8Array(contextString)
72 const contentToEncrypt = new SignedPlaintextContent({
76 const hkdfSalt = crypto.getRandomValues(new Uint8Array(HKDF_SALT_SIZE))
77 const key = await deriveGcmKey(sessionKey, hkdfSalt, contextBytes)
78 const ciphertext = await gcmEncryptWith16ByteIV(key, contentToEncrypt.serializeBinary(), contextBytes)
79 return Result.ok(mergeUint8Arrays([hkdfSalt, ciphertext]))
81 return Result.fail(`Failed to sign and encrypt data ${error}`)
85 public async decryptData(
86 encryptedData: Uint8Array,
87 associatedData: string,
88 sessionKey: SessionKey,
89 ): Promise<Result<SignedPlaintextContent>> {
91 const contextString = this.getContext(associatedData)
92 const contextBytes = stringToUtf8Array(contextString)
93 const hkdfSalt = encryptedData.subarray(0, HKDF_SALT_SIZE)
94 const ciphertext = encryptedData.subarray(HKDF_SALT_SIZE)
95 const key = await deriveGcmKey(sessionKey, hkdfSalt, contextBytes)
96 const decryptedData = await gcmDecrypt(key, ciphertext, contextBytes, true)
98 return Result.ok(SignedPlaintextContent.deserializeBinary(decryptedData))
100 return Result.fail(`Failed to decrypt data ${error}`)
106 signature: Uint8Array,
107 associatedData: string,
108 verificationKeys: MaybeArray<PublicKeyReference>,
109 ): Promise<Result<VERIFICATION_STATUS>> {
111 const contextString = this.getContext(associatedData)
112 const { verified } = await CryptoProxy.verifyMessage({
114 binarySignature: signature,
116 context: { value: contextString, required: true },
120 return Result.ok(verified)
122 return Result.fail(`Failed to verify data ${error}`)
126 async getVerificationKey(email: string): Promise<Result<PublicKeyReference[]>> {
127 if (!this.driveCompat.userCompat) {
128 return Result.fail('User compat not available')
132 const value = await this.driveCompat.userCompat.getVerificationKey(email)
134 return Result.ok(value)
136 return Result.fail(`Failed to get verification key ${error}`)