Basic concepts
When planning and developing the security features of your product, it is crucial to know what tools the ZeroKit does provide and to understand the underlying concepts.
3.1 Architecture
Since encryption and key handling is a complicated subject, it is important to agree on the names of each part of the architecture, so no key gets misplaced or leaked to a third party.
- Application server: This is the backend part of your application that handles communication with our Administrative API to approve or reject user actions on the Tenant server. It may be made up of multiple servers, but for simplicity's sake, we consider it a single unit in this documentation.
- Application client: When we talk about the frontend or client part of your application, it means all the code running on the user's device (browser or mobile device).
- Embedding domains (web app only): This setting is stored on the Tenant server but defines the web domains where the SDK can be loaded. This can be set on the administrative portal
- Tenant Server: This is where your instance of the ZeroKit infrastructure resides. This server hosts the APIs, the service-access iframes and the related data needed to identify and manage the keys of users. This infrastructure is owned by Tresorit and used exclusively by the subscriber and their users during the subscription period. These instances run on a dedicated infrastructure and have a unique API domain address. All user registrations, operations and shares are available strictly within that single tenant.
- AdminKey: The admin key is a 64 character long string that encodes your key that can be used to sign requests to the Administrative API. This is the hexadecimal representation of a 32 byte random key. This secret belongs to the application server and should never be shared with any untrusted parties or any of your clients. If your key has been compromised, you should regenerate it as soon as possible on the administrative portal to prevent abuse. It is important to note, that this can't be used to access or decrypt user data but can be used to seriously disrupt the operation of your applications on all your clients.**
- TenantId: Your tenant ID is a 3 to 10 character long, lowercase code, which uniquely identifies your tenant. It is part of your service access URL and your user IDs.
- Administrative API: The Administrative API is a REST API provided by the Tenant Server. It provides the application server control over user actions: it is used to approve or reject user actions like registration, tresor creation and sharing.
- JS SDK: The SDK is a small JavaScript code that handles the adding of the service access iframes to your site and the communication with them through a message passing interface. Parts of it also loaded by mobile SDKs.
- Mobile SDK (Android and iOS): Native libraries for the specific platforms. They wrap around a JS core lib in a hidden webview and provide native api for mobile apps.
3.1.1 Responsibilities
- Password handling: User passwords never leave the iframes provided by ZeroKit and are never entered or used in the application itself. Even inside our implementation they are used immediately to derive secure keys and are not stored.
- Encryption keys: Encryption keys never leave the iframes, they are handled entirely by ZeroKit .
- Storing data: Storing data is the responsibility of the application, the tenant server only stores the data needed for authentication and encryption.**
3.2 Integration
It is important to understand that ZeroKit is not a stand-alone application but rather a set of building blocks and tools to build secure applications, therefore it needs to be integrated. While ZeroKit provides many features that simplify implementing secure user-handling and client-side encryption, it will still take some effort to use it in your product.
3.2.1 Iframes
In the SDK, we do all computations and store all sensitive data inside iframes. This is important because modern browsers provide complete separation for iframes loaded from different domains, and accessing them is only possible through a strictly message passing interface. Since the iframes are loaded from your tenant server they are completely separated from all third party code, this way we can ensure that no attacker can gain access to your users' passwords or encryption keys by XSS, and it also provides a separation of responsibilities that is conducive to achieving zero-knowledge.
All the iframes can be customized by a uploading custom css files through the administrative API, and dynamically through a set of customization methods adding and removing css classes, and setting placeholder texts.
There are a few types of iframes used in the SDK:
- Registration: this iframe contains only two password fields. It is responsible for getting the user's password and registering the user. It provides methods to query meta-information about the password input (matching, password strength) and to commit a registration to the ZeroKit system. Registering with ZeroKit is slightly more complicated than usual: for more details see Common flows.
- Login: the login iframe contains a password input field, and it provides a method to initiate the login process.
- CreateInvitationLink: this iframe provides the password input fields for invitation link creation and a method to create password protected links.
- AcceptInvitationLink: this iframe contains the password input and the necessary method to accept password protected invitation links.
- ChangePassword: this iframe contains an input for the current and 2 password input for the new password, this is needed, because a password change operation needs a fresh login.
- Api: this iframe does most of the heavy lifting in the system and is responsible for all other operations beside the above. It provides many methods, for a detailed documentation see JavaScript SDK.
3.2.2 SDK
The SDK can be loaded from the tenant server with the URL we provide. This is a fairly small script that handles loading and wrapping the iframes in custom objects that provide simple interfaces. Loading the script will inject a zkit_sdk object into the global namespace; this object can be used to access the features provided by ZeroKit .
3.2.3 Administrative API
The administrative API provided by the tenant server is a REST API that can be used to control user actions in ZeroKit . Most user actions, everything besides login and en/decryption require approval from the application server to be effective. This includes registration, which has a slightly different than usual flow (documented in Common flows). Approvals could be done manually, but we recommend some kind of automated process to handle this, since for most operations approving one can invalidate other pending operations. Every request against this API has to be signed by the AdminKey you received at the time of your purchase. The available methods are documented in API reference, and the exact signing procedure is documented in Authentication (request signing)).
The administrative API is the application's way to enforce its ACL, it allows the application to control which operation a given user is able to perform, e.g. it can be controlled who can share what with whom. Another important feature of this API is that it provides transactional control and ensures that the server knows about significant user actions.
3.3 Users
The user concept of ZeroKit is a really bare-bone one: we only store the necessary data for authentication and key management. We don't store passwords and we store no profile data.
3.3.1 Registration
During registration the user's password is entered in the registration iframe which exposes a register method that is used as a part of the registration process, committing the registration to the ZeroKit database. The SDK only provides meta-information about the password, the iframe's wrapper object provides methods to query whether the entered passwords match and the strength of the password is acceptable. We use zxcvbn (https://github.com/dropbox/zxcvbn) to measure password strength.
3.3.2 Login
To log in, users enter their password into the login iframe and your application provides the user ID when calling the login method. This login is only effective in the SDK. A logged in user can en/decrypt data and initiate other user actions provided by the SDK. This alone however is not sufficient to most applications, using our IDP is required in most cases. This is because while the SDK returns the ID of the logged in user, it provides no way of proving it to the application server, so a malicious user could impersonate someone else just by knowing the user ID. This doesn't affect the security of any encrypted data, because without the password of the impersonated user the attacker cannot decrypt any data, however it could still cause problems.
3.3.3 IDP - Identity Provider
IDP makes it possible for users to prove their identities to the server. This is necessary because the application doesn't handle any login data and has no way of ensuring the users are who they say they are. In our case the only information we provide about users is the identification information as we store no other profile data, and since we only provide this for the application there is no consent screen involved. In most use-cases server side identification is a must and so is using IDP. This is documented in detail in Built-in IDP.
3.4 Tresor
Tresors are the basic units of key management in ZeroKit . They are basically groups that share a history of common keys that can be used to en/decrypt data belonging to the tresor. Using this simple concept ZeroKit covers up a great deal of complexity stemming from shareable encryption.
3.4.1 Members
A tresor can have multiple members, and all current users can decrypt data encrypted by the tresor and encrypt using the current key of the tresor. Access level of tresor members (ACL) is implemented and controlled by the application - through admin API approvals and/or rejects.
3.4.2 Who can access/decrypt the data?
As data is stored by the application, access to data is controlled solely by the application's ACL, ZeroKit only provides access control for keys. Current members of a tresor can download and decrypt its keys and then use it to decrypt any data that was encrypted by that tresor at any time. It is important to notice that - cryptographically speaking - anybody who has had access to a chunk of data could have saved it, so will have 'access' to it in the future. This is also true of the keys used: while we don't allow users to download even the encrypted keys of tresors that they are not members of, any user could potentially save the keys they had access to and use it later to decrypt any data encrypted by those keys. Refreshing keys is handled by ZeroKit , no user action is needed after removing a user from a tresor.
When to re-encrypt?
Cryptographically speaking if you once had access to a chunk of data, you know that version of it, so in general there is no need to re-encrypt anything. If somehow the removed user gains access to that bit of data again, the only new information they could get from it is that it hasn't changed. Re-encrypting may prevent this, but it can be a costly operation and in most cases it is not recommended.
3.4.3 Example
Let's use the example of an encrypted chat service. In this case, the user has many different tresors. First, each user would have a private profile tresor, encrypted with this the application can store data that enhances the user experience, like preferences, but not necessarily public like channel memberships and contacts. In addition to this, each user could have a public profile tresor, something that would be shared with all contacts containing a profile picture and a name. Lastly, each channel would have its own tresor and all messages on the channel would be encrypted by that tresor. Users invited to a channel are added to it by a member of the channel/tresor. Permission to invite users into the channel should be managed by both appropriate tresor permissions e.g.: only channel admins/owner gets the necessary Administrator permission, and the applications own ACL, only approving the appropriate invitations. This way the history of messages and all profile data can be stored safely and securely in the application's database. Users could access all needed data, and have great control over what they share with others. Chat history is accessible by anyone who was a member of the channel after the message was sent, access to this history has to be controlled by the application.
3.5 Secrets & Keys
This section will give you a high-level overview of what keys are used in the system and grant a brief glimpse into the complexity covered up by ZeroKit .
3.5.1 User keys
Users own a fairly big set of keys:
- Master key: a key derived from the password, used to encrypt the cryptographic profile of the user and used to decrypt this profile upon login.
- Additional keys: these symmetric encryption keys are stored in the above mentioned profile and are used to encrypt different 'fragments' of information belonging to the user. These fragments are stored server side.
- Agreement key pair: This private-public pair of keys can be used to grant the user to access to tresors. The private key is stored in a fragment that the user can access and decrypt after logging in and the public key is public information that other users can query.
3.5.2 Sharable encryption
Inside the tresor there are a few different kinds of keys:
- Encryption key history: this list of keys is stored encrypted in the tresor, and has a new key added to it after removing a user from the tresor. Data encrypted by the tresor is always encrypted by the latest key in this list.**
- Common key: a key used to encrypt the above key history, that every member has access to and is refreshed after adding/removing users. An encrypted version of this is stored in the tresor for each member, encrypted by their on keys.
- Key encryption key: the key encryption key (KEK) is different for each member of the tresor, and is used to encrypt the common key. This is still stored in the tresor, but encrypted with the public agreement key of the user it belongs to.
You can add or remove users to/from this structure and share keys with them as you like. These are done in the following way:
Adding a new user:
- Add the new member to the tresor, save the user ID and public agreement key
- Generate a new common key
- Generate a KEK for all members including the new one
- Encrypt the common key by the KEK of each member
- Encrypt the KEK for each user with their public agreement key
- Re-encrypt the key history with the new common key
- Upload the updated tresor data
Removing a user:
- Remove the member from the tresor
- Generate a new common key
- Generate a KEK for all current members
- Encrypt the common key by the KEK of each member
- Encrypt the KEK for each user with their public agreement key
- Add a new key version to the key history
- Re-encrypt the key history with the new common key
- Upload the updated tresor data
3.6 Under the hood
3.6.1 WebCrypto and support in different browsers
WebCrypto is a low level API that we use in our SDK to access native cryptographic functions. This is really important as it provides us with a cryptographically safe random generator that is crucial to the security of our product. Also, native implementations are much faster than JavaScript ones, so it is also important from a performance viewpoint.
3.6.2 Key types
- Keys from the user password: we use PBKDF2 and scrypt to derive a safe key from the password
- Asymmetric keys: we use elliptic keys from the X25519 curve as asymmetric keys
- Symmetric keys: our symmetric keys are, if not derived, random strings generated using WebCrypto or msCrypto
3.6.3 Used algorithms
- Login: When logging in we use SRP protocol to agree on a session key that is used to sign requests to the Tenant Server
- Encryption: All data is encrypted using AES256-GCM