Onderteken elk WebSocket-bericht

TLS bewijst dat het kanaal niet met de wire is geknoeid. Het bewijst niet wie een bericht heeft geproduceerd, en het overleeft niet zodra het bericht wordt gelogd, gepersisteerd of herhaald. Deze tutorial combineert sgcWebSockets en sgcSign zodat elk frame dat je server uitstuurt — of elk frame dat je client verstuurt — een detached CAdES-handtekening draagt die de andere kant valideert tegen een bekend signer-certificaat.

Identiteits-gebonden berichten
Detached CAdES per frame
Overleeft replay en logging

TLS is niet genoeg

TLS beschermt bytes onderweg tussen twee endpoints. Zodra een frame wordt gelogd, gearchiveerd of door middleware herhaald, is het TLS-bewijs weg — alleen een digitale handtekening op de payload zelf overleeft.

Non-repudiation per bericht

Trading floors, betting exchanges, IoT-commandkanalen en klinische data-feeds hebben allemaal een tamper-proof audit trail per bericht nodig. Een detached CAdES-handtekening op elk frame laat de ontvanger uren of jaren later precies bewijzen wie wat wanneer uitstuurde.

Wederzijdse identiteit

WebSocket mTLS bewijst de client-identiteit bij verbindingstijd. Per-message-ondertekenen bewijst de applicatie-identiteit per frame — nuttig wanneer de WebSocket-gateway een gedeelde edge-proxy is en de echte producer downstream zit.

Replay-veilig

Neem de WebSocket-sessie-id en een monotoon stijgende nonce op in de ondertekende payload. Een replay-aanvaller kan het bericht niet opnieuw ondertekenen met een nieuwe nonce zonder toegang tot de private key.

Envelope-vorm

Wikkel elk WebSocket-bericht in een kleine JSON-envelope die de payload, een nonce, een timestamp en de base64-gecodeerde detached-handtekening draagt.

Envelope-contract

De envelope wordt ondertekend over een deterministische byte-volgorde: session_id || nonce || timestamp || payload_sha256. We gebruiken SHA-256 omdat de payload binair kan zijn (Protobuf, MessagePack) en we een input van vaste lengte naar de signer willen.

CAdES is hier de juiste keuze — het produceert compacte CMS/PKCS#7-handtekeningen die base64-gecodeerd klein genoeg zijn om comfortabel naast de payload te passen. PAdES is PDF-specifiek en XAdES is XML-specifiek; geen van beide past bij een binair-of-JSON real-time-kanaal.

envelope.json
{
  "sid": "7f3a-b6e1",
  "nonce": 42,
  "ts": "2026-05-26T11:24:09Z",
  "payload": { /* the actual app message */ },
  "sig": "MIIK...base64...detached CAdES..."
}

Haal de units binnen en laad de signing-key

sgcWebSockets levert het transport, sgcSign levert de signature-engine. Ze delen geen state — je bedraadt ze in je applicatiecode.

Uses-clausule

Voor zowel client als server heb je de WebSocket-componenten, het CAdES-profiel, een key-provider en de verifier nodig. Gebruik een PFX voor de client-kant en een langlevend service-certificaat (meestal uit Windows-store) voor de server-kant.

uses-clauses.pas
uses
  Classes, SysUtils,
  // sgcWebSockets
  sgcWebSocket, sgcWebSocket_Classes,
  // sgcSign
  sgcSign_KeyProvider_PFX,
  sgcSign_CAdES,
  sgcSign_Profile_CAdES,
  sgcSign_Verifier,
  sgcJSON;

Client-kant — onderteken voor versturen

Haak in op de uitgaande flow van de client, bereken het digest van payload plus sessie-metadata, koppel de CAdES-handtekening en stuur de envelope uit via WriteData.

Wikkel de payload

Houd de signer en key-provider als velden op je formulier of data-module. TsgcSignCAdES.SignDetached accepteert een byte-buffer en geeft de detached CMS-structuur terug. We bouwen de canonieke byte-volgorde in BuildToBeSigned — sessie-id, nonce, ISO 8601-timestamp en SHA-256 van de payload-bytes.

Het aanroepen van WriteData op TsgcWebSocketClient verstuurt de envelope als een gewoon tekstframe. De server-side verifier draait dit proces om voordat de payload aan de applicatiehandler wordt doorgegeven.

client-send.pas
procedure TForm1.SendSignedMessage(const aPayload: string);
var
  vTBS: TBytes;
  vSig: TBytes;
  vEnvelope: ISuperObject;
begin
  Inc(FNonce);
  vTBS := BuildToBeSigned(FSessionId, FNonce, NowUTC, aPayload);

  vSig := FCAdESSigner.SignDetached(vTBS);  // CMS SignedData, bytes

  vEnvelope := SO();
  vEnvelope.S['sid']     := FSessionId;
  vEnvelope.I['nonce']   := FNonce;
  vEnvelope.S['ts']      := DateToISO8601(NowUTC, True);
  vEnvelope.S['payload'] := aPayload;
  vEnvelope.S['sig']     := TNetEncoding.Base64.EncodeBytesToString(vSig);

  WSClient.WriteData(vEnvelope.AsJSon);
end;

