PDFium VCL potpisuje PAdES dokumente privatnim ključem pohranjenim u macOS Keychain kroz backend koji razrješuje svaki Security i CoreFoundation simbol u runtimeu s dlopen i dlsym. Ništa nije link-time vezano, što znači da krivo tipkano ime simbola ispliva kao KeychainAvailable koji vraća False i KeychainMissingSymbols koji imenuje krivca, umjesto kao linkerska greška ili crash
Taj je izbor bio prisiljen neugodnim ograničenjem, i način na koji je obrađen se generalizira. Unit je pisan na mašini bez macOS SDK-a, pa je svako ime framework simbola i svaka konstanta došla iz dokumentacije i nijedno se nije moglo provjeriti protiv headera. Krivi odgovor na tu situaciju je pažljivo napisati kod i nadati se. Pravi je urediti da neizbježne greške same najave sebe u najlakše locirajućem obliku
Zašto je dinamičko vezivanje pravi poziv čak i na ciljnoj platformi
Jer pretvara klasu neuspjeha koja zaustavlja program u klasu neuspjeha koja sama sebe javlja. Statički linkana referenca na framework koja je kriva pade u trenutku linkanja na cilju i nikad se ne linka drugdje. Dinamički vezana koja je kriva proizvodi nedostupan backend i popis nerazriješenih imena, i prvo pokretanje na Macu pretvara pitanje iz zašto je ovo nedostupno u jednu liniju koja imenuja tipfeler
Tu je druga korist koja se isplati svakodnevno umjesto jednom. Budući da unit ne linka nijedan framework, kompajlira se na svakoj platformi, pa obični Windows build i dalje provjerava njegovu sintaksu, njegove tipove i njegov uses clause. Unit koji se kompajlira samo na platformi koju nitko u timu nema je unit kojeg nijedan kompajler ne gleda, i truli tiho sa svakim refaktorom dijeljenog tipa
uses
FPdfCrypto, FPdfCryptoMac;
var
Options: TPadesSignerOptions;
begin
if not KeychainAvailable then
raise Exception.Create('Keychain backend unavailable, unresolved: ' +
KeychainMissingSymbols);
ConfigureKeychainSignerProvider; // instaliraj kao PAdES signer backend
ConfigureKeychainCmsVerifier; // i kao verification backend
Writeln('signer backend : ', PadesCryptoBackendName);
Writeln('verify backend : ', PadesCmsVerificationBackendName);
Options := TPadesSignerOptions.Default;
Options.CertificateThumbprint := 'B1 3F 9C ...'; // SHA-1, nebitna velika/mala
Options.PaddingScheme := psRsaPss;
end;
Dvije vrste izvezenog simbola, dva načina čitanja
Ovo je pojedinačno najzbunjujući detalj u cijelom vezivanju, i poremetiti ga se kompajlira čisto a pade u runtimeu. CoreFoundation i Security izvoze dvije kategorijalno različite stvari kroz isti dlsym poziv, i kod mora znati koje je koje
Imenovane konstante poput ključeva klasa keychain stavki i CoreFoundation boolean singletona su izvezene varijable čiji je sadržaj CFStringRef ili CFBooleanRef koje želite. dlsym vraća adresu te varijable, pa morate jednom dereferencirati da dobijete vrijednost. Strukture tablica callbackova poput dictionary key i value callbackova su izvezene strukture, i dlsym vraća adresu strukture, što je upravo pokazivač koji funkcija stvaranja dictionaryja očekuje. Dereferencirate li tu, predajete prvu mašinsku riječ strukture kao da je pokazivač
Nijedna greška ne proizvodi compile grešku, i nijedna ne proizvodi jasnu runtime grešku. Dobijete garbage pokazivač koji pade negdje nizvodno. Način da razliku učinite nemogućom za pogriješiti je prestati oslanjati se na pamćenje: dvije helper funkcije, jedna koja veže i dereferencira i jedna koja veže i ne dereferencira, pa call site deklarira koje vrste simbola traži a helper nameće ostalo
// Izvezena varijabla: dlsym daje adresu varijable koja drži
// CFTypeRef, pa jednom dereferencirajte
FSecClassKey := BindConstant(SecurityLib, 'kSecClass');
// Izvezena struktura: dlsym daje adresu STRUKTURE, a to je
// ono što API želi. Nemojte dereferencirati
FKeyCallbacks := BindStruct(CoreFoundationLib,
'kCFTypeDictionaryKeyCallBacks');
Zašto RSA-PSS potpis treba dva odvojena fallbacka?
Jer algoritam može nedostajati na dva neovisna načina, i samo jedan od njih je pitanje verzije. PSS digest-signing algoritamska konstanta pojavila se u macOS 10.13, pa na starijem sustavu simbol jednostavno nije tamo i vezivanje dobiva nil. To je provjera verzije. Odvojeno, na sustavu gdje konstanta postoji, određeni ključ može je ipak odbiti, i framework odgovara na to pitanje kroz SecKeyIsAlgorithmSupported za taj ključ. Hardverski poduprt ključ ili ključ s restriktivnim atributima može odbiti PSS dok ga softverski ključ na istoj mašini prima
Oba puta moraju dovesti do istog fallbacka: prebaciti na PKCS#1 v1.5. I kritični dio je da fallback mora promijeniti i identifikator algoritma zapisan u CMS strukturu, a ne samo potpisni poziv. Emitirati PSS identifikator algoritma dok stvarno proizvodite v1.5 potpis daje dokument koji svaki verifier odmah odbija, što je strogo gore nego javiti da PSS nije podržan. Downgrade je prihvatljiv, neslaganje između onoga što deklarirate i onoga što ste učinili nije, i to je opće pravilo za potpisni kod a ne macOS čudnost. Implikacije na razini potpisa razložene su u potpisivanju PDF-ova s PAdES B-B
ECDSA encoding potpisa, i obrat koji vrijedi primijetiti
Put eliptičkih krivulja ne treba nikakvu konverziju na macOS-u, a to je suprotno od onoga što PKCS#11 vezivanje traži. Security framework digest-signing algoritam za ECDSA vraća potpis već u X9.62 DER obliku, što je točno ono što CMS želi. PKCS#11 token vraća umjesto toga sirovi fiksne širine P1363 par, koji se mora ponovno kodirati prije nego uđe u strukturu potpisa
Dakle dva backenda koja implementiraju isti interface trebaju suprotan tretman za isti algoritam, i nijedan nije kriv. To je točno ona vrsta razlike koju apstrakcija mora apsorbirati umjesto izložiti: PAdES sloj traži od providera da potpiše, i encoding konvencije ostaju unutar providera. Ako iscure naviše, svaki pozivatelj završi noseći per-backend uvjetni. Isti oblik se pojavljuje u priči o udaljenom potpisivanju opisanoj u udaljenim PAdES signing sesijama protiv HSM-a
// Provider interface je isti na svakoj platformi, pa je izbor
// startup odluka umjesto po-pozivu
{$IFDEF DARWIN}
if KeychainAvailable then
ConfigureKeychainSignerProvider;
{$ENDIF}
{$IFDEF MSWINDOWS}
// Windows CNG provider instalira platformski unit
{$ENDIF}
if not PadesCryptoAvailable then
raise Exception.Create('no signing backend on this platform');
// Odtud je potpisni kod platformski neutralan
Signer := ResolvePadesSigner(Options);
Pravila reference countinga koja sjede tri linije udaljena
Core Foundation upravljanje memorijom slijedi naming konvencije, i zamka je da se funkcije s različitim konvencijama pojavljuju jedne pokraj drugih u istom kratkom bloku. Funkcija koja dobavlja certifikat iz trust objekta vraća posuđenu referencu koja se ne smije otpustiti. Funkcije koje certifikat potpisnika kopiraju ili kopiraju njegove podatke vraćaju vlasničke reference koje se moraju otpustiti. Tri poziva zaredom, dva pravila vlasništva, i otpuštanje posuđene ne pade na toj liniji. Pokvari retain count i obori nešto nepovezano kasnije
Ublažavanje je pročitati glagol u imenu svake framework funkcije prije pisanja čišćenja, svaki put, bez izuzetka. To je CoreFoundation pandan provjeri vraća li API kopiju ili pogled, i cijena pogreške je isprekidani crash umjesto greške
Što ovaj backend ne tvrdi
Nikad nije trčao na macOS-u u trenutku pisanja, i reći to otvoreno korisnije je od impliciranog uvjeravanja. Ono što je dokazivo istina je uže i još uvijek vrijedno: unit se kompajlira na Windowsima kao dio dnevnog builda, svaki framework simbol se veže po imenu u runtimeu s enumeriranim neuspjesima, i logika izbora algoritma uključujući oba PSS fallbacka je običan Pascal koji se može pregledati i o njemu razmišljati. Prvo pokretanje na Macu ili će raditi ili će proizvesti popis imena za popraviti
Verifikacijski pandan, koji koristi višu razinu CMS dekoder umjesto da sastavlja CMS strukturu rukom, pokriven je u verificiranju PDF potpisa na macOS-u sa SecTrustom, i dijeli istu vezivnu infrastrukturu i isti dijagnostički pristup
Prenosiva ideja ovdje je o smještanju rizika umjesto o macOS-u. Kad morate pisati kod protiv interfacea koji ne možete verificirati, odaberite konstrukciju gdje su greške najjeftinije locirati. Dinamičko vezivanje s eksplicitnim popisom nerazriješenih imena pretvara dvadeset neverifikabilnih pretpostavki u jednu dijagnostičku liniju. Oba backenda isporučuju se kao source uz PDFium Delphi komponentu, pa ako ime simbola doista treba ispraviti, to je promjena jedne linije u vlastitom stablu umjesto support tiketa