diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c8affe671..bf9638586 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,9 @@ package-lock.json # Architecture department for architecture content docs/architecture @bitwarden/dept-architecture +# Key management and Architecture for crypto concepts +docs/architecture/crypto @bitwarden/team-key-management-dev @bitwarden/dept-architecture + # Architecture and AppSec for security concepts docs/architecture/security @bitwarden/team-appsec @bitwarden/dept-architecture diff --git a/custom-words.txt b/custom-words.txt index aaf6cc11e..f3cd42fc1 100644 --- a/custom-words.txt +++ b/custom-words.txt @@ -1,7 +1,6 @@ # Custom dictionary for spellchecking. Before adding a word here, consider whether you can put # it in a single (`) or multiline (```) code snippet instead, as they are automatically ignored # by the spellchecker. Please keep the list sorted alphabetically. - AndroidX AOSP Bitwarden @@ -17,8 +16,8 @@ deinitializer deinitializers dockerized dotfile -frontmatter F-Droid +frontmatter Gitter HKDF hotfix @@ -97,3 +96,14 @@ YubiKeys # Forbidden words !auto-fill !auto-filled +CBOR +ciphertext +Cose +CTXT +HMAC +kibibytes +namespacing +PKCS +plaintexts +unpadded +unsynchronized diff --git a/docs/architecture/cryptography/crypto-guide.md b/docs/architecture/cryptography/crypto-guide.md new file mode 100644 index 000000000..319ff3fb5 --- /dev/null +++ b/docs/architecture/cryptography/crypto-guide.md @@ -0,0 +1,106 @@ +--- +title: How to use Cryptography in Bitwarden +sidebar_label: Cryptography Guide +description: A +--- + +# How to use cryptography in Bitwarden + +This guide is aimed at non-cryptography teams that want to consume cryptographic APIs in order to +build features that need end-to-end encryption. + +## Rules + +The primary rule here is: don't roll your own crypto. Where possible, high level safe, tested and +analyzed protocols and primitives need to be used. The higher level the primitive, the less likely +that security bugs get introduced, and the less complexity for you to maintain and keep track of. +Only where not otherwise possible low level primitives can be used, and this should be done with +extreme caution and external analysis. + +Encryption in the typescript clients for new cases is deprecated. Any new cryptographic code must be +written in the SDK if possible. Existing use-cases can be continued in the typescript clients for +now, but eventually will have to be migrated too. There are several reasons behind this. On the one +hand the SDK has better memory safety guarantees and prevents key material from being left behind in +memory. On the other hand, newer, safer APIs are not exposed outside of the SDK. + +## How do I use cryptography? + +To use cryptographic primitives, you can decompose your feature into a chain of simpler use-cases. +Which cryptographic primitive you use depends on what you want to do. The following, is a list of +use-cases, and the corresponding construction that is currently supported for use. Most features can +be built out of a combination of the below constructs. If you believe your feature cannot be +constructed out of a combination of these, please reach out! + +### I want to protect a document / struct + +Use [DataEnvelope](https://github.com/bitwarden/sdk-internal/pull/336). This handles encryption and +versioning, and hides exact sizes of the encrypted contents. You can follow the existing +[example](https://github.com/bitwarden/sdk-internal/blob/cbc84a33f3cbb59806a472459226150b86cc06e7/crates/bitwarden-crypto/examples/seal_struct.rs). + +#### Historical: EncStrings + +Historically, EncStrings have been used for this process. These are no longer recommended for new +use-cases. + +### I want to protect a file + +Existing attachments are protected using an EncArrayBuffer. This is just an EncString, but encoded +slightly differently. + +#### Future outlook: FileEnvelope + +In the future, a higher-level abstraction will be provided that supports streaming / random access +decryption securely. This is yet to be designed / specified. If you need this, please reach out. + +### I want to protect a key with another key + +Currently, you have to use EncStrings for this. The SDK contains high-level functions for doing this +as shown in this +[example](https://github.com/bitwarden/sdk-internal/blob/03646591c366a5568b0f8062a0cb3b4745bcbd93/crates/bitwarden-crypto/src/store/context.rs#L154). + +#### Future outlook: KeyEnvelope + +In the future, a CoseEncrypt0 message with more context will be provided that supports advertising +the contained key id, so that the server can validate key relationships. Further, strong context +binding / namespacing will be provided. + +### I want to protect a key with a password + +Use +[PasswordProtectedKeyEnvelope](https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-crypto/src/safe/password_protected_key_envelope.rs) +as described in +[example](https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-crypto/examples/protect_key_with_password.rs). +This allows you to store a key with a low-entropy password or PIN. The envelope handles brute-force +protection. + +#### Historical: MasterPasswordUnlockData + +MasterPasswordUnlockData is a struct that encapsulates the data needed to unlock a vault using a +master password. It contains the protected symmetric key that is encrypted with the stretched master +key, along with the KDF settings and salt used. The cryptography used is the same as for using the +master key directly, but the abstraction is safer and prevents decryption issues resulting from +unsynchronized state. + +#### Historical: MasterKey + +Historically, the master-key was used to protect keys with passwords. The master key is derived from +the user's master password using PBKDF2 or Argon2id user's email address as salt and the +synchronized account KDF parameters, producing a 256-bit key. This master key is then expanded using +HKDF into a 512-bit stretched master key, 256-bit of which are used as an aes256-cbc key, and +256-bit of which are used as an HMAC key. The stretched master key is used to encrypt the user's +symmetric key. + +New usage of MasterKey is not supported. + +### I want to authenticate with a password + +Use MasterPasswordAuthenticationData. It encapsulates the data needed to unlock a vault using a +master password. It contains the serverAuthorizationMasterKeyHash, the KDF settings and salt used. +The cryptography is the same as for MasterKey based authentication, but the abstraction prevents +authentication issues resulting from unsynchronized state. + +#### Historical: MasterKey + +The master-key used for unlock is also re-used for authentication. The +severAuthorizationMasterKeyHash is derived from the master-key using pbkdf2, with the password as a +salt and 1 iteration applied. This hash is then sent to the server for authentication. diff --git a/docs/architecture/cryptography/index.md b/docs/architecture/cryptography/index.md new file mode 100644 index 000000000..21e9e9f94 --- /dev/null +++ b/docs/architecture/cryptography/index.md @@ -0,0 +1,16 @@ +--- +sidebar_position: 7 +--- + +# Cryptography + +This document in detail describes how cryptography in Bitwarden should be used, and serves as a +reference of how it is implemented. + +Currently, there is a set of low-level APIs (EncString, UnsignedSharedKey, MasterKey) that have been +historically used to build most features, with each team owning the cryptographic constructions +created. Increasingly, high-level safe primitives are introduced that move the complexity out of +each teams ownership. These are not yet complete, and if your use-case is not covered by them, +please reach out! The goal of these is to have most teams never have to think about cryptography, or +having to do safety analysis. These abstract away all complex details and give teams a +low-complexity, easy to use and hard to mis-use interface to work with. diff --git a/docs/architecture/cryptography/specification/data-envelope.md b/docs/architecture/cryptography/specification/data-envelope.md new file mode 100644 index 000000000..cf4653c7a --- /dev/null +++ b/docs/architecture/cryptography/specification/data-envelope.md @@ -0,0 +1,89 @@ +--- +sidebar_position: 3 +--- + +# Data Envelope + +Data envelope is a cryptographic format for encrypting structured data (documents). It addresses the +problem of: "I have a struct of related data that I want to protect from tampering and unauthorized +access.". It can be used for encrypting structured data such as vault items, reports, or user +settings, that are stored long-term. To solve existing usability goals, it includes versioning and +makes key-rotation and key-sharing simple by enforcing the use of per-document content-encryption +keys. + +Data envelope is **not** designed for: + +1. Encrypting cryptographic keys +2. Encrypting large binary blobs like such as file attachments + +## Security + +Data envelope fulfills three core security goals that formalize what end-to-end encryption means: + +1. **SG1: Integrity (INT-CTXT security)** - The ciphertext must not be malleable. An attacker with + full control over the encrypted data cannot modify it in ways that result in different but valid + plaintexts. +2. **SG2: Confidentiality (IND-CCA security)** - The attacker cannot infer information about the + plaintext contents beyond approximate length. The format uses padding to minimize length leakage. +3. **SG3: Context binding** - Data can only be decrypted in the correct context. For example, an + encrypted vault item cannot be swapped into a user settings slot, preventing undefined behavior + and security bugs. + +### Attacker model + +The attacker has complete control over the server and all data in transit (fully compromised server +model per P01 - Servers are zero knowledge). The attacker can: + +1. Read all encrypted data +2. Modify or replace encrypted data +3. Replay old versions of encrypted data + +## Format specification + +A data envelope is a COSE_Encrypt0 structure with the following components: + +### Protected header + +The protected header contains: + +1. **Algorithm (alg)**: Set to XChaCha20-Poly1305 (`-70000`) +2. **Key ID (kid)**: Identifier of the content encryption key used +3. **Content type**: Set to `"application/x.bitwarden.cbor-padded"` +4. **Namespace**: Custom header field containing an integer identifying the document type + +### Unprotected header + +The unprotected header contains: + +1. **Initialization vector (iv)**: The nonce used for XChaCha20-Poly1305 encryption + +### Serialization format + +1. **Document encoding**: The plaintext document is serialized using CBOR +2. **Padding**: PKCS#5-style padding to 64-byte blocks is applied to hide the exact size + +### Namespaces + +Each document type is assigned a unique integer namespace identifier. The namespace is stored in the +protected header and validated during decryption. Examples: + +``` +VaultItem = 1 +UserSettings = 2 +Report = 3 +``` + +Namespaces prevent documents from being decrypted in the wrong context, even if an attacker attempts +to substitute one encrypted document for another. + +### Versioning + +Documents support internal versioning. The direct inner contents of the unpadded payload are +(represented as json, but in reality encoded as CBOR): + +``` +{ + version: "1", + content: {...} +} +``` diff --git a/docs/architecture/cryptography/specification/index.md b/docs/architecture/cryptography/specification/index.md new file mode 100644 index 000000000..d41bae5df --- /dev/null +++ b/docs/architecture/cryptography/specification/index.md @@ -0,0 +1,9 @@ +--- +sidebar_position: 0 +--- + +# Specification + +Specification covers the design of the various cryptographic constructs used in Bitwarden, and +explains the rationale behind them. _This is section is not meant for consumers of the APIs, but for +developers working on the cryptography and for security researches verifying the designs._ diff --git a/docs/architecture/cryptography/specification/password-protected-key-envelope.md b/docs/architecture/cryptography/specification/password-protected-key-envelope.md new file mode 100644 index 000000000..4d9f1e885 --- /dev/null +++ b/docs/architecture/cryptography/specification/password-protected-key-envelope.md @@ -0,0 +1,143 @@ +--- +sidebar_position: 2 +--- + +# Password-Protected Key Envelope + +## Overview + +The Password-Protected Key Envelope is a cryptographic building block that enables sealing a +symmetric key with a low-entropy secret (such as a password or PIN). It produces an opaque blob that +can later be unsealed using the same password. This primitive is designed to protect cryptographic +keys at rest using user-memorable passwords, while defending against brute-force attacks through the +use of a memory-hard key derivation function. + +The Password-Protected Key Envelope is used when a symmetric encryption key needs to be stored +securely using only a user-provided password, or other low-entropy secret + +Common applications can include: + +- Protecting the account symmetric key for local unlock with a PIN +- Creating a password protected export +- Creating a url-shared encrypted item (sends) + - The URL would contain the low-entropy secret that is used as the password, as the fragment + +## Security + +The Password-Protected Key Envelope design intends following security goals: + +1. **SG1: Integrity (INT-CTXT security)** - The ciphertext must not be malleable. An attacker with + full control over the encrypted data cannot modify it in ways that result in different but valid + plaintexts. +2. **SG2: Confidentiality (IND-CCA security)** - The attacker cannot infer information about the + contained key. +3. **SG3: Brute-force resistance**: A low entropy secret is used, so brute-forcing the secret must + be made costly, while keeping unlock time reasonable. + +### Attacker model + +The Password-Protected Key Envelope is designed to protect against: + +- **Offline brute-force attacks**: An attacker with a copy of the envelope but not the password must + perform expensive KDF computations for each password guess +- **Rainbow table attacks**: Unique random salts prevent pre-computation attacks +- **Tampering detection**: Modification of the envelope or its parameters will be detected during + decryption + +### Cryptographic primitives + +The envelope uses the following cryptographic primitives: + +- **Key Derivation Function (KDF)**: Argon2id version 0x13 +- **Authenticated Encryption**: XChaCha20-Poly1305 +- **Random Number Generation**: Cryptographically secure random number generator for salt and nonce + generation + +## Specification + +### High-level operation + +#### Sealing + +1. Generate a random salt of 16 bytes +2. Derive an "envelope key" from the password and salt using Argon2id +3. Encrypt the target symmetric key using XChaCha20-Poly1305 with the envelope key +4. Package the ciphertext, nonce, KDF parameters, and salt into a COSE_Encrypt structure + +#### Unsealing + +1. Extract the KDF parameters and salt from the envelope +2. Derive the envelope key using the same Argon2id parameters and provided password +3. Decrypt the ciphertext using XChaCha20-Poly1305 with the derived envelope key +4. If decryption succeeds, return the unsealed symmetric key; if it fails, the password is incorrect + or the envelope was tampered with + +### Encoding format + +The Password-Protected Key Envelope is encoded as a COSE_Encrypt object. + +#### Structure + +```text +COSE_Encrypt = [ + protected: bstr, // Serialized protected headers + unprotected: { // Unprotected headers + 5: bstr // IV/nonce (24 bytes for XChaCha20) + }, + ciphertext: bstr, // Encrypted key material + recipients: [ // Array with exactly one recipient + COSE_Recipient + ] +] + +COSE_Recipient = [ + protected: { + 1: -70007 // Algorithm: Argon2id (private use) + }, + unprotected: { // Argon2id parameters + 70023: int, // Iterations + 70024: int, // Memory in KiB + 70025: int, // Parallelism + 70026: bstr // Salt (16 bytes) + }, + ciphertext: null +] +``` + +#### Protected headers + +The protected header contains the content format identifier: + +- `3`: Content type label + - `"application/x.bitwarden.legacy-key"`: Legacy Bitwarden key format + - Value `101`: COSE key format + +#### Unprotected headers + +The main COSE_Encrypt unprotected header contains: + +- `5`: IV/nonce for XChaCha20-Poly1305 (24 bytes) + +#### Recipient unprotected headers + +The single recipient's unprotected headers contain Argon2id parameters: + +- `70023` (ARGON2_ITERATIONS): Number of iterations (u32) +- `70024` (ARGON2_MEMORY): Memory in kibibytes/KiB (u32) +- `70025` (ARGON2_PARALLELISM): Degree of parallelism (u32) +- `70026` (ARGON2_SALT): Random salt (16 bytes) + +#### Algorithm identifiers + +- **Argon2id**: Private use algorithm identifier `-70007` (ALG_ARGON2ID13) + +### Serialization + +The COSE_Encrypt structure is serialized using CBOR (Concise Binary Object Representation) and then +encoded using Base64 for text-based storage and transmission. + +#### Wire format + +```text +Base64(CBOR(COSE_Encrypt)) +``` diff --git a/docs/architecture/cryptography/specification/user-encryption.md b/docs/architecture/cryptography/specification/user-encryption.md new file mode 100644 index 000000000..8946e70a1 --- /dev/null +++ b/docs/architecture/cryptography/specification/user-encryption.md @@ -0,0 +1,92 @@ +--- +title: User encryption (legacy ➜ V2) +sidebar_label: User encryption +description: Overview of legacy (V0/V1) and the 2025 V2 user encryption schemes in Bitwarden. +--- + +# User encryption (legacy ➜ V2) + +This page describes previously existing user encryption schemes and the 2025 V2 redesign. + +## V0: Master-key users (legacy) + +These users have no user key. Encryption is performed directly with the 256-bit master key, and is +thus tightly coupled to master password or Key Connector encryption. Encryption used AES-CBC without +HMAC. Support for these users was discontinued in 2025 for technical-debt reasons and because these +users introduced vulnerabilities that could affect other users. + +Optionally, these users had a public-key encryption key pair. + +## V1: User-key users + +The user key is a central, account-wide symmetric key that protects everything downstream from it. +It was first added in 2017 +([server introduction](https://github.com/bitwarden/server/commit/a01d5d9a51d0175e9c3e39fa8271a469df07a105#diff-4ca29d3671adb5899fda48584f1107536495573ad37151297ee7599bd8424e98)). +The user key has only ever been AES-CBC with HMAC +([clients change](https://github.com/bitwarden/clients/commit/3845c55155bd928bae6fb8b58822f49b21afc071#diff-0e526b2c7acdbb0c577346cd6ce9d251aea8880900e11ed361cd580e04712e90R240)). + +### Legacy master-password wrapping + +For a brief period of time, the master key was not stretched when saving the master-key-wrapped user +key. This allowed unauthenticated user-key material to be stored in the database. + +### Stretched master-password wrapping + +The current (2017–2025) approach to storing the user key for authentication and unlock with a master +password for most users is using a stretched master key. Here, the user key is wrapped with +(encrypted with) the stretched master key. + +Optionally (for most users), these users had a public-key encryption key pair. + +## V2: 2025 user-encryption scheme + +In 2025, a new user-encryption scheme is introduced, aimed to provide a clean break to V1 and to +introduce several security and stability enhancements. + +### COSE + +Encodings for keys, encrypted messages, and signatures now all happen in COSE instead of custom +Bitwarden EncString encodings. This is more flexible, standardized, and security tested. COSE has +support or at least RFCs for all relevant cryptographic algorithms, and is also extensible. + +### Main cryptographic changes + +The user key is a COSE-encoded XChaCha20-Poly1305 key. Anything encrypted directly with the user key +is a COSE Encrypt0 message. The public-key encryption keypair is mandatory. A new signature keypair +(also encoded as COSE) is introduced, which is also mandatory. Finally, a signed “security state” +object is introduced, which is also mandatory. Any of these items missing or failing to decrypt, +means that the client must reject the unlock / login process. + +### Stability improvements + +Each COSE key (signing, XChaCha20-Poly1305 user key) has a key ID. This key ID is unique to the key +(locally generated). It is written onto every encrypted object. Thus the server can now validate the +key ID of a specific object against the keys the user owns. + +For instance, we can introduce a user key ID column that tracks the ID of the user key. This can be +used on the server side to validate the key ID of a modified or newly uploaded cipher against the +key ID of the user to verify that the key is correct. This is not a security feature, but a +stability feature. + +### User signature key pair + +In addition to the encryption private key, the user now also has a signature key pair that can be +used to sign messages that can be verified by other users. The signature key pair is the new root of +trust between users. Users - in the medium term - no longer trust another user’s public encryption +key by fingerprint, but they trust the signature key pair. + +The signature key pair signs the encryption public key and the security state. Since the signature +key pair is the new root of trust, this signature can be used to find the correct public key for a +user when sharing secrets with them. + +### Signed security state + +The security state aims to solve a systematic problem with the V1 account format. There is no +cryptographically attested versioning. This means that a class of vulnerabilities are hard to solve. +This makes it hard to solve issues where new data must be introduced to fix an issue (such as with +the not yet rolled out icon URL fixes that add an encrypted and authenticated checksum of the item +URL). + +In case a format or feature becomes insecure, a user’s account can be migrated, and the signed +security states version can be bumped. Then, clients no longer accept migrations of the feature, so +that a server cannot create downgraded / insecure versions.