IronWeb Security Architecture

Browser Security

Browsers are notoriously insecure places to run code. A user can debug and change values. Malicious javascript can be injected to cause mischief. Most storage mechanisms are trivially visible to other programs running on the same system as the browser, and in fact, it’s common for malware to steal saved passwords directly from the browser's insecure storage areas.

The WebCrypto API has made things better within the browser, but storage of keys is still problematic for a few reasons. For one thing, Safari doesn’t support the CryptoKey interface (desktop or mobile). For another, different browsers support different algorithms and key types. So for storage of keys in the browser, the mechanism that gives us the most hope is not usable.

One thing that browsers do well (for the most part) is the sandboxing achieved by the same-origin policy, which greatly limits the ability of a script from source A to interact with the content or javascript execution environment associated with source B. This is why most attackers work hard to load malicious javascript into the context of the originating domain, such as when the attacker modifies a web page in transit to introduce "<script>" tags into the document.

IronCore’s approach leverages the browser security in a few different ways in order to achieve our goals. There are two separate concerns: the protection of keys inside the browser, and the management and handling of keys in the system at large.

IronWeb Architecture

The IronWeb SDK architecture has been designed to be performant in the browser while also reducing the security issues within the browser as much as possible to make the SDK a usable library for consumers. To make the SDK performant, we use WebWorkers to avoid freezing the UI within the browser when performing CPU-intensive cryptographic operations. To make the SDK secure, we take advantage of the same-domain policy to sandbox access to sensitive user keys within the browser.

Page Load

When a client application embeds the IronWeb SDK (e.g. as a dependency from NPM), upon startup, it creates an invisible iFrame in the containing page which points to a page on the ironcorelabs.com domain. We call this bit of code the IronWeb Shim. The iFrame loads a blank page that has its own JS file which we call the IronWeb Frame. This frame JS code then creates a new WebWorker which is responsible for running all cryptographic operations on a separate thread. Once the iFrame has loaded, we create a new MessageChannel interface to open a communication channel between the shim JS code and the frame JS code.

Initialization

Initial User Context

When a new session is started in the browser and the IronWeb.initialize() method is called, the customer application provides a valid JWT and password for the user initializing the application. This information is passed to the frame via the MessageChannel, at which point the JWT is sent to the IronCore API as an assertion of the user's identity. After validating the JWT, the API uses this assertion to retrieve information about the user, including their encrypted master key, if needed.

New Device Key Generation

In a new browser instance, once we have the context of the user, their password, and their encrypted master key, we generate a new device key for the user's session. We first decrypt the user's master key using the supplied password. If this is successful, we generate a random value for the new device's key, then use the decrypted master key to "approve" the new device key. This involves creating a transform key that allows the device to decrypt anything that was encrypted to the user. Once key generation is complete, we no longer need the user’s master key, so we flush it from memory. All of these operations are done within the iFrame on the IronCore domain.

We then store the device key for the user locally, so that the next time IronWeb.initialize() is called, the user doesn’t need to provide their password and generate new device keys.

Device Key Storage

The user’s device key is stored within JS memory inside the IronCore domain iFrame, and that key is used for all future cryptographic operations. But we also want to save this device key in persistent storage so it is available if the user refreshes their browser tab, closes/reopens their browser, etc. Storing the device key in plaintext in LocalStorage is very insecure, because any JavaScript running on a domain can access that domain’s LocalStorage. In addition, any user can directly inspect their LocalStorage and copy out values.

In order to mitigate both of these issues, we encrypt the device keys before storing them in LocalStorage. This involves the following steps:

  1. Generate a new random AES key.
  2. Encrypt the device key with this random AES key.
  3. Store the encrypted device key in the LocalStorage of the IronCore frame domain.
  4. Send the random AES key back to the IronWeb Shim which stores the key in LocalStorage of the customer domain.

When the encrypted device key is stored on the IronCore domain, it is identified in LocalStorage by a unique value which includes version, customer ID, and segment information that is unique to the containing application. If a user’s browser hosts multiple applications that use the IronWeb SDK, their various device keys will not conflict.

Subsequent Initialize Calls

Once this first initialization has occurred, we have set the user’s session within the browser. The next time that IronWeb.initialize() is called, the shim code first checks the customer domain LocalStorage for the random AES key. If it is found, this key is passed to the iFrame on the IronCore domain. The IronWeb Frame tries to find the appropriate encrypted device key in the IronCore domain LocalStorage, and if it is present, the Frame tries to decrypt it using the key. If this succeeds, the Frame has an existing device key to use; it stores the key in JS memory within the iFrame. It is not necessary to ask the user for their password to generate and authorize a new device key.

Deauthorize Device

When the containing application makes a call to the IronWeb.user.deauthorizeDevice() method, we first clear out the random AES key from the customer domain LocalStorage. We then send a message to the IronCore iFrame to cause it to perform a number of actions to completely revoke the device key. First, it clears out the encrypted device key from local storage, then it sends a request to the IronCore API to remove the device key from the database, and finally it removes the device key from the iFrame JS memory.

Secure Components

This process is a bit complicated, but for good reason; all of the components described above create a secure but usable system for the IronWeb SDK. There are a handful of benefits that the above architecture provides.

Same Origin Policy

Because the architecture relies on having code running on two different domains, we gain a level of security because the browser enforces restrictions on what data code running on a particular domain can access. Malicious JavaScript running on the customer’s domain cannot access the code, LocalStorage, or private keys that are only accessible in the IronCore domain.

