1/*
2 * Copyright (c) 2010, 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 6911951 7150092
27 * @summary NTLM should be a supported Java SASL mechanism
28 * @modules java.base/sun.security.util
29 *          java.security.sasl
30 */
31import java.io.IOException;
32import javax.security.sasl.*;
33import javax.security.auth.callback.*;
34import java.util.*;
35import sun.security.util.HexDumpEncoder;
36
37public class NTLMTest {
38
39    private static final String MECH = "NTLM";
40    private static final String REALM = "REALM";
41    private static final String PROTOCOL = "jmx";
42    private static final byte[] EMPTY = new byte[0];
43
44    private static final String USER1 = "dummy";
45    private static final char[] PASS1 = "bogus".toCharArray();
46    private static final String USER2 = "foo";
47    private static final char[] PASS2 = "bar".toCharArray();
48
49    private static final Map<String,char[]> maps =
50            new HashMap<String,char[]>();
51    static {
52        maps.put(USER1, PASS1);
53        maps.put(USER2, PASS2);
54    }
55
56    static char[] getPass(String d, String u) {
57        if (!d.equals(REALM)) return null;
58        return maps.get(u);
59    }
60
61    public static void main(String[] args) throws Exception {
62
63        checkAuthOnly();
64        checkClientNameOverride();
65        checkClientDomainOverride();
66        checkVersions();
67        checkClientHostname();
68    }
69
70    static void checkVersions() throws Exception {
71        // Server accepts all version
72        checkVersion(null, null);
73        checkVersion("LM/NTLM", null);
74        checkVersion("LM", null);
75        checkVersion("NTLM", null);
76        checkVersion("NTLM2", null);
77        checkVersion("LMv2/NTLMv2", null);
78        checkVersion("LMv2", null);
79        checkVersion("NTLMv2", null);
80
81        // Client's default version is LMv2
82        checkVersion(null, "LMv2");
83
84        // Also works if they specified identical versions
85        checkVersion("LM/NTLM", "LM");
86        checkVersion("LM", "LM");
87        checkVersion("NTLM", "LM");
88        checkVersion("NTLM2", "NTLM2");
89        checkVersion("LMv2/NTLMv2", "LMv2");
90        checkVersion("LMv2", "LMv2");
91        checkVersion("NTLMv2", "LMv2");
92
93        // But should not work if different
94        try {
95            checkVersion("LM/NTLM", "LMv2");
96            throw new Exception("Should not succeed");
97        } catch (SaslException se) {
98            // OK
99        }
100        try {
101            checkVersion("LMv2/NTLMv2", "LM");
102            throw new Exception("Should not succeed");
103        } catch (SaslException se) {
104            // OK
105        }
106
107    }
108
109    /**
110     * A test on version matching
111     * @param vc ntlm version specified for client
112     * @param vs ntlm version specified for server
113     * @throws Exception
114     */
115    private static void checkVersion(String vc, String vs) throws Exception {
116        Map<String,Object> pc = new HashMap<>();
117        pc.put("com.sun.security.sasl.ntlm.version", vc);
118        Map<String,Object> ps = new HashMap<>();
119        ps.put("com.sun.security.sasl.ntlm.version", vs);
120        SaslClient clnt = Sasl.createSaslClient(
121                new String[]{MECH}, USER1, PROTOCOL, REALM, pc,
122                new CallbackHandler() {
123                    public void handle(Callback[] callbacks)
124                            throws IOException, UnsupportedCallbackException {
125                        for (Callback cb: callbacks) {
126                            if (cb instanceof PasswordCallback) {
127                                ((PasswordCallback)cb).setPassword(PASS1);
128                            }
129                        }
130                    }
131                });
132
133        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, ps,
134                new CallbackHandler() {
135                    public void handle(Callback[] callbacks)
136                            throws IOException, UnsupportedCallbackException {
137                        String domain = null, name = null;
138                        PasswordCallback pcb = null;
139                        for (Callback cb: callbacks) {
140                            if (cb instanceof NameCallback) {
141                                name = ((NameCallback)cb).getDefaultName();
142                            } else if (cb instanceof RealmCallback) {
143                                domain = ((RealmCallback)cb).getDefaultText();
144                            } else if (cb instanceof PasswordCallback) {
145                                pcb = (PasswordCallback)cb;
146                            }
147                        }
148                        if (pcb != null) {
149                            pcb.setPassword(getPass(domain, name));
150                        }
151                    }
152                });
153
154        handshake(clnt, srv);
155    }
156
157    private static void checkClientHostname() throws Exception {
158        Map<String,Object> pc = new HashMap<>();
159        pc.put("com.sun.security.sasl.ntlm.hostname", "this.is.com");
160        SaslClient clnt = Sasl.createSaslClient(
161                new String[]{MECH}, USER1, PROTOCOL, REALM, pc,
162                new CallbackHandler() {
163                    public void handle(Callback[] callbacks)
164                            throws IOException, UnsupportedCallbackException {
165                        for (Callback cb: callbacks) {
166                            if (cb instanceof PasswordCallback) {
167                                ((PasswordCallback)cb).setPassword(PASS1);
168                            }
169                        }
170                    }
171                });
172
173        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
174                new CallbackHandler() {
175                    public void handle(Callback[] callbacks)
176                            throws IOException, UnsupportedCallbackException {
177                        String domain = null, name = null;
178                        PasswordCallback pcb = null;
179                        for (Callback cb: callbacks) {
180                            if (cb instanceof NameCallback) {
181                                name = ((NameCallback)cb).getDefaultName();
182                            } else if (cb instanceof RealmCallback) {
183                                domain = ((RealmCallback)cb).getDefaultText();
184                            } else if (cb instanceof PasswordCallback) {
185                                pcb = (PasswordCallback)cb;
186                            }
187                        }
188                        if (pcb != null) {
189                            pcb.setPassword(getPass(domain, name));
190                        }
191                    }
192                });
193
194        handshake(clnt, srv);
195        if (!"this.is.com".equals(
196                srv.getNegotiatedProperty("com.sun.security.sasl.ntlm.hostname"))) {
197            throw new Exception("Hostname not trasmitted to server");
198        }
199    }
200
201    /**
202     * Client realm override, but finally overridden by server response
203     */
204    private static void checkClientDomainOverride() throws Exception {
205        SaslClient clnt = Sasl.createSaslClient(
206                new String[]{MECH}, USER1, PROTOCOL, "ANOTHERREALM", null,
207                new CallbackHandler() {
208                    public void handle(Callback[] callbacks)
209                            throws IOException, UnsupportedCallbackException {
210                        for (Callback cb: callbacks) {
211                            if (cb instanceof RealmCallback) {
212                                ((RealmCallback)cb).setText(REALM);
213                            } else if (cb instanceof PasswordCallback) {
214                                ((PasswordCallback)cb).setPassword(PASS1);
215                            }
216                        }
217                    }
218                });
219
220        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
221                new CallbackHandler() {
222                    public void handle(Callback[] callbacks)
223                            throws IOException, UnsupportedCallbackException {
224                        String domain = null, name = null;
225                        PasswordCallback pcb = null;
226                        for (Callback cb: callbacks) {
227                            if (cb instanceof NameCallback) {
228                                name = ((NameCallback)cb).getDefaultName();
229                            } else if (cb instanceof RealmCallback) {
230                                domain = ((RealmCallback)cb).getDefaultText();
231                            } else if (cb instanceof PasswordCallback) {
232                                pcb = (PasswordCallback)cb;
233                            }
234                        }
235                        if (pcb != null) {
236                            pcb.setPassword(getPass(domain, name));
237                        }
238                    }
239                });
240
241        handshake(clnt, srv);
242    }
243
244    /**
245     * Client side user name provided in callback.
246     * @throws Exception
247     */
248    private static void checkClientNameOverride() throws Exception {
249        SaslClient clnt = Sasl.createSaslClient(
250                new String[]{MECH}, "someone", PROTOCOL, REALM, null,
251                new CallbackHandler() {
252                    public void handle(Callback[] callbacks)
253                            throws IOException, UnsupportedCallbackException {
254                        for (Callback cb: callbacks) {
255                            if (cb instanceof NameCallback) {
256                                NameCallback ncb = (NameCallback) cb;
257                                ncb.setName(USER1);
258                            } else if (cb instanceof PasswordCallback) {
259                                ((PasswordCallback)cb).setPassword(PASS1);
260                            }
261                        }
262                    }
263                });
264
265        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, "FAKE", null,
266                new CallbackHandler() {
267                    public void handle(Callback[] callbacks)
268                            throws IOException, UnsupportedCallbackException {
269                        String domain = null, name = null;
270                        PasswordCallback pcb = null;
271                        for (Callback cb: callbacks) {
272                            if (cb instanceof NameCallback) {
273                                name = ((NameCallback)cb).getDefaultName();
274                            } else if (cb instanceof RealmCallback) {
275                                domain = ((RealmCallback)cb).getDefaultText();
276                            } else if (cb instanceof PasswordCallback) {
277                                pcb = (PasswordCallback)cb;
278                            }
279                        }
280                        if (pcb != null) {
281                            pcb.setPassword(getPass(domain, name));
282                        }
283                    }
284                });
285
286        handshake(clnt, srv);
287    }
288
289    private static void checkAuthOnly() throws Exception {
290        Map<String,Object> props = new HashMap<>();
291        props.put(Sasl.QOP, "auth-conf");
292        try {
293            Sasl.createSaslClient(
294                    new String[]{MECH}, USER2, PROTOCOL, REALM, props, null);
295            throw new Exception("NTLM should not support auth-conf");
296        } catch (SaslException se) {
297            // Normal
298        }
299    }
300
301    private static void handshake(SaslClient clnt, SaslServer srv)
302            throws Exception {
303        if (clnt == null) {
304            throw new IllegalStateException(
305                    "Unable to find client impl for " + MECH);
306        }
307        if (srv == null) {
308            throw new IllegalStateException(
309                    "Unable to find server impl for " + MECH);
310        }
311
312        byte[] response = (clnt.hasInitialResponse()
313                ? clnt.evaluateChallenge(EMPTY) : EMPTY);
314        System.out.println("Initial:");
315        new HexDumpEncoder().encodeBuffer(response, System.out);
316        byte[] challenge;
317
318        while (!clnt.isComplete() || !srv.isComplete()) {
319            challenge = srv.evaluateResponse(response);
320            response = null;
321            if (challenge != null) {
322                System.out.println("Challenge:");
323                new HexDumpEncoder().encodeBuffer(challenge, System.out);
324                response = clnt.evaluateChallenge(challenge);
325            }
326            if (response != null) {
327                System.out.println("Response:");
328                new HexDumpEncoder().encodeBuffer(response, System.out);
329            }
330        }
331
332        if (clnt.isComplete() && srv.isComplete()) {
333            System.out.println("SUCCESS");
334            if (!srv.getAuthorizationID().equals(USER1)) {
335                throw new Exception("Not correct user");
336            }
337        } else {
338            throw new IllegalStateException(
339                    "FAILURE: mismatched state:"
340                    + " client complete? " + clnt.isComplete()
341                    + " server complete? " + srv.isComplete());
342        }
343
344        if (!clnt.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
345                !srv.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
346                !clnt.getNegotiatedProperty(
347                    "com.sun.security.sasl.ntlm.domain").equals(REALM)) {
348            throw new Exception("Negotiated property error");
349        }
350        clnt.dispose();
351        srv.dispose();
352    }
353}
354