CertificateFolderResolver.java
package io.mersel.dss.signer.api.services;
import eu.europa.esig.dss.model.x509.CertificateToken;
import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Certificate Folder Resolver
* Belirtilen klasördeki tüm .crt ve .cer dosyalarını güvenilir kök sertifika olarak yükler.
* Klasördeki tüm sertifika dosyalarını tarar ve yükler.
*/
@Service("certificateFolderResolver")
public class CertificateFolderResolver implements TrustedRootCertificateResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(CertificateFolderResolver.class);
private final ResourceLoader resourceLoader;
private final String folderPath;
private final AtomicReference<List<X509Certificate>> trustedRoots = new AtomicReference<>(Collections.emptyList());
private final AtomicReference<List<CertificateToken>> trustedRootTokens = new AtomicReference<>(Collections.emptyList());
private CommonTrustedCertificateSource trustedCertificateSource;
static {
Security.addProvider(new BouncyCastleProvider());
}
public CertificateFolderResolver(ResourceLoader resourceLoader,
@Value("${trusted.root.cert.folder.path:}") String folderPath) {
this.resourceLoader = resourceLoader;
// Path'teki baştaki ve sondaki tırnakları temizle (Spring properties'te çift tırnak kullanımı için)
if (folderPath != null) {
folderPath = folderPath.trim();
// Çift tırnak veya tek tırnak ile başlayıp bitiyorsa kaldır
if ((folderPath.startsWith("\"") && folderPath.endsWith("\"")) ||
(folderPath.startsWith("'") && folderPath.endsWith("'"))) {
folderPath = folderPath.substring(1, folderPath.length() - 1);
}
// Encoding sorununu çöz: Eğer path ISO-8859-1 olarak yanlış okunduysa UTF-8'e çevir
try {
// ISO-8859-1 olarak yanlış okunmuş gibi görünen karakterleri UTF-8'e çevir
// Örnek: "Ãn" -> "Ön", "Hazırlık" -> "Hazırlık"
if (folderPath.contains("Ã") || folderPath.contains("Ä")) {
byte[] bytes = folderPath.getBytes("ISO-8859-1");
String correctedPath = new String(bytes, "UTF-8");
// Eğer düzeltilmiş path Türkçe karakterler içeriyorsa kullan
if (correctedPath.contains("Ö") || correctedPath.contains("ö") ||
correctedPath.contains("ı") || correctedPath.contains("İ") ||
correctedPath.contains("ş") || correctedPath.contains("Ş") ||
correctedPath.contains("ğ") || correctedPath.contains("Ğ") ||
correctedPath.contains("ü") || correctedPath.contains("Ü") ||
correctedPath.contains("ç") || correctedPath.contains("Ç")) {
folderPath = correctedPath;
LOGGER.debug("Path encoding düzeltildi: {}", folderPath);
}
}
} catch (Exception e) {
LOGGER.debug("Path encoding düzeltme hatası: {}", e.getMessage());
}
}
this.folderPath = folderPath;
}
@Override
public void refreshTrustedRoots() {
// Mevcut sertifikaları sakla (başarısız olursa geri yüklemek için)
List<X509Certificate> previousRoots = new ArrayList<>(trustedRoots.get());
List<CertificateToken> previousTokens = new ArrayList<>(trustedRootTokens.get());
try {
if (folderPath == null || folderPath.trim().isEmpty()) {
LOGGER.warn("Sertifika klasoru yolu belirtilmemiş. Sertifika yüklenemiyor.");
return;
}
LOGGER.info("Sertifika klasorunden güvenilir kök sertifikalar yükleniyor: {}", folderPath);
// Klasör yolunu çöz
File certFolder = resolveFolderPath(folderPath);
if (certFolder == null || !certFolder.exists() || !certFolder.isDirectory()) {
LOGGER.warn("Sertifika klasoru bulunamadi veya bir dizin degil: {}", folderPath);
return;
}
List<X509Certificate> certificates = loadCertificatesFromFolder(certFolder);
if (certificates.isEmpty()) {
LOGGER.warn("Klasorde sertifika bulunamadi: {}", folderPath);
return;
}
List<CertificateToken> tokens = new ArrayList<CertificateToken>(certificates.size());
for (X509Certificate certificate : certificates) {
tokens.add(new CertificateToken(certificate));
}
trustedRoots.set(Collections.unmodifiableList(certificates));
trustedRootTokens.set(Collections.unmodifiableList(tokens));
LOGGER.info("Klasorden {} adet güvenilir kök sertifika yuklendi", certificates.size());
// Trusted certificate source'u da guncelle
updateTrustedCertificateSource();
} catch (Exception ex) {
LOGGER.error("Sertifika klasorunden yukleme basarisiz: {} - mevcut liste korunuyor", ex.getMessage(), ex);
// Başarısız olursa önceki sertifikaları geri yükle
if (!previousRoots.isEmpty()) {
trustedRoots.set(Collections.unmodifiableList(previousRoots));
trustedRootTokens.set(Collections.unmodifiableList(previousTokens));
LOGGER.info("Onceki sertifikalar geri yuklendi ({} adet)", previousRoots.size());
}
}
}
/**
* Klasör yolunu çözer (file:, classpath: veya direkt path)
* Desteklenen formatlar:
* - file:/path/to/folder (Unix/Linux/Mac)
* - file:/C:/path/to/folder (Windows)
* - /absolute/path/to/folder (Unix/Linux/Mac absolute path)
* - C:\path\to\folder (Windows absolute path)
* - C:/path/to/folder (Windows absolute path with forward slash)
*/
private File resolveFolderPath(String path) {
try {
if (path.startsWith("file:")) {
String filePath = path.substring(5);
// Windows path'lerinde file: prefix'i sonrası /C:/ gibi olabilir, bunu düzelt
if (filePath.startsWith("/") && filePath.length() > 3 && filePath.charAt(2) == ':') {
filePath = filePath.substring(1); // Baştaki /'yi kaldır (file:/C:/ -> C:/)
}
return new File(filePath);
} else if (path.startsWith("classpath:")) {
Resource resource = resourceLoader.getResource(path);
if (resource.exists()) {
return resource.getFile();
}
} else {
// Direkt dosya yolu (absolute path - Unix/Linux/Mac veya Windows)
return new File(path);
}
} catch (Exception e) {
LOGGER.warn("Klasor yolu cozulemedi: {}", e.getMessage());
}
return null;
}
/**
* Klasördeki tüm .crt ve .cer dosyalarını yükler
*/
private List<X509Certificate> loadCertificatesFromFolder(File folder) {
List<X509Certificate> certificates = new ArrayList<X509Certificate>();
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
} catch (Exception e) {
LOGGER.error("CertificateFactory olusturulamadi: {}", e.getMessage());
return certificates;
}
File[] files = folder.listFiles();
if (files == null) {
LOGGER.warn("Klasor okunamadi veya bos: {}", folder.getAbsolutePath());
return certificates;
}
for (File file : files) {
if (file.isFile() && (file.getName().toLowerCase().endsWith(".crt") ||
file.getName().toLowerCase().endsWith(".cer") ||
file.getName().toLowerCase().endsWith(".pem"))) {
try {
try (InputStream is = new FileInputStream(file)) {
X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
certificates.add(cert);
LOGGER.debug("Sertifika yuklendi: {} - Subject: {}",
file.getName(), cert.getSubjectDN());
}
} catch (Exception e) {
LOGGER.warn("Sertifika dosyasi yuklenemedi: {} - {}", file.getName(), e.getMessage());
}
}
}
return certificates;
}
/**
* Trusted certificate source'u gunceller
*/
private void updateTrustedCertificateSource() {
if (trustedCertificateSource == null) {
trustedCertificateSource = new CommonTrustedCertificateSource();
}
// Klasörden yüklenen sertifikaları ekle
for (CertificateToken token : trustedRootTokens.get()) {
trustedCertificateSource.addCertificate(token);
}
LOGGER.info("Trusted certificate source updated with {} certificates",
trustedCertificateSource.getCertificates().size());
}
@Override
public List<X509Certificate> getTrustedRoots() {
return trustedRoots.get();
}
@Override
public List<CertificateToken> getTrustedRootTokens() {
return trustedRootTokens.get();
}
@Override
public CommonTrustedCertificateSource getTrustedCertificateSource() {
if (trustedCertificateSource == null) {
updateTrustedCertificateSource();
}
return trustedCertificateSource;
}
@Override
public void addTrustedCertificate(CertificateToken certificate) {
if (trustedCertificateSource == null) {
trustedCertificateSource = new CommonTrustedCertificateSource();
}
trustedCertificateSource.addCertificate(certificate);
LOGGER.info("Added trusted certificate: {}", certificate.getSubject());
}
@Override
public void addTrustedCertificate(X509Certificate certificate) {
addTrustedCertificate(new CertificateToken(certificate));
}
@Override
public boolean isTrusted(CertificateToken certificate) {
if (trustedCertificateSource == null) {
return false;
}
return trustedCertificateSource.getCertificates().contains(certificate);
}
}