Techninis straipsnis

ISO/TS 32002 ECDSA ir EdDSA politikos patikra PDFium Delphi

PDFium Component for Delphi kiekvieną ECDSA ir EdDSA PDF parašą tikrina prieš ISO/TS 32002 algoritmų profilį ir verdiktą praneša TPadesSignatureValidation.AlgorithmPolicyStatus. Praeiti gali tik P-256, P-384, P-521, trys Brainpool r1 kreivės, Ed25519 ir Ed448, kiekviena su derančia santrauka, o nesutapimas pakelia ppeiSignatureAlgorithmMismatch. Ta politika svarbiau, nei skamba. Parašas ant brainpoolP160r1 arba P-256 raktas, pasirašantis SHA-512 santrauką, matematikos lygiu gali patikrintis puikiai, tad Windows CryptoAPI praneša parašo reikšmę kaip gerą, o griežtas PDF 2.0 validatorius failą atmeta. Politikos tikra tą plyšį uždaro, ir ji sąmoningai atskirta nuo klausimo, ar parašo baitai kriptografiškai teisingi

Ką ISO/TS 32002 iš tikrųjų leidžia eliptinių kreivių parašams?

ISO/TS 32002 PDF parašams leidžia lygiai šešias ECDSA kreives ir dvi EdDSA schemas, ir kiekvieną kreivę suriša su santraukų dydžiais, kuriuos ji gali nešti. PDFium Component tą lentelę užkoduoja PadesCurveDigestAllowed, surištoje su kreivės OID iš pasirašytojo sertifikato. NIST kreivės griežtos: santrauka turi būti to paties bitų pločio kaip kreivė, SHA-2 arba SHA-3. Brainpool kreivės laisvesnės ir priima savąjį plotį ar bet ką platesnį:

  • P-256 (1.2.840.10045.3.1.7): tik SHA-256 arba SHA3-256
  • P-384 (1.3.132.0.34): tik SHA-384 arba SHA3-384
  • P-521 (1.3.132.0.35): tik SHA-512 arba SHA3-512
  • brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7): bet kuri SHA-2 arba SHA-3 santrauka nuo 256 iki 512 bitų
  • brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11): 384 ar 512 bitų SHA-2 / SHA-3
  • brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13): tik SHA-512 arba SHA3-512
ISO TS 32002 algoritmų profilio matrica PDFium Component: P-256, P-384 ir P-521 priima tik derančius santraukų pločius PadesCurveDigestAllowed, brainpoolP256r1 priima 256 iki 512 bitų, brainpoolP384r1 — 384 ir 512, Ed25519 ir Ed448 deklaruoja SHA-512 ir SHAKE256 su ilgiu 512, o nesutapimai kelija ppeiSignatureAlgorithmMismatch, kuomet už profilio ribų esančios kreivės grąžina pcsUnsupported
Praeiti gali šešios ECDSA kreivės ir dvi EdDSA schemos, ir kiekviena kreivė surišta su santraukų pločiais, kuriuos gali nešti; visa kita neteisėta arba nepalaikoma, niekada tyliai priimta

EdDSA neturi nei kreivės pasirinkimo, nei santraukos pasirinkimo, ir būtent todėl jos taisyklės — apie koduotę, o ne stiprumą. Pagal RFC 8419 Ed25519 SignerInfo turi deklaruoti SHA-512 kaip savą digestAlgorithm be parametrų, o Ed448 SignerInfo, pasirašytųjų atributų kelyje, kurį visada naudoja PAdES, turi deklaruoti id-shake256-len (2.16.840.1.101.3.4.2.18) su INTEGER parametru lygiai 512. Abiems schemoms parašo AlgorithmIdentifier ir sertifikato viešojo rakto AlgorithmIdentifier turi nešti apskritai jokių parametrų. Gamintojas, ten parašantis NULL — įprotį, į kurį RSA koduotojai išmokino daugelį ASN.1 bibliotekų, — pagamina nederantį standartui parašą, nors raktas ir parašo reikšmė geros

Kaip PDFium Component ištraukia algoritmų trejetą iš CMS

