Only this pageAll pages
Powered by GitBook
1 of 42

Storage Kit

WHAT IS THE STORAGE KIT?

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Getting started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Concepts

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Usage / Examples

Loading...

Loading...

Loading...

Community

DEVELOPER RELATIONS

Product Editions

Loading...

Loading...

Loading...

Architecture

The following sections elaborate the architecture from a systematic and functional perspective. Also, components and dependencies are described.

System Architecture

Confidential Storage as a general system involves various actors that have to interoperate:

  • EDVs - Encrypted Data Vaults allowing to store data, managing data access and performing queries on the stored data.

  • Users - Human or nonhuman actor that wishes to access their EDVs.

  • Clients - Programs or libraries supporting access to conform EDVs.

  • Providers - Servers that provide an environment to create and manage EDVs.

  • Services - Instances besides the user that wish to access data on a specific users EDV (needing the users consent to do so).

A user uses one or more clients to access to their EDVs (multiple clients devices may be used to access data using multiple devices, e.g. stationary and mobile, or for redundancy, to avoid loosing data access if one device fails).

Clients may be connected to multiple EDVs (for redundancy or performance reasons).

Services are a special kind of client that access EDVs through Service Data Requests (which is described later on). EDVs are hosted by providers, and are created through specific requests sent by a client.

Quick Start

Getting started with the Storage Kit.

There are different ways to get started in using the Storage Kit.

Ways of getting the Storage Kit:

  • Docker: Quick way to launch and try out the latest builds. No build environment required.

  • Local Build: Build and run the Storage Kit locally. Requires a JDK 16 build environment including, Gradle.

  • Dependency (JVM): The Storage Kit can be used directly as JVM-dependency via Maven or Gradle.

Where to use the Storage Kit:

: The Storage Kit comes with a command-line interface (CLI) tool, which offers a rich set of commands to run the entire functionality the Storage Kit provides. The CLI tool can be used by running the Docker container or the executable by the local build.

: In case you want to run the Storage Kit as a service, your application can access all functionalities via the REST API.

: The Storage Kit can be used directly as JVM-dependency via Maven or Gradle.

CLI Tool
REST API
Dependency (JVM)

L3 | HL Server-Side Functions

This layer consists of high-level server-side functions that work on top of the encrypted credentials.

Notifications (Server)

It is helpful if data storage providers are able to notify clients when changes to persisted data occurs. A server may optionally implement a mechanism by which clients can subscribe to changes in the vault.

Vault-wide integrity protection (Client)

Integrity protection is needed to prevent modification attacks. Some form of integrity protection must be integrated (e.g. HASHLINKS) to prevent the successful execution of such attacks.

L2 | Data Sharing, Versioning & Search

This layer consists of a system that is capable of sharing data among multiple entities of versioning and replication and of performing privacy-preserving search in an efficient manner.

Encrypted search indexes (Client)

To enable privacy-preserving querying (where the search index is opaque to the server), the client must prepare a list of encrypted index tags (which are stored in the Encrypted Resource, alongside the encrypted data contents).

Versioning and replication (Client)

At least one versioning mechanism has to be supported. Replication is done by the client, not by the server (since the client controls the keys, knows about which other servers to replicate to, etc.). The versioning strategy may be implicit ("last write wins").

Sharing with other entities (Client)

An individual vault's choice of authorization mechanism determines how a client shares resources with other entities (authorization capability link or similar mechanism).

CLI | Command Line Interface

Run different functionalities of the Storage Kit by executing individual commands.

Installation & Running the Project

Make sure you have Docker or a JDK 16 build environment including Gradle installed on your machine

Examples

Document options (document, doc, d):
  - document load   - Load a document from this EDV, cache it locally (gets updated automatically on notification)
  - document create - Create (sequence 0) a new document in this EDV and publish it to to EDV peers (e.g. backups)
  - document update - Recreate, resequence, and publish update to EDV peers (e.g. backups)
  - document delete - Unlink document from EDV, optionally remove all backups at replica nodes (notify peers)
  - document purge  - Purge document in all versions from EDV, optionally remove all backups at replica nodes (notify peers)
  - document cache  - Show cached documents
  - document search - Start remote-run encrypted-search operation

Index options (index, i):
  - index tree      - Displays the whole index tree (all EDVs, all documents)
  - index show      - Displays the index of the selected EDV
  - index documents - Filters the index of the selected EDV to base documents

EDV options (edv, e):
  - edv delegate - Delegate certain permissions to another keypair
  - edv add      - Create a new EDV to add to the session
  
  - Notification options:
    - edv notifications connect    - Connect to the notification channel for this EDV
    - edv notifications disconnect - Disconnect from the notification channel for this EDV

Data request options (datarequest, dr):
  - datarequest - Accept a data request

Session options (session, s):
  - session info   - Display stored information about the current session
  - session export - Exports the current session to use on another device, or as a backup
  - session switch - Switch to another session

