/* * DSS - Digital Signature Services * * Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel * * Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" 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. * * DSS 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. * * You should have received a copy of the GNU Lesser General Public License along with * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.validation102853; import java.io.Serializable; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.security.auth.x500.X500Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.ec.markt.dss.CertificateIdentifier; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.exception.DSSNullException; import eu.europa.ec.markt.dss.validation102853.certificate.CertificateSourceType; import eu.europa.ec.markt.dss.validation102853.condition.ServiceInfo; /** * This class hosts the set of certificates which is used during the validation process. A certificate can be found in * different sources: trusted list, signature, OCSP response... but each certificate is unambiguously identified by its * issuer DN and serial number. This class allows to keep only one occurrence of the certificate regardless its * provenance. Two pools of certificates can be merged using the {@link #merge(CertificatePool)} method. * * @author bielecro */ public class CertificatePool implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(CertificatePool.class); public static final List<CertificateToken> EMPTY_UNMODIFIABLE_CERTIFICATE_TOKEN_LIST = Collections.unmodifiableList(new ArrayList<CertificateToken>()); /** * Map of encapsulated certificates with unique DSS identifier as key */ private final Map<Integer, CertificateToken> certById = new HashMap<Integer, CertificateToken>(); /** * Map of encapsulated certificates with subject distinguished name as key. */ // TODO-Bob (17/02/2015): The key should be the ASN.1 bytes of X500Principal private final Map<String, List<CertificateToken>> certBySubject = new HashMap<String, List<CertificateToken>>(); /** * Returns the instance of a certificate token. If the certificate is not referenced yet a new instance of * {@link CertificateToken} is created. * * @param x509Certificate {@code X509Certificate} to add to the pool * @param source {@code CertificateSourceType} associated to the certificate * @return an existing or newly created instance of the {@code CertificateToken} */ public CertificateToken getInstance(final X509Certificate x509Certificate, final CertificateSourceType source) { return getInstance(x509Certificate, source, null); } /** * This method returns the instance of a {@link CertificateToken} corresponding to the given {@link X509Certificate}. * If the given certificate is not yet present in the pool it will be added. If the {@link CertificateToken} exists * already in the pool but has no {@link ServiceInfo} this reference will be added. * * @param x509Certificate {@code X509Certificate} to add to the pool * @param source {@code CertificateSourceType} associated to the certificate * @param serviceInfo {@code ServiceInfo} associated to the certificate * @return an existing or newly created instance of the {@code CertificateToken} */ public CertificateToken getInstance(final X509Certificate x509Certificate, final CertificateSourceType source, final ServiceInfo serviceInfo) { final List<ServiceInfo> services = new ArrayList<ServiceInfo>(); if (serviceInfo != null) { services.add(serviceInfo); } final List<CertificateSourceType> sources = new ArrayList<CertificateSourceType>(); if (source != null) { sources.add(source); } return getInstance(x509Certificate, sources, services); } /** * This method returns the instance of a {@link CertificateToken} corresponding to the given {@link X509Certificate}. * If the given certificate is not yet present in the pool it will added. If the {@link CertificateToken} exists * already in the pool but has no {@link ServiceInfo} this reference will be added. * * @param x509Certificate {@code X509Certificate} to add to the pool * @param sources {@code List} of {@code CertificateSourceType} associated to the certificate * @param services {@code List} of {@code ServiceInfo} associated to the certificate * @return an existing or newly created instance of the {@code CertificateToken} */ public CertificateToken getInstance(final X509Certificate x509Certificate, final List<CertificateSourceType> sources, final List<ServiceInfo> services) { if (x509Certificate == null) { throw new DSSNullException(X509Certificate.class); } if (sources == null || sources.size() == 0) { throw new DSSException("The certificate source type must be set."); } // TRACE ++ // if (LOG.isTraceEnabled()) { // LOG.trace("Certificate to add: " + certificateToAdd.getIssuerX500Principal().toString() + "|" + certificateToAdd.getSerialNumber()); // } final CertificateToken certToken = getCertificateToken(x509Certificate); for (final CertificateSourceType sourceType : sources) { certToken.addSourceType(sourceType); } if (services != null) { for (final ServiceInfo serviceInfo : services) { certToken.addServiceInfo(serviceInfo); } } return certToken; } private CertificateToken getCertificateToken(final X509Certificate x509Certificate) { final int id = CertificateIdentifier.getId(x509Certificate); synchronized (certById) { CertificateToken certToken = certById.get(id); if (certToken == null) { certToken = CertificateToken.newInstance(x509Certificate, id); certById.put(id, certToken); final X500Principal subjectX500Principal = DSSUtils.getSubjectX500Principal(x509Certificate); final String subjectName = subjectX500Principal.getName(X500Principal.CANONICAL); List<CertificateToken> list = certBySubject.get(subjectName); if (list == null) { list = new ArrayList<CertificateToken>(); certBySubject.put(subjectName, list); } list.add(certToken); } else if (LOG.isTraceEnabled()) { checkCertificateUniqueness(x509Certificate, certToken); } return certToken; } } private static void checkCertificateUniqueness(final X509Certificate certificateToAdd, final CertificateToken certToken) { final X509Certificate foundCertificate = certToken.getCertificate(); final byte[] foundCertificateSignature = foundCertificate.getSignature(); final byte[] certificateToAddSignature = certificateToAdd.getSignature(); if (!Arrays.equals(foundCertificateSignature, certificateToAddSignature)) { LOG.warn("Found certificate: " + certToken.getIssuerX500Principal().toString() + "|" + certToken.getSerialNumber()); LOG.warn("More than one certificate for the same issuer subject name and serial number! The standard is not met by the certificate issuer!"); } } /** * @return an unmodifiable list containing all encapsulated certificate tokens {@link CertificateToken} */ public List<CertificateToken> getCertificateTokens() { ArrayList<CertificateToken> certificateTokenArrayList = new ArrayList<CertificateToken>(certById.values()); return Collections.unmodifiableList(certificateTokenArrayList); } /** * This method return the number of certificates contained by this pool. * * @return the number of certificates */ public int getNumberOfCertificates() { return certById.size(); } /** * This method allows to add certificates from another {@link CertificatePool}. If an instance of the * {@link CertificateToken} already exists in this pool only the {@link ServiceInfo} and * {@link CertificateSourceType} are updated. * * @param certPool {@code CertificatePool} to merge */ public void merge(final CertificatePool certPool) { final Collection<CertificateToken> certTokens = certPool.getCertificateTokens(); for (final CertificateToken certificateToken : certTokens) { final X509Certificate x509Certificate = certificateToken.getCertificate(); final List<CertificateSourceType> sources = certificateToken.getSources(); final List<ServiceInfo> services = certificateToken.getAssociatedTSPS(); getInstance(x509Certificate, sources, services); } } /** * This method returns an unmodifiable list of certificates with the same issuerDN. * * @param x500Principal subject distinguished name to match * @return If no match is found then an empty list is returned */ public List<CertificateToken> get(final X500Principal x500Principal) { if (x500Principal != null) { final String x500PrincipalCanonicalized = x500Principal.getName(X500Principal.CANONICAL); final List<CertificateToken> certificateTokenList = certBySubject.get(x500PrincipalCanonicalized); if (certificateTokenList != null) { return Collections.unmodifiableList(certificateTokenList); } } return EMPTY_UNMODIFIABLE_CERTIFICATE_TOKEN_LIST; } }