Tekninen artikkeli

PAdES-allekirjoitus macOS Keychain -identiteetillä Delphissä

PDFium VCL allekirjoittaa PAdES-dokumentit macOS Keychainissa pidetyllä yksityisellä avaimella taustajärjestelmän kautta, joka ratkaisee jokaisen Security- ja CoreFoundation-symbolin ajonaikana dlopenilla ja dlsymillä. Mikään ei ole linkitysaikana sidottua, mikä tarkoittaa, että väärin kirjoitettu symbolinimi ilmestyy KeychainAvailablein palauttamana Falsena ja KeychainMissingSymbolsin nimeämänä syyllisenä, linkitysvirheen tai kaatumisen sijaan

Tuon valinnan pakotti epämukava rajoite, ja sen käsittelytapa yleistyy. Yksikkö kirjoitettiin koneella, jossa ei ollut macOS SDK:ta, joten jokainen frameworkin symbolinimi ja jokainen vakio tuli dokumentaatiosta, eikä mitään niistä voinut tarkistaa otsikkotiedostoa vasten. Väärä vastaus siihen tilanteeseen on kirjoittaa koodi huolellisesti ja toivoa. Oikea on järjestää niin, että väistämättömät virheet ilmoittavat itsestään mahdollisimman paikannettavassa muodossa

Miksi dynaaminen sidonta on oikea valinta jopa kohdealustalla

Koska se muuntaa vikaluokan, joka pysäyttää ohjelman, vikaluokaksi, joka raportoi itsestään. Staattisesti linkitetty framework-viittaus, joka on väärä, epäonnistuu linkitysaikana kohteella eikä koskaan linkity muualla. Dynaamisesti sidottu, joka on väärä, tuottaa käytettävissä olemattoman taustan ja luettelon ratkaisemattomista nimistä, ja ensimmäinen ajo Macilla kääntää kysymyksen miksi tämä ei ole käytettävissä yhdeksi riviksi, joka nimeää kirjoitusvirheen

On toinenkin hyöty, joka maksaa itsensä takaisin päivittäin eikä kerran. Koska yksikkö ei linkitä frameworkkeja, se kääntyy jokaisella alustalla, joten tavallinen Windows-käännös pitää tarkistamassa sen syntaksia, tyyppejään ja uses-lausettaan. Yksikkö, joka kääntyy vain alustalla, jota kukaan tiimissä ei omista, on yksikkö ilman mitään kääntäjää katsomassa sitä, ja se rappeutuu hiljaa jokaisen jaetun tyypin refaktorin myötä

uses
  FPdfCrypto, FPdfCryptoMac;

var
  Options: TPadesSignerOptions;
begin
  if not KeychainAvailable then
    raise Exception.Create('Keychain backend unavailable, unresolved: ' +
      KeychainMissingSymbols);

  ConfigureKeychainSignerProvider;   // asenna PAdES-allekirjoitustaustaksi
  ConfigureKeychainCmsVerifier;      // ja varmennustaustaksi

  Writeln('signer backend  : ', PadesCryptoBackendName);
  Writeln('verify backend  : ', PadesCmsVerificationBackendName);

  Options := TPadesSignerOptions.Default;
  Options.CertificateThumbprint := 'B1 3F 9C ...';   // SHA-1, mikä kirjainkoko tahansa
  Options.PaddingScheme := psRsaPss;
end;

Kahdenlaista vietyä symbolia, kaksi tapaa lukea niitä

Tämä on koko sidonnan sekavin yksittäinen yksityiskohta, ja sen saaminen väärin päin kääntyy puhtaasti ja epäonnistuu ajonaikana. CoreFoundation ja Security vievät kaksi kategorisesti erilaista asiaa saman dlsym-kutsun kautta, ja koodin on tunnettava, kumpi on kumpi

Nimetyt vakiot, kuten keychain-alkuluokan avaimet ja CoreFoundationin boolen singletonit, ovat viettyjä muuttujia, joiden sisältö on haluamasi CFStringRef tai CFBooleanRef. dlsym palauttaa kyseisen muuttujan osoitteen, joten sinun on dereferoitava kerran saadaksesi arvon. Callback-taulurakenteet, kuten sanakirjan avain- ja arvocallbackit, ovat viettyjä rakenteita, ja dlsym palauttaa rakenteen osoitteen, mikä on täsmälleen se osoitin, jota sanakirjan luontifunktio odottaa. Dereferoi tuo, ja passaat rakenteen ensimmäisen konesanan ikään kuin se olisi osoitin

Kumpikaan virhe ei tuota käännösvirhettä, eikä kumpikaan tuota selkeää ajonaikaista virhettä. Saat roskaposittimen, joka epäonnistuu jossain alavirtassa. Tapa verottaa erottelu mahdottomaksi saada väärin on lopettaa sen muistamiseen nojaaminen: kaksi apufunktiota, toinen, joka sitoo ja dereferoi, ja toinen, joka sitoo muttei, joten kutsupaikka julistaa, minkälaista symbolia se pyytää, ja apuri valvoo loput

