1. Secret Logo

Secrets Security Design

Secrets was designed to prioritize security from the very beginning. This entails the use of standard and robust encryption algorithms, as well as the implementation of every feature with end-to-end encryption in mind. This document provides an in-depth explanation of how Secrets handles user data and the reasoning behind its approach. For information regarding the security of previous versions of Secrets, you can click here.

The target audience for this document includes anyone with an interest in understanding how Secrets ensures the security of user data. While a basic understanding of encryption is recommended, it is not necessary. When discussing standard encryption algorithms or protocols, explanations of their provided properties will be provided as well.

If you're just seeking a brief overview of the encryption methods employed, here it is:

Secrets utilizes Libsodium for the majority of its cryptographic functions. Specifically, your data is encrypted with a 256-bit key that is itself encrypted using an Elliptic Curve key pair stored and safeguarded by Apple's keychain on your device. This key in the keychain can be secured with either biometric or password authentication, or both. Items are encrypted with XSalsa20+Poly1305, while file attachments employ XChaCha20+Poly1305.

If you're interested in a more more comprehensive explanation of how your information is protected, keep reading.

Data Storage

Data Storage

Overview

Secrets utilizes a proprietary persistence layer that has been designed based on the lessons learned from earlier versions and inspired by storage solutions like Git and Datomic.

The fundamental unit of storage in Secrets is called a Fact, which consists of an entity, attribute, and value1. Here are some examples of facts:

EntityAttributeValue
Login/1C46B925…usernamePaulo
Service/799FD6B0…addresshttps://secrets.app
Attachment/F7E463B4…itemLogin/1C46B925…

This structure is similar to a conventional key-value storage system, with the addition of the entity value. In Secrets, the entity is composed of the item's type and a UUID.

When saving data, Secrets collects the current changes into a list of facts and appends them to the storage. This collection of facts is referred to as a Commit.

The storage system is designed to be append-only, and facts are immutable. This does not mean that you cannot change a Login's username; rather, when you do, a new fact is created and written to disk.

The persistence layer extensively employs hashes for data indexing. Each commit is a Merkle tree, with each node of the tree transformed into a blob and stored on disk, with its hash value serving as the key2. Secrets uses BLAKE2b for its hashing requirements.

Disk storage will also coalesce these blobs in files named by the hex value of the first hash value byte. For instance, a blob with the hash 339de1115336b4… would be stored in the file 33. Similarly, files are organized into directories named after the first byte of their hash. You can check this by opening the Secrets.sct4 file package3.

Storage is divided into user-managed groups called Vaults. A Vault can be categorized as:

  • Local: Only present on the device that created it.
  • Synced: Present on all of the user's devices and iCloud.
  • Shared: Present on the devices of the vault's owner and members, as well as iCloud (the owner determines who the members are).

Synced and Shared vaults are store on iCloud, CloudKit to be more precise, under separate zones. Each of these vaults has a one to one mapping to a CloudKit zone. Blobs are stored as regular CloudKit records and files as CloudKit assets.

Encryption

As mentioned in the Overview, prior to writing, the persistence layer only deals with a blob and its hash, or a file and its hash. Blobs are encrypted using XSalsa20+Poly1305, while files are encrypted using XChaCha20+Poly1305. Both encryption algorithms are provided by Libsodium.

Hash values are also encrypted. They are encrypted using AES-128 in CBC mode. A new 256-bit key, specifically for encrypting these hashes, is derived from the storage key. The first 128 bits are utilized as an initialization vector, and the remaining bits serve as the AES key. Since the hash length is a multiple of the AES block size, no padding is necessary. This approach effectively conceals the hash value without requiring additional space. Hiding the hashes prevents the possibility of determining if a given value (e.g., a known file) is present in a Secrets database.

For iCloud-synced vaults, the encryption of blobs, files, and hashes mirrors the disk storage encryption. Further details regarding the sharing of vault keys among devices and members are provided in the following sections.

The 256-bit storage key, which protects each Secrets document, is encrypted using the device keys and can optionally be encrypted with one or more recovery keys.

Device Keys

Each device running Secrets generates a long-term key that uniquely identifies the device and provides encryption and authentication. Specifically, two keys are created: an identity Ed25519 key for signatures and an X25519 key for encryption.