Client options:
  - help/?    - Show this help
  - history   - Show command history
  - exit/quit - Quit the Confidential Storage Interactive Console Interface
                
> sess01 > 
  1. Clone the project

git clone https://github.com/walt-id/waltid-storage-kit.git

2. Change folder

cd waltid-storage-kit/

3. Build docker container

docker build -t storagekit .

4. Run the project

docker run storagekit

Find other build options for docker .

  1. Clone the project

git clone https://github.com/walt-id/waltid-storage-kit.git

2. Change folder

cd waltid-storage-kit/

3. Build the project ()

./storagekit.sh build

4. Set an alias

To make it more convient to use, you can also set an alias as follows for the executable:

alias storagekit="./build/distributions/waltid-storagekit-1.0-SNAPSHOT/bin/waltid-storagekit"

5. Use the CLI

storagekit client

See also:

here
other build options
Client CLI examples

Service

Data models for data requests (serialized to and from JSON):

@Serializable
data class DataRequest(
    val context: String,
    val preferredDataType: String,
    val did: String,
    val responseUrl: String
)
@Serializable
data class DataResponse(
    val filesIndex: Map<String, String>,
    val capabilities: List<String>,
    val baseUrl: String,
    val edvId: String
)

The rest API is largely defined by the service provider.

It simply has to adhere to:

  • having at least one POST endpoint

  • with the DataResponse as request body

L1 | Data Encryption

This layer consists of a client-server system with capabilities of encrypting data in transit and at rest.

Validate Request (Server)

When a vault client makes a request to store, query, modify, or delete data in the vault, the server validates the request. Since the actual data and metadata in any given request is encrypted, such validation is necessarily limited and largely depends on the protocol and the semantics of the request.

Persist data (Server)

The mechanism a server uses to persist data, such as storage on a local, networked, or distributed file system, is determined by the implementation. The persistence mechanism is expected to adhere to the common expectations of a data storage provider, such as reliable storage and retrieval of data.

Persist global configuration (Server)

The configuration allows the the client to perform capability discovery regarding things like authorization, protocol, and replication mechanisms that are used by the server.

Enforcement of authorization policies (Server)

When a client makes a request to store, query, modify, or delete data in the vault, the server enforces any authorization policy that is associated with the request.

Encrypted data chunking (Client)

It is necessary that large data is chunked into sizes that are easily managed by a server. It is the responsibility of the client to set the chunk size of each resource and chunk large data into manageable chunks for the server. It is the responsibility of the server to deny requests to store chunks larger that it can handle. Each chunk is encrypted individually using authenticated encryption.

Resource structure (Client)

The process of storing encrypted data starts with the creation of a Resource by the client. If the data is less than the chunk size, it is embedded directly into the content. Otherwise, the data is sharded into chunks by the client (see next section), and each chunk is encrypted and sent to the server.

Encrypted resource structure (Client)

The process of creating the Encrypted Resource. If the data was sharded into chunks, this is done after the individual chunks are written to the server.

Configurations

Services come with their own configuration files.

The default mapping file is "service-matrix.properties", and looks like this:

id.walt.services.essif.didebsi.DidEbsiService=id.walt.services.essif.didebsi.WaltIdDidEbsiService
id.walt.services.vc.JsonLdCredentialService=id.walt.services.vc.WaltIdJsonLdCredentialService
id.walt.services.vc.JwtCredentialService=id.walt.services.vc.WaltIdJwtCredentialService
id.walt.services.crypto.CryptoService=id.walt.services.crypto.SunCryptoService
id.walt.services.keystore.KeyStoreService=confidentialstorage.ssikitext.EncryptedKeyStore:crypto.conf
id.walt.services.key.KeyService=id.walt.services.key.WaltIdKeyService
id.walt.services.jwt.JwtService=id.walt.services.jwt.WaltIdJwtService
id.walt.services.vcstore.VcStoreService=id.walt.services.vcstore.FileSystemVcStoreService
id.walt.services.hkvstore.HKVStoreService=id.walt.services.hkvstore.FileSystemHKVStore:fsStore.conf
id.walt.services.context.ContextManager=id.walt.services.context.WaltIdContextManager
id.walt.signatory.Signatory=id.walt.signatory.WaltIdSignatory:signatory.conf
id.walt.custodian.Custodian=id.walt.custodian.WaltIdCustodian
id.walt.auditor.Auditor=id.walt.auditor.WaltIdAuditor
id.walt.services.essif.jsonrpc.JsonRpcService=id.walt.services.essif.jsonrpc.WaltIdJsonRpcService

e.g., to change the keystore service, simply replace the line

id.walt.services.keystore.KeyStoreService=id.walt.services.keystore.SqlKeyStoreService

with your own implementation mapping, e.g. for the Azure HSM keystore:

id.walt.services.keystore.KeyStoreService=id.walt.services.keystore.azurehsm.AzureHSMKeystoreService

To add a service configuration:

