# mm.erp — e-Defter İmzalama ve Doğrulama Kütüphanesi

GİB e-Defter (Yevmiye, Kebir) ve Berat XML dosyalarını **XAdES-BES** formatında imzalayan ve doğrulayan, .NET 10 üzerine bina edilmiş, Ma3Api'ye bağımlı olmayan, üretim-onaylı kütüphane.

İmzalama **Ma3Api**'nin ürettiği wire-format ile bit düzeyinde uyumlu çıktı üretir; hem **GİB e-Defter web servisi (Ma3Api validator)** hem de **GİB e-Defter Görüntüleyici (Apache Santuario)** tarafından "matematiksel olarak doğrulu" kabul edilir.

---

## 1. Genel Amaç

- GİB **e-Defter** (Yevmiye/Kebir) ve **Berat** XML dosyalarını dijital olarak imzala.
- Mevzuata uygun **XAdES-BES** zarfı üret (`xades:SignedProperties`, `ds:SignedInfo`, `ds:Signature`, `ds:KeyInfo`).
- Üretilmiş imzalı dosyaları **bağımsız doğrula** (kontrol mekanizması — bkz. §6).
- Dört anahtar kaynağını (USB token, **HSM**, Windows sertifika deposu, PFX dosyası) **şeffafça** destekle.

---

## 2. Katmanlar ve Sorumluluklar

```
┌─────────────────────────────────────────────────────────────┐
│  Çağıran Uygulama / CLI (tools/SignerCli)                   │
│   sign-pfx | sign-p11 | sign-csp | verify                   │
└─────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  MegaEDefter.Signing.EDefterSigner  (dosya/byte sarmalayıcı)│
│    • CRLF→LF normalize, XmlDocument load, byte[] döner      │
└─────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  MegaEDefter.Crypto.XAdES.XAdesBesSigner   (asıl motor)    │
│    • SignedProperties, Reference, SignedInfo, KeyInfo       │
│    • c14n, pretty-print, digest, sign, base64               │
└─────────────────────────────────────────────────────────────┘
                          │ ISigningKey (algoritma + cihaz soyutlaması)
                          ▼
┌────────────────────────┬────────────────────────┬─────────────────┐
│ Pkcs11SigningKey       │ WindowsCspSigningKey   │ PfxSigningKey   │
│ USB e-Mali Mühür / HSM │ Windows sertifika dep. │ Geliştirme/test │
│ akisp11.dll /          │ CryptoAPI / CNG (RDP)  │ PFX dosyası     │
│ cryptoki.dll           │ + HSM KSP              │                 │
└────────────────────────┴────────────────────────┴─────────────────┘
```

| Katman | Proje | Açıklama |
|---|---|---|
| Kripto çekirdek | `MegaeDefter4Net.Crypto` | `ISigningKey`, `XAdesBesSigner`, c14n + digest, `SigningSelfTest`, `CertificateFileLoader` |
| Yüksek seviye sarmalayıcı | `MegaeDefter4Net.Signing` | `EDefterSigner`, `PfxSigningKey` |
| Donanım anahtarları | `MegaeDefter4Net.SmartCard` | `Pkcs11SigningKey` + `Pkcs11KeyOptions` + `Pkcs11SlotSelector` + `Pkcs11RetryPolicy` + `PinResolver`/`PinProtector`, `WindowsCspSigningKey`, `CngKeyDiagnostics` |
| Doğrulama | `MegaeDefter4Net.Validation` | `XAdesEnvelopeValidator`, `DssEquivalentValidator`, `XmlRepairer`, `XsdValidator`, `SchematronRunner` |
| CLI | `tools/SignerCli` | İnce komut satırı sarmalayıcı |
| Birim test | `tests/MegaeDefter4Net.Signing.Tests` | XAdES round-trip, repairer, log pattern, cert utility, imza self-test, PKCS#11 options, cert-file yükleyici |

---

## 3. Anahtar Soyutlama — `ISigningKey`

[src/MegaeDefter4Net.Crypto/ISigningKey.cs](src/MegaeDefter4Net.Crypto/ISigningKey.cs)

