CSJCurrent de:Eigene Client Store bereitstellen

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



Einführung

Im bestimmten Situationen kann es sinnvoll sein, auf das Erstellen einer "client.store" Datei zur Aufbewahrung von Verifizierungsdaten zu verzichten. Um dieses Szenario zu unterstützen, bieten wir verschiedene Möglichkeiten an, die Verifizierungsdaten sicher aufzubewahren. Untenstehend finden Sie ein Diagramm, welches die Klassen und Interfaces veranschaulicht, die Sie zum Bereitstellen eines benutzerdefinierten `IStore` verwenden können. 65669268.png

IStore

Interface

Standardmäßig wird eine Client Instanz durch das Übergeben einer E-Mail-Adresse, einer Cryptshare Web Service URI, sowie ein Pfad zu einer Client Store, welche den Verifizierungszustand jedes einzelnen Benutzers aufbewahrt und je nach Betriebssystem unter Windows mithilfe der Microsoft Data Protection API (DPAPI) und unter Linux mithilfe einer AES-Verschlüsselung geschützt wird, erstellt. Es gibt jedoch auch die Möglichkeit, ein benutzerdefiniertes `IStore` zu übergeben, welches die Methoden `g``etValue`, `s``etValue`, `r``emoveValue` und `p``ersist` implementiert. Dies könnte beispielsweise dann nützlich sein, wenn Sie den Verifizierungszustand in einer Datenbank oder einem anderen Ort ablegen möchten und eine benutzerdefinierte Implementierung für die jeweilige Methode anbieten. Wir stellen für das `IStore` die Referenzimplementierung `ProtectedFileStore` bereit, welche auch intern genutzt wird, um den Inhalt der Client Store mithilfe der DPAPI oder des Machine Keys zu schützen.

Implementierung

Das `ProtectedFileStore` ist eine Referenzimplementierung für das `IStore` und führt, zusätzlich zur Implementierung der `IStore` Methoden, den `IProtectionService` ein. Dieser Service kapselt das Schützen der Daten von der Datenablage und ermöglicht Ihnen beispielsweise die Angabe einer Verschlüsselungsmethode, ohne dass Sie eine separate Implementierung für den Datenzugriff anbieten müssen. Das `ProtectedFileStore` stellt die statische Methode `Load` bereit, die zusätzlich zum Dateipfad auch ein `IProtectionService` akzeptiert, welcher den zu nutzenden Schutzmechanismus definiert. Wenn kein `IProtectionService` übergeben wird, wird standardmäßig der `DpApiProtectionService` bzw. der `AesProtectionService` verwendet (diese Vorgehensweise wird intern genutzt, wenn Sie einen `Client` mit einem Dateipfad statt `IStore` instanzieren). Wenn Sie mehr Kontrolle darüber haben möchten, wie die Client Store geschützt werden soll, können Sie auch einen eigenen `IProtectionService` bereitstellen, für den wir auch eine Referenzimplementierung anbieten, um den gängigsten Use Case abzudecken.

IProtectionService

Interface

Der `IProtectionService` beschreibt das allgemeine Konzept des Schutzes eines Byte-Arrays über die jeweiligen Methoden `protect` und `unprotect`. Der Service kommt primär in der `IStore` Referenzimplementierung `ProtectedFileStore` zum Einsatz - der Inhalt der Datei wird geschützt, sobald `IStore#persist` aufgerufen wird und der Schutz wird aufgehoben, sobald `ProtectedFileStore#load` aufgerufen wird. Der `ProtectedFileStore` verwendet standardmäßig den `DpApiProtectionService` unter Windows und den `AesProtectionService` unter Linux. Der `AesProtectionService` nutzt einen AES-Verschlüsselungsalgorithmus und erlaubt Ihnen, die Quelle des Schlüssels selbst anzugeben.

Implementierung

Der `AesProtectionService` ist eine Implementierung des `IProtectionService` und stellt eine Alternative zum `DpApiProtectionService` dar. Während sich letzteres auf die einfache Bedienbarkeit fokussiert und die Schlüsselverwaltung übernimmt, erlaubt Ihnen der `AesProtectionService` die Angabe der Quelle des Schlüssels. Dies ist besonders dann nützlich, wenn Sie einen möglicherweise bereits existierenden Schlüssel verwenden möchten. Sie können einen `AesProtectionService` einfach durch die Angabe eines `IKeySourceProviders` erstellen.

IKeySourceProvider

Interface

Der `IKeySourceProvider` beschreibt die Anforderungen an die Implementierung des Quellmaterials zum Aufbau eines kryptografischen Schlüssels. Diese Schlüsselquelle wird vom `AesProtectionService` verwendet, um den Speicherinhalt zu ver- und entschlüsseln.

Implementierung

Die `LinuxKeySourceProvider`-Klasse ist eine Referenzimplementierung, die standardmäßig unter Linux verwendet wird, als `IKeySourceProvider` für den `AesProtectionService`. Diese Klasse erstellt standardmäßig einen Schlüssel basierend auf der Machine-ID, falls diese verfügbar ist. Falls nicht, basiert der Schlüssel auf den MAC-Adressen der Netzwerkschnittstellen des Systems. Der generierte Schlüssel wird in ein Bytearray umgewandelt und wird als Quelle eines kryptografischen Schlüssel für den `AesProtectionService` verwendet.

`Der LinuxKeySourceProvider` schützt den Store unter der Voraussetzung, dass die Machine-ID auf allen Maschinen in Ihrer Umgebung einzigartig sind. Dies kann von Bedeutung sein, wenn Sie ein sogenanntes "goldenes" oder "Master"-Image zur Instanziierung Ihrer VMs verwenden und Sysprep auf dem Image ausführen, um eindeutige IDs sicherzustellen. Wenn Sie möchten, dass der Store nur von einer bestimmten Maschine aus zugänglich ist und sichergestellt wird, dass Benutzer immer einer bestimmten Maschine zugeordnet sind, dann ist der `LinuxKeySourceProvider` eine geeignete Lösung.

Beispiel: Benutzerdefinierte IStore mittels AesProtectionService und LinuxKeySourceProvider

Das folgende Programm implementiert ein benutzerdefiniertes `IStore` auf Basis eines `ProtectedFileStore`, welches wiederum zum Schützen des Client Store-Inhalts den `AesProtectionService` und `LinuxKeySourceProvider` als Quelle des Schlüssels nutzt.

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());
			}
		}
	}
}

Beispiel: Benutzerdefinierte IStore mit in einer Datenbank gespeicherten Schlüsseln

Erstellen Sie eine eigene Implementierung des `IStore`-Interfaces. Nachfolgend ein grobes Beispiel, wie eine eigene Implementierung aussehen kann:

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();
	}
}

Sobald Sie eine eigene Implementierung des `IStore`-Interfaces erstellt haben, in diesem Fall mit dem Namen `CustomStore`, so können Sie den Client wie folgt initialisieren:

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