/* dCache - http://www.dcache.org/ * * Copyright (C) 2015 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.util; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheStats; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import eu.emi.security.authn.x509.ProxySupport; import eu.emi.security.authn.x509.RevocationParameters; import eu.emi.security.authn.x509.StoreUpdateListener; import eu.emi.security.authn.x509.ValidationError; import eu.emi.security.authn.x509.ValidationErrorCode; import eu.emi.security.authn.x509.ValidationErrorListener; import eu.emi.security.authn.x509.ValidationResult; import eu.emi.security.authn.x509.X509CertChainValidatorExt; import java.security.cert.CertPath; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Collections.singletonList; /** * A Certificate validator that caches validation results for a configurable * period of time. The cache is keyed by the SHA256 of the certificate chain. */ public class CachingCertificateValidator implements X509CertChainValidatorExt { protected final Cache<String, ValidationResult> cache; protected final X509CertChainValidatorExt validator; public CachingCertificateValidator(X509CertChainValidatorExt val, long maxCacheEntryLifetime) { cache = CacheBuilder.newBuilder().expireAfterWrite(maxCacheEntryLifetime, TimeUnit.MILLISECONDS).build(); validator = val; } @Override public ValidationResult validate(final X509Certificate[] certChain) { checkNotNull(certChain, "Cannot validate a null cert chain."); checkArgument(certChain.length > 0, "Cannot validate a cert chain of length 0."); int pos = 0; try { /* Check that the chain is still valid; would be nice if we instead could limit the lifetime of the cache * entry, but Guava doesn't allow us to do that. */ for (X509Certificate cert : certChain) { cert.checkValidity(); pos++; } pos = 0; Hasher hasher = Hashing.sha256().newHasher(); for (X509Certificate cert : certChain) { hasher.putBytes(cert.getEncoded()); pos++; } String certFingerprint = hasher.hash().toString(); return cache.get(certFingerprint, new Callable<ValidationResult>() { @Override public ValidationResult call() throws Exception { return validator.validate(certChain); } }); } catch (CertificateEncodingException e) { return new ValidationResult(false, singletonList(new ValidationError(certChain, pos, ValidationErrorCode.inputError, e.getMessage()))); } catch (ExecutionException e) { return new ValidationResult(false, singletonList(new ValidationError(certChain, pos, ValidationErrorCode.inputError, e.getMessage()))); } catch (CertificateExpiredException e) { return new ValidationResult(false, singletonList(new ValidationError(certChain, pos, ValidationErrorCode.certificateExpired, e.getMessage()))); } catch (CertificateNotYetValidException e) { return new ValidationResult(false, singletonList(new ValidationError(certChain, pos, ValidationErrorCode.certificateNotYetValid, e.getMessage()))); } } public CacheStats stats() { return cache.stats(); } @Override public void dispose() { validator.dispose(); } @Override public ProxySupport getProxySupport() { return validator.getProxySupport(); } @Override public ValidationResult validate(CertPath certPath) { return validator.validate(certPath); } @Override public RevocationParameters getRevocationCheckingMode() { return validator.getRevocationCheckingMode(); } @Override public X509Certificate[] getTrustedIssuers() { return validator.getTrustedIssuers(); } @Override public void addValidationListener(ValidationErrorListener listener) { validator.addValidationListener(listener); } @Override public void removeValidationListener(ValidationErrorListener listener) { validator.removeValidationListener(listener); } @Override public void addUpdateListener(StoreUpdateListener listener) { validator.addUpdateListener(listener); } @Override public void removeUpdateListener(StoreUpdateListener listener) { validator.removeUpdateListener(listener); } }