Pats PDFium šio klausimo atsakyti negali, nes jo viešasis parašų API skaito parašų žodyną, bet nei CMS patikrina, nei atveria pasirašytojo sertifikato kreivę. PAdES patikros sluoksnis, pastatytas ant PDFium, todėl CMS SignedData (RFC 5652) išskaido pats. InspectPadesSignatureAlgorithm skaito pirmojo SignerInfo digestAlgorithm ir signatureAlgorithm, tada randa pasirašytojo sertifikatą ir skaito jo SubjectPublicKeyInfo, kad gautų rakto algoritmą ir kreivę. Sertifikato paieška sąmoningai ribota: CMS certificates rinkinyje apžiūrima ne daugiau nei 64 sertifikatai, atitikimas — tikslus baitų palyginimas iš issuerAndSerialNumber paimto leidėjo ir serijos numerio, o kodas į „vienintelį ten esantį sertifikatą“ grįžta tik tada, kai rinkinyje lygiai vienas išskaidomas sertifikatas. Paimti pirmą EC sertifikatą iš nesurikiuoto rinkinio būtų paprasta, ir CA sertifikatas tada nuspręstų, kurią kreivę pasirašytojas, kaip teigiama, naudojo

Kaip PDFium Component tiria PDF parašo algoritmų trejetą: InspectPadesSignatureAlgorithm skaito pirmojo SignerInfo CMS digestAlgorithm ir signatureAlgorithm, pasirašytojo sertifikatą prisega tiksliu issuerAndSerialNumber atitikimu tarp ne daugiau nei 64 kandidatų, skaito SubjectPublicKeyInfo dėl kreivės, o EvaluatePadesSignatureAlgorithm grąžina AlgorithmPolicyStatus
Pats PDFium nei CMS patikrina, nei atveria pasirašytojo kreivę, tad PAdES sluoksnis išskaido SignedData ir įraše palieka kiekvieną žalią OID, kad atmetimas būtų paaiškinamas
uses
  PDFium, FPdfPades;

const
  StatusNames: array[TPadesCryptoStatus] of string =
    ('not checked', 'valid', 'invalid', 'unsupported', 'indeterminate');

var
  Pdf: TPdf;
  R: TPadesValidationResult;
  A: TPadesSignatureAlgorithmInfo;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      A := R.Signatures[I].AlgorithmInfo;
      Writeln('Signature ', I);
      Writeln('  digestAlgorithm    : ', string(A.DigestAlgorithmOid));
      Writeln('  signatureAlgorithm : ', string(A.SignatureAlgorithmOid));
      Writeln('  public key / curve : ', string(A.PublicKeyAlgorithmOid),
        ' / ', string(A.CurveOid));
      Writeln('  policy             : ',
        StatusNames[R.Signatures[I].AlgorithmPolicyStatus]);
    end;
    if ppeiSignatureAlgorithmMismatch in R.Issues then
      Writeln('At least one signature violates the ISO/TS 32002 profile');
  finally
    Pdf.Free;
  end;
end;

TPdf.ValidatePades politiką taiko kaip savo atitikties pravažiavimo dalį, pradėdamas nuo CMS viduje rasto sertifikato, o TPdf.ValidatePadesTrust ją pakartoja prieš pasirašytojo sertifikatą, kurį Windows CryptoAPI realiai naudojo patikrinimui, tad CryptoAPI praneštas sertifikatas turi paskutinį žodį. Kiekviena žalia įvestis atsiduria TPadesSignatureAlgorithmInfo, įskaitant DigestParametersPresent, DigestParameterBits, SignatureParametersPresent ir PublicKeyParametersAreNamedCurve, tad atmetimas visada paaiškinamas iš įrašo, o ne iš žurnalo eilutės

Kodėl P-256 parašas su SHA3-256 nepraeina politikos?

P-256 parašas nepraeina PDFium Component politikos, kai tik CMS digestAlgorithm ir ECDSA signatureAlgorithm numanoma santrauka nesutampa, net jei abi atskiros kreivei priimtinos. EvaluatePadesSignatureAlgorithm pirmiausia ecdsa-with-SHA256, ecdsa-with-SHA3-256 ir jų brolius susieja su santrauka, palygina su deklaruotąja digestAlgorithm ir grąžina pcsInvalid bet kokiu skirtumu, dar prieš sprendžiant iš kreivių lentelės. Atvejis realus: pasirašymo įrankis persijungia į SHA3-256, bet palieka įkaltą ecdsa-with-SHA256 identifikatorių, ir rezultatas — failas, kurio nė vienas derantis patikrintuvas nesugebės aiškinti nuosekliai. Funkcija vieša, tad matricą galima prisegti unit teste nepastatant PDF:

