SignatureApplication.java

package io.mersel.dss.signer.api;

import io.mersel.dss.signer.api.dtos.CertificateInfoDto;
import io.mersel.dss.signer.api.services.CertificateInfoService;
import io.mersel.dss.signer.api.services.keystore.KeyStoreProvider;
import io.mersel.dss.signer.api.services.keystore.PKCS11KeyStoreProvider;
import io.mersel.dss.signer.api.services.keystore.PfxKeyStoreProvider;
import io.mersel.dss.signer.api.services.keystore.iaik.IaikPkcs11Module;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * Dijital İmza Servisi API'nin ana uygulaması.
 * <p>
 * XAdES, PAdES ve WS-Security imzalama servisleri sağlar.
 * <p>
 * Command-line kullanım:
 * - java -jar mersel-dss-signer.jar                    : API sunucusunu başlatır
 * - java -jar mersel-dss-signer.jar --list-certificates : Keystore sertifikalarını listeler
 * - java -jar mersel-dss-signer.jar --help             : Yardım mesajını gösterir
 */
@SpringBootApplication
@EnableScheduling
public class SignatureApplication {

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

    public static final String FileSeparator = System.getProperty("file.separator");
    public static final String ROOT_FILE_FOLDER = ".mersel-signature-service";
    public static final String ROOT_DIR = System.getProperty("user.home") + FileSeparator + ROOT_FILE_FOLDER + FileSeparator;

    public static void main(String[] args) {
        // Command-line argümanlarını kontrol et
        if (args.length > 0) {
            String command = args[0];

            if ("--list-certificates".equals(command) || "--list-certs".equals(command)) {
                listCertificates();
                System.exit(0);
                return;
            } else if ("--help".equals(command) || "-h".equals(command)) {
                printHelp();
                System.exit(0);
                return;
            } else if ("--version".equals(command) || "-v".equals(command)) {
                printVersion();
                System.exit(0);
                return;
            }
        }

        // Normal Spring Boot başlatma
        LOGGER.info("Mersel DSS Signer API başlatılıyor...");
        LOGGER.info("Log dizini: {}", System.getProperty("LOG_PATH", "./logs"));

        SpringApplication.run(SignatureApplication.class, args);

        LOGGER.info("Mersel DSS Signer API başarıyla başlatıldı");
    }

