npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nhtio/rocketchat-server-side-e2ee

v0.1.0-master-3eb6a4c9

Published

This library provides a drop-in API for allowing RocketChat Apps-Engine Apps to participate in conversations using end-to-end encryption (E2EE).

Downloads

133

Readme

RocketChat Server Side End to End Encryption

This library provides a drop-in API for allowing RocketChat Apps-Engine Apps to participate in conversations using end-to-end encryption (E2EE).

Security Warning

This implementation allows a server-side application to decrypt encrypted messages in an encrypted conversation. Applications implementing this library should be explicit about their usage with clear reasoning for its usage and purpose. App consumers should be explicitly warned about the security implications of allowing programmatic access to encrypted conversations so they can evaluate the risks before installing and using the application.

Why Apps-Engine Apps Cannot Participate Today

The server cannot decrypt (by design)

  • The room key is never available to the server in plaintext.
  • Server stores per-user encrypted room keys; only the client with the RSA private key can decrypt.

Apps-Engine surface does not expose needed fields

Even if an app wanted to decrypt:

  • Apps-Engine message hooks receive an IMessage abstraction.
  • The E2EE ciphertext payload is stored in message content (server-side document), but Apps-Engine does not expose that field via its interfaces.
  • Apps-Engine also does not expose subscription key fields (E2EKey, E2ESuggestedKey, key history).

Crypto runtime mismatch

  • Rocket.Chat's E2EE implementation uses browser WebCrypto.
  • Apps-Engine apps run server-side and would need a Node crypto implementation.

Key distribution requires identity

  • Room keys are encrypted to recipients’ RSA public keys.
  • An app would need a distinct keypair and be treated as a room participant/device.

E2EE architecture in Rocket.Chat

This section is a technical reference for how E2EE is implemented in Rocket.Chat core. Understanding this architecture is necessary to build a compatible server-side participant.

Where E2EE lives

The actual cryptographic operations and key handling are entirely client-side, implemented in Meteor client code under apps/meteor/client/lib/e2ee/:

  • Orchestration: rocketchat.e2e.ts — top-level E2EE lifecycle
  • Per-room encrypt/decrypt: rocketchat.e2e.room.ts
  • Keychain / private-key wrapping: keychain.ts
  • Encrypted content parsing (v1/v2): content.ts
  • WebCrypto wrappers: crypto/aes.ts, crypto/rsa.ts, crypto/pbkdf2.ts, crypto/shared.ts

The server never sees plaintext. Server-side code under apps/meteor/app/e2e/server/ only stores public keys, encrypted private keys, and relays per-user encrypted room keys:

  • Key upload/download: methods/fetchMyKeys.ts, methods/setUserPublicAndPrivateKeys.ts
  • Room key distribution / suggested keys: methods/updateGroupKey.ts, methods/requestSubscriptionKeys.ts, functions/provideUsersSuggestedGroupKeys.ts
  • Room key ID and rotation: methods/setRoomKeyID.ts, functions/resetRoomKey.ts

The REST API surface is defined in apps/meteor/app/api/server/v1/e2e.ts.

Crypto primitives

Rocket.Chat uses the browser's Web Crypto API (window.crypto.subtle) with the following algorithms:

  • User keypair: RSA-OAEP 2048-bit, SHA-256
  • Private key protection:
    • Current: AES-GCM (256-bit) + PBKDF2 SHA-256 (100,000 iterations)
    • Legacy: AES-CBC + PBKDF2 (lower iteration count)
  • Room session/group key: AES-GCM (256-bit)
  • Message encryption: AES-GCM with random 12-byte IV
  • Files: AES-CTR with per-file key + SHA-256 integrity metadata

Key management and distribution

User keys — The server stores the user's plaintext public key (JWK) and an encrypted private key blob (AES-GCM wrapped with the user's passphrase). The client decrypts the private key using the passphrase and holds the plaintext key only in memory.