var
  Info: TPadesSignatureAlgorithmInfo;
begin
  Info := Default(TPadesSignatureAlgorithmInfo);
  Info.Family := psafEcdsa;
  Info.PublicKeyAlgorithmOid := '1.2.840.10045.2.1';      // id-ecPublicKey
  Info.PublicKeyParametersPresent := True;
  Info.PublicKeyParametersAreNamedCurve := True;
  Info.CurveOid := '1.2.840.10045.3.1.7';                 // P-256
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.8';    // SHA3-256
  Info.SignatureAlgorithmOid := '2.16.840.1.101.3.4.3.10'; // ecdsa-with-SHA3-256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsValid);

  Info.SignatureAlgorithmOid := '1.2.840.10045.4.3.2';    // ecdsa-with-SHA256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsInvalid); // santraukos nesutapimas

  Info.CurveOid := '1.3.36.3.3.2.8.1.1.1';                // brainpoolP160r1
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.1';    // SHA-256, dabar suderinta
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // kreivės profilyje nėra
end;

Kreivės koduotė gauna tą pačią griežtumą. RFC 5480 §2.1.1 leidžia ECParameters būti vardinės kreivės OID, netiesiogine kreive (NULL) ar visu aiškiu parametrų rinkiniu, o PKIX profiliai reikalauja vardinės formos. PDFium Component grąžina pcsInvalid, kai id-ecPublicKey sertifikatas neša jokių parametrų, netiesioginius parametrus ar aiškius parametrus, nes aiškūs parametrai leidžia puolėjui apibūdinti kreivę, kuri tik panaši į standartinę. Teisingai pavadinta kreivė, tiesiog nesanti ISO/TS 32002 sąraše, pavyzdžiui aukščiau minėtas brainpoolP160r1 ar secp256k1, vietoj to gauna pcsUnsupported

Invalid, unsupported ar indeterminate: statusą skaitome sąžiningai

Trys ne validūs AlgorithmPolicyStatus statusai reiškia skirtingus dalykus, ir jų suglaudimas į vieną „nepavyko“ kibirą išmeta informaciją, kurios reikia auditoriams. pcsInvalid reiškia, jog atpažintas algoritmų derinys yra kreivas arba nesutampa; jis prideda ppeiSignatureAlgorithmMismatch prie TPadesValidationResult.Issues ir varo bendrąjį IntegrityStatus iki pcsInvalid, tad IsCryptographicallyValid grąžina False net tada, kai CMS parašo reikšmė sutvarkyta. pcsUnsupported reiškia, jog kreivė ar santrauka yra už to, ką profilis įvardija — tai gebėjimų rezultatas, ne įrodymas apie kišimąsi. pcsIndeterminate reiškia, jog pasirašytojo sertifikato nepavyko prisegti — dažniausiai CertificateSet su keliais kandidatais ir be tikraus issuerAndSerialNumber atitikimo, tad kodas atsisako spėti kreivę; nuo v3.124.0 jis taip pat žymi RSA parašą virš SHA-1 ar 112 bitų santraukos, tokios kaip SHA-224, kurios dabartinei validacijai jau nesutariama. Tas pats skirstymas tinka EdDSA mašinoje, kurios CryptoAPI nesugeba patikrinti Ed25519 ar Ed448: CmsSignatureStatus lieka pcsUnsupported, o AlgorithmPolicyStatus vis dar gali būti pcsValid, nes koduotė buvo teisinga, o nebuvęs buvo tik patikrintuvas. Jei medžiojate atmetimą iš Adobe ar DSS pagrindo validatoriaus, gidas, kodėl validatoriai atmeta PAdES parašus dengia kitas dažnas priežastis