These keys are stored in the device's operating system Keychain. Depending on the device, the keychain entry can be protected by various authentication options provided by the Keychain, including Face ID, Touch ID, passcode, passphrase, and Watch. You can learn more about the different authentication options in the "Securing your secrets" support page.

Unlike previous versions of Secrets, users can now utilize Secrets without ever setting up a passphrase. The keychain entry is also created with the protection set to kSecAttrAccessibleWhenUnlockedThisDeviceOnly, meaning the keys never leave the device, even during backups.

This means there are various scenarios in which a user could be locked out of Secrets, such as a malfunctioning Touch ID or Face ID sensor, unreadable fingerprint, loss of the device despite having backups, and so on.

To address this issue, Secrets allows the creation of a recovery key pair (X25519) that also encrypts the vault's 256-bit symmetric key. During creation, the private part of this key pair is printed on paper. The printed key can later be used to decrypt the document from a backup in case of sensor malfunction or other similar situations.

Recovery keys can be created for a specific document on each device. Alternatively, a Paper Device can serve as a recovery key and is automatically used by all of the user's devices.

Footnotes

  1. In Datomic this would be called a Datom.

  2. If you know how Git stores its objects this will sound familiar.

  3. On macOS, you can find this file at ~/Library/Containers/com.outercorner.Secrets/Data/Documents/Secrets.sct4

Trust Chains

Fundamentals

In order to facilitate trust among devices owned by a user, Secrets implements a concept called the "Trust Chain." The Trust Chain serves as a tamper-proof ledger of operations1 and consists of a series of links.

struct Link {
    var sequenceNo: Int
    var operation: Operation
    var previousLinkHash: Data
    var linkHash: Data
    var signature: Signature
}
  • sequenceNo: This represents the sequence number of the link in the chain. It starts from 0 for the first link and increments sequentially for each subsequent link. The sequence number serves several purposes:
    1. It establishes the order of events represented by the Trust Chain. To determine the currently valid set of operations, the Trust Chain must be replayed in the same order. The sequence number allows for easy sorting of the links.
    2. It can be used as a unique identifier for each link when stored in a database table. The sequence number serves as a good candidate for a UNIQUE constraint.
    3. It helps detect gaps in the sequence number, which would invalidate the chain. This guards against omission attacks, where an attacker attempts to omit certain links from the chain (e.g., revoking a device).
    4. For long chains, a device can store the sequence number of the last link it has processed. When new links are added, the device can simply process the new additions instead of replaying the entire chain.
  • operation: This field represents the specific operation being added to the Trust Chain. Examples of operations include adding or revoking a device, or rolling the user keys. The details of these operations will be explained later.
  • previousLinkHash: This field contains the hash value of the link that precedes the current one. It establishes the relationship with the previous link, ensuring that the links are created in sequential order. By referencing the previous link's hash, only the signature of the current link needs to be validated. However, the hashes of all previously processed links still need to be verified to prevent data replacement on earlier links while preserving the hash integrity.
  • hash: This field represents the hash of all the previous attributes (including the previousLinkHash) concatenated with the public key used in the signature. It provides a unique identifier for the link and ensures data integrity.
  • signature: The signature field contains the signature of the link's hash, created using the private key of the link's author. The signature serves as proof of authenticity and ensures that the link has not been tampered with.

By utilizing the Trust Chain, Secrets establishes a secure and verifiable history of operations, allowing devices to trust each other and enabling actions on behalf of the user.

Device Trust Chain

The Device Trust Chain serves the purpose of establishing the identity of a user's devices. In this context, "identity" refers to the device's keys. The devices that are part of the Device Trust Chain are considered the user's "trusted devices."

The chain begins when a user sets up their first device. This device initiates a new Device Trust Chain by creating a Link with a sequence number of 0. The Link contains an AddDevice operation, which includes the device's identity (the public key portion of the device keys), its APNS token (used for Apple Push Notification service), as well as its name and model. The device then signs this link and stores it in the private default zone of CloudKit.

It's important to note that this first link can only be created by a non-trusted device, as it is the device that starts the Device Trust Chain.

For any subsequent device that the user wishes to set up, authorization must be obtained from a currently trusted device in the Device Trust Chain. This ensures that only trusted devices can add new devices to the chain and maintain the integrity of the trust relationship.