```csharp
public interface ISigningKey : IDisposable
{
    X509Certificate2 Certificate { get; }
    HashAlgorithmName HashAlgorithm { get; }   // SHA-256/384/512 (anahtar gücüne göre)
    string SignatureMethodUri { get; }         // XML DSig URI
    bool IsEcdsa { get; }
    byte[] SignData(byte[] data);
    byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding? padding = null);
}
```

**Hash seçimi otomatik**: P-256 → SHA-256, P-384 → SHA-384, P-521 → SHA-512 (NIST güç eşleme). Üretim ortamı (Mali Mühür Sürüm 3, P-384) için `ecdsa-sha384` URI'si emit edilir.

### Üç implementasyon

| Sınıf | Çalıştığı yer | Kullanım amacı |
|---|---|---|
| `Pkcs11SigningKey` | Herhangi bir PKCS#11 kütüphanesi: `akisp11.dll` (USB token) veya `cryptoki.dll` (**HSM**) | **Üretim** — özel anahtar **cihazdan çıkmaz** |
| `WindowsCspSigningKey` | Windows sertifika deposu (CSP/CNG), HSM KSP dahil | **RDP/VM** — PKCS#11 RDP redirect'i görmez, CSP görür |
| `PfxSigningKey` | Dosya tabanlı PFX/P12 | Birim test, geliştirme, offline işler |

### 3.1. HSM: sertifika dosyada, anahtar cihazda

Kartlarda sertifika **ve** anahtar birlikte durur (ortak `CKA_ID`). HSM'lerde genelde
**yalnız anahtar çifti** cihazdadır, sertifika dosyada tutulur. `Pkcs11SigningKey` her iki
senaryoyu **aynı kod yolundan** destekler — ayrı bir `HsmSigningKey` yoktur, böylece GİB
wire-format kararları tek yerde kalır.

```csharp
using var key = new Pkcs11SigningKey(new Pkcs11KeyOptions
{
    DriverPath = @"C:\Program Files\SafeNet\LunaClient\cryptoki.dll",
    SlotLabel = "edefter-partition",      // ya da TokenSerial / SlotId
    StrictSlotSelection = true,           // birden çok partition varsa tahmin etme
    PinProvider = PinResolver.Create(     // env > DPAPI dosyası > literal > prompt
        new PinResolver.Sources(PinEnvVar: "HSM_PIN")),
    // LoginRequired = false,             // PED-auth / önceden doğrulanmış partition
    CertificateFile = "mali-muhur.cer",   // DER veya PEM (PFX REDDEDİLİR)
    KeyLabel = "edefter-signer",          // ya da KeyIdHex = "a1b2c3"
    // VerifyKeyMatchesCertificate varsayılan true
});
new EDefterSigner(key, new XAdesSignerOptions { ClaimedRole = "Supplier" })
    .SignFile("yev.xml", "yev.signed.xml");
```