EvaluatePadesSignatureAlgorithm sprendimų kelias PDFium Component: santraukos nesutapimas stato pcsInvalid ir ppeiSignatureAlgorithmMismatch, ISO TS 32002 profiliui nepriklausanti vardinė kreivė, tokia kaip brainpoolP160r1, stato pcsUnsupported, neprisegamas pasirašytojo sertifikatas stato pcsIndeterminate, o nuo v3.124.0 RSA šaka taiko ETSI TS 119 312 santraukų rinkinius, palikdama rakto dydį programai
Trys ne validūs statusai reiškia skirtingus dalykus: invalid yra sugadinto derinio įrodymas, unsupported — gebėjimų rezultatas, o indeterminate reiškia, jog kodas atsisako spėti
var
  Pdf: TPdf;
  Options: TPadesTrustValidationOptions;
  R: TPadesValidationResult;
  S: TPadesSignatureValidation;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    Options := TPadesTrustValidationOptions.Default; // be tinklo, be atšaukimo patikros
    R := Pdf.ValidatePadesTrust(Options);
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      S := R.Signatures[I];
      if S.IsDocumentTimeStamp then
        Continue;
      case S.AlgorithmPolicyStatus of
        pcsInvalid:       Writeln(I, ': reject, algorithm combination is invalid');
        pcsUnsupported:   Writeln(I, ': manual review, curve or digest outside the profile');
        pcsIndeterminate: Writeln(I, ': manual review, signer not identified or digest no longer agreed');
        pcsValid:
          if S.AlgorithmInfo.Family = psafRsa then
            Writeln(I, ': RSA digest suite accepted, check key size yourself')
          else
            Writeln(I, ': EC/EdDSA profile satisfied');
      end;
    end;
    Writeln('Cryptographically valid: ', R.IsCryptographicallyValid);
  finally
    Pdf.Free;
  end;
end;

Ką pcsValid negarantuoja?

AlgorithmPolicyStatus = pcsValid liudija tik tai, jog ECDSA ar EdDSA parašas naudoja patvirtintą kreivę su suderinta, teisingai užkoduota santrauka, o RSA parašas — santrauką iš dabartinio rinkinio; apie tai, ar parašo reikšmė teisinga, jis nesako nieko. Iki v3.124.0 EvaluatePadesSignatureAlgorithm RSA šaka buvo sąmoningai plati: bet koks signatureAlgorithm po PKCS #1 šaka 1.2.840.113549.1.1.* grąžindavo pcsValid, įskaitant paveldėtąjį sha1WithRSAEncryption. Nuo PDFiumPas v3.124.0 RSA šaka taiko ETSI TS 119 312 parašų rinkinius. MD2, MD4 ir MD5 santraukos — pcsInvalid. SHA-1 ir 112 bitų santraukos, tokios kaip SHA-224, — pcsIndeterminate, tad SHA-1 parašas išlaiko bendrąjį vientisumo rezultatą ir pažymimas peržiūrai, vietoj to, kad būtų atmetamas. digestAlgorithm, skiriantis nuo parašo algoritmo fiksuotos santraukos, pavyzdžiui sha256WithRSAEncryption virš SHA-1 santraukos, ar RSA parašo algoritmas ant ne RSA pasirašytojo rakto — pcsInvalid, pranešamas kaip ppeiSignatureAlgorithmMismatch ir krintantis su vientisumu. Nerandamas pasirašytojo sertifikatas duoda pcsIndeterminate, kaip ir anksčiau ECDSA atveju, o neatpažintos santraukos ar ne parašo RSA OID — pcsUnsupported. Modulio ilgis vis dar netikrinamas, PSS parametrai čia nevaliduojami (RFC 4055 RSASSA-PSS-params straipsnis pasakoja, kaip jie užkoduoti pasirašymo pusėje), ir SHA-1 ar MD5 santraukos papildomai kelia atskirą ppeiBadDigestAlgorithm problemą. Panašiai parašo matematika, sertifikatų grandinė ir atšaukimas lieka CmsSignatureStatus, CertificateTrustStatus ir RevocationStatus darbas, kurie atkeliauja iš Windows CryptoAPI. Elkitės su pcsValid kaip su „algoritmų profilis laikosi“, niekada kaip su „šis raktas pakankamai stiprus“

Delphi programai, priimančiai pasirašytas PDF sąskaitas, sutartis ar archyvo paketus, praktinis nustatymas trumpas: paleiskite ValidatePadesTrust, atmeskite ties ppeiSignatureAlgorithmMismatch, pcsUnsupported ir pcsIndeterminate nukreipkite žmogui ir patys užveskite savą RSA rakto dydžio lubas, nes politika to nedaros. PDFium Component for Delphi and Lazarus atveža PAdES validatorių, įrodymų ataskaitos statytoją ir pasirašymo konvejerį, tad ta pati biblioteka gali šiuos parašus ir pagaminti, ir patikrinti nuo galo iki galo