HotPDF podpisuje dokumente PDF z zasebnim ključem, ki ga hrani oddaljena storitev Cloud Signature Consortium (CSC), prek THPDFCSCSignatureProvider, ponudnika podpisov, ki poganja API CSC — credential info, avtorizacijo, signatures/signHash in anketiranje — medtem ko vaša aplikacija Delphi dovaja prevoz HTTP in dostopni žeton OAuth. Ključ nikoli ne zapusti HSM storitve
To je vse bolj edini način, da sploh dobite kvalificiran podpisni ključ. Ponudniki storitev zaupanja izročijo končno točko CSC in odjemalca OAuth, ne datoteke PFX ali žetona USB, zato ni ničesar, kar bi se naložilo v krajevno shrambo certifikatov, kot to počne podpisovanje iz shrambe certifikatov Windows skozi CNG in CAPI. Naivna integracija odpove na predvidljive načine: klic signHash zamudi in ponovitev podpiše isti pogodbo dvakrat, ali pa serija štiridesetih računov sproži štirideset enkratnih gesel, ker je bil vsak hash avtoriziran posebej. Večina tega, kar ponudnik počne, je obramba pred tema dvema odpovedema
Zakaj HotPDF pusti HTTP vaši aplikaciji?
Ker je prevoz točno tisto mesto, kjer se vsaka namestitev razlikuje. Posredniki, pripenjanje TLS, certifikati odjemalcev, podjetjaški sef OAuth in politika beleženja vsi živijo v plasti HTTP, zato THPDFCSCSignatureProvider orkestrira stanje protokola in za vsako zahtevo pokliče funkcijo THPDFCSCTransport. Ponudnik vam izroči THPDFCSCTransportRequest z Method (vselej POST), polnim URL, zgrajenim iz ServiceBaseURL plus poti končne točke, pripravljeno glavo Authorization bearer, ContentType, telesom JSON Body, IdempotencyKey, številko Attempt in MaxResponseBytes. Vi napolnite THPDFCSCTransportResponse s StatusCode, Body in RetryAfterMS ter vrnete enega od ctsSuccess, ctsTemporaryFailure, ctsPermanentFailure ali ctsCancelled
uses
System.Net.HttpClient, System.Net.URLClient, HPDFSignatureProvider,
HPDFCSCSignatureProvider;
function MakeCSCTransport(Client: THTTPClient): THPDFCSCTransport;
begin
Result :=
function(const Request: THPDFCSCTransportRequest;
out Response: THPDFCSCTransportResponse): THPDFCSCTransportStatus
var
Body: TStringStream;
Reply: TMemoryStream;
Headers: TNetHeaders;
HttpResp: IHTTPResponse;
begin
Response := Default(THPDFCSCTransportResponse);
Body := TStringStream.Create(string(Request.Body), TEncoding.UTF8);
Reply := TMemoryStream.Create;
try
Headers := [TNameValuePair.Create('Authorization', string(Request.Authorization)),
TNameValuePair.Create('Content-Type', string(Request.ContentType))];
if Request.IdempotencyKey <> '' then // ime glave, kot ga dokumentira vaša storitev
Headers := Headers + [TNameValuePair.Create('Idempotency-Key',
string(Request.IdempotencyKey))];
try
HttpResp := Client.Post(Request.URL, Body, Reply, Headers);
except
on ENetHTTPClientException do
Exit(ctsTemporaryFailure); // težava vtičnice ali DNS: ponovljivo
end;
Response.StatusCode := HttpResp.StatusCode; // poročajte 503 kot je, ne klasificirajte
SetLength(Response.Body, Reply.Size);
if Reply.Size > 0 then
Move(Reply.Memory^, Response.Body[1], Reply.Size);
Response.RetryAfterMS := StrToIntDef(HttpResp.HeaderValue['Retry-After'], 0) * 1000;
Result := ctsSuccess;
finally
Reply.Free;
Body.Free;
end;
end;
end;
Edino pravilo, vredno naučenja: vrnite ctsSuccess, kadarkoli je strežnik dejansko odgovoril, tudi z 503. Ponudnik sam klasificira kode stanj, prevoz, ki spremeni 429 v ctsPermanentFailure, pa tiho onemogoči logiko ponovitev, opisano spodaj. Konstruktor je strog v drugo smer — sproži EHPDFCSCSignatureProviderError, ko prevoz manjka, CredentialID je prazen, ni podan ne AccessToken ne povratni klic žetona, proračun je izven obsega ali ServiceBaseURL ni HTTPS. Goli http:// je sprejet le z AllowInsecureHTTP, ki pripada preskusni postaji in nikamor drugam
Kaj je SAD in zakaj ga HotPDF vrže proč po eni uporabi?
THPDFCSCSignatureProvider obravnava Signature Activation Data (SAD) kot enkratno: počiščena je iz stanja ponudnika v trenutku, ko je signatures/signHash sprejet, tudi kadar podpis sama pride pozneje skozi asinhrono anketiranje. SAD je dokaz storitve, da je podpisovalec odobril točno te hashe, SAD, ki muli v spominu, pa je avtorizacija, ki čaka, da bo zapravljena na napačnem dokumentu
S privzetimi vrednostmi iz THPDFCSCOptions.Default — RequireSAD in AutoAuthorize oba True — ponudnik enkrat naloži credentials/info, od vašega THPDFCSCAuthenticationCallback povpraša po vrednostih authData (OTP, PIN, kar koli zahteva blok auth poverilnice) in pošlje credentials/authorize. 200 nosi SAD neposredno; 202 nosi ročaj, ki se anketira skozi credentials/authorizeCheck do MaxPollAttempts (60) krat pri PollIntervalMS (250 ms). Povratni klic lahko vrne največ 32 vrednosti, vsaka z nepraznim ID do 256 bajtov in vrednostjo do 4.096 bajtov. Če se mreža prekine, preden je signHash sprejet, se samodejno pridobljeni SAD obdrži, tako da se lahko ista serija ponovi, brez da bi ponovno vprašali podpisovalca
var
Options: THPDFCSCOptions;
Provider: THPDFCSCSignatureProvider;
begin
Options := THPDFCSCOptions.Default; // RequireSAD, AutoAuthorize, asinhroni način, 2 ponovitvi
Options.ServiceBaseURL := 'https://csc.example.com/csc/v2';
Options.CredentialID := 'contracts-signing-01';
Options.ClientData := 'invoice-run-2026-09';
Provider := THPDFCSCSignatureProvider.Create(Options, MakeCSCTransport(HttpClient),
function(ForceRefresh: Boolean; const OperationIdentifier: AnsiString;
out AccessToken: AnsiString; out ExpiresAtUTC: TDateTime): THPDFSignatureProviderStatus
begin
// vaš odjemalec OAuth; ForceRefresh je resničen, potem ko je storitev odgovorila 401
if not TokenVault.Acquire(ForceRefresh, AccessToken, ExpiresAtUTC) then
Exit(spsProviderError);
Result := spsValid;
end,
function(const CredentialID, CredentialInfoJSON, OperationIdentifier: AnsiString;
out Values: THPDFCSCAuthenticationValues): THPDFSignatureProviderStatus
var
Otp: string;
begin
if not AskSignerForOtp(Otp) then // vaš vmesnik
Exit(spsCancelled);
SetLength(Values, 1);
Values[0].ID := 'otp';
Values[0].Value := AnsiString(Otp);
Result := spsValid;
end);
SAD, ki ga sami podate prek Options.SAD, se obnaša drugače in namenoma. HotPDF ne more vedeti, za katere hashe je bil izdan, zato ponudnik uporabi vnaprej nastavljeni SAD le za zahtevo z enim hashem. Za serijo z izklopljenim AutoAuthorize ponudnik odpove z »CSC SAD is not pinned to the requested hash batch«, namesto da bi ugibal
Kako SignHashBatch podpiše veliko dokumentov z eno avtorizacijo?
SignHashBatch pošlje en credentials/authorize in en signatures/signHash za do MaxBatchSignatures (64) povzetkov in zgradi obe telesi iz istega polja, tako da so numSignatures, vrstni red hashes in hashAlgorithmOID identični v obeh klicih. To ujemanje zahteva model multisign CSC. Zankajte metodo Sign z enim hashom štirideset krat in dobili boste štirideset avtorizacij; pošljite authorize in signHash, ki se ne strinjata, in storitev lahko porabi SAD za napačno serijo
Pred kakršnim koli omrežnim prometom ponudnik preveri serijo. Vsaka zahteva mora biti povzetek (sikDigest) od 1 do 1.024 bajtov z OID povzetka, vse zahteve pa si morajo deliti en OID podpisnega algoritma, en OID povzetka in, pri RSASSA-PSS, eno dolžino soli. Večhashna serija prav tako naloži credentials/info in vrne spsUnsupported, ko je vrednost multisign poverilnice manjša od serije. SAD je nato pripet na prstni odtis serije — SHA-256 nad oznako različice, številom in, na zahtevo, OID algoritma, OID povzetka, algoritmom, dolžino soli in bajti povzetka, vsak s predpono dolžine. Zamenjajte dva hasha in to je drugačna serija, ki potrebuje svežo avtorizacijo
var
Requests: THPDFCSCSignatureRequests;
Signatures: THPDFCSCSignatures;
Status: THPDFSignatureProviderStatus;
I: Integer;
begin
SetLength(Requests, Length(Digests)); // Digests: vrednosti SHA-256, ki ste jih izračunali
for I := 0 to High(Digests) do
begin
Requests[I] := Default(THPDFSignatureProviderRequest);
Requests[I].Algorithm := hsaRSAPKCS1v15; // signAlgo izpeljan, kadar je AlgorithmOID prazen
Requests[I].DigestAlgorithmOID := '2.16.840.1.101.3.4.2.1';
Requests[I].InputKind := sikDigest;
Requests[I].Input := Digests[I];
end;
Status := Provider.SignHashBatch(Requests, 'invoices-2026-09-25-a', Signatures);
if Status <> spsValid then
raise Exception.CreateFmt('CSC batch failed (HTTP %d): %s',
[Provider.LastHTTPStatus, Provider.LastError]);
// Signatures[I] pripada Digests[I]; število je bilo preverjeno proti zahtevi
end;
Za RSASSA-PSS ponudnik pošlje še signAlgoParams, strukturo RSASSA-PSS-params v DER, kodirano base64, z algoritmom hasha, MGF1 in dolžino soli. Njena gradnja pomeni kodiranje OID-jev, različica 2.748.5 pa je popravila kot takega: X.690 §8.19.4 prepogne prva dva loka v eno vrednost (40 × prvi + drugi), pod korenom 2 pa drugi lok nad 39 potisne to vrednost čez 127, kjer potrebuje večbajtno obliko osnova 128, ki je starejše gradnje niso uporabile. Noben OID SHA-2 ni prizadet — 2.16 se prepogne v 96 — deformiran OID pa zdaj sproži lastno napako ponudnika namesto EConvertError
Zakaj ponovljena zahteva ne izdela drugega podpisa?
THPDFCSCSignatureProvider naredi, da vsak ponovljiv klic nosi deterministični ključ idempotentnosti, in predpomni opravljene rezultate, tako da ponovitev po izgubljenem odgovoru vrne izvirne podpise, namesto da bi HSM vprašal za nove. Ključ je csc-, ki mu sledi šestnajstiški SHA-256 identifikatorja operacije in faze, faza pa vgradi prstni odtis serije tako za avtorizacijo kot za signHash. Hashiranje namesto krajšanja je pomembno: dva dolga ID operacij, ki imata skupno predpono, bi trčila pod krajšanjem, fiksno dolg ključ po vsebini pa ostane enkraten in stabilen čez poskuse
Politika ponovitev v skupni poti zahtev je namenoma ozka:
- HTTP 401 vsili točno eno osvežitev žetona skozi povratni klic dostopnega žetona, nato se zahteva ponovi enkrat, kadar je povratni klic dostopnega žetona dodeljen; drugi 401 je končen
- Drugi odgovori 4xx in
ctsPermanentFailurezaključita klic zspsProviderError,error_descriptionstoritve pa pristane vLastError - 408, 429, 5xx in
ctsTemporaryFailureso ponovljeni doRetryLimit(privzeto 2), s čakanjem naRetry-AfteraliRetryBaseDelayMS× 2poskus (osnova 100 ms), omejeno naMaxRetryAfterMS(5.000 ms) - Čakanja tečejo v rezinah po 25 ms, ki preverjajo
Cancel, tako da uporabnik, ki prekine, ne sedi skozi umik petih sekund signHashje ponovljen le, medtem ko jeEnableIdempotencyvklopljen; izklopite ga in zamuda po oddaji je končna, ker nihče ne more vedeti, ali je bil ključ že uporabljen
Asinhrono podpisovanje (operationMode »A«, privzeto) dodaja še eno varovalo: responseID je shranjen, preden se anketrira signatures/signPolling, tako da ponovljen klic z istim identifikatorjem operacije nadaljuje anketiranje, namesto da bi oddal znova. Opravljene serije sedijo v predpomnilniku s ključi iz identifikatorja operacije, poverilnice in prstnega odtisa, omejenem z MaxOperationCacheEntries (128) in vračanem kot globoke kopije. Ta predpomnilnik živi v primerku ponudnika in ne preživi ponovnega zagona. Ključ idempotentnosti pa preživi, ker je izpeljan in ne naključen, zato ponovno zagnan proces, ki ponovno uporabi svoj identifikator operacije, pošlje isti ključ — ali storitev na njem odpravlja dvojnosti, je obljuba storitve in ne HotPDF
Kako vstavite podpis CSC v PDF?
Podajte ponudnika HPDFCMSSignPDFStreamWithProvider skupaj s končnim certifikatom iz GetCertificateChain; HotPDF zgradi CMS SignedData, ponudnik pa podpiše povzetek podpisanih lastnosti. Vhodni PDF potrebuje ograda /ByteRange in /Contents, ki ju zapiše THPDFPage.AddSignedSignatureField, točno kot v poteku podpisovanja PAdES v HotPDF, model ponudnika pa je isti, kot ga pokriva vstavljanje ponudnikov podpisov HotPDF za ML-DSA in EdDSA
var
Chain: THPDFCSCCertificateChain;
SignOpts: THPDFCMSSignOptions;
Src, Dst: TFileStream;
begin
if Provider.RefreshCredentialInfo <> spsValid then
raise Exception.Create(Provider.LastError);
Chain := Provider.GetCertificateChain; // CSC našteje končni certifikat najprej
if Length(Chain) = 0 then
raise Exception.Create('Credential returned no certificate');
SignOpts := HPDFCMSDefaultOptions(palBaseline_B_B);
SignOpts.DigestAlgorithm := cmsdaSHA256;
SignOpts.SignatureScheme := cmsRSAPKCS1v15;
Src := TFileStream.Create('contract-unsigned.pdf', fmOpenRead or fmShareDenyWrite);
Dst := TFileStream.Create('contract-signed.pdf', fmCreate);
try
if not HPDFCMSSignPDFStreamWithProvider(Src, Dst, Chain[0], Provider, '', SignOpts) then
raise Exception.Create('PDF signing failed');
finally
Dst.Free;
Src.Free;
end;
end;
Merjenje ograda /Contents gre skozi EstimateSignatureSize, ki vrne EstimatedSignatureBytes, ko ga nastavite, sicer pa velikost modula RSA iz dolžine ključa poverilnice. Za ECDSA nastavite EstimatedSignatureBytes sami, sicer ocena poroča spsUnsupported. Različica podpisovanja s samodejno velikostjo ponovno podpiše, ko se pokaže, da je ogradek premajhen, in to stori le za ponudnike, ki oglašujejo spcSafeSignRetry — kar THPDFCSCSignatureProvider počne le, medtem ko je EnableIdempotency vklopljen. Za potke PAdES-B-T TimestampDigest zahteva žeton časovnega žiga od iste storitve skozi signatures/timestamp, omejen na MaxTimestampBytes (1 MB)
Kaj ponudnik CSC ne počne?
Ne podpisuje sporočil, le povzetke. Ed25519 in Ed448 v čistem načinu izročita ponudniku celotno sporočilo podpisanih lastnosti (sikMessage), preverjevalnik serij pa to zavrne kot deformirano, ker je signHash po definiciji na hashih. Enota ponudnika se prevede pod Free Pascal z navadnimi tipi funkcij namesto anonimnih metod, graditelji CMS, gnani s ponudnikom, pa danes pod FPC sprožijo izjemo, zato je vstavljanje podpisa CSC v PDF pot Delphi
Ne odloča tudi o politiki. CredentialInfo poroča stanje ključa, stanje certifikata, način avtorizacije, raven SCAL in omejitev multisign, ponudnik pa ne bo odklonil onemogočenega ključa ali poverilnice SCAL1 na svojo roko — preverite to, preden podpisovalcu pokažete poziv za OTP. In en primerek ponudnika podpiše eno serijo naenkrat: SignHashBatch je serializiran interno, tako da dve niti ne moreta tekmovati za en SAD, kar pomeni, da pretočnost pride od serij, ne od delitve ponudnika čez delovne niti. Ali je nastali podpis kvalificiran, je odvisno od storitve zaupanja in njene poverilnice, ne od knjižnice, ki je hash nesla tja
Ponudnik CSC, graditelja CMS in PAdES ter krajevni ponudnik in ponudnik PKCS#11 vsi prihajajo v komponento HotPDF Delphi PDF