summaryrefslogtreecommitdiffstats
path: root/simple/simple-transport/src/main/java/org/simpleframework/transport/SecureTransport.java
blob: 20838735ef58cf0ec545c73e063ec6e2d793c2cc (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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
 * SecureTransport.java February 2007
 *
 * Copyright (C) 2007, 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 java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Map;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.Status;

import org.simpleframework.transport.trace.Trace;

/**
 * The <code>SecureTransport</code> object provides an implementation
 * of a transport used to send and receive data over SSL. Data read
 * from this transport is decrypted using an <code>SSLEngine</code>.
 * Also, all data is written is encrypted with the same engine. This
 * ensures that data can be send and received in a transparent way.
 * 
 * @author Niall Gallagher
 */
class SecureTransport implements Transport {  
   
   /**
    * This is the certificate associated with this SSL connection.
    */
   private Certificate certificate;

   /**
    * This is the transport used to send data over the socket.
    */
   private Transport transport;
   
   /**
    * This buffer is used to output the data for the SSL sent.
    */
   private ByteBuffer output;
   
   /**
    * This is the internal buffer used to exchange the SSL data.
    */
   private ByteBuffer input;

   /**
    * This is the internal buffer used to exchange the SSL data.
    */
   private ByteBuffer swap;
   
   /**
    * This is the SSL engine used to encrypt and decrypt data.
    */
   private SSLEngine engine;
   
   /**
    * This is the trace that is used to monitor socket activity.
    */
   private Trace trace;

   /**
    * This is used to determine if the transport was closed.
    */ 
   private boolean closed;
   
   /**
    * This is used to determine if the end of stream was reached.
    */
   private boolean finished;
   
   /**
    * Constructor for the <code>SecureTransport</code> object. This
    * is used to create a transport for sending and receiving data
    * over SSL. This must be created with a pipeline that has already
    * performed the SSL handshake and is read to used.
    * 
    * @param transport this is the transport to delegate operations to
    * @param certificate this is the certificate for the connection     
    * @param input this is the input buffer used to read the data
    * @param swap this is the swap buffer to be used for reading 
    */
   public SecureTransport(Transport transport, Certificate certificate, ByteBuffer input, ByteBuffer swap) {
      this(transport, certificate, input, swap, 20480);
   }
   
   /**
    * Constructor for the <code>SecureTransport</code> object. This
    * is used to create a transport for sending and receiving data
    * over SSL. This must be created with a pipeline that has already
    * performed the SSL handshake and is read to used.
    * 
    * @param transport this is the transport to delegate operations to
    * @param certificate this is the certificate for the connection
    * @param input this is the input buffer used to read the data
    * @param swap this is the swap buffer to be used for reading 
    * @param size this is the size of the buffers to be allocated
    */
   public SecureTransport(Transport transport, Certificate certificate, ByteBuffer input, ByteBuffer swap, int size) {
      this.output = ByteBuffer.allocate(size);
      this.engine = transport.getEngine();
      this.trace = transport.getTrace();
      this.certificate = certificate;
      this.transport = transport;
      this.input = input;    
      this.swap = swap;
   }

   /**
    * This is used to acquire the SSL certificate used when the
    * server is using a HTTPS connection. For plain text connections
    * or connections that use a security mechanism other than SSL
    * this will be null. This is only available when the connection
    * makes specific use of an SSL engine to secure the connection.
    * 
    * @return this returns the associated SSL certificate if any
    */
   public Certificate getCertificate() {
      return certificate;
   }
   
   /**
    * This is used to acquire the trace object that is associated
    * with the socket. A trace object is used to collection details
    * on what operations are being performed on the socket. For
    * instance it may contain information relating to I/O events
    * or more application specific events such as errors. 
    * 
    * @return this returns the trace associated with this socket
    */
   public Trace getTrace() {
      return trace;
   }
   
   /**
    * This is used to acquire the SSL engine used for HTTPS. If the
    * pipeline is connected to an SSL transport this returns an SSL
    * engine which can be used to establish the secure connection
    * and send and receive content over that connection. If this is
    * null then the pipeline represents a normal transport. 
    *  
    * @return the SSL engine used to establish a secure transport
    */   
   public SSLEngine getEngine() {
      return engine;
   }
   
   /**
    * This method is used to get the <code>Map</code> of attributes 
    * by this pipeline. The attributes map is used to maintain details
    * about the connection. Information such as security credentials
    * to client details can be placed within the attribute map.
    *
    * @return this returns the map of attributes for this pipeline
    */   
   public Map getAttributes() {
      return transport.getAttributes();
   }
   
   /**
    * This method is used to acquire the <code>SocketChannel</code>
    * for the connection. This allows the server to acquire the input
    * and output streams with which to communicate. It can also be 
    * used to configure the connection and perform various network 
    * operations that could otherwise not be performed.
    *
    * @return this returns the socket used by this HTTP pipeline
    */ 
   public SocketChannel getChannel() {
      return transport.getChannel();
   }   
   
   /**
    * This is used to perform a non-blocking read on the transport.
    * If there are no bytes available on the input buffers then
    * this method will return zero and the buffer will remain the
    * same. If there is data and the buffer can be filled then this
    * will return the number of bytes read. Finally if the socket
    * is closed this will return a -1 value.
    *
    * @param buffer this is the buffer to append the bytes to
    *
    * @return this returns the number of bytes that have been read  
    */ 
   public int read(ByteBuffer buffer) throws IOException {
      if(closed) {
         throw new TransportException("Transport is closed");              
      }   
      if(finished) {
         return -1;
      }
      int count = fill(buffer); 
      
      if(count <= 0) {
         return process(buffer);
      }
      return count;
   }
   
   /**
    * This is used to perform a non-blocking read on the transport.
    * If there are no bytes available on the input buffers then
    * this method will return zero and the buffer will remain the
    * same. If there is data and the buffer can be filled then this
    * will return the number of bytes read. 
    *
    * @param buffer this is the buffer to append the bytes to
    *
    * @return this returns the number of bytes that have been read  
    */ 
   private int process(ByteBuffer buffer) throws IOException {
      int size = swap.position();  
      
      if(size >= 0) {
         swap.compact(); 
      }
      int space = swap.remaining(); 
      
      if(space > 0) {
         size = transport.read(swap); 
         
         if(size < 0) {
            finished = true;
         }
      }
      if(size > 0 || space > 0) { 
         swap.flip();
         receive(); 
      }
      return fill(buffer);
   }     
   
   /**
    * This is used to fill the provided buffer with data that has 
    * been read from the secure socket channel. This enables reading
    * of the decrypted data in chunks that are smaller than the 
    * size of the input buffer used to contain the plain text data.
    * 
    * @param buffer this is the buffer to append the bytes to
    *
    * @return this returns the number of bytes that have been read 
    */
   private int fill(ByteBuffer buffer) throws IOException {
      int space = buffer.remaining();
      int count = input.position();
      
      if(count > 0) {
         if(count > space) {
            count = space;
         }
      }
      return fill(buffer, count);
      
   }

   /**
    * This is used to fill the provided buffer with data that has 
    * been read from the secure socket channel. This enables reading
    * of the decrypted data in chunks that are smaller than the 
    * size of the input buffer used to contain the plain text data.
    * 
    * @param buffer this is the buffer to append the bytes to
    * @param count this is the number of bytes that are to be read
    *
    * @return this returns the number of bytes that have been read 
    */
   private int fill(ByteBuffer buffer, int count) throws IOException {
      input.flip();
      
      if(count > 0) {
         count = append(buffer, count);
      }
      input.compact();    
      return count;
   }
   
   /**
    * This will append bytes within the transport to the given buffer. 
    * Once invoked the buffer will contain the transport bytes, which
    * will have been drained from the buffer. This effectively moves
    * the bytes in the buffer to the end of the packet instance.
    *
    * @param buffer this is the buffer containing the bytes
    * @param count this is the number of bytes that should be used
    *
    * @return returns the number of bytes that have been moved
    */  
   private int append(ByteBuffer buffer, int count) throws IOException {
      ByteBuffer segment = input.slice();

      if(closed) {
         throw new TransportException("Transport is closed");               
      }
      int mark = input.position();
      int size = mark + count;
      
      if(count > 0) {
         input.position(size); 
         segment.limit(count);         
         buffer.put(segment);
      }
      return count;
   }
   
   /**
    * This is used to perform a non-blocking read on the transport.
    * If there are no bytes available on the input buffers then
    * this method will return zero and the buffer will remain the
    * same. If there is data and the buffer can be filled then this
    * will return the number of bytes read. Finally if the socket
    * is closed this will return a -1 value.
    */    
   private void receive() throws IOException {
      int count = swap.remaining(); 
      
      while(count > 0) {
         SSLEngineResult result = engine.unwrap(swap, input);
         Status status = result.getStatus();
         
         switch(status) {
         case BUFFER_OVERFLOW:
         case BUFFER_UNDERFLOW:
            return;
         case CLOSED:     
            throw new TransportException("Transport error " + result);       
         }    
         count = swap.remaining();
         
         if(count <= 0) {
            break;
         }
      }
   }
   
   /**
    * This method is used to deliver the provided buffer of bytes to
    * the underlying transport. Depending on the connection type the
    * array may be encoded for SSL transport or send directly. Any
    * implementation may choose to buffer the bytes for performance.
    *
    * @param buffer this is the array of bytes to send to the client
    */ 
   public void write(ByteBuffer buffer) throws IOException {    
      if(closed) {
        throw new TransportException("Transport is closed");              
      }            
      int capacity = output.capacity();
      int ready = buffer.remaining();
      int length = ready;
      
      while(ready > 0) {
         int size = Math.min(ready, capacity / 2);
         int mark = buffer.position();
         
         if(length * 2 > capacity) {                      
            buffer.limit(mark + size);         
         }
         send(buffer);
         output.clear();
         ready -= size;
      }
   }
   
   /**
    * This method is used to deliver the provided buffer of bytes to
    * the underlying transport. Depending on the connection type the
    * array may be encoded for SSL transport or send directly. Any
    * implementation may choose to buffer the bytes for performance.
    *
    * @param buffer this is the array of bytes to send to the client
    */ 
   private void send(ByteBuffer buffer) throws IOException {   
      SSLEngineResult result = engine.wrap(buffer, output);
      Status status = result.getStatus();
      
      switch(status){
      case BUFFER_OVERFLOW:
      case BUFFER_UNDERFLOW:
      case CLOSED:
         throw new TransportException("Transport error " + status);
      default:
         output.flip(); 
      }     
      transport.write(output);
   }
   
   /**
    * This method is used to flush the contents of the buffer to 
    * the client. This method will block until such time as all of
    * the data has been sent to the client. If at any point there
    * is an error sending the content an exception is thrown.    
    */     
   public void flush() throws IOException {
      if(closed) {
         throw new TransportException("Transport is closed");              
       }           
       transport.flush(); 
   }
   
   /**
    * This is used to close the sender and the underlying transport.
    * If a close is performed on the sender then no more bytes can
    * be read from or written to the transport and the client will
    * received a connection close on their side.
    */  
   public void close() throws IOException {
      if(!closed) {
         transport.close();
         closed = true;
      }
   }
}