1 /*
2  * Copyright (c) 2015, 2018, 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 import javax.net.ssl.KeyManagerFactory;
25 import javax.net.ssl.SNIHostName;
26 import javax.net.ssl.SNIMatcher;
27 import javax.net.ssl.SNIServerName;
28 import javax.net.ssl.SSLContext;
29 import javax.net.ssl.SSLEngine;
30 import javax.net.ssl.SSLSession;
31 import javax.net.ssl.SSLEngineResult;
32 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
33 import javax.net.ssl.SSLException;
34 import javax.net.ssl.SSLParameters;
35 import javax.net.ssl.TrustManagerFactory;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.IOException;
39 import java.nio.ByteBuffer;
40 import java.security.KeyManagementException;
41 import java.security.KeyStore;
42 import java.security.KeyStoreException;
43 import java.security.NoSuchAlgorithmException;
44 import java.security.UnrecoverableKeyException;
45 import java.security.cert.CertificateException;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.HashMap;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Map;
52 
53 /**
54  * Basic class to inherit SSLEngine test cases from it. Tests apply for
55  * the TLS or DTLS security protocols and their versions.
56  */
57 abstract public class SSLEngineTestCase {
58 
59     public enum Ciphers {
60 
61         /**
62          * Ciphers supported by the tested SSLEngine without those with
63          * kerberos authentication.
64          */
65         SUPPORTED_NON_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_CIPHERS,
66                 "Supported non kerberos"),
67         /**
68          * Ciphers supported by the tested SSLEngine without those with
69          * kerberos authentication and without those with SHA256 ans SHA384.
70          */
71         SUPPORTED_NON_KRB_NON_SHA_CIPHERS(
72                 SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS,
73                 "Supported non kerberos non SHA256 and SHA384"),
74         /**
75          * Ciphers supported by the tested SSLEngine with kerberos
76          * authentication.
77          */
78         SUPPORTED_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_KRB_CIPHERS,
79                 "Supported kerberos"),
80         /**
81          * Ciphers enabled by default for the tested SSLEngine without kerberos
82          * and anon.
83          */
84         ENABLED_NON_KRB_NOT_ANON_CIPHERS(
85                 SSLEngineTestCase.ENABLED_NON_KRB_NOT_ANON_CIPHERS,
86                 "Enabled by default non kerberos not anonymous"),
87         /**
88          * Ciphers supported by TLS 1.3 only.
89          */
90         TLS13_CIPHERS(
91                 SSLEngineTestCase.TLS13_CIPHERS,
92                 "Supported by TLS 1.3 only"),
93         /**
94          * Ciphers unsupported by the tested SSLEngine.
95          */
96         UNSUPPORTED_CIPHERS(SSLEngineTestCase.UNSUPPORTED_CIPHERS,
97                 "Unsupported");
98 
Ciphers(String[] ciphers, String description)99         Ciphers(String[] ciphers, String description) {
100             this.ciphers = ciphers;
101             this.description = description;
102         }
103 
104         final String[] ciphers;
105         final String description;
106     }
107 
108     /**
109      * Enumeration used to distinguish handshake mode in
110      * {@link SSLEngineTestCase#doHandshake(javax.net.ssl.SSLEngine,
111      * javax.net.ssl.SSLEngine, int, SSLEngineTestCase.HandshakeMode, boolean)
112      * SSLEngineTestCase.doHandshake} method.
113      */
114     public enum HandshakeMode {
115 
116         /**
117          * Initial handshake done for the first time: both engines call
118          * {@link SSLEngine#beginHandshake()} method.
119          */
120         INITIAL_HANDSHAKE,
121         /**
122          * Repeated handshake done by client: client engine calls
123          * {@link SSLEngine#beginHandshake()} method.
124          */
125         REHANDSHAKE_BEGIN_CLIENT,
126         /**
127          * Repeated handshake done by server: server engine calls
128          * {@link SSLEngine#beginHandshake()} method.
129          */
130         REHANDSHAKE_BEGIN_SERVER;
131     }
132     /**
133      * Security protocol to be tested: "TLS" or "DTLS" or their versions,
134      * e.g. "TLSv1", "TLSv1.1", "TLSv1.2", "DTLSv1.0", "DTLSv1.2".
135      */
136     public static final String TESTED_SECURITY_PROTOCOL
137             = System.getProperty("test.security.protocol", "TLS");
138     /**
139      * Test mode: "norm", "norm_sni" or "krb".
140      * Modes "norm" and "norm_sni" are used to run
141      * with all supported non-kerberos ciphers.
142      * Mode "krb" is used to run with kerberos ciphers.
143      */
144     public static final String TEST_MODE
145             = System.getProperty("test.mode", "norm");
146 
147     private static final String FS = System.getProperty("file.separator", "/");
148     private static final String PATH_TO_STORES = ".." + FS + "etc";
149     private static final String KEY_STORE_FILE = "keystore";
150     private static final String TRUST_STORE_FILE = "truststore";
151     private static final String PASSWD = "passphrase";
152 
153     private static final String KEY_FILE_NAME
154             = System.getProperty("test.src", ".") + FS + PATH_TO_STORES
155             + FS + KEY_STORE_FILE;
156     private static final String TRUST_FILE_NAME
157             = System.getProperty("test.src", ".") + FS + PATH_TO_STORES
158             + FS + TRUST_STORE_FILE;
159 
160     // Need an enhancement to use none-static mutable global variables.
161     private static ByteBuffer net;
162     private static boolean doUnwrapForNotHandshakingStatus;
163     private static boolean endHandshakeLoop = false;
164 
165     private static final int MAX_HANDSHAKE_LOOPS = 100;
166     private static final String EXCHANGE_MSG_SENT = "Hello, peer!";
167     private static final String TEST_SRC = System.getProperty("test.src", ".");
168     private static final String KTAB_FILENAME = "krb5.keytab.data";
169     private static final String KRB_REALM = "TEST.REALM";
170     private static final String KRBTGT_PRINCIPAL = "krbtgt/" + KRB_REALM;
171     private static final String KRB_USER = "USER";
172     private static final String KRB_USER_PASSWORD = "password";
173     private static final String KRB_USER_PRINCIPAL = KRB_USER + "@" + KRB_REALM;
174     private static final String KRB5_CONF_FILENAME = "krb5.conf";
175     private static final String PATH_TO_COMMON = ".." + FS + "TLSCommon";
176     private static final String JAAS_CONF_FILE = PATH_TO_COMMON
177             + FS + "jaas.conf";
178     private static final int DELAY = 1000;
179     private static final String HOST = "localhost";
180     private static final String SERVER_NAME = "service.localhost";
181     private static final String SNI_PATTERN = ".*";
182 
183     private static final String[] TLS13_CIPHERS = {
184             "TLS_AES_256_GCM_SHA384",
185             "TLS_AES_128_GCM_SHA256",
186             "TLS_CHACHA20_POLY1305_SHA256"
187     };
188 
189     private static final String[] SUPPORTED_NON_KRB_CIPHERS;
190 
191     static {
192         try {
193             String[] allSupportedCiphers = getContext()
194                     .createSSLEngine().getSupportedCipherSuites();
195             List<String> supportedCiphersList = new LinkedList<>();
196             for (String cipher : allSupportedCiphers) {
197                 if (!cipher.contains("KRB5")
198                         && !isTLS13Cipher(cipher)
199                         && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
200                     supportedCiphersList.add(cipher);
201                 }
202             }
203             SUPPORTED_NON_KRB_CIPHERS =
204                     supportedCiphersList.toArray(new String[0]);
205         } catch (Exception ex) {
206             throw new Error("Unexpected issue", ex);
207         }
208     }
209 
210     private static final String[] SUPPORTED_NON_KRB_NON_SHA_CIPHERS;
211 
212     static {
213         try {
214             String[] allSupportedCiphers = getContext()
215                     .createSSLEngine().getSupportedCipherSuites();
216             List<String> supportedCiphersList = new LinkedList<>();
217             for (String cipher : allSupportedCiphers) {
218                 if (!cipher.contains("KRB5")
219                         && !isTLS13Cipher(cipher)
220                         && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")
221                         && !cipher.endsWith("_SHA256")
222                         && !cipher.endsWith("_SHA384")) {
223                     supportedCiphersList.add(cipher);
224                 }
225             }
226             SUPPORTED_NON_KRB_NON_SHA_CIPHERS
227                     = supportedCiphersList.toArray(new String[0]);
228         } catch (Exception ex) {
229             throw new Error("Unexpected issue", ex);
230         }
231     }
232 
233     private static final String[] SUPPORTED_KRB_CIPHERS;
234 
235     static {
236         try {
237             String[] allSupportedCiphers = getContext()
238                     .createSSLEngine().getSupportedCipherSuites();
239             List<String> supportedCiphersList = new LinkedList<>();
240             for (String cipher : allSupportedCiphers) {
241                 if (cipher.contains("KRB5")
242                         && !isTLS13Cipher(cipher)
243                         && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
244                     supportedCiphersList.add(cipher);
245                 }
246             }
247             SUPPORTED_KRB_CIPHERS = supportedCiphersList.toArray(new String[0]);
248         } catch (Exception ex) {
249             throw new Error("Unexpected issue", ex);
250         }
251     }
252 
253     private static final String[] ENABLED_NON_KRB_NOT_ANON_CIPHERS;
254 
255     static {
256         try {
257             SSLEngine temporary = getContext().createSSLEngine();
258             temporary.setUseClientMode(true);
259             String[] enabledCiphers = temporary.getEnabledCipherSuites();
260             List<String> enabledCiphersList = new LinkedList<>();
261             for (String cipher : enabledCiphers) {
262                 if (!cipher.contains("anon") && !cipher.contains("KRB5")
263                         && !isTLS13Cipher(cipher)
264                         && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
265                     enabledCiphersList.add(cipher);
266                 }
267             }
268             ENABLED_NON_KRB_NOT_ANON_CIPHERS =
269                     enabledCiphersList.toArray(new String[0]);
270         } catch (Exception ex) {
271             throw new Error("Unexpected issue", ex);
272         }
273     }
274 
275     private static final String[] UNSUPPORTED_CIPHERS = {
276             "SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA",
277             "SSL_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA",
278             "SSL_DHE_DSS_WITH_RC4_128_SHA",
279             "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
280             "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA",
281             "SSL_DH_DSS_WITH_DES_CBC_SHA",
282             "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
283             "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA",
284             "SSL_DH_RSA_WITH_DES_CBC_SHA",
285             "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA",
286             "SSL_FORTEZZA_DMS_WITH_NULL_SHA",
287             "SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA",
288             "SSL_RSA_EXPORT1024_WITH_RC4_56_SHA",
289             "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
290             "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA",
291             "SSL_RSA_FIPS_WITH_DES_CBC_SHA",
292             "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
293             "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
294             "TLS_KRB5_WITH_IDEA_CBC_MD5",
295             "TLS_KRB5_WITH_IDEA_CBC_SHA",
296             "SSL_RSA_WITH_IDEA_CBC_SHA",
297             "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
298             "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
299             "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
300             "TLS_DH_DSS_WITH_AES_256_GCM_SHA384"
301     };
302 
303     private final int maxPacketSize;
304 
305     /**
306      * Constructs test case with the given MFLN maxMacketSize.
307      *
308      * @param maxPacketSize - MLFN extension max packet size.
309      */
SSLEngineTestCase(int maxPacketSize)310     public SSLEngineTestCase(int maxPacketSize) {
311         this.maxPacketSize = maxPacketSize;
312     }
313 
314     /**
315      * Constructs test case with {@code maxPacketSize = 0}.
316      */
SSLEngineTestCase()317     public SSLEngineTestCase() {
318         this.maxPacketSize = 0;
319     }
320 
isTLS13Cipher(String cipher)321     private static boolean isTLS13Cipher(String cipher) {
322         for (String cipherSuite : TLS13_CIPHERS) {
323             if (cipherSuite.equals(cipher)) {
324                 return true;
325             }
326         }
327 
328         return false;
329     }
330 
331     /**
332      * Wraps data with the specified engine.
333      *
334      * @param engine        - SSLEngine that wraps data.
335      * @param wrapper       - Set wrapper id, e.g. "server" of "client".
336      *                        Used for logging only.
337      * @param maxPacketSize - Max packet size to check that MFLN extension
338      *                        works or zero for no check.
339      * @param app           - Buffer with data to wrap.
340      * @return - Buffer with wrapped data.
341      * @throws SSLException - thrown on engine errors.
342      */
doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app)343     public static ByteBuffer doWrap(SSLEngine engine, String wrapper,
344                                     int maxPacketSize, ByteBuffer app)
345             throws SSLException {
346         return doWrap(engine, wrapper, maxPacketSize,
347                 app, SSLEngineResult.Status.OK, null);
348     }
349 
350     /**
351      * Wraps data with the specified engine.
352      *
353      * @param engine        - SSLEngine that wraps data.
354      * @param wrapper       - Set wrapper id, e.g. "server" of "client".
355      *                        Used for logging only.
356      * @param maxPacketSize - Max packet size to check that MFLN extension
357      *                        works or zero for no check.
358      * @param app           - Buffer with data to wrap.
359      * @param result        - Array which first element will be used to
360      *                        output wrap result object.
361      * @return - Buffer with wrapped data.
362      * @throws SSLException - thrown on engine errors.
363      */
doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult[] result)364     public static ByteBuffer doWrap(SSLEngine engine, String wrapper,
365                                     int maxPacketSize, ByteBuffer app,
366                                     SSLEngineResult[] result)
367             throws SSLException {
368         return doWrap(engine, wrapper, maxPacketSize,
369                 app, SSLEngineResult.Status.OK, result);
370     }
371 
372     /**
373      * Wraps data with the specified engine.
374      *
375      * @param engine        - SSLEngine that wraps data.
376      * @param wrapper       - Set wrapper id, e.g. "server" of "client".
377      *                        Used for logging only.
378      * @param maxPacketSize - Max packet size to check that MFLN extension
379      *                        works or zero for no check.
380      * @param app           - Buffer with data to wrap.
381      * @param wantedStatus  - Specifies expected result status of wrapping.
382      * @return - Buffer with wrapped data.
383      * @throws SSLException - thrown on engine errors.
384      */
doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult.Status wantedStatus)385     public static ByteBuffer doWrap(SSLEngine engine, String wrapper,
386                                     int maxPacketSize, ByteBuffer app,
387                                     SSLEngineResult.Status wantedStatus)
388             throws SSLException {
389         return doWrap(engine, wrapper, maxPacketSize,
390                 app, wantedStatus, null);
391     }
392 
393     /**
394      * Wraps data with the specified engine.
395      *
396      * @param engine        - SSLEngine that wraps data.
397      * @param wrapper       - Set wrapper id, e.g. "server" of "client".
398      *                        Used for logging only.
399      * @param maxPacketSize - Max packet size to check that MFLN extension
400      *                        works or zero for no check.
401      * @param app           - Buffer with data to wrap.
402      * @param wantedStatus  - Specifies expected result status of wrapping.
403      * @param result        - Array which first element will be used to output
404      *                        wrap result object.
405      * @return - Buffer with wrapped data.
406      * @throws SSLException - thrown on engine errors.
407      */
doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult.Status wantedStatus, SSLEngineResult[] result)408     public static ByteBuffer doWrap(SSLEngine engine, String wrapper,
409                                     int maxPacketSize, ByteBuffer app,
410                                     SSLEngineResult.Status wantedStatus,
411                                     SSLEngineResult[] result)
412             throws SSLException {
413         ByteBuffer net = ByteBuffer.allocate(engine.getSession()
414                 .getPacketBufferSize());
415         SSLEngineResult r = engine.wrap(app, net);
416         net.flip();
417         int length = net.remaining();
418         System.out.println(wrapper + " wrapped " + length + " bytes.");
419         System.out.println(wrapper + " handshake status is "
420                 + engine.getHandshakeStatus() + " Result is " + r.getStatus());
421         if (maxPacketSize < length && maxPacketSize != 0) {
422             throw new AssertionError("Handshake wrapped net buffer length "
423                     + length + " exceeds maximum packet size "
424                     + maxPacketSize);
425         }
426         checkResult(r, wantedStatus);
427         if (result != null && result.length > 0) {
428             result[0] = r;
429         }
430         return net;
431     }
432 
433     /**
434      * Unwraps data with the specified engine.
435      *
436      * @param engine    - SSLEngine that unwraps data.
437      * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for
438      *                  logging only.
439      * @param net       - Buffer with data to unwrap.
440      * @return - Buffer with unwrapped data.
441      * @throws SSLException - thrown on engine errors.
442      */
doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net)443     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
444             ByteBuffer net) throws SSLException {
445         return doUnWrap(engine, unwrapper,
446                 net, SSLEngineResult.Status.OK, null);
447     }
448 
449     /**
450      * Unwraps data with the specified engine.
451      *
452      * @param engine    - SSLEngine that unwraps data.
453      * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for
454      *                  logging only.
455      * @param net       - Buffer with data to unwrap.
456      * @param result    - Array which first element will be used to output wrap
457      *                  result object.
458      * @return - Buffer with unwrapped data.
459      * @throws SSLException - thrown on engine errors.
460      */
doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult[] result)461     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
462             ByteBuffer net, SSLEngineResult[] result) throws SSLException {
463         return doUnWrap(engine, unwrapper,
464                 net, SSLEngineResult.Status.OK, result);
465     }
466 
467     /**
468      * Unwraps data with the specified engine.
469      *
470      * @param engine       - SSLEngine that unwraps data.
471      * @param unwrapper    - Set unwrapper id, e.g. "server" of "client".
472      *                     Used for logging only.
473      * @param net          - Buffer with data to unwrap.
474      * @param wantedStatus - Specifies expected result status of wrapping.
475      * @return - Buffer with unwrapped data.
476      * @throws SSLException - thrown on engine errors.
477      */
doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult.Status wantedStatus)478     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
479             ByteBuffer net,
480             SSLEngineResult.Status wantedStatus) throws SSLException {
481         return doUnWrap(engine, unwrapper, net, wantedStatus, null);
482     }
483 
484     /**
485      * Unwraps data with the specified engine.
486      *
487      * @param engine       - SSLEngine that unwraps data.
488      * @param unwrapper    - Set unwrapper id, e.g. "server" of "client".
489      *                       Used for logging only.
490      * @param net          - Buffer with data to unwrap.
491      * @param wantedStatus - Specifies expected result status of wrapping.
492      * @param result       - Array which first element will be used to output
493      *                       wrap result object.
494      * @return - Buffer with unwrapped data.
495      * @throws SSLException - thrown on engine errors.
496      */
doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult.Status wantedStatus, SSLEngineResult[] result)497     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
498             ByteBuffer net, SSLEngineResult.Status wantedStatus,
499             SSLEngineResult[] result) throws SSLException {
500 
501         ByteBuffer app = ByteBuffer.allocate(
502                 engine.getSession().getApplicationBufferSize());
503         int length = net.remaining();
504         System.out.println(unwrapper + " unwrapping " + length + " bytes...");
505         SSLEngineResult r = engine.unwrap(net, app);
506         app.flip();
507         System.out.println(unwrapper + " handshake status is "
508                 + engine.getHandshakeStatus() + " Result is " + r.getStatus());
509         checkResult(r, wantedStatus);
510         if (result != null && result.length > 0) {
511             result[0] = r;
512         }
513         return app;
514     }
515 
516     /**
517      * Does the handshake of the two specified engines according to the
518      * {@code mode} specified.
519      *
520      * @param clientEngine  - Client SSLEngine.
521      * @param serverEngine  - Server SSLEngine.
522      * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit.
523      * @param mode          - Handshake mode according to
524      *                        {@link HandshakeMode} enum.
525      * @throws SSLException - thrown on engine errors.
526      */
doHandshake(SSLEngine clientEngine, SSLEngine serverEngine, int maxPacketSize, HandshakeMode mode)527     public static void doHandshake(SSLEngine clientEngine,
528         SSLEngine serverEngine,
529         int maxPacketSize, HandshakeMode mode) throws SSLException {
530 
531         doHandshake(clientEngine, serverEngine, maxPacketSize, mode, false);
532     }
533 
534     /**
535      * Does the handshake of the two specified engines according to the
536      * {@code mode} specified.
537      *
538      * @param clientEngine          - Client SSLEngine.
539      * @param serverEngine          - Server SSLEngine.
540      * @param maxPacketSize         - Maximum packet size for MFLN of zero
541      *                                for no limit.
542      * @param mode                  - Handshake mode according to
543      *                                {@link HandshakeMode} enum.
544      * @param enableReplicatedPacks - Set {@code true} to enable replicated
545      *                                packet sending.
546      * @throws SSLException - thrown on engine errors.
547      */
doHandshake(SSLEngine clientEngine, SSLEngine serverEngine, int maxPacketSize, HandshakeMode mode, boolean enableReplicatedPacks)548     public static void doHandshake(SSLEngine clientEngine,
549             SSLEngine serverEngine, int maxPacketSize,
550             HandshakeMode mode,
551             boolean enableReplicatedPacks) throws SSLException {
552 
553         System.out.println("=============================================");
554         System.out.println("Starting handshake " + mode.name());
555         int loop = 0;
556         if (maxPacketSize < 0) {
557             throw new Error("Test issue: maxPacketSize is less than zero!");
558         }
559         SSLParameters params = clientEngine.getSSLParameters();
560         params.setMaximumPacketSize(maxPacketSize);
561         clientEngine.setSSLParameters(params);
562         params = serverEngine.getSSLParameters();
563         params.setMaximumPacketSize(maxPacketSize);
564         serverEngine.setSSLParameters(params);
565         SSLEngine firstEngine;
566         SSLEngine secondEngine;
567         switch (mode) {
568             case INITIAL_HANDSHAKE:
569                 firstEngine = clientEngine;
570                 secondEngine = serverEngine;
571                 doUnwrapForNotHandshakingStatus = false;
572                 clientEngine.beginHandshake();
573                 serverEngine.beginHandshake();
574                 break;
575             case REHANDSHAKE_BEGIN_CLIENT:
576                 firstEngine = clientEngine;
577                 secondEngine = serverEngine;
578                 doUnwrapForNotHandshakingStatus = true;
579                 clientEngine.beginHandshake();
580                 break;
581             case REHANDSHAKE_BEGIN_SERVER:
582                 firstEngine = serverEngine;
583                 secondEngine = clientEngine;
584                 doUnwrapForNotHandshakingStatus = true;
585                 serverEngine.beginHandshake();
586                 break;
587             default:
588                 throw new Error("Test issue: unknown handshake mode");
589         }
590         endHandshakeLoop = false;
591         while (!endHandshakeLoop) {
592             if (++loop > MAX_HANDSHAKE_LOOPS) {
593                 throw new Error("Too much loops for handshaking");
594             }
595             System.out.println("============================================");
596             System.out.println("Handshake loop " + loop + ": round 1");
597             System.out.println("==========================");
598             handshakeProcess(firstEngine, secondEngine, maxPacketSize,
599                     enableReplicatedPacks);
600             if (endHandshakeLoop) {
601                 break;
602             }
603             System.out.println("Handshake loop " + loop + ": round 2");
604             System.out.println("==========================");
605             handshakeProcess(secondEngine, firstEngine, maxPacketSize,
606                     enableReplicatedPacks);
607         }
608     }
609 
610     /**
611      * Routine to send application data from one SSLEngine to another.
612      *
613      * @param fromEngine - Sending engine.
614      * @param toEngine   - Receiving engine.
615      * @return - Result of unwrap method of the receiving engine.
616      * @throws SSLException - thrown on engine errors.
617      */
sendApplicationData(SSLEngine fromEngine, SSLEngine toEngine)618     public static SSLEngineResult sendApplicationData(SSLEngine fromEngine,
619                                                       SSLEngine toEngine)
620             throws SSLException {
621         String sender = null;
622         String reciever = null;
623         String excMsgSent = EXCHANGE_MSG_SENT;
624         if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) {
625             sender = "Client";
626             reciever = "Server";
627             excMsgSent += " Client.";
628         } else if (toEngine.getUseClientMode() &&
629                 !fromEngine.getUseClientMode()) {
630             sender = "Server";
631             reciever = "Client";
632             excMsgSent += " Server.";
633         } else {
634             throw new Error("Test issue: both engines are in the same mode");
635         }
636         System.out.println("=============================================");
637         System.out.println("Trying to send application data from " + sender
638                 + " to " + reciever);
639         ByteBuffer clientAppSent
640                 = ByteBuffer.wrap(excMsgSent.getBytes());
641         net = doWrap(fromEngine, sender, 0, clientAppSent);
642         SSLEngineResult[] r = new SSLEngineResult[1];
643         ByteBuffer serverAppRecv = doUnWrap(toEngine, reciever, net, r);
644         byte[] serverAppRecvTrunc = Arrays.copyOf(serverAppRecv.array(),
645                 serverAppRecv.limit());
646         String msgRecv = new String(serverAppRecvTrunc);
647         if (!msgRecv.equals(excMsgSent)) {
648             throw new AssertionError(sender + " to " + reciever
649                     + ": application data"
650                     + " has been altered while sending."
651                     + " Message sent: " + "\"" + excMsgSent + "\"."
652                     + " Message recieved: " + "\"" + msgRecv + "\".");
653         }
654         System.out.println("Successful sending application data from " + sender
655                 + " to " + reciever);
656         return r[0];
657     }
658 
659     /**
660      * Close engines by sending "close outbound" message from one SSLEngine to
661      * another.
662      *
663      * @param fromEngine - Sending engine.
664      * @param toEngine   - Receiving engine.
665      * @throws SSLException - thrown on engine errors.
666      */
closeEngines(SSLEngine fromEngine, SSLEngine toEngine)667     public static void closeEngines(SSLEngine fromEngine,
668                                     SSLEngine toEngine) throws SSLException {
669         String from = null;
670         String to = null;
671         ByteBuffer app;
672         if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) {
673             from = "Client";
674             to = "Server";
675         } else if (toEngine.getUseClientMode() &&
676                 !fromEngine.getUseClientMode()) {
677             from = "Server";
678             to = "Client";
679         } else {
680             throw new Error("Both engines are in the same mode");
681         }
682         System.out.println("=============================================");
683         System.out.println(
684                 "Trying to close engines from " + from + " to " + to);
685         // Sending close outbound request to peer
686         fromEngine.closeOutbound();
687         app = ByteBuffer.allocate(
688                 fromEngine.getSession().getApplicationBufferSize());
689         net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED);
690         doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED);
691         app = ByteBuffer.allocate(
692                 fromEngine.getSession().getApplicationBufferSize());
693         net = doWrap(toEngine, to, 0, app, SSLEngineResult.Status.CLOSED);
694         doUnWrap(fromEngine, from, net, SSLEngineResult.Status.CLOSED);
695         if (!toEngine.isInboundDone()) {
696             throw new AssertionError(from + " sent close request to " + to
697                     + ", but " + to + "did not close inbound.");
698         }
699         // Executing close inbound
700         fromEngine.closeInbound();
701         app = ByteBuffer.allocate(
702                 fromEngine.getSession().getApplicationBufferSize());
703         net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED);
704         doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED);
705         if (!toEngine.isOutboundDone()) {
706             throw new AssertionError(from + "sent close request to " + to
707                     + ", but " + to + "did not close outbound.");
708         }
709         System.out.println("Successful closing from " + from + " to " + to);
710     }
711 
712     /**
713      * Runs the same test case for all given {@code ciphers}. Method counts all
714      * failures and throws {@code AssertionError} if one or more tests fail.
715      *
716      * @param ciphers - Ciphers that should be tested.
717      */
runTests(Ciphers ciphers)718     public void runTests(Ciphers ciphers) {
719         int total = ciphers.ciphers.length;
720         int failed = testSomeCiphers(ciphers);
721         if (failed > 0) {
722             throw new AssertionError("" + failed + " of " + total
723                     + " tests failed!");
724         }
725         System.out.println("All tests passed!");
726     }
727 
728     /**
729      * Runs test cases for ciphers defined by the test mode.
730      */
runTests()731     public void runTests() {
732         switch (TEST_MODE) {
733             case "norm":
734             case "norm_sni":
735                 switch (TESTED_SECURITY_PROTOCOL) {
736                     case "DTLSv1.0":
737                     case "TLSv1":
738                     case "TLSv1.1":
739                         runTests(Ciphers.SUPPORTED_NON_KRB_NON_SHA_CIPHERS);
740                         break;
741                     case "DTLSv1.1":
742                     case "TLSv1.2":
743                         runTests(Ciphers.SUPPORTED_NON_KRB_CIPHERS);
744                         break;
745                     case "TLSv1.3":
746                         runTests(Ciphers.TLS13_CIPHERS);
747                         break;
748                 }
749                 break;
750             case "krb":
751                 runTests(Ciphers.SUPPORTED_KRB_CIPHERS);
752                 break;
753             default:
754                 throw new Error(
755                         "Test error: unexpected test mode: " + TEST_MODE);
756         }
757     }
758 
759     /**
760      * Returns maxPacketSize value used for MFLN extension testing
761      *
762      * @return - MLFN extension max packet size.
763      */
getMaxPacketSize()764     public int getMaxPacketSize() {
765         return maxPacketSize;
766     }
767 
768     /**
769      * Checks that status of result {@code r} is {@code wantedStatus}.
770      *
771      * @param r            - Result.
772      * @param wantedStatus - Wanted status of the result.
773      * @throws AssertionError - if status or {@code r} is not
774      *                        {@code wantedStatus}.
775      */
checkResult(SSLEngineResult r, SSLEngineResult.Status wantedStatus)776     public static void checkResult(SSLEngineResult r,
777                                    SSLEngineResult.Status wantedStatus) {
778         SSLEngineResult.Status rs = r.getStatus();
779         if (!rs.equals(wantedStatus)) {
780             throw new AssertionError("Unexpected status " + rs.name()
781                     + ", should be " + wantedStatus.name());
782         }
783     }
784 
785     /**
786      * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and
787      * sets up keys.
788      *
789      * @return - SSLContext with a protocol specified by
790      *           TESTED_SECURITY_PROTOCOL.
791      */
getContext()792     public static SSLContext getContext() {
793         try {
794             java.security.Security.setProperty(
795                     "jdk.tls.disabledAlgorithms", "");
796             java.security.Security.setProperty(
797                     "jdk.certpath.disabledAlgorithms", "");
798             KeyStore ks = KeyStore.getInstance("JKS");
799             KeyStore ts = KeyStore.getInstance("JKS");
800             char[] passphrase = PASSWD.toCharArray();
801             try (FileInputStream keyFileStream =
802                     new FileInputStream(KEY_FILE_NAME)) {
803                 ks.load(keyFileStream, passphrase);
804             }
805             try (FileInputStream trustFileStream =
806                     new FileInputStream(TRUST_FILE_NAME)) {
807                 ts.load(trustFileStream, passphrase);
808             }
809             KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
810             kmf.init(ks, passphrase);
811             TrustManagerFactory tmf =
812                     TrustManagerFactory.getInstance("SunX509");
813             tmf.init(ts);
814             SSLContext sslCtx =
815                     SSLContext.getInstance(TESTED_SECURITY_PROTOCOL);
816             sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
817             return sslCtx;
818         } catch (KeyStoreException | IOException | NoSuchAlgorithmException |
819                 CertificateException | UnrecoverableKeyException |
820                 KeyManagementException ex) {
821             throw new Error("Unexpected exception", ex);
822         }
823     }
824 
825     /**
826      * Sets up and starts kerberos KDC server.
827      */
setUpAndStartKDC()828     public static void setUpAndStartKDC() {
829         String servicePrincipal = "host/" + SERVER_NAME + "@" + KRB_REALM;
830         Map<String, String> principals = new HashMap<>();
831         principals.put(KRB_USER_PRINCIPAL, KRB_USER_PASSWORD);
832         principals.put(KRBTGT_PRINCIPAL, null);
833         principals.put(servicePrincipal, null);
834         System.setProperty("java.security.krb5.conf", KRB5_CONF_FILENAME);
835         startKDC(KRB_REALM, principals, KTAB_FILENAME);
836         System.setProperty("java.security.auth.login.config",
837                 TEST_SRC + FS + JAAS_CONF_FILE);
838         System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
839     }
840 
841     /**
842      * Sets up and starts kerberos KDC server if
843      * SSLEngineTestCase.TEST_MODE is "krb".
844      */
setUpAndStartKDCIfNeeded()845     public static void setUpAndStartKDCIfNeeded() {
846         if (TEST_MODE.equals("krb")) {
847             setUpAndStartKDC();
848         }
849     }
850 
851     /**
852      * Returns client ssl engine.
853      *
854      * @param context - SSLContext to get SSLEngine from.
855      * @param useSNI  - flag used to enable or disable using SNI extension.
856      *                Needed for Kerberos.
857      */
getClientSSLEngine( SSLContext context, boolean useSNI)858     public static SSLEngine getClientSSLEngine(
859             SSLContext context, boolean useSNI) {
860 
861         SSLEngine clientEngine = context.createSSLEngine(HOST, 80);
862         clientEngine.setUseClientMode(true);
863         if (useSNI) {
864             SNIHostName serverName = new SNIHostName(SERVER_NAME);
865             List<SNIServerName> serverNames = new ArrayList<>();
866             serverNames.add(serverName);
867             SSLParameters params = clientEngine.getSSLParameters();
868             params.setServerNames(serverNames);
869             clientEngine.setSSLParameters(params);
870         }
871         return clientEngine;
872     }
873 
874     /**
875      * Returns server ssl engine.
876      *
877      * @param context - SSLContext to get SSLEngine from.
878      * @param useSNI  - flag used to enable or disable using SNI extension.
879      *                Needed for Kerberos.
880      */
getServerSSLEngine( SSLContext context, boolean useSNI)881     public static SSLEngine getServerSSLEngine(
882             SSLContext context, boolean useSNI) {
883 
884         SSLEngine serverEngine = context.createSSLEngine();
885         serverEngine.setUseClientMode(false);
886         if (useSNI) {
887             SNIMatcher matcher = SNIHostName.createSNIMatcher(SNI_PATTERN);
888             List<SNIMatcher> matchers = new ArrayList<>();
889             matchers.add(matcher);
890             SSLParameters params = serverEngine.getSSLParameters();
891             params.setSNIMatchers(matchers);
892             serverEngine.setSSLParameters(params);
893         }
894         return serverEngine;
895     }
896 
897     /**
898      * Runs the test case for one cipher suite.
899      *
900      * @param cipher - Cipher suite name.
901      * @throws SSLException - If tests fails.
902      */
testOneCipher(String cipher)903     abstract protected void testOneCipher(String cipher)
904             throws SSLException;
905 
906     /**
907      * Iterates through an array of ciphers and runs the same test case for
908      * every entry.
909      *
910      * @param ciphers - Array of cipher names.
911      * @return - Number of tests failed.
912      */
testSomeCiphers(Ciphers ciphers)913     protected int testSomeCiphers(Ciphers ciphers) {
914         int failedNum = 0;
915         String description = ciphers.description;
916         System.out.println("===============================================");
917         System.out.println(description + " ciphers testing");
918         System.out.println("===========================================");
919         for (String cs : ciphers.ciphers) {
920             System.out.println("---------------------------------------");
921             System.out.println("Testing cipher suite " + cs);
922             System.out.println("---------------------------------------");
923             Throwable error = null;
924 
925             // Reset global mutable static variables
926             net = null;
927             doUnwrapForNotHandshakingStatus = false;
928             endHandshakeLoop = false;
929 
930             try {
931                 testOneCipher(cs);
932             } catch (Throwable t) {
933                 error = t;
934             }
935             switch (ciphers) {
936                 case SUPPORTED_NON_KRB_CIPHERS:
937                 case SUPPORTED_NON_KRB_NON_SHA_CIPHERS:
938                 case SUPPORTED_KRB_CIPHERS:
939                 case ENABLED_NON_KRB_NOT_ANON_CIPHERS:
940                 case TLS13_CIPHERS:
941                     if (error != null) {
942                         System.out.println("Test Failed: " + cs);
943                         System.err.println("Test Exception for " + cs);
944                         error.printStackTrace();
945                         failedNum++;
946                     } else {
947                         System.out.println("Test Passed: " + cs);
948                     }
949                     break;
950                 case UNSUPPORTED_CIPHERS:
951                     if (error == null) {
952                         System.out.println("Test Failed: " + cs);
953                         System.err.println("Test for " + cs +
954                                 " should have thrown " +
955                                 "IllegalArgumentException, but it has not!");
956                         failedNum++;
957                     } else if (!(error instanceof IllegalArgumentException)) {
958                         System.out.println("Test Failed: " + cs);
959                         System.err.println("Test Exception for " + cs);
960                         error.printStackTrace();
961                         failedNum++;
962                     } else {
963                         System.out.println("Test Passed: " + cs);
964                     }
965                     break;
966                 default:
967                     throw new Error("Test issue: unexpected ciphers: "
968                             + ciphers.name());
969             }
970         }
971 
972         return failedNum;
973     }
974 
975     /**
976      * Method used for the handshake routine.
977      *
978      * @param wrapingEngine         - Engine that is expected to wrap data.
979      * @param unwrapingEngine       - Engine that is expected to unwrap data.
980      * @param maxPacketSize         - Maximum packet size for MFLN of zero
981      *                                for no limit.
982      * @param enableReplicatedPacks - Set {@code true} to enable replicated
983      *                                packet sending.
984      * @throws SSLException - thrown on engine errors.
985      */
handshakeProcess(SSLEngine wrapingEngine, SSLEngine unwrapingEngine, int maxPacketSize, boolean enableReplicatedPacks)986     private static void handshakeProcess(SSLEngine wrapingEngine,
987             SSLEngine unwrapingEngine,
988             int maxPacketSize,
989             boolean enableReplicatedPacks) throws SSLException {
990 
991         HandshakeStatus wrapingHSStatus = wrapingEngine.getHandshakeStatus();
992         HandshakeStatus unwrapingHSStatus =
993                 unwrapingEngine.getHandshakeStatus();
994         SSLEngineResult r;
995         String wrapper, unwrapper;
996         if (wrapingEngine.getUseClientMode()
997                 && !unwrapingEngine.getUseClientMode()) {
998             wrapper = "Client";
999             unwrapper = "Server";
1000         } else if (unwrapingEngine.getUseClientMode()
1001                 && !wrapingEngine.getUseClientMode()) {
1002             wrapper = "Server";
1003             unwrapper = "Client";
1004         } else {
1005             throw new Error("Both engines are in the same mode");
1006         }
1007         System.out.println(
1008                 wrapper + " handshake (wrap) status " + wrapingHSStatus);
1009         System.out.println(
1010                 unwrapper + " handshake (unwrap) status " + unwrapingHSStatus);
1011 
1012         ByteBuffer netReplicatedClient = null;
1013         ByteBuffer netReplicatedServer = null;
1014         switch (wrapingHSStatus) {
1015             case NEED_WRAP:
1016                 if (enableReplicatedPacks) {
1017                     if (net != null) {
1018                         net.flip();
1019                         if (net.remaining() != 0) {
1020                             if (wrapingEngine.getUseClientMode()) {
1021                                 netReplicatedServer = net;
1022                             } else {
1023                                 netReplicatedClient = net;
1024                             }
1025                         }
1026                     }
1027                 }
1028                 ByteBuffer app = ByteBuffer.allocate(
1029                         wrapingEngine.getSession().getApplicationBufferSize());
1030                 net = doWrap(wrapingEngine, wrapper, maxPacketSize, app);
1031                 wrapingHSStatus = wrapingEngine.getHandshakeStatus();
1032                 // No break, falling into unwrapping.
1033             case NOT_HANDSHAKING:
1034                 switch (unwrapingHSStatus) {
1035                     case NEED_TASK:
1036                         runDelegatedTasks(unwrapingEngine);
1037                     case NEED_UNWRAP:
1038                         doUnWrap(unwrapingEngine, unwrapper, net);
1039                         if (enableReplicatedPacks) {
1040                             System.out.println(unwrapper +
1041                                     " unwrapping replicated packet...");
1042                             if (unwrapingEngine.getHandshakeStatus()
1043                                     .equals(HandshakeStatus.NEED_TASK)) {
1044                                 runDelegatedTasks(unwrapingEngine);
1045                             }
1046                             ByteBuffer netReplicated;
1047                             if (unwrapingEngine.getUseClientMode()) {
1048                                 netReplicated = netReplicatedClient;
1049                             } else {
1050                                 netReplicated = netReplicatedServer;
1051                             }
1052                             if (netReplicated != null) {
1053                                 doUnWrap(unwrapingEngine,
1054                                         unwrapper, netReplicated);
1055                             } else {
1056                                 net.flip();
1057                                 doUnWrap(unwrapingEngine, unwrapper, net);
1058                             }
1059                         }
1060                         break;
1061                     case NEED_UNWRAP_AGAIN:
1062                         break;
1063                     case NOT_HANDSHAKING:
1064                         if (doUnwrapForNotHandshakingStatus) {
1065                             System.out.println("Not handshake status unwrap");
1066                             doUnWrap(unwrapingEngine, unwrapper, net);
1067                             doUnwrapForNotHandshakingStatus = false;
1068                             break;
1069                         } else {
1070                             if (wrapingHSStatus ==
1071                                         HandshakeStatus.NOT_HANDSHAKING) {
1072                                 System.out.println("Handshake is completed");
1073                                 endHandshakeLoop = true;
1074                             }
1075                         }
1076                         break;
1077                     case NEED_WRAP:
1078                         SSLSession session = unwrapingEngine.getSession();
1079                         int bufferSize = session.getApplicationBufferSize();
1080                         ByteBuffer b = ByteBuffer.allocate(bufferSize);
1081                         net = doWrap(unwrapingEngine,
1082                                         unwrapper, maxPacketSize, b);
1083                         unwrapingHSStatus =
1084                                 unwrapingEngine.getHandshakeStatus();
1085                         if ((wrapingHSStatus ==
1086                                     HandshakeStatus.NOT_HANDSHAKING) &&
1087                             (unwrapingHSStatus ==
1088                                     HandshakeStatus.NOT_HANDSHAKING)) {
1089 
1090                             System.out.println("Handshake is completed");
1091                             endHandshakeLoop = true;
1092                         }
1093 
1094                         break;
1095                     default:
1096                         throw new Error(
1097                                 "Unexpected unwraping engine handshake status "
1098                                 + unwrapingHSStatus.name());
1099                 }
1100                 break;
1101             case NEED_UNWRAP:
1102                 break;
1103             case NEED_UNWRAP_AGAIN:
1104                 net.flip();
1105                 doUnWrap(wrapingEngine, wrapper, net);
1106                 break;
1107             case NEED_TASK:
1108                 runDelegatedTasks(wrapingEngine);
1109                 break;
1110             default:
1111                 throw new Error("Unexpected wraping engine handshake status "
1112                         + wrapingHSStatus.name());
1113         }
1114     }
1115 
runDelegatedTasks(SSLEngine engine)1116     private static void runDelegatedTasks(SSLEngine engine) {
1117         Runnable runnable;
1118         System.out.println("Running delegated tasks...");
1119         while ((runnable = engine.getDelegatedTask()) != null) {
1120             runnable.run();
1121         }
1122         HandshakeStatus hs = engine.getHandshakeStatus();
1123         if (hs == HandshakeStatus.NEED_TASK) {
1124             throw new Error("Handshake shouldn't need additional tasks.");
1125         }
1126     }
1127 
1128     /**
1129      * Start a KDC server:
1130      * - create a KDC instance
1131      * - create Kerberos principals
1132      * - save Kerberos configuration
1133      * - save keys to keytab file
1134      * - no pre-auth is required
1135      */
startKDC(String realm, Map<String, String> principals, String ktab)1136     private static void startKDC(String realm, Map<String, String> principals,
1137                                  String ktab) {
1138         try {
1139             KDC kdc = KDC.create(realm, HOST, 0, true);
1140             kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE);
1141             if (principals != null) {
1142                 principals.entrySet().stream().forEach((entry) -> {
1143                     String name = entry.getKey();
1144                     String password = entry.getValue();
1145                     if (password == null || password.isEmpty()) {
1146                         System.out.println("KDC: add a principal '" + name
1147                                 + "' with a random password");
1148                         kdc.addPrincipalRandKey(name);
1149                     } else {
1150                         System.out.println("KDC: add a principal '" + name
1151                                 + "' with '" + password + "' password");
1152                         kdc.addPrincipal(name, password.toCharArray());
1153                     }
1154                 });
1155             }
1156             KDC.saveConfig(KRB5_CONF_FILENAME, kdc);
1157             if (ktab != null) {
1158                 File ktabFile = new File(ktab);
1159                 if (ktabFile.exists()) {
1160                     System.out.println("KDC: append keys to an exising "
1161                             + "keytab file " + ktab);
1162                     kdc.appendKtab(ktab);
1163                 } else {
1164                     System.out.println("KDC: create a new keytab file "
1165                             + ktab);
1166                     kdc.writeKtab(ktab);
1167                 }
1168             }
1169             System.out.println("KDC: started on " + HOST + ":" + kdc.getPort()
1170                     + " with '" + realm + "' realm");
1171         } catch (Exception e) {
1172             throw new RuntimeException("KDC: unexpected exception", e);
1173         }
1174     }
1175 }
1176