CMS-säiliön sisällä oleva ECDSA-signatureValue on DER-SEQUENCE { INTEGER r, INTEGER s }. Windowsin CNG-funktio BCryptVerifySignature ei hyväksy kumpaakaan niistä: se haluaa IEEE P1363 -muotoisen kiinteäleveyksisen r || s-arvon, ilman tageja ja ilman pituuksia. HotPDF, natiivi VCL PDF -komponentti Delphille ja C++Builderille, muuntaa näiden kahden välillä tiukkojen DER-sääntöjen mukaisesti ennen avaimen tuontia
Vika, jonka tämä estää, on tarkka ja lannistava. Acrobat avaa dokumentin ja näyttää vihreän valintamerkin. Oma tarkistimesi, kulkien samat tavut läpi, palauttaa virheellisen, tai CNG antaa takaisin arvon STATUS_INVALID_SIGNATURE ilman lisäselitystä. Allekirjoituksessa ei ole mitään vikaa. Se, mikä on vikana, on se, että noin seitsemänkymmentä tavua ASN.1:tä välitettiin API:lle, joka odotti kuuttakymmentäneljää tavua raakaa kokonaislukua, ja epäsuhta on näkymätön ellei tiedä etsiä sitä
Miksi BCryptVerifySignature hylkää pätevän ECDSA-allekirjoituksen?
Koska kutsun kaksi puolta puhuvat eri allekirjoituskoodauksia, eikä kumpikaan ilmoita siitä. ISO 32000-1 §12.8 sanoo, että allekirjoitussanakirja kantaa CMS-blobia kohdassa /Contents; RFC 5652 §5.3 sanoo, että jokaisen SignerInfo:n signatureValue on OCTET STRING, jonka sisältö on mitä tahansa allekirjoitusalgoritmi määrittää. ECDSA:lle tuo sisältö on SEC 1 DER -rakenne: SEQUENCE, joka pitää sisällään kaksi INTEGERia. Se on tarkoituksella vaihtuvamittainen, koska r ja s ovat kokonaislukuja ja DER poistaa johtavat nollaoktetit kokonaisluvuista
IEEE P1363 ottaa vastakkaisen näkökannan. Se määrittelee allekirjoituksen kahden koordinaatin ketjutuksena, kumpikin vasemmalta täytettynä nollilla täsmälleen käyräkentän tavuleveyteen asti. P-256-allekirjoitus on aina 64 tavua. Saman allekirjoituksen DER-koodaus on normaalisti 70 tai 71 tavua ja voi olla mitä tahansa noin 8:sta 72:een. Anna DER-muoto BCryptVerifySignature-funktiolle, ja pelkkä pituustarkistus jo tuhoaa kutsun, minkä vuoksi HotPDF normalisoi ennen tarkistusta eikä sen jälkeen
uses
HPDFECDSA;
// Converts a CMS signatureValue into the fixed-width form CNG expects.
// ARaw comes back as 64 bytes for P-256, 96 for P-384, 132 for P-521.
function ToP1363(const ADerSig: TBytes; ACurve: THPDFECDSACurve;
out ARaw: TBytes): Boolean;
begin
Result := HPDFECDSANormalizeSignature(ADerSig, ACurve, eseDER, ARaw);
end;
DER-säännöt, joita allekirjoitusjäsentäjä ei saa lieventää
Jokainen tässä lueteltu hylkäys on hylkäys, jonka HotPDF tekee tarkoituksella, ja jokainen niistä sulkee polun, jonka anteeksiantavainen jäsentäjä jättäisi auki. Kiusaus muunninta kirjoitettaessa on löytää kaksi INTEGER-solmua, kopioida niiden sisältö ja jatkaa eteenpäin. Se toimii hyvin muodostetulla syötteellä ja hyväksyy hiljaa joukon muunneltavia uudelleenkoodauksia vihamielisellä syötteellä. Niinpä HPDFECDSANormalizeSignature hylkää negatiivisen kokonaisluvun, eli minkä tahansa r:n tai s:n, jonka ensimmäisen sisältöoktetin ylin bitti on asetettu, koska pätevä ECDSA-skalaari on positiivinen. Se hylkää arvon, joka on kokonaan nolla, koska r = 0 tai s = 0 ei koskaan ole laillinen allekirjoitus. Se hylkää tarpeettoman johtavan nollaoktetin: X.690 §8.3 sallii täsmälleen yhden, ja vain silloin, kun seuraava oktetti muuten lukeutuisi negatiiviseksi, joten 00 jota seuraa oktetti alle 0x80 on uudelleenkoodaus, ei allekirjoitus. Se hylkää ei-minimaalisen pituusotsikon, koska X.690 §10.1 vaatii määrätyn muodon koodattuna mahdollisimman harvalla oktetilla, ja pitkän muodon pituus, joka olisi voinut olla lyhyt muoto, on eri tavumerkkijono, joka kantaa samaa merkitystä. Se hylkää kokonaisluvun, joka on leveämpi kuin käyräkoordinaatin koko, koska tuo arvo ei voi olla kenttäalkio. Ja se hylkää minkä tahansa perässä olevan solmun s:n jälkeen, samoin kuin ulomman SEQUENCE:n, jonka kokonaispituus ei täsmää koko blobin pituuteen
Nuo kaksi viimeistä ovat merkittävämpiä kuin miltä ne näyttävät. Perässä olevat tavut SEQUENCE:n jälkeen ovat klassinen allekirjoituksen muunneltavuustemppu: liitä roskaa perään, ja lepsu tarkistin sanoo silti pätevä, vaikka tavumerkkijono, jonka se validoi, ei ole se tavumerkkijono, joka allekirjoitettiin. Sama vaisto ajaa ASN.1-pituuden vahvistamista, joka kuvataan muistiossa PKCS#12-jäsennyksestä, ja sama vaisto on tässäkin. Tarkistuspolulla hyväksytty rakenne, jota mikään vaatimustenmukainen allekirjoittaja ei koskaan tuottanut, on vika, ei kohteliaisuus
Koordinaattileveys kuuluu käyrälle, ei allekirjoitukselle
HotPDF johtaa tulosteen leveyden nimetystä käyrän OID:sta, ei koskaan juuri jäsentämänsä DER:n pituudesta. Tämä on muunnoksen toinen puolisko, ja se puolisko, jonka on helppo saada hienovaraisesti väärin. RFC 5480 §2.1.1 tunnistaa käyrän sertifikaatin SubjectPublicKeyInfo-parametreissa, ja HPDFECDSACurveFromOID kartoittaa kolme OID:tä, joita HotPDF tukee: 1.2.840.10045.3.1.7 P-256:lle, 1.3.132.0.34 P-384:lle, ja 1.3.132.0.35 P-521:lle. HPDFECDSACoordinateSize palauttaa sitten 32, 48 tai 66 tavua, ja P1363-puskuri on kaksi kertaa se: 64, 96 tai 132. Jokainen dekoodattu kokonaisluku tasataan oikealle omaan puoliskoonsa, joten lyhyt r täytetään nollilla vasemmalta eikä siirretä. P-521 on se, joka nappaa ihmiset, koska 521 bittiä on 65,125 tavua ja pyöristyy ylöspäin 66:een, mikä antaa 132-tavuisen allekirjoituksen, jota mikään kahden potenssin intuitio ei olisi ennustanut. Julkinen avain kulkee mukana pakkaamattomana EC-pisteenä RFC 5480 §2.2:n mukaisesti, joka on 0x04 ja sen jälkeen X ja Y, joten HotPDF tarkistaa, että se on täsmälleen 1 + 2 * CoordinateSize tavua ja alkaa arvolla 0x04 ennen kuin se koskettaa CNG:tä
var
Digest, SigDER, PublicPoint: TBytes;
Curve: THPDFECDSACurve;
Res: THPDFECDSAVerifyResult;
begin
// secp256r1, taken from the certificate SubjectPublicKeyInfo parameters
Curve := HPDFECDSACurveFromOID('1.2.840.10045.3.1.7');
// PublicPoint must be $04 || X || Y, so 1 + 2 * 32 = 65 bytes for P-256
Res := HPDFECDSAVerifyDigest(Digest, SigDER, PublicPoint, Curve, eseDER);
case Res of
evrValid:
Memo1.Lines.Add('signature verifies');
evrInvalid:
Memo1.Lines.Add('signature does not match the digest');
evrMalformed:
Memo1.Lines.Add('DER encoding or public point rejected');
evrUnsupported:
Memo1.Lines.Add('curve or algorithm not supported here');
evrProviderUnavailable:
Memo1.Lines.Add('bcrypt.dll or the curve provider is missing');
evrProviderError:
Memo1.Lines.Add('CNG returned an unexpected status');
end;
end;
Huomaa viimeinen parametri. HPDFECDSAVerifyDigest hyväksyy myös arvon eseP1363 kutsujille, joilla on jo kiinteäleveyksinen allekirjoitus, laitteistotokenilta tai etäallekirjoituspalvelusta, joka palauttaa raa'an r || s-arvon. Tuo polku pakottaa silti pituustarkistuksen ja ei-nolla-tarkistuksen molemmille puoliskoille, joten oikean kokoinen puskuri täynnä nollia hylätään sen sijaan, että se välitettäisiin tarjoajalle sellaisenaan
Miksi geneerinen ECDSA-algoritminimi epäonnistuu vanhemmilla Windows-versioilla?
Koska geneerinen nimi on uudempi kuin käyttöönottopohja, johon toimitat. CNG paljastaa algoritmitunnisteen ECDSA, joka päättelee käyrän tuodusta avaimesta, ja se on siisti tapa kirjoittaa tämä koodi, mutta BCryptOpenAlgorithmProvider taataan ratkaisevan sen vain uudemmilla Windows-versioilla. Vanhemmalla koneella avauskutsu epäonnistuu, tarjoajakahva pysyy nil-arvossa, ja jokainen ECDSA-tarkistus sovelluksessasi raportoi tukemattomaksi allekirjoituksen, joka on täysin kunnossa. HotPDF välttää tämän kuilun avaamalla käyräkohtaiset tunnisteet sen sijaan. Se ratkaisee ECDSA_P256-, ECDSA_P384- ja ECDSA_P521-tunnisteet kerran, välimuistittaa yhden tarjoajakahvan käyrää kohti ja sulkee ne yksikön finalisoinnissa. Jokainen tarkistus tekee sitten vain halvan työn: tuo väliaikaisen julkisen avaimen ECCPUBLICBLOB-lohkosta, kutsuu BCryptVerifySignature-funktiota, tuhoaa avaimen. Ei toistuvaa LoadLibrary-kutsua, ei toistuvaa GetProcAddress-kutsua, ei tarjoajan avaamista ja sulkemista jokaista allekirjoitusta kohti. Muutaman sadan dokumentin eräajotarkistus tuntee eron, ja niin tuntee myös palveluprosessi, joka muuten kierrättäisi tarjoajakahvoja kuormituksen alla
Tulostuskoodit pysyvät rehellisinä erosta. evrProviderUnavailable tarkoittaa, että kone ei pystynyt antamaan HotPDF:lle tarjoajaa; evrInvalid tarkoittaa, että CNG vastasi arvolla STATUS_INVALID_SIGNATURE. Näiden kahden yhdistäminen yhdeksi virheeksi on tapa, jolla käyttöönotto-ongelma raportoidaan virheellisesti väärennettynä dokumenttina. Sama erottelu ympäristövirheen ja kryptografisen virheen välillä kulkee läpi CNG- ja CAPI-käsittelyn allekirjoituspuolella, käsitelty artikkelissa sertifikaattivaraston allekirjoituksesta ja tavujärjestyksestä
Mikä sertifikaatti allekirjoitti tämän? SignerIdentifier on kaksi eri asiaa
RFC 5652 §5.3 tekee SignerIdentifier:sta CHOICE-tyypin, ja tarkistin, joka käsittelee vain yhtä haaraa, tarkistaa hiljaa väärää avainta vastaan. Ensimmäinen haara on issuerAndSerialNumber, SEQUENCE, joka pitää sisällään myöntäjän nimen raakana DER:nä ja sarjanumero-INTEGERin, ja sen täsmäys on tavuvertailu jokaista CMS certificates-joukon sertifikaattia vastaan. Toinen haara on [0] subjectKeyIdentifier, implisiittisesti tagattu OCTET STRING, ja sen täsmäys vaatii kaivautumista sertifikaattiin sen otsikkokenttien vertailun sijaan
Kaivautumisessa on kerros, joka yllättää ihmiset. Avaintunniste elää X.509v3-laajennuksessa, joten HotPDF kulkee läpi tbsCertificaten [3]-laajennuskenttää, löytää laajennuksen, jonka OID on 2.5.29.14, ohittaa valinnaisen kriittisen BOOLEAN-arvon ja ottaa extnValue-OCTET STRING:n. Tuo oktettijono ei ole tunniste. RFC 5280 §4.2.1.2:n mukaan sen sisältö on itsessään DER, ja KeyIdentifier-tyyppi on toinen OCTET STRING, joten sinun täytyy jäsentää toiseen kertaan päästäksesi todellisiin tavuihin. Pysähdy yksi kerros liian aikaisin, ja vertaat 22-tavuista kääretyötä 20-tavuiseen tunnisteeseen, mikään sertifikaatti ei koskaan täsmää, ja tarkistin turvautuu mihin tahansa heuristiikkaan, jonka kirjoitit seuraavaksi, mikä on todellinen vaara. Joukon ensimmäisen sertifikaatin ottaminen on houkutteleva oikotie, ja se on väärin aina, kun CMS kantaa ketjua, mikä on useimmiten totta, koska lehteä ei vaadita tulemaan ensimmäisenä. HotPDF hyväksyy täsmäämättömän sertifikaatin vain silloin, kun säiliö pitää sisällään täsmälleen yhden; kun useita sertifikaatteja on läsnä, tarkka SignerIdentifier-täsmäys on pakollinen. Tiivisteen tarkistaminen välikertoimen sertifiointielimen julkista avainta vastaan ei tuota ystävällistä virhettä, se tuottaa itsevarman virheellisen dokumentille, joka on kunnossa
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('signed.pdf') > 0 then
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
if Pdf.VerifyLoadedSignatureEx(I, Info) = svValid then
Memo1.Lines.Add(Format('%s: %s %s over %s, signer %s',
[String(Info.FieldName), String(Info.PublicKeyAlgorithm),
String(Info.CurveName), String(Info.HashAlgorithm),
String(Info.SignerName)]))
else
Memo1.Lines.Add(Format('%s: not valid', [String(Info.FieldName)]));
finally
Pdf.Free;
end;
end;
THPDFSignatureInfo.CurveName raportoi arvon P-256, P-384 tai P-521, joten auditointiloki tallentaa, mitä käyrää todella käytettiin pelkän sanan ECDSA sijaan. Tähän kutsuun liittyvä dokumenttitason koneisto, erityisesti se, miten /ByteRange-segmentit tiivistetään ja miksi tiiviste täytyy laskea tiedoston yli eikä jäsennetyn objektipuun yli, käsitellään artikkelissa PDF-digitaalisten allekirjoitusten tarkistamisesta
Mitä tämä ei anna sinulle
Vihreä tulos HPDFECDSAVerifyDigest-funktiolta vastaa vain yhteen kysymykseen: nämä tavut allekirjoitti tätä julkista avainta vastaava yksityinen avain. Se ei kerro mitään siitä, kuuluuko tuo avain kenellekään, johon sinun pitäisi luottaa. Ketjun rakentaminen luottamusankkuriin, peruutus CRL:n tai OCSP:n kautta, ja politiikkatarkistukset ovat erillistä työtä, ja mikä tahansa tuote, joka raportoi pätevän allekirjoituksen ilman niitä, raportoi vähemmän kuin käyttäjä olettaa. Sertifikaatin voimassaoloajat tuodaan erikseen esiin THPDFSignatureInfo:ssa juuri tästä syystä: allekirjoitus voi tarkistua kryptografisesti kunnossa olevaksi, vaikka sen tehnyt sertifikaatti vanheni kaksi vuotta sitten. Käyrätuki on myös tarkoituksella kapea. Kolmea NIST-alkulukukäyrää käsitellään, ja allekirjoitus minkä tahansa muun käyrän yli palauttaa tukemattoman arvauksen sijaan. CNG-polku on vain Windows-ympäristössä, mikä on oikea kompromissi VCL-komponentille mutta kannattaa mainita ennen kuin suunnittelet sen ympärille alustariippumatonta palvelua. Ja tiukkuus ei ole konfiguroitavissa: ei ole lepsua tilaa, joka hyväksyisi ei-minimaalisen DER-pituuden, koska joku vanha allekirjoittaja tuotti sellaisen. Jos kohtaat tällaisen tiedoston tuotannossa, rehellinen vastaus on kirjata se ja jäljittää tuottaja, ei laajentaa jäsentäjää, kunnes tiedosto läpäisee
Tässä kuvattu ECDSA-tarkistuspolku toimitetaan osana vakiota HotPDF-komponenttia Delphille ja C++Builderille, yhdessä RSA PKCS#1 v1.5- ja RSA-PSS-polkujen sekä täydellisen allekirjoitustietotietueen kanssa; tuotesivu sisältää täydellisen digitaalisen allekirjoituksen viitteen