CSJCurrent en:Provide your own Client Store

Aus Cryptshare Documentation
Version vom 13. Oktober 2021, 11:17 Uhr von Maintenance script (Diskussion | Beiträge) (Imported from text file)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu:Navigation, Suche



Introduction

There may be situations in which you don't want your application to write a "client.store" file containing the verification states to disk. To support this scenario, we offer several ways store the verification states in a secure manner. Below is an overview of the classes and interfaces that allow you to provide a custom `IStore` implementation. 65669232.png

IStore

Interface

The default approach to instantiating a `Client` is to pass its constructor a user email address, a Cryptshare Web Service URI, as well as a path to the client store that will persist the verification state of each user and will utilise Microsoft's Data Protection API on Windows machines and AES encryption on Linux machines to protect its contents. However, we also offer the possibility to pass in a custom `IStore` interface that implements the methods `getValue`, `s``etValue`, `r``emoveValue`, and `p``ersist`. This might be useful if you, for example, want to store the verification state in a database or as a data stream in a remote location while providing custom implementations for each method. We provide a reference `IStore` implementation called `ProtectedFileStore`, which is also used internally to protect the contents of the client store via the DPAPI or the machine key.

Implementation

The `ProtectedFileStore` class is a reference `IStore` implementation and, alongside implementing the `IStore` methods, introduces the concept of protecting the store contents via an `IProtectionService` to encapsulate the data storage/retrieval process from the data protection/unprotection process. This allows you to use different protection mechanism without having to provide a separate implementation for the data storage. The `ProtectedFileStore` exposes the static method `Load` and, in addition to the file path, may also accept an `IProtectionService` which defines the protection mechanism to be used. If no `IProtectionService` is passed, it will default to the `DpApiProtectionService` or the `AesProtectionService` (this approach is used internally when you instantiate a `Client` with just a store path instead of an `IStore`). If you want to have more control over how the data is going to be protected, you may provide your own custom `IProtectionService` , for which we also provide a reference implementation to cover the most common use case.

IProtectionService

Interface

The `IProtectionService` interface describes the general notion of protecting and unprotecting a byte array via their respective methods `protect` and `unprotect`. It is primarily used in the reference `IStore` implementation called `ProtectedFileStore`–data is protected whenever the `IStore#persist` method is called, and unprotected whenever the `ProtectedFileStore#load` method is called. By default, the `ProtectedFileStore` uses the `DpApiProtectionService` under Windows and `AesProtectionService` under Linux. The `AesProtectionService` uses the AES encryption algorithm and allows you to provide a custom key source.

Implementation

The `AesProtectionService` class is an `IProtectionService` implementation and an alternative to the `DpApiProtectionService`. While the latter focuses on ease of use and takes the key management part off your hands, the `AesProtectionService` allows you to specify a custom key source provider. This is especially useful if you want to make use of an already existing key source. You can create an `AesProtectionService` by simply providing it an `IKeySourceProvider` .

IKeySourceProvider

Interface

The `IKeySourceProvider` describes the contract for the implementation of the source material to build a cryptographic key. This key source will be used by the `AesProtectionService` to en- and decrypt the store content.

Implementation

The `LinuxKeySourceProvider` class is a reference implementation used by default under Linux, as `IKeySourceProvider` for the `AesProtectionService`. It creates a key based on the machine id by default, but falls back to MAC addresses as key source base if the machine id is not available. The generated key will be converted to a byte array and then used to provide a cryptographic key for the `AesProtectionService`.

The `LinuxKeySourceProvider` protects the store under the assumption that the machine ID is unique across all machines in your environment. This may be of importance if you use a so-called "golden" or "master" image to instantiate your VMs and run Sysprep on the image to ensure unique IDs. If you want the store to only be accessible from a particular machine and users are ensured to always be assigned to a particular machine, then `LinuxKeySourceProvider` is a suitable solution.

Example: Custom IStore using AesProtectionService and LinuxKeySourceProvider

The following code implements a custom `IStore` that utilises the `ProtectedFileStore` and `AesProtectionService` with the `LinuxKeySourceProvider` as the key source.

package com.cryptshare.examples.api;
 
import com.cryptshare.api.*;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.Locale;
 
public class ExampleMain {
 
    public static void main(String[] args) {
        try {
            IProtectionService protectionService = new AesProtectionService(new LinuxKeySourceProvider());
            IStore store = ProtectedFileStore.open(Paths.get("/opt/tmp"), protectionService);
            Client client = new Client("foo@example.com",
                    Locale.ENGLISH,
                    new CryptshareConnection(new WebServiceUri("https://cryptshare.example.com")),
                    store);
            CheckVerificationResult cvr = client.checkVerification();
            if (!cvr.isUserVerified()) {
                client.requestSenderVerification();
                System.out.println("A verification code has been sent to '" + client.getUserEmailAddress()
                        + "'; please enter the code below and confirm with [Enter]:");
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String vericode = reader.readLine();
                reader.close();
                client.confirmSenderVerification(vericode.trim());
            } else {
                System.out.println("The sender '" + client.getUserEmailAddress() + "' is verified; the client ID is:");
                System.out.println(client.getClientId());
            }
        } catch (Exception e) {
            System.err.println("An error has occurred: " + e);
        } finally {
            System.out.println("Press any key to terminate the program.");
            try {
                System.in.read();
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
        }
    }
}

Example: Custom IStore with keys stored in database

Create an own implementation of the IStore interface. The following example shows one possible implementation when key values pairs are stored inside an external database:

package com.cryptshare.examples.api;
 
import com.cryptshare.api.ClientException;
import com.cryptshare.api.IStore;
import javax.persistence.*;
 
/**
 * A store for key/value pairs that can be persisted.
 */
public class CustomStore implements IStore {
    EntityManager entityManager;
     
    /**
     * Gets a stored value from the store.
     *
     * @param key Unique key to identify the value. If null, the method returns null.
     * @return The value that corresponds to the given key, or null, if the key was null or not found.
     */
    @Override
    public String getValue(String key) {
        // Get the value from your database
        Query query = entityManager.createQuery("select keyValue from clientStorage where id = :key");
        query.setParameter("key", key);
        return (String) query.getResultList().get(0);
    }
 
    /**
     * Sets a value in the store.
     *
     * @param key   Unique key for the value. If null, the method call has no effect.
     * @param value Value to be stored. If null, the method call has no effect.
     */
    @Override
    public void setValue(String key, String value) {
        // Write the value to your database
        Query query = entityManager.createNativeQuery("insert into clientStorage (id, keyValue) values (:key, :value)");
        query.setParameter("key", key);
        query.setParameter("value", value);
        query.executeUpdate();
    }
 
    @Override
    public void persist() throws ClientException {
        entityManager.flush();
    }
 
    @Override
    public void removeValue(String key) {
        // Write the value to your database
        Query query = entityManager.createNativeQuery("delete from clientStorage where id = :key");
        query.setParameter("key", key);
        query.executeUpdate();
    }
}

As soon as you created an own implementation of the `IStore` interface, in this case named `CustomStore`, you are able to initialize the `Client` by using this constructor:

Client client = new Client(SENDER_EMAIL_ADDRESS, Locale.ENGLISH, connection, new CustomStore());