Podpisz każdą wiadomość WebSocket

TLS udowadnia, że kanał nie został zmanipulowany na łączu. Nie udowadnia, kto wyprodukował wiadomość, i nie przetrwa, gdy wiadomość zostanie zalogowana, utrwalona lub odtworzona. Ten samouczek łączy sgcWebSockets i sgcSign, więc każda ramka emitowana przez Twój serwer — lub każda ramka wysyłana przez Twojego klienta — przenosi detached podpis CAdES, który druga strona waliduje względem znanego certyfikatu podpisującego.

Wiadomości związane z tożsamością
Detached CAdES na ramkę
Przetrwa replay i logowanie

TLS nie wystarcza

TLS chroni bajty w locie między dwoma endpointami. Gdy ramka zostanie zalogowana, zarchiwizowana lub odtworzona przez middleware, dowód TLS znika — przetrwa tylko cyfrowy podpis na samym ładunku.

Niezaprzeczalność per-wiadomość

Parkiety tradingowe, giełdy bukmacherskie, kanały sterowania IoT i strumienie danych klinicznych potrzebują niezniszczalnego śladu audytowego per wiadomość. Detached podpis CAdES na każdej ramce pozwala odbiorcy udowodnić, godziny lub lata później, dokładnie kto emitował co i kiedy.

Wzajemna tożsamość

WebSocket mTLS udowadnia tożsamość klienta w czasie połączenia. Podpisywanie per-wiadomość udowadnia tożsamość aplikacji per ramkę — przydatne, gdy brama WebSocket jest współdzielonym proxy brzegowym, a prawdziwy producent jest poniżej.

Bezpieczne wobec replay

Dołącz identyfikator sesji WebSocket i monotonicznie rosnący nonce wewnątrz podpisanego ładunku. Napastnik replay nie może ponownie podpisać wiadomości świeżym nonce bez dostępu do klucza prywatnego.

Kształt koperty

Owiń każdą wiadomość WebSocket w małą kopertę JSON, która niesie ładunek, nonce, znacznik czasu i zakodowany base64 detached podpis.

Kontrakt koperty

Koperta jest podpisywana nad deterministyczną sekwencją bajtów: session_id || nonce || timestamp || payload_sha256. Używamy SHA-256, ponieważ ładunek może być binarny (Protobuf, MessagePack) i chcemy stałej długości wejście do signera.

CAdES to właściwy wybór tutaj — produkuje kompaktowe podpisy CMS/PKCS#7, które zakodowane base64 są wystarczająco małe, by zmieścić się komfortowo obok ładunku. PAdES jest specyficzny dla PDF, a XAdES dla XML; żaden nie pasuje do binarnego-lub-JSON kanału czasu rzeczywistego.

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

Podłącz jednostki i załaduj klucz podpisujący

sgcWebSockets dostarcza transport, sgcSign dostarcza silnik podpisu. Nie dzielą stanu — podłączasz je w kodzie aplikacji.

Klauzula uses

Dla klienta i serwera potrzebujesz komponentów WebSocket, profilu CAdES, dostawcy kluczy i weryfikatora. Użyj PFX dla strony klienta i długotrwałego certyfikatu usługi (zwykle z Windows store) dla strony serwera.

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

Strona klienta — podpisz przed wysłaniem

Podepnij się do wychodzącego przepływu klienta, oblicz digest ładunku plus metadanych sesji, dołącz podpis CAdES i emituj kopertę przez WriteData.

Owiń ładunek

Trzymaj signer i dostawcę kluczy jako pola na swoim formularzu lub data module. TsgcSignCAdES.SignDetached akceptuje bufor bajtów i zwraca detached strukturę CMS. Budujemy kanoniczną sekwencję bajtów w BuildToBeSigned — identyfikator sesji, nonce, znacznik czasu ISO 8601 i SHA-256 bajtów ładunku.

Wywołanie WriteData na TsgcWebSocketClient wysyła kopertę jako zwykłą ramkę tekstową. Weryfikator po stronie serwera odwróci ten proces przed przekazaniem ładunku do handlera aplikacji.

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;

Strona serwera — zweryfikuj przed dispatch

Podepnij się do zdarzenia OnMessage serwera, ponownie oblicz bajty do podpisania z pól koperty, zweryfikuj detached podpis i dopiero potem przekaż ładunek do swojej logiki biznesowej.