Kaavio PDFium VCL:n macOS Keychain -taustasta, joka ratkaisee Security- ja CoreFoundation-symbolit dlsymillä: kSecClass on viety muuttuja, jonka BindConstant dereferoi kerran saadakseen CFStringRef-arvon, kun taas kCFTypeDictionaryKeyCallBacks on viety rakenne, jonka BindStruct passaa osoitteella, ja kahden säännön sekoittaminen tuottaa roskaposittimia alavirtaan
Yksi dlsym-kutsu palauttaa kaksi kategorisesti erilaista asiaa: muuttujan osoitteen, joka pitää CFTypeRefiä, ja callback-rakenteen osoitteen. Kaksi apuria tekee dereferoida-tai-ei-päätöksen sidontapaikalla muistin sijaan
// Viety muuttuja: dlsym antaa muuttujan osoitteen, joka pitää
// CFTypeRefiä, joten dereferoi kerran
FSecClassKey := BindConstant(SecurityLib, 'kSecClass');

// Viety rakenne: dlsym antaa rakenteen OSOITTEEN, mikä on se,
// mitä API haluaa. Älä dereferoi
FKeyCallbacks := BindStruct(CoreFoundationLib,
  'kCFTypeDictionaryKeyCallBacks');

Miksi RSA-PSS-allekirjoitus tarvitsee kaksi erillistä varapolkua?

Koska algoritmi voi puuttua kahdella itsenäisellä tavalla, ja vain yksi niistä on versiokysymys. PSS-tiivistysallekirjoitusalgoritmin vakio ilmestyi macOS 10.13:ssa, joten vanhemmalla järjestelmällä symboli yksinkertaisesti ei ole siellä ja sidonta saa nilin. Tuo on versiotarkistus. Erikseen järjestelmällä, jossa vakio on olemassa, tietty avain voi silti kieltäytyä siitä, ja framework vastaa tuon kysymyksen SecKeyIsAlgorithmSupportedilla kyseiselle avaimelle. Laitteistopohjainen avain tai rajoittavilla attribuuteilla varustettu avain voi kieltäytyä PSS:stä, kun taas ohjelmistoavain samalla koneella hyväksyy sen

Molempien polkujen on johdettava samaan varapolkuun: vaihda PKCS#1 v1.5:een. Ja kriittinen osa on, että varapolun on muutettava myös CMS-rakenteeseen kirjoitettava algoritmitunniste, ei vain allekirjoituskutsua. PSS-algoritmitunnisteen emittoiminen tuottaen oikeasti v1.5-allekirjoitusta tuottaa dokumentin, jonka jokainen verifiointi hylkää täysin, mikä on ankarasti pahempaa kuin raportti, ettei PSS ole tuettu. Alennus on hyväksyttävä, ristiriita sen välillä, mitä julistat ja mitä teit, ei ole, ja se on yleinen sääntö allekirjoituskoodille eikä macOS:n erikoisuus. Allekirjoitustason vaikutukset on ladottu artikkelissa PDF-tiedostojen allekirjoittaminen PAdES B-B:llä

Päätösketju, joka osoittaa, miksi RSA-PSS-allekirjoitus PDFium VCL:n Keychain-taustassa tarvitsee kaksi itsenäistä varapolkua: dlsym palauttaa nilin tiivistysallekirjoituksen vakiolle macOS-versioissa ennen 10.13:a, SecKeyIsAlgorithmSupported voi kieltäytyä laitteistopohjaisesta avaimesta, ja molemmat portit suppenevat samaan PKCS#1 v1.5 -alennukseen, jonka CMS-algoritmitunnisteen on muututtava mukana
PSS voi olla käytettävissä olematon kahdesti, kerran per macOS-versio ja kerran per avain, ja vain versioportti on järjestelmäkysymys. Molemmat portit suppenevat samaan v1.5-alennukseen, ja CMS-tunniste seuraa

ECDSA-allekirjoituksen koodaus ja käänteisyys, joka kannattaa huomioida

Elliptisen käyrän polku ei tarvitse lainkaan muunnosta macOS:lla, ja se on päinvastainen sille, mitä PKCS#11-sidonta vaatii. Security frameworkin tiivistysallekirjoitusalgoritmi ECDSA:lle palauttaa allekirjoituksen jo X9.62 DER-muodossa, mikä on täsmälleen se, mitä CMS haluaa. PKCS#11-tunnus palauttaa sen sijaan raakan kiinteäleveän P1363-parin, joka on koodattava uudelleen ennen kuin se menee allekirjoitusrakenteeseen

