/* * NegotiationCertificate.java June 2013 * * Copyright (C) 2013, Niall Gallagher <niallg@users.sf.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.transport; import static org.simpleframework.transport.TransportEvent.CERTIFICATE_CHALLENGE; import static org.simpleframework.transport.TransportEvent.ERROR; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.RunnableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import javax.security.cert.X509Certificate; import org.simpleframework.transport.trace.Trace; /** * The <code>NegotiationState</code> represents the certificate * that is sent by a client during a secure HTTPS conversation. This * may or may not contain an X509 certificate chain from the client. * If it does not a <code>CertificateChallenge</code> may be used to * issue a renegotiation of the connection. One completion of the * renegotiation the challenge executes a completion operation. * * @author Niall Gallagher */ class NegotiationState implements Certificate { /** * This is used to hold the completion task for the challenge. */ private final RunnableFuture<Certificate> future; /** * This is the handshake used to acquire the certificate details. */ private final Negotiation negotiation; /** * This is the challenge used to request the client certificate. */ private final Challenge challenge; /** * This is the runnable task that is executed on task completion. */ private final Delegate delegate; /** * This is the socket representing the underlying TCP connection. */ private final Socket socket; /** * Constructor for the <code>NegotiationCertificate</code> object. * This creates an object used to provide certificate details and * a means to challenge for certificate details for the connected * client if required. * * @param negotiation the negotiation associated with this * @param socket the underlying TCP connection to the client */ public NegotiationState(Negotiation negotiation, Socket socket) { this.delegate = new Delegate(socket); this.future = new FutureTask<Certificate>(delegate, this); this.challenge = new Challenge(socket); this.negotiation = negotiation; this.socket = socket; } /** * This is used to determine if the state is in challenge mode. * In challenge mode a challenge future will be executed on * completion of the challenge. This will the completion task. * * @return this returns true if the state is in challenge mode */ public boolean isChallenge() { return delegate.isSet(); } /** * This returns the completion task associated with any challenge * made for the client certificate. If this returns null then no * challenge has been made for the client certificate. * * @return this returns the challenge completion task if any */ public RunnableFuture<Certificate> getFuture() { return future; } /** * This returns a challenge for the certificate. A challenge is * issued by providing a <code>Runnable</code> task which is to * be executed when the challenge has completed. Typically this * task should be used to drive completion of an HTTPS request. * * @return this returns a challenge for the client certificate */ public CertificateChallenge getChallenge() throws Exception { return challenge; } /** * This will return the X509 certificate chain, if any, that * has been sent by the client. A certificate chain is typically * only send when the server explicitly requests the certificate * on the initial connection or when it is challenged for. * * @return this returns the clients X509 certificate chain */ public X509Certificate[] getChain() throws Exception { SSLSession session = getSession(); if(session != null) { return session.getPeerCertificateChain(); } return null; } /** * This is used to acquire the SSL session associated with the * handshake. The session makes all of the details associated * with the handshake available, including the cipher suites * used and the SSL context used to create the session. * * @return the SSL session associated with the connection */ public SSLSession getSession() throws Exception{ SSLEngine engine = socket.getEngine(); if(engine != null) { return engine.getSession(); } return null; } /** * This is used to determine if the X509 certificate chain is * present for the request. If it is not present then a challenge * can be used to request the certificate. * * @return true if the certificate chain is present */ public boolean isChainPresent() { try { return getChain() != null; } catch(Exception e) { return false; } } /** * The <code>Challenge</code> object is used to enable the server * to challenge for the client X509 certificate if desired. It * performs the challenge by performing an SSL renegotiation to * request that the client sends the */ private class Challenge implements CertificateChallenge { /** * This is the SSL engine that is used to begin the handshake. */ private final SSLEngine engine; /** * This is used to trace the certificate challenge request. */ private final Trace trace; /** * Constructor for the <code>Challenge</code> object. This can * be used to challenge the client for their X509 certificate. * It does this by performing an SSL renegotiation on the * existing TCP connection. * * @param socket this is the TCP connection to the client */ public Challenge(Socket socket) { this.engine = socket.getEngine(); this.trace = socket.getTrace(); } /** * This method will challenge the client for their certificate. * It does so by performing an SSL renegotiation. Successful * completion of the SSL renegotiation results in the client * providing their certificate, and execution of the task. */ public Future<Certificate> challenge() { return challenge(null); } /** * This method will challenge the client for their certificate. * It does so by performing an SSL renegotiation. Successful * completion of the SSL renegotiation results in the client * providing their certificate, and execution of the task. * * @param completion task to be run on successful challenge */ public Future<Certificate> challenge(Runnable task) { try { if(!isChainPresent()) { resume(task); } else { future.run(); } } catch(Exception cause) { trace.trace(ERROR, cause); } return future; } /** * This method will challenge the client for their certificate. * It does so by performing an SSL renegotiation. Successful * completion of the SSL renegotiation results in the client * providing their certificate, and execution of the task. * * @param completion task to be run on successful challenge */ private void resume(Runnable task) { try { trace.trace(CERTIFICATE_CHALLENGE); delegate.set(task); engine.setNeedClientAuth(true); engine.beginHandshake(); negotiation.resume(); } catch(Exception cause) { trace.trace(ERROR, cause); negotiation.cancel(); } } } /** * The <code>Delegate</code> is basically a settable runnable object. * It enables the challenge to set an optional runnable that will * be executed when the challenge has completed. If the challenge * has not been given a completion task this runs straight through * without any state change or action on the certificate. */ private class Delegate implements Runnable { /** * This is the reference to the runnable that is to be executed. */ private final AtomicReference<Runnable> task; /** * This is used to determine if the challenge is ready to run. */ private final AtomicBoolean ready; /** * This is used to trace any errors when running the task. */ private final Trace trace; /** * Constructor for the <code>Delegate</code> object. This is * used to create a wrapper for the completion task so that it * can be executed safely and have any errors traced. * * @param socket this socket the handshake is associated with */ public Delegate(Socket socket) { this.task = new AtomicReference<Runnable>(); this.ready = new AtomicBoolean(); this.trace = socket.getTrace(); } /** * This is used to determine if the delegate is ready to be * used. It is ready only after the completion task has been * set. When ready a challenge can be executed. * * @return this returns true if a completion task is set */ public boolean isSet() { return ready.get(); } /** * This is used to set the completion task that is to be executed * when the challenge has finished. This can be set to null if * no task is to be executed on completion. * * @param runnable the task to run when the challenge finishes */ public void set(Runnable runnable) { ready.set(true); task.set(runnable); } /** * This is used to run the completion task. If no completion * task has been set this will run through without any change to * the state of the certificate. All errors thrown by the task * will be caught and traced. */ public void run() { try { Runnable runnable = task.get(); if(runnable != null) { runnable.run(); } } catch(Exception cause) { trace.trace(ERROR, cause); } finally { task.set(null); } } } }