PKCS#11 ile E-İmza PDF İmzalama | C#

PKCS#11 ile E-İmza PDF İmzalama | C#

Merhaba,

Bir süredir projelerimde KAMUSM kütüphanelerini kullanarak e-imza ile PDF imzalama işlemlerini yapıyordum. Ancak KAMUSM’un ücretsiz lisans hizmetini sonlandırmasıyla birlikte, kendi PKCS#11 implementasyonumu yazmak zorunda kaldım.

Bu yazıda, akıllı kart (e-imza dongle) kullanarak PDF imzalama işlemini PKCS#11 standardı ile nasıl gerçekleştirebileceğinizi adım adım anlatacağım.

Önemli: Bu kod, BouncyCastle ve Pkcs11Interop kütüphanelerini kullanır. NuGet üzerinden bu paketleri projenize eklemeyi unutmayın.

Temel Yapı

Çözümüm üç ana sınıftan oluşuyor:

  • PdfImzaServisi – PDF imzalama işlemini yönetir
  • Pkcs11InteropSignature – PKCS#11 ile imzalama işlemini yapar
  • KartServisi – Akıllı kartı bulur ve sertifika bilgilerini okur

1. Kart Bilgilerini Okuma

Öncelikle takılı olan e-imza kartını bulmamız ve sertifika zincirini okumamız gerekiyor:

public static class KartServisi
{
    public static KartBilgisi KartiBul(string pin)
    {
        // PKCS#11 DLL yollarını kontrol et
        var dllListesi = new List
        {
            @"C:\Windows\System32\eTPKCS11.dll",
            @"C:\Windows\System32\akisp11.dll",
            @"C:\Windows\System32\eps2003csp11.dll",
            // ... diğer üretici DLL'leri
        };

        foreach (var dllYolu in dllListesi)
        {
            if (!File.Exists(dllYolu)) continue;

            // Kartı test et ve bilgileri al
            var chain = GetCertChainFromCard(dllYolu, pin, out string etiket, out byte[] ckaId);
            
            if (chain.Count > 0)
            {
                return new KartBilgisi
                {
                    DllYolu = dllYolu,
                    SertifikaEtiketi = etiket,
                    CkaId = ckaId,
                    Chain = chain
                };
            }
        }

        throw new ArgumentException("E-imza kartı bulunamadı!");
    }

    public static ICollection<X509Certificate> GetCertChainFromCard(
        string libraryPath, 
        string pin, 
        out string bulunanEtiket, 
        out byte[] bulunanCkaId)
    {
        var chain = new List<X509Certificate>();
        bulunanEtiket = string.Empty;
        bulunanCkaId = null;

        var factories = new Pkcs11InteropFactories();
        
        using (var pkcs11 = factories.Pkcs11LibraryFactory.LoadPkcs11Library(
            factories, libraryPath, AppType.SingleThreaded))
        {
            var slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);
            
            using (var session = slots[0].OpenSession(SessionType.ReadWrite))
            {
                session.Login(CKU.CKU_USER, pin);
                
                // Sertifika nesnelerini oku
                var template = new List<IObjectAttribute>
                {
                    session.Factories.ObjectAttributeFactory.Create(
                        CKA.CKA_CLASS, CKO.CKO_CERTIFICATE)
                };

                var parser = new X509CertificateParser();

                foreach (var obj in session.FindAllObjects(template))
                {
                    var valAttr = session.GetAttributeValue(
                        obj, new List<CKA> { CKA.CKA_VALUE });

                    if (valAttr.Count > 0 && valAttr[0].GetValueAsByteArray() != null)
                    {
                        chain.Add(parser.ReadCertificate(
                            valAttr[0].GetValueAsByteArray()));

                        // CKA_ID ve etiket bilgilerini al
                        var idAttr = session.GetAttributeValue(
                            obj, new List<CKA> { CKA.CKA_ID });
                        
                        if (idAttr.Count > 0)
                            bulunanCkaId = idAttr[0].GetValueAsByteArray();
                    }
                }
            }
        }

        return chain;
    }
}

2. PKCS#11 ile İmzalama

Asıl sihir burada gerçekleşiyor. PDF’in hash’ini alıp, bunu akıllı karttaki private key ile imzalıyoruz:

public class Pkcs11InteropSignature : IExternalSignatureContainer
{
    public byte[] Sign(Stream data)
    {
        // 1. PDF verisini SHA-256 ile hash'le
        byte[] pdfHash;
        using (var sha256 = SHA256.Create())
            pdfHash = sha256.ComputeHash(pdfBytes);

        // 2. Signed attributes oluştur
        var signedAttrsVec = new Asn1EncodableVector();
        
        // contentType, messageDigest, signingTime ekle
        signedAttrsVec.Add(new Attribute(
            CmsAttributes.ContentType, 
            new DerSet(new DerObjectIdentifier(Sabitler.OID_DATA))));
        
        signedAttrsVec.Add(new Attribute(
            CmsAttributes.MessageDigest, 
            new DerSet(new DerOctetString(pdfHash))));

        // 3. Signed attributes'ı hash'le ve kartla imzala
        byte[] signedAttrsHash;
        using (var sha256 = SHA256.Create())
            signedAttrsHash = sha256.ComputeHash(signedAttrsBytes);

        byte[] digestInfo = DigestInfoSarmalaSHA256(signedAttrsHash);
        byte[] rsaImza = KartlaImzala(digestInfo);

        // 4. CMS SignedData yapısını oluştur
        var signerInfo = new SignerInfo(
            new SignerIdentifier(issuerAndSerial),
            new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256, DerNull.Instance),
            new DerSet(signedAttrsVec),
            new AlgorithmIdentifier(PkcsObjectIdentifiers.RsaEncryption, DerNull.Instance),
            new DerOctetString(rsaImza),
            null);

