/****************************************************************************
* Copyright (C) 2013-2015 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.binding.tctoken;
import generated.TCTokenType;
import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.SecureRandom;
import org.openecard.bouncycastle.crypto.tls.ProtocolVersion;
import org.openecard.bouncycastle.crypto.tls.TlsClient;
import org.openecard.bouncycastle.crypto.tls.TlsClientProtocol;
import org.openecard.bouncycastle.crypto.tls.TlsPSKIdentity;
import org.openecard.common.interfaces.Dispatcher;
import org.openecard.crypto.common.sal.GenericCryptoSignerFinder;
import org.openecard.crypto.tls.ClientCertDefaultTlsClient;
import org.openecard.crypto.tls.ClientCertPSKTlsClient;
import org.openecard.crypto.tls.ClientCertTlsClient;
import org.openecard.crypto.tls.TlsPSKIdentityImpl;
import org.openecard.crypto.tls.auth.CredentialFactory;
import org.openecard.crypto.tls.auth.DynamicAuthentication;
import org.openecard.crypto.tls.verify.SameCertVerifier;
import org.openecard.crypto.tls.auth.SmartCardCredentialFactory;
import org.openecard.crypto.tls.proxy.ProxySettings;
import static org.openecard.binding.tctoken.ex.ErrorTranslations.*;
import org.openecard.common.OpenecardProperties;
import org.openecard.common.util.UrlBuilder;
import org.openecard.crypto.common.ReusableSecureRandom;
import org.openecard.crypto.tls.verify.JavaSecVerifier;
/**
*
* @author Tobias Wich
*/
public class TlsConnectionHandler {
private final Dispatcher dispatcher;
private final TCTokenRequest tokenRequest;
private final ConnectionHandleType handle;
private URL serverAddress;
private String hostname;
private int port;
private String resource;
private String sessionId;
private ClientCertTlsClient tlsClient;
public TlsConnectionHandler(Dispatcher dispatcher, TCTokenRequest tokenRequest, ConnectionHandleType handle)
throws ConnectionError {
this.dispatcher = dispatcher;
this.tokenRequest = tokenRequest;
this.handle = handle;
}
public void setUpClient() throws ConnectionError {
try {
TCTokenType token = tokenRequest.getTCToken();
String cardType = null;
if (handle.getRecognitionInfo() != null) {
cardType = handle.getRecognitionInfo().getCardType();
}
if (cardType == null) {
cardType = tokenRequest.getCardType();
}
// eID servers usually have problems with sni, so disable it for them
// TODO: check occasionally if this still holds
boolean doSni = ! "http://bsi.bund.de/cif/npa.xml".equals(cardType);
sessionId = token.getSessionIdentifier();
serverAddress = new URL(token.getServerAddress());
String serverHost = serverAddress.getHost();
if (Boolean.valueOf(OpenecardProperties.getProperty("legacy.session"))) {
serverAddress = fixServerAddress(serverAddress, sessionId);
}
// extract connection parameters from endpoint
hostname = serverAddress.getHost();
port = serverAddress.getPort();
if (port == -1) {
port = serverAddress.getDefaultPort();
}
resource = serverAddress.getFile();
resource = resource.isEmpty() ? "/" : resource;
String secProto = token.getPathSecurityProtocol();
// use same channel as demanded in TR-03124 sec. 2.4.3
if (isSameChannel()) {
tlsClient = tokenRequest.getTokenContext().getTlsClient();
} else {
// kill open channel in tctoken request, it is not needed anymore
if (tokenRequest.getTokenContext() != null) {
tokenRequest.getTokenContext().closeStream();
}
// determine TLS version to use
boolean tls1 = Boolean.valueOf(OpenecardProperties.getProperty("legacy.tls1"));
ProtocolVersion version = ProtocolVersion.TLSv12;
ProtocolVersion minVersion = ProtocolVersion.TLSv12;
switch (secProto) {
case "urn:ietf:rfc:4346":
minVersion = ProtocolVersion.TLSv11;
version = ProtocolVersion.TLSv11;
break;
case "urn:ietf:rfc:5246":
// no changes
break;
case "urn:ietf:rfc:4279":
minVersion = ProtocolVersion.TLSv11;
break;
}
// Set up TLS connection
DynamicAuthentication tlsAuth = new DynamicAuthentication(serverHost);
switch (secProto) {
case "urn:ietf:rfc:4279":
{
byte[] psk = token.getPathSecurityParameters().getPSK();
TlsPSKIdentity pskId = new TlsPSKIdentityImpl(sessionId.getBytes(), psk);
tlsClient = new ClientCertPSKTlsClient(pskId, serverHost, doSni);
tlsClient.setClientVersion(version);
tlsClient.setMinimumVersion(minVersion);
break;
}
case "urn:ietf:rfc:4346":
case "urn:ietf:rfc:5246":
{
// use a smartcard for client authentication if needed
tlsAuth.setCredentialFactory(makeSmartCardCredential());
tlsClient = new ClientCertDefaultTlsClient(serverHost, doSni);
tlsClient.setClientVersion(version);
tlsClient.setMinimumVersion(minVersion);
// add PKIX verifier
tlsAuth.addCertificateVerifier(new JavaSecVerifier());
break;
}
default:
throw new ConnectionError(UNKNOWN_SEC_PROTOCOL, secProto);
}
// make sure nobody changes the server when the connection gets reestablished
tlsAuth.addCertificateVerifier(new SameCertVerifier());
// save eService certificate for use in EAC
tlsAuth.addCertificateVerifier(new SaveEServiceCertHandler());
// set the authentication class in the tls client
tlsClient.setAuthentication(tlsAuth);
}
} catch (MalformedURLException ex) {
throw new ConnectionError(MALFORMED_URL, ex, "ServerAddress");
}
}
public boolean isSameChannel() {
TCTokenType token = tokenRequest.getTCToken();
String secProto = token.getPathSecurityProtocol();
// check security proto
if (secProto == null || "".equals(secProto)) {
return true;
}
// check PSK value
if (secProto.equals("urn:ietf:rfc:4279")) {
TCTokenType.PathSecurityParameters pathsecParams = token.getPathSecurityParameters();
return pathsecParams == null || pathsecParams.getPSK() == null || pathsecParams.getPSK().length == 0;
} else {
return false;
}
}
public URL getServerAddress() {
return serverAddress;
}
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
public String getResource() {
return resource;
}
public String getSessionId() {
return sessionId;
}
public TlsClient getTlsClient() {
return tlsClient;
}
public TlsClientProtocol createTlsConnection() throws IOException, URISyntaxException {
return createTlsConnection(tlsClient.getClientVersion());
}
public TlsClientProtocol createTlsConnection(ProtocolVersion tlsVersion)
throws IOException, URISyntaxException {
if (! isSameChannel()) {
// normal procedure, create a new channel
Socket socket = ProxySettings.getDefault().getSocket(hostname, port);
tlsClient.setClientVersion(tlsVersion);
// TLS
InputStream sockIn = socket.getInputStream();
OutputStream sockOut = socket.getOutputStream();
SecureRandom sr = ReusableSecureRandom.getInstance();
TlsClientProtocol handler = new TlsClientProtocol(sockIn, sockOut, sr);
handler.connect(tlsClient);
return handler;
} else {
// if something fucks up the channel we are out of luck creating a new one as the TR demands to use the
// exact same channel
return tokenRequest.getTokenContext().getTlsClientProto();
}
}
private static URL fixServerAddress(URL serverAddress, String sessionIdentifier) throws MalformedURLException {
// FIXME: remove this hilariously stupid bull*#@%&/ code which satisfies a mistake introduced by the AA
try {
UrlBuilder b = UrlBuilder.fromUrl(serverAddress);
return b.queryParam("sessionid", sessionIdentifier, false).build().toURL();
} catch (URISyntaxException ex) {
throw new MalformedURLException(ex.getMessage());
}
}
private CredentialFactory makeSmartCardCredential() {
GenericCryptoSignerFinder finder = new GenericCryptoSignerFinder(dispatcher, handle, false);
SmartCardCredentialFactory scFac = new SmartCardCredentialFactory(finder);
return scFac;
}
}