1 /*
2  * Copyright (c) 2008, 2016, 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 java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.security.Security;
28 import java.util.Locale;
29 import javax.security.auth.callback.Callback;
30 import javax.security.auth.callback.CallbackHandler;
31 import javax.security.auth.callback.NameCallback;
32 import javax.security.auth.callback.PasswordCallback;
33 import sun.security.krb5.Config;
34 
35 /**
36  * This class starts a simple KDC with one realm, several typical principal
37  * names, generates delete-on-exit krb5.conf and keytab files, and setup
38  * system properties for them. There's also a helper method to generate a
39  * JAAS login config file that can be used for JAAS or JGSS apps.
40  * <p>
41  * Just call this line to start everything:
42  * <pre>
43  * new OneKDC(null).writeJaasConf();
44  * </pre>
45  */
46 public class OneKDC extends KDC {
47 
48     public static final String USER = "dummy";
49     public static final char[] PASS = "bogus".toCharArray();
50     public static final String USER2 = "foo";
51     public static final char[] PASS2 = "bar".toCharArray();
52     public static final String KRB5_CONF = "localkdc-krb5.conf";
53     public static final String KTAB = "localkdc.ktab";
54     public static final String JAAS_CONF = "localkdc-jaas.conf";
55     public static final String REALM = "RABBIT.HOLE";
56     public static final String REALM_LOWER_CASE = REALM.toLowerCase(Locale.US);
57     public static String SERVER = "server/host." + REALM_LOWER_CASE;
58     public static String BACKEND = "backend/host." + REALM_LOWER_CASE;
59     public static String KDCHOST = "kdc." + REALM_LOWER_CASE;
60     /**
61      * Creates the KDC and starts it.
62      * @param etype Encryption type, null if not specified
63      * @throws java.lang.Exception if there's anything wrong
64      */
OneKDC(String etype)65     public OneKDC(String etype) throws Exception {
66         super(REALM, KDCHOST, 0, true);
67         addPrincipal(USER, PASS);
68         addPrincipal(USER2, PASS2);
69         addPrincipalRandKey("krbtgt/" + REALM);
70         addPrincipalRandKey(SERVER);
71         addPrincipalRandKey(BACKEND);
72 
73         String extraConfig = "";
74         if (etype != null) {
75             extraConfig += "default_tkt_enctypes=" + etype
76                     + "\ndefault_tgs_enctypes=" + etype;
77             if (etype.startsWith("des")) {
78                 extraConfig += "\nallow_weak_crypto = true";
79             }
80         }
81         KDC.saveConfig(KRB5_CONF, this,
82                 "forwardable = true",
83                 "default_keytab_name = " + KTAB,
84                 extraConfig);
85         System.setProperty("java.security.krb5.conf", KRB5_CONF);
86         // Whatever krb5.conf had been loaded before, we reload ours now.
87         Config.refresh();
88 
89         writeKtab(KTAB);
90         Security.setProperty("auth.login.defaultCallbackHandler",
91                 "OneKDC$CallbackForClient");
92     }
93 
94     /**
95      * Writes a JAAS login config file, which contains as many as useful
96      * entries, including JGSS style initiator/acceptor and normal JAAS
97      * entries with names using existing OneKDC principals.
98      * @throws java.lang.Exception if anything goes wrong
99      */
writeJAASConf()100     public OneKDC writeJAASConf() throws IOException {
101         System.setProperty("java.security.auth.login.config", JAAS_CONF);
102         File f = new File(JAAS_CONF);
103         FileOutputStream fos = new FileOutputStream(f);
104         fos.write((
105                 "com.sun.security.jgss.krb5.initiate {\n" +
106                 "    com.sun.security.auth.module.Krb5LoginModule required;\n};\n" +
107                 "com.sun.security.jgss.krb5.accept {\n" +
108                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
109                 "    principal=\"*\"\n" +
110                 "    useKeyTab=true\n" +
111                 "    isInitiator=false\n" +
112                 "    storeKey=true;\n};\n" +
113                 "client {\n" +
114                 "    com.sun.security.auth.module.Krb5LoginModule required;\n};\n" +
115                 "server {\n" +
116                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
117                 "    principal=\"" + SERVER + "\"\n" +
118                 "    useKeyTab=true\n" +
119                 "    storeKey=true;\n};\n" +
120                 "backend {\n" +
121                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
122                 "    principal=\"" + BACKEND + "\"\n" +
123                 "    useKeyTab=true\n" +
124                 "    storeKey=true\n" +
125                 "    isInitiator=false;\n};\n"
126                 ).getBytes());
127         fos.close();
128         return this;
129     }
130 
131     /**
132      * The default callback handler for JAAS login. Note that this handler is
133      * hard coded to provide only info for USER1. If you need to provide info
134      * for another principal, please use Context.fromUserPass() instead.
135      */
136     public static class CallbackForClient implements CallbackHandler {
handle(Callback[] callbacks)137         public void handle(Callback[] callbacks) {
138             String user = OneKDC.USER;
139             char[] pass = OneKDC.PASS;
140             for (Callback callback : callbacks) {
141                 if (callback instanceof NameCallback) {
142                     System.out.println("Callback for name: " + user);
143                     ((NameCallback) callback).setName(user);
144                 }
145                 if (callback instanceof PasswordCallback) {
146                     System.out.println("Callback for pass: "
147                             + new String(pass));
148                     ((PasswordCallback) callback).setPassword(pass);
149                 }
150             }
151         }
152     }
153 }
154