summaryrefslogtreecommitdiffstats
path: root/simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java
blob: 2a4b9a29090a84ff8c37f90a3dfa749ff63f1cec (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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
/*
 * Handshake.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 static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static org.simpleframework.transport.PhaseType.COMMIT;
import static org.simpleframework.transport.PhaseType.CONSUME;
import static org.simpleframework.transport.PhaseType.PRODUCE;
import static org.simpleframework.transport.TransportEvent.ERROR;
import static org.simpleframework.transport.TransportEvent.HANDSHAKE_BEGIN;
import static org.simpleframework.transport.TransportEvent.HANDSHAKE_DONE;
import static org.simpleframework.transport.TransportEvent.HANDSHAKE_FAILED;
import static org.simpleframework.transport.TransportEvent.READ;
import static org.simpleframework.transport.TransportEvent.WRITE;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Future;

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

import org.simpleframework.transport.reactor.Reactor;
import org.simpleframework.transport.trace.Trace;

/**
 * The <code>Handshake</code> object is used to perform secure SSL
 * negotiations on a pipeline or <code>Transport</code>. This can
 * be used to perform an SSL handshake. To perform the negotiation 
 * this uses an SSL engine provided with the transport to direct 
 * the conversation. The SSL engine tells the negotiation what is
 * expected next, whether this is a response to the client or a 
 * message from it. During the negotiation this may need to wait 
 * for either a write ready event or a read ready event. Event 
 * notification is done using the processor provided.
 *
 * @author Niall Gallagher
 *
 * @see org.simpleframework.transport.TransportProcessor
 */
class Handshake implements Negotiation {   
   
   /**
    * This is the processor used to process the secure transport.
    */
   private final TransportProcessor processor;   
   
   /**
    * This is the certificate associated with this negotiation.
    */
   private final NegotiationState state;
   
   /**
    * This is the socket channel used to read and write data to.
    */
   private final SocketChannel channel;
   
   /**
    * This is the transport dispatched when the negotiation ends.
    */
   private final Transport transport;   
   
   /**
    * This is the reactor used to register for I/O notifications.
    */
   private final Reactor reactor;   
   
   /**
    * This is the output buffer used to generate data to.
    */
   private final ByteBuffer output;
   
   /**
    * This is the input buffer used to read data from the socket.
    */
   private final ByteBuffer input;
   
   /**
    * This is an empty byte buffer used to generate a response.
    */
   private final ByteBuffer empty;
   
   /**
    * This is the SSL engine used to direct the conversation.
    */
   private final SSLEngine engine;
   
   /**
    * This is the trace that is used to monitor handshake events.
    */
   private final Trace trace;
   
   /**
    * This determines if the handshake is from the client side.
    */
   private final boolean client;
   
   /**
    * Constructor for the <code>Handshake</code> object. This is
    * used to create an operation capable of performing negotiations
    * for SSL connections. Typically this is used to perform request
    * response negotiations, such as a handshake or termination.
    *
    * @param processor the processor used to dispatch the transport
    * @param transport the transport to perform the negotiation for     
    * @param reactor this is the reactor used for I/O notifications      
    */
   public Handshake(TransportProcessor processor, Transport transport, Reactor reactor) {
      this(processor, transport, reactor, 20480);           
   }
  
   /**
    * Constructor for the <code>Handshake</code> object. This is
    * used to create an operation capable of performing negotiations
    * for SSL connections. Typically this is used to perform request
    * response negotiations, such as a handshake or termination.
    *
    * @param transport the transport to perform the negotiation for
    * @param processor the processor used to dispatch the transport
    * @param reactor this is the reactor used for I/O notifications     
    * @param size the size of the buffers used for the negotiation
    */
   public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, int size) {
      this(processor, transport, reactor, size, false);
   }
   
   /**
    * Constructor for the <code>Handshake</code> object. This is
    * used to create an operation capable of performing negotiations
    * for SSL connections. Typically this is used to perform request
    * response negotiations, such as a handshake or termination.
    *
    * @param transport the transport to perform the negotiation for
    * @param processor the processor used to dispatch the transport
    * @param reactor this is the reactor used for I/O notifications     
    * @param client determines the side of the SSL handshake
    */
   public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, boolean client) {
      this(processor, transport, reactor, 20480, client);
   }
   
   /**
    * Constructor for the <code>Handshake</code> object. This is
    * used to create an operation capable of performing negotiations
    * for SSL connections. Typically this is used to perform request
    * response negotiations, such as a handshake or termination.
    *
    * @param transport the transport to perform the negotiation for
    * @param processor the processor used to dispatch the transport
    * @param reactor this is the reactor used for I/O notifications  
    * @param size the size of the buffers used for the negotiation
    * @param client determines the side of the SSL handshake
    */
   public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, int size, boolean client) {
      this.state = new NegotiationState(this, transport);
      this.output = ByteBuffer.allocate(size);
      this.input = ByteBuffer.allocate(size);
      this.channel = transport.getChannel();   
      this.engine = transport.getEngine();
      this.trace = transport.getTrace();
      this.empty = ByteBuffer.allocate(0);
      this.processor = processor;
      this.transport = transport;
      this.reactor = reactor;
      this.client = client;
   }
   
   /**
    * This is used to acquire the trace object that is associated
    * with the operation. A trace object is used to collection details
    * on what operations are being performed. For instance it may 
    * contain information relating to I/O events or errors. 
    * 
    * @return this returns the trace associated with this operation
    */  
   public Trace getTrace() {
      return trace;
   }   
   
   /**
    * This returns the socket channel for the connected pipeline. It
    * is this channel that is used to determine if there are bytes
    * that can be read. When closed this is no longer selectable.
    *
    * @return this returns the connected channel for the pipeline
    */
   public SelectableChannel getChannel() {
      return channel;
   }   
   
   /**
    * This is used to start the negotiation. Once started this will
    * send a message to the other side, once sent the negotiation 
    * reads the response. However if the response is not yet ready 
    * this will schedule the negotiation for a selectable operation 
    * ensuring that it can resume execution when ready.
    */
   public void run() {      
      if(engine != null) {
         trace.trace(HANDSHAKE_BEGIN);
         engine.setUseClientMode(client);         
         input.flip();
      }         
      begin();    
   }

   /**
    * This is used to terminate the negotiation. This is excecuted
    * when the negotiation times out. When the negotiation expires it
    * is rejected by the processor and must be canceled. Canceling
    * is basically termination of the connection to free resources.
    */
   public void cancel() {
      try {
         terminate();
      } catch(Exception cause) { 
         trace.trace(ERROR, cause);
      }
   }
   
   /**
    * This is used to start the negotation. Once started this will
    * send a message to the other side, once sent the negotiation 
    * reads the response. However if the response is not yet ready 
    * this will schedule the negotiation for a selectable operation 
    * ensuring that it can resume execution when ready.
    */
   private void begin() {
      try {
         resume();
      } catch(Exception cause) {
         trace.trace(ERROR, cause);
         cancel();
      }
   }
   
   /**
    * This is the main point of execution within the negotiation. It
    * is where the negotiation is performed. Negotiations are done
    * by performing a request response flow, governed by the SSL
    * engine associated with the pipeline. Typically the client is
    * the one to initiate the handshake and the server initiates the
    * termination sequence. This may be executed several times 
    * depending on whether reading or writing blocks.
    */
   public void resume() throws IOException {
      Runnable task = process();

      if(task != null) {
         task.run();
      }
   }
   
   /**
    * This is the main point of execution within the negotiation. It
    * is where the negotiation is performed. Negotiations are done
    * by performing a request response flow, governed by the SSL
    * engine associated with the transport. Typically the client is
    * the one to initiate the handshake and the server initiates the
    * termination sequence. This may be executed several times 
    * depending on whether reading or writing blocks.
    * 
    * @return this returns a task used to execute the next phase
    */
   private Runnable process() throws IOException {
      PhaseType require = exchange();
      
      if(require == CONSUME) {
         return new Consumer(this, reactor, trace);
      } 
      if(require == PRODUCE) {
         return new Producer(this, reactor, trace);
      } 
      return new Committer(this, reactor, trace);
   }
   
   /**
    * This is the main point of execution within the negotiation. It
    * is where the negotiation is performed. Negotiations are done
    * by performing a request response flow, governed by the SSL
    * engine associated with the transport. Typically the client is
    * the one to initiate the handshake and the server initiates the
    * termination sequence. This may be executed several times 
    * depending on whether reading or writing blocks.
    * 
    * @return this returns what is expected next in the negotiation
    */
   private PhaseType exchange() throws IOException {
      HandshakeStatus status = engine.getHandshakeStatus();
      
      switch(status){
      case NEED_WRAP:
         return write();
      case NOT_HANDSHAKING:
      case NEED_UNWRAP:
         return read();
      }      
      return COMMIT;
   }
   
   /**
    * This is used to perform the read part of the negotiation. The
    * read part is where the other side sends information where it
    * is consumed and is used to determine what action to take. 
    * Typically it is the SSL engine that determines what action is
    * to be taken depending on the data send from the other side.
    *
    * @return the next action that should be taken by the handshake
    */
   private PhaseType read() throws IOException {
      return read(5);
   }

   /**
    * This is used to perform the read part of the negotiation. The
    * read part is where the other side sends information where it
    * is consumed and is used to determine what action to take. 
    * Typically it is the SSL engine that determines what action is
    * to be taken depending on the data send from the other side.
    *
    * @param count this is the number of times a read can repeat
    *
    * @return the next action that should be taken by the handshake
    */
   private PhaseType read(int count) throws IOException {
      while(count > 0) {
         SSLEngineResult result = engine.unwrap(input, output); 
         HandshakeStatus status = result.getHandshakeStatus();

         switch(status) {
         case NOT_HANDSHAKING:
            return COMMIT;
         case NEED_WRAP:
            return PRODUCE;
         case FINISHED:
         case NEED_UNWRAP:
            return read(count-1);
         case NEED_TASK:
            execute(); 
         }      
      }
      return CONSUME;
   }
   
   /**
    * This is used to perform the write part of the negotiation. The
    * read part is where the this sends information to the other side
    * and the other side interprets the data and determines what action 
    * to take. After a write the negotiation typically completes or
    * waits for the next response from the other side.
    *
    * @return the next action that should be taken by the handshake
    */
   private PhaseType write() throws IOException {   
      return write(5);
   }
   
   /**
    * This is used to perform the write part of the negotiation. The
    * read part is where the this sends information to the other side
    * and the other side interprets the data and determines what action 
    * to take. After a write the negotiation typically completes or
    * waits for the next response from the other side.
    *
    * @param count this is the number of times a read can repeat
    *
    * @return the next action that should be taken by the handshake
    */
   private PhaseType write(int count) throws IOException {
      while(count > 0) {
         SSLEngineResult result = engine.wrap(empty, output);
         HandshakeStatus status = result.getHandshakeStatus();

         switch(status) {
         case NOT_HANDSHAKING:
         case FINISHED:
         case NEED_UNWRAP:
            return PRODUCE;
         case NEED_WRAP:
            return write(count-1);
         case NEED_TASK:
            execute();
         }
      }
      return PRODUCE;
   }  
   
   /**
    * This is used to execute the delegated tasks. These tasks are
    * used to digest the information received from the client in
    * order to generate a response. This may need to execute several
    * tasks from the associated SSL engine.
    */
   private void execute() throws IOException {
      while(true) {
         Runnable task = engine.getDelegatedTask();

         if(task == null) {
            break;
         }
         task.run();
      }
   }
   
   /**
    * This is used to receive data from the client. If at any
    * point during the negotiation a message is required that
    * can not be read immediately this is used to asynchronously
    * read the data when a select operation is signalled.
    *  
    * @return this returns true when the message has been read
    */
   public boolean receive() throws IOException {
      int count = input.capacity();
      
      if(count > 0) {
         input.compact();
      }
      int size = channel.read(input); 

      if(trace != null) {
        trace.trace(READ, size);
      }      
      if(size < 0) {
         throw new TransportException("Client closed connection");      
      }
      if(count > 0) {
         input.flip(); 
      }
      return size > 0;
   }

   /**
    * Here we attempt to send all data within the output buffer. If
    * all of the data is delivered to the other side then this will
    * return true. If however there is content yet to be sent to
    * the other side then this returns false, telling the negotiation
    * that in order to resume it must attempt to send the content
    * again after a write ready operation on the underlying socket.
    * 
    * @return this returns true if all of the content is delivered
    */
   public boolean send() throws IOException {
      int require = output.position();
      int count = 0;
      
      if(require > 0) { 
         output.flip();
      }
      while(count < require) { 
         int size = channel.write(output);

         if(trace != null) {
            trace.trace(WRITE, size);
         }
         if(size <= 0) {
            break;
         }
         count += size;
      }
      if(require > 0) {
         output.compact(); 
      }
      return count == require;
   }   
   
   /**
    * This method is invoked when the negotiation is done and the
    * next phase of the connection is to take place. This will
    * be invoked when the SSL handshake has completed and the new
    * secure transport is to be handed to the processor.
    */
   private void dispatch() throws IOException {
      Transport secure = new SecureTransport(transport, state, output, input);

      if(processor != null) {
         trace.trace(HANDSHAKE_DONE);
         processor.process(secure);
      }
   }  
   
   /**
    * This method is used to terminate the handshake. Termination
    * typically occurs when there has been some error in the handshake
    * or when there is a timeout on some event, such as waiting for 
    * for a read or write operation to occur. As a result the TCP
    * channel is closed and any challenge future is cancelled.
    */
   private void terminate() throws IOException {
      Future<Certificate> future = state.getFuture();
      
      trace.trace(HANDSHAKE_FAILED);
      transport.close();
      future.cancel(true);
   }
   
   /**
    * This is used to execute the completion task after a challenge
    * for the clients X509 certificate. Execution of the completion
    * task in this way allows any challanger to be notified that
    * the handshake has complete.
    */
   private void complete() throws IOException {
      Runnable task = state.getFuture();
      
      if(task != null) {
         task.run();
      }
   }

   /**
    * This method is invoked when the negotiation is done and the
    * next phase of the connection is to take place. If a certificate
    * challenge was issued then the completion task is executed, if
    * this was the handshake for the initial connection a transport
    * is created and handed to the processor.
    */
   public void commit() throws IOException {
      if(!state.isChallenge()) {
         dispatch();
      } else {
         complete();
      }
   }
   
   /**
    * The <code>Committer</code> task is used to transfer the transport
    * created to the processor. This is executed when the SSL
    * handshake is completed. It allows the transporter to use the
    * newly created transport to read and write in plain text and
    * to have the SSL transport encrypt and decrypt transparently.
    */
   private class Committer extends Phase {
      
      /**
       * Constructor for the <code>Committer</code> task. This is used to
       * pass the transport object object to the processor when the
       * SSL handshake has completed. 
       * 
       * @param state this is the underlying negotiation to use
       * @param reactor this is the reactor used for I/O notifications
       * @param trace the trace that is used to monitor the handshake        
       */
      public Committer(Negotiation state, Reactor reactor, Trace trace) {
         super(state, reactor, trace, OP_READ);
      }

      /**
       * This is used to execute the task. It is up to the specific
       * task implementation to decide what to do when executed. If
       * the task needs to read or write data then it can attempt
       * to perform the read or write, if it incomplete the it can
       * be scheduled for execution with the reactor.
       */
      @Override
      public void execute() throws IOException{
         state.commit();
      }
   }
   
   /**
    * The <code>Consumer</code> task is used to schedule the negotiation
    * for a read operation. This allows the negotiation to receive any
    * messages generated by the client asynchronously. Once this has 
    * completed then it will resume the negotiation.
    */
   private class Consumer extends Phase {
      
      /**
       * Constructor for the <code>Consumer</code> task. This is used 
       * to create a task which will schedule a read operation for 
       * the negotiation. When the operation completes this will 
       * resume the negotiation.
       * 
       * @param state this is the negotiation object that is used
       * @param reactor this is the reactor used for I/O notifications        
       * @param trace the trace that is used to monitor the handshake        
       */
      public Consumer(Negotiation state, Reactor reactor, Trace trace) {
         super(state, reactor, trace, OP_READ);
      }
      
      /**
       * This method is used to determine if the task is ready. This 
       * is executed when the select operation is signalled. When this 
       * is true the the task completes. If not then this will 
       * schedule the task again for the specified select operation.
       * 
       * @return this returns true when the task has completed
       */
      @Override
      protected boolean ready() throws IOException {
         return state.receive();
      }
   }
   
   /**
    * The <code>Producer</code> is used to schedule the negotiation
    * for a write operation. This allows the negotiation to send any
    * messages generated during the negotiation asynchronously. Once
    * this has completed then it will resume the negotiation.
    */
   private class Producer extends Phase {
      
      /**
       * Constructor for the <code>Producer</code> task. This is used 
       * to create a task which will schedule a write operation for 
       * the negotiation. When the operation completes this will 
       * resume the negotiation.
       * 
       * @param state this is the negotiation object that is used
       * @param reactor this is the reactor used for I/O notifications        
       * @param trace the trace that is used to monitor the handshake        
       */
      public Producer(Negotiation state, Reactor reactor, Trace trace) {
         super(state, reactor, trace, OP_WRITE);
      }
      
      /**
       * This method is used to determine if the task is ready. This 
       * is executed when the select operation is signalled. When this 
       * is true the the task completes. If not then this will 
       * schedule the task again for the specified select operation.
       * 
       * @return this returns true when the task has completed
       */
      @Override
      protected boolean ready() throws IOException {
         return state.send();
      }
   }
}