MessageChannel Interface

The MessageChannel interface provides a better security model than using the traditional postMessage API. If we used the postMessage API, any messages passed between the customer's domain and the IronCore iFrame would be broadcast to anyone who was listening. Thus, if malicious JS was added to the containing application, all it would need to do is add its own listener to the iFrame and then eavesdrop on all messages being passed back and forth.

With the MessageChannel interface, a port is set up on both sides, and communications between the iFrame and the customer domain can only be accessed if you have access to the port. This port is not accessible to code outside of the IronWeb shim code and therefore cannot be abused by malicious code.

No CORS Access to IronCore API

Because the IronCore iFrame is hosted on the same domain as the IronCore API, the API doesn’t need to enable CORS access, as there aren’t any requests coming from a different domain to the API.

Vulnerability Use Cases

The following are examples of attack types that could target an IronCore-enabled customer application and the resistance the IronWeb architecture provides against the attack.

Customer Domain XSS

The most common vulnerability that can occur is if the customer site is vulnerable to a XSS attack and an attacker can get malicious JS running on the customer’s page.

Attacker CANNOT:

  • Access the user’s in-memory device keys
  • Access the user’s encrypted device keys stored in the IronCore iFrame LocalStorage
  • Listen to messages coming back from the IronCore iFrame
  • Send messages to the IronCore iFrame
  • Make any modifications to the WebCrypto API (for example, change RNG generation to be deterministic). All Crypto operations only happen directly within the IronCore iFrame.

Attacker CAN:

  • Access decrypted content that is displayed within the customer’s domain in the DOM.
  • Access the random AES key stored within the customer domain’s LocalStorage.

Depending on how the IronWeb SDK is loaded into the containing application, access to the SDK is also a risk. If the IronWeb SDK is loaded globally, then an attacker can use it to make calls to decrypt documents, modify group admins/members, etc. If the SDK is loaded using a bundler (e.g. webpack), this is much less likely, as the IronWeb code is usually obfuscated behind various closures and is not accessible to the global page context.

Malicious Application Running in Browser

If another application is running in the same browser that is hosting an application containing the IronWeb SDK, the sandboxing facilities in the browser provide even more protection against that application compromising any keys or data.

Attacker CANNOT:

  • Access the user’s in-memory device keys
  • Access the user’s encrypted device keys stored in the IronCore iFrame LocalStorage
  • Access the random AES key from the customer domain’s local storage
  • Listen to any messages between the containing application and the IronWeb Frame
  • Access the IronWeb Frame in any way
  • Access decrypted data in the containing application’s DOM
  • Gain access to the file system to retrieve the contents of the browser’s LocalStorage directly

IronCore Domain XSS

Because the IronCore iFrame is just an empty page with a few JavaScript files, getting an XSS attack on the frame is very unlikely. Most XSS attacks involve displaying user-provided information back to the user, which isn’t possible within the iFrame. This means that if an attacker is able to get malicious code injected into the IronCore iFrame, the attacker probably has access to the IronCore servers somehow. If this happens, there are likely better ways to attack the system than to inject XSS into the iFrame. Regardless, if this happens, then:

Attacker CANNOT:

  • Access the random AES key stored in the customer domain LocalStorage
  • Listen to messages coming into the IronCore iFrame
  • Send messages to the customer IronWeb shim
  • Get access to the in-memory decrypted user device keys
  • Get access to decrypted document bytes

Attacker CAN:

  • Access the user’s encrypted device keys stored in the IronCore iFrame LocalStorage
  • Make malicious modifications to the WebCrypto API (for example, change RNG generation to be deterministic).

Side Channel Attacks from Another Application

There are a variety of attacks that can be used to extract information from a running application using side channels, such as timing attacks and exploiting the CPU’s branch prediction algorithms. The core crypto components of IronWeb are implemented such that code that handles secret key material has a data invariant execution path. That is, it always executes the same instructions regardless of the values contained in the secret keys. It is constant time (there are no loops that terminate early depending on values), and there are no branches based on values. This data invariant code eliminates many of the the common side channel attacks that might be executed by other code running on the same machine as the application that contains IronWeb.

Attacker CANNOT:

  • Use a timing-based side-channel attack to extract bits of any secret key material generated and used by IronWeb
  • Use a branch prediction-based side-channel attack such as Spectre to extract bits of any secret key material generated and used by IronWeb
  • Use a power consumption-based side-channel attack to extract bits of any secret key material generated and used by IronWeb

Machine Access

If an attacker is able to get direct access to the user’s machine, there are a number of ways the attacker could abuse that access. For example, if a user is using a shared computer in a library and doesn’t log out after they leave, the attacker can access decrypted data in the user’s context. Similarly, an attacker or bit of malware with admin access to the machine can potentially access local storage across domains by accessing the browser’s disk storage area.

Attacker CANNOT:

  • Discover the user’s master key password

Attacker CAN:

  • Extract the random AES key stored in the customer domain LocalStorage, extract the encrypted device key from the IronCore iFrame LocalStorage, and combine them to get the decrypted device key.
  • Make calls to the IronWeb SDK to access/decrypt all the documents the user has access to, modify groups for which the user is an admin, etc.

Mitigation: If the original user revokes their device key, then the device key the attacker obtained is no longer of any use. Any decrypted data that the attacker has seen is still vulnerable, but access to newly encrypted data using the compromised key is blocked.