CSJCurrent de:Eigene Client Store bereitstellen
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.
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.
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());