**Sürücü nasıl bulunur?** `CardDriverRegistry` kart (AKİS, SafeNet eToken, E-Tugra, OpenSC) ve HSM
(Thales Luna, Utimaco, AWS CloudHSM, SoftHSM2) kütüphanelerini Windows **ve** Linux yollarında
arar; `MMERP_PKCS11_MODULE` ortam değişkeni hepsinin önüne geçer. **Otomatik seçim yalnız kart
sürücüleri içindir** — kurulu bir HSM kütüphanesi kendiliğinden seçilmez, `--driver` ile açıkça
verilir. Hata metinleri cihaz türüne göre değişir (HSM'de AKİS/RDP tavsiyesi basılmaz).

```bash
dotnet run --project tools/SignerCli -- p11-drivers   # kurulu sürücüler: [KART] / [HSM]
```

**PIN nereden gelir?** Komut satırındaki `--pin` process listesinde ve kabuk geçmişinde görünür;
üretimde kullanmayın. Öncelik: `--pin-env <DEĞİŞKEN>` → `--pin-file <dosya>` (DPAPI şifreli) →
`--pin` (uyarı basar) → interaktif giriş (echo kapalı). PIN **tembel** çözülür (login anında),
`Pkcs11KeyOptions` sırrı taşımaz ve `ToString()` PIN'i maskeler.

```bash
# PIN'i bir kez DPAPI ile şifreli dosyaya yaz (PIN interaktif sorulur, argüman olarak alınmaz)
dotnet run --project tools/SignerCli -- protect-pin --out C:\ProgramData\mm.erp\hsm.pin
# Servis/LocalSystem altında koşacaksa: --machine (NTFS izinlerini kısıtlayın)
```

DPAPI dosyası **makineye** (ve CurrentUser kapsamında **yazan hesaba**) bağlıdır; başka makineye
kopyalanırsa çözülmez. Düz metin PIN dosyası bilerek reddedilir.

**Login gerekmeyen cihazlar:** Luna PED-auth veya önceden kimlik doğrulanmış partition'da
`LoginRequired = false` (`--no-login`) ile `C_Login` hiç çağrılmaz. Cihaz oturumu zaten
doğrulanmış açtıysa (`CKR_USER_ALREADY_LOGGED_IN`) bu hata sayılmaz, imzalama devam eder.
`UserType` varsayılan `CKU_USER`'dır (`--user-type so` yalnız token yönetimi içindir, imza değil).

**Slot / partition seçimi — öncelik sırası:**
`SlotLabel` → `TokenSerial` → `SlotId` (gerçek CK_SLOT_ID) → tek slot → (strict değilse) ilk slot.
Label eşleşmesi trim'li ve harf-duyarsızdır (PKCS#11 label'ları boşlukla doldurur). Seçicilerden
**yalnız biri** verilebilir. `StrictSlotSelection = true` iken birden çok slot varken seçici
verilmemişse hata alırsınız — kart yolunda varsayılan `false`, yani tek okuyucu senaryosu
eskisi gibi çalışır. Aynı label'a sahip iki partition da hatadır; hata mesajları her zaman
slot dökümünü (slotId, label, serial, üretici, model) içerir.

**Anahtar seçim sırası — sabit, tahmin içermez:**
`KeyLabel` (CKA_LABEL) → `KeyIdHex` (CKA_ID) → (kart yolu) sertifikanın CKA_ID'si →
(sertifika dosyadan + seçici yok) cihazdaki **tek** anahtar. Birden çok anahtar varken seçici
verilmemişse **hata** verir (mevcut label/ID dökümüyle) — yanlış anahtarla imzalamak yerine durur.

**Anahtar–sertifika doğrulaması:** `VerifyKeyMatchesCertificate` açıkken ctor sonunda
`SigningSelfTest` koşar (sabit probe hash'i imzalanır, sertifikanın public key'i ile doğrulanır).
Eşleşmezse **imzalama başlamadan** hata alırsınız — yanlış sertifika/anahtar çifti GİB'den
"imza degeri gecersizdir" reddi olarak günler sonra dönmez. Kart yolunda varsayılan kapalıdır
(eski davranış); `--no-key-verify` ile de kapatılabilir.

⚠️ **`CertificateFile` yalnız public sertifika kabul eder.** PFX/P12 verilirse reddedilir:
dosyadaki bir private key'in "HSM imzaladı" sanılarak kullanılması engellenmiştir.

