XAdESLevelC.java
// @formatter:off
/**
* DSS - Digital Signature Services
* Copyright (C) 2015 European Commission, provided under the CEF programme
* <p>
* This file is part of the "DSS - Digital Signature Services" project.
* <p>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* <p>
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package eu.europa.esig.dss.xades.signature;
import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.model.x509.CertificateToken;
import eu.europa.esig.dss.signature.SignatureRequirementsChecker;
import eu.europa.esig.dss.spi.DSSRevocationUtils;
import eu.europa.esig.dss.spi.x509.ResponderId;
import eu.europa.esig.dss.spi.x509.revocation.crl.CRLToken;
import eu.europa.esig.dss.spi.x509.revocation.ocsp.OCSPToken;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.spi.signature.AdvancedSignature;
import eu.europa.esig.dss.spi.validation.CertificateVerifier;
import eu.europa.esig.dss.spi.validation.ValidationData;
import eu.europa.esig.dss.spi.validation.ValidationDataContainer;
import eu.europa.esig.dss.xades.validation.XAdESSignature;
import eu.europa.esig.dss.xml.utils.DomUtils;
import eu.europa.esig.dss.xades.definition.xades141.XAdES141Element;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.RespID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import javax.xml.datatype.XMLGregorianCalendar;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import static eu.europa.esig.dss.enumerations.SignatureLevel.XAdES_C;
import static eu.europa.esig.dss.enumerations.SignatureLevel.XAdES_XL;
/**
* Contains XAdES-C profile aspects
*
*/
public class XAdESLevelC extends XAdESLevelBaselineT {
private static final Logger LOGGER = LoggerFactory.getLogger(XAdESLevelC.class);
// ################ BLOK BAŞLANGICI (XADES-C,XL GELİŞMELERİ) ################
// DSS-XAdES-C seviye geliştirmeleri için dokümantasyon:
// - OCSP ve CRL doğrulama nesneleri thread-safe olarak ve imza başına özgü olarak cache'lenir.
// - C seviyesi (referans oluşturma) ve XL seviyesi (bileşen gömme) için aynı doğrulama verisi yeniden kullanılır.
// - Tüm detaylar ve örnek iş akışları teknik dokümantasyonda açıklanmıştır.
// ########################OVERRIDE_DSS#########################
// ##### Bu alan, DSS kütüphanesinin XAdES-C seviyesindeki #
// ##### gelişmelerimizi özetler. #
// ##### Tam teknik detaylar dökümantasyonda ayrıca verilecek.#
// #############################################################
//
// DSS kütüphanesi XAdES-C seviyesinde, hem revokasyon verileri
// referanslarının (OCSP/CRL), hem de önbellek/cache yönetiminin
// doğru ve tutarlı şekilde kullanılması hedeflenmiştir.
//
// Bu kapsamda;
// - OCSP ve CRL token'ları thread-safe ve imzaya özel olarak cache'lenmektedir.
// - Aynı veriler, hem referans oluştururken (C seviyesi) hem de gömülü veri eklerken
// (XL seviyesi) tekrar kullanılmaktadır.
// - Bu sayede OCSP/CRL digest eşleşmezliği ve tutarsızlıklar engellenir.
//
// Dökümanda ayrıntılı işleyiş ve örnek akışlar yer almaktadır.
// #############################################################
/**
* Cached validation data container to ensure consistency across signature
* levels (C, XL, A).
* This prevents OCSP/CRL digest mismatches between references (C-level) and
* embedded values (XL-level).
*
* The same OCSP/CRL responses must be used when:
* - Creating references with digests at C-level
* - Embedding actual response binaries at XL-level
*/
protected ValidationDataContainer cachedValidationDataContainer;
/**
* Thread-safe, signature-specific cache for OCSP tokens.
* Outer Map: Signature ID -> Inner cache
* Inner Map: Certificate (base64) -> OCSPToken
*
* This ensures:
* 1. Thread-safety for concurrent signature operations
* 2. Cache isolation between different signatures
* 3. No cache pollution between requests
*/
protected static final java.util.concurrent.ConcurrentHashMap<String, java.util.concurrent.ConcurrentHashMap<String, OCSPToken>> ocspCacheBySignature = new java.util.concurrent.ConcurrentHashMap<>();
/**
* Current signature ID being processed (for cache lookup)
*/
protected String currentSignatureId;
// ################ BLOK BİTTİ (XADES-C,XL GELİŞMELERİ) ################
/**
* The default constructor for XAdESLevelC.
*
* @param certificateVerifier {@link CertificateVerifier}
*/
public XAdESLevelC(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
/**
* This format builds up taking XAdES-T signature and incorporating additional data required for validation:
*
* The sequence of references to the full set of CA certificates that have been used to validate the electronic
* signature up to (but not including ) the signer's certificate.<br>
* A full set of references to the revocation data that have been used in the validation of the signer and CA
* certificates.<br>
* Adds {@code <CompleteCertificateRefs>} and {@code <CompleteRevocationRefs>} segments into
* {@code <UnsignedSignatureProperties>} element.
*
* There SHALL be at most <b>one occurrence of CompleteRevocationRefs and CompleteCertificateRefs</b> properties in
* the signature. Old references must be removed.
*
* @see XAdESLevelBaselineT#extendSignatures(List)
*/
@Override
protected void extendSignatures(List<AdvancedSignature> signatures) {
super.extendSignatures(signatures);
final List<AdvancedSignature> signaturesToExtend = getExtendToCLevelSignatures(signatures);
if (Utils.isCollectionEmpty(signaturesToExtend)) {
return;
}
// ################ BLOK BAŞLANGICI (XADES-C,XL GELİŞMELERİ) ################
// ########################OVERRIDE_DSS#########################
// ##### Bu alan, XAdES-C seviyesinde imza için gerekli #
// ##### doğrulama ve revocation verilerinin hazırlanması. #
// ##### Thread-safe cache kullanımı ve tutarlılık sağlanır. #
// #############################################################
//
// XAdES-C seviyesi, zaman damgası (T) sonrası ek doğrulama
// gereksinimlerini karşılar. Bu aşamada:
//
// 1. CompleteCertificateRefs ve CompleteRevocationRefs
// öğeleri UnsignedSignatureProperties içine eklenir.
//
// 2. OCSP/CRL yanıtları bu aşamada elde edilir ve
// imza-özel (signature-specific) cache'te saklanır.
//
// 3. Cache kullanımı sayesinde, aynı revocation verileri
// hem C seviyesinde (referans) hem de XL seviyesinde
// (gömülü değer) tutarlı şekilde kullanılır.
//
// 4. Thread-safe ConcurrentHashMap ile eş zamanlı işlemler
// izole edilir ve cache kirliliği engellenir.
//
// Bu sayede OCSP/CRL digest eşleşmezliği önlenir ve
// imza arşiv seviyelerine (XL, A) sorunsuz geçiş sağlanır.
// #############################################################
// Initialize signature-specific cache context
if (!signaturesToExtend.isEmpty()) {
AdvancedSignature firstSignature = signaturesToExtend.get(0);
currentSignatureId = firstSignature.getId();
if (currentSignatureId == null || currentSignatureId.isEmpty()) {
currentSignatureId = "sig_" + System.currentTimeMillis() + "_"
+ System.identityHashCode(firstSignature);
}
// Initialize cache for this signature if not exists
ocspCacheBySignature.putIfAbsent(currentSignatureId, new java.util.concurrent.ConcurrentHashMap<>());
}
// ################ BLOK BİTTİ (XADES-C,XL GELİŞMELERİ) ################
// Reset sources
for (AdvancedSignature signature : signaturesToExtend) {
initializeSignatureBuilder((XAdESSignature) signature);
// Data sources can already be loaded in memory (force reload)
xadesSignature.resetCertificateSource();
xadesSignature.resetRevocationSources();
xadesSignature.resetTimestampSource();
}
final SignatureRequirementsChecker signatureRequirementsChecker = getSignatureRequirementsChecker();
if (XAdES_C.equals(params.getSignatureLevel())) {
signatureRequirementsChecker.assertExtendToCLevelPossible(signaturesToExtend);
}
signatureRequirementsChecker.assertSignaturesValid(signaturesToExtend);
signatureRequirementsChecker.assertCertificateChainValidForCLevel(signaturesToExtend);
// ################ BLOK BAŞLANGICI (XADES-C,XL GELİŞMELERİ) ################
// CRITICAL: Cache validation data for reuse in XL and A levels
// This ensures the same OCSP/CRL responses are used across all levels
ValidationDataContainer validationDataContainer;
if (cachedValidationDataContainer == null) {
// First call: Fetch fresh validation data from OCSP/CRL sources
validationDataContainer = documentAnalyzer.getValidationData(signaturesToExtend);
// Cache it for subsequent levels (XL, A)
cachedValidationDataContainer = validationDataContainer;
} else {
// Reuse cached data (should not happen in C-level, but safe fallback)
validationDataContainer = cachedValidationDataContainer;
}
// #############################################################
// Append ValidationData
for (AdvancedSignature signature : signaturesToExtend) {
initializeSignatureBuilder((XAdESSignature) signature);
if (signatureRequirementsChecker.hasXLevelOrHigher(signature)) {
// Unable to extend due to higher levels covering the current C-level
continue;
}
String indent = removeOldCertificateRefs();
removeOldRevocationRefs();
ValidationData validationDataForInclusion = getValidationDataForCLevelInclusion(validationDataContainer, signature);
Element levelTUnsignedProperties = (Element) unsignedSignaturePropertiesDom.cloneNode(true);
// XAdES-C: complete certificate references
// <xades:CompleteCertificateRefs>
// ...<xades:CertRefs>
// ......<xades:Cert>
// .........<xades:CertDigest>
incorporateCertificateRefs(unsignedSignaturePropertiesDom, validationDataForInclusion.getCertificateTokens(), indent);
// XAdES-C: complete revocation references
// <xades:CompleteRevocationRefs>
if (Utils.isCollectionNotEmpty(validationDataForInclusion.getCrlTokens()) ||
Utils.isCollectionNotEmpty(validationDataForInclusion.getOcspTokens())) {
final Element completeRevocationRefsDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom,
getXadesNamespace(), getCurrentXAdESElements().getElementCompleteRevocationRefs());
incorporateCRLRefs(completeRevocationRefsDom, validationDataForInclusion.getCrlTokens());
incorporateOCSPRefs(completeRevocationRefsDom, validationDataForInclusion.getOcspTokens());
}
unsignedSignaturePropertiesDom = indentIfPrettyPrint(unsignedSignaturePropertiesDom, levelTUnsignedProperties);
}
}
private List<AdvancedSignature> getExtendToCLevelSignatures(List<AdvancedSignature> signatures) {
final List<AdvancedSignature> signaturesToExtend = new ArrayList<>();
for (AdvancedSignature signature : signatures) {
if (cLevelExtensionRequired(signature)) {
signaturesToExtend.add(signature);
}
}
return signaturesToExtend;
}
private boolean cLevelExtensionRequired(AdvancedSignature signature) {
return XAdES_C.equals(params.getSignatureLevel()) || XAdES_XL.equals(params.getSignatureLevel()) ||
!signature.hasXProfile();
}
private String removeOldCertificateRefs() {
String text = null;
final Element certRefs = DomUtils.getElement(xadesSignature.getSignatureElement(), xadesPath.getCompleteCertificateRefsPath());
final Element certRefsV2 = DomUtils.getElement(xadesSignature.getSignatureElement(), xadesPath.getCompleteCertificateRefsV2Path());
if (certRefs != null || certRefsV2 != null) {
text = removeNode(certRefs);
if (text == null || certRefsV2 != null) {
text = removeNode(certRefsV2);
}
/* Because the element was removed, the certificate source needs to be reset */
xadesSignature.resetCertificateSource();
}
return text;
}
private void removeOldRevocationRefs() {
final Element toRemove = DomUtils.getElement(xadesSignature.getSignatureElement(), xadesPath.getCompleteRevocationRefsPath());
if (toRemove != null) {
removeNode(toRemove);
/* Because the element was removed, the revocation sources need to be reset */
xadesSignature.resetRevocationSources();
}
}
private void incorporateCertificateRefs(Element parentDom, Collection<CertificateToken> certificatesToBeAdded,
String indent) {
// TODO : review indent usage
if (Utils.isCollectionNotEmpty(certificatesToBeAdded)) {
final Element completeCertificateRefsDom = createCompleteCertificateRefsDom(parentDom);
final Element certRefsDom = createCertRefsDom(completeCertificateRefsDom);
DigestAlgorithm tokenReferencesDigestAlgorithm = params.getTokenReferencesDigestAlgorithm();
for (final CertificateToken certificateToken : certificatesToBeAdded) {
incorporateCert(certRefsDom, certificateToken, tokenReferencesDigestAlgorithm);
}
}
}
private Element createCompleteCertificateRefsDom(Element parentDom) {
if (params.isEn319132()) {
return DomUtils.addElement(documentDom, parentDom, getXades141Namespace(),
XAdES141Element.COMPLETE_CERTIFICATE_REFS_V2);
} else {
return DomUtils.addElement(documentDom, parentDom, getXadesNamespace(),
getCurrentXAdESElements().getElementCompleteCertificateRefs());
}
}
private Element createCertRefsDom(Element parentDom) {
if (params.isEn319132()) {
return DomUtils.addElement(documentDom, parentDom, getXades141Namespace(),
XAdES141Element.CERT_REFS);
} else {
return DomUtils.addElement(documentDom, parentDom, getXadesNamespace(),
getCurrentXAdESElements().getElementCertRefs());
}
}
private ValidationData getValidationDataForCLevelInclusion(final ValidationDataContainer validationDataContainer,
final AdvancedSignature signature) {
// Zaman damgası doğrulama verileri artık ayrı bir alanda tutulduğu için buradan siliniyor.
// Diğer düzenlemelerle uyumlu olması amacıyla bu alandan kaldırılmıştır.
signature.getSignatureTimestamps().clear();
ValidationData validationData = validationDataContainer.getAllValidationDataForSignature(signature);
validationData.excludeCertificateTokens(getCertificateTokensForExclusion());
return validationData;
}
private Collection<CertificateToken> getCertificateTokensForExclusion() {
/*
* A.1.1 The CompleteCertificateRefsV2 qualifying property
*
* The CompleteCertificateRefsV2 qualifying property:
* ...
* 2) Shall not contain the reference to the signing certificate.
* ...
*/
CertificateToken signingCertificateToken = xadesSignature.getSigningCertificateToken();
if (signingCertificateToken != null) {
return Collections.singletonList(signingCertificateToken);
}
return Collections.emptyList();
}
/**
* This method incorporates CRL References like
*
* <pre>
* {@code
* <xades:CRLRefs>
* <xades:CRLRef>
* <xades:DigestAlgAndValue>
* <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
* <ds:DigestValue>G+z+DaZ6X44wEOueVYvZGmTh4dBkjjctKxcJYEV4HmU=</ds:DigestValue>
* </xades:DigestAlgAndValue>
* <xades:CRLIdentifier URI="LevelACAOK.crl">
* <xades:Issuer>CN=LevelACAOK,OU=Plugtests_STF-428_2011-2012,O=ETSI,C=FR</xades:Issuer>
* <xades:IssueTime>2012-03-13T13:58:28.000-03:00</xades:IssueTime>
* <xades:Number>4415260066222</xades:Number>
* }
* </pre>
*
* @param completeRevocationRefsDom {@link Element} "CompleteRevocationRefs"
* @param crlTokens a collection of {@link CRLToken}s to add
*/
private void incorporateCRLRefs(Element completeRevocationRefsDom,
Collection<CRLToken> crlTokens) {
if (crlTokens.isEmpty()) {
return;
}
final Element crlRefsDom = DomUtils.addElement(documentDom, completeRevocationRefsDom, getXadesNamespace(), getCurrentXAdESElements().getElementCRLRefs());
for (final CRLToken crlToken : crlTokens) {
final Element crlRefDom = DomUtils.addElement(documentDom, crlRefsDom, getXadesNamespace(), getCurrentXAdESElements().getElementCRLRef());
DigestAlgorithm digestAlgorithm = params.getTokenReferencesDigestAlgorithm();
final Element digestAlgAndValueDom = DomUtils.addElement(documentDom, crlRefDom, getXadesNamespace(),
getCurrentXAdESElements().getElementDigestAlgAndValue());
incorporateDigestMethod(digestAlgAndValueDom, digestAlgorithm);
incorporateDigestValue(digestAlgAndValueDom, digestAlgorithm, crlToken);
final Element crlIdentifierDom = DomUtils.addElement(documentDom, crlRefDom, getXadesNamespace(), getCurrentXAdESElements().getElementCRLIdentifier());
// crlIdentifierDom.setAttribute("URI",".crl");
final String issuerX500PrincipalName = crlToken.getIssuerX500Principal().getName();
DomUtils.addTextElement(documentDom, crlIdentifierDom, getXadesNamespace(), getCurrentXAdESElements().getElementIssuer(), issuerX500PrincipalName);
final Date thisUpdate = crlToken.getThisUpdate();
XMLGregorianCalendar xmlGregorianCalendar = DomUtils.createXMLGregorianCalendar(thisUpdate);
final String thisUpdateAsXmlFormat = xmlGregorianCalendar.toXMLFormat();
DomUtils.addTextElement(documentDom, crlIdentifierDom, getXadesNamespace(), getCurrentXAdESElements().getElementIssueTime(), thisUpdateAsXmlFormat);
// DSSXMLUtils.addTextElement(documentDom, crlRefDom, XAdESNamespaces.XAdES, "xades:Number", ???);
// ######################## OVERRIDE_DSS #########################
// Bu alan orijinal DSS kütüphanesinde eklenmemiştir, üstteki DSS'nin kaynak kodundaki
// yorumda da görülebileceği gibi <xades:Number> etiketi atlanmaktadır.
// Ancak IMZAGER gibi bazı doğrulayıcılar CRL Number içermeyen imza referanslarını
// geçerli saymamaktadır. Bu nedenle, aşağıda ilgili kodda CRL Number (xades:Number)
// etiketi dahil edilmiştir.
// Benzer bir konu için DSS projesine PR gönderilmiştir:
// https://github.com/esig/dss/pull/187
// Kalıcı çözüm resmi yayınlanana kadar bu satırlar override olarak çerçevemizde tutulacaktır.
// (NOT: Bu bir override değildir; eksik davranışın tamamlanmasıdır.)
try {
X509CRL x509CRL = (X509CRL) CertificateFactory.getInstance("X.509")
.generateCRL(crlToken.getCRLStream());
String crlNumber = XadesUtil.extractCrlNumber(x509CRL);
DomUtils.addTextElement(documentDom, crlIdentifierDom, getXadesNamespace(),
getCurrentXAdESElements().getElementNumber(), crlNumber);
} catch (Exception ignored) {
}
}
}
/**
* This method adds OCSP References like :
*
* <pre>
* {@code
* <xades:CRLRefs/>
* <xades:OCSPRefs>
* <xades:OCSPRef>
* <xades:OCSPIdentifier>
* <xades:ResponderID>
* <xades:ByName>C=AA,O=DSS,CN=OCSP A</xades:ByName>
* </xades:ResponderID>
* <xades:ProducedAt>2013-11-25T12:33:34.000+01:00</xades:ProducedAt>
* </xades:OCSPIdentifier>
* <xades:DigestAlgAndValue>
* <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
* <ds:DigestValue>O1uHdchN+zFzbGrBg2FP3/idD0k=</ds:DigestValue>
* ...
*}
* </pre>
*
* @param completeRevocationRefsDom {@link Element} "CompleteRevocationRefs"
* @param ocspTokens a collection of {@link OCSPToken}s to add
*/
private void incorporateOCSPRefs(Element completeRevocationRefsDom,
Collection<OCSPToken> ocspTokens) {
if (ocspTokens.isEmpty()) {
return;
}
final Element ocspRefsDom = DomUtils.addElement(documentDom, completeRevocationRefsDom, getXadesNamespace(), getCurrentXAdESElements().getElementOCSPRefs());
for (OCSPToken ocspToken : ocspTokens) {
BasicOCSPResp basicOcspResp = ocspToken.getBasicOCSPResp();
if (basicOcspResp != null) {
final Element ocspRefDom = DomUtils.addElement(documentDom, ocspRefsDom, getXadesNamespace(), getCurrentXAdESElements().getElementOCSPRef());
final Element ocspIdentifierDom = DomUtils.addElement(documentDom, ocspRefDom,
getXadesNamespace(), getCurrentXAdESElements().getElementOCSPIdentifier());
final Element responderIDDom = DomUtils.addElement(documentDom, ocspIdentifierDom,
getXadesNamespace(), getCurrentXAdESElements().getElementResponderID());
final RespID respID = basicOcspResp.getResponderId();
final ResponderId responderId = DSSRevocationUtils.getDSSResponderId(respID);
if (responderId.getX500Principal() != null) {
DomUtils.addTextElement(documentDom, responderIDDom, getXadesNamespace(),
getCurrentXAdESElements().getElementByName(), responderId.getX500Principal().toString());
} else {
final String base64EncodedKeyHashOctetStringBytes = Utils.toBase64(responderId.getSki());
DomUtils.addTextElement(documentDom, responderIDDom, getXadesNamespace(),
getCurrentXAdESElements().getElementByKey(), base64EncodedKeyHashOctetStringBytes);
}
final Date producedAt = basicOcspResp.getProducedAt();
final XMLGregorianCalendar xmlGregorianCalendar = DomUtils.createXMLGregorianCalendar(producedAt);
final String producedAtXmlEncoded = xmlGregorianCalendar.toXMLFormat();
DomUtils.addTextElement(documentDom, ocspIdentifierDom, getXadesNamespace(),
getCurrentXAdESElements().getElementProducedAt(), producedAtXmlEncoded);
DigestAlgorithm digestAlgorithm = params.getTokenReferencesDigestAlgorithm();
final Element digestAlgAndValueDom = DomUtils.addElement(documentDom, ocspRefDom, getXadesNamespace(),
getCurrentXAdESElements().getElementDigestAlgAndValue());
incorporateDigestMethod(digestAlgAndValueDom, digestAlgorithm);
incorporateDigestValue(digestAlgAndValueDom, digestAlgorithm, ocspToken);
// ################ BLOK BAŞLANGICI (OCSP CACHE) ################
// ########################OVERRIDE_DSS#########################
// ##### Bu alan, OCSP token'larının C-seviyesinde #
// ##### cache'lenerek XL-seviyesinde tekrar kullanımını #
// ##### sağlar. Digest eşleşmezliği engellenir. #
// #############################################################
//
// DSS kütüphanesinin orijinal kodunda, OCSP token'ları
// C-seviyesinde (referans) ve XL-seviyesinde (gömülü değer)
// iki kez ayrı olarak çekilmektedir. Bu durum, OCSP
// yanıtlarının dinamik doğası nedeniyle digest uyumsuzluğuna
// yol açmaktadır.
//
// Çözüm olarak:
// 1. Her OCSP token, ilgili sertifikanın Base64 anahtarı
// ile imza-özel (signature-specific) cache'te saklanır.
//
// 2. XL-seviyesinde aynı sertifika için OCSP gerektiğinde,
// cache'ten alınarak tutarlılık garanti edilir.
//
// 3. Thread-safe ConcurrentHashMap kullanımı ile eş zamanlı
// işlemler güvenli şekilde izole edilir.
//
// 4. Cache, imza işlemi tamamlandıktan sonra temizlenir.
//
// Bu sayede OCSPRef digest'i ile EncapsulatedOCSPValue
// digest'i her zaman eşleşir ve XAdES-A doğrulaması başarılı olur.
// #############################################################
try {
if (currentSignatureId != null) {
CertificateToken relatedCert = ocspToken.getRelatedCertificate();
if (relatedCert != null) {
String certKey = Utils.toBase64(relatedCert.getEncoded());
// Get signature-specific cache
java.util.concurrent.ConcurrentHashMap<String, OCSPToken> signatureCache = ocspCacheBySignature
.get(currentSignatureId);
if (signatureCache != null) {
signatureCache.put(certKey, ocspToken);
// Also store digest for debugging
byte[] ocspDigest = ocspToken.getDigest(digestAlgorithm);
LOGGER.info("C-LEVEL [{}]: Cached OCSP for cert {} with digest {}",
currentSignatureId,
certKey.substring(0, Math.min(20, certKey.length())),
Utils.toBase64(ocspDigest).substring(0, 20));
}
}
}
} catch (Exception e) {
LOGGER.error("C-LEVEL: Failed to cache OCSP token: {}", e.getMessage());
e.printStackTrace();
throw e;
}
// ################ BLOK BİTTİ (OCSP CACHE) ################
}
}
}
// ################ BLOK BAŞLANGICI (CACHE CLEANUP) ################
// ########################OVERRIDE_DSS#########################
// ##### Bu alan, OCSP cache temizliği için eklenen #
// ##### yardımcı metodları içerir. Memory leak önlenir. #
// ##### DSS orijinalinde bulunmayan ek fonksiyonlardır. #
// #############################################################
//
// DSS kütüphanesinde OCSP cache mekanizması olmadığı için
// cache temizliği de yoktur. Bizim eklediğimiz cache
// yapısının memory leak'e yol açmaması için bu metodlar
// geliştirilmiştir.
//
// İki tür temizleme sağlanır:
//
// 1. cleanupOcspCache(signatureId):
// Belirli bir imza işlemi tamamlandığında, o imzaya
// özel cache'in hemen silinmesini sağlar. SignService'de
// her imza işlemi sonrası finally bloğunda çağrılır.
//
// 2. cleanupOldCaches(maxAgeMillis):
// Eğer bir nedenle (exception, crash vb.) cleanup
// çağrılmamışsa, eski cache'leri periyodik olarak
// temizler. Güvenlik ağı (fallback) görevi görür.
//
// Thread-safe: ConcurrentHashMap kullanımı sayesinde
// eş zamanlı temizleme işlemleri güvenlidir.
//
// Kullanım:
// - Her imza sonrası: cleanupOcspCache(signatureId)
// - Periyodik: cleanupOldCaches(5 * 60 * 1000L) // 5 dakika
// #############################################################
/**
* Cleans up the OCSP cache for a specific signature.
* Should be called after signature extension is complete.
* Thread-safe operation.
*
* @param signatureId The signature ID to clean up
*/
public static void cleanupOcspCache(String signatureId) {
if (signatureId != null) {
java.util.concurrent.ConcurrentHashMap<String, OCSPToken> removed = ocspCacheBySignature
.remove(signatureId);
if (removed != null) {
LOGGER.info("🧹 Cleaned up OCSP cache for signature: {} (removed {} entries)",
signatureId, removed.size());
}
}
}
/**
* Cleans up all OCSP caches older than the specified age.
* Useful for preventing memory leaks if cleanup wasn't called explicitly.
* Thread-safe operation.
*
* @param maxAgeMillis Maximum age in milliseconds
*/
public static void cleanupOldCaches(long maxAgeMillis) {
long cutoffTime = System.currentTimeMillis() - maxAgeMillis;
int removedCount = 0;
for (String signatureId : ocspCacheBySignature.keySet()) {
// Extract timestamp from signature ID if it follows our format
if (signatureId.startsWith("sig_")) {
try {
String[] parts = signatureId.split("_");
if (parts.length >= 2) {
long timestamp = Long.parseLong(parts[1]);
if (timestamp < cutoffTime) {
ocspCacheBySignature.remove(signatureId);
removedCount++;
}
}
} catch (NumberFormatException ignored) {
// Skip if timestamp can't be parsed
}
}
}
if (removedCount > 0) {
LOGGER.info("🧹 Cleaned up {} old OCSP cache(s)", removedCount);
}
}
// ################ BLOK BİTTİ (CACHE CLEANUP) ################
}