1/*
2 * Copyright (c) 2008, 2011, 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/*
25 * @test
26 * @bug 6765491
27 * @run main/othervm LoginModuleOptions
28 * @summary Krb5LoginModule a little too restrictive, and the doc is not clear.
29 */
30import com.sun.security.auth.module.Krb5LoginModule;
31import java.util.HashMap;
32import java.util.Map;
33import javax.security.auth.Subject;
34import javax.security.auth.callback.Callback;
35import javax.security.auth.callback.CallbackHandler;
36import javax.security.auth.callback.NameCallback;
37import javax.security.auth.callback.PasswordCallback;
38
39public class LoginModuleOptions {
40
41    private static final String NAME = "javax.security.auth.login.name";
42    private static final String PWD = "javax.security.auth.login.password";
43
44    public static void main(String[] args) throws Exception {
45        OneKDC kdc = new OneKDC(null);
46        kdc.addPrincipal("foo", "bar".toCharArray());
47        kdc.writeKtab(OneKDC.KTAB); // rewrite to add foo
48
49        // All 4 works: keytab, shared state, callback, cache
50        login(null, "useKeyTab", "true", "principal", "dummy");
51        login(null, "tryFirstPass", "true", NAME, OneKDC.USER,
52                PWD, OneKDC.PASS);
53        System.setProperty("test.kdc.save.ccache", "krbcc");
54        login(new MyCallback(OneKDC.USER, OneKDC.PASS));    // save the cache
55        System.clearProperty("test.kdc.save.ccache");
56        login(null, "useTicketCache", "true", "ticketCache", "krbcc");
57
58        // Fallbacks
59        // 1. ccache -> keytab
60        login(null, "useTicketCache", "true", "ticketCache", "krbcc_non_exists",
61                "useKeyTab", "true", "principal", "dummy");
62
63        // 2. keytab -> shared
64        login(null, "useKeyTab", "true", "principal", "dummy",
65                "keyTab", "ktab_non_exist",
66                "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
67
68        // 3. shared -> callback
69        // 3.1. useFirstPass, no callback
70        boolean failed = false;
71        try {
72            login(new MyCallback(OneKDC.USER, OneKDC.PASS),
73                    "useFirstPass", "true",
74                    NAME, OneKDC.USER, PWD, "haha".toCharArray());
75        } catch (Exception e) {
76            failed = true;
77        }
78        if (!failed) {
79            throw new Exception("useFirstPass should not fallback to callback");
80        }
81        // 3.2. tryFirstPass, has callback
82        login(new MyCallback(OneKDC.USER, OneKDC.PASS),
83                "tryFirstPass", "true",
84                NAME, OneKDC.USER, PWD, "haha".toCharArray());
85
86        // Preferences of type
87        // 1. ccache preferred to keytab
88        login(new MyCallback("foo", null),
89                "useTicketCache", "true", "ticketCache", "krbcc",
90                "useKeyTab", "true");
91        // 2. keytab preferred to shared. This test case is not exactly correct,
92        // because principal=dummy would shadow the PWD setting in the shared
93        // state. So by only looking at the final authentication user name
94        // (which is how this program does), there's no way to tell if keyTab
95        // is picked first, or shared is tried first but fallback to keytab.
96        login(null, "useKeyTab", "true", "principal", "dummy",
97                "tryFirstPass", "true", NAME, "foo", PWD, "bar".toCharArray());
98        // 3. shared preferred to callback
99        login(new MyCallback("foo", "bar".toCharArray()),
100                "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
101
102        // Preferences of username
103        // 1. principal preferred to NAME (NAME can be wrong or missing)
104        login(null, "principal", OneKDC.USER,
105                "tryFirstPass", "true", NAME, "someone_else", PWD, OneKDC.PASS);
106        login(null, "principal", OneKDC.USER,
107                "tryFirstPass", "true", PWD, OneKDC.PASS);
108        // 2. NAME preferred to callback
109        login(new MyCallback("someone_else", OneKDC.PASS),
110                "principal", OneKDC.USER);
111        // 3. With tryFirstPass, NAME preferred to callback
112        login(new MyCallback("someone_else", null),
113                "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
114        // 3.1. you must provide a NAME (when there's no principal)
115        failed = false;
116        try {
117            login(new MyCallback(OneKDC.USER, null),
118                    "tryFirstPass", "true", PWD, OneKDC.PASS);
119        } catch (Exception e) {
120            failed = true;
121        }
122        if (!failed) {
123            throw new Exception("useFirstPass must provide a NAME");
124        }
125        // 3.2 Hybrid, you can use NAME as "", and provide it using callback.
126        // I don't think this is designed.
127        login(new MyCallback(OneKDC.USER, null),
128                "tryFirstPass", "true", NAME, "", PWD, OneKDC.PASS);
129
130        // Test for the bug fix: doNotPrompt can be true if tryFirstPass=true
131        login(null, "doNotPrompt", "true", "storeKey", "true",
132                "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
133    }
134
135    static void login(CallbackHandler callback, Object... options)
136            throws Exception {
137        Krb5LoginModule krb5 = new Krb5LoginModule();
138        Subject subject = new Subject();
139        Map<String, String> map = new HashMap<>();
140        Map<String, Object> shared = new HashMap<>();
141
142        int count = options.length / 2;
143        for (int i = 0; i < count; i++) {
144            String key = (String) options[2 * i];
145            Object value = options[2 * i + 1];
146            if (key.startsWith("javax")) {
147                shared.put(key, value);
148            } else {
149                map.put(key, (String) value);
150            }
151        }
152        krb5.initialize(subject, callback, shared, map);
153        krb5.login();
154        krb5.commit();
155        if (!subject.getPrincipals().iterator().next()
156                .getName().startsWith(OneKDC.USER)) {
157            throw new Exception("The authenticated is not " + OneKDC.USER);
158        }
159    }
160
161    static class MyCallback implements CallbackHandler {
162
163        private String name;
164        private char[] password;
165
166        public MyCallback(String name, char[] password) {
167            this.name = name;
168            this.password = password;
169        }
170
171        public void handle(Callback[] callbacks) {
172            for (Callback callback : callbacks) {
173                System.err.println(callback);
174                if (callback instanceof NameCallback) {
175                    System.err.println("name is " + name);
176                    ((NameCallback) callback).setName(name);
177                }
178                if (callback instanceof PasswordCallback) {
179                    System.err.println("pass is " + new String(password));
180                    ((PasswordCallback) callback).setPassword(password);
181                }
182            }
183        }
184    }
185}
186