Trusting a new device

The process of requesting authorization from a trusted device involves several steps:

  1. The device requesting authorization establishes a connection to a stateless WebSocket server operated by Outer Corner. Upon connection, it receives an ID from the server. The WebSocket server acts as a bridge between the connecting parties but does not retain any information about them.
  2. A push notification is sent to the trusted device, requesting authorization and including the ID obtained in the previous step.
  3. The trusted device connects to the same WebSocket server and requests communication with the device identified by the given ID.
  4. An end-to-end encrypted session is established between the two devices using the Noise protocol. Specifically, the Noise_NX_25519_ChaChaPoly_SHA256 variant of the protocol is employed.
  5. The requesting device uses the encrypting key of its device keys as the local static key for the Noise protocol. By successfully establishing the session, the authorizer can be certain that the requester possesses the private part of the encrypting key.
  6. The authorizer generates 16 bytes of random data, which will serve as a challenge, and sends it to the requester.
  7. The requester signs the hash of its device identity keys (specifically, the public part), along with the received challenge and the handshake hash from the Noise protocol. It then sends this signed data back to the authorizer.
  8. The authorizer validates the signature. By combining this step with step 5, the authorizer can confirm that the requester owns both the encrypting and signing device keys.
  9. The final step aims to ensure that there is no man-in-the-middle interference during the Noise session. To achieve this, a 4-digit number is generated using a combination of the handshake hash and 16 bytes of random data. The user is then prompted to input this number on the authorizer's device. This verification process helps prevent potential attacks.

Revoking an existing device

Any trusted device can easily revoke another trusted device by adding a new link with the RevokeDevice operation.

Identity Trust Chain

The Identity Trust Chain is established alongside the Device Trust Chain and serves the purpose of managing the user's identity keys. Just as each device has its own set of keys, the user also possesses a set of signing and encrypting keys. The Identity Trust Chain keeps track of the user's current identity keys.

In addition to the identity keys, there is another set of transient keys used for encryption. These transient keys are automatically rotated every 30 days. The public part of the transient key-pair is not included in the trust chain but is stored in the user's public record on iCloud. It is signed with the user's current identity keys.

Both the identity keys and the transient keys are utilized when encrypting data intended for a specific user. This encryption process is further explained in the section on Vault Keys.

The identity chain, which consists of the user's identity keys, is stored in the public database of iCloud. Other users can access this chain to determine the current identity keys for a given user.

To facilitate the sharing of the private part of the user's identity keys and transient key-pair among trusted devices, the Libsodium library's crypto_box function is employed. When a device rolls its transient key-pair or identity keys, it generates an encrypted version of the private keys for each of the other trusted devices, ensuring secure sharing and synchronization of these keys.

Member Trust Chain

The Member Trust Chain serves the purpose of managing the current members of a specific vault. It operates in a similar manner to the Device Trust Chain, but instead of adding and revoking devices, it handles the addition and removal of users to the vault.

Just as the set of trusted devices shares the private part of the user's keys, the current members of a vault share the vault's symmetric key. This symmetric key is used for encrypting and decrypting the vault's contents.

The Member Trust Chain is stored in the same shared iCloud zone that is utilized for sharing the data of the vault. This ensures that the trust chain and the vault's data are stored and synchronized together for secure access and management by the authorized members of the vault.

Footnotes

  1. Think blockchain but without the environmental impact.

Cloud Storage

Summary

Currently Secrets uses iCloud for storing both Synced and Shared vaults. Each vault gets its own zone in the private database of the user that creates it. When sharing a vault, invited users will have access to it via their shared database on iCloud.

All operations related to sharing, adding and removing users are done exclusively through CloudKit APIs. Outer Corner cannot know, for example, who is a member of each vault, how many vaults are shared, etc.

Vault Keys

The vault key in Secrets is a 256-bit symmetric key that is responsible for encrypting the vault. When encrypting the vault key for each member, Secrets employs a process similar to Signal's X3DH protocol, but without the use of one-time pre-keys.

Here's how the encryption of the vault key works in Secrets:

  1. When a device encrypts the vault key for a member, it generates a new ephemeral X25519 key-pair.
  2. The device then performs double Diffie-Hellman on both the long-term encryption key and the transient key of the member using the ephemeral key.
  3. The output of both Diffie-Hellman operations is combined, and the BLAKE2b hash of the resulting value becomes the symmetric key used for encrypting the vault's key.

