se.idsec.signservice.security.certificate.impl.SimpleCertificateValidator Maven / Gradle / Ivy
/*
* Copyright 2019-2024 IDsec Solutions AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.idsec.signservice.security.certificate.impl;
import lombok.extern.slf4j.Slf4j;
import se.idsec.signservice.security.certificate.CertificateUtils;
import se.idsec.signservice.security.certificate.CertificateValidationResult;
import se.idsec.signservice.security.certificate.CertificateValidator;
import java.security.GeneralSecurityException;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathBuilderResult;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CRL;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A simple validator that does not perform revocation checking and only relies upon the supplied certificates when
* building the chain.
*
*
* Note: If no trust anchors are defined, the path root must be available as the last element of the
* {@code additionalCertificates} parameter. This certificate must be self-signed.
*
*
* @author Martin Lindström ([email protected])
* @author Stefan Santesson ([email protected])
*/
@Slf4j
public class SimpleCertificateValidator implements CertificateValidator {
/** The date/time when the certificate status should be determined. For testing mainly. */
protected Date validationDate;
/** The trust anchors. */
protected List defaultTrustAnchors;
/**
* Default constructor.
*/
public SimpleCertificateValidator() {
}
/** {@inheritDoc} */
@Override
public CertificateValidationResult validate(final X509Certificate subjectCertificate,
final List additionalCertificates, final List crls)
throws CertPathBuilderException, CertPathValidatorException, GeneralSecurityException {
return this.validate(subjectCertificate, additionalCertificates, crls, this.defaultTrustAnchors);
}
/** {@inheritDoc} */
@Override
public CertificateValidationResult validate(final X509Certificate subjectCertificate,
final List additionalCertificates,
final List crls,
final List trustAnchors)
throws CertPathBuilderException, CertPathValidatorException, GeneralSecurityException {
log.debug("Validating certificate: {}", CertificateUtils.toLogString(subjectCertificate));
// Setup trust anchors
//
final Set trusted = this.setupTrustAnchors(trustAnchors, additionalCertificates);
// Get cert stores ...
//
final List certStores = this.getCertStores(subjectCertificate, additionalCertificates, crls);
// Set up the builder and validation params...
//
final PKIXBuilderParameters params = this.buildParameters(subjectCertificate, trusted, certStores);
// Now, build a certificate chain ...
//
final CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
final CertPathBuilderResult builderResult = certPathBuilder.build(params);
// Finally, validate the path ...
//
final CertPathValidator validator = CertPathValidator.getInstance("PKIX");
final PKIXCertPathValidatorResult pkixResult =
(PKIXCertPathValidatorResult) validator.validate(builderResult.getCertPath(), params);
final DefaultCertificateValidationResult result = new DefaultCertificateValidationResult(
builderResult.getCertPath().getCertificates().stream().map(X509Certificate.class::cast)
.collect(Collectors.toList()));
result.setPkixCertPathValidatorResult(pkixResult);
log.debug("Successful validation of [{}]", CertificateUtils.toLogString(subjectCertificate));
return result;
}
/**
* The implementation creates a set of trust anchors based on the {@code trustAnchors} parameter. This parameter was
* either supplied in a call to {@link #validate(X509Certificate, List, List, List)} or set as the default anchors
* ({@link #setDefaultTrustAnchors(List)}).
*
* If no trust anchors is available, this implementation uses the last certificate from the supplied
* {@code additionalCertificates} parameter as the root.
*
*
* @param trustAnchors the trust anchors
* @param additionalCertificates additional certs
* @return a set of trust anchors
* @throws CertPathBuilderException if trust can not be setup
*/
protected Set setupTrustAnchors(final List trustAnchors,
final List additionalCertificates) throws CertPathBuilderException {
if (trustAnchors != null) {
return trustAnchors.stream().map(t -> new TrustAnchor(t, null)).collect(Collectors.toSet());
}
else {
if (additionalCertificates == null || additionalCertificates.isEmpty()) {
final String msg = "No trust anchors supplied and no root set in additionalCertificates";
log.error(msg);
throw new CertPathBuilderException(msg);
}
final X509Certificate root = additionalCertificates.getLast();
// Make sure it is self-signed
if (root.getSubjectX500Principal().equals(root.getIssuerX500Principal())) {
return Collections.singleton(new TrustAnchor(root, null));
}
else {
final String msg = "No root supplied in additionalCertificates";
log.error(msg);
throw new CertPathBuilderException(msg);
}
}
}
/**
* Gets the certificate stores that should be used during path building and validation. The default implementation
* builds one store holding the certificates supplied in {@code subjectCertificate} and
* {@code additionalCertificates}.
*
* @param subjectCertificate the certificate to validate
* @param additionalCertificates other certificates that may be useful when building a certificate path
* @param crls optional list of CRL:s that may be useful during path validation
* @return a list of cert stores
* @throws GeneralSecurityException for cert store creation errors
*/
protected List getCertStores(final X509Certificate subjectCertificate,
final List additionalCertificates,
final List crls) throws GeneralSecurityException {
final List