**Oturum dayanıklılığı:** İmzalama serileştirilir (PKCS#11 oturumu eşzamanlı `C_Sign` kaldırmaz).
Oturum koparsa (kart çıkarıldı, HSM istemcisi yeniden bağlandı, boşta kalan oturum düşürüldü)
slot yeniden seçilir, login tekrarlanır, anahtar aynı seçicilerle yeniden çözümlenir ve imza
**bir kez** yeniden denenir — böylece bir kopma tüm partiyi düşürmez. Yeniden bağlanmanın ardından
anahtar–sertifika eşleşmesi tekrar doğrulanır: aynı etiketli *farklı* bir cihaz takılmışsa imzalama
durur. `CKR_PIN_INCORRECT` gibi operasyon hataları **yeniden denenmez** (tekrar denemek token
kilitleyebilir).

⚠️ **Durum:** PKCS#11/HSM yolunun tamamı kodlandı ve birim testleriyle doğrulandı, ancak
**gerçek bir HSM/token üzerinde uçtan uca çalıştırılmadı** (bugüne dek yalnız sürücü yükleme +
slot enumerasyonu gerçek `akisp11.dll` ile denendi). Üretimde kullanmadan önce §1.7'deki
doğrulama adımlarını koşun.

Kapsam ve durum (fazlar, cihazda doğrulanmayı bekleyenler):
[docs/YAPILACAKLAR.md §1](docs/YAPILACAKLAR.md) ·
KSP yolu için PoC runbook: [docs/hsm-poc-csp.md](docs/hsm-poc-csp.md)

---

## 4. Sarmalayıcı — `EDefterSigner`

[src/MegaeDefter4Net.Signing/EDefterSigner.cs](src/MegaeDefter4Net.Signing/EDefterSigner.cs)

İki kritik küçük iş yapar:

1. **`SignFile(input, output)`** — dosya tabanlı kullanım kolaylığı.
2. **CR/LF normalize** — XML 1.0 §2.11 uyarınca `CR` ve `CRLF`'leri `LF`'ye çevirir. Aksi halde C14N `&#xD;` literal olarak emit eder → GİB Görüntüleyici "Matematiksel doğrulama HATALI" verir.

```csharp
using var key    = new PfxSigningKey("test.pfx", "1234");
var signer       = new EDefterSigner(key, new XAdesSignerOptions { ClaimedRole = "Supplier" });
signer.SignFile("yevmiye.xml", "yevmiye.signed.xml");
```

Asıl iş alttaki `XAdesBesSigner.Sign(XmlDocument)`'a delege edilir.

---

## 5. Motor — `XAdesBesSigner`

[src/MegaeDefter4Net.Crypto/XAdES/XAdesBesSigner.cs](src/MegaeDefter4Net.Crypto/XAdES/XAdesBesSigner.cs)

`Sign(XmlDocument)` 8 adımlık akış:

1. **Skeleton kur** — `ds:Signature` içinde boş `ds:Object → xades:QualifyingProperties → SignedProperties` (`SigningTime`, `SigningCertificate`, `ClaimedRole`, `DataObjectFormat`).
2. **Pretty-print** — hash öncesi whitespace yerleştir (sonra c14n bunu imzaya dahil eder, verify aynı bytları görür).
3. **Reference 1 digest** — `SignedProperties` → c14n → SHA-256.
4. **Reference 2 digest** — Tüm belge, `enveloped-signature` transform uygulandıktan sonra c14n → SHA-256.
5. **SignedInfo inşa et** — iki Reference, `SignatureMethod` URI, `CanonicalizationMethod=xml-c14n-20010315` (plain, **WithComments DEĞİL** — Ma3Api kuralı).
6. **SignedInfo'yu c14n'le ve imzala** — `ISigningKey.SignData(...)`. ECDSA için **P1363 → DER** dönüşümü — GİB Java validatörü DER bekler.
7. **`SignatureValue`** — base64 olarak yerleştir.
8. **`KeyInfo`** — `X509Data` (sertifika + `SubjectName`) ÖNCE, `KeyValue` (modulus/exponent) SONRA. Sıra önemlidir — bazı strict validatörler şikayet eder.

### Üretim deneyimiyle kazanılmış wire-format kararları

Aşağıdaki kararların hepsi gerçek GİB red/kabul vakalarıyla doğrulanmış ve kaynak kodda tarih + firma referansıyla belgelenmiştir:

- **`CanonicalizationMethod = plain c14n`** (WithComments yok). WithComments kullanılırsa Ma3Api **"Belge üzerindeki imza degeri gecersizdir"** verir.
- **ECDSA imza encoding = DER** (P1363 değil). GİB file-level validatörü DER bekler.
- **Reference sırası**: önce `SignedProperties`, sonra belge (Ma3Api sırası).
- **DigestMethod = SHA-256** sabit (anahtar SHA-384 olsa bile) — Ma3Api uyumu.
- **`HashForEc`** = anahtar gücüyle eşleşmeli: P-384 anahtar → `ecdsa-sha384`. Sabit-SHA-256 eski davranışı bug'tı.
- **`X509IssuerName`** boşluksuz format (`CN=...,OU=...`) — binary cert encoding'iyle eşleşir.
- **`xmlns:ds` ve `xmlns:xades`** yerel'de tekrar bildirilir — bazı Java verify'larında inherited vs. declared farkı c14n çıkışını değiştirir.
- **Sertifika seçim ranking'i**: Mali Mühür + `KeyUsage=DigitalSignature` → sırayla düşer. Yanlış cert (KeyAgreement) seçimi GİB "imza geçersiz" sebebi olmuştu.
- **LF-only XmlWriter output** — Windows'da default CRLF, verify-time c14n hash'i bozar.

---

## 6. Doğrulama (Kontrol Mekanizması)

Repo iki katmanlı doğrulama sunar:

### 6.1. Hızlı kriptografik doğrulama — `XAdesBesSigner.Verify`

Aynı sınıfta bulunan `static Verify(XmlDocument)`:

- **Path A** — manuel yeniden inşa: c14n method ve digest URI'leri envelope'tan okuyup birebir yeniden hashleyerek doğrular (bizim imzaladığımız dosyaları bit-bit doğrulayabilir).
- **Path B** — fallback olarak .NET `SignedXml.CheckSignature` (başka aletlerce üretilmiş imzalar için).

```bash
SignerCli verify --in yevmiye.signed.xml --mode fast
```

### 6.2. EU DSS-eşdeğer kapsamlı doğrulama — `DssEquivalentValidator`

[src/MegaeDefter4Net.Validation/DssEquivalentValidator.cs](src/MegaeDefter4Net.Validation/DssEquivalentValidator.cs)

EU DSS (eSignature DSS / Apache Santuario) semantiğini taklit eden tam zincirli doğrulayıcı. Her imza için **7 yapı taşı** ayrı ayrı raporlanır:

- `FormatChecking` — XAdES-BES yapısal kontrol
- `IdentificationOfSigningCertificate` — `SigningCertificate` referansı, cert digest
- `ValidationContextInitialization` — sertifika zinciri inşası
- `X509CertificateValidation` — **KamuSM kök CA** listesine karşı zincir doğrulama
- `CryptographicVerification` — Reference digest'leri + `SignedInfo` imza doğrulama
- `SignatureAcceptanceValidation` — `ClaimedRole`, `SigningTime`, MimeType kontrolleri
- `AlgorithmObsolescenceValidation` — SHA-1 / RSA-1024 vb. eski algoritma reddi

Çıktı: `DssEquivalentReport` — her imzanın `Indication` (TOTAL_PASSED / TOTAL_FAILED / INDETERMINATE) + `DigestMatchers` + `Timestamps`.

```bash
SignerCli verify --in yevmiye.signed.xml --mode dss
```

### 6.3. Yardımcı doğrulayıcılar

| Sınıf | Görev |
|---|---|
| `XAdesEnvelopeValidator` | Yapısal: doğru element sırası, zorunlu alanlar |
| `XsdValidator` | GİB `edefter.xsd` + `XAdES.xsd` şema doğrulaması |
| `SchematronRunner` | GİB schematron kurallarını çalıştırır (Saxon-HE üzerinden) |
| `XmlRepairer` | Yaygın bozuklukları tamir eder (encoding, period bound, MimeType) |
| `KamuSmTrustList` | KamuSM trusted CA listesi yöneticisi (resources/kamusm-trusted-cas.json) |

---

## 7. Legacy Entegrasyon Notu

Bu repo **kütüphane + CLI** olarak tasarlandı. Eski monorepo'da VB.NET tarafının (`ModMega_SignersInProcess`, `ModMega_Sign_Operations`) kullandığı entegrasyon **in-process** çağrıdır — `Process.Start("SignerCli.exe sign-p11 …")` shell-out değil:

- ~150ms latency tasarrufu (process spawn yok)
- Native exception propagation
- Customer paketinde `SignerCli.exe` + sibling DLL'ler artık gerekmiyor

Yeni bir uygulamadan bu kütüphaneyi kullanırken: `Pkcs11SigningKey` / `WindowsCspSigningKey` / `PfxSigningKey`'i doğrudan `new` ile yarat ve `EDefterSigner.SignFile(...)` çağır. Çift imza (`Accountant` önce, `Supplier` sonra) sırası mevzuat gereği; `XAdesSignerOptions.ClaimedRole` ile her imzaya `Supplier` veya `Accountant` rolü gömülür.

---

## 8. Özet Kullanım Senaryoları

| Senaryo | Sınıf | Sertifika kaynağı |
|---|---|---|
| Üretim — fiziksel USB token | `Pkcs11SigningKey` | `akisp11.dll` üzerinden token |
| RDP / VM (token client'ta, host'ta yok) | `WindowsCspSigningKey` | Windows cert store (AKİS Manager'ın yansıttığı) |
| Geliştirme / test / CLI | `PfxSigningKey` | PFX/P12 dosyası |
| **HSM — PKCS#11** (sertifika dosyada) | `Pkcs11SigningKey` + `Pkcs11KeyOptions` | `.cer/.pem` dosyası; anahtar cihazda (`KeyLabel`/`KeyIdHex`) — bkz. §3.1 |
| **HSM — Windows KSP/CSP kurulu** | `WindowsCspSigningKey` (`--provider <KSP>`) | HSM partition — bkz. [docs/hsm-poc-csp.md](docs/hsm-poc-csp.md) |

### Hızlı başlangıç

```csharp
// 1) PFX ile imza (test)
using var key = new PfxSigningKey("test.pfx", "1234");
new EDefterSigner(key).SignFile("in.xml", "out.signed.xml");

// 2) USB token ile imza (üretim)
using var key = new Pkcs11SigningKey(
    driverPath: CardDriverRegistry.AutoDetect(),
    tokenSerial: null,
    pin: "123456");
new EDefterSigner(key, new XAdesSignerOptions { ClaimedRole = "Supplier" })
    .SignFile("in.xml", "out.signed.xml");

// 3) HSM ile imza — sertifika dosyadan, anahtar cihazdan (bkz. §3.1)
using var key = new Pkcs11SigningKey(new Pkcs11KeyOptions
{
    DriverPath = @"C:\Program Files\SafeNet\LunaClient\cryptoki.dll",
    Pin = "123456",
    CertificateFile = "mali-muhur.cer",
    KeyLabel = "edefter-signer",
});
new EDefterSigner(key, new XAdesSignerOptions { ClaimedRole = "Supplier" })
    .SignFile("in.xml", "out.signed.xml");

// 4) Doğrulama (hızlı)
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load("out.signed.xml");
bool ok = XAdesBesSigner.Verify(doc);

// 5) Doğrulama (EU DSS eşdeğeri)
var report = new DssEquivalentValidator().Validate(doc, "out.signed.xml");
bool dssOk = report.OverallIndication == DssIndication.TotalPassed;
```

### CLI ile kullanım

```bash
# Build
dotnet build -c Release

# PFX ile imzala
dotnet run --project tools/SignerCli -- sign-pfx \
    --pfx test.pfx --password 1234 \
    --in samples/sample_unsigned_berat.xml \
    --out berat.signed.xml \
    --role Supplier

# USB token ile imzala
dotnet run --project tools/SignerCli -- sign-p11 \
    --in yev.xml --out yev.signed.xml --pin 123456

# HSM ile imzala (partition seçimi + şifreli PIN dosyası + anahtar cihazda)
dotnet run --project tools/SignerCli -- sign-p11 \
    --driver "C:\Program Files\SafeNet\LunaClient\cryptoki.dll" \
    --slot-label "edefter-partition" --strict-slot \
    --pin-file C:\ProgramData\mm.erp\hsm.pin \
    --cert-file mali-muhur.cer --key-label "edefter-signer" \
    --in yev.xml --out yev.signed.xml

# Windows cert store ile imzala (RDP)
dotnet run --project tools/SignerCli -- sign-csp \
    --in yev.xml --out yev.signed.xml --vkn 1234567890

# Anahtar/provider tanılama — hangi KSP/CSP anahtarı tutuyor? (HSM devreye alma)
dotnet run --project tools/SignerCli -- csp-info
dotnet run --project tools/SignerCli -- csp-info --provider "Luna" --test-sign

# Doğrula (hızlı)
dotnet run --project tools/SignerCli -- verify --in yev.signed.xml

# Doğrula (DSS eşdeğeri, tam rapor)
dotnet run --project tools/SignerCli -- verify --in yev.signed.xml --mode dss
```

---

## 9. SOAP / WS-Security Notu

Bu repo **e-Defter dosya imzasını** kapsar — yani GİB'e gönderilecek `*.xml` zarfları. **GİB web servisi (Ma3Api SOAP)** çağrıları için WS-Security imzası ayrı bir katmandır ve bu repoda bulunmaz (orijinal monorepo'daki `MegaeDefter4Net.GibClient.WsSecurity` ailesi). Aynı `ISigningKey` soyutlaması kullanılabilir; ama SOAP envelope üzerinde çalışan ayrı `SoapMessageSigner` gerekir. İhtiyaç olursa ileride bu repo'ya da eklenebilir.

---

## 10. Web Sitesi İletişim Formu — ERP Posta Rölesi (Y-316)

`src/MegaeDefter4Net.MmWeb` (mmweb.erp.tr vitrini) `/api/contact` ucunun gönderim kararı
`Iletisim/ContactMailer.cs`'de: `appsettings`'teki **`ErpPosta`** bölümü (`BaseUrl` + `ApiKey`)
doluysa talep ERP Posta uygulama rölesine (`POST /api/v1/send`, amaç **`contact-form`**) gider;
boşsa eski **`Smtp`** bölümü üzerinden doğrudan SMTP ile gönderilir. Karar her istekte güncel
ayardan verilir (yeniden başlatma gerekmez); röle hatası (`PostaRoleHatasi`) SMTP'ye
düşürülmez — kullanıcıya kısa Türkçe hata döner, talep EventLog'a da düşer.

Röle yolunda gönderen kutuyu ERP Posta'nın `/gonderim-eslesme` eşleşmesi belirlediğinden
Reply-To taşınmaz; ziyaretçinin e-postası bu yüzden gövdede açıkça yer alır
(`IletisimFormu.Govde`). Alıcılar: `Contact:To` (zorunlu — boşsa hem röle hem SMTP yolunda
erken "Sunucu konfigürasyonu eksik" hatası döner) ve isteğe bağlı `Contact:Cc`.

Testler: `tests/MegaeDefter4Net.Signing.Tests/Web/ContactMailerTests.cs` (röle yok → eski yol,
`Contact:To` boş → erken dönüş, röle → doğru uç/amaç/alıcı, röle hatası SMTP'ye düşmez).

---

## Proje Yapısı

```
mm.erp/
├── src/
│   ├── MegaeDefter4Net.Crypto/         # ISigningKey + XAdES motoru
│   ├── MegaeDefter4Net.Signing/        # EDefterSigner + PfxSigningKey
│   ├── MegaeDefter4Net.SmartCard/      # Pkcs11SigningKey + WindowsCspSigningKey
│   ├── MegaeDefter4Net.Validation/     # DssEquivalentValidator + XAdesEnvelopeValidator + ...
│   └── MegaeDefter4Net.MmWeb/          # mmweb.erp.tr vitrini (/api/contact → ERP Posta rölesi, Y-316)
├── tools/
│   ├── SignerCli/                      # CLI: sign-pfx / sign-p11 / sign-csp / verify
│   └── Saxon/                          # saxon-he.jar (SchematronRunner için)
├── tests/
│   └── MegaeDefter4Net.Signing.Tests/  # xUnit
├── resources/
│   ├── kamusm-trusted-cas.json         # KamuSM kök CA listesi
│   ├── xsd/                            # GİB + XAdES şemaları
│   ├── sch/                            # GİB schematron kuralları
│   └── xslt/                           # Yevmiye / Kebir / Berat HTML görünüm
├── samples/                            # Örnek imzasız / imzalı XML'ler
├── docs/
│   ├── YAPILACAKLAR.md                 # Yaşayan backlog (HSM planı §1)
│   └── hsm-poc-csp.md                  # HSM PoC runbook (KSP yolu)
└── MegaeDefter.Signing.slnx
```

---

## Build & Test

```bash
# Bağımlılıkları çek
dotnet restore

# Tümünü derle
dotnet build -c Release

# Birim testler
dotnet test
```

Hedef: .NET 10 (`net10.0`). Bağımlılıklar:

- `BouncyCastle.Cryptography 2.4.0` — RFC3161 timestamp parsing
- `Pkcs11Interop 5.1.2` — USB token PKCS#11 erişimi
- `System.Security.Cryptography.Xml 10.0.12` — XML DSig

---

## Lisans / Telif

İç kullanım. Üretim ortamı kararları ve yorum blokları, gerçek GİB red/kabul karşılaştırmalarına dayanır — kaynak kodu yorumlarındaki tarih + firma + VKN referansları **silinmemelidir**; bunlar regresyon zırhıdır.
