summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCertificate.java
blob: 1da8b54121f68a0f5a347cc9ccf7065e51e22ce7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
 * 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);
      }
   }
}