Handler OnMessage

Handler parsuje kopertę, przebudowuje kanoniczną sekwencję bajtów i wywołuje TsgcSignatureVerifier.VerifyDetached z bajtami podpisu i bajtami do podpisania. Weryfikator zwraca raport z podmiotem podpisującego, statusem łańcucha i wszelkimi metadanymi znacznika czasu.

Buforuj zaufane certyfikaty podpisujących po odcisku palca wewnątrz serwera — pełna weryfikacja łańcucha na każdej ramce staje się kosztowna na połączeniach o wysokiej przepustowości. Po pierwszej weryfikacji potrzebujesz tylko potwierdzić, że podpis jest ważny i odcisk palca jest taki, któremu już ufasz.

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;

Ochrona przed replay

Ważny podpis nie wystarcza — napastnik, który przechwyci podpisaną kopertę, może ją odtworzyć, dopóki certyfikat jest nadal ważny. Powiąż każdą wiadomość z sesją i wymuszaj monotoniczne nonce.

Okno nonce per-połączenie

Trzymaj słownik po stronie serwera kluczowany przez (SessionId, SignerThumbprint) z najwyższym nonce widzianym dotychczas. Odrzuć każdą kopertę, której nonce nie jest ściśle większy. Odrzuć każdą kopertę, której znacznik czasu jest oddalony o więcej niż kilka sekund od zegara serwera — ogranicz okno replay, nawet jeśli ten sam nonce zostanie jakoś ponownie użyty.

Dla stratnych sieci, gdzie kolejność nie jest gwarantowana, przełącz ścisłe sprawdzenie większe-niż na przesuwające się okno: akceptuj każdy nonce w ostatnich N wpisach, który nie był widziany wcześniej, odrzucając cokolwiek starszego. Fundamentalnym wymogiem jest, by żadne dwie ramki nie niosły tej samej pary (SessionId, Nonce).

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;

Co zwykle idzie nie tak

Trzy tryby awarii, które widzimy najczęściej, gdy deweloperzy po raz pierwszy podpinają sgcSign do sgcWebSockets.

Niespójna kanonikalizacja

Jeśli klient buduje bajty do podpisania z stringa JSON z kluczami w kolejności wstawiania, a serwer odbudowuje je z sparsowanego i ponownie zserializowanego obiektu, digesty się nie zgodzą. Zawsze podpisuj deterministyczną sekwencję bajtów, którą sam konstruujesz — nie podpisuj ponownie zserializowanego JSON.

Latencja na strumieniach o wysokiej przepustowości

Podpisywanie każdej wiadomości kluczem RSA-2048 dodaje kilka milisekund na ramkę. Na strumieniach 10k komunikatów/s staje się to wąskim gardłem. Przełącz na klucze ECDSA-P256 (o rząd wielkości szybsze) lub podpisuj wsadowo, grupując N wiadomości w jedną kopertę.

Zapominanie o SubjectKeyIdentifier

CAdES domyślnie zawiera certyfikat podpisującego, co rozdyma każdą ramkę o 1-2 KB. Użyj vProfile.IncludeCertificate := False po pierwszej ramce w sesji i pozwól weryfikatorowi dopasować po SubjectKeyIdentifier do jego bufora zaufanych signerów.

Dokąd dalej

Szyfrowanie end-to-end uzupełnia podpisywanie. sgcSign Server centralizuje powierzanie kluczy dla flot klientów.

10 dostawców kluczy

Użyj Azure Trusted Signing, AWS KMS, Google KMS lub HashiCorp Vault, aby trzymać klucze prywatne całkowicie poza procesem aplikacji.

Czytaj więcej →

sgcSign Server

Usługa podpisywania REST, więc klienci nigdy nie widzą klucza prywatnego. Przydatne, gdy producent WebSocket jest sandboxowanym pracownikiem.

Czytaj więcej →

sgcWebSockets

Pełna biblioteka transportowa WebSocket / HTTP2 / HTTP3 / MQTT / AMQP używana w tym samouczku.

Czytaj więcej →

Gotowy podpisać każdą ramkę WebSocket?

Obie biblioteki są dostarczane w tej samej wersji próbnej. Pobierz raz, zbuduj end-to-end podpisany kanał w popołudnie.