Product Releases/7 Nov, 2022

Introducing Data Vault - Secure HSM-backed PII storage directly from the frontend

Today we are releasing Data Vault, which allows the safe and compliant storage of sensitive user data directly from the frontend.
Data Vault takes care of data localization and protection transparently, without having to build ad-hoc infrastructure to handle encryption or key management and rotation.


Introduction

Building a truly secure data storage is still extremely hard, and the increased regulatory pressure makes mishandling of users’ personally identifiable information (PII) a very costly mistake.

We built Data Vault to help developers store user data and PII securely, without having to worry about data localization, key management, key rotation or the million other issues you face when building and using cryptographic primitives.

Data Vault is ideal for scenarios in which you need to save sensitive user data such as data collected during KYC, Health-related records and credit cards data but it can also be used as a simple and convenient key-value store for user attributes. For instance, we built our request access form using Data Vault and we use Data Vault to keep track of a user’s cart in our e-commerce demo.

You can try Data Vault yourself in our playground.

Data Vault has a number of properties that are not available through a database or through products like Hashicorp Vault:

How do you use Data Vault?

From the outside, Data Vault’s interface looks like a standard REST API, coupled with a set of SDKs (both client-side and server-side) that together allow developers to store and retrieve users and their associated data.

Below you’ll see a few examples of how to use Data Vault, both client-side through our SDK and server-side through our APIs. We’ll show a few examples with and without SlashID as the Identity Provider.

Data Vault with SlashID Access client-side

If you are using SlashID’s Access module for identity management, then Data Vault is already provided as part of Access.

For instance, this is a code snippet that shows how to register or sign-in a user and how to store and retrieve encrypted attributes using our client-side JavaScript SDK:

var sid = new slashid.SlashID()

const user = await sid.id(
   Organization_ID,
   {
       type: "phone_number",
       value: "+13337777777"
   },
   {
       method: "webauthn"
   }
)

await user.set({"passport": "E65471234"})

const attrs = await user.get()
// => attrs === {"passport": "E65471234"}

In the example above, we are registering/logging in a user with the phone number +13337777777, and using webauthn as the authentication method. By default, if the user connected to the website from Lisbon their data would be automatically stored in the EU, and if they connected from New York the data would be stored in the US - you can override that choice in the id() call.

Calling set on a user creates a new attribute in our Data Vault module. When that happens an attribute-specific encryption key is generated, which is used to encrypt passport. The key is itself encrypted with a user-specific key generated at user-creation time.

Calling get on a user allows you to retrieve one or more attributes associated with the user. The attributes are decrypted and returned in clear-text to the caller.

Note how, throughout the example, you don’t have to worry about encryption nor data localization, and the user data never touches your own infrastructure.

Client-side Data Vault without SlashID Access

If you are not using Access for Identity Management, you can still use Data Vault securely from your frontend.

You first need to create a user in SlashID through our APIs:

curl -L -X POST 'https://sandbox.slashid.dev/persons' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks' \
--data-raw '[
  {
    "attributes": {},
    "handles": [
      {
        "type": "email_address",
        "value": "user_blogpost@user.com"
      }
    ],
    "region": "us-iowa",
    "roles": [
    ],
    "groups": [
    ]
  }
]'

{
    "result":
    [
        {
            "personID":"pid:01975547e47da59d74d5fa5df2e1f1df3ffa1a2ece1785c3e97e6ede0478e89e8d3ca7612a:1",
            "active":true,
            "roles":[]
        }
    ]
}

Once you have the user you can mint an access token for it through our APIs:

curl -L -X POST 'https://sandbox.slashid.dev/persons/pid:01975547e47da59d74d5fa5df2e1f1df3ffa1a2ece1785c3e97e6ede0478e89e8d3ca7612a:1/mint-token' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'
{
 "result": "eyJhbGciOiJSUzI1NiIsImtpZCI6InZFcHNGZyJ9.dsaRoZW50aWNhdGVkX21ldGhvZHMiOlsiYXBpIl0sImV4cCI6MTY2NzU2NzIxMCwiZmlyc3RfdG9rZW4iOmZhbHNlLCJpYXQiOjE2Njc0ODA4MTAsImlzcyI6InNhbmRib3guc2xhc2hpZC5kZXYvYXV0aG4iLCJqdGkiOiI2MDU3Yzc1MDNhYzhkNjM2NWVkMDZkMDFkOGIwNGZlOSIsIm9pZCI6ImY5NzhhNmJkLTNlNDUtYmNkYS1jYjRlLTU3M2QwYmFkMTU1YiIsInVzZXJfaWQiOiJwaWQ6MDE5NzU1NDdlNDdkYTU5ZDc0ZDVmYTVkZjJlMWYxZGYzZmZhMWEyZWNlMTc4NWMzZTk3ZTZlZGUwNDc4ZTg5ZThkM2NhNzYxMmE6MSJ9.EMwqVfJLvVVhOy_TQ7UuX1kiXphXBwt3E38Rkdkgkld_C5TfHqi7BRgwyc6FQEsnX4QzoB9-vCjupVxpAlY_PuVaxvNI3j1NUilZuPB8GMsiUS5Ivc1k9yUBMt11qOd1GR6lU9GiUZnXzOaYiOMc5LZJs8ig7YsFJCSZ34QpgcwQAkR0gIWOBezc_q4vHel8NnOgjx4syhUz-1gLngyqgwpdWae1zmzMY9zrof7NfXnSr69waLQvToA7gQWDgC0TLimpvNNiHrE3Do7v0JgXJ7JQ63ARE7ES0uppaX-mvpo7HYp32RfDmh29EOsMkSt0NFrY8t1UhUvySh57ECkePA"
}