id.walt.services.keystore.KeyStoreService=id.walt.services.keystore.SqlKeyStoreService:sql.conf Service configuration is by default in HOCON format. Refer to the specific service on how their configuration is laid out.

Dependencies

Cryptography

We are using Nimbus JOSE and JWT for all JWE (JSON Web Encryption) related purposes, as it is the most popular JOSE Java library4 and the project team has already had experience with this library.

Bouncy Castle is used as cryptography-provider, providing Java Cryptography Extension (JCE) and Java Cryptography Architecture (JCA) implementations. It was chosen as they are one of the most commonly used - and thus tightly integrated - layer for cryptography in Java (and C), providing audited APIs, parts of it even being certified for FIPS-140-25 (Level 1).

Storage

To store the data at Layer A on the system, TPM-backed full disk encryption can be optionally utilized to disallow metadata access even in case of a physical breach. Clients are also secured with passphrase-based symmetric encryption, using PBES2 (published by RSA Laboratories).

Functional Architecture

The Storage Kit provides diverse functionality that can be segmented into three layers (based on the Confidential Storage specs and requirements by the Decentralized Identity Foundation):

  • Layer 1 consists of a client-server system with capabilities of encrypting data in transit and at rest.

  • Layer 2 consists of a system that is capable of sharing data among multiple entities of versioning and replication and of performing privacy-preserving search in an efficient manner.

  • Layer 3 consists of high-level server-side functions that work on top of the encrypted credentials.

The following graphic illustrates these three layers and offers a functional perspective:

For the configuration of service -> implementation mappings, is used.

Our is used for signing ZCap-LD capability delegations and invocations.

ServiceMatrix

Local Build

For building the project JDK 16+ is required.

Wrapper

The walt.id wrapper script storagekit.sh is a convenient way for building and using the library on Linux.

./storagekit.sh {build|build-docker|build-podman|extract|execute (default)}

The script takes one of the the following arguments:

build|build-docker|build-podman|extract|execute.

For example, for building the project, simply supply the "build" argument:

./storagekit.sh build

Gradle

Manually with Gradle:

gradle clean build

After the Gradle build you can run the executable.

In build/distributions/ you have two archives, a .tar, and a .zip.

Extract either one of them, and run waltid-storagekit-1.0-SNAPSHOT/bin/waltid-storagekit.

cd build/distributions
tar xf waltid-stroagekit-1.0-SNAPSHOT.tar    # or unzip for the .zip
cd ../..  # go back to the root-directory

./build/distributions/waltid-storagekit-1.0-SNAPSHOT/bin/waltid-storagekit

Docker Build

Docker Container

Building the Docker Container

./storagekit.sh build-docker

or with Podman

./storagekit.sh build-podman

or without script (manually) with docker:

docker build -t storagekit .

or with Podman

podman build -t storagekit .

Docker Compose

Run as RESTful service via Docker Compose:

docker-compose build
docker-compose up

Searchable Symmetric Encryption (SEE)

Notice: This section describes a non-default alternative backend for using encrypted search. The current recommended way is to use the default hash-based index search.

A key feature of Confidential Storage is the ability to search through encrypted data. The main challenge is that the higher the security of a system is, the lower its performance and efficiency.

The reasons that the search functionality consumes more performance are obvious: If you want to search through encrypted data, you either have to decrypt the data first to be able to search through it, or you use other methods that also involve additional operations. Regardless of the method, the system must always carry out additional steps that are not required in unencrypted systems.

The SSE concept tries to achieve a suitable balance between security and efficiency.

The following graphic shows the main components of a simple SEE system:

Sessions

A client supports multiple EDV-groupings by utilizing a session-based storage container. It will be referred to simply as "session" hereafter.

Such a session consists of a unique identifier, key identifier, Decentralized Identifier (DID), list of EDV metadata as required to interact with the respective EDVs. EDV metadata consists of the EDV identifier, server URL, root delegation (from the EDV DID to the client’s DID) and an index key:

This container is not explicitly defined in the specifications, and was chosen to allow users to easy import and export of all relevant data to multiple of their devices.

Note that this container as a whole can be exported from a client and imported into other clients, allowing users to easily access their connected EDVs on multiple devices. To do so, the session will be encoded as JSON and encrypted to a JWT, using the clients master passphrase as the symmetric key.

Client

The client REST API is still in beta testing.

Currently it is recommended to include the JVM client library as a dependency using Maven or Gradle.

Please let us know if you are interested in using the client in non-JVM languages, so we can prioritize this feature accordingly.

Server

Method
Route
Description

GET

/api-routes

Shows a simple routing table

GET

/api-documentation

Contains the OpenAPI documentation/definition

GET

/swagger

Shows a Swagger API interface

GET

/redoc

Shows a ReDoc API interface

POST

/edvs

Create a EDV

POST

/edvs/{edv-id}/docs

Create a document in an EDV

POST

