SigningMaterialFactory.java

package io.mersel.dss.signer.api.services;

import io.mersel.dss.signer.api.models.SigningContext;
import io.mersel.dss.signer.api.models.SigningKeyEntry;
import io.mersel.dss.signer.api.models.SigningMaterial;
import io.mersel.dss.signer.api.services.certificate.CertificateChainBuilderService;
import io.mersel.dss.signer.api.services.certificate.CertificateValidatorService;
import io.mersel.dss.signer.api.services.keystore.KeyStoreLoaderService;
import io.mersel.dss.signer.api.services.keystore.KeyStoreProvider;
import io.mersel.dss.signer.api.services.keystore.iaik.IaikPkcs11Module;
import io.mersel.dss.signer.api.services.keystore.iaik.Pkcs11Signer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;

/**
 * {@link SigningContext} oluşturan fabrika. İki backend'i tek API'nin
 * arkasına alır:
 *
 * <ul>
 *   <li>{@link #createPfxSigningContext} — JCA {@link KeyStore} +
 *       {@link PrivateKey} kullanır. PFX/PKCS#12 dosyaları için ideal.</li>
 *   <li>{@link #createPkcs11SigningContext} — {@link IaikPkcs11Module}
 *       üzerinden HSM'de C_FindObjects + C_Sign çağırır; SunPKCS11'in
 *       P11KeyStore alias-mapping katmanını tamamen by-pass eder.</li>
 * </ul>
 *
 * <p>İki yol da {@link SigningContext} döner; çağıran kod
 * ({@link io.mersel.dss.signer.api.config.SignatureConfiguration}) hangi
 * backend'in yapılandırıldığına bakıp birini çağırır.</p>
 */
@Service
public class SigningMaterialFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(SigningMaterialFactory.class);

    private final KeyStoreLoaderService keyStoreLoader;
    private final CertificateChainBuilderService chainBuilder;
    private final CertificateValidatorService certificateValidator;

    public SigningMaterialFactory(KeyStoreLoaderService keyStoreLoader,
                                 CertificateChainBuilderService chainBuilder,
                                 CertificateValidatorService certificateValidator) {
        this.keyStoreLoader = keyStoreLoader;
        this.chainBuilder = chainBuilder;
        this.certificateValidator = certificateValidator;
    }

    /**
     * PFX / PKCS#12 yolunda {@link SigningContext} üretir. JCA {@link KeyStore}
     * yüklenir, {@link PrivateKey} alınır, zincir derlenir.
     */
    public SigningContext createPfxSigningContext(KeyStoreProvider provider,
                                                  char[] pin,
                                                  String certificateAlias,
                                                  String certificateSerialNumber) {
        try {
            LOGGER.info("{} keystore kullanılarak (PFX yolu) signing context oluşturuluyor",
                provider.getType());

            KeyStore keyStore = keyStoreLoader.loadKeyStore(provider, pin);
            SigningKeyEntry keyEntry = keyStoreLoader.resolveKeyEntry(
                keyStore, provider, pin, certificateAlias, certificateSerialNumber);

            PrivateKey privateKey = keyEntry.getEntry().getPrivateKey();
            X509Certificate certificate = (X509Certificate) keyEntry.getEntry().getCertificate();

            certificateValidator.validateCertificateDates(certificate);
            List<X509Certificate> chain = chainBuilder.buildCertificateChain(certificate);
            SigningMaterial material = new SigningMaterial(privateKey, certificate, chain);

            LOGGER.info("PFX signing context hazır. Sertifika: {}, Zincir uzunluğu: {}",
                certificate.getSubjectX500Principal(), chain.size());

            return new SigningContext(keyEntry.getAlias(), material);

        } catch (Exception e) {
            LOGGER.error("PFX signing context oluşturulamadı", e);
            throw new io.mersel.dss.signer.api.exceptions.KeyStoreException(
                "PFX signing context oluşturulamadı: " + e.getMessage(), e);
        }
    }

    /**
     * HSM / PKCS#11 yolunda IAIK üzerinden {@link SigningContext} üretir.
     * {@link IaikPkcs11Module#findSigner} ile token'da private key + cert
     * çözülür; private key referansı HSM'de kalır.
     */
    public SigningContext createPkcs11SigningContext(IaikPkcs11Module module,
                                                     String certificateAlias,
                                                     String certificateSerialNumber) {
        try {
            LOGGER.info("IAIK PKCS#11 modülü kullanılarak (HSM yolu) signing context oluşturuluyor");

            Pkcs11Signer signer = module.findSigner(certificateAlias, certificateSerialNumber);
            X509Certificate certificate = signer.getCertificate();

            certificateValidator.validateCertificateDates(certificate);
            List<X509Certificate> chain = chainBuilder.buildCertificateChain(certificate);
            SigningMaterial material = new SigningMaterial(signer, certificate, chain);

            LOGGER.info("HSM signing context hazır. Sertifika: {}, Zincir uzunluğu: {}, alias: {}",
                certificate.getSubjectX500Principal(), chain.size(), signer.getAlias());

            return new SigningContext(signer.getAlias(), material);

        } catch (Exception e) {
            LOGGER.error("HSM signing context oluşturulamadı", e);
            throw new io.mersel.dss.signer.api.exceptions.KeyStoreException(
                "HSM signing context oluşturulamadı: " + e.getMessage(), e);
        }
    }
}