AbstractKamuSMXmlDepoResolver.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 io.mersel.dss.signer.api.util.xml.SecureXmlFactories;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* KamuSM XML Depo Resolver için ortak base class
* Online ve Offline resolver'lar için ortak metodları içerir
*/
public abstract class AbstractKamuSMXmlDepoResolver implements TrustedRootCertificateResolver {
protected static final Logger logger = LoggerFactory.getLogger(AbstractKamuSMXmlDepoResolver.class);
protected final AtomicReference<List<X509Certificate>> trustedRoots = new AtomicReference<>(Collections.emptyList());
protected final AtomicReference<List<CertificateToken>> trustedRootTokens = new AtomicReference<>(Collections.emptyList());
protected CommonTrustedCertificateSource trustedCertificateSource;
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* XML içeriğini yükler (subclass'lar implement eder)
*/
protected abstract String loadRepositoryXml() throws Exception;
/**
* XML içeriğinden sertifikaları parse eder
*/
protected List<X509Certificate> parseCertificates(String xmlBody) throws Exception {
// KamuSM legacy XML formatı namespace-aware değil; SecureXmlFactories
// namespaceAware=false varyantı ile çağrılıyor, XXE/DTD/entity vektörleri
// yine kapalı (DOCTYPE reddedilir).
DocumentBuilderFactory dbf = SecureXmlFactories.newDocumentBuilderFactory(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document document = builder.parse(new ByteArrayInputStream(xmlBody.getBytes()));
List<String> values = new ArrayList<String>();
// KamuSM XML formati: Her <koksertifika> altinda <mValue> tag'i var
NodeList kokNodes = document.getElementsByTagName("koksertifika");
for (int i = 0; i < kokNodes.getLength(); i++) {
Element kokElement = (Element) kokNodes.item(i);
// Sadece <mValue> tag'ini al
NodeList mValueNodes = kokElement.getElementsByTagName("mValue");
if (mValueNodes.getLength() > 0) {
String certBase64 = mValueNodes.item(0).getTextContent();
values.add(certBase64);
} else {
logger.warn("koksertifika #{} icin mValue bulunamadi, atlanıyor", i + 1);
}
}
List<X509Certificate> certificates = new ArrayList<X509Certificate>();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
for (int i = 0; i < values.size(); i++) {
String base64 = values.get(i);
if (base64 == null || base64.isEmpty()) {
continue;
}
// Base64 76 kolon formatinda olabilir (satir sonlari, bosluklar var)
// Tum whitespace karakterlerini temizle
String normalized = base64.replaceAll("\\s+", "");
if (normalized.isEmpty()) {
continue;
}
try {
// Base64.getMimeDecoder() 76 kolon formatini otomatik handle eder
byte[] der = Base64.getMimeDecoder().decode(normalized);
X509Certificate certificate = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(der));
certificates.add(certificate);
logger.debug("Kok sertifika #{} basariyla parse edildi", i + 1);
} catch (Exception e) {
logger.warn("Kok sertifika #{} parse edilemedi, atlaniyor: {}", i + 1, e.getMessage());
}
}
logger.info("Toplam {} adet kok sertifika parse edildi", certificates.size());
return certificates;
}
/**
* Trusted certificate source'u gunceller
*/
protected void updateTrustedCertificateSource() {
if (trustedCertificateSource == null) {
trustedCertificateSource = new CommonTrustedCertificateSource();
}
// KamuSM sertifikalarini 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);
}
}