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