/edvs/{edv-id}/docs/search

Perform an encrypted search

GET

/edvs/{edv-id}/docs/{doc-id}

Retrieve a specific document

PATCH

/edvs/{edv-id}/docs/{doc-id}

Update a specific document

DELETE

/edvs/{edv-id}/docs/{doc-id}

Delete a specific document

GET

/capabilities

Displays the capabilities of a server

GET

/configuration

Displays the configuration of a server

WEBSOCKET

/edvs/{edv-id}/notifications

Interact with the notification API

REST APIs

We embraced the controller-service model for all REST components: The web server maps a specific route to a specific controller, which itself does nothing other than handling the request and response data, it does not contain any business logic. All of that is separated into service managers.

Also, recognizable for all methods suffixed with -Docs in the controllers, is that our OpenAPI documentation, is configured and fed in-code, directly beneath the actual code. Having the code and it’s documentation nearly makes it very easy to maintain this documentation, e.g. to keep it up-to-date when the code changes, which can be forgotten easily if it was in external configuration files with special syntax.

Dependency (JVM)

Enhance your existing applications with Confidential Storage functionality, by using the Storage Kit as a dependency.

Gradle

implementation("id.walt:waltid-storage-kit:VERSION")

Maven

  <dependency>
        <groupId>id.walt</groupId>
        <artifactId>waltid-storagekit</artifactId>
        <version>VERSION</version>
   </dependency>

Required Maven repos:

https://maven.walt.id/repository/waltid/

Introduction

Learn what the Storage Kit is.

In a nutshell, the Storage Kit is a zero-trust solution for all things data storage and sharing.

The main purpose of the Storage Kit is to provide a highly secure and private infrastructure layer for data storage and data sharing for any application.

The importance of the Storage Kit becomes particularly evident in the context of Decentralized Identity and will be a core component for developers who are building solutions and use cases that involve keys, personal data or other secrets.

The following sections elaborate the basics of the Storage Kit to help you get started.

Key Traits

Here are the most important things you need to know about the Storage Kit:

  • It is written in Kotlin/Java. It can be directly integrated (Maven/Gradle dependency) or run as RESTful web-service. A CLI tool allows you to run all functions manually.

  • It is open source (Apache 2). You can use the code for free and without strings attached.

  • It abstracts complexity and low-level functionality via different interfaces (CLI, APIs).

  • It is a holistic solution that allows you to build use cases “end-to-end”. There is no need to research, combine or tweak different libraries to build pilots or production systems.

  • It is modular, composable and built on open standards allowing you to customize and extend functionality with your own or third party implementations and to preventing lock-in.

  • It is flexible in a sense that you can deploy and run it on-premise, in your (multi) cloud environment or as a library in your application.

Feature List

Find a full overview of all features

here
The semicircles outside the subsystem box are showing the interfaces (actually the data which is needed or returned by the component) the component uses. The rectangles in which component is located represent a component of the system. These are interpreted as class elements and can thus be functions or function calls. The squares are so-called ports. They represent the interfaces to functions and resources outside the subsystem. The only drawback to this illustration is that it does not show the flow of how the system and the individual functions must be called.

ZCaps - Caveats Extension

By advancing the existing basic ZCap-LD implementation with the ZCap-Caveats extension system, it is now a full-blown ZCap-LD issuance and verification system.

ZCap-based capabilities may also include ZCap-Caveats.

ZCap-Caveats allow users even tighter control control on who can access what data in which specific circumstances. These ZCap-Caveats can also be set at multiple levels of delegations, thus allowing a user to restrict access to their documents, and a service setting ZCap-Caveats in a delegation to a sub-service, further restricting access to the users data.

An overview is shown here:

The graphic above shows how capabilities work in the chain (and how we utilize them):

  1. The EDV delegates access from the Root-of-Trust DID (of the EDV), to the user. The user receives a capability delegation without any caveats, so the user has full access, not only to the EDV, but also to delegate access to others.

  2. When the user delegates access to a service, it is possible (and recommended) to include so called "Caveats" in the capability chain. These provide various restrictions, starting at the layer at which they have been included.

  3. The services then creates a capability invocation from the (restricted) capability delegation, naturally the caveats are thus also in the capability chain. If the service tried to remove the Caveats from the delegation in the chain, the capability delegations signature would not match anymore.

In the example featured in graphic above, the following caveats were added to the capability delegation:

  1. NoSubdelegationsAllowed - This caveat disallows the service from sub-delegating access to a sub-service/child-service (e.g. contractors).

  2. ValidUntil - This caveat will expire the capability automatically at the specified date. At or after this date, the capability delegation is no longer valid, thus no (positive-verifiable) capability invocation can be constructed from the chain.

  3. AllowedOperations - This caveat restricts the service to only being able to execute a specified set of operations (READ, CREATE, UPDATE, DELETE). In this case, the service may only read documents, and is disallowed from writing, updating or deleting any documents from the EDV.

  4. AllowedDocuments - This caveat restricts the service to only allow operations on a specified set of documents. This is the primary caveat we are using for document sharing.

  5. CredentialStatus2020 - This caveat allows easy revocation of the capability. When the capability is verified, the specified host will be queried using the CredentialStatus2020 protocol if a specific ID has been revoked. If this is the case, the capability will not be verified positively.

