Tehnički članak

ISO/TS 32002 ECDSA i EdDSA provjere politike u Delphiju

PDFium Component za Delphi svaki ECDSA i EdDSA PDF potpis provjerava prema algoritamskom profilu ISO/TS 32002 i presudu javlja u TPadesSignatureValidation.AlgorithmPolicyStatus. Proći mogu samo P-256, P-384, P-521, tri Brainpool r1 krivulje, Ed25519 i Ed448, svaki s odgovarajućim digestom, a neslaganje podiže ppeiSignatureAlgorithmMismatch. Ta politika važnija je nego što zvuči. Potpis na brainpoolP160r1, ili P-256 ključ koji potpisuje SHA-512 digest, matematički se može vrednovati sasvim dobro, pa Windows CryptoAPI javlja vrijednost potpisa kao dobru dok ga strogi PDF 2.0 validator odbacuje. Politika provjera zatvara taj jaz, i namjerno je odvojena od pitanja jesu li bajtovi potpisa kriptografski ispravni

Što ISO/TS 32002 stvarno dopušta za potpise eliptičkim krivuljama?

ISO/TS 32002 dopušta točno šest ECDSA krivulja i dvije EdDSA sheme u PDF potpisima, i svaku krivulju veže uz veličine digesta koje može nositi. PDFium Component tu tablicu kodira u PadesCurveDigestAllowed, ključanu OID-om krivulje iz certifikata potpisnika. NIST krivulje su stroge: digest mora imati istu bitnu širinu kao krivulja, SHA-2 ili SHA-3. Brainpool krivulje su blage i prihvaćaju vlastitu širinu ili bilo što šire:

  • P-256 (1.2.840.10045.3.1.7): samo SHA-256 ili SHA3-256
  • P-384 (1.3.132.0.34): samo SHA-384 ili SHA3-384
  • P-521 (1.3.132.0.35): samo SHA-512 ili SHA3-512
  • brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7): bilo koji SHA-2 ili SHA-3 digest od 256 do 512 bita
  • brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 od 384 ili 512 bita
  • brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13): samo SHA-512 ili SHA3-512
Matrica algoritamskog profila ISO TS 32002 u PDFium Componentu: P-256, P-384 i P-521 primaju samo odgovarajuće širine digesta u PadesCurveDigestAllowed, brainpoolP256r1 prima 256 do 512 bita, brainpoolP384r1 prima 384 i 512, Ed25519 i Ed448 deklariraju SHA-512 i SHAKE256 s duljinom 512, a neslaganja podižu ppeiSignatureAlgorithmMismatch dok krivulje izvan profila vraćaju pcsUnsupported
Proći može šest ECDSA krivulja i dvije EdDSA sheme, a svaka je krivulja vezana uz širine digesta koje smije nositi; sve ostalo je nevažeće ili nepodržano, nikad tiho prihvaćeno

EdDSA nema izbor krivulje ni izbor digesta, i to je točno razlog što su njegova pravila o kodiranju a ne o snazi. Po RFC 8419 Ed25519 SignerInfo mora deklarirati SHA-512 kao svoj digestAlgorithm bez parametara, a Ed448 SignerInfo, na putu potpisanih atributa koji PAdES uvijek koristi, mora deklarirati id-shake256-len (2.16.840.1.101.3.4.2.18) s INTEGER parametrom točno 512. Za obje sheme AlgorithmIdentifier potpisa i AlgorithmIdentifier javnog ključa certifikata moraju biti bez ijednog parametra. Proizvođač koji tamo upiše NULL, navika koju su RSA enkoderi ugurale u mnoge ASN.1 biblioteke, proizvodi nesuklađen potpis iako su ključ i vrijednost potpisa ispravni

Kako PDFium Component izvlači algoritamski trojac iz CMS-a

PDFium sam po sebi ne može odgovoriti na ovo pitanje, jer njegov javni signature API čita rječnik potpisa ali ni CMS ne verificira ni krivulju certifikata potpisnika ne izlaže. PAdES inspekcijski sloj građen na PDFiumu stoga sam parsira CMS SignedData (RFC 5652). InspectPadesSignatureAlgorithm čita digestAlgorithm i signatureAlgorithm prvog SignerInfo, zatim nađe certifikat potpisnika i iz njegova SubjectPublicKeyInfo čita algoritam ključa i krivulju. Pretraga certifikata namjerno je ograničena: pregleda se najviše 64 certifikata u CMS certificates skupu, podudaranje je egzaktna usporedba bajtova izdavatelja i serijskog broja iz issuerAndSerialNumber, a kod pada natrag na "jedini certifikat koji tamo jest" samo kad skup drži točno jedan parsirivi certifikat. Pokupiti prvi EC certifikat iz nesortiranog skupa bilo bi lako, i dopustilo bi CA certifikatu da odluči koju je krivulju potpisnik navodno koristio

