Tekninen artikkeli

PDF-digitaalisten allekirjoitusten varmistaminen Delphissä HotPDF-komponentilla

HotPDF varmistaa ladattujen PDF-dokumenttien digitaaliset allekirjoitukset kolmella THotPDF-metodilla: GetLoadedSignatureInfo, VerifyLoadedSignature ja VerifyLoadedSignatureEx, jotka esiteltiin versiossa v2.259.0. Komponentti laskee uudelleen alkuperäisen tiedoston /ByteRange-osioiden tiivisteet, tarkistaa CMS-määritteen messageDigest ja suorittaa RSA PKCS#1 v1.5 -varmistuksen upotetulle allekirjoittajan varmenteelle palauttaen arvon svValid, kun dokumentin tavut ovat ehjät

Skenaario on arkinen, mutta panokset eivät. Vastapuoli palauttaa allekirjoitetun sopimuksen, työnkulkusi on arkistoitava se, ja joku kysyy ainoan kysymyksen, jolla on merkitystä: onko tämä lähettämämme dokumentti tavulleen sama ja allekirjoitettu sillä varmenteella, jolla väitetään? Tähän vastaaminen koodissa on allekirjoitustarinan varmistuspuoli. Allekirjoituspuolta, eli PAdES-allekirjoitusten luomista ja upottamista, käsitellään kumppaniartikkelissa PAdES-digitaalisten allekirjoitusten luomisesta HotPDF-komponentilla. Tämä artikkeli käsittelee toista suuntaa: PDF saapuu valmiiksi allekirjoitettuna, ja haluat ohjelmallisen päätöksen Acrobat-ohjelman vihreän valintamerkin kuvakaappauksen sijaan

Miten allekirjoitettu PDF todistaa, ettei sitä ole peukaloitu?

PDF-allekirjoitus suojaa tiedoston tiettyjä tavualueita, ei abstraktia käsitettä "dokumentista". Standardi ISO 32000-1 §12.8 määrittelee mekanismin: allekirjoituksen lomakekenttä sisältää hakemiston, jonka /Contents-tietue sisältää CMS SignedData -säiliön (RFC 5652) ja jonka /ByteRange-taulukko nimeää ne tarkat tiedoston alueet, jotka allekirjoitus kattaa pykälän §12.8.1 mukaisesti. Taulukko on luettelo kohdistus- ja pituuspareista, käytännössä kahdesta segmentistä: kaikki ennen /Contents-heksamerkkijonoa ja kaikki sen jälkeen. Allekirjoitusarvo ei voi kattaa itseään, joten tiedosto tiivistetään (hash) tuon aukon ympäriltä

Tästä rakenteesta seuraa koko API:a muovaava tekijä: varmistuksen on tiivistettävä alkuperäiset sarjoitetut tavut sellaisina kuin ne ovat levyllä. Jäsennetty objektimalli on tähän hyödytön, sillä jopa muuttamattoman dokumentin uudelleensarjoitus tuottaa erilaisia tavuja. Siksi HotPDF tekee varmistuksen lähdetiedostoa vasten, josta dokumentti ladattiin, tai toimittamaasi raakojen tavujen TStream-virtaa vasten — ei koskaan sen muistissa olevaa esitystä vasten

Allekirjoituksen metatietojen lukeminen ennen varmistamista

GetLoadedSignatureInfo jäsentää allekirjoitushakemiston ja sen CMS-säiliön koskematta yhteenkään dokumentin tavuun. Tämä tekee siitä sopivan ensimmäisen kutsun silloin, kun halutaan vain näyttää kuka allekirjoitti ja milloin. Allekirjoituskentät on indeksoitu nollasta alkaen lomakekenttien järjestyksessä, ja GetLoadedSignatureFieldCount kertoo kuinka monta niitä on. Palautettu THPDFSignatureInfo-tietue sisältää kentän nimen, /SubFilter-tunnisteen, allekirjoittajan varmenteen yleisen nimen (common name), subject- ja issuer-tunnisteet, sarjanumeron, voimassaolopäivät, allekirjoitusajan (allekirjoitetusta attribuutista jos läsnä, muussa tapauksessa hakemiston /M-tietueesta), tiivistealgoritmin nimen sekä /Reason-, /Location- ja /ContactInfo-merkkijonot. Sen Status-jäsen pysyy arvossa svNotVerified, mikä on rehellinen leima tilalle "jäsennetty, ei varmistettu"

