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