HotPDF, PDF belgelerini özel anahtarı uzak bir Cloud Signature Consortium (CSC) servisinde tutulan anahtarla imzalar; THPDFCSCSignatureProvider, CSC API'sini süren — credential info, yetkilendirme, signatures/signHash ve polling — bir imza sağlayıcısıdır ve HTTP transport ile OAuth access tokenini sizin Delphi uygulamanız sağlar. Anahtar, servisin HSMinden asla çıkmaz
Bu, nitelikli bir imzalama anahtarına hiç ulaşmanın giderek tek yolu. Güven hizmeti sağlayıcıları bir CSC endpointi ve bir OAuth client verir; PFX dosyası ya da USB token değil, dolayısıyla CNG ve CAPI ile Windows cert store imzalamasının yaptığı gibi yerel bir sertifika deposuna yüklenecek bir şey yoktur. Naif entegrasyon öngörülebilir biçimlerde başarısız olur: bir signHash çağrısı zaman aşımına uğrar ve yeniden deneme aynı sözleşmeyi iki kez imzalar, ya da kırk faturalık bir parti her hash ayrı yetkilendirildiği için kırk tek kullanımlık parola tetikler. Sağlayıcının yaptığı şeyin çoğu bu iki başarısızlığa karşı savunmadır
HotPDF HTTP'yi neden uygulamanıza bırakır?
Çünkü transport, tam olarak her dağıtımın farklılaştığı yerdir. Proxy'ler, TLS pinning, client sertifikaları, kurumsal OAuth kasaları ve loglama politikası hep HTTP katmanında yaşar; THPDFCSCSignatureProvider bu yüzden protokol durumunu orkestra eder ve her istek için bir THPDFCSCTransport fonksiyonu çağırır. Sağlayıcı size Methodu (daima POST), ServiceBaseURL artı endpoint yoluyla kurulan tam URL, hazır bir Authorization bearer başlığı, ContentType, JSON Body, bir IdempotencyKey, Attempt numarası ve MaxResponseBytes taşıyan bir THPDFCSCTransportRequest verir. Siz bir THPDFCSCTransportResponseu StatusCode, Body ve RetryAfterMS ile doldurur ve ctsSuccess, ctsTemporaryFailure, ctsPermanentFailure ya da ctsCancelledden birini döndürürsünüz
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 // başlık adı, servisinizin belgelediği hâliyle
Headers := Headers + [TNameValuePair.Create('Idempotency-Key',
string(Request.IdempotencyKey))];
try
HttpResp := Client.Post(Request.URL, Body, Reply, Headers);
except
on ENetHTTPClientException do
Exit(ctsTemporaryFailure); // socket ya da DNS sorunu: yeniden denenebilir
end;
Response.StatusCode := HttpResp.StatusCode; // 503'ü olduğu gibi bildir, sınıflandırma
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;
Ezberlenmeye değer tek kural: bir sunucu gerçekten cevap verdiyse, 503 ile bile ctsSuccess döndürün. Sağlayıcı durum kodlarını kendisi sınıflandırır ve 429'u ctsPermanentFailuree çeviren bir transport, aşağıda anlatılan yeniden deneme mantığını sessizce etkisiz bırakır. Constructor öbür yönde katıdır — transport eksikse, CredentialID boşsa, ne bir AccessToken ne bir token callback verilmişse, bütçe aralık dışındaysa ya da ServiceBaseURL HTTPS değilse EHPDFCSCSignatureProviderError fırlatır. Düz http:// yalnızca AllowInsecureHTTP ile kabul edilir; o da bir test düzeneğine aittir, başka hiçbir yere
SAD nedir ve HotPDF onu tek kullanımdan sonra neden çöpe atar?
THPDFCSCSignatureProvider, Signature Activation Data'yı (SAD) tek kullanımlık sayar: signatures/signHash kabul edildiği anda sağlayıcı durumundan temizlenir, imzanın kendisi sonradan asenkron polling ile gelse bile. SAD, imzalayanın tam olarak bu hashleri onayladığının servise kanıtıdır ve bellekte asılı kalan bir SAD, yanlış belgeye harcanmayı bekleyen bir yetkilendirmedir
THPDFCSCOptions.Defaultin varsayılanlarıyla — RequireSAD ile AutoAuthorize ikisi de True — sağlayıcı credentials/infoyu bir kez yükler, authData değerlerini (bir OTP, bir PIN, credentialın auth bloğunun istediği her neyse) THPDFCSCAuthenticationCallbackinizden ister ve credentials/authorize gönderir. 200 SAD'yi doğrudan taşır; 202, credentials/authorizeCheck üzerinden en çok MaxPollAttempts (60) kez, PollIntervalMS (250 ms) arayla yoklanan bir tutamaç taşır. Callback en çok 32 değer döndürebilir; her biri en çok 256 baytlık boş olmayan bir ID ve en çok 4.096 baytlık bir değerle. Ağ, signHash kabul edilmeden düşerse otomatik elde edilmiş bir SAD saklanır, böylece aynı parti imzalayana yeniden sormadan tekrar denenebilir
var
Options: THPDFCSCOptions;
Provider: THPDFCSCSignatureProvider;
begin
Options := THPDFCSCOptions.Default; // RequireSAD, AutoAuthorize, async mod, 2 yeniden deneme
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
// sizin OAuth clientınız; ForceRefresh, servis 401 cevapladıktan sonra True
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 // sizin arayüzünüz
Exit(spsCancelled);
SetLength(Values, 1);
Values[0].ID := 'otp';
Values[0].Value := AnsiString(Otp);
Result := spsValid;
end);
Options.SAD üzerinden kendiniz geçtiğiniz bir SAD farklı davranır ve bilinçli olarak öyle. HotPDF hangi hashler için verildiğini bilemez; sağlayıcı bu yüzden hazır bir SAD'yi yalnızca tek hashli istekte kullanır. AutoAuthorize kapatılmış bir parti için sağlayıcı tahmin yürütmek yerine "CSC SAD is not pinned to the requested hash batch" ile başarısız olur
SignHashBatch tek yetkilendirmeyle çok belgeyi nasıl imzalar?
SignHashBatch, en çok MaxBatchSignatures (64) özet için tek bir credentials/authorize ve tek bir signatures/signHash gönderir ve her iki bodyi de aynı diziden kurar; böylece numSignatures, hashesin sırası ve hashAlgorithmOID iki çağrıda özdeş olur. O eşleşme, CSC multisign modelinin istediği şeydir. Tek hashli Sign metodunu kırk kez döngüye alırsanız kırk yetkilendirme alırsınız; anlaşmayan bir authorize ile signHash gönderirseniz servis SAD'yi yanlış partiye harcayabilir
Herhangi bir ağ trafiğinden önce sağlayıcı partiyi doğrular. Her istek, 1 ile 1.024 bayt arası, digest OIDli bir özet (sikDigest) olmalı ve tüm istekler bir imza algoritması OID, bir digest OID ve RSASSA-PSS için bir salt uzunluğu paylaşmalıdır. Çok hashli bir parti ayrıca credentials/infoyu yükler ve credentialın multisign değeri partiden küçükse spsUnsupported döndürür. SAD ardından bir parti parmak izine sabitlenir — bir sürüm etiketi, sayı ve istek başına algoritma OID, digest OID, algoritma, salt uzunluğu ve özet baytları üzerinde, her biri uzunluk önekli, bir SHA-256. İki hashi değiştirirseniz artık taze bir yetkilendirme isteyen başka bir partidir
var
Requests: THPDFCSCSignatureRequests;
Signatures: THPDFCSCSignatures;
Status: THPDFSignatureProviderStatus;
I: Integer;
begin
SetLength(Requests, Length(Digests)); // Digests: sizin hesapladığınız SHA-256 değerleri
for I := 0 to High(Digests) do
begin
Requests[I] := Default(THPDFSignatureProviderRequest);
Requests[I].Algorithm := hsaRSAPKCS1v15; // AlgorithmOID boşken signAlgo türetilir
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], Digests[I]ye aittir; sayı istekle karşılaştırılarak sınandı
end;
RSASSA-PSS için sağlayıcı ayrıca signAlgoParams gönderir: hash algoritması, MGF1 ve salt uzunluğu taşıyan base64 DER bir RSASSA-PSS-params yapısı. Onu kurmak OID kodlamak demektir ve 2.748.5 sürümü bunun bir köşesini düzeltti: X.690 §8.19.4 ilk iki yayı tek değere katlar (40 × birinci + ikinci) ve 2 kökü altında 39un üzerinde bir ikinci yay, o değeri 127yi aşırır; erken derlemelerin uygulamadığı taban-128 çok baytlı biçim burada gerekir. Hiçbir SHA-2 OID etkilenmez — 2.16, 96ya katlanır — ama bozuk bir OID artık EConvertError yerine sağlayıcının kendi hatasını fırlatır
Yeniden denenen bir istek neden ikinci bir imza üretmez?
THPDFCSCSignatureProvider, yeniden denenebilir her çağrıya deterministik bir idempotency anahtarı taşıtır ve tamamlanmış sonuçları önbellekler; kaybolmuş bir cevaptan sonraki yeniden deneme, HSMden yeni imzalar istemek yerine özgün imzaları döndürür. Anahtar, csc- ve ardından işlem tanımlayıcısı ile fazın hex SHA-256sıdır ve faz, hem yetkilendirme hem signHash için parti parmak izini gömer. Kırpma yerine hash almak önemlidir: önek paylaşan iki uzun işlem ID kırpma altında çakışırdı; sabit uzunluklu, içeriğe adresli bir anahtar denemeler boyunca özgün ve kararlı kalır
Paylaşılan istek yolundaki yeniden deneme politikası bilinçli olarak dardır:
- HTTP 401, access-token callbacki üzerinden tam olarak bir token yenilemesi dayatır ve access-token callback atanmışsa istek bir kez yinelenir; ikinci 401 kesindir
- Diğer 4xx cevapları ve
ctsPermanentFailureçağrıyıspsProviderErrorile bitirir ve servisinerror_descriptionıLastErrora düşer - 408, 429, 5xx ve
ctsTemporaryFailure,RetryLimite (varsayılan 2) kadar yeniden denenir;Retry-Afterya daRetryBaseDelayMS× 2attempt beklenir (100 ms taban),MaxRetryAfterMSile (5.000 ms) sınırlandırılır - Beklemeler
Canceli kontrol eden 25 ms dilimler hâlinde yürür; vazgeçen bir kullanıcı beş saniyelik geri çekilmeyi beklemek zorunda kalmaz signHash,EnableIdempotencyaçıkken yeniden denenir; kapatırsanız gönderimden sonraki zaman aşımı kesindir, çünkü anahtarın zaten kullanılıp kullanılmadığını kim söyleyemez
Asenkron imzalama (operationMode "A", varsayılan) bir koruma daha ekler: responseID, signatures/signPolling yoklanmadan önce saklanır, böylece aynı işlem tanımlayıcıyla yinelenen bir çağrı yeniden göndermek yerine yoklamayı sürdürür. Tamamlanan partiler, işlem tanımlayıcısı, credential ve parmak iziyle anahtarlanmış bir önbellekte durur; MaxOperationCacheEntries ile (128) sınırlıdır ve derin kopyalar olarak döndürülür. O önbellek sağlayıcı örneğinde yaşar ve bir yeniden başlatmayı atlatmaz. Idempotency anahtarı atlatır, çünkü rastgele değil türetilmiştir; işlem tanımlayıcısını yeniden kullanan yeniden başlatılmış bir süreç aynı anahtarı gönderir — servisin bunun üzerinde tekilleştirme yapıp yapmayacağı servisin sözüdür, HotPDF'in değil
Bir CSC imzasını PDF'e nasıl koyarsınız?
Sağlayıcıyı GetCertificateChainden gelen uç varlık sertifikasıyla birlikte HPDFCMSSignPDFStreamWithProvidere geçirin; HotPDF CMS SignedDatayı kurar ve sağlayıcı imzalı özniteliklerin özetini imzalar. Girdi PDF'i, THPDFPage.AddSignedSignatureFieldin yazdığı /ByteRange ve /Contents yer tutucusuna ihtiyaç duyar; tam olarak HotPDF'de PAdES imzalama iş akışındaki gibi ve sağlayıcı model, ML-DSA ve EdDSA için HotPDF takılabilir imza sağlayıcılarında kapsanan modeldir
var
Chain: THPDFCSCCertificateChain;
SignOpts: THPDFCMSSignOptions;
Src, Dst: TFileStream;
begin
if Provider.RefreshCredentialInfo <> spsValid then
raise Exception.Create(Provider.LastError);
Chain := Provider.GetCertificateChain; // CSC uç varlık sertifikasını önce listeler
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;
/Contents yer tutucusunu boyutlandırma EstimateSignatureSize üzerinden gider; set ettiğinizde EstimatedSignatureBytesi döndürür, aksi hâlde credentialın anahtar uzunluğundan RSA modulus boyutunu. ECDSA için EstimatedSignatureBytesi kendiniz set edin ya da kestirim spsUnsupported bildirir. Otomatik boyutlu imzalama varyantı, yer tutucu küçük çıkarsa yeniden imzalar ve bunu yalnızca spcSafeSignRetry ilan eden sağlayıcılar için yapar — THPDFCSCSignatureProvider bunu yalnızca EnableIdempotency açıkken yapar. PAdES-B-T iş akışları için TimestampDigest, aynı servisten signatures/timestamp üzerinden bir zaman damgası tokeni ister; MaxTimestampBytes ile (1 MB) sınırlıdır
CSC sağlayıcısı neyi yapmaz?
Mesaj imzalamaz, yalnızca özet imzalar. Saf moddaki Ed25519 ile Ed448, sağlayıcıya imzalı öznitelikler mesajının tamamını (sikMessage) verir ve parti doğrulayıcı bunu bozuk olarak reddeder; signHash tanımı gereği hash temellidir. Sağlayıcı birimi, anonim metotlar yerine düz fonksiyon türleriyle Free Pascal altında derlenir ama sağlayıcı güdümlü CMS kurucuları bugün FPC altında istisna fırlatır; dolayısıyla bir CSC imzasını PDF'e gömmek bir Delphi yoludur
Politika da belirlemez. CredentialInfo anahtar durumunu, sertifika durumunu, yetkilendirme modunu, SCAL seviyesini ve multisign sınırını bildirir ama sağlayıcı, devre dışı bir anahtarı ya da SCAL1 bir credentialı kendi başına reddetmez — imzalayana OTP istemini göstermeden önce bunları kontrol edin. Ve bir sağlayıcı örneği bir anda tek parti imzalar: SignHashBatch içeride serileştirilir, böylece iki iş parçacığı tek bir SAD için yarışamaz; bu da verimin partileşmeden geldiği anlamına gelir, bir sağlayıcının işçi iş parçacıkları arasında paylaşılmasından değil. Ortaya çıkan imzanın nitelikli olup olmayacağı güven hizmetine ve credentialına bağlıdır, hashi oraya taşıyan kütüphaneye değil
CSC sağlayıcısı, CMS ile PAdES kurucuları ve yerel ile PKCS#11 sağlayıcılarının hepsi HotPDF Delphi PDF component ile gelir