var
  Pdf: THotPDF;
  Info: THPDFSignatureInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('signed-contract.pdf');
    for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
    begin
      Info := Pdf.GetLoadedSignatureInfo(I);
      Writeln('Field:     ', Info.FieldName);
      Writeln('Signer:    ', Info.SignerName);
      Writeln('Issuer:    ', Info.IssuerDN);
      Writeln('Algorithm: ', Info.HashAlgorithm);
      Writeln('SubFilter: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

Kryptografisen tarkistuksen suorittaminen

VerifyLoadedSignatureEx suorittaa täyden varmistuksen tiedostosta ladatulle dokumentille ja palauttaa täytetyn tietueen yhdellä kutsulla: se avaa lähdetiedoston uudelleen, tiivistää /ByteRange-segmentit SignerInfo-tiivistealgoritmilla, vertaa tulosta messageDigest-attribuuttiin (RFC 5652 §5.4) ja suorittaa sen jälkeen RSA-varmistuksen allekirjoitetuille attribuuteille niiden DER SET -uudelleenkoodauksen yli. Jos allekirjoitus ei sisällä allekirjoitettuja attribuutteja, RSA-tarkistus suoritetaan suoraan dokumentin tiivisteen yli. Tuetut allekirjoitukset ovat RSA PKCS#1 v1.5 varustettuna SHA-1-, SHA-256-, SHA-384- tai SHA-512-tiivisteillä, mikä kattaa valtavirran allekirjoitustyökalujen tuottamat adbe.pkcs7.detached- ja ETSI.CAdES.detached-alisuodattimet

var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Valid; signature covers the whole file')
      else
        Writeln('Valid; file was extended after signing');
    svDigestMismatch:
      Writeln('Document bytes changed after signing');
    svSignatureInvalid:
      Writeln('RSA check failed over signed attributes');
    svUnsupportedAlgorithm:
      Writeln('Non-RSA key or unknown digest algorithm');
    svMalformed:
      Writeln('CMS container could not be parsed');
    svSourceUnavailable:
      Writeln('No source bytes; use the TStream overload');
  end;
end;

Kaksi toteutuksen yksityiskohtaa on hyvä tietää, sillä ne selittävät ulospäin mystisiltä näyttäviä epäonnistumisia. Ensinnäkin allekirjoitettujen attribuuttien tarkistus on tarkka koodauksesta: tiedoston sisällä attribuutit on merkitty tunnisteella [0] IMPLICIT, mutta allekirjoitus laskettiin niiden DER SET OF -muodon yli. Siksi varmistaja merkitsee ne uudelleen ennen tiivistämistä, täsmälleen kuten RFC 5652 §5.4 vaatii. Omatekoinen varmistaja, joka tiivistää tavut sellaisenaan kuin ne esiintyvät tiedostossa, hylkää jokaisen oikein allekirjoitetun dokumentin. Toiseksi /Contents on perinteisesti nollatäytteinen varattua tavubudjettia varten, joten varmistaja katkaisee DER-blobin sen ulomman SEQUENCE-osan todelliseen pituuteen ennen jäsentämistä; roskalta näyttävät loppunollat ovat normaaleja, eivät korruptiota. Samaan ASN.1-jäsennyksen vaarojen perheeseen, varmenteen tuontipuolella, pureudutaan artikkelissa PKCS#12- ja ASN.1-tietoturvan vahvistamisesta HotPDF-komponentissa

Mitä voimassa oleva allekirjoitus todellisuudessa takaa?

svValid tarkoittaa täsmälleen tätä: /ByteRange-taulukon nimeämät tavut tiivistyvät arvoon, jonka allekirjoittaja allekirjoitti, ja allekirjoitus on varmistettu CMS-säiliöön upotetun varmenteen julkisella avaimella. Se takaa tavujen eheyden ja avainsidonnan, ei mitään muuta. Varmenteiden ketjutus ja luottamuksen varmistaminen jäävät HotPDF:n varmistajan ulkopuolelle: se ei käy läpi ketjua juurivarmenteeseen saakka, tarkista kumottuja varmenteita tai kysy tietoja luottamusvarastoista. Hyökkääjän itse allekirjoittama varmenne, jolla on allekirjoitettu muokattu dokumentti uudelleen, varmistuu tilaksi svValid, koska matematiikka on sisäisesti johdonmukaista. Se, onko allekirjoittaja se, joka hän väittää olevansa, ja pitäisikö häneen luottaa, on käytäntöpäätös, joka kuuluu erilliselle kerrokselle — olipa se sitten organisaatiosi varmenteiden sallittujen luettelo, Windowsin varmennesäilö tai varmennusviranomainen

CoversWholeDocument-lippu suojaa hienovaraisemmalta aukolta. Allekirjoitus kattaa aina vain oman /ByteRange-alueensa, ja PDF:n inkrementaalinen päivitysmekanismi sallii sisällön lisäämisen allekirjoituksen jälkeen mitätöimättä sitä. Tämä on suunniteltu ominaisuus, jonka avulla moniallekirjoituksen työnkulut toimivat. Lippu lasketaan varmistuksen aikana ja se on tosi vain silloin, kun kaksi segmenttiä ja /Contents-aukko kattavat koko tiedoston. Kun svValid saapuu siten, että CoversWholeDocument on epätosi, allekirjoitettu versio on ehjä, mutta tiedosto sisältää myöhempiä lisäyksiä, ja työnkulkusi tulisi päättää, sallitaanko näiden lisäysten tekemät muutokset

Virrasta ladatut ja salatut dokumentit tarvitsevat omat lähdetavut

Parametriton VerifyLoadedSignature ja VerifyLoadedSignatureEx riippuvat siitä, että komponentti muistaa, mistä tiedostosta dokumentti on peräisin. Jos lataat dokumentin virrasta (stream), uudelleenavattavaa tiedostonimeä ei ole; sama pätee salattujen dokumenttien salasanan uudelleenlatauspolun jälkeen, eli työnkulkuun, jota käsitellään artikkelissa AES-256-PDF-salauksesta HotPDF-komponentilla. Molemmissa tapauksissa tiedostopohjaiset ylikuormitukset palauttavat arvon svSourceUnavailable arvailun sijaan. Korjaus tähän on TStream-ylikuormitus, jonka avulla voit välittää alkuperäiset raa'at tavut mistä tahansa, missä niitä säilytät — tallessa olevasta tiedostosta, muistipuskurista tai tietokannan blob-kentästä

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Stream-loaded document: the component holds no source
  // file name, so supply the original bytes yourself.
  Src := TFileStream.Create('signed-contract.pdf',
    fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignature(0, Src, Info);
    if Status <> svValid then
      Writeln('Verification failed: ', Ord(Status));
  finally
    Src.Free;
  end;
end;

Sen raportointi, mitä ei voida varmistaa

Varmistaja, joka tuntee vain tilat "validi" ja "epävalidi", raportoi väärin dokumenteista, joita se ei vain ymmärrä. Siksi tilan luettelointi erottelee tapaukset, jotka käyttöliittymäsi tulisi erottaa toisistaan. svDigestMismatch tarkoittaa, että dokumentin tavut ovat muuttuneet allekirjoittamisen jälkeen, mikä on perinteinen peukalointisignaali. svSignatureInvalid tarkoittaa, että tavut tiivistyvät oikein, mutta RSA-tarkistus epäonnistui, mikä viittaa korruptoituneeseen tai väärennettyyn allekirjoitusarvoon. svUnsupportedAlgorithm on rehellinen vastaus ECDSA-avaimille ja tuntemattomille tiivisteille: allekirjoitus voi olla täysin kunnossa, HotPDF ei pysty varmistamaan sitä, ja sen raportointi "epävalidiksi" leimaisi ehjän dokumentin syyttä. svMalformed merkitsee CMS-säiliön, jota ei voitu jäsentää lainkaan. Porttityyppisissä tarkistuksissa VerifyAllLoadedSignatures palauttaa arvon tosi vain silloin, kun vähintään yksi allekirjoituskenttä on olemassa ja jokainen niistä varmistuu tilaksi svValid. Tämä on kätevä yksittäinen totuusarvo arkiston vastaanottoputkelle, joka hylkää kaiken muun

Allekirjoituksen varmistus, PAdES-allekirjoitus, AES-256-salaus ja ladatun dokumentin muokkaus-API toimitetaan kaikki samassa natiivissa VCL-kirjastossa Delphille ja C++Builderille ilman ulkoisia DLL-riippuvuuksia. Täydellinen ominaisuusluettelo ja tuetut IDE-versiot ovat saatavilla HotPDF Component -tuotesivulta