Server-kant — verifieer voor dispatch

Haak in op het OnMessage-event van de server, bereken de to-be-signed-bytes opnieuw uit de envelope-velden, verifieer de detached-handtekening en stuur dan pas de payload door naar je businesslogica.

OnMessage-handler

De handler parset de envelope, bouwt de canonieke byte-volgorde opnieuw op en roept TsgcSignatureVerifier.VerifyDetached aan met de signature-bytes en de to-be-signed-bytes. De verifier geeft een rapport terug met de signer-subject, de chain-status en eventuele timestamp-metadata.

Cache vertrouwde signer-certificaten op thumbprint binnen de server — volledige chain-verificatie op elk frame wordt duur op high-throughput-verbindingen. Na de eerste verificatie hoef je alleen te bevestigen dat de handtekening geldig is en dat het thumbprint er een is die je al hebt vertrouwd.

server-receive.pas
procedure TFormSrv.WSServerMessage(Connection: TsgcWSConnection;
  const Text: string);
var
  vEnv: ISuperObject;
  vSig, vTBS: TBytes;
  vReport: TsgcSignatureReport;
begin
  vEnv := SO(Text);
  vSig := TNetEncoding.Base64.DecodeStringToBytes(vEnv.S['sig']);
  vTBS := BuildToBeSigned(
    vEnv.S['sid'], vEnv.I['nonce'],
    ISO8601ToDate(vEnv.S['ts'], True),
    vEnv.S['payload']);

  vReport := FVerifier.VerifyDetached(vSig, vTBS);
  if vReport.Signatures[0].Status <> svValid then
  begin
    Connection.Disconnect;
    Exit;
  end;

  HandlePayload(Connection, vEnv.S['payload']);
end;

Replay-bescherming

Een geldige handtekening is niet genoeg — een aanvaller die een ondertekende envelope opvangt, kan deze herhalen zolang het certificaat nog geldig is. Bind elk bericht aan de sessie en dwing monotone nonces af.

Nonce-window per verbinding

Houd een server-side dictionary bij gesleuteld op (SessionId, SignerThumbprint) met de hoogste tot dusver geziene nonce. Weiger elke envelope wiens nonce niet strikt groter is. Weiger elke envelope wiens timestamp meer dan enkele seconden van de server-klok ligt — begrens het replay-venster ook als dezelfde nonce op de een of andere manier wordt hergebruikt.

Voor lossy netwerken waar ordening niet gegarandeerd is, schakel de strikte greater-than-check naar een sliding window: accepteer elke nonce binnen de laatste N entries die nog niet is gezien, en weiger alles dat ouder is. De fundamentele vereiste is dat geen twee frames dezelfde (SessionId, Nonce) dragen.

replay.pas
function TFormSrv.AcceptNonce(const aSid: string;
  aNonce: Int64): Boolean;
var
  vLast: Int64;
begin
  FNonceLock.Acquire;
  try
    vLast := FNonces.Items[aSid];
    Result := aNonce > vLast;
    if Result then
      FNonces.AddOrSetValue(aSid, aNonce);
  finally
    FNonceLock.Release;
  end;
end;

Wat meestal misgaat

Drie failure modes die we het vaakst zien wanneer ontwikkelaars sgcSign voor het eerst in sgcWebSockets bedraden.

Inconsistente canonicalisatie

Als de client de to-be-signed-bytes bouwt uit een JSON-string met insertion-order-keys en de server ze opnieuw bouwt uit een geparset en opnieuw geserialiseerd object, zullen de digests niet overeenkomen. Onderteken altijd een deterministische byte-volgorde die je zelf construeert — onderteken geen opnieuw geserialiseerde JSON.

Latentie op high-throughput-streams

Elk bericht ondertekenen met een RSA-2048-key voegt enkele milliseconden per frame toe. Op 10k msg/s-streams wordt dat een bottleneck. Schakel over op ECDSA-P256-keys (een orde van grootte sneller) of onderteken in batches door N berichten in een enkele envelope te groeperen.

De SubjectKeyIdentifier vergeten

CAdES neemt standaard het signer-certificaat op, wat elk frame met 1-2 KB opblaast. Gebruik vProfile.IncludeCertificate := False na het eerste frame in een sessie en laat de verifier matchen op SubjectKeyIdentifier tegen zijn vertrouwde-signer-cache.

Waar nu verder

End-to-end-encryptie vult ondertekenen aan. De sgcSign Server centraliseert key-custody voor vloten van clients.

10 key-providers

Gebruik Azure Trusted Signing, AWS KMS, Google KMS of HashiCorp Vault om private keys volledig buiten het applicatieproces te houden.

Lees meer →

sgcSign Server

REST signing-service zodat clients nooit de private key zien. Nuttig wanneer de WebSocket-producer een sandboxed worker is.

Lees meer →

sgcWebSockets

De volledige WebSocket- / HTTP2- / HTTP3- / MQTT- / AMQP-transportbibliotheek die in deze tutorial wordt gebruikt.

Lees meer →

Klaar om elk WebSocket-frame te ondertekenen?

Beide bibliotheken worden in dezelfde proefversie geleverd. Download een keer, bouw het end-to-end ondertekend kanaal in een middag.