        var signedData = new SignedData(
            new DerSet(digestAlgsVec),
            new ContentInfo(new DerObjectIdentifier(Sabitler.OID_DATA), null),
            new BerSet(certsVec),
            null,
            new DerSet(signerInfo));

        return contentInfo.GetDerEncoded();
    }

    private byte[] KartlaImzala(byte[] digestInfo)
    {
        var factories = new Pkcs11InteropFactories();
        
        using (var pkcs11 = factories.Pkcs11LibraryFactory.LoadPkcs11Library(
            factories, _pkcs11LibraryPath, AppType.SingleThreaded))
        {
            var slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);
            
            using (var session = slots[0].OpenSession(SessionType.ReadWrite))
            {
                session.Login(CKU.CKU_USER, _pin);
                
                // Private key'i bul (CKA_ID veya CKA_LABEL ile)
                IObjectHandle privateKeyHandle = FindPrivateKey(session);
                
                // RSA-PKCS mekanizması ile imzala
                var mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS);
                return session.Sign(mechanism, privateKeyHandle, digestInfo);
            }
        }
    }
}

3. PDF İmzalama Servisi

Son olarak tüm parçaları birleştiren ana servis:

public static class PdfImzaServisi
{
    public static void PdfImzala(
        string kaynakPdfYolu, 
        string hedefPdfYolu, 
        KartBilgisi kart, 
        string pin)
    {
        // Sertifika zincirini tamamla (AIA extension'dan)
        var chain = ZinciriTamamla(kart.Chain);

        using (var reader = new PdfReader(kaynakPdfYolu))
        using (var os = new FileStream(hedefPdfYolu, FileMode.Create))
        using (var stamper = PdfStamper.CreateSignature(
            reader, os, '\0', null, true))
        {
            var appearance = stamper.SignatureAppearance;
            appearance.Reason = "Doküman onaylandı.";
            appearance.Location = "İstanbul";

            // External signature container oluştur
            IExternalSignatureContainer container = 
                new Pkcs11InteropSignature(
                    kart.DllYolu, 
                    pin, 
                    kart.SertifikaEtiketi, 
                    kart.CkaId, 
                    chain);

            // 64 KB - zincir + CMS için yeterli
            MakeSignature.SignExternalContainer(
                appearance, container, 65536);
        }
    }

    private static ICollection<X509Certificate> ZinciriTamamla(
        ICollection<X509Certificate> kartZinciri)
    {
        var tumZincir = new List<X509Certificate>(kartZinciri);
        var parser = new X509CertificateParser();

        // AIA (Authority Information Access) extension'dan 
        // issuer sertifikaları indir
        for (int i = 0; i < 5; i++)
        {
            var sonCert = tumZincir[tumZincir.Count - 1];

            // Self-signed ise zincir tamamdır
            if (sonCert.SubjectDN.Equivalent(sonCert.IssuerDN))
                break;

            string aiaUrl = AiaUrlGetir(sonCert);
            if (string.IsNullOrEmpty(aiaUrl)) break;

            try
            {
                using (var wc = new WebClient())
                {
                    byte[] certBytes = wc.DownloadData(aiaUrl);
                    var yeniCert = parser.ReadCertificate(certBytes);
                    tumZincir.Add(yeniCert);
                }
            }
            catch { break; }
        }

        return tumZincir;
    }
}

Kullanım Örneği

// Kartı bul ve bilgileri al
var kart = KartServisi.KartiBul("123456");

// PDF'i imzala
PdfImzaServisi.PdfImzala(
    @"C:\Belgeler\sozlesme.pdf",
    @"C:\Belgeler\sozlesme_imzali.pdf",
    kart,
    "123456"
);

Console.WriteLine("PDF başarıyla imzalandı!");

Önemli Noktalar

  • CKA_ID Kullanımı: Sertifikaların CKA_ID özelliği, private key ile eşleştirmek için kritik. Bu sayede doğru anahtarı buluyoruz.
  • Sertifika Zinciri: AIA extension’dan issuer sertifikalarını otomatik indirerek tam zincir oluşturuyoruz.
  • Thread Safety: PKCS#11 işlemleri için lock mekanizması kullanmayı unutmayın.
  • 32/64 Bit Uyumluluğu: PKCS#11 DLL’leri process mimarisi ile uyumlu olmalı.

Umarım bu kodlar sizin de işinize yarar.

Selamlar.


Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top