Kako PDFium Component pregledava algoritamski trojac PDF potpisa: InspectPadesSignatureAlgorithm čita digestAlgorithm i signatureAlgorithm prvog SignerInfo u CMS-u, prianja certifikat potpisnika kroz egzaktno podudaranje issuerAndSerialNumber među najviše 64 kandidata, čita SubjectPublicKeyInfo za krivulju, a EvaluatePadesSignatureAlgorithm vraća AlgorithmPolicyStatus
PDFium sam ni CMS ne verificira ni krivulju potpisnika ne izlaže, pa PAdES sloj parsira SignedData i čuva svaki sirovi OID u zapisu za objasnivu odbijenicu
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 politiku primjenjuje kao dio svog prolaza usklađenosti, polazeći od certifikata koji ga nađe unutar CMS-a, a TPdf.ValidatePadesTrust ponovno je provodi nad certifikatom potpisnika koji je Windows CryptoAPI stvarno koristio za vrednovanje, pa certifikat kojeg javlja CryptoAPI ima zadnju riječ. Svaki sirovi ulaz slijeće u TPadesSignatureAlgorithmInfo, uključujući DigestParametersPresent, DigestParameterBits, SignatureParametersPresent i PublicKeyParametersAreNamedCurve, pa je odbijenica uvijek objasniva iz zapisa, a ne iz retka loga

Zašto P-256 potpis sa SHA3-256 pada na politici?

P-256 potpis pada na politici PDFium Componenta svaki put kad se CMS digestAlgorithm i digest koji podrazumijeva ECDSA signatureAlgorithm ne slažu, čak i ako su oba pojedinačno prihvatljiva za krivulju. EvaluatePadesSignatureAlgorithm prvo mapira ecdsa-with-SHA256, ecdsa-with-SHA3-256 i njihovu braću na digest, usporedi to s deklariranim digestAlgorithm i vraća pcsInvalid pri svakoj razlici prije nego se uopće konzultira tablica krivulja. Slučaj je stvaran: alat za potpisivanje prebaci svoj hash na SHA3-256 ali zadrži hardkodirani identifikator ecdsa-with-SHA256, a rezultat je datoteka koju nijedan sukladan verifier ne može dosljedno tumačiti. Funkcija je javna, pa se matrica može prianjati u unit testu bez gradnje PDF-a:

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); // digest se ne slaže

  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, sada sukladan
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // krivulja nije u profilu
end;

Kodiranje krivulje dobiva istu strogost. RFC 5480 §2.1.1 dopušta da ECParameters bude OID imenovane krivulje, implicitna krivulja (NULL) ili puni eksplicitni skup parametara, a PKIX profili traže imenovani oblik. PDFium Component vraća pcsInvalid kad certifikat s id-ecPublicKey dolazi bez parametara, s implicitnim parametrima ili s eksplicitnim parametrima, jer eksplicitni parametri napadaču dopuštaju da opše krivulju koja samo liči na standardnu. Ispravno imenovana krivulja koja jednostavno nedostaje na ISO/TS 32002 popisu, poput brainpoolP160r1 gore ili secp256k1, dobiva pcsUnsupported

Nevažeće, nepodržano ili neodređeno: čitajte status pošteno