These are the capabilities currently integrated into our ZCap-LD module. However, these are not the only ones we support, as built a plugin-like system for creating and integrating new restrictions in the form of ZCap-Caveats. They even be dynamically registered at runtime, as the plugin system is based upon dynamic reflective access.

Client code examples

Applicable to all following examples

val clientService = ClientService()

Setup master key (initial run)

val masterKey = "123456".toByteArray()

clientService.createMasterKey(masterKey)

Session setup

val masterKey = "123456".toByteArray()

clientService.unlockWithMasterKey(masterKey)
clientService.setupSessionService()

Create a session

val newSession = clientService.sessionService.createSession("sess01")

clientService.sessionService.selectSession(newSession.sessionId)

clientService.setup()

Create EDV

val providerUrl = "http://localhost:7000"

val result = clientService.edvService.createEdv(providerUrl)

println("Created EDV ${res.edvId} at $providerUrl")

Create document

clientService.documentService.create("documentId123", "the content".toByteArray())

Update document

clientService.documentService.update("documentId123", "new content".toByteArray())

Load document

val result = clientService.documentService.load(edvId, documentId).toString()  // or .toBytes()

Delete document

clientService.documentService.delete("documentId123")

Search document

val results = clientService.documentService.search(edvId, "keyword")
results.forEach { println(it) }

Get all documents in EDV

val results = clientService.indexService.getDocuments(edvId)
results.forEach { println(it) }

Retrieve tree of EDV

val results = clientService.indexService.getTree()
results.forEach { println(it) }

Enable notification channel

clientService.edvService.notificationsConnect(edvId) { event ->
    println("Received notification from EDV $edvId: " +
    "Document ${event.documentId} was ${event.operation.name}" +
    " by ${event.invoker}.")
}

Diconnect notification channels

clientService.edvService.notificationsDisconnect()

Export session

val jwe = sessionService.export(sessionService.sessionId)
println("Session export (it's encrypted with the masterkey): $jwe")

Basic Concepts

SSI Kit

Enterprise | Self-Managed

Run our products with the support of our experts.

  • You can use, modify and distribute our products for free and without strings attached.

  • You can use our products to build pilots or even production systems.

  • You can deploy, run and manage our products on-premise or in your own cloud environments.

To complement our free products, we offer services for clients who want to manage our products by themselves:

  • Consulting

  • Custom development and integration

  • Support / SLAs

All our products are open source under the permissive license. This means:

if you want to learn more.

Apache 2
Get in touch

Build

For building the project Gradle 7 as well as JDK 16 (or above) is required.

Building the application

First clone the Git repo and switch into the project folder:

git clone https://github.com/walt-id/waltid-storage-kit.git
cd waltid-storage-kit/

Advanced Concepts

The following sections elaborate more advanced conepts like Searchable Symmetric Encryoption (SEE), Authorization Capabilities, zCaps and Linked Data (LD) frameworks.

Cloud Platform | Managed

Let us run our products for you.

We offer our products as a managed cloud service for clients who do not want to deploy, manage and run our products by themselves.

The Cloud Platform includes:

  • All products

  • Enterprise use cases

  • Fully managed cluster

  • Cloud SLA / support

Service Access

When uploading a document, the users client will store a file key to the encrypted file index, chunk the file (explained below) and encrypt the chunks with the file key:

Client data request from services

  1. The service transmits a request to the client, indicating a context, desired data type, and it’s did:key (for the services public key).

  2. Assuming the client accepts the request, it queries the document index for the list of required chunks (in correct order), and responds with a delegated ZCap authorization for each of the specific chunks, bundled with the file key.

Sequence Diagram illustrating service access:

Data exchange between service and EDV

  1. The service contacts the EDV with a object retrieval request for each of the required file chunks. The requests are accompanied with the ZCap capability invocation the client delegated to the service.

  2. The EDV encrypts the requested objects with the public key of the delegated did:key in the ZCap capability invocation and answers them back to the service.

Note that, both of steps may happen in parallel.

Service data decryption

  1. The service decrypts each of the individually EDV-encrypted chunks with their private key.

  2. The service decrypts each of the individually client-encrypted chunks using the file key provided by the client.

  3. The service puts the chunks in the order specified by the client response.

Revocation

The client may revoke the data access of a previously shared(/trusted) service at any time using one of two possible methods:

  • The client indicates a caveat in the initial ZCap capability delegation to the service, e.g. using SimpleCredentialStatus2022, which accesses a trusted revocation service. Multiple of such may be defined.

  • The client uses the inbuilt did:key revocation feature of the EDV, which works like a blacklist, indicating otherwise valid ZCap authorizations for specific did:key ids no longer valid.

