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.
Temel Yapı
Çözümüm üç ana sınıftan oluşuyor:
PdfImzaServisi– PDF imzalama işlemini yönetirPkcs11InteropSignature– PKCS#11 ile imzalama işlemini yaparKartServisi– 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
lockmekanizması 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.