The resulting token can be loaded in Javascript through the SDK as follows:

const user = new slashid.User(
  'eyJhbGciOiJSUzI1NiIsImtpZCI6InZFcHNGZyJ9.dsaRoZW50aWNhdGVkX21ldGhvZHMiOlsiYXBpIl0sImV4cCI6MTY2NzU2NzIxMCwiZmlyc3RfdG9rZW4iOmZhbHNlLCJpYXQiOjE2Njc0ODA4MTAsImlzcyI6InNhbmRib3guc2xhc2hpZC5kZXYvYXV0aG4iLCJqdGkiOiI2MDU3Yzc1MDNhYzhkNjM2NWVkMDZkMDFkOGIwNGZlOSIsIm9pZCI6ImY5NzhhNmJkLTNlNDUtYmNkYS1jYjRlLTU3M2QwYmFkMTU1YiIsInVzZXJfaWQiOiJwaWQ6MDE5NzU1NDdlNDdkYTU5ZDc0ZDVmYTVkZjJlMWYxZGYzZmZhMWEyZWNlMTc4NWMzZTk3ZTZlZGUwNDc4ZTg5ZThkM2NhNzYxMmE6MSJ9.EMwqVfJLvVVhOy_TQ7UuX1kiXphXBwt3E38Rkdkgkld_C5TfHqi7BRgwyc6FQEsnX4QzoB9-vCjupVxpAlY_PuVaxvNI3j1NUilZuPB8GMsiUS5Ivc1k9yUBMt11qOd1GR6lU9GiUZnXzOaYiOMc5LZJs8ig7YsFJCSZ34QpgcwQAkR0gIWOBezc_q4vHel8NnOgjx4syhUz-1gLngyqgwpdWae1zmzMY9zrof7NfXnSr69waLQvToA7gQWDgC0TLimpvNNiHrE3Do7v0JgXJ7JQ63ARE7ES0uppaX-mvpo7HYp32RfDmh29EOsMkSt0NFrY8t1UhUvySh57ECkePA'
)

From this point onwards, you can use the user object just like you would if you were using Access for Identity Management:

await user.set({ passport: 'E65471234' })

const attrs = await user.get()
// => attrs === {"passport": "E65471234"}

Accessing Data Vault from the backend

Accessing Data Vault from your backend is straightforward through a simple REST API. Here is an example to retrieve all the attributes in clear-text for a given user:

curl -L -X GET 'https://sandbox.slashid.dev/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'

{
  "result": {
    "email": "testuser_32819h@slashid.dev",
    "firstName": "John",
    "lastName": "Smith",
    "passport": "E65471234",
  }
}

To add a new attribute we can PUT it as follows:

curl -L -X PUT 'https://sandbox.slashid.dev/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'
--data-raw '{"creditcard_num": "324345678123403"}'

And to delete an attribute, it’s a simple DELETE:

curl -L -X DELETE 'https://sandbox.slashid.dev/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes?attributes=lastName' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'
curl -L -X GET 'https://sandbox.slashid.dev/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'

{
  "result": {
    "email": "testuser_32819h@slashid.dev",
    "firstName": "John",
    "creditcard_num": "324345678123403",
    "passport": "E65471234",
    "uuid": "1909981882"
  }
}

Lastly, you can selectively GET individual attributes to minimize accessing unneeded clear-text confidential information:

curl -L -X GET 'https://sandbox.slashid.dev/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes?attributes=creditcard_num' \
-H 'Accept: application/json' \
-H 'SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395' \
-H 'SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks'

{
  "result": {
    "creditcard_num": "324345678123403"
  }
}

The internals

We provide a deep-dive on our encryption scheme in this blogpost.

At a high level, we decided early on that our encryption layer would be database-agnostic, because we wanted the ability to expand datastore coverage beyond our current database, if needed.

Some data structures in our architecture are encrypted and globally replicated, while others are encrypted but localized to a specific region.

We make extensive use of envelope encryption. Our keys are encrypted following a tree-like logic as shown schematically in the picture below.

SlashID Key Tree

Properties of the key hierarchy

The key hierarchy mentioned above is generated per-region and this structure brings a number of advantages:

Key lifecycle

Keys are created on demand when a new organization, a new user or a new attribute is generated. Keys are never stored unencrypted anywhere at rest and only one service within SlashID’s infrastructure has the ability to access the HSM to decrypt the master key, significantly reducing the chances of compromise.

Derived keys are stored in a database that is encrypted, geo-localized and backed-up every 24 hours.

Lastly, all keys are rotated at regular, configurable intervals.

The storage engine

While Data Vault is database agnostic and is exposed as a key-value storage, we are currently using Cloud SQL for PostgreSQL for attribute storage. Cloud SQL is deployed in multiple GCP regions to obtain as much granular coverage as possible for data localization purposes.

As mentioned earlier, key material is stored in the region closest to the user to comply with data localization laws. Encrypted user data is either localized with the key or globally replicated across different regions to reduce latency, depending on access patterns.

What’s next?

We have a number of exciting upcoming features for Data Vault:

And many more!

In the next few weeks we’ll describe how we are approaching in-browser encryption so you can decide whether to encrypt the data in the backend or let the user encrypt data client-side in a non-custodial fashion.

Conclusion

If you are interested in Data Vault or you have built a similar system yourself, we’d love to hear from you. Please reach out to us!