/* * RequestCertificate.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.http.core; import java.io.IOException; import java.util.concurrent.Future; import javax.security.cert.X509Certificate; import org.simpleframework.http.message.Entity; import org.simpleframework.transport.Certificate; import org.simpleframework.transport.CertificateChallenge; import org.simpleframework.transport.Channel; /** * The <code>RequestCertificate</code> represents a certificate for * an HTTP request. It basically wraps the raw SSL certificate that * comes with the <code>Channel</code>. Wrapping the raw certificate * allows us to enforce the HTTPS workflow for SSL renegotiation, * which requires some rather weird behaviour. Most importantly * we only allow a challenge when the response has not been sent. * * @author Niall Gallagher * * @see org.simpleframework.transport.CertificateChallenge */ class RequestCertificate implements Certificate { /** * This is used to challenge the client for an X509 certificate. */ private final CertificateChallenge challenge; /** * This is the raw underlying certificate for the SSL channel. */ private final Certificate certificate; /** * This is the channel representing the client connection. */ private final Channel channel; /** * Constructor for the <code>RequestCertificate</code>. This is * used to create a wrapper for the raw SSL certificate that * is provided by the underlying SSL session. * * @param observer the observer used to observe the transaction * @param entity the request entity containing the data */ public RequestCertificate(BodyObserver observer, Entity entity) { this.challenge = new Challenge(observer, entity); this.channel = entity.getChannel(); this.certificate = channel.getCertificate(); } /** * 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 { return certificate.getChain(); } /** * 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 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() throws Exception { return certificate.isChainPresent(); } /** * The <code>Challenge</code> provides a basic wrapper around the * challenge provided by the SSL connection. It is used to enforce * the workflow required by HTTP, this workflow requires that the * SSL renegotiation be issued before the response is sent. This * will also throw an exception if a challenge is issued for * a request that already has a client certificate. */ private static class Challenge implements CertificateChallenge { /** * This is the observer used to keep track of the HTTP transaction. */ private final BodyObserver observer; /** * This is the certificate associated with the SSL connection. */ private final Certificate certificate; /** * This is the channel representing the underlying TCP stream. */ private final Channel channel; /** * Constructor for the <code>Challenge</code> object. This is * basically a wrapper for the raw certificate challenge that * will enforce some of the workflow required by HTTPS. * * @param observer this observer used to track the transaction * @param entity this entity containing the request data */ public Challenge(BodyObserver observer, Entity entity) { this.channel = entity.getChannel(); this.certificate = channel.getCertificate(); this.observer = observer; } /** * 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() throws Exception { 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 completion) throws Exception { if(certificate == null) { throw new IOException("Challenging must be done on a secure connection"); } CertificateChallenge challenge = certificate.getChallenge(); if(certificate.isChainPresent()) { throw new IOException("Certificate is already present"); } if(observer.isCommitted()) { throw new IOException("Response has already been committed"); } return challenge.challenge(completion); } } }