Room (group/session) keys — Each room has a key ID (rooms.e2eKeyId). Every member receives a copy of the room's AES session key encrypted to their RSA public key, stored in per-user subscription fields (subscriptions.E2EKey, subscriptions.E2ESuggestedKey).

When a new member joins:

  1. The server marks them as needing a key.
  2. Any existing member who already has the room key encrypts it to the new member's public key and posts it as a suggested key.
  3. The new member accepts after verifying they can decrypt.

On rotation, the server generates a new key ID and invalidates the current key. Old keys are preserved client-side via per-user history so older messages can still be decrypted.

Message format (wire envelope)

The server stores only the ciphertext envelope; plaintext is never persisted. The current (v2) encrypted message shape is:

  • t: 'e2e' — message type marker
  • e2e: 'pending' | 'done' — encryption status
  • content — the encrypted envelope:
    • algorithm: e.g. rc.v2.aes-sha2
    • kid: room key UUID
    • iv: base64-encoded initialization vector
    • ciphertext: base64-encoded ciphertext

The msg field is absent or empty for encrypted messages.

What this library provides

The goal is to treat an Apps-Engine app as a cryptographic participant — giving it a managed identity, access to room keys, and the crypto primitives needed to read and write encrypted messages.

App identity and key management

An app is introduced as a first-class E2EE participant with its own RSA-OAEP keypair managed server-side. The private key should be protected via a KMS or secret store; encrypted-at-rest is the minimum acceptable baseline. Each app identity is scoped to a specific set of rooms it is authorized to participate in.

  • [ ] Generate and store an RSA-OAEP keypair per app
  • [ ] Protect the private key (KMS / secret store / encrypted-at-rest)
  • [ ] Associate app identity with an app id and an authorized room list
  • [ ] Implement key rotation flow
  • [ ] Implement key revocation flow

Expose encrypted payloads to Apps-Engine (read path)

Apps-Engine message hooks currently receive an IMessage abstraction that does not expose the E2EE ciphertext stored in the server-side content field. To allow an app to decrypt, the encrypted content envelope must be surfaced.

  • [ ] Extend Apps-Engine IMessage to include encryptedContent?: { algorithm: string; kid: string; iv: string; ciphertext: string } for t:'e2e' messages
  • [ ] Add runtime changes to populate the field when delivering messages to apps
  • [ ] Version the capability so older apps are not broken

Expose key distribution and key store APIs to Apps-Engine

An app needs access to the AES session key for each room it participates in. The recommended model is direct key provisioning: when a room is encrypted and the app is authorized, the server delivers the current room key encrypted to the app’s public key and notifies the app on rotation. An alternative is to let the app appear in the E2ESuggestedKey waiting queue and receive keys through the standard client flow.

  • [ ] Implement getEncryptedRoomKey(roomId, kid) API for apps
  • [ ] Emit key rotation events to authorized apps
  • [ ] Expose key history API for decrypting older messages

Node-side crypto compatible with the RC client format

The RC client uses browser WebCrypto. Server-side crypto must produce and consume the same wire format so that client-encrypted messages can be decrypted by the app and vice versa.

  • [ ] Implement RSA-OAEP encrypt/decrypt with SHA-256
  • [ ] Implement AES-GCM encrypt/decrypt
  • [ ] Implement wire envelope parsing and encoding matching apps/meteor/client/lib/e2ee/content.ts
  • [ ] Handle EJSON serialization of plaintext payloads
  • [ ] Write interop tests: client-encrypt ↔ server-decrypt and server-encrypt ↔ client-decrypt

Apps-Engine E2EE send support (write path)

An authorized app should be able to send an encrypted message to an encrypted room. This can be exposed as a high-level sendEncryptedMessage(roomId, plaintext) method that the server encrypts and stores as a t:'e2e' message, or as a lower-level API that lets the app build the envelope itself.

  • [ ] Implement send API (high-level or envelope-based)
  • [ ] Enforce that only authorized apps with a valid room key can send
  • [ ] Ensure plaintext is never passed to message indexing or search

References