if you want to learn more.

Get in touch

Simple service example

fun main() {
    ServiceMatrix("service-matrix.properties")

    val requests = HashMap<String, DataRequest>()

    println("[Service example]")
    println()

    println("Setting up service...")

    val keyService = KeyService.getService()
    println("Creating key pair...")
    val keyId = keyService.generate(KeyAlgorithm.EdDSA_Ed25519)
    println()
    println("Key pair created: $keyId")

    val pem = keyService.toPem(keyId.id, KeyType.PUBLIC)
    println("New public key:")
    println(pem)

    println()
    println("Creating service DID...")

    val did = DidService.create(DidMethod.key, keyId.id)
    println("New service DID: $did")

    Javalin.create {
        it.enableDevLogging()
    }.routes {
        post("/accept-request/{id}") {
            val resp = it.bodyAsClass<DataResponse>()
            println("Our request was accepted, querying EDV...")
            //println(resp)

            val baseUrl = resp.baseUrl
            val edvId = resp.edvId
            val docId = resp.filesIndex.keys.first()
            val zcap = resp.capabilities.first()
            val fileKey = resp.filesIndex.values.first()

            val doc = retrieveDocument(edvId, docId, Base58.decode(fileKey), baseUrl, zcap, did)

            println(doc.toString())
        }
    }.start(8000)


    println()
    println("-------------")

    println("Generating data request...")
    val reqId = UUID.randomUUID().toString()

    val req = DataRequest("Job application", "EuropassCredential", did, "http://localhost:8000/accept-request/$reqId")
    requests[reqId] = req

    println("Data request $reqId:")

    val json = Klaxon().toJsonString(req)

    val signed = JwtService.getService().sign(keyId.id, json)

    println("JWS: $signed")
}

See also: This is a simple service that generates a data request (which is printed to the terminal), and handles acceptance of such data requests.

https://github.com/walt-id/waltid-storage-kit/blob/master/src/main/kotlin/id/walt/storagekit/service/ServiceTest.kt

Client Set-up

When a client instance is started for the very first time, a number of things have to setup first to allow creating a EDV at a provider:

  1. A master key has to be setup. For human-facing clients, this key is derived from a master passphrase. This symmetric master key will be used to encrypt all data-at-rest of the client instance.

  2. A session is created. This session is initialized with a new Ed255191 based EdDSA public-private key-pair for requests to services and EDVs, and authorization with ZCaps.

  3. This key is used to create the session DID - also known as "controller DID".

  4. The controller DID is used to request a new EDV at a chosen provider. The request contains data about the client, most importantly the did:key.

  5. The key receives the initial capability delegation from the root of trust. Several attributes are generated (e.g. IDs, sets up a did:key for the EDV) for the EDV.

An overview of how ZCap-Caveats work in the context of capability delegation

Client CLI Examples

All commands can be displayed by entering help at the session command prompt.

First startup

Master passphrase setup

[Master passphrase setup]

You have not setup a master passphrase.
A new one will be setup.

Please enter your desired master passphrase: 123456

Session wizard

[Add session]

Welcome to the session setup wizard!
Please follow the instructions below:

Enter new session identifier or enter "import" to import an exiting session.
Enter session identifier: session1

Create first EDV

