PDFium VCL podpisuje dokumente PAdES z zasebnim ključem, shranjenim na verigi ključev macOS, skozi zaledje, ki vsak simbol Security in CoreFoundation razreši med zagonom z dlopen in dlsym. Nič ni vezano ob povezovanju, kar pomeni, da se napačno natipkano ime simbola pokaže kot KeychainAvailable, ki vrne False, in KeychainMissingSymbols, ki poimenuje krivca — in ne kot napaka povezovalnika ali sesutje
To izbiro je vsilila neprijetna omejitev, način ravnanja z njo pa se posplošuje. Enota je bila napisana na stroju brez macOS SDK, tako da je vsako ime simbola ogrodja in vsaka konstanta prišla iz dokumentacije in ničesar ni bilo mogoče preveriti proti glavi. Napačen odgovor na to situacijo je kodu zapisati skrbno in upati. Pravi pa je urediti, da se neizbežne napake javijo same v najbolje umestljivi obliki
Zakaj je dinamično vezanje prava odločitev, tudi na ciljni platformi
Ker razred odpovedi, ki ustavi program, spremeni v razred odpovedi, ki sporoči samo sebe. Statično povezan sklic na ogrodje, ki je napačen, spodleti ob povezovanju na cilju in se nikjer drugje ne poveže. Dinamično vezan, ki je napačen, proizvede nedostopno zaledje in seznam nerazrešenih imen, prvi zagon na Macu pa vprašanje zakaj to ni na voljo spremeni v eno vrstico, ki poimenuje tipkarsko napako
Obstaja še druga korist, ki se izplača dnevno in ne enkrat. Ker enota ne povezuje ogrodij, se prevede na vsaki platformi, običajna gradnja Windows pa še naprej preverja njeno skladnjo, tipe in stavek uses. Enota, ki se prevede le na platformi, ki je nihče v ekipi nima, je enota brez prevajalnika, ki bi jo gledal, in tiho propada z vsako prenovo deljenega tipa
uses
FPdfCrypto, FPdfCryptoMac;
var
Options: TPadesSignerOptions;
begin
if not KeychainAvailable then
raise Exception.Create('Keychain backend unavailable, unresolved: ' +
KeychainMissingSymbols);
ConfigureKeychainSignerProvider; // namesti kot zaledje podpisovalca PAdES
ConfigureKeychainCmsVerifier; // in kot zaledje preverjanja
Writeln('signer backend : ', PadesCryptoBackendName);
Writeln('verify backend : ', PadesCmsVerificationBackendName);
Options := TPadesSignerOptions.Default;
Options.CertificateThumbprint := 'B1 3F 9C ...'; // SHA-1, katera koli velikost črk
Options.PaddingScheme := psRsaPss;
end;
Dve vrsti izvoženih simbolov, dva načina branja
To je najbolj zmedena podrobnost v celem vezanju, obratno razumevanje pa se čisto prevede in spodleti med zagonom. CoreFoundation in Security izvažata dve kategorično različni stvari skozi isti klic dlsym in koda mora vedeti, katera je katera
Poimenovane konstante, kot so ključi razredov postavk verige ključev in boolean enojci CoreFoundation, so izvožene spremenljivke, katerih vsebina je CFStringRef ali CFBooleanRef, ki ga želite. dlsym vrne naslov te spremenljivke, zato morate enkrat dereferencirati, da pridobite vrednost. Strukture tabel povratnih klicev, kot so povratni klici ključev in vrednosti slovarja, pa so izvožene strukture in dlsym vrne naslov strukture — natanko kazalec, ki ga pričakuje funkcija za ustvarjanje slovarja. Če to dereferencirate, podate prvo strojno besedo strukture, kot da bi bila kazalec
Nobena napaka ne proizvede napake prevoda in nobena ne proizvede jasne napake med zagonom. Dobite smetni kazalec, ki spodleti nekje na spodnjem toku. Način, da razlike ni mogoče zamenjati, je prenehati se zanašati na zapomnitev: dve pomožni funkciji, ena, ki veže in dereferencira, in ena, ki veže in ne, tako da mesto klica razglasi, katero vrsto simbola sprašuje, pomožnica pa uveljavi ostalo
// Izvožena spremenljivka: dlsym da naslov spremenljivke, ki drži
// CFTypeRef, zato enkrat dereferencirajte
FSecClassKey := BindConstant(SecurityLib, 'kSecClass');
// Izvožena struktura: dlsym da naslov STRUKTURE, kar je
// tisto, kar API želi. Ne dereferencirajte
FKeyCallbacks := BindStruct(CoreFoundationLib,
'kCFTypeDictionaryKeyCallBacks');
Zakaj potrebuje podpis RSA-PSS dva ločena zasilna izhoda?
Ker se lahko algoritem manjka na dva neodvisna načina in je le eno od njiju vprašanje verzije. Konstanta algoritma za podpisovanje povzetkov PSS se je pojavila v macOS 10.13, tako da na starejšem sistemu simbola preprosto ni in vezanje dobi nil. To je preverjanje verzije. Ločeno pa lahko na sistemu, kjer konstanta obstaja, določen ključ vseeno odkloni, ogrodje pa to vprašanje odgovori skozi SecKeyIsAlgorithmSupported za tisti ključ. Ključ, podprt s strojno opremo, ali ključ z omejujočimi atributi lahko odklonita PSS, medtem ko ga programski ključ na istem stroju sprejme
Obi poti morata voditi k istemu zasilnemu izhodu: preklop na PKCS#1 v1.5. Kritični del pa je, da mora zasilni izhod spremeniti tudi identifikator algoritma, zapisan v strukturo CMS, in ne le podpisni klic. Oddajanje identifikatorja algoritma PSS, medtem ko dejansko nastaja podpis v1.5, da dokument, ki ga vsak preverjevalnik odkloni na mestu — kar je strogo slabše kot sporočiti, da PSS ni podprt. Povzdignitev navzdol je sprejemljiva, neskladje med tem, kar razglasite, in tem, kar ste storili, pa ni, in to je splošno pravilo za podpisno kodo in ne posebnost macOS. Implikacije na ravni podpisa so razložene v podpisovanju PDF-jev s PAdES B-B
Kodiranje podpisov ECDSA in obrat, vreden omembe
Pot eliptičnih krivulj na macOS ne potrebuje nobene pretvorbe in to je nasprotje tega, kar zahteva vezava PKCS#11. Algoritem ogrodja Security za podpisovanje povzetkov ECDSA vrne podpis že v obliki X9.62 DER — natanko to, kar želi CMS. Žeton PKCS#11 pa vrne surovi par P1363 fiksne širine, ki ga je treba znova kodirati, preden gre v strukturo podpisa
Tako dve zaledji, ki implementirata isti vmesnik, potrebujeta nasprotno obravnavo za isti algoritem in nobeno ni napačno. To je natanko tista vrsta razlike, ki jo mora abstrakcija absorbirati in ne izpostaviti: plast PAdES prosi ponudnika, naj podpiše, konvencije kodiranja pa ostanejo znotraj ponudnika. Če pronicajo navzgor, vsak klicatelj na koncu nosi pogoj na zaledje. Isti vzorec se pokaže v zgodbi oddaljenega podpisovanja, opisani v oddaljenih sejah podpisovanja PAdES proti HSM
// Vmesnik ponudnika je isti na vsaki platformi, zato je izbira
// odločitev ob zagonu in ne na klic
{$IFDEF DARWIN}
if KeychainAvailable then
ConfigureKeychainSignerProvider;
{$ENDIF}
{$IFDEF MSWINDOWS}
// Ponudnik CNG Windows namesti platformska enota
{$ENDIF}
if not PadesCryptoAvailable then
raise Exception.Create('no signing backend on this platform');
// Od tu naprej je podpisna koda platformsko nevtralna
Signer := ResolvePadesSigner(Options);
Pravila štetja sklicev, ki sedejo tri vrstice narazen
Upravljanje pomnilnika Core Foundation sledi poimenovalnim konvencijam in past tu je, da se funkcije z različnimi konvencijami pojavijo druga ob drugi v istem kratkem bloku. Funkcija, ki dobi certifikat iz objekta zaupanja, vrne izposojeni sklic, ki se ga ne sme sprostiti. Funkcije, ki kopirajo certifikat podpisovalca ali kopirajo njegove podatke, vrnejo lastniške sklice, ki se jih mora sprostiti. Trije klici v zaporedju, dve pravili lastništva, sprostitev izposojenega pa ne spodleti na tisti vrstici. Pokvari števec obdržev in pozneje podre nekaj nepovezanega
Ublažitev je prebrati glagol v imenu vsake funkcije ogrodja, preden zapišete čiščenje — vsakič, brez izjem. To je CoreFoundation ustreznica preverjanja, ali API vrne izvod ali pogled, cena napake pa je občasno sesutje in ne napaka
Česa to zaledje ne trdi
Ob času pisanja še nikoli ni tekel na macOS in povedati to na glas je uporabneje kot implicitno zagotovilo. Dokazljivo resnica je ožja in še vedno vredna: enota se prevede na Windows kot del dnevne gradnje, vsak simbol ogrodja je vezan po imenu med zagonom z naštetimi odpovedmi, logika izbire algoritma — vključno z obema zasilnima izhodoma PSS — pa je navaden Pascal, ki ga je mogoče pregledati in o njem sklepati. Prvi zagon na Macu bo ali deloval ali proizvedel seznam imen za popravilo
Preverjevalna stran, ki uporablja dekoder CMS višje ravni in ne sestavljanja strukture CMS na roko, je pokrita v preverjanju podpisov PDF na macOS s SecTrust in si deli isto vezalno infrastrukturo ter isti diagnostični pristop
Prenosljiva ideja tu gre za umestitev tveganja in ne za macOS. Ko morate pisati kodo proti vmesniku, ki ga ne morete preveriti, izberite konstrukcijo, kjer so napake najcenejše za lociranje. Dinamično vezanje z izrecnim seznamom nerazrešenih imen spremeni dvajset neverificiranih predpostavk v eno diagnostično vrstico. Obe zaledji prideta kot vir z PDFium Delphi component, tako da če ime simbola res potrebuje popravek, je to sprememba ene vrstice v vašem lastnem drevesu in ne prijava na podporo