Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP 🔑feat: Implement End-to-End Encryption E2EE Across Messaging #5906

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

rubentalstra
Copy link
Collaborator

@rubentalstra rubentalstra commented Feb 16, 2025

Summary

This PR introduces a comprehensive end-to-end encryption (E2EE) feature that secures both message content and user credentials using modern cryptographic standards. The changes span across backend models, API endpoints, controllers, and client-side components to ensure that sensitive data is encrypted during transit and at rest.

Closes: #5712
reference: #5856

Key highlights include:

  • Message Encryption:

    • AES-GCM Encryption: All outgoing messages are now encrypted using AES-GCM. When a user with an active encryption configuration sends or receives a message, the plaintext is converted to ciphertext using a randomly generated 256-bit AES key and a 12-byte initialization vector (IV).
    • RSA Key Wrapping: The AES key is encrypted with the recipient’s RSA public key (provided in PEM format) using RSA-OAEP with SHA-256. This ensures that only the intended recipient, who possesses the corresponding private key, can decrypt the AES key and then the message content.
    • Data Model Updates: The Message model and its schema have been updated to include additional fields such as iv, authTag, and encryptedKey for storing encryption metadata. These changes propagate through all related methods (e.g., saving, updating, and retrieving messages) to handle encryption parameters properly.
  • User Encryption Settings:

    • Encryption Setup UI: A new UI component, EncryptionPassphrase, has been added to the settings tab. Users can set or change their encryption passphrase. The passphrase is used to derive a symmetric AES-GCM key (via PBKDF2) to encrypt the user’s RSA private key.
    • Key Pair Generation: Upon activation, a new RSA-OAEP key pair is generated. The public key is stored in the user’s profile, while the private key is encrypted using the derived key, with both encryption salt and IV stored as part of the user’s settings.
    • Disabling Encryption: Users also have the option to disable encryption. In this case, all encryption-related fields are set to null, ensuring that no cryptographic operations occur when handling messages or other sensitive data.
  • Controller Enhancements:

    • AskController Enhancements: The response generation logic in AskController now includes an encryption branch. If the user has a valid encryption public key, the plaintext response is encrypted before being sent to the client. This ensures that even automated or AI-generated messages are secured.
    • UserController Updates: Additional endpoints have been added for updating user encryption settings, enabling clients to change their encryption keys or disable encryption entirely.
  • Client-Side Decryption:

    • MessageContent Updates: On the client side, message components (e.g., MessageContent.tsx) have been updated to include decryption logic. If a message is received with encryption metadata (i.e., iv, authTag, and encryptedKey), the client uses the stored RSA private key (after decryption with the user’s passphrase) to decrypt the AES key, which in turn decrypts the message.
    • Error Handling: Enhanced error logging and error UI components are in place to handle decryption failures gracefully, providing users with clear notifications if any issues occur during the decryption process.
  • API & Data Provider Adjustments:

    • New Endpoints: An API endpoint (/api/user/encryption) has been added to handle updates to user encryption settings.
    • Mutation Hooks: New hooks and mutations (e.g., useSetUserEncryptionMutation) have been implemented to seamlessly update user encryption keys from the client.
    • Schema Updates: Data schemas have been revised to ensure that encryption fields are treated as nullable strings, which is vital for proper type handling and backward compatibility.

Change Type

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Testing

To test the E2EE functionality, please follow these steps:

Test Configuration:

  1. Frontend Testing:
    • Encryption Activation:
      • Navigate to the chat settings and activate encryption by entering a valid passphrase. Verify that a new RSA key pair is generated and that the public key is displayed (truncated for brevity) in the UI.
    • Message Encryption/Decryption:
      • Send a message from an account with encryption enabled. On the receiving side, check that the message content is decrypted correctly.
      • Temporarily disable encryption and verify that messages are transmitted and rendered in plaintext.
    • Error Scenarios:
      • Test with an incorrect passphrase to simulate decryption failure. Ensure that a clear error message is displayed and logged.
    • UI Regression: Validate that non-encryption related functionalities (e.g., file attachments, UI layout) continue to operate as expected.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code (especially in the encryption and decryption logic)
  • I have made pertinent documentation changes (see inline comments and updated README/technical docs)
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes
  • Any changes dependent on mine have been merged and published in downstream modules.
  • A pull request for updating the documentation has been submitted.

