A PDFium VCL PAdES dokumentumokat ír alá macOS Keychainben tárolt privát kulccsal egy olyan backenden keresztül, amely minden Security és CoreFoundation szimbólumot futásidőben old fel dlopen-nel és dlsym-mel. Semmi sem linkelésidejű kötés, ami azt jelenti, hogy egy elgépelt szimbólumnév úgy mutatkozik meg, hogy a KeychainAvailable False-t ad vissza, és a KeychainMissingSymbols megnevezi a bűnöst, nem linkerhibaként vagy crashként
Ezt a választást kényelmetlen megszorítás kényszerítette ki, és az kezelési mód általánosítható. A unitot egy macOS SDK-val nem bíró gépen írták, tehát minden keretrendszer-szimbólumnév és minden konstans dokumentációból származott, és egyiken sem lehetett fejléccel ellenőrizni. A rossz válasz arra a helyzetre az, hogy gondosan megírja a kódot, és reménykedik. A helyes az, hogy úgy rendezi be a dolgot, hogy az elkerülhetetlen hibák a lehető legjobban lokalizálható formában jelentsék magukat
Miért jó döntés a dinamikus kötés még a célplatformon is
Mert átfordítja azt a hibaosztályt, amely megállítja a programot, olyan hibaosztállyá, amely magát jelenti. Egy statikusan linkelt, rossz keretrendszerhivatkozás linkelésidejű hibaként bukik meg a célon, és sehol máshol nem linkel. Egy dinamikusan kötött, rossz olyan hátteret ad, amely nem érhető el, és fel nem oldott nevek listáját, és az első futás egy Macen a kérdést arról, miért nem érhető el, egyetlen sorra váltja, amely egy elírást nevez meg
Van második haszon is, amely naponta fizet ki, nem egyszer. Mivel a unit egyetlen keretrendszert sem linkel, minden platformon lefordul, tehát a szokásos Windows build továbbra is ellenőrzi a szintaxisát, típusait és uses záradékát. Egy unit, amely csak olyan platformon fordul, amely a csapatban senkinek sincs meg, olyan unit, amelyre egyetlen fordító sem néz rá, és minden megosztott típus refactorjánál csendben romlik
uses
FPdfCrypto, FPdfCryptoMac;
var
Options: TPadesSignerOptions;
begin
if not KeychainAvailable then
raise Exception.Create('Keychain backend unavailable, unresolved: ' +
KeychainMissingSymbols);
ConfigureKeychainSignerProvider; // telepítés PAdES aláíró backendként
ConfigureKeychainCmsVerifier; // és hitelesítő backendként
Writeln('signer backend : ', PadesCryptoBackendName);
Writeln('verify backend : ', PadesCmsVerificationBackendName);
Options := TPadesSignerOptions.Default;
Options.CertificateThumbprint := 'B1 3F 9C ...'; // SHA-1, bármilyen kis- és nagybetűzés
Options.PaddingScheme := psRsaPss;
end;
Két fajta exportált szimbólum, két olvasási mód
Ez a legzavaróbb részlet az egész kötésben, és visszafelé eltalálva tisztán fordul, és futásidőben bukik meg. A CoreFoundation és a Security két kategóriában is különböző dolgot exportál ugyanazon a dlsym híváson, és a kódnak tudnia kell, melyik melyik
A nevesített konstansok, mint a keychain elemosztály-kulcsok és a CoreFoundation boolean szingletonok, exportált változók, amelyek tartalma az Ön által kért CFStringRef vagy CFBooleanRef. A dlsym annak a változónak a címét adja vissza, tehát egyszer vissza kell dereferálnia az érték megszerzéséhez. A callback-tábla struktúrák, mint a szótárkulcs- és érték-callbackek, exportált struktúrák, és a dlsym a struktúra címét adja vissza, ami pontosan az a pointer, amelyet a szótárlétrehozó függvény vár. Ha azt dereferálja, a struktúra első gépszavát adja át, mintha pointer volna
Egyik hiba sem ad fordítási hibát, és egyik sem ad tiszta futásidejű hibát. Egy szemétpointert kap, amely valahol lejjebb bukik meg. Az a mód, amely lehetetlenné teszi a megkülönböztetés elrontását, az, hogy abbahagyja annak megjegyzésére való hagyatkozást: két segédfüggvény, amelyik köt és dereferál, és amelyik köt, de nem, tehát a hívási hely deklarálja, melyik fajta szimbólumot kér, a segéd pedig érvényesíti a többit
// Exportált változó: a dlsym a CFTypeRef-et hordozó változó címét adja,
// tehát egyszer dereferáljon
FSecClassKey := BindConstant(SecurityLib, 'kSecClass');
// Exportált struktúra: a dlsym A struktúra címét adja, ami az, amit
// az API kér. Ne dereferálja
FKeyCallbacks := BindStruct(CoreFoundationLib,
'kCFTypeDictionaryKeyCallBacks');
Miért van egy RSA-PSS aláírásnak két külön tartalékra szüksége?
Mert az algoritmus két független módon hiányozhat, és csak az egyik verziókérdés. A PSS kivonatoló-aláíró algoritmus konstansa a macOS 10.13-ban jelent meg, tehát idősebb rendszeren a szimbólum egyszerűen nincs ott, és a kötés nil-t kap. Az a verzióellenőrzés. Külön, olyan rendszeren, ahol a konstans létezik, egy konkrét kulcs továbbra is visszautasíthatja, és a keretrendszer azt a kérdést a SecKeyIsAlgorithmSupported-dal válaszolja meg arra a kulcsra. Egy hardveres kulcs vagy korlátozó attribútumokkal bíró kulcs elutasíthatja a PSS-t, miközben ugyanazon gépen egy szoftveres kulcs elfogadja
Mindkét útnak ugyanahhoz a tartalékhoz kell vezetnie: váltás PKCS#1 v1.5-re. És a kritikus rész az, hogy a tartaléknak a CMS struktúrába írt algoritmusazonosítót is meg kell változtatnia, nem csak az aláíró hívást. PSS algoritmusazonosító kibocsátása úgy, hogy valójában v1.5 aláírás készül, olyan dokumentumot ad, amelyet minden verifikátor elutasít, ami szigorúan rosszabb, mint jelenteni, hogy a PSS nem támogatott. Egy lefokozás elfogadható, egy eltérés az Ön által deklarált és a tett között nem, és ez az aláíráskód általános szabálya, nem macOS furcsaság. Az aláírás szintjén járó következményeket a PDF-ek aláírása PAdES B-B-vel fejti ki
ECDSA aláíráskódolás, és egy figyelemre méltó megfordulás
Az elliptikusgörbe útnak macOS-en egyáltalán nincs szüksége átalakításra, és az az ellenkezője annak, amit egy PKCS#11 kötés megkíván. A Security keretrendszer ECDSA kivonatoló-aláíró algoritmusa az aláírást már X9.62 DER formában adja vissza, ami pontosan az, amit a CMS akar. Egy PKCS#11 token ehelyett a nyers, rögzített szélességű P1363 párt adja vissza, amelyet újra kell kódolni, mielőtt aláírás-struktúrába kerülne
Tehát két backend, amelyek ugyanazt az interfészt implementálják, ellenkező bánásmódot igényel ugyanaz az algoritmus, és egyik sem rossz. Ez pontosan az a különbségfajta, amelyet egy absztrakciónak fel kell nyelnie, nem kitennie: a PAdES réteg egy szolgáltatót kér aláírásra, és a kódolási konvenciók a szolgáltatón belül maradnak. Ha felfelé szivárognak, minden hívó backendenkénti feltételt cipel végül. Ugyanez az alak jelenik meg a távoli PAdES aláírási munkamenetek HSM-mel cikkben leírt távoli aláírási történetben
// A szolgáltatói interfész minden platformon ugyanaz, tehát a
// kiválasztás indulási döntés, nem hívásonkénti
{$IFDEF DARWIN}
if KeychainAvailable then
ConfigureKeychainSignerProvider;
{$ENDIF}
{$IFDEF MSWINDOWS}
// A Windows CNG szolgáltatót a platform unit telepíti
{$ENDIF}
if not PadesCryptoAvailable then
raise Exception.Create('no signing backend on this platform');
// Innentől az aláíró kód platformsemleges
Signer := ResolvePadesSigner(Options);
Hivatkozásszámlálási szabályok, amelyek három sorra esnek egymástól
A Core Foundation memóriakezelése elnevezési konvenciókat követ, és a csapda itt az, hogy a különböző konvenciójú függvények ugyanabban a rövid blokkban jelennek meg egymás mellett. Egy tanúsítványt kapó függvény egy bizalmi objektumtól kölcsönzött hivatkozást ad vissza, amelyet nem szabad elengedni. Azok a függvények, amelyek másolnak egy aláíró tanúsítványt vagy másolják az adatait, birtokolt hivatkozásokat adnak vissza, amelyeket el kell engedni. Három hívás sorban, két birtoklási szabály, és a kölcsönzött elengedése nem azon a soron bukik meg. Megrongál egy retainszámlálót, és később lebont valami tőle függetlent
Az enyhítés az, hogy minden alkalommal, kivétel nélkül, elolvassa az igét minden keretrendszerfüggvény nevében, mielőtt megírja a takarítást. Ez a CoreFoundation megfelelője annak, hogy ellenőrzi, egy API másolatot ad-e vissza vagy nézetet, és az elrontásának az ára időszakos crash, nem hiba
Mit nem állít ez a backend
Sosem futott macOS-en a cikk írásakor, és ezt egyszerűen kimondani hasznosabb, mint burkolt biztosíték. Ami bizonyíthatóan igaz, az szűkebb, és így is értékes: a unit Windowson lefordul a napi build részeként, minden keretrendszer-szimbólum név szerint kötődik futásidőben a hibák felsorolásával, és az algoritmusválasztó logika, mindkét PSS tartalékkal együtt, közönséges Pascal, amelyet át lehet nézni és racionálisan mérlegelni. Az első futás egy Macen vagy működni fog, vagy javítandó nevek listáját adja
A verifikációs pár, amely a magasabb szintű CMS dekódert használja a CMS struktúra kézi összeállítása helyett, a PDF aláírások verifikálása macOS-en SecTrust-tal cikkben van lefedve, és osztozik ugyanazon kötési infrastruktúrán és ugyanazon diagnosztikai megközelítésen
Az átvihető gondolat itt a kockázat elhelyezéséről szól, nem a macOS-ről. Amikor olyan interfész ellen kell kódot írnia, amelyet nem tud verifikálni, válassza azt a konstrukciót, amelyben a hibák a legolcsóbban lokalizálhatók. A dinamikus kötés a fel nem oldott nevek explicit listájával húsz nem verifikálható feltevést egyetlen diagnosztikai sorrá tesz. Mindkét backend forrásként szállul a PDFium Delphi komponenssel, tehát ha egy szimbólumnevet mégis javítani kell, az egy soros változtatás a saját forrásfájában, nem támogatási jegy