TimestampConfigurationService.java

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

import eu.europa.esig.dss.service.http.commons.TimestampDataLoader;
import eu.europa.esig.dss.service.tsp.OnlineTSPSource;
import io.mersel.dss.signer.api.exceptions.TimestampException;
import io.mersel.dss.signer.api.services.timestamp.tubitak.TubitakTimestampDataLoader;
import io.mersel.dss.signer.api.services.timestamp.tubitak.TubitakTspDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.net.URI;

/**
 * Zaman damgası sunucularını yapılandıran ve yöneten servis.
 * <p>
 * Standart RFC 3161 TSP sunucularının yanı sıra TÜBİTAK ESYA
 * zaman damgası sunucusunu da destekler.
 */
@Service
public class TimestampConfigurationService {

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

    private final String tspServerUrl;
    private final String tspUserId;
    private final String tspUserPassword;
    private final boolean isTubitakTsp;
    
    private volatile OnlineTSPSource tspSource;
    private volatile boolean configured = false;

    public TimestampConfigurationService(
            @Value("${TS_SERVER_HOST:}") String tspServerUrl,
            @Value("${TS_USER_ID:}") String tspUserId,
            @Value("${TS_USER_PASSWORD:}") String tspUserPassword,
            @Value("${IS_TUBITAK_TSP:false}") boolean isTubitakTsp) {
        this.tspServerUrl = tspServerUrl;
        this.tspUserId = tspUserId;
        this.tspUserPassword = tspUserPassword;
        this.isTubitakTsp = TubitakTspDetector.resolveTubitakTspMode(isTubitakTsp, tspServerUrl);

        if (!isTubitakTsp && this.isTubitakTsp) {
            LOGGER.info("IS_TUBITAK_TSP explicit olarak set edilmemiş, ancak TS_SERVER_HOST " +
                    "({}) KamuSM zaman damgası endpoint'i; TÜBİTAK modu otomatik aktif edildi.",
                    tspServerUrl);
        }
    }

    /**
     * TSP kaynağını yapılandırır ve döndürür.
     * Yapılandırma bir kez yapılır ve cache'lenir.
     * 
     * @return Yapılandırılmış OnlineTSPSource
     * @throws TimestampException Yapılandırma başarısız olursa
     */
    public OnlineTSPSource getTspSource() {
        if (!StringUtils.hasText(tspServerUrl)) {
            throw new TimestampException(
                "Timestamp sunucu URL'si yapılandırılmamış. TS_SERVER_HOST property'sini ayarlayın.");
        }

        if (configured) {
            return tspSource;
        }

        synchronized (this) {
            if (configured) {
                return tspSource;
            }

            try {
                TimestampDataLoader dataLoader;
                
                if (isTubitakTsp) {
                    dataLoader = configureTubitakAuthentication();
                } else {
                    dataLoader = new TimestampDataLoader();
                    if (StringUtils.hasText(tspUserId)) {
                        configureStandardAuthentication(dataLoader);
                    }
                }

                tspSource = new OnlineTSPSource(tspServerUrl, dataLoader);
                configured = true;

                LOGGER.info("Timestamp sunucusu yapılandırıldı: {} (Tip: {})", 
                        tspServerUrl, isTubitakTsp ? "TÜBİTAK" : "Standart");
                return tspSource;

            } catch (Exception e) {
                throw new TimestampException(
                    "Timestamp sunucusu yapılandırılamadı: " + tspServerUrl, e);
            }
        }
    }

    /**
     * Timestamp servisinin kullanılabilir ve yapılandırılmış olup olmadığını kontrol eder.
     */
    public boolean isAvailable() {
        return StringUtils.hasText(tspServerUrl);
    }

    /**
     * TÜBİTAK zaman damgası sunucusu için DataLoader yapılandırır.
     */
    private TimestampDataLoader configureTubitakAuthentication() {
        if (!StringUtils.hasText(tspUserId)) {
            throw new TimestampException(
                "TÜBİTAK TSP için kullanıcı ID gerekli. TS_USER_ID ayarlayın.");
        }
        
        if (!StringUtils.hasText(tspUserPassword)) {
            throw new TimestampException(
                "TÜBİTAK TSP için parola gerekli. TS_USER_PASSWORD ayarlayın.");
        }

        try {
            int customerId = Integer.parseInt(tspUserId);
            TubitakTimestampDataLoader dataLoader = new TubitakTimestampDataLoader(
                    customerId,
                    tspUserPassword
            );
            
            LOGGER.info("TÜBİTAK timestamp yapılandırıldı. Kullanıcı ID: {}", customerId);
            return dataLoader;
            
        } catch (NumberFormatException e) {
            throw new TimestampException(
                "Kullanıcı ID sayısal olmalı: " + tspUserId, e);
        }
    }

    /**
     * Standart HTTP Basic Auth yapılandırır.
     */
    private void configureStandardAuthentication(TimestampDataLoader dataLoader) {
        try {
            URI tspUri = URI.create(tspServerUrl);
            int port = tspUri.getPort();
            
            if (port < 0) {
                port = "https".equalsIgnoreCase(tspUri.getScheme()) ? 443 : 80;
            }

            char[] password = StringUtils.hasText(tspUserPassword)
                ? tspUserPassword.toCharArray()
                : new char[0];

            dataLoader.addAuthentication(
                tspUri.getHost(), 
                port, 
                tspUri.getScheme(),
                tspUserId, 
                password);
            dataLoader.setPreemptiveAuthentication(true);

            LOGGER.debug("HTTP Basic Auth yapılandırıldı. Kullanıcı: {}", tspUserId);

        } catch (Exception e) {
            LOGGER.warn("Kimlik doğrulama yapılandırılamadı: {}", e.getMessage());
        }
    }
}