CAdESSignatureService.java

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

import java.io.InputStream;
import java.util.Base64;
import java.util.concurrent.Semaphore;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import eu.europa.esig.dss.cades.CAdESSignatureParameters;
import eu.europa.esig.dss.cades.signature.CAdESService;
import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.enumerations.SignaturePackaging;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.InMemoryDocument;
import eu.europa.esig.dss.model.SignatureValue;
import eu.europa.esig.dss.model.ToBeSigned;
import io.mersel.dss.signer.api.exceptions.SignatureException;
import io.mersel.dss.signer.api.models.SignResponse;
import io.mersel.dss.signer.api.models.SigningMaterial;
import io.mersel.dss.signer.api.services.crypto.CryptoSignerService;
import io.mersel.dss.signer.api.services.crypto.DigestAlgorithmResolverService;

/**
 * CAdES-BES seviyesinde elektronik imza üreten servis.
 *
 * <p>EU DSS (Digital Signature Services) kütüphanesinin {@link CAdESService} altyapısı
 * üzerinden RFC 5652 (CMS) ve ETSI EN 319 122-1 (CAdES Baseline) gereksinimlerine
 * uygun imzalar oluşturur. XAdES imzalama ile aynı mimari kullanılır:</p>
 *
 * <ul>
 *   <li>{@link CAdESService} — CMS zarfı oluşturma, SigningCertificateV2 attribute yönetimi</li>
 *   <li>{@link CryptoSignerService} — HSM-aware kriptografik imzalama</li>
 *   <li>{@link DigestAlgorithmResolverService} — sertifikaya göre digest algoritma çözümleme</li>
 * </ul>
 *
 * <h3>Eşzamanlılık</h3>
 * <p>PKCS#11 (HSM) session havuzlarının tükenmesini engellemek için eş zamanlı imza
 * sayısı bir {@link Semaphore} ile sınırlandırılır.</p>
 *
 * @see CAdESService
 * @see CryptoSignerService
 * @see DigestAlgorithmResolverService
 */
@Service
public class CAdESSignatureService {

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

    private final CAdESService cadesService;
    private final CryptoSignerService cryptoSigner;
    private final DigestAlgorithmResolverService digestAlgorithmResolver;
    private final Semaphore semaphore;

    /**
     * @param cadesService            DSS CAdES imza servisi
     * @param cryptoSigner            HSM-aware kriptografik imzalama servisi
     * @param digestAlgorithmResolver sertifikaya göre digest algoritma çözümleme servisi
     * @param signatureSemaphore      eş zamanlı imza işlemi sayısını kısıtlayan semaphore
     */
    public CAdESSignatureService(CAdESService cadesService,
                                 CryptoSignerService cryptoSigner,
                                 DigestAlgorithmResolverService digestAlgorithmResolver,
                                 Semaphore signatureSemaphore) {
        this.cadesService = cadesService;
        this.cryptoSigner = cryptoSigner;
        this.digestAlgorithmResolver = digestAlgorithmResolver;
        this.semaphore = signatureSemaphore;
    }

    /**
     * Verilen stream'deki veriyi okuyarak CAdES-BES imzası üretir.
     *
     * @param dataInputStream imzalanacak dosyanın stream'i — metot içinde tüketilir, kapatılmaz
     * @param detached        {@code true} → ayrık imza, {@code false} → gömülü imza
     * @param material        sertifika zinciri ve private key'i barındıran imzalama materyali
     * @return imzalanmış byte dizisi ve Base64 kodlanmış imza değerini içeren {@link SignResponse}
     * @throws SignatureException imza oluşturma sırasında herhangi bir hata meydana gelirse
     */
    public SignResponse signData(InputStream dataInputStream,
                                 boolean detached,
                                 SigningMaterial material) {
        try {
            byte[] contentBytes = IOUtils.toByteArray(dataInputStream);
            DSSDocument document = new InMemoryDocument(contentBytes, "document.bin");

            DigestAlgorithm digestAlgorithm =
                    digestAlgorithmResolver.resolveDigestAlgorithm(material.getSigningCertificate());

            CAdESSignatureParameters parameters = buildParameters(detached, digestAlgorithm, material);

            semaphore.acquire();
            try {
                ToBeSigned dataToSign = cadesService.getDataToSign(document, parameters);

                SignatureValue signatureValue = cryptoSigner.sign(
                        dataToSign,
                        material,
                        digestAlgorithm);

                DSSDocument signedDocument = cadesService.signDocument(document, parameters, signatureValue);

                byte[] signedBytes = IOUtils.toByteArray(signedDocument.openStream());
                String encodedSignature = Base64.getEncoder().encodeToString(signatureValue.getValue());

                LOGGER.info("CAdES imzası başarıyla oluşturuldu (detached: {})", detached);
                return new SignResponse(signedBytes, encodedSignature);

            } finally {
                semaphore.release();
            }

        } catch (SignatureException e) {
            throw e;
        } catch (Exception e) {
            LOGGER.error("CAdES imzası oluşturulurken hata", e);
            throw new SignatureException("CADES_SIGN_ERROR", "CAdES imzası oluşturulamadı", e);
        }
    }

    /**
     * DSS {@link CAdESSignatureParameters} nesnesini oluşturur.
     *
     * <p>Signature level {@code CAdES_BASELINE_B} olarak ayarlanır; bu seviye
     * CAdES-BES'e karşılık gelir ve {@code SigningCertificateV2} attribute'ünü
     * otomatik olarak ekler.</p>
     *
     * @param detached        ayrık imza mı
     * @param digestAlgorithm kullanılacak digest algoritması
     * @param material        imzalama materyali
     * @return yapılandırılmış CAdES parametreleri
     */
    private CAdESSignatureParameters buildParameters(boolean detached,
                                                     DigestAlgorithm digestAlgorithm,
                                                     SigningMaterial material) {
        CAdESSignatureParameters parameters = new CAdESSignatureParameters();
        parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B);
        parameters.setSignaturePackaging(
                detached ? SignaturePackaging.DETACHED : SignaturePackaging.ENVELOPING);
        parameters.setDigestAlgorithm(digestAlgorithm);
        parameters.setSigningCertificate(material.getPrimaryCertificateToken());
        parameters.setCertificateChain(material.getCertificateTokens());
        return parameters;
    }
}