Bir CMS konteynerı içindeki ECDSA signatureValue, bir DER SEQUENCE { INTEGER r, INTEGER s }'tir. Windows CNG fonksiyonu BCryptVerifySignature bunların hiçbirini kabul etmez: etiketsiz ve uzunluksuz, IEEE P1363 sabit genişlikli r || s ister. Delphi ve C++Builder için yerel VCL PDF bileşeni olan HotPDF, bir anahtarı içe aktarmadan önce ikisi arasında sıkı DER kurallarına göre dönüşüm yapar
Bunun önlediği hata, özel ve moral bozucu bir hatadır. Acrobat dokümanı açar ve yeşil bir onay işareti gösterir. Aynı baytları gezen kendi doğrulayıcınız geçersiz döner ya da CNG başka açıklama yapmadan STATUS_INVALID_SIGNATURE döndürür. İmzada yanlış bir şey yoktur. Yanlış olan, kabaca yetmiş bayt ASN.1'in altmış dört bayt ham tamsayı bekleyen bir API'ye geçirilmiş olmasıdır ve bu uyumsuzluk, aranması gerektiğini bilmedikçe görünmezdir
BCryptVerifySignature neden geçerli bir ECDSA imzasını reddeder?
Çünkü çağrının iki tarafı farklı imza kodlamaları konuşur ve hiçbiri bunu duyurmaz. ISO 32000-1 §12.8, bir imza sözlüğünün /Contents içinde bir CMS blob'u taşıdığını söyler; RFC 5652 §5.3, her SignerInfo'daki signatureValue'nun, imza algoritmasının tanımladığı her şey olabilen içeriğe sahip bir OCTET STRING olduğunu söyler. ECDSA için bu içerik SEC 1 DER yapısıdır: iki INTEGER tutan bir SEQUENCE. Tasarım gereği değişken uzunluktadır, çünkü r ve s tamsayıdır ve DER, tamsayılardan baştaki sıfır oktetlerini soyar
IEEE P1363 tam tersi bir görüş benimser. İmzayı, her biri eğri alanının bayt genişliğine tam olarak sıfırla soldan doldurulmuş iki koordinatın birleştirilmesi olarak tanımlar. Bir P-256 imzası her zaman 64 bayttır. Aynı imzanın DER kodlaması normalde 70 veya 71 bayttır ve kabaca 8 ile 72 arasında herhangi bir yerde olabilir. DER biçimini BCryptVerifySignature'a verin, tek başına uzunluk kontrolü çağrıyı mahkum eder; bu yüzden HotPDF doğrulamadan önce değil, doğrulamadan sonra değil, doğrulamadan önce normalize eder
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;
Bir imza ayrıştırıcısının gevşetmemesi gereken DER kuralları
Burada listelenen her ret, HotPDF'nin kasıtlı olarak gerçekleştirdiği bir rettir ve her biri, hoşgörülü bir ayrıştırıcının açık bırakacağı bir yolu kapatır. Bir dönüştürücü yazarken cazibe, iki INTEGER düğümünü bulup içeriklerini kopyalamak ve devam etmektir. Bu, iyi biçimlendirilmiş girdide çalışır ve düşman girdide sessizce bir değiştirilebilir yeniden kodlama ailesini kabul eder. Bu yüzden HPDFECDSANormalizeSignature, herhangi bir r veya s'nin ilk içerik oktetinin yüksek biti ayarlanmışsa, yani negatif bir tamsayı olduğunda reddeder, çünkü geçerli bir ECDSA skaler pozitiftir. Tamamen sıfır olan bir değeri reddeder, çünkü r = 0 veya s = 0 asla meşru bir imza değildir. Gereksiz baştaki bir sıfır okteti reddeder: X.690 §8.3 tam olarak birine izin verir ve yalnızca bir sonraki oktet başka türlü negatif okunacaksa, bu yüzden 0x80'in altında bir oktet izleyen bir 00, bir yeniden kodlamadır, bir imza değil. Minimal olmayan bir uzunluk başlığını reddeder, çünkü X.690 §10.1, en az sayıda oktetle kodlanmış kesin biçimi gerektirir ve kısa biçimde olabilecek uzun biçimli bir uzunluk, aynı anlamı taşıyan farklı bir bayt dizisidir. Eğri koordinat boyutundan geniş bir tamsayıyı reddeder, çünkü bu değer bir alan elemanı olamaz. Ve s'den sonra herhangi bir arta kalan düğümü, dış SEQUENCE'in toplam uzunluğu tüm blob'un uzunluğuna eşit değilse onunla birlikte reddeder
Son ikisi göründüğünden daha önemlidir. SEQUENCE'ten sonraki arta kalan baytlar, klasik imza-değiştirilebilirlik hilesidir: çöp ekleyin, hoşgörülü bir doğrulayıcı yine de geçerli der ama doğruladığı bayt dizisi imzalanan bayt dizisi değildir. Aynı içgüdü, PKCS#12 ayrıştırma üzerine notta anlatılan ASN.1 uzunluk sertleştirmesini yönlendirir ve burada da aynı içgüdüdür. Bir doğrulama yolunda, uyumlu bir imzalayan tarafından hiç yayınlanmamış kabul edilmiş bir yapı bir nezaket değil, bir kusurdur
Koordinat genişliği imzaya değil eğriye aittir
HotPDF, çıktı genişliğini az önce ayrıştırdığı DER'in uzunluğundan değil, her zaman isimlendirilmiş eğri OID'sinden türetir. Bu, dönüşümün ikinci yarısıdır ve incelikli biçimde yanlış yapılması kolay olan yarısıdır. RFC 5480 §2.1.1, eğriyi sertifikanın SubjectPublicKeyInfo parametrelerinde tanımlar ve HPDFECDSACurveFromOID, HotPDF'nin desteklediği üç OID'yi eşler: P-256 için 1.2.840.10045.3.1.7, P-384 için 1.3.132.0.34 ve P-521 için 1.3.132.0.35. HPDFECDSACoordinateSize daha sonra 32, 48 veya 66 bayt döndürür ve P1363 tamponu bunun iki katıdır: 64, 96 veya 132. Her çözülen tamsayı, kendi yarısına sağa hizalanır, bu yüzden kısa bir r, kaydırılmak yerine soldan sıfırla doldurulur. P-521, insanları yakalayan tanedir, çünkü 521 bit 65,125 bayttır ve 66'ya yuvarlanır, bu da hiçbir ikinin-kuvveti sezgisinin tahmin edemeyeceği 132 baytlık bir imza verir. Genel anahtar, RFC 5480 §2.2'ye göre sıkıştırılmamış bir EC noktası olarak birlikte seyahat eder; bu, X ve Y'nin ardından gelen 0x04'tür, bu yüzden HotPDF, CNG'ye dokunmadan önce bunun tam olarak 1 + 2 * CoordinateSize bayt olduğunu ve 0x04 ile başladığını kontrol eder
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;
Son parametreye dikkat edin. HPDFECDSAVerifyDigest, bir donanım token'ından veya ham r || s döndüren uzak bir imzalama hizmetinden gelen, zaten sabit genişlikli bir imza tutan çağıranlar için eseP1363'ü de kabul eder. Bu yol da her iki yarıda uzunluk ve sıfır-olmama kontrolünü uygular, bu yüzden doğru boyutta ama tamamen sıfırlarla dolu bir tampon, sağlayıcıya geçirilmek yerine reddedilir
Genel ECDSA algoritma ismi eski Windows'ta neden başarısız olur?
Çünkü genel isim, yayına aldığınız dağıtım tabanından daha yenidir. CNG, içe aktarılan anahtardan eğriyi çıkarsayan ECDSA bir algoritma tanımlayıcısı sunar ve bu kodu yazmanın temiz yoludur, ama BCryptOpenAlgorithmProvider'ın bunu çözeceği yalnızca daha yeni Windows sürümlerinde garantidir. Daha eski bir makinede açma çağrısı başarısız olur, sağlayıcı tanıtıcısı nil kalır ve uygulamanızdaki her ECDSA doğrulaması, mükemmel derecede iyi bir imza için desteklenmediğini bildirir. HotPDF, bu uçurumu eğri-başına tanımlayıcıları açarak önler. ECDSA_P256, ECDSA_P384 ve ECDSA_P521'i bir kez çözer, eğri başına bir sağlayıcı tanıtıcısını önbelleğe alır ve birim sonlandırmasında kapatır. Her doğrulama daha sonra yalnızca ucuz işi yapar: bir ECCPUBLICBLOB'dan geçici bir genel anahtar içe aktarır, BCryptVerifySignature'ı çağırır, anahtarı yok eder. Tekrarlanan LoadLibrary yok, tekrarlanan GetProcAddress yok, imza başına sağlayıcı açma ve kapatma yok. Birkaç yüz dokümanın toplu doğrulaması bu farkı hisseder, yük altında aksi takdirde sağlayıcı tanıtıcılarını harcayacak bir servis süreci de öyle
Sonuç kodları bu ayrım konusunda dürüst kalır. evrProviderUnavailable, makinenin HotPDF'ye bir sağlayıcı veremediği anlamına gelir; evrInvalid, CNG'nin STATUS_INVALID_SIGNATURE yanıtladığı anlamına gelir. Bu ikisini tek bir başarısızlıkta birleştirmek, bir dağıtım sorununun sahte bir doküman olarak yanlış rapor edilmesinin yoludur. Ortam başarısızlığı ile kriptografik başarısızlık arasındaki aynı ayrım, imzalama tarafındaki CNG ve CAPI işlemesi boyunca da geçerlidir ve sertifika deposu imzalama ve bayt sırası üzerine yazıda ele alınmıştır
Bu dokümanı kim imzaladı? SignerIdentifier iki farklı şeydir
RFC 5652 §5.3, SignerIdentifier'ı bir CHOICE yapar ve yalnızca bir kolu ele alan bir doğrulayıcı, sessizce yanlış anahtara karşı doğrulama yapar. İlk kol, ham DER'de veren Name'i ve seri INTEGER'ını tutan bir SEQUENCE olan issuerAndSerialNumber'dır ve bunu eşleştirmek, CMS certificates kümesindeki her sertifikaya karşı bir bayt karşılaştırmasıdır. İkinci kol, örtük olarak etiketlenmiş bir OCTET STRING olan [0] subjectKeyIdentifier'dır ve bunu eşleştirmek, başlık alanlarını karşılaştırmak yerine sertifikanın içine kazmayı gerektirir
Kazının insanları şaşırtan bir katmanı vardır. Anahtar tanımlayıcısı bir X.509v3 uzantısında yaşar, bu yüzden HotPDF, tbsCertificate'in [3] uzantılar alanını gezer, OID'si 2.5.29.14 olan uzantıyı bulur, isteğe bağlı critical BOOLEAN'ı atlar ve extnValue OCTET STRING'ini alır. Bu oktet dizisi tanımlayıcı değildir. RFC 5280 §4.2.1.2'ye göre içeriği kendisi DER'dir ve KeyIdentifier türü başka bir OCTET STRING'tir, bu yüzden gerçek baytlara ulaşmak için ikinci kez ayrıştırırsınız. Bir katman erken durursanız, 22 baytlık bir sarmalayıcıyı 20 baytlık bir tanımlayıcıya karşı karşılaştırırsınız, hiçbir sertifika eşleşmez ve doğrulayıcı sonraki yazdığınız her sezgisel yönteme düşer; asıl tehlike budur. Kümedeki ilk sertifikayı almak cazip bir kısayoldur ve CMS bir zincir taşıdığında yanlıştır; ki bu çoğu zamandır, çünkü leaf'in ilk sırada gelmesi zorunlu değildir. HotPDF, eşleşmeyen bir sertifikayı yalnızca konteynerin tam olarak bir tane taşıması durumunda kabul eder; birden fazla sertifika mevcut olduğunda, tam bir SignerIdentifier eşleşmesi zorunludur. Bir digest'i bir ara CA genel anahtarına karşı doğrulamak dostane bir hata üretmez, sorunsuz olan bir doküman üzerinde kendinden emin bir geçersiz üretir
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, yalnızca "ECDSA" kelimesi yerine hangi eğrinin gerçekten kullanıldığını bir denetim günlüğünün kaydetmesi için P-256, P-384 veya P-521 raporlar. Bu çağrının etrafındaki doküman seviyesindeki tesisat, özellikle /ByteRange segmentlerinin nasıl hash'lendiği ve digest'in neden ayrıştırılmış nesne ağacı yerine dosya üzerinden hesaplanması gerektiği, PDF imzalarını doğrulama üzerine yoldaş yazının konusudur
Bu size ne vermez
HPDFECDSAVerifyDigest'ten yeşil bir sonuç, yalnızca bir soruyu yanıtlar: bu baytlar, bu genel anahtara karşılık gelen özel anahtar tarafından imzalanmıştır. Bu anahtarın güvenmeniz gereken birine ait olup olmadığı hakkında hiçbir şey söylemez. Bir güven çıpasına zincir kurma, CRL veya OCSP aracılığıyla iptal ve politika kontrolleri ayrı işlerdir ve bunlar olmadan geçerli bir imza bildiren herhangi bir ürün, kullanıcının varsaydığından daha azını bildiriyordur. Sertifika geçerlilik tarihleri, tam olarak bu yüzden THPDFSignatureInfo'da ayrı olarak sunulur: bir imza, onu yapan sertifika iki yıl önce süresi dolmuş olsa bile kriptografik olarak doğrulanabilir. Eğri desteği de kasıtlı olarak dardır. Üç NIST prime eğrisi ele alınır ve başka bir eğri üzerindeki bir imza, tahmin yerine desteklenmiyor döndürür. CNG yolu yalnızca Windows'tur; bu bir VCL bileşeni için doğru bir takas ama bunun etrafında bir platformlar-arası servis planlamadan önce belirtilmeye değer. Ve sıkılık yapılandırılabilir değildir: bazı eski imzalayanların yayınladığı minimal olmayan bir DER uzunluğunu kabul eden hoşgörülü bir mod yoktur. Üretimde böyle bir dosyayla karşılaşırsanız, dürüst yanıt onu kaydetmek ve üreticinin peşine düşmektir, dosya geçene kadar ayrıştırıcıyı genişletmek değil
Burada anlatılan ECDSA doğrulama yolu, Delphi ve C++Builder için standart HotPDF Component'in, RSA PKCS#1 v1.5 ve RSA-PSS yollarının ve tam imza bilgi kaydının yanı sıra bir parçası olarak sunulur; ürün sayfası eksiksiz dijital imza referansını içerir