    /**
     * Keystore'daki sertifikaları listeler.
     * Spring context olmadan çalışır.
     */
    private static void listCertificates() {
        System.out.println("\n🔐 Mersel DSS Signer - Certificate Lister\n");

        try {
            // Environment variable'lardan yapılandırmayı oku
            String pkcs11Library = System.getenv("PKCS11_LIBRARY");
            String pkcs11SlotListIndexStr = System.getenv("PKCS11_SLOT_LIST_INDEX");
            String pkcs11SlotStr = System.getenv("PKCS11_SLOT");
            // AKİS / TÜBİTAK uyumluluk: bkz. IaikPkcs11Module Javadoc.
            // Auto-detect zaten CKR_ARGUMENTS_BAD'da devreye girer; bu env var
            // operatöre "doğrudan NULL-args'a git" demek için escape hatch.
            String pkcs11NullInitArgsStr = System.getenv("PKCS11_NULL_INIT_ARGS");
            String pfxPath = System.getenv("PFX_PATH");
            String pin = System.getenv("CERTIFICATE_PIN");

            if (pin == null || pin.isEmpty()) {
                System.err.println("❌ CERTIFICATE_PIN environment variable tanımlanmamış!");
                System.err.println("\nÖrnek:");
                System.err.println("  export CERTIFICATE_PIN=yourpin");
                System.exit(1);
                return;
            }

            KeyStoreProvider provider;
            IaikPkcs11Module iaikModule = null;

            // StringUtils.hasText: null / "" / "   " (whitespace) hepsini boş
            // sayar — Spring config tarafındaki @ConditionalOnExpression ile
            // tutarlı. Aksi halde PKCS11_LIBRARY="   " gibi yanlış env var
            // çağrısı CLI'da PKCS#11 yoluna sapar ve IaikPkcs11Module ctor
            // anlamsız bir hata atar.
            if (StringUtils.hasText(pkcs11Library)) {
                Long slot = NumberUtils.isDigits(pkcs11SlotStr) ? Long.parseLong(pkcs11SlotStr) : null;
                Long slotIndex = NumberUtils.isDigits(pkcs11SlotListIndexStr) ? Long.parseLong(pkcs11SlotListIndexStr) : null;
                slot = slot != null && slot >= 0 ? slot : -1;
                slotIndex = slotIndex != null && slotIndex >= 0 ? slotIndex : -1;


                boolean forceNullInitArgs = "true".equalsIgnoreCase(
                    pkcs11NullInitArgsStr == null ? "" : pkcs11NullInitArgsStr.trim());

                System.out.println("📦 Keystore Type: PKCS#11 (IAIK)");
                System.out.println("📂 Library: " + pkcs11Library);
                System.out.println("🎰 Slot: " + (slot != null ? slot : "<unset>"));
                System.out.println("🎰 Slot List Index: " + (slotIndex != null ? slotIndex : "<unset>"));
                if (forceNullInitArgs) {
                    System.out.println("⚙️  Init args: NULL (AKİS / TÜBİTAK uyumluluk modu)");
                }
                System.out.println();

                // PKCS#11 yolunda IAIK Module ile listeliyoruz; SunPKCS11
                // alias-mapping katmanından bağımsız. Module manuel olarak
                // initialize edilip listing sonunda kapatılır.
                iaikModule = new IaikPkcs11Module(
                    pkcs11Library, slot, slotIndex, pin.toCharArray(), forceNullInitArgs);
                iaikModule.afterPropertiesSet();
                // KeyStoreProvider sadece getType()/info için referans olarak duruyor.
                provider = new PKCS11KeyStoreProvider(
                        pkcs11Library,
                        slot != null ? slot : -1L,
                        slotIndex != null ? slotIndex : -1L);

            } else if (StringUtils.hasText(pfxPath)) {
                System.out.println("📦 Keystore Type: PFX/PKCS12");
                System.out.println("📂 Path: " + pfxPath);
                System.out.println();

                provider = new PfxKeyStoreProvider(pfxPath);

            } else {
                System.err.println("❌ Ne PKCS11_LIBRARY ne de PFX_PATH tanımlanmamış!");
                System.err.println("\nPKCS#11 için:");
                System.err.println("  export PKCS11_LIBRARY=/usr/local/lib/libakisp11.dylib");
                System.err.println("  export PKCS11_SLOT=0");
                System.err.println("  export CERTIFICATE_PIN=yourpin");
                System.err.println("\nPFX için:");
                System.err.println("  export PFX_PATH=/path/to/certificate.pfx");
                System.err.println("  export CERTIFICATE_PIN=yourpassword");
                System.exit(1);
                return;
            }

            try {
                CertificateInfoService service = new CertificateInfoService(iaikModule);
                List<CertificateInfoDto> certificates = service.listCertificates(provider, pin.toCharArray());
                service.printCertificates(certificates);
            } finally {
                if (iaikModule != null) {
                    iaikModule.destroy();
                }
            }

        } catch (Exception e) {
            System.err.println("\n❌ Hata: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Yardım mesajını gösterir.
     */
    private static void printHelp() {
        System.out.println("\n🔐 Mersel DSS Signer API - Dijital İmza Servisi\n");
        System.out.println("Kullanım:");
        System.out.println("  java -jar mersel-dss-signer.jar [SEÇENEK]\n");
        System.out.println("Seçenekler:");
        System.out.println("  (seçeneksiz)           API sunucusunu başlatır (varsayılan port: 8085)");
        System.out.println("  --list-certificates    Keystore'daki sertifikaları listeler");
        System.out.println("  --list-certs           (kısa versiyon)");
        System.out.println("  --help, -h             Bu yardım mesajını gösterir");
        System.out.println("  --version, -v          Versiyon bilgisini gösterir\n");
        System.out.println("Environment Variables:");
        System.out.println("  PKCS#11 Keystore:");
        System.out.println("    PKCS11_LIBRARY          PKCS#11 kütüphane yolu");
        System.out.println("    PKCS11_SLOT             Slot numarası (varsayılan: 0)");
        System.out.println("    PKCS11_NULL_INIT_ARGS   AKİS / TÜBİTAK uyumluluk modu (true/false)");
        System.out.println("                            Sürücü CKR_ARGUMENTS_BAD dönerse otomatik");
        System.out.println("                            aktive olur; explicit set etmek operatöre");
        System.out.println("                            trial-and-error'u atlatır.");
        System.out.println("    CERTIFICATE_PIN         PIN kodu\n");
        System.out.println("  PFX Keystore:");
        System.out.println("    PFX_PATH                PFX dosya yolu");
        System.out.println("    CERTIFICATE_PIN         Şifre\n");
        System.out.println("  Sertifika Seçimi (İsteğe bağlı):");
        System.out.println("    CERTIFICATE_ALIAS            Sertifika alias'ı");
        System.out.println("    CERTIFICATE_SERIAL_NUMBER    Sertifika seri numarası (hex)\n");
        System.out.println("Örnekler:");
        System.out.println("  # PKCS#11 sertifikalarını listele");
        System.out.println("  export PKCS11_LIBRARY=/usr/local/lib/libakisp11.dylib");
        System.out.println("  export PKCS11_SLOT=0");
        System.out.println("  export CERTIFICATE_PIN=1234");
        System.out.println("  java -jar mersel-dss-signer.jar --list-certificates\n");
        System.out.println("  # PFX sertifikalarını listele");
        System.out.println("  export PFX_PATH=/path/to/certificate.pfx");
        System.out.println("  export CERTIFICATE_PIN=password");
        System.out.println("  java -jar mersel-dss-signer.jar --list-certs\n");
        System.out.println("Dokümantasyon:");
        System.out.println("  https://github.com/mersel-dss/mersel-dss-server-signer-java\n");
    }

    /**
     * Versiyon bilgisini gösterir.
     */
    private static void printVersion() {
        String version = SignatureApplication.class.getPackage().getImplementationVersion();
        if (version == null) {
            version = "development";
        }
        System.out.println("Mersel DSS Signer API v" + version);
    }
}