/**************************************************************************** * Copyright (C) 2013 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.common.sal; import iso.std.iso_iec._24727.tech.schema.CardApplicationConnect; import iso.std.iso_iec._24727.tech.schema.CardApplicationConnectResponse; import iso.std.iso_iec._24727.tech.schema.CardApplicationPath; import iso.std.iso_iec._24727.tech.schema.CardApplicationPathResponse; import iso.std.iso_iec._24727.tech.schema.CardApplicationPathType; import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticate; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticateResponse; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticationDataType; import iso.std.iso_iec._24727.tech.schema.DIDGet; import iso.std.iso_iec._24727.tech.schema.DIDGetResponse; import iso.std.iso_iec._24727.tech.schema.DIDScopeType; import iso.std.iso_iec._24727.tech.schema.DIDStructureType; import iso.std.iso_iec._24727.tech.schema.DSIRead; import iso.std.iso_iec._24727.tech.schema.DSIReadResponse; import iso.std.iso_iec._24727.tech.schema.DataSetSelect; import iso.std.iso_iec._24727.tech.schema.DataSetSelectResponse; import iso.std.iso_iec._24727.tech.schema.Sign; import iso.std.iso_iec._24727.tech.schema.SignResponse; import iso.std.iso_iec._24727.tech.schema.TargetNameType; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import org.openecard.bouncycastle.crypto.tls.Certificate; import org.openecard.common.SecurityConditionUnsatisfiable; import org.openecard.common.WSHelper; import org.openecard.common.WSHelper.WSException; import org.openecard.common.interfaces.Dispatcher; import org.openecard.common.interfaces.DispatcherException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Wrapper for the sign functionality of generic crypto DIDs. * * @author Tobias Wich <tobias.wich@ecsec.de> * @author Dirk Petrautzki <dirk.petrautzki@hs-coburg.de> */ public class GenericCryptoSigner { private static final Logger logger = LoggerFactory.getLogger(GenericCryptoSigner.class); private final Dispatcher dispatcher; private final ConnectionHandleType handle; private final String didName; private byte[] rawCertData; private Map<String, java.security.cert.Certificate[]> javaCerts; private org.openecard.bouncycastle.crypto.tls.Certificate bcCert; /** * Creates a Generic Crypto signer and defines the card, application and target DID through the parameters. * * @param dispatcher Dispatcher used to talk to the SAL. * @param handle Handle naming the SAL, card and application of the DID. The application connection can change over * time and does not even have to be selected when this method is called. * @param didName Name of the DID this instance encapsulates. */ public GenericCryptoSigner(@Nonnull Dispatcher dispatcher, @Nonnull ConnectionHandleType handle, @Nonnull String didName) { this.dispatcher = dispatcher; this.handle = handle; this.didName = didName; this.javaCerts = new HashMap<String, java.security.cert.Certificate[]>(); } /** * Gets the certificate for this generic crypto DID. * This function returns the certificate in encoded form exactly as it is saved on the respective token. The * certificate is not converted whatsoever. * * @return Certificate of this DID 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) { String dataSetName = getCertificateDataSetName(); if (dataSetName != null) { rawCertData = readCertificateDataset(handle, dataSetName); } else { throw new IOException("Could not get the certificate data set name."); } if (rawCertData == null) { throw new IOException("Failed to read certificate contents."); } } return rawCertData; } /** * Gets the certificate for this generic crypto DID converted to a Java security certificate. * This method is just a convenience function to call the equivalent with the parameter {@code X.509}. * * @return An array representing the certificate chain (in X.509 format) of this DID. * @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. * @see #getJavaSecCertificateChain(java.lang.String) */ public java.security.cert.Certificate[] getJavaSecCertificateChain() throws CredentialPermissionDenied, CertificateException, IOException { return getJavaSecCertificateChain("X.509"); } /** * Gets the certificate for this generic crypto DID 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 DID. * @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 CredentialPermissionDenied, CertificateException, 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 generic crypto DID 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 = convertToCertificate(certs); } return bcCert; } /** * Signs the given hash with the DID 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. Causes are most likely problems in the * SAL, such as a removed card. * @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 { connectApplication(); TargetNameType target = new TargetNameType(); target.setDIDName(didName); performMissingAuthentication(target); Sign sign = new Sign(); sign.setMessage(hash); sign.setDIDName(didName); sign.setDIDScope(DIDScopeType.LOCAL); sign.setConnectionHandle(handle); SignResponse res = (SignResponse) dispatcher.deliver(sign); WSHelper.checkResult(res); byte[] sig = res.getSignature(); if (sig == null) { logger.error("Failed to create signature for TLS connection."); return new byte[] {}; } else { return sig; } } catch (InvocationTargetException e) { logger.error("Signature generation failed.", e); throw new SignatureException(e); } catch (DispatcherException e) { logger.error("Signature generation failed.", e); throw new SignatureException(e); } catch (WSException e) { logger.error("Signature generation failed.", e); throw new SignatureException(e); } catch (SecurityConditionUnsatisfiable e) { logger.error("Signature generation failed.", e); throw new CredentialPermissionDenied(e); } } private Certificate convertToCertificate(byte[] certificateBytes) throws CertificateException { org.openecard.bouncycastle.asn1.x509.Certificate x509Certificate = org.openecard.bouncycastle.asn1.x509.Certificate.getInstance(certificateBytes); if(x509Certificate == null) { throw new CertificateException("Couldn't convert to x509Certificate."); } org.openecard.bouncycastle.asn1.x509.Certificate[] certs = new org.openecard.bouncycastle.asn1.x509.Certificate[] { x509Certificate }; Certificate cert = new Certificate(certs); return cert; } /** * Read the given DSI from the card application and card identified through the given connection handle. * * @param cHandle connection handle identifying the card and card application * @param dsiName name of the DSI to read * @return the contents of the DSI, or null if an error occurred */ private byte[] readCertificateDataset(ConnectionHandleType cHandle, String dsiName) throws CredentialPermissionDenied { byte[] content = null; try { connectApplication(); TargetNameType target = new TargetNameType(); target.setDataSetName(dsiName); performMissingAuthentication(target); // select the dataset which contains the dsi DataSetSelect dataSetSelect = new DataSetSelect(); dataSetSelect.setConnectionHandle(cHandle); dataSetSelect.setDataSetName(dsiName); DataSetSelectResponse dataSetSelectResponse = (DataSetSelectResponse) dispatcher.deliver(dataSetSelect); WSHelper.checkResult(dataSetSelectResponse); // read dsi DSIRead dsiRead = new DSIRead(); dsiRead.setConnectionHandle(cHandle); dsiRead.getConnectionHandle().setCardApplication(cHandle.getCardApplication()); dsiRead.setDSIName(dsiName); DSIReadResponse dsiReadResponse = (DSIReadResponse) dispatcher.deliver(dsiRead); WSHelper.checkResult(dsiReadResponse); content = dsiReadResponse.getDSIContent(); } catch (WSException e) { logger.error("Failed to read certificate data set for DSI: {}.", dsiName, e); } catch (InvocationTargetException e) { logger.error("Failed to read certificate data set for DSI: {}.", dsiName, e); } catch (DispatcherException e) { logger.error("Failed to read certificate data set for DSI: {}.", dsiName, e); } catch (SecurityConditionUnsatisfiable e) { logger.error("Failed to read certificate data set for DSI: {}.", dsiName, e); throw new CredentialPermissionDenied(e); } return content; } /** * Get the Name of the certificate reference data set. * * @return name of the data set, or null if an error occurred */ private String getCertificateDataSetName() { String dataSetName = null; try { DIDGet didGet = new DIDGet(); didGet.setConnectionHandle(handle); didGet.setDIDName(didName); didGet.setDIDScope(DIDScopeType.LOCAL); DIDGetResponse response = (DIDGetResponse) dispatcher.deliver(didGet); CryptoMarkerType cryptoMarker = new CryptoMarkerType(response.getDIDStructure().getDIDMarker()); dataSetName = cryptoMarker.getCertificateRef().getDataSetName(); } catch (DispatcherException e) { logger.error("Failed to get DataSetName for DID: {}.", didName, e); } catch (InvocationTargetException e) { logger.error("Failed to get DataSetName for DID: {}.", didName, e); } catch (NullPointerException e) { logger.error("Failed to get DataSetName for DID: {}.", didName, e); } return dataSetName; } private ConnectionHandleType connectApplication() throws DispatcherException, InvocationTargetException, WSException { // is the application already connected? boolean connected = false; CardApplicationPath pathReq = new CardApplicationPath(); CardApplicationPathType pathType = new CardApplicationPathType(); pathReq.setCardAppPathRequest(pathType); pathType.setChannelHandle(handle.getChannelHandle()); pathType.setContextHandle(handle.getContextHandle()); pathType.setIFDName(handle.getIFDName()); pathType.setCardApplication(handle.getCardApplication()); CardApplicationPathResponse pathRes = (CardApplicationPathResponse) dispatcher.deliver(pathReq); WSHelper.checkResult(pathRes); List<CardApplicationPathType> paths = pathRes.getCardAppPathResultSet().getCardApplicationPathResult(); if (! paths.isEmpty()) { connected = true; } if (! connected) { CardApplicationConnect req = new CardApplicationConnect(); req.setCardApplicationPath(handle); CardApplicationConnectResponse res = (CardApplicationConnectResponse) dispatcher.deliver(req); WSHelper.checkResult(res); return res.getConnectionHandle(); } else { return handle; } } private void performMissingAuthentication(TargetNameType target) throws DispatcherException, WSException, InvocationTargetException, SecurityConditionUnsatisfiable { // get unauthenticated DID ACLResolver resolver = new ACLResolver(dispatcher, handle); List<DIDStructureType> missingDIDs = resolver.getUnsatisfiedDIDs(target); // authenticate those DIDs for (DIDStructureType did : missingDIDs) { DIDAuthenticate req = new DIDAuthenticate(); req.setConnectionHandle(handle); req.setDIDName(did.getDIDName()); req.setDIDScope(did.getDIDScope()); DIDAuthenticationDataType authData = new DIDAuthenticationDataType(); authData.setProtocol(did.getDIDMarker().getProtocol()); // TODO: no further content does not work for all protocols // however it does work for PIN Compare, which seems enough so far req.setAuthenticationProtocolData(authData); DIDAuthenticateResponse res = (DIDAuthenticateResponse) dispatcher.deliver(req); WSHelper.checkResult(res); } } }