Tri nevaljana statusa AlgorithmPolicyStatus znače različite stvari, i njihovo sažimanje u jedan kanta "palo" odbacuje informaciju koju revizori trebaju. pcsInvalid znači da je prepoznata kombinacija algoritama deformirana ili se ne slaže; dodaje ppeiSignatureAlgorithmMismatch u TPadesValidationResult.Issues i agregirani IntegrityStatus tjera u pcsInvalid, pa IsCryptographicallyValid vraća False i kad CMS vrijednost potpisa prolazi. pcsUnsupported znači da je krivulja ili digest izvan onoga što profil imenuje, što je rezultat sposobnosti, ne dokaz manipulacije. pcsIndeterminate znači da se certifikat potpisnika nije mogao prianjati, obično CertificateSet s više kandidata bez egzaktnog issuerAndSerialNumber podudaranja, pa kod odbija nagađati krivulju; od v3.124.0 tako označava i RSA potpis nad SHA-1 ili 112-bitnim digestom poput SHA-224, koji se više ne slaže za tekuću validaciju. Isti raspad vrijedi za EdDSA na stroju čiji CryptoAPI ne zna vrednovati Ed25519 ili Ed448: CmsSignatureStatus ostaje pcsUnsupported dok AlgorithmPolicyStatus može i dalje biti pcsValid, jer je kodiranje bilo ispravno a nedostajao je samo verifier. Ako gonite odbijenicu iz Adobea ili DSS-baziranog validatora, vodič o tome zašto validatori odbacuju PAdES potpise pokriva ostale uobičajene uzroke

Staza odluke EvaluatePadesSignatureAlgorithm u PDFium Componentu: neslaganje digesta postavlja pcsInvalid i ppeiSignatureAlgorithmMismatch, imenovana krivulja izvan ISO TS 32002 profila poput brainpoolP160r1 postavlja pcsUnsupported, certifikat potpisnika koji se ne može prianjati postavlja pcsIndeterminate, a od v3.124.0 RSA grana primjenjuje ETSI TS 119 312 digest suiteove, s veličinom ključa i dalje prepuštenom aplikaciji
Tri nevaljana statusa znače različite stvari: invalid je dokaz slomljene kombinacije, unsupported je rezultat sposobnosti, a indeterminate znači da je kod odbio nagađati
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; // offline, bez revokacije
    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;

Što pcsValid ne jamči?

AlgorithmPolicyStatus = pcsValid potvrđuje samo da ECDSA ili EdDSA potpis koristi odobrenu krivulju s sukladnim, ispravno kodiranim digestom, i da RSA potpis koristi digest iz tekućeg suitea; o ispravnosti vrijednosti potpisa ne kaže ništa. Prije v3.124.0 RSA grana EvaluatePadesSignatureAlgorithm bila je namjerno široka: svaki signatureAlgorithm pod PKCS #1 arc-om 1.2.840.113549.1.1.* vraćao je pcsValid, uključujući naslijeđeni sha1WithRSAEncryption. Od PDFiumPas v3.124.0 RSA grana primjenjuje ETSI TS 119 312 signature suiteove. MD2, MD4 i MD5 digesti su pcsInvalid. SHA-1 i 112-bitni digesti poput SHA-224 su pcsIndeterminate, pa SHA-1 potpis zadržava svoj ukupni integritetski rezultat i označava se za pregled umjesto da se odbaci. digestAlgorithm koji se razlikuje od digesta fiksiranog algoritmom potpisa, poput sha256WithRSAEncryption nad SHA-1 digestom, ili RSA algoritam potpisa na ne-RSA ključu potpisnika, je pcsInvalid, javlja se kao ppeiSignatureAlgorithmMismatch i pada na integritetu. Certifikat potpisnika koji se ne može naći daje pcsIndeterminate, kao što je već bio za ECDSA, a neprepoznati digesti ili ne-signature RSA OID-ovi daju pcsUnsupported. Duljina modula i dalje se ne provjerava, PSS parametri se ovdje ne validiraju (članak o RFC 4055 RSASSA-PSS-params pokriva kako se kodiraju na strani potpisivanja), a SHA-1 ili MD5 digesti dodatno podižu i odvojeni problem ppeiBadDigestAlgorithm. Isto tako, matematika potpisa, lanac certifikata i revokacija ostaju posao CmsSignatureStatus, CertificateTrustStatus i RevocationStatus, koji stižu iz Windows CryptoAPI-ja. Tretirajte pcsValid kao "algoritamski profil drži", nikad kao "ovaj je ključ dovoljno jak"

Za Delphi aplikaciju koja prima potpisane PDF fakture, ugovore ili arhivske pakete praktična je postava kratka: izvršite ValidatePadesTrust, odbacite na ppeiSignatureAlgorithmMismatch, pcsUnsupported i pcsIndeterminate uputite čovjeku, i sami namećite vlastiti RSA prag veličine ključa jer to politika neće. PDFium Component za Delphi i Lazarus isporučuje PAdES validator, graditelj izvještaja o dokazima i pipeline potpisivanja, pa ista biblioteka može i proizvoditi ove potpise i provjeravati ih od početka do kraja