/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.wave.crypto; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.security.GeneralSecurityException; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.List; import java.util.Set; /** * A cert path (aka cert chain) validator that stores validation results in * a cache. It will also first attempt to look up validation results in the * cache, before performing a full-blown cert chain verification. */ public class CachedCertPathValidator implements WaveCertPathValidator { private static final String VALIDATOR_TYPE = "PKIX"; private static final String CERTIFICATE_TYPE = "X.509"; // the cache that stores, for a limited amount of time, cert chain // verification result. private final VerifiedCertChainCache certPathCache; // source for current time, so that expiration of certificates can be checked private final TimeSource timeSource; // set of trusted Certification Authorities private final Set<TrustAnchor> trustRoots; public CachedCertPathValidator(VerifiedCertChainCache certPathCache, TimeSource timeSource, TrustRootsProvider trustRootsProvider) { this.certPathCache = certPathCache; this.timeSource = timeSource; this.trustRoots = getTrustRoots(trustRootsProvider); } @Override public void validate(List<? extends X509Certificate> certs) throws SignatureException { if (!certPathCache.contains(certs)) { validateNoCache(certs); // we don't get here if certs didn't validate certPathCache.add(certs); } } private Set<TrustAnchor> getTrustRoots(TrustRootsProvider provider) { List<TrustAnchor> anchors = Lists.newArrayList(); for (X509Certificate c : provider.getTrustRoots()) { anchors.add(new TrustAnchor(c, null)); } return ImmutableSet.copyOf(anchors); } private static WaveOidChecker WAVE_OID_CHECKER = new WaveOidChecker(); private void validateNoCache(List<? extends X509Certificate> certs) throws SignatureException { try { CertPathValidator validator = CertPathValidator.getInstance( VALIDATOR_TYPE); PKIXParameters params = new PKIXParameters(trustRoots); params.addCertPathChecker(WAVE_OID_CHECKER); params.setDate(timeSource.now()); // turn off default revocation-checking mechanism params.setRevocationEnabled(false); // TODO: add a way for clients to add certificate revocation checks, // perhaps by letting them pass in PKIXCertPathCheckers. This can also be // useful to check for Wave-specific certificate extensions. CertificateFactory certFactory = CertificateFactory.getInstance( CERTIFICATE_TYPE); CertPath certPath = certFactory.generateCertPath(certs); validator.validate(certPath, params); } catch (GeneralSecurityException e) { throw new SignatureException("Certificate validation failure", e); } } private static class WaveOidChecker extends PKIXCertPathChecker { private static final String WAVE_OID = "1.3.6.1.4.1.11129.2.1.1"; private static final Set<String> SUPPORTED_EXTENSIONS = ImmutableSet.of(WAVE_OID); @Override public void check(Certificate cert, Collection<String> unresolvedCritExts) { // We know about the WAVE OID - if it's in the unresolved critical // extensions, that should not cause an error. In fact, in the future // we might _require_ this extension unresolvedCritExts.remove(WAVE_OID); } @Override public Set<String> getSupportedExtensions() { return SUPPORTED_EXTENSIONS; } @Override public void init(boolean forward) { // nothing to do } @Override public boolean isForwardCheckingSupported() { return true; } } }