/****************************************************************************
 * 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.control.module.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 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.auth.SmartCardCredentialFactory;
import org.openecard.crypto.tls.proxy.ProxySettings;


/**
 *
 * @author Tobias Wich <tobias.wich@ecsec.de>
 */
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;
    private boolean usesTls = false;

    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
	    boolean noSni = "http://bsi.bund.de/cif/npa.xml".equals(cardType);

	    sessionId = token.getSessionIdentifier();
	    serverAddress = new URL(token.getServerAddress());
	    String serverHost = serverAddress.getHost();
	    String secProto = token.getPathSecurityProtocol();

	    serverAddress = fixServerAddress(serverAddress, sessionId);

	    // extract connection parameters from endpoint
	    hostname = serverAddress.getHost();
	    port = serverAddress.getPort();
	    if (port == -1) {
		port = serverAddress.getDefaultPort();
	    }
	    resource = serverAddress.getFile();

	    // TODO: remove this workaround as soon as eGK server uses HTTPS
	    if (serverAddress.getProtocol().equals("http")) {
		usesTls = false;
		return;
	    } else {
		usesTls = true;
	    }

	    // Set up TLS connection
	    if (secProto.equals("urn:ietf:rfc:4279") || secProto.equals("urn:ietf:rfc:5487")) {
		DynamicAuthentication tlsAuth = new DynamicAuthentication();
		tlsAuth.setHostname(serverHost);
		// FIXME: verify certificate chain as soon as a usable solution exists fpr the trust problem
		//tlsAuth.setCertificateVerifier(new JavaSecVerifier());
		byte[] psk = token.getPathSecurityParameters().getPSK();
		TlsPSKIdentity pskId = new TlsPSKIdentityImpl(sessionId.getBytes(), psk);
		tlsClient = new ClientCertPSKTlsClient(pskId, noSni ? null : serverHost);
		tlsClient.setAuthentication(tlsAuth);
		tlsClient.setClientVersion(ProtocolVersion.TLSv12);
	    } else if (secProto.equals("urn:ietf:rfc:4346")) {
		DynamicAuthentication tlsAuth = new DynamicAuthentication();
		tlsAuth.setHostname(serverHost);
		// use a smartcard for client authentication if needed
		tlsAuth.setCredentialFactory(makeSmartCardCredential());
		// FIXME: verify certificate chain as soon as a usable solution exists fpr the trust problem
		//tlsAuth.setCertificateVerifier(new JavaSecVerifier());
		tlsClient = new ClientCertDefaultTlsClient(noSni ? null : serverHost);
		tlsClient.setAuthentication(tlsAuth);
		tlsClient.setClientVersion(ProtocolVersion.TLSv11);
	    } else {
		throw new ConnectionError("Unknow security protocol '" + secProto + "' requested.");
	    }

	} catch (MalformedURLException ex) {
	    throw new ConnectionError(ex);
	}
    }

    public boolean usesTls() {
	return usesTls;
    }

    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 {
	Socket socket = ProxySettings.getDefault().getSocket(hostname, port);
	tlsClient.setClientVersion(tlsVersion);
	// TLS
	InputStream sockIn = socket.getInputStream();
	OutputStream sockOut = socket.getOutputStream();
	TlsClientProtocol handler = new TlsClientProtocol(sockIn, sockOut);
	handler.connect(tlsClient);

	return handler;
    }

    private static URL fixServerAddress(URL serverAddress, String sessionIdentifier) throws MalformedURLException {
	URL realServerAddress = serverAddress;
	// FIXME: remove this hilariously stupid bull*#@%&/ code which satisfies a mistake introduced by the AA
	String queryPart = serverAddress.getQuery();
	if (queryPart == null || ! (queryPart.contains("?sessionid=") || queryPart.contains("&sessionid="))) {
	    String sAddr = serverAddress.toString();
	    // fix path of url
	    if (serverAddress.getPath().isEmpty()) {
		sAddr += "/";
	    }
	    // add parameter
	    if (sAddr.endsWith("?")) {
		sAddr += "sessionid=" + sessionIdentifier;
	    } else if (sAddr.contains("?")) {
		sAddr += "&sessionid=" + sessionIdentifier;
	    } else {
		sAddr += "?sessionid=" + sessionIdentifier;
	    }
	    realServerAddress = new URL(sAddr);
	}
	// END: ugly fix

	return realServerAddress;
    }

    private CredentialFactory makeSmartCardCredential() {
	GenericCryptoSignerFinder finder = new GenericCryptoSignerFinder(dispatcher, handle, false);
	SmartCardCredentialFactory scFac = new SmartCardCredentialFactory(finder);
	return scFac;
    }

}

@@ -5145,32 +5145,108 @@
 ation(tlsAuth);%0A
+%09%09// TODO: bouncycastle 1.50 has problems with client auth in 1.2, fix this%0A
 %09%09tlsClient.setC

Add note why TLS 1.2 is not used for client auth connections