1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.incubator.http;
  25 
  26 import jdk.incubator.http.internal.common.Demand;
  27 import jdk.incubator.http.internal.common.FlowTube;
  28 import jdk.incubator.http.internal.common.SSLFlowDelegate;
  29 import jdk.incubator.http.internal.common.SSLTube;
  30 import jdk.incubator.http.internal.common.SequentialScheduler;
  31 import jdk.incubator.http.internal.common.Utils;
  32 import org.testng.annotations.Test;
  33 
  34 import javax.net.ssl.KeyManagerFactory;
  35 import javax.net.ssl.SSLContext;
  36 import javax.net.ssl.SSLEngine;
  37 import javax.net.ssl.SSLParameters;
  38 import javax.net.ssl.SSLServerSocket;
  39 import javax.net.ssl.SSLServerSocketFactory;
  40 import javax.net.ssl.SSLSocket;
  41 import javax.net.ssl.TrustManagerFactory;
  42 import java.io.BufferedOutputStream;
  43 import java.io.File;
  44 import java.io.FileInputStream;
  45 import java.io.IOException;
  46 import java.io.InputStream;
  47 import java.io.OutputStream;
  48 import java.net.Socket;
  49 import java.nio.ByteBuffer;
  50 import java.security.KeyManagementException;
  51 import java.security.KeyStore;
  52 import java.security.KeyStoreException;
  53 import java.security.NoSuchAlgorithmException;
  54 import java.security.UnrecoverableKeyException;
  55 import java.security.cert.CertificateException;
  56 import java.util.List;
  57 import java.util.Queue;
  58 import java.util.StringTokenizer;
  59 import java.util.concurrent.BlockingQueue;
  60 import java.util.concurrent.CompletableFuture;
  61 import java.util.concurrent.ConcurrentLinkedQueue;
  62 import java.util.concurrent.Executor;
  63 import java.util.concurrent.ExecutorService;
  64 import java.util.concurrent.Executors;
  65 import java.util.concurrent.Flow;
  66 import java.util.concurrent.ForkJoinPool;
  67 import java.util.concurrent.LinkedBlockingQueue;
  68 import java.util.concurrent.SubmissionPublisher;
  69 import java.util.concurrent.atomic.AtomicBoolean;
  70 import java.util.concurrent.atomic.AtomicInteger;
  71 import java.util.concurrent.atomic.AtomicLong;
  72 
  73 @Test
  74 public class SSLTubeTest {
  75 
  76     private static final long COUNTER = 600;
  77     private static final int LONGS_PER_BUF = 800;
  78     private static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
  79 
  80     private static ByteBuffer getBuffer(long startingAt) {
  81         ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
  82         for (int j = 0; j < LONGS_PER_BUF; j++) {
  83             buf.putLong(startingAt++);
  84         }
  85         buf.flip();
  86         return buf;
  87     }
  88 
  89     @Test(timeOut = 30000)
  90     public void run() throws IOException {
  91         /* Start of wiring */
  92         ExecutorService sslExecutor = Executors.newCachedThreadPool();
  93         /* Emulates an echo server */
  94 //        FlowTube server = new SSLTube(createSSLEngine(false),
  95 //                                      sslExecutor,
  96 //                                      new EchoTube(16));
  97         SSLLoopbackSubscriber server =
  98                 new SSLLoopbackSubscriber((new SimpleSSLContext()).get(), sslExecutor);
  99         server.start();
 100 
 101         FlowTube client = new SSLTube(createSSLEngine(true),
 102                                       sslExecutor,
 103                                       server);
 104         SubmissionPublisher<List<ByteBuffer>> p =
 105                 new SubmissionPublisher<>(ForkJoinPool.commonPool(),
 106                                           Integer.MAX_VALUE);
 107         FlowTube.TubePublisher begin = p::subscribe;
 108         CompletableFuture<Void> completion = new CompletableFuture<>();
 109         EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion);
 110         client.connectFlows(begin, end);
 111         /* End of wiring */
 112 
 113         long count = 0;
 114         System.out.printf("Submitting %d buffer arrays\n", COUNTER);
 115         System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
 116         for (long i = 0; i < COUNTER; i++) {
 117             ByteBuffer b = getBuffer(count);
 118             count += LONGS_PER_BUF;
 119             p.submit(List.of(b));
 120         }
 121         System.out.println("Finished submission. Waiting for loopback");
 122         p.close();
 123         try {
 124             completion.join();
 125             System.out.println("OK");
 126         } finally {
 127             sslExecutor.shutdownNow();
 128         }
 129     }
 130 
 131     static class SSLLoopbackSubscriber implements FlowTube {
 132         private final BlockingQueue<ByteBuffer> buffer;
 133         private final Socket clientSock;
 134         private final SSLSocket serverSock;
 135         private final Thread thread1, thread2, thread3;
 136         private volatile Flow.Subscription clientSubscription;
 137         private final SubmissionPublisher<List<ByteBuffer>> publisher;
 138 
 139         SSLLoopbackSubscriber(SSLContext ctx, ExecutorService exec) throws IOException {
 140             SSLServerSocketFactory fac = ctx.getServerSocketFactory();
 141             SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(0);
 142             SSLParameters params = serv.getSSLParameters();
 143             params.setApplicationProtocols(new String[]{"proto2"});
 144             serv.setSSLParameters(params);
 145 
 146 
 147             int serverPort = serv.getLocalPort();
 148             clientSock = new Socket("127.0.0.1", serverPort);
 149             serverSock = (SSLSocket) serv.accept();
 150             this.buffer = new LinkedBlockingQueue<>();
 151             thread1 = new Thread(this::clientWriter, "clientWriter");
 152             thread2 = new Thread(this::serverLoopback, "serverLoopback");
 153             thread3 = new Thread(this::clientReader, "clientReader");
 154             publisher = new SubmissionPublisher<>(exec, Flow.defaultBufferSize(),
 155                     this::handlePublisherException);
 156             SSLFlowDelegate.Monitor.add(this::monitor);
 157         }
 158 
 159         public void start() {
 160             thread1.start();
 161             thread2.start();
 162             thread3.start();
 163         }
 164 
 165         private void handlePublisherException(Object o, Throwable t) {
 166             System.out.println("Loopback Publisher exception");
 167             t.printStackTrace(System.out);
 168         }
 169 
 170         private final AtomicInteger readCount = new AtomicInteger();
 171 
 172         // reads off the SSLSocket the data from the "server"
 173         private void clientReader() {
 174             try {
 175                 InputStream is = clientSock.getInputStream();
 176                 final int bufsize = FlowTest.randomRange(512, 16 * 1024);
 177                 System.out.println("clientReader: bufsize = " + bufsize);
 178                 while (true) {
 179                     byte[] buf = new byte[bufsize];
 180                     int n = is.read(buf);
 181                     if (n == -1) {
 182                         System.out.println("clientReader close: read "
 183                                 + readCount.get() + " bytes");
 184                         publisher.close();
 185                         sleep(2000);
 186                         Utils.close(is, clientSock);
 187                         return;
 188                     }
 189                     ByteBuffer bb = ByteBuffer.wrap(buf, 0, n);
 190                     readCount.addAndGet(n);
 191                     publisher.submit(List.of(bb));
 192                 }
 193             } catch (Throwable e) {
 194                 e.printStackTrace();
 195                 Utils.close(clientSock);
 196             }
 197         }
 198 
 199         // writes the encrypted data from SSLFLowDelegate to the j.n.Socket
 200         // which is connected to the SSLSocket emulating a server.
 201         private void clientWriter() {
 202             long nbytes = 0;
 203             try {
 204                 OutputStream os =
 205                         new BufferedOutputStream(clientSock.getOutputStream());
 206 
 207                 while (true) {
 208                     ByteBuffer buf = buffer.take();
 209                     if (buf == FlowTest.SENTINEL) {
 210                         // finished
 211                         //Utils.sleep(2000);
 212                         System.out.println("clientWriter close: " + nbytes + " written");
 213                         clientSock.shutdownOutput();
 214                         System.out.println("clientWriter close return");
 215                         return;
 216                     }
 217                     int len = buf.remaining();
 218                     int written = writeToStream(os, buf);
 219                     assert len == written;
 220                     nbytes += len;
 221                     assert !buf.hasRemaining()
 222                             : "buffer has " + buf.remaining() + " bytes left";
 223                     clientSubscription.request(1);
 224                 }
 225             } catch (Throwable e) {
 226                 e.printStackTrace();
 227             }
 228         }
 229 
 230         private int writeToStream(OutputStream os, ByteBuffer buf) throws IOException {
 231             byte[] b = buf.array();
 232             int offset = buf.arrayOffset() + buf.position();
 233             int n = buf.limit() - buf.position();
 234             os.write(b, offset, n);
 235             buf.position(buf.limit());
 236             os.flush();
 237             return n;
 238         }
 239 
 240         private final AtomicInteger loopCount = new AtomicInteger();
 241 
 242         public String monitor() {
 243             return "serverLoopback: loopcount = " + loopCount.toString()
 244                     + " clientRead: count = " + readCount.toString();
 245         }
 246 
 247         // thread2
 248         private void serverLoopback() {
 249             try {
 250                 InputStream is = serverSock.getInputStream();
 251                 OutputStream os = serverSock.getOutputStream();
 252                 final int bufsize = FlowTest.randomRange(512, 16 * 1024);
 253                 System.out.println("serverLoopback: bufsize = " + bufsize);
 254                 byte[] bb = new byte[bufsize];
 255                 while (true) {
 256                     int n = is.read(bb);
 257                     if (n == -1) {
 258                         sleep(2000);
 259                         is.close();
 260                         os.close();
 261                         serverSock.close();
 262                         return;
 263                     }
 264                     os.write(bb, 0, n);
 265                     os.flush();
 266                     loopCount.addAndGet(n);
 267                 }
 268             } catch (Throwable e) {
 269                 e.printStackTrace();
 270             }
 271         }
 272 
 273 
 274         /**
 275          * This needs to be called before the chain is subscribed. It can't be
 276          * supplied in the constructor.
 277          */
 278         public void setReturnSubscriber(Flow.Subscriber<List<ByteBuffer>> returnSubscriber) {
 279             publisher.subscribe(returnSubscriber);
 280         }
 281 
 282         @Override
 283         public void onSubscribe(Flow.Subscription subscription) {
 284             clientSubscription = subscription;
 285             clientSubscription.request(5);
 286         }
 287 
 288         @Override
 289         public void onNext(List<ByteBuffer> item) {
 290             try {
 291                 for (ByteBuffer b : item)
 292                     buffer.put(b);
 293             } catch (InterruptedException e) {
 294                 e.printStackTrace();
 295                 Utils.close(clientSock);
 296             }
 297         }
 298 
 299         @Override
 300         public void onError(Throwable throwable) {
 301             throwable.printStackTrace();
 302             Utils.close(clientSock);
 303         }
 304 
 305         @Override
 306         public void onComplete() {
 307             try {
 308                 buffer.put(FlowTest.SENTINEL);
 309             } catch (InterruptedException e) {
 310                 e.printStackTrace();
 311                 Utils.close(clientSock);
 312             }
 313         }
 314 
 315         @Override
 316         public boolean isFinished() {
 317             return false;
 318         }
 319 
 320         @Override
 321         public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
 322             publisher.subscribe(subscriber);
 323         }
 324     }
 325 
 326     private static void sleep(long millis) {
 327         try {
 328             Thread.sleep(millis);
 329         } catch (InterruptedException e) {
 330 
 331         }
 332     }
 333 //    private static final class EchoTube implements FlowTube {
 334 //
 335 //        private final static Object EOF = new Object();
 336 //        private final Executor executor = Executors.newSingleThreadExecutor();
 337 //
 338 //        private final Queue<Object> queue = new ConcurrentLinkedQueue<>();
 339 //        private final int maxQueueSize;
 340 //        private final SequentialScheduler processingScheduler =
 341 //                new SequentialScheduler(createProcessingTask());
 342 //
 343 //        /* Writing into this tube */
 344 //        private long unfulfilled;
 345 //        private Flow.Subscription subscription;
 346 //
 347 //        /* Reading from this tube */
 348 //        private final Demand demand = new Demand();
 349 //        private final AtomicBoolean cancelled = new AtomicBoolean();
 350 //        private Flow.Subscriber<? super List<ByteBuffer>> subscriber;
 351 //
 352 //        private EchoTube(int maxBufferSize) {
 353 //            if (maxBufferSize < 1)
 354 //                throw new IllegalArgumentException();
 355 //            this.maxQueueSize = maxBufferSize;
 356 //        }
 357 //
 358 //        @Override
 359 //        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
 360 //            this.subscriber = subscriber;
 361 //            System.out.println("EchoTube got subscriber: " + subscriber);
 362 //            this.subscriber.onSubscribe(new InternalSubscription());
 363 //        }
 364 //
 365 //        @Override
 366 //        public void onSubscribe(Flow.Subscription subscription) {
 367 //            unfulfilled = maxQueueSize;
 368 //            System.out.println("EchoTube request: " + maxQueueSize);
 369 //            (this.subscription = subscription).request(maxQueueSize);
 370 //        }
 371 //
 372 //        @Override
 373 //        public void onNext(List<ByteBuffer> item) {
 374 //            if (--unfulfilled == (maxQueueSize / 2)) {
 375 //                long req = maxQueueSize - unfulfilled;
 376 //                subscription.request(req);
 377 //                System.out.println("EchoTube request: " + req);
 378 //                unfulfilled = maxQueueSize;
 379 //            }
 380 //            System.out.println("EchoTube add " + Utils.remaining(item));
 381 //            queue.add(item);
 382 //            processingScheduler.deferOrSchedule(executor);
 383 //        }
 384 //
 385 //        @Override
 386 //        public void onError(Throwable throwable) {
 387 //            System.out.println("EchoTube add " + throwable);
 388 //            queue.add(throwable);
 389 //            processingScheduler.deferOrSchedule(executor);
 390 //        }
 391 //
 392 //        @Override
 393 //        public void onComplete() {
 394 //            System.out.println("EchoTube add EOF");
 395 //            queue.add(EOF);
 396 //            processingScheduler.deferOrSchedule(executor);
 397 //        }
 398 //
 399 //        @Override
 400 //        public boolean isFinished() {
 401 //            return false;
 402 //        }
 403 //
 404 //        private class InternalSubscription implements Flow.Subscription {
 405 //
 406 //            @Override
 407 //            public void request(long n) {
 408 //                System.out.println("EchoTube got request: " + n);
 409 //                if (n <= 0) {
 410 //                    throw new InternalError();
 411 //                }
 412 //                demand.increase(n);
 413 //                processingScheduler.runOrSchedule();
 414 //            }
 415 //
 416 //            @Override
 417 //            public void cancel() {
 418 //                cancelled.set(true);
 419 //            }
 420 //        }
 421 //
 422 //        @Override
 423 //        public String toString() {
 424 //            return "EchoTube";
 425 //        }
 426 //
 427 //        private SequentialScheduler.RestartableTask createProcessingTask() {
 428 //            return new SequentialScheduler.CompleteRestartableTask() {
 429 //
 430 //                @Override
 431 //                protected void run() {
 432 //                    try {
 433 //                        while (!cancelled.get()) {
 434 //                            Object item = queue.peek();
 435 //                            if (item == null)
 436 //                                return;
 437 //                            try {
 438 //                                System.out.println("EchoTube processing item");
 439 //                                if (item instanceof List) {
 440 //                                    if (!demand.tryDecrement()) {
 441 //                                        System.out.println("EchoTube no demand");
 442 //                                        return;
 443 //                                    }
 444 //                                    @SuppressWarnings("unchecked")
 445 //                                    List<ByteBuffer> bytes = (List<ByteBuffer>) item;
 446 //                                    Object removed = queue.remove();
 447 //                                    assert removed == item;
 448 //                                    System.out.println("EchoTube processing "
 449 //                                            + Utils.remaining(bytes));
 450 //                                    subscriber.onNext(bytes);
 451 //                                } else if (item instanceof Throwable) {
 452 //                                    cancelled.set(true);
 453 //                                    Object removed = queue.remove();
 454 //                                    assert removed == item;
 455 //                                    System.out.println("EchoTube processing " + item);
 456 //                                    subscriber.onError((Throwable) item);
 457 //                                } else if (item == EOF) {
 458 //                                    cancelled.set(true);
 459 //                                    Object removed = queue.remove();
 460 //                                    assert removed == item;
 461 //                                    System.out.println("EchoTube processing EOF");
 462 //                                    subscriber.onComplete();
 463 //                                } else {
 464 //                                    throw new InternalError(String.valueOf(item));
 465 //                                }
 466 //                            } finally {
 467 //                            }
 468 //                        }
 469 //                    } catch(Throwable t) {
 470 //                        t.printStackTrace();
 471 //                        throw t;
 472 //                    }
 473 //                }
 474 //            };
 475 //        }
 476 //    }
 477 
 478     /**
 479      * The final subscriber which receives the decrypted looped-back data. Just
 480      * needs to compare the data with what was sent. The given CF is either
 481      * completed exceptionally with an error or normally on success.
 482      */
 483     private static class EndSubscriber implements FlowTube.TubeSubscriber {
 484 
 485         private static final int REQUEST_WINDOW = 13;
 486 
 487         private final long nbytes;
 488         private final AtomicLong counter = new AtomicLong();
 489         private final CompletableFuture<?> completion;
 490         private volatile Flow.Subscription subscription;
 491         private long unfulfilled;
 492 
 493         EndSubscriber(long nbytes, CompletableFuture<?> completion) {
 494             this.nbytes = nbytes;
 495             this.completion = completion;
 496         }
 497 
 498         @Override
 499         public void onSubscribe(Flow.Subscription subscription) {
 500             this.subscription = subscription;
 501             unfulfilled = REQUEST_WINDOW;
 502             System.out.println("EndSubscriber request " + REQUEST_WINDOW);
 503             subscription.request(REQUEST_WINDOW);
 504         }
 505 
 506         public static String info(List<ByteBuffer> i) {
 507             StringBuilder sb = new StringBuilder();
 508             sb.append("size: ").append(Integer.toString(i.size()));
 509             int x = 0;
 510             for (ByteBuffer b : i)
 511                 x += b.remaining();
 512             sb.append(" bytes: ").append(x);
 513             return sb.toString();
 514         }
 515 
 516         @Override
 517         public void onNext(List<ByteBuffer> buffers) {
 518             if (--unfulfilled == (REQUEST_WINDOW / 2)) {
 519                 long req = REQUEST_WINDOW - unfulfilled;
 520                 System.out.println("EndSubscriber request " + req);
 521                 subscription.request(req);
 522                 unfulfilled = REQUEST_WINDOW;
 523             }
 524 
 525             long currval = counter.get();
 526             if (currval % 500 == 0) {
 527                 System.out.println("End: " + currval);
 528             }
 529             System.out.println("EndSubscriber onNext " + Utils.remaining(buffers));
 530 
 531             for (ByteBuffer buf : buffers) {
 532                 while (buf.hasRemaining()) {
 533                     long n = buf.getLong();
 534                     if (currval > (SSLTubeTest.TOTAL_LONGS - 50)) {
 535                         System.out.println("End: " + currval);
 536                     }
 537                     if (n != currval++) {
 538                         System.out.println("ERROR at " + n + " != " + (currval - 1));
 539                         completion.completeExceptionally(new RuntimeException("ERROR"));
 540                         subscription.cancel();
 541                         return;
 542                     }
 543                 }
 544             }
 545 
 546             counter.set(currval);
 547         }
 548 
 549         @Override
 550         public void onError(Throwable throwable) {
 551             System.out.println("EndSubscriber onError " + throwable);
 552             completion.completeExceptionally(throwable);
 553         }
 554 
 555         @Override
 556         public void onComplete() {
 557             long n = counter.get();
 558             if (n != nbytes) {
 559                 System.out.printf("nbytes=%d n=%d\n", nbytes, n);
 560                 completion.completeExceptionally(new RuntimeException("ERROR AT END"));
 561             } else {
 562                 System.out.println("DONE OK");
 563                 completion.complete(null);
 564             }
 565         }
 566         @Override
 567         public String toString() {
 568             return "EndSubscriber";
 569         }
 570     }
 571 
 572     private static SSLEngine createSSLEngine(boolean client) throws IOException {
 573         SSLContext context = (new SimpleSSLContext()).get();
 574         SSLEngine engine = context.createSSLEngine();
 575         SSLParameters params = context.getSupportedSSLParameters();
 576         params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
 577         if (client) {
 578             params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
 579         } else {
 580             params.setApplicationProtocols(new String[]{"proto2"}); // server will choose proto2
 581         }
 582         engine.setSSLParameters(params);
 583         engine.setUseClientMode(client);
 584         return engine;
 585     }
 586 
 587     /**
 588      * Creates a simple usable SSLContext for SSLSocketFactory or a HttpsServer
 589      * using either a given keystore or a default one in the test tree.
 590      *
 591      * Using this class with a security manager requires the following
 592      * permissions to be granted:
 593      *
 594      * permission "java.util.PropertyPermission" "test.src.path", "read";
 595      * permission java.io.FilePermission "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys",
 596      * "read"; The exact path above depends on the location of the test.
 597      */
 598     private static class SimpleSSLContext {
 599 
 600         private final SSLContext ssl;
 601 
 602         /**
 603          * Loads default keystore from SimpleSSLContext source directory
 604          */
 605         public SimpleSSLContext() throws IOException {
 606             String paths = System.getProperty("test.src.path");
 607             StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
 608             boolean securityExceptions = false;
 609             SSLContext sslContext = null;
 610             while (st.hasMoreTokens()) {
 611                 String path = st.nextToken();
 612                 try {
 613                     File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
 614                     if (f.exists()) {
 615                         try (FileInputStream fis = new FileInputStream(f)) {
 616                             sslContext = init(fis);
 617                             break;
 618                         }
 619                     }
 620                 } catch (SecurityException e) {
 621                     // catch and ignore because permission only required
 622                     // for one entry on path (at most)
 623                     securityExceptions = true;
 624                 }
 625             }
 626             if (securityExceptions) {
 627                 System.err.println("SecurityExceptions thrown on loading testkeys");
 628             }
 629             ssl = sslContext;
 630         }
 631 
 632         private SSLContext init(InputStream i) throws IOException {
 633             try {
 634                 char[] passphrase = "passphrase".toCharArray();
 635                 KeyStore ks = KeyStore.getInstance("JKS");
 636                 ks.load(i, passphrase);
 637 
 638                 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
 639                 kmf.init(ks, passphrase);
 640 
 641                 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
 642                 tmf.init(ks);
 643 
 644                 SSLContext ssl = SSLContext.getInstance("TLS");
 645                 ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 646                 return ssl;
 647             } catch (KeyManagementException | KeyStoreException |
 648                     UnrecoverableKeyException | CertificateException |
 649                     NoSuchAlgorithmException e) {
 650                 throw new RuntimeException(e.getMessage());
 651             }
 652         }
 653 
 654         public SSLContext get() {
 655             return ssl;
 656         }
 657     }
 658 }