Ende-zu-Ende-Verschlüsselung
Die Kommunikation zwischen STP.Documents.OnPremise Mobile DESK und dem On-Premise-Agenten ist verschlüsselt. Da Daten und Dokumente aus dem sicheren On-Premise-System über die Cloud übertragen werden, müssen sie so verschlüsselt werden, dass nur die jeweiligen Empfänger sie entschlüsseln können. Dieser Verschlüsselungsmechanismus wird auch als Ende-zu-Ende-Verschlüsselung bezeichnet, da nur die Endpunkte der Kommunikation den Klartext kennen oder wiederherstellen können. Andere Kommunikationsteilnehmer, wie Smartphones anderer Nutzer oder Agenten anderer Firmen, können die Kommunikationsinhalte nicht entschlüsseln. Selbst STP kann nicht sehen, was zwischen der App und dem Agenten übertragen wird.
Folgende Algorithmen werden verwendet:
- ECDH/ECDSA auf Kurve P-256 für den Schlüsselaustausch
- AES-GCM 256bit für die symmetrische Verschlüsselung
Folgendes wird sichergestellt:
- Bevor das Gerät eine verschlüsselte Verbindung herstellen kann, muss es sich im Namen des Benutzers mittels OpenID Connect (OIDC) authentifizieren.
- Private Schlüssel verbleiben an den Endpunkten, d.h. in der App und im Agenten.
- Nur die öffentlichen Schlüssel der beteiligten Endpunkte sind den jeweiligen anderen Endpunkten bekannt.
- Kurzlebige verschlüsselte Verbindungen werden aus den privaten Schlüsseln der Endpunkte abgeleitet.
- Selbst wenn ein privater Schlüssel bekannt werden sollte, kann aufgezeichnete Kommunikation nicht entschlüsselt werden (Perfect Forward Secrecy).
- Nach 15 Minuten oder 1000 Anfragen wird eine neue verschlüsselte Verbindung hergestellt, und Daten aus der alten können nicht mehr wiederhergestellt werden.
- Für das Senden und Empfangen von Daten werden separate Schlüssel verwendet.
- Verschlüsselte Anfragen werden nur einmal akzeptiert und können nicht wiederholt werden (Schutz vor Replay-Angriffen).
- Jede Dokumentversion hat ihren eigenen einzigartigen privaten Schlüssel.
- Die Ende-zu-Ende-verschlüsselte Verbindung zwischen App und Cloud sowie Cloud und Agent ist zusätzlich in eine TLS 1.2+ Verbindung eingebettet.
- Die verwendeten kryptografischen Algorithmen werden von allen modernen Browsern unterstützt und sind sogar auf dem iPhone hardwarebeschleunigt.
Verbindungsaufbau
Wenn die App eine Verbindung zum Agenten herstellen möchte, um Daten und Dokumente abzurufen, muss sie zunächst eine sichere Verbindung aufbauen. Dies funktioniert wie folgt:
//Authentifizierung
var tokenProvider = new ResourceOwnerPasswordCredentials( authority: new Authority(new Uri("https://...stp-cloud.de/identity/")), ...);
var keys = new KeyStore("local-keys");
keys.Prepare();
//Vorbereitung der Ende-zu-Ende-Verschlüsselung
using var device = new Device(tokenProvider);
await device.LoadAgentInfo();
Info($"Agent {device.Agent.Fingerprint}");
var deviceName = "Beispielanwendung";
if (!keys.Contains(deviceName)) //Wenn das Gerät neu ist, muss es zuerst registriert werden
{
Info($"Registriere neues Gerät '{deviceName}'...");
keys.Add(deviceName, await device.RegisterAs(deviceName));
keys.Postpare();
}
//Einrichtung der Ende-zu-Ende-Verbindung
using (var session = await device.Login(keys[deviceName]))
{
Info($"Sitzung {session.SessionId} hergestellt");
//Anfragen können nun verschlüsselt & gesendet und Antworten empfangen & entschlüsselt werden
...
}Der erste Kontakt zwischen der App und dem Agenten erfolgt über die LoadAgentInfo-Methode (/api/sessions/agentinfo?api-version=1.0). Hierbei werden der öffentliche Schlüssel und einige Eigenschaften des Agenten abgerufen, um sicherzustellen, dass die App im nächsten Schritt die Verbindung zum richtigen Agenten herstellt.
Wenn noch kein Gerät registriert wurde, kann dies mit der Methode RegisterAs (/api/sessions/register?api-version=1.0) erfolgen. Dabei wird die WebCrypto-API des Browsers verwendet, um ein neues zufälliges Schlüsselpaar zu erzeugen, das aus einem privaten und einem öffentlichen Schlüssel besteht. Der private Schlüssel wird mit dem öffentlichen Schlüssel des Agenten verknüpft und bleibt auf dem Gerät. Der entsprechende öffentliche Schlüssel wird an den Agenten gesendet. Dadurch kennen sowohl die App als auch der Agent die öffentlichen Schlüssel des jeweils anderen.
var key = WebCrypto.CreateAsymmetricKey();
var myFingerprint = WebCrypto.Fingerprint( key.GetParams( exportPrivate: false));
var privateKey = WebCrypto.EncodeKeyToBase64( key.GetParams( exportPrivate: true), this.Agent.PublicKey.GetParams( exportPrivate: false));
var (myPrivateKeyParams, agentKeyParams) = WebCrypto.DecodeKeyFromBase64( privateKey);
var myPublicKey = new JsonWebKey(myPrivateKeyParams);
var response = await Http.Post(
url: Url($"/api/sessions/register?api-version=1.0"),
body: new
{
Device = deviceName,
DeviceFingerprint = myFingerprint,
PublicKey = myPublicKey,
});Bei der Registrierung muss sichergestellt werden, dass der der App bekannte öffentliche Schlüssel des Agenten tatsächlich mit dem öffentlichen Schlüssel des Agenten in der lokalen Umgebung übereinstimmt. Dieses Prinzip ist als Trust On First Use (TOFU) bekannt. Der Benutzer muss dies manuell überprüfen, indem er die Fingerabdrücke der öffentlichen Schlüssel vergleicht. Wenn dies nicht sichergestellt ist, könnte die App sich mit einem gefälschten Agenten verbinden.
Sobald die App und der Agent die öffentlichen Schlüssel des jeweils anderen kennen, kann der Schlüsselaustausch stattfinden. Dazu wird die Login-Methode aufgerufen (/api/sessions/start?api-version=1.0). Zuerst wird überprüft, ob der Agent noch den öffentlichen Schlüssel hat, der bei der Geräte-Registrierung verwendet wurde. Dann werden kryptografische Verbindungsparameter aus dem privaten Schlüssel der App abgeleitet und an den Agenten gesendet. Der Agent leitet ebenfalls kryptografische Verbindungsparameter aus seinem eigenen privaten Schlüssel ab und gibt sie an die App zurück. Mit diesen kryptografischen Parametern können die App und der Agent nun geheimes Schlüsselmateriel für die Ende-zu-Ende-verschlüsselte Verbindung mithilfe von Elliptic-curve Diffie-Hellman auf der Kurve P-256 (ECDH-P256) ableiten.
var (myPrivateKeyParams, agentKeyParams) = WebCrypto.DecodeKeyFromBase64( privateKey);
this.myPrivateKey = new JsonWebKey(myPrivateKeyParams);
var agentKey = new JsonWebKey(agentKeyParams);
var myPublicJwk = new JsonWebKey( this.myPrivateKey.GetParams( exportPrivate: false));
if (this.Agent.PublicKey.X != agentKey.X || this.Agent.PublicKey.Y != agentKey.Y)
{
throw new Exception("AGENT_PUBLIC_KEY_CHANGED");
}
this.MyFingerprint = WebCrypto.Fingerprint( myPublicJwk.GetParams( exportPrivate: false));
var payload = WebCrypto.PrepareSession( this.myPrivateKey.GetParams( exportPrivate: true), out var a, out var b);
var response = await Http.Post(
url: Url($"/api/sessions/start?api-version=1.0"),
body: new ByteArrayContent(payload),
customHeaders: new[]
{
("Device", this.MyFingerprint)
});
var content = await response.Content.ReadAsByteArrayAsync();
var sessionId = string.Join(",", response.Headers.GetValues("x-session-id"));
var (encryptionKey, decryptionKey) = WebCrypto.SessionKeyExchange(a, b, this.Agent.PublicKey.GetParams( exportPrivate: false), content);
return new Session(this, sessionId, encryptionKey, decryptionKey);Verbindung
Die App und der Agent können nun sicher über die etablierte Ende-zu-Ende-verschlüsselte Verbindung (/api/requests?api-version=2.0) kommunizieren. Eine Anfrage von der App wird mithilfe des Sitzungsschlüssels verschlüsselt. Die Antwort des Agents wird dann entsprechend entschlüsselt.
object paylod = requestDataFromAgent;
var request = new
{
PayloadType = paylod.GetType().Name,
Payload = paylod,
};
var requestEncoded = Encoding.UTF8.GetBytes( Device.Serialize(request));
var encryptedRequest = WebCrypto.EncryptSymmetric( requestEncoded, this.encryptionKey);
var response = await Http.Post(
url: device.Url($"/api/requests?api-version=2.0"),
body: new ByteArrayContent(encryptedRequest),
customHeaders: new[]
{
("x-session-id", this.SessionId)
});
var content = await response.Content.ReadAsByteArrayAsync();
var dataFromAgent = WebCrypto.DecryptSymmetric(content, this.decryptionKey);Während die App nur eine einzige Verbindung mit dem Agenten aufrechterhalten muss, muss der Agent mehrere parallele Verbindungen mit verschiedenen Apps offenhalten. Diese Verbindungen werden jeweils im flüchtigen Speicher der Kommunikationsteilnehmer gehalten. Wenn die App oder der Agent neu gestartet wird, müssen die Verbindungen neu hergestellt werden.
Damit der Schlüsselaustausch und die Verschlüsselung wie beschrieben funktionieren, müssen die Endpunkte ihre privaten Schlüssel geheim halten. Das bedeutet, dass sie diese selbst verwalten müssen. Wie dies geschieht, wird im Zusammenhang mit der Agenteninstallation und der App-Installation beschrieben.
Verknüpfung mit