/**************************************************************************** * Copyright (C) 2013 HS Coburg. * 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.common.keystore; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStore.Entry; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.SignatureException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.interfaces.RSAPrivateCrtKey; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.annotation.Nonnull; import org.openecard.bouncycastle.crypto.CryptoException; import org.openecard.bouncycastle.crypto.Signer; import org.openecard.bouncycastle.crypto.digests.NullDigest; import org.openecard.bouncycastle.crypto.encodings.PKCS1Encoding; import org.openecard.bouncycastle.crypto.engines.RSABlindedEngine; import org.openecard.bouncycastle.crypto.params.ParametersWithRandom; import org.openecard.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; import org.openecard.bouncycastle.crypto.signers.GenericSigner; import org.openecard.bouncycastle.crypto.tls.Certificate; import org.openecard.crypto.common.sal.CredentialPermissionDenied; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Wrapper for the sign functionality of keystore entries. * * @author Dirk Petrautzki <dirk.petrautzki@hs-coburg.de> */ public class KeyStoreSigner { private static final Logger logger = LoggerFactory.getLogger(KeyStoreSigner.class); private byte[] rawCertData; private Map<String, java.security.cert.Certificate[]> javaCerts; private org.openecard.bouncycastle.crypto.tls.Certificate bcCert; private KeyStore keyStore; private char[] password; private String alias; private InputStream inputStream; /** * Creates a KeyStoreSigner and defines the InputStream to load from and the password and alias. * * @param keyStore * @param inputStream * @param password * @param alias */ public KeyStoreSigner(@Nonnull KeyStore keyStore, InputStream inputStream, char[] password, String alias) { this.keyStore = keyStore; this.password = password; this.alias = alias; this.inputStream = inputStream; this.javaCerts = new HashMap<String, java.security.cert.Certificate[]>(); } /** * Gets the certificate for this keystore entry. * This function returns the certificate in encoded form. * * @return Certificate of this KeyStore entry in encoded form. * @throws CredentialPermissionDenied In case the certificate could not be read from the token. * @throws IOException In case any other error occurred during the reading of the certificate. */ public synchronized byte[] getCertificateChain() throws CredentialPermissionDenied, IOException { if (rawCertData == null) { try { keyStore.load(inputStream, password); java.security.cert.Certificate[] cert; cert = keyStore.getCertificateChain(alias); // TODO this is only the users certificate rawCertData = cert[0].getEncoded(); } catch (KeyStoreException e) { throw new IOException("Keystore is not initialized.", e); } catch (NoSuchAlgorithmException e) { throw new IOException(e); } catch (CertificateException e) { throw new IOException(e); } } return rawCertData; } /** * Gets the certificate for this KeyStore entry converted to a Java security certificate. * This method is just a convenience function to call the equivalent with the parameter {@code X.509}. * * @return * @throws CredentialPermissionDenied * @throws CertificateException * @throws IOException * @throws KeyStoreException * @throws NoSuchAlgorithmException * * @see #getJavaSecCertificateChain(java.lang.String) */ public java.security.cert.Certificate[] getJavaSecCertificateChain() throws CredentialPermissionDenied, CertificateException, IOException, NoSuchAlgorithmException, KeyStoreException { return getJavaSecCertificateChain("X.509"); } /** * Gets the certificate for this KeyStore entry converted to a Java security certificate. * The type parameter is used to determine the requested certificate type. Each certificate type is cached * once it is requested. * * @param certType Certificate type according to <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html#CertificateFactory"> * CertificateFactory Types</a> * @return An array representing the certificate chain of this entry. * @throws CredentialPermissionDenied In case the certificate could not be read from the token. See * {@link #getCertificateChain()}. * @throws IOException In case any other error occurred during the reading of the certificate. See * {@link #getCertificateChain()}. * @throws CertificateException In case the certificate could not be converted. */ @Nonnull public java.security.cert.Certificate[] getJavaSecCertificateChain(@Nonnull String certType) throws CertificateException, CredentialPermissionDenied, IOException { // is the certificate already available in java.security form? if (! javaCerts.containsKey(certType)) { byte[] certs = getCertificateChain(); CertificateFactory cf = CertificateFactory.getInstance(certType); Collection<? extends java.security.cert.Certificate> javaCert; javaCert = cf.generateCertificates(new ByteArrayInputStream(certs)); javaCerts.put(certType, javaCert.toArray(new java.security.cert.Certificate[javaCert.size()])); } return javaCerts.get(certType); } /** * Gets the certificate for this KeyStore entry converted to a BouncyCastle TLS certificate. * * @return The certificate chain in BouncyCastle format. * @throws CredentialPermissionDenied In case the certificate could not be read from the token. See * {@link #getCertificateChain()}. * @throws IOException In case any other error occurred during the reading of the certificate. See * {@link #getCertificateChain()}. * @throws CertificateException In case the certificate could not be converted. */ @Nonnull public org.openecard.bouncycastle.crypto.tls.Certificate getBCCertificateChain() throws CredentialPermissionDenied, CertificateException, IOException { // is the certificate already available in BC form? if (bcCert == null) { byte[] certs = getCertificateChain(); bcCert = convertToBCCertificate(certs); } return bcCert; } /** * Signs the given hash with the entry represented by this instance. * * @param hash The hash that should be signed. * @return Signature of the given hash. * @throws SignatureException In case the signature could not be created. * @throws CredentialPermissionDenied In case the signature could not be performed by the token due to missing * permissions. */ public byte[] sign(@Nonnull byte[] hash) throws SignatureException, CredentialPermissionDenied { try { KeyStore.PasswordProtection param = new KeyStore.PasswordProtection(password); Entry entry = keyStore.getEntry(alias, param); if (entry == null) { throw new SignatureException("No key entry for the given alias found."); } KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) entry; PrivateKey myPrivateKey = pkEntry.getPrivateKey(); RSAPrivateCrtKey privCrtKey = (RSAPrivateCrtKey) myPrivateKey; RSAPrivateCrtKeyParameters key = new RSAPrivateCrtKeyParameters(privCrtKey.getModulus(), privCrtKey.getPublicExponent(), privCrtKey.getPrivateExponent(), privCrtKey.getPrimeP(), privCrtKey.getPrimeQ(), privCrtKey.getPrimeExponentP(), privCrtKey.getPrimeExponentQ(), privCrtKey.getCrtCoefficient()); Signer signer = new GenericSigner(new PKCS1Encoding(new RSABlindedEngine()), new NullDigest()); signer.init(true, new ParametersWithRandom(key, new SecureRandom())); signer.update(hash, 0, hash.length); return signer.generateSignature(); } catch (NoSuchAlgorithmException e) { throw new SignatureException(e); } catch (UnrecoverableEntryException e) { String msg = "Private key entry couldn't be recovered from keystore. May a wrong password is used."; throw new CredentialPermissionDenied(msg, e); } catch (KeyStoreException e) { throw new SignatureException(e); } catch (CryptoException e) { throw new SignatureException(e); } } private Certificate convertToBCCertificate(byte[] certificateBytes) { org.openecard.bouncycastle.asn1.x509.Certificate x509Certificate; x509Certificate = org.openecard.bouncycastle.asn1.x509.Certificate.getInstance(certificateBytes); org.openecard.bouncycastle.asn1.x509.Certificate[] certs; certs = new org.openecard.bouncycastle.asn1.x509.Certificate[] { x509Certificate }; Certificate cert = new Certificate(certs); return cert; } }