/**************************************************************************** * Copyright (C) 2012 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.crypto.tls.verify; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.PKIXParameters; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import org.openecard.bouncycastle.asn1.x500.RDN; import org.openecard.bouncycastle.asn1.x500.style.BCStrictStyle; import org.openecard.bouncycastle.crypto.tls.Certificate; import org.openecard.crypto.tls.CertificateVerificationException; import org.openecard.crypto.tls.CertificateVerifier; /** * Java Security based certificate verifier. <br/> * This implementation converts the BouncyCastle certificates to java.security certificates and uses the Java-bundled * mechanisms to verify the certificate chain. * * @author Tobias Wich <tobias.wich@ecsec.de> */ public class JavaSecVerifier implements CertificateVerifier { private final KeyStore keyStore; private final CertPathValidator certPathValidator; /** * Create a JavaSecVerifier and load the system keystore. * * @throws KeyStoreException Keystore type could not be instantiated. * @throws FileNotFoundException Keystore was not found in standard locations. * @throws IOException Error loading keystore from disc. * @throws GeneralSecurityException Error processing loaded keystore. */ public JavaSecVerifier() throws IOException, GeneralSecurityException { keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); // initialize keystore KeyStore tmpKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); // determine system keystore final String fSep = File.separator; File keyStoreFile; // try system property keyStoreFile = getKeystore(System.getProperty("java.home"), "lib" + fSep + "security" + fSep + "cacerts"); // load file if (keyStoreFile != null) { tmpKeyStore.load(new FileInputStream(keyStoreFile), null); // system keystore has no password protection } else { // TODO: this is either on android or it doesn' work at all throw new FileNotFoundException("Unable to find system keystore in standard locations."); } addKeyStore(tmpKeyStore); } private File getKeystore(String prefix, String fileName) { if (fileName == null) { return null; } if (prefix != null) { fileName = prefix + File.separator + fileName; } File f = new File(fileName); if (! f.canRead()) { return null; } return f; } /** * Merge the given keystore into the one enclosed in this verifier instance. * * @param keyStore Keystore to merge. * @throws KeyStoreException In case access to the given keystore is not possible. */ public final void addKeyStore(KeyStore keyStore) throws KeyStoreException { Enumeration aliases = keyStore.aliases(); while(aliases.hasMoreElements()) { String alias = (String) aliases.nextElement(); if (keyStore.isCertificateEntry(alias)) { java.security.cert.Certificate cert = keyStore.getCertificate(alias); this.keyStore.setCertificateEntry(alias, cert); } } } /** * Merge all given keystores into the one enclosed in this verifier instance. * @param keyStores Keystores to merge. * @throws KeyStoreException In case access to the given keystore is not possible. */ public final void addKeyStore(List<KeyStore> keyStores) throws KeyStoreException { for (KeyStore next : keyStores) { addKeyStore(next); } } @Override public void isValid(Certificate chain) throws CertificateVerificationException { isValid(chain, null); } @Override public void isValid(Certificate chain, String hostname) throws CertificateVerificationException { // check hostname if (hostname != null) { org.openecard.bouncycastle.asn1.x509.Certificate cert = chain.getCertificateAt(0); RDN[] cn = cert.getSubject().getRDNs(BCStrictStyle.CN); if (cn.length != 1) { throw new CertificateVerificationException("Multiple CN entries in certificate's Subject."); } // extract hostname from certificate // TODO: add safeguard code if cn doesn't contain a string String hostNameReference = cn[0].getFirst().getValue().toString(); checkWildcardName(hostname, hostNameReference); } try { CertPath certPath = convertChain(chain); // create the parameters for the validator PKIXParameters params = new PKIXParameters(keyStore); // disable CRL checking since we are not supplying any CRLs yet params.setRevocationEnabled(false); // validate - exception marks failure certPathValidator.validate(certPath, params); } catch (CertPathValidatorException ex) { throw new CertificateVerificationException(ex.getMessage()); } catch (GeneralSecurityException ex) { throw new CertificateVerificationException(ex.getMessage()); } catch (IOException ex) { throw new CertificateVerificationException("Error converting certificate chain to java.security format."); } } private CertPath convertChain(Certificate chain) throws CertificateException, IOException { final int numCerts = chain.getCertificateList().length; ArrayList<java.security.cert.Certificate> result = new ArrayList<java.security.cert.Certificate>(numCerts); CertificateFactory cf = CertificateFactory.getInstance("X.509"); for (org.openecard.bouncycastle.asn1.x509.Certificate next : chain.getCertificateList()) { byte[] nextData = next.getEncoded(); ByteArrayInputStream nextDataStream = new ByteArrayInputStream(nextData); java.security.cert.Certificate nextConverted = cf.generateCertificate(nextDataStream); result.add(nextConverted); } return cf.generateCertPath(result); } private static void checkWildcardName(String givenHost, String wildcardHost) throws CertificateVerificationException { final String errorMsg = "Hostname in certificate differs from actually requested host."; String[] givenToken = givenHost.split("\\."); String[] wildToken = wildcardHost.split("\\."); // error if number of token is different if (givenToken.length != wildToken.length) { throw new CertificateVerificationException(errorMsg); } // compare entries for (int i = 0; i < givenToken.length; i++) { if (wildToken[i].equals("*")) { // skip wildcard part continue; } if (!givenToken[i].equals(wildToken[i])) { throw new CertificateVerificationException(errorMsg); } } } }