It's important to note that Secrets does not utilize one-time pre-keys from X3DH. These pre-keys are designed for use in chat protocols like Signal, where each message has its own unique one-time pre-key. However, in Secrets, the goal is to encrypt the vault key, not individual messages. Therefore, incorporating the complexity of one-time pre-keys would not provide significant benefits. It would be similar to including the entire history of exchanged messages within each message in Signal.

Adding a user

  1. The owner of the vault retrieves the user's CloudKit user record, which contains their identity and transient keys.
  2. The owner fetches the participant to be added to the share.
  3. The user is added to the Member Trust Chain, and the current vault shared key is encrypted for the user using the double Diffie-Hellman (DH) method mentioned above.
  4. The participant is added to the CloudKit share and the share is saved.
  5. The share URL is then sent to the user as an invitation link.

Removing a user

  1. The participant to be removed is first removed from the participant list of the CloudKit share.
  2. The user is also removed from the Member Trust Chain.
  3. A new vault key is generated, and the new key is encrypted for all remaining members of the vault.

Existing data in the database remains protected with the old key, while new data saved to the database uses the new key. The database keeps track of all historic keys.

Other Features

Secrets Helper (Mac)

On the Mac, the Secrets Helper is a small utility app that Secrets starts automatically on launch. It's purpose is to facilitate a few different features on the Mac:

  • Displaying an easily accessible icon on the menubar
  • Observing the running applications to offer shortcuts to actions in Secrets
  • Serve as a bridge for Browser Extensions

The Secrets Helper never accesses your data directly, nor does it ever ask you or handle your passphrase. It communicates with the main application via a unix socket. Communications between the browser extensions and the Secrets app are encrypted with the Noise Protocol using our open source wrapper for Noise-C.

More precisely, the NoisePSK_NN_25519_ChaChaPoly_SHA256 handshake format is used with a pre-shared key being generated by the main app and stored in the system keychain.

Browser Extensions (Mac)

Similarly to the Secrets Helper, the browser extensions also never access your data directly. They serve two main purposes:

  • Informing the Secrets Helper of fillable forms
  • Performing a fill at the command of the user

The extensions use Native Messaging to communicate with the Secrets Helper. More precisely, the extension code issues and receives commands to the browser launched executable included in Secrets. This communication is done simply via standard input and output. The executable itself then communicates with the Secrets Helper using another unix socket. Just like the communication between the main application and the helper, the browser extensions also use the Noise Protocol for encryption.

On Windows and other platforms the browser extensions can also remotely request secrets from Secrets for iOS. See Remote Access.

iOS Extensions (iOS)

To allow for a better integration with operating system, iOS allows apps to include some extensions that itself will launch on behalf of the user. By design these extensions are not the same as your main app. Secrets includes extensions for Siri, Password AutoFill, Share and potentially others as iOS evolves.

Unlike the Mac, there's no communication between the extensions and the main app. So they all must read/write to a shared container where your data is stored. Although technically these are different apps, they share the same code with the main app. And for the most part they'll look and feel like the same app to you.

Remote Access (iOS)

Starting with version 3.4.0, Secrets for iOS can also be used as a remote keychain. Similarly to how the browser extensions request secrets from a locally running instance of Secrets for Mac, remote requests also use the Noise protocol (with Noise_KK_25519_ChaChaPoly_SHA256) to establish a secure end-to-end encrypted connection with Secrets for iOS. A WebSocket server in our control is used to bridge the two devices. Note however this server is stateless, it doesn't know anything about each of the connecting parties.

Here's a brief overview on how a request is sent:

  1. The browser extension connects to the WebSocket server and is assigned a random id.
  2. The extension then sends a push notification to the device stating the user is requesting a secrets and the extensions is connected with the random id assigned on step 1.
  3. After tapping the notification, Secrets for iOS also connects to the WebSocket server, gets its own random id, and ​establishes a secure connection with the extension. The WebSocket server knows where to route these messages because of these ids.

Secrets 3.6.0 introduced Ad Hoc requests with the goal of making it easier to do one-time requests on shared computers. These type of requests exchange the pairing phase with showing a QR code for every request.