Context.java revision 9330:8b1f1c2a400f
1/*
2 * Copyright (c) 2008, 2013, 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
24import com.sun.security.auth.module.Krb5LoginModule;
25import java.security.Key;
26import java.security.PrivilegedActionException;
27import java.security.PrivilegedExceptionAction;
28import java.util.Arrays;
29import java.util.HashMap;
30import java.util.Map;
31import javax.security.auth.Subject;
32import javax.security.auth.kerberos.KerberosKey;
33import javax.security.auth.kerberos.KerberosTicket;
34import javax.security.auth.login.LoginContext;
35import org.ietf.jgss.GSSContext;
36import org.ietf.jgss.GSSCredential;
37import org.ietf.jgss.GSSException;
38import org.ietf.jgss.GSSManager;
39import org.ietf.jgss.GSSName;
40import org.ietf.jgss.MessageProp;
41import org.ietf.jgss.Oid;
42import com.sun.security.jgss.ExtendedGSSContext;
43import com.sun.security.jgss.InquireType;
44import com.sun.security.jgss.AuthorizationDataEntry;
45import com.sun.security.jgss.ExtendedGSSCredential;
46import java.io.ByteArrayInputStream;
47import java.io.ByteArrayOutputStream;
48import java.security.Principal;
49
50/**
51 * Context of a JGSS subject, encapsulating Subject and GSSContext.
52 *
53 * Three "constructors", which acquire the (private) credentials and fill
54 * it into the Subject:
55 *
56 * 1. static fromJAAS(): Creates a Context using a JAAS login config entry
57 * 2. static fromUserPass(): Creates a Context using a username and a password
58 * 3. delegated(): A new context which uses the delegated credentials from a
59 *    previously established acceptor Context
60 *
61 * Two context initiators, which create the GSSContext object inside:
62 *
63 * 1. startAsClient()
64 * 2. startAsServer()
65 *
66 * Privileged action:
67 *    doAs(): Performs an action in the name of the Subject
68 *
69 * Handshake process:
70 *    static handShake(initiator, acceptor)
71 *
72 * A four-phase typical data communication which includes all four GSS
73 * actions (wrap, unwrap, getMic and veryfyMiC):
74 *    static transmit(message, from, to)
75 */
76public class Context {
77
78    private Subject s;
79    private ExtendedGSSContext x;
80    private String name;
81    private GSSCredential cred;     // see static method delegated().
82
83    static boolean usingStream = false;
84
85    private Context() {}
86
87    /**
88     * Using the delegated credentials from a previous acceptor
89     * @param c
90     */
91    public Context delegated() throws Exception {
92        Context out = new Context();
93        out.s = s;
94        try {
95            out.cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
96                @Override
97                public GSSCredential run() throws Exception {
98                    GSSCredential cred = x.getDelegCred();
99                    if (cred == null && x.getCredDelegState() ||
100                            cred != null && !x.getCredDelegState()) {
101                        throw new Exception("getCredDelegState not match");
102                    }
103                    return cred;
104                }
105            });
106        } catch (PrivilegedActionException pae) {
107            throw pae.getException();
108        }
109        out.name = name + " as " + out.cred.getName().toString();
110        return out;
111    }
112
113    /**
114     * No JAAS login at all, can be used to test JGSS without JAAS
115     */
116    public static Context fromThinAir() throws Exception {
117        Context out = new Context();
118        out.s = new Subject();
119        return out;
120    }
121
122    /**
123     * Logins with a JAAS login config entry name
124     */
125    public static Context fromJAAS(final String name) throws Exception {
126        Context out = new Context();
127        out.name = name;
128        LoginContext lc = new LoginContext(name);
129        lc.login();
130        out.s = lc.getSubject();
131        return out;
132    }
133
134    /**
135     * Logins with username/password as a new Subject
136     */
137    public static Context fromUserPass(
138            String user, char[] pass, boolean storeKey) throws Exception {
139        return fromUserPass(new Subject(), user, pass, storeKey);
140    }
141
142    /**
143     * Logins with username/password as an existing Subject. The
144     * same subject can be used multiple times to simulate multiple logins.
145     * @param s existing subject
146     */
147    public static Context fromUserPass(Subject s,
148            String user, char[] pass, boolean storeKey) throws Exception {
149        Context out = new Context();
150        out.name = user;
151        out.s = s;
152        Krb5LoginModule krb5 = new Krb5LoginModule();
153        Map<String, String> map = new HashMap<>();
154        Map<String, Object> shared = new HashMap<>();
155
156        if (pass != null) {
157            map.put("useFirstPass", "true");
158            shared.put("javax.security.auth.login.name", user);
159            shared.put("javax.security.auth.login.password", pass);
160        } else {
161            map.put("doNotPrompt", "true");
162            map.put("useTicketCache", "true");
163            if (user != null) {
164                map.put("principal", user);
165            }
166        }
167        if (storeKey) {
168            map.put("storeKey", "true");
169        }
170
171        krb5.initialize(out.s, null, shared, map);
172        krb5.login();
173        krb5.commit();
174        return out;
175    }
176
177    /**
178     * Logins with username/keytab as an existing Subject. The
179     * same subject can be used multiple times to simulate multiple logins.
180     * @param s existing subject
181     */
182    public static Context fromUserKtab(
183            String user, String ktab, boolean storeKey) throws Exception {
184        return fromUserKtab(new Subject(), user, ktab, storeKey);
185    }
186
187    /**
188     * Logins with username/keytab as a new subject,
189     */
190    public static Context fromUserKtab(Subject s,
191            String user, String ktab, boolean storeKey) throws Exception {
192        Context out = new Context();
193        out.name = user;
194        out.s = s;
195        Krb5LoginModule krb5 = new Krb5LoginModule();
196        Map<String, String> map = new HashMap<>();
197
198        map.put("isInitiator", "false");
199        map.put("doNotPrompt", "true");
200        map.put("useTicketCache", "false");
201        map.put("useKeyTab", "true");
202        map.put("keyTab", ktab);
203        map.put("principal", user);
204        if (storeKey) {
205            map.put("storeKey", "true");
206        }
207
208        krb5.initialize(out.s, null, null, map);
209        krb5.login();
210        krb5.commit();
211        return out;
212    }
213
214    /**
215     * Starts as a client
216     * @param target communication peer
217     * @param mech GSS mech
218     * @throws java.lang.Exception
219     */
220    public void startAsClient(final String target, final Oid mech) throws Exception {
221        doAs(new Action() {
222            @Override
223            public byte[] run(Context me, byte[] dummy) throws Exception {
224                GSSManager m = GSSManager.getInstance();
225                me.x = (ExtendedGSSContext)m.createContext(
226                          target.indexOf('@') < 0 ?
227                            m.createName(target, null) :
228                            m.createName(target, GSSName.NT_HOSTBASED_SERVICE),
229                        mech,
230                        cred,
231                        GSSContext.DEFAULT_LIFETIME);
232                return null;
233            }
234        }, null);
235    }
236
237    /**
238     * Starts as a server
239     * @param mech GSS mech
240     * @throws java.lang.Exception
241     */
242    public void startAsServer(final Oid mech) throws Exception {
243        startAsServer(null, mech, false);
244    }
245
246    public void startAsServer(final String name, final Oid mech) throws Exception {
247        startAsServer(name, mech, false);
248    }
249    /**
250     * Starts as a server with the specified service name
251     * @param name the service name
252     * @param mech GSS mech
253     * @throws java.lang.Exception
254     */
255    public void startAsServer(final String name, final Oid mech, final boolean asInitiator) throws Exception {
256        doAs(new Action() {
257            @Override
258            public byte[] run(Context me, byte[] dummy) throws Exception {
259                GSSManager m = GSSManager.getInstance();
260                me.cred = m.createCredential(
261                        name == null ? null :
262                          (name.indexOf('@') < 0 ?
263                            m.createName(name, null) :
264                            m.createName(name, GSSName.NT_HOSTBASED_SERVICE)),
265                        GSSCredential.INDEFINITE_LIFETIME,
266                        mech,
267                        asInitiator?
268                                GSSCredential.INITIATE_AND_ACCEPT:
269                                GSSCredential.ACCEPT_ONLY);
270                me.x = (ExtendedGSSContext)m.createContext(me.cred);
271                return null;
272            }
273        }, null);
274    }
275
276    /**
277     * Accesses the internal GSSContext object. Currently it's used for --
278     *
279     * 1. calling requestXXX() before handshake
280     * 2. accessing source name
281     *
282     * Note: If the application needs to do any privileged call on this
283     * object, please use doAs(). Otherwise, it can be done directly. The
284     * methods listed above are all non-privileged calls.
285     *
286     * @return the GSSContext object
287     */
288    public ExtendedGSSContext x() {
289        return x;
290    }
291
292    /**
293     * Accesses the internal subject.
294     * @return the subject
295     */
296    public Subject s() {
297        return s;
298    }
299
300    /**
301     * Returns the cred inside, if there is one
302     */
303    public GSSCredential cred() {
304        return cred;
305    }
306
307    /**
308     * Disposes the GSSContext within
309     * @throws org.ietf.jgss.GSSException
310     */
311    public void dispose() throws GSSException {
312        x.dispose();
313    }
314
315    /**
316     * Does something using the Subject inside
317     * @param action the action
318     * @param in the input byte
319     * @return the output byte
320     * @throws java.lang.Exception
321     */
322    public byte[] doAs(final Action action, final byte[] in) throws Exception {
323        try {
324            return Subject.doAs(s, new PrivilegedExceptionAction<byte[]>() {
325
326                @Override
327                public byte[] run() throws Exception {
328                    return action.run(Context.this, in);
329                }
330            });
331        } catch (PrivilegedActionException pae) {
332            throw pae.getException();
333        }
334    }
335
336    /**
337     * Prints status of GSSContext and Subject
338     * @throws java.lang.Exception
339     */
340    public void status() throws Exception {
341        System.out.println("STATUS OF " + name.toUpperCase());
342        try {
343            StringBuffer sb = new StringBuffer();
344            if (x.getAnonymityState()) {
345                sb.append("anon, ");
346            }
347            if (x.getConfState()) {
348                sb.append("conf, ");
349            }
350            if (x.getCredDelegState()) {
351                sb.append("deleg, ");
352            }
353            if (x.getIntegState()) {
354                sb.append("integ, ");
355            }
356            if (x.getMutualAuthState()) {
357                sb.append("mutual, ");
358            }
359            if (x.getReplayDetState()) {
360                sb.append("rep det, ");
361            }
362            if (x.getSequenceDetState()) {
363                sb.append("seq det, ");
364            }
365            if (x instanceof ExtendedGSSContext) {
366                if (((ExtendedGSSContext)x).getDelegPolicyState()) {
367                    sb.append("deleg policy, ");
368                }
369            }
370            System.out.println("Context status of " + name + ": " + sb.toString());
371            System.out.println(x.getSrcName() + " -> " + x.getTargName());
372        } catch (Exception e) {
373            ;// Don't care
374        }
375        if (s != null) {
376            System.out.println("====== START SUBJECT CONTENT =====");
377            for (Principal p: s.getPrincipals()) {
378                System.out.println("    Principal: " + p);
379            }
380            for (Object o : s.getPublicCredentials()) {
381                System.out.println("    " + o.getClass());
382                System.out.println("        " + o);
383            }
384            System.out.println("====== Private Credentials Set ======");
385            for (Object o : s.getPrivateCredentials()) {
386                System.out.println("    " + o.getClass());
387                if (o instanceof KerberosTicket) {
388                    KerberosTicket kt = (KerberosTicket) o;
389                    System.out.println("        " + kt.getServer() + " for " + kt.getClient());
390                } else if (o instanceof KerberosKey) {
391                    KerberosKey kk = (KerberosKey) o;
392                    System.out.print("        " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " ");
393                    for (byte b : kk.getEncoded()) {
394                        System.out.printf("%02X", b & 0xff);
395                    }
396                    System.out.println();
397                } else if (o instanceof Map) {
398                    Map map = (Map) o;
399                    for (Object k : map.keySet()) {
400                        System.out.println("        " + k + ": " + map.get(k));
401                    }
402                } else {
403                    System.out.println("        " + o);
404                }
405            }
406            System.out.println("====== END SUBJECT CONTENT =====");
407        }
408        if (x != null && x instanceof ExtendedGSSContext) {
409            if (x.isEstablished()) {
410                ExtendedGSSContext ex = (ExtendedGSSContext)x;
411                Key k = (Key)ex.inquireSecContext(
412                        InquireType.KRB5_GET_SESSION_KEY);
413                if (k == null) {
414                    throw new Exception("Session key cannot be null");
415                }
416                System.out.println("Session key is: " + k);
417                boolean[] flags = (boolean[])ex.inquireSecContext(
418                        InquireType.KRB5_GET_TKT_FLAGS);
419                if (flags == null) {
420                    throw new Exception("Ticket flags cannot be null");
421                }
422                System.out.println("Ticket flags is: " + Arrays.toString(flags));
423                String authTime = (String)ex.inquireSecContext(
424                        InquireType.KRB5_GET_AUTHTIME);
425                if (authTime == null) {
426                    throw new Exception("Auth time cannot be null");
427                }
428                System.out.println("AuthTime is: " + authTime);
429                if (!x.isInitiator()) {
430                    AuthorizationDataEntry[] ad = (AuthorizationDataEntry[])ex.inquireSecContext(
431                            InquireType.KRB5_GET_AUTHZ_DATA);
432                    System.out.println("AuthzData is: " + Arrays.toString(ad));
433                }
434            }
435        }
436    }
437
438    public byte[] wrap(byte[] t, final boolean privacy)
439            throws Exception {
440        return doAs(new Action() {
441            @Override
442            public byte[] run(Context me, byte[] input) throws Exception {
443                System.out.printf("wrap %s privacy from %s: ", privacy?"with":"without", me.name);
444                MessageProp p1 = new MessageProp(0, privacy);
445                byte[] out;
446                if (usingStream) {
447                    ByteArrayOutputStream os = new ByteArrayOutputStream();
448                    me.x.wrap(new ByteArrayInputStream(input), os, p1);
449                    out = os.toByteArray();
450                } else {
451                    out = me.x.wrap(input, 0, input.length, p1);
452                }
453                System.out.println(printProp(p1));
454                return out;
455            }
456        }, t);
457    }
458
459    public byte[] unwrap(byte[] t, final boolean privacy)
460            throws Exception {
461        return doAs(new Action() {
462            @Override
463            public byte[] run(Context me, byte[] input) throws Exception {
464                System.out.printf("unwrap %s privacy from %s: ", privacy?"with":"without", me.name);
465                MessageProp p1 = new MessageProp(0, privacy);
466                byte[] bytes;
467                if (usingStream) {
468                    ByteArrayOutputStream os = new ByteArrayOutputStream();
469                    me.x.unwrap(new ByteArrayInputStream(input), os, p1);
470                    bytes = os.toByteArray();
471                } else {
472                    bytes = me.x.unwrap(input, 0, input.length, p1);
473                }
474                System.out.println(printProp(p1));
475                return bytes;
476            }
477        }, t);
478    }
479
480    public byte[] getMic(byte[] t) throws Exception {
481        return doAs(new Action() {
482            @Override
483            public byte[] run(Context me, byte[] input) throws Exception {
484                MessageProp p1 = new MessageProp(0, true);
485                byte[] bytes;
486                p1 = new MessageProp(0, true);
487                System.out.printf("getMic from %s: ", me.name);
488                if (usingStream) {
489                    ByteArrayOutputStream os = new ByteArrayOutputStream();
490                    me.x.getMIC(new ByteArrayInputStream(input), os, p1);
491                    bytes = os.toByteArray();
492                } else {
493                    bytes = me.x.getMIC(input, 0, input.length, p1);
494                }
495                System.out.println(printProp(p1));
496                return bytes;
497            }
498        }, t);
499    }
500
501    public void verifyMic(byte[] t, final byte[] msg) throws Exception {
502        doAs(new Action() {
503            @Override
504            public byte[] run(Context me, byte[] input) throws Exception {
505                MessageProp p1 = new MessageProp(0, true);
506                System.out.printf("verifyMic from %s: ", me.name);
507                if (usingStream) {
508                    me.x.verifyMIC(new ByteArrayInputStream(input),
509                            new ByteArrayInputStream(msg), p1);
510                } else {
511                    me.x.verifyMIC(input, 0, input.length,
512                            msg, 0, msg.length,
513                            p1);
514                }
515                System.out.println(printProp(p1));
516                return null;
517            }
518        }, t);
519    }
520
521    /**
522     * Transmits a message from one Context to another. The sender wraps the
523     * message and sends it to the receiver. The receiver unwraps it, creates
524     * a MIC of the clear text and sends it back to the sender. The sender
525     * verifies the MIC against the message sent earlier.
526     * @param message the message
527     * @param s1 the sender
528     * @param s2 the receiver
529     * @throws java.lang.Exception If anything goes wrong
530     */
531    static public void transmit(final String message, final Context s1,
532            final Context s2) throws Exception {
533        final byte[] messageBytes = message.getBytes();
534        System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n",
535                s1.name, s2.name);
536        byte[] wrapped = s1.wrap(messageBytes, true);
537        byte[] unwrapped = s2.unwrap(wrapped, true);
538        if (!Arrays.equals(messageBytes, unwrapped)) {
539            throw new Exception("wrap/unwrap mismatch");
540        }
541        byte[] mic = s2.getMic(unwrapped);
542        s1.verifyMic(mic, messageBytes);
543    }
544
545    /**
546     * Returns a string description of a MessageProp object
547     * @param prop the object
548     * @return the description
549     */
550    static public String printProp(MessageProp prop) {
551        StringBuffer sb = new StringBuffer();
552        sb.append("MessagePop: ");
553        sb.append("QOP="+ prop.getQOP() + ", ");
554        sb.append(prop.getPrivacy()?"privacy, ":"");
555        sb.append(prop.isDuplicateToken()?"dup, ":"");
556        sb.append(prop.isGapToken()?"gap, ":"");
557        sb.append(prop.isOldToken()?"old, ":"");
558        sb.append(prop.isUnseqToken()?"unseq, ":"");
559        if (prop.getMinorStatus() != 0) {
560            sb.append(prop.getMinorString()+ "(" + prop.getMinorStatus()+")");
561        }
562        return sb.toString();
563    }
564
565    public Context impersonate(final String someone) throws Exception {
566        try {
567            GSSCredential creds = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
568                @Override
569                public GSSCredential run() throws Exception {
570                    GSSManager m = GSSManager.getInstance();
571                    GSSName other = m.createName(someone, GSSName.NT_USER_NAME);
572                    if (Context.this.cred == null) {
573                        Context.this.cred = m.createCredential(GSSCredential.INITIATE_ONLY);
574                    }
575                    return ((ExtendedGSSCredential)Context.this.cred).impersonate(other);
576                }
577            });
578            Context out = new Context();
579            out.s = s;
580            out.cred = creds;
581            out.name = name + " as " + out.cred.getName().toString();
582            return out;
583        } catch (PrivilegedActionException pae) {
584            throw pae.getException();
585        }
586    }
587
588    public byte[] take(final byte[] in) throws Exception {
589        return doAs(new Action() {
590            @Override
591            public byte[] run(Context me, byte[] input) throws Exception {
592                if (me.x.isEstablished()) {
593                    System.out.println(name + " side established");
594                    if (input != null) {
595                        throw new Exception("Context established but " +
596                                "still receive token at " + name);
597                    }
598                    return null;
599                } else {
600                    if (me.x.isInitiator()) {
601                        System.out.println(name + " call initSecContext");
602                        return me.x.initSecContext(input, 0, input.length);
603                    } else {
604                        System.out.println(name + " call acceptSecContext");
605                        return me.x.acceptSecContext(input, 0, input.length);
606                    }
607                }
608            }
609        }, in);
610    }
611
612    /**
613     * Handshake (security context establishment process) between two Contexts
614     * @param c the initiator
615     * @param s the acceptor
616     * @throws java.lang.Exception
617     */
618    static public void handshake(final Context c, final Context s) throws Exception {
619        byte[] t = new byte[0];
620        while (true) {
621            if (t != null || !c.x.isEstablished()) t = c.take(t);
622            if (t != null || !s.x.isEstablished()) t = s.take(t);
623            if (c.x.isEstablished() && s.x.isEstablished()) break;
624        }
625    }
626}
627