small issue to fix. when full response is received it replaces the text with the text from the DB. and then the decryption is not yet implement.
# Conflicts:
#	client/src/components/Nav/SettingsTabs/Chat/EncryptionPassphrase.tsx
#	client/src/hooks/SSE/useSSE.ts
#	packages/data-provider/src/types.ts
const { encryptionPublicKey, encryptedPrivateKey, encryptionSalt, encryptionIV } = req.body;

// Allow disabling encryption by passing null for all fields.
const allNull = encryptionPublicKey === null && encryptedPrivateKey === null && encryptionSalt === null && encryptionIV === null;

Check failure

Code scanning / ESLint

Enforce a maximum line length Error

This line has a length of 133. Maximum allowed is 120.
[message.messageId, latestMessage?.messageId],
);
const showCursorState = useMemo(() => showCursor === true && isSubmitting, [showCursor, isSubmitting]);
const isLatestMessage = useMemo(() => message.messageId === latestMessage?.messageId, [message.messageId, latestMessage?.messageId]);

Check failure

Code scanning / ESLint

Enforce a maximum line length Error

This line has a length of 135. Maximum allowed is 120.
@rubentalstra rubentalstra self-assigned this Feb 16, 2025
@rubentalstra rubentalstra changed the title feat: Implement End-to-End Encryption E2EE Across Messaging 🔑feat: Implement End-to-End Encryption E2EE Across Messaging Feb 16, 2025
@rubentalstra
Copy link
Collaborator Author

Screenshot 2025-02-16 at 10 43 45

Comment on lines +891 to +895
user && typeof user === 'object'
? user
: (this.options.req && this.options.req.user
? this.options.req.user
: { id: user });

Check warning

Code scanning / ESLint

Disallow nested ternary expressions Warning

Do not nest ternary expressions.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no nested ternaries please

onClick={disableEncryption}
data-testid="disableEncryption"
>
<span>Disable Encryption</span>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string: Disable Encryption
@rubentalstra
Copy link
Collaborator Author

@danny-avila need some help here 😅 to decrypt the refresh callback? where can I find this because it's a little maze with the chat's and messages and conversations

https://github.com/user-attachments/assets/616be36c-7cca-4b97-8c09-36b25073f720

Screenshot 2025-02-16 at 10 43 45

const { logger } = require('~/config');

let crypto;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this encryption function outside of this module to where the other encryption methods live.

@@ -13,6 +13,77 @@ import Container from './Container';
import Markdown from './Markdown';
import { cn } from '~/utils';
import store from '~/store';
import { useAuthContext } from '~/hooks/AuthContext';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest making no changes here, as it could impact rendering performance

Comment on lines +891 to +895
user && typeof user === 'object'
? user
: (this.options.req && this.options.req.user
? this.options.req.user
: { id: user });
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no nested ternaries please

const dbUser = await getUserById(currentUserId, 'encryptionPublicKey');

// --- NEW ENCRYPTION BLOCK: Encrypt AI response if encryptionPublicKey exists ---
if (dbUser.encryptionPublicKey && message && message.text) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if possible, consolidate the encryption logic to its own function and outside of this module

* @param {string} pemPublicKey - The RSA public key in PEM format.
* @returns {Object} An object containing the ciphertext, iv, authTag, and encryptedKey.
*/
function encryptText(plainText, pemPublicKey) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this encryption function outside of this module to where the other encryption methods live. also it seems duplicated elsewhere

@rubentalstra
Copy link
Collaborator Author

the hook useGetMessagesByConvoId is what makes the api/messages request, that would be the easiest place to start. it should be done within the hook.

() => dataService.getMessagesByConvoId(id),

what this function returns is what ends up getting cached in the query cache, so it would be best to do it here, i think.

to use atom store, you would need to move useGetMessagesByConvoId from packages/data-provider to client/src/data-provider/Messages/queries.ts (doesn't exist yet)

https://tanstack.com/query/v4/docs/framework/react/reference/useQuery

Reference ℹ️ ^

@rubentalstra rubentalstra changed the title 🔑feat: Implement End-to-End Encryption E2EE Across Messaging WIP 🔑feat: Implement End-to-End Encryption E2EE Across Messaging Feb 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Question]: How to enable encryption for the Vector DB and the Mongo DB for a single VM deployment use case
2 participants