Creating first EDV for session:
Enter provider url [http://localhost:7000]:

(you can just press enter here to go with the default,
otherwise, enter the remote URL)
EDV created: kGG5MGziJsak
Session "session1" was stored to local session saves.

The main CLI

[Session session1]

> session1 > 

Enter help for a list of all possible commands.

history shows the command history.

exit or quit will leave the interactive CLI.

Session management

Displaying information about the currently active session

> session1 > session info

[Session info]

ID:     session1
DID:    did:key:z6MkeVJPhuvkmKrXLdopdxbeX5XXQJSLaD4V21on9G9LY2Fa
Key ID: 187f31477eaa45978597c3520e1d5b8e

Linked EDVs:
1: kGG5MGziJsak @ http://localhost:7000

Exporting the currently active session

> session1 > session export

[Session export]

Exporting session "session1"...

Session token for "session1": eyJwMnM<snip>...
This session token is encrypted with your master passphrase.
The same master passphrase has to be used to decrypt the session token
  in the other client.

Switching the current active session

> session1 > session switch

[Select session]

Cached sessions:
- 1. session1
Type 'add' to add additional sessions using the session wizard.

Enter session identifier or index: 1

Importing a session from the session selection:

...
Enter session identifier or index: add

[Add session]

Welcome to the session setup wizard!
Please follow the instructions below:

Enter new session identifier or enter "import" to import an exiting session.
Enter session identifier: import

[Import session]

Enter session token: eyJwMnM...

Document management

Creating a document

> session1 > document create
Document identifier: doc1
Path/to/content.file: doc.txt
Generating file key...
Mapping chunks...
(Content size 30, chunks needed: 1)
Uploading EncryptedResourceStructure...
Done. Creating encrypted search index...
Uploading encrypted search index...
All done. File "doc1" is now stored to EDV "kGG5MGziJsak"!
Successfully created document!

Loading a document

> session1 > document load
Document identifier: doc1
Retrieved document "doc1" (below):
this is some document content

like document update.

Searching for a document

> session1 > document search
Enter keyword: content
Searchable Symmetric Encryption done.
Keyword "content" was found 1 time(s):
- 1. doc1

Deleting a document

> session1 > document delete
Document identifier: doc1
Successfully deleted document: doc1

Data requests

> session1 > datarequest
Enter data request: eyJraWQiOiI3ODE<snip>...
Verifying request...
Signature successfully verified.

[Job application]:
DID did:key:z6Mkmrm6<snip>GDGMd3SZk requests a EuropassCredential.

Do you want to accept at http://localhost:8000/accept-request/553bfe15-ed39-4bc9-b6f3-54ebb1a27b0d?
Accept request (y/n): y
Delegated permissions for EDV "kGG5MGziJsak" from owner "did:key:z6Mke<snip>Y2Fa" to child "did:key:z6M<snip>Zk"!
Transmitting acceptance respones...
Data request accepted!

Client Document Upload

When uploading a document, the users client will store a file key to the encrypted file index, chunk the file (explained below) and encrypt the chunks with the file key.

Encrypted search

  1. The underlying encrypted search implementation parses the document structure (depending on file format, e.g. JSON, XML, etc.)

  2. and creates a list of keywords that were found in the file. This is the search index for this file.

  3. It gets encrypted with the encrypted search key. This is the encrypted index.

Chunking

  1. The document is split into chunks. Chunks may not have a size exceeding the maximum of 1 MiB each. This is restricted per the Confidential Storage specification document.

  2. Each chunk is individually encrypted with authenticated encryption using a file key.

  3. An index (Resource Structure) is created, which is used to be able to recreate the file from the individual chunks later on. It gets encrypted, then being the encrypted chunk index (Encrypted Resource Structure).

Chunk transmission to the EDV

  1. The encrypted chunks are sent to the EDV using individual request authorizations using ZCaps.

  2. The encrypted chunk index and encrypted search index get stored in the EDV (each also being authorized using ZCap capability invocations).

ZCap-LD (Authorization)

The Storage Kit uses the concept of ZCap-LD to build a capability-based authorization, but with a modified specification.

This framework provides functionality such as sharing and managing confidential data by using capabilities. In this document any ZCap-LD capability data model is using the JSON format. A ZCap-LD capability represents several attributes:

  • a context definition

  • an associated id

  • an invoker, which represents a nonce

  • a parentCapability, which contains the whole parent ZCap-LD capability and not only a reference to the capability.

  • a proof field, which signs the capability with a Linked Data Proof to verify the capability. It also contains a proofPurpose to indicate if it is a capability delegation or invocation.

The field proof contains the following attributes:

  • "created" represents the exact date and time when the capability delegation was created

  • "creator" shows the key, a nonce, of the creator

  • "jws" is a claim signed with a signature. The server can verify the claim with a secret signing key. So the JSON Web Signature (jws) claim signature can be seen as a private key and the server’s secret signing key as the public key. With this the server is able to verify the proof.

  • "purpose" a string to show if it is a delegation or invocation.

  • "type" is used to indicate which digital signature the proof includes.

  • "verificationMethod" represents a key to verify the delegation

The root ZCap-LD capability contains another attribute called "rootCapability". It represents the associated EDV. An EDV is an encrypted data vault. This EDV can be seen as a root of trust. The EDV grants permission to the root capability and the root capability is able to grant permission to a child and so on.

Delegation

Any capability instead of the target capability needs to have a "parentCapability" property. It points on another capability or at the target. Several capabilities together form a capability chain.

In ZCap-LD delegation is handled via the capability chain.

Example:

The example represents attributes which are needed to create a ZCap-LD capability delegation. Any capability needs an unique id. This id is a simple uuid (Universally unique identifier). In this example the edv delegates permission to the root capability. So the edv serves as the invoker. The attribute invoker is represented as a did:key, nonce, starting with "z6Mk...". To verify this delegation there is a proof attribute. It contains important details: the capability was created on the 19th of december 2021 at 10:42 and 14 seconds, the key of the creator starting with "z6Mkp...", the jws starting with "eyJi...", the proof purpose shows that this capability is a delegation, it includes a digital signature produced by an ed25519 cryptographic key and the key to verify the delegation starting with "z6Mkk...".

At first the proof itself needs to be verified. This can be done by checking the jws attribute. After that the delegation can be verified by comparing the verification method and the invoker. In this example the delegation verification is successful because the key of the "invoker" property and the key of the "verificationMethod" property are equal. At the end there is an attribute called "rootCapability". It is a reference to the associated edv. The root capability can be compared to a root of trust component. It ensures that this delegation can be trusted.

Invocation

In ZCap-LD an invocation is used to authorize a further capability delegation. It contains a property called "action". This property represents the behavior of an invocation. The "action" property consists of 3 parts. What should be performed, the document id to identify the right document and the content encrypted as a SHA256 of the content.

Example:

At first the initial capability needs to be created and verified. This is done by the attribute "parentCapability". It is a capability delegation. This delegation example is described in the delegation part of this document. Then the invocation will be created. The action attribute tells what the invocation is about. In this case the system will create a new document with the name "testDocument" and the content encrypted as a SHA256ofContent. Like the delegation an invocation includes an unique id, an invoker and a proof field. This capability invocation was created on the 19th of December 2021 at 10:44 and 1 seconds, the key of the creator starts with "z6Mkk...", the jws starts with "eyJi...", the proof purpose shows that this capability is an invocation, it includes a digital signature produced by an ed25519 cryptographic key and the key to verify the invocation starts with "z6Mkk...".

To verify that the invocation has the permission to take place its proof needs to be verified. After that the attribute "verficationMethod" of the initial capability needs to be the same as the "creator" attribute of the invocation capability. In this example both keys are equal, so the initial capability grants authority to the invocation and the document will be created.

Caveats

A caveat can be used to restrict a capability. In many cases it is useful to define restrictions how the capability may be used. For child capabilities caveats don’t need to be redefined, because every capability inherit all the caveats from their parents. Caveats are identified by their types and other properties. The following code example shows how a caveat has to be declared in JSON format.

Example:

Open Source | Always Free

  • You can use, modify and distribute our products for free and without strings attached.

  • You can use our products to build pilots or even production systems.

  • You can deploy, run and manage our products on-premise or in your own cloud environments.

(Theory) Authorization Capabilities

A secure authorization system is a vital part of a Confidential Storage solution to protected data from misuse, such as data manipulation or data theft. This is true for data storage and data transfer as data must be processed confidentially and may only reach those who have the respective right to access the data.

ZCap-LD uses the object capability model to grant and express authority. Its main function is to securely share and manage data. We opted for a capability-based system (instead of a traditional access control list based system).

To understand capability-based systems it is useful to start with the concept of capabilities:

Basically, a capability consists of a token or a key. The owner uses it to verify that he/she has permission to access an entity or object. It is implemented as a data structure consisting of the access rights and a unique identifier:

This identifier points to a specific object. The access rights declare which operations may be performed. For example, the access rights define a read-only access on a file, or a write access on a memory segment.

In a capability-based system, each user has access to a capability list. With these capabilities the system is able to check if the user is allowed to interact with the object.

A capability protects the object from unauthorized access, but the whole concept is useless if a capability is not protected from manipulation. If a program could change the access list and the identifier of a capability at any time, the program would be able to force access to any object. Therefore, a capability-based system is usually built in such a way that direct modifications by a program are not allowed. So the capability list can only be modified by the operating system or the hardware. What programs can do is to call operating system or hardware operations. That means programs can get new capabilities, delete capabilities or change the rights in a capability.

The goal of an authorization system is to protect confidential data. Conventional systems only partially perform this important task. Capabilities can be used to prevent processes from gaining access to data for which they do not have permission. This means a process is allowed to access capabilities which are necessary to only access the resources that the process needs. This ensures that no confidential data is revealed without having the right permissions to access this data.

The concept of capabilities can be well combined with sandboxing (i.e. the creation of an isolated test environment in which it is safe to execute suspicious URLs or files). Capabilities can be used to grant access to all data associated with the program. That means capabilities are used to sandbox every process in the program and giving it access to all associated data. This prevents the program from gaining access to sensitive data located on the other side (i.e. on the user’s desktop).

All our products are open source under the permissive license. This means:

if you are using our products. We are curious to learn about your experience and happy to spread the word about your project via our newsletter or case studies.

Apache 2
Get in touch
Intro Storage-Kit
The general protocol sequence is described in this sequence diagram. It has the specialty that to authorize the service for an particular EDV, no contact with the EDV has to be established, the authorization may happen entirely offline [from the client-side point-of-view] (given there is another [offline] way to exchange the authorization with the EDV, e.g. via QR codes).
This is an example of an invocation to create a new document in JSON format.
For example Bob wants to buy a new car. He searched on the internet and found a guy called Jimmy who sells his dream car. He wants to take a test drive. So he asks Jimmy if he can get the key for the car. Before Jimmy gives the key to Bob he wants to make sure that Bob comes back with the car. He decides there is no need to take the risk that Bob just wants to steal his car. That’s why Jimmy creates a new more restricted capability and delegates the new car key to Bob. The car’s gauge currently says 149980 kilometers driven. So as we can see in the code example above Jimmy restricts Bob to drive up to 20 kilometers, but no more.