Joten kaksi taustaa, jotka toteuttavat saman rajapinnan, tarvitsevat vastakkaisen kohtelun samalle algoritmille, eikä kumpikaan ole väärässä. Tämä on täsmälleen se erojen laji, jonka abstraktion on nieltävä paljastamisen sijaan: PAdES-kerros pyytää palveluntarjoajaa allekirjoittamaan, ja koodauskonventiot pysyvät palveluntarjoajan sisällä. Jos ne vuotavat ylöspäin, jokainen kutsuja päätyy kantamaan per tausta -ehdollistusta. Sama muoto ilmestyy artikkelin etä-PAdES-allekirjoitusistunnot HSM:ää vasten kuvailemassa etäallekirjoitustarinassa

Vertailu ECDSA-allekirjoituksen koodauksesta PDFium VCL:n PAdES-allekirjoittajan kahden taustan yli: macOS Keychainin Security framework palauttaa X9.62 DERin, jonka CMS hyväksyy ilman muunnosta, kun taas PKCS#11-tunnus palauttaa raakan kiinteäleveän P1363-parin, joka on koodattava uudelleen, joten ResolvePadesSigner pitää koodauskonventiot palveluntarjoajan sisällä
Sama ECDSA-rajapinta tarvitsee vastakkaisen kohtelun per tausta: Security luovuttaa valmiin DERin, kun taas PKCS#11-tunnus luovuttaa raakan P1363:n, joten muunnos asuu palveluntarjoajan sisällä ja kutsujat eivät koskaan näe per tausta -ehdollistusta
// Palveluntarjoajan rajapinta on sama jokaisella alustalla, joten valinta on
// käynnistyspäätös eikä per kutsu tehtävä päätös
{$IFDEF DARWIN}
  if KeychainAvailable then
    ConfigureKeychainSignerProvider;
{$ENDIF}
{$IFDEF MSWINDOWS}
  // Windowsin CNG-palveluntarjoaja asennetaan alustayksikössä
{$ENDIF}

if not PadesCryptoAvailable then
  raise Exception.Create('no signing backend on this platform');

// Tästä eteenpäin allekirjoituskoodi on alustaneutraalia
Signer := ResolvePadesSigner(Options);

Referenssilaskennan säännöt, jotka istuvat kolmen rivin päässä

Core Foundationin muistinhallinta noudattaa nimikäytäntöjä, ja ansa täällä on, että eri konventioiden funktiot ilmestyvät vierekkäin samassa lyhyessä lohkossa. Funktio, joka hakee varmenteen trust-objektista, palauttaa lainatun viittauksen, jota ei saa vapauttaa. Funktiot, jotka kopioivat allekirjoittajan varmenteen tai kopioivat sen datan, palauttavat omistetut viittaukset, jotka on vapautettava. Kolme kutsua peräkkäin, kaksi omistussääntöä, ja lainatun vapauttaminen ei kaadu tuolla rivillä. Se korruptoi retain-laskurin ja kaataa jotain asiaatonta myöhemmin

Liuvutus on lukea verbi jokaisesta frameworkfunktion nimestä ennen siivouksen kirjoittamista, joka kerta, ilman poikkeusta. Se on CoreFoundationin vastine sille, tarkistaako API palauttaako kopion vai näkymän, ja väärin saamisen hinta on ajoittainen kaatuma eikä virhe

Mitä tämä tausta ei väitä

Se ei ole koskaan ajanut macOS:lla kirjoitushetkellä, ja sen sanominen suoraan on hyödyllisempää kuin vihjattu takuu. Se, mikä on todistettavasti totta, on kapeampaa ja yhä arvokasta: yksikkö kääntyy Windowsilla osana päivittäistä käännöstä, jokainen framework-symboli sidotaan nimellä ajonaikana viat lueteltuina, ja algoritmin valintalogiikka molempine PSS-varapolkuineen on tavallista Pascalia, jota voi katselmoida ja josta voi päästellä. Ensimmäinen ajo Macilla joko toimii tai tuottaa luettelon korjattavista nimistä

Varmennusvastine, joka käyttää korkeamman tason CMS-dekooderia CMS-rakenteen käsin kokoamisen sijaan, on käsitelty artikkelissa PDF-allekirjoitusten varmennus macOS:lla SecTrustilla, ja se jakaa saman sidontainfrastruktuurin ja saman diagnostiikkalähestymistavan

Siirrettävä idea tässä koskee riskin sijoittelua eikä macOS:ia. Kun sinun on kirjoitettava koodia rajapintaa vasten, jota et voi varmistaa, valitse rakennelma, jossa virheet ovat halvimpia paikallistaa. Dynaaminen sidonta eksplisiittisellä luettelolla ratkaisemattomista nimistä muuntaa kaksikymmentä varmistamatonta oletusta yhdeksi diagnostiikkariviksi. Molemmat taustat toimituvat lähteinä PDFium Delphi -komponentin mukana, joten jos symbolinimi kaipaakin korjausta, se on yhden rivin muutos omassa puussasi eikä tukipyyntö