1/*
2 * Copyright (c) 2000, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.jgss;
27
28import org.ietf.jgss.*;
29import sun.security.jgss.spi.*;
30
31import java.util.*;
32import sun.security.jgss.spnego.SpNegoCredElement;
33
34public class GSSCredentialImpl implements GSSCredential {
35
36    private GSSManagerImpl gssManager = null;
37    private boolean destroyed = false;
38
39    /*
40     * We store all elements in a hashtable, using <oid, usage> as the
41     * key. This makes it easy to locate the specific kind of credential we
42     * need. The implementation needs to be optimized for the case where
43     * there is just one element (tempCred).
44     */
45    private Hashtable<SearchKey, GSSCredentialSpi> hashtable = null;
46
47    // XXX Optimization for single mech usage
48    private GSSCredentialSpi tempCred = null;
49
50    public GSSCredentialImpl() {
51        // Useless
52    }
53
54    // Used by new ExtendedGSSCredential.ExtendedGSSCredentialImpl(cred)
55    protected GSSCredentialImpl(GSSCredentialImpl src) {
56        this.gssManager = src.gssManager;
57        this.destroyed = src.destroyed;
58        this.hashtable = src.hashtable;
59        this.tempCred = src.tempCred;
60    }
61
62    GSSCredentialImpl(GSSManagerImpl gssManager, int usage)
63        throws GSSException {
64        this(gssManager, null, GSSCredential.DEFAULT_LIFETIME,
65             (Oid[]) null, usage);
66    }
67
68    GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name,
69                             int lifetime, Oid mech, int usage)
70        throws GSSException {
71        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
72
73        init(gssManager);
74        add(name, lifetime, lifetime, mech, usage);
75    }
76
77    GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name,
78                      int lifetime, Oid[] mechs, int usage)
79        throws GSSException {
80        init(gssManager);
81        boolean defaultList = false;
82        if (mechs == null) {
83            mechs = gssManager.getMechs();
84            defaultList = true;
85        }
86
87        for (int i = 0; i < mechs.length; i++) {
88            try {
89                add(name, lifetime, lifetime, mechs[i], usage);
90            } catch (GSSException e) {
91                if (defaultList) {
92                    // Try the next mechanism
93                    GSSUtil.debug("Ignore " + e + " while acquring cred for "
94                        + mechs[i]);
95                    //e.printStackTrace();
96                } else throw e; // else try the next mechanism
97            }
98        }
99        if ((hashtable.size() == 0) || (usage != getUsage()))
100            throw new GSSException(GSSException.NO_CRED);
101    }
102
103    // Wrap a mech cred into a GSS cred
104    public GSSCredentialImpl(GSSManagerImpl gssManager,
105                      GSSCredentialSpi mechElement) throws GSSException {
106
107        init(gssManager);
108        int usage = GSSCredential.ACCEPT_ONLY;
109        if (mechElement.isInitiatorCredential()) {
110            if (mechElement.isAcceptorCredential()) {
111                usage = GSSCredential.INITIATE_AND_ACCEPT;
112            } else {
113                usage = GSSCredential.INITIATE_ONLY;
114            }
115        }
116        SearchKey key = new SearchKey(mechElement.getMechanism(),
117                                        usage);
118        tempCred = mechElement;
119        hashtable.put(key, tempCred);
120        // More mechs that can use this cred, say, SPNEGO
121        if (!GSSUtil.isSpNegoMech(mechElement.getMechanism())) {
122            key = new SearchKey(GSSUtil.GSS_SPNEGO_MECH_OID, usage);
123            hashtable.put(key, new SpNegoCredElement(mechElement));
124        }
125    }
126
127    void init(GSSManagerImpl gssManager) {
128        this.gssManager = gssManager;
129        hashtable = new Hashtable<SearchKey, GSSCredentialSpi>(
130                                                gssManager.getMechs().length);
131    }
132
133    public void dispose() throws GSSException {
134        if (!destroyed) {
135            GSSCredentialSpi element;
136            Enumeration<GSSCredentialSpi> values = hashtable.elements();
137            while (values.hasMoreElements()) {
138                element = values.nextElement();
139                element.dispose();
140            }
141            destroyed = true;
142        }
143    }
144
145    public GSSCredential impersonate(GSSName name) throws GSSException {
146        if (destroyed) {
147            throw new IllegalStateException("This credential is " +
148                                        "no longer valid");
149        }
150        Oid mech = tempCred.getMechanism();
151        GSSNameSpi nameElement = (name == null ? null :
152                                  ((GSSNameImpl)name).getElement(mech));
153        GSSCredentialSpi cred = tempCred.impersonate(nameElement);
154        return (cred == null ?
155            null : GSSManagerImpl.wrap(new GSSCredentialImpl(gssManager, cred)));
156    }
157
158    public GSSName getName() throws GSSException {
159        if (destroyed) {
160            throw new IllegalStateException("This credential is " +
161                                        "no longer valid");
162        }
163        return GSSNameImpl.wrapElement(gssManager, tempCred.getName());
164    }
165
166    public GSSName getName(Oid mech) throws GSSException {
167
168        if (destroyed) {
169            throw new IllegalStateException("This credential is " +
170                                        "no longer valid");
171        }
172
173        SearchKey key = null;
174        GSSCredentialSpi element = null;
175
176        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
177
178        key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
179        element = hashtable.get(key);
180
181        if (element == null) {
182            key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
183            element = hashtable.get(key);
184        }
185
186        if (element == null) {
187            key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
188            element = hashtable.get(key);
189        }
190
191        if (element == null) {
192            throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
193        }
194
195        return GSSNameImpl.wrapElement(gssManager, element.getName());
196
197    }
198
199    /**
200     * Returns the remaining lifetime of this credential. The remaining
201     * lifetime is defined as the minimum lifetime, either for initiate or
202     * for accept, across all elements contained in it. Not terribly
203     * useful, but required by GSS-API.
204     */
205    public int getRemainingLifetime() throws GSSException {
206
207        if (destroyed) {
208            throw new IllegalStateException("This credential is " +
209                                        "no longer valid");
210        }
211
212        SearchKey tempKey;
213        GSSCredentialSpi tempCred;
214        int tempLife = 0, tempInitLife = 0, tempAcceptLife = 0;
215        int min = INDEFINITE_LIFETIME;
216
217        for (Enumeration<SearchKey> e = hashtable.keys();
218                                        e.hasMoreElements(); ) {
219            tempKey = e.nextElement();
220            tempCred = hashtable.get(tempKey);
221            if (tempKey.getUsage() == INITIATE_ONLY)
222                tempLife = tempCred.getInitLifetime();
223            else if (tempKey.getUsage() == ACCEPT_ONLY)
224                tempLife = tempCred.getAcceptLifetime();
225            else {
226                tempInitLife = tempCred.getInitLifetime();
227                tempAcceptLife = tempCred.getAcceptLifetime();
228                tempLife = (tempInitLife < tempAcceptLife ?
229                            tempInitLife:
230                            tempAcceptLife);
231            }
232            if (min > tempLife)
233                min = tempLife;
234        }
235
236        return min;
237    }
238
239    public int getRemainingInitLifetime(Oid mech) throws GSSException {
240
241        if (destroyed) {
242            throw new IllegalStateException("This credential is " +
243                                        "no longer valid");
244        }
245
246        GSSCredentialSpi element = null;
247        SearchKey key = null;
248        boolean found = false;
249        int max = 0;
250
251        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
252
253        key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
254        element = hashtable.get(key);
255
256        if (element != null) {
257            found = true;
258            if (max < element.getInitLifetime())
259                max = element.getInitLifetime();
260        }
261
262        key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
263        element = hashtable.get(key);
264
265        if (element != null) {
266            found = true;
267            if (max < element.getInitLifetime())
268                max = element.getInitLifetime();
269        }
270
271        if (!found) {
272            throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
273        }
274
275        return max;
276
277    }
278
279    public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
280
281        if (destroyed) {
282            throw new IllegalStateException("This credential is " +
283                                        "no longer valid");
284        }
285
286        GSSCredentialSpi element = null;
287        SearchKey key = null;
288        boolean found = false;
289        int max = 0;
290
291        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
292
293        key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
294        element = hashtable.get(key);
295
296        if (element != null) {
297            found = true;
298            if (max < element.getAcceptLifetime())
299                max = element.getAcceptLifetime();
300        }
301
302        key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
303        element = hashtable.get(key);
304
305        if (element != null) {
306            found = true;
307            if (max < element.getAcceptLifetime())
308                max = element.getAcceptLifetime();
309        }
310
311        if (!found) {
312            throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
313        }
314
315        return max;
316
317    }
318
319    /**
320     * Returns the usage mode for this credential. Returns
321     * INITIATE_AND_ACCEPT if any one element contained in it supports
322     * INITIATE_AND_ACCEPT or if two different elements exist where one
323     * support INITIATE_ONLY and the other supports ACCEPT_ONLY.
324     */
325    public int getUsage() throws GSSException {
326
327        if (destroyed) {
328            throw new IllegalStateException("This credential is " +
329                                        "no longer valid");
330        }
331
332        SearchKey tempKey;
333        boolean initiate = false;
334        boolean accept = false;
335
336        for (Enumeration<SearchKey> e = hashtable.keys();
337                                        e.hasMoreElements(); ) {
338            tempKey = e.nextElement();
339            if (tempKey.getUsage() == INITIATE_ONLY)
340                initiate = true;
341            else if (tempKey.getUsage() == ACCEPT_ONLY)
342                accept = true;
343            else
344                return INITIATE_AND_ACCEPT;
345        }
346        if (initiate) {
347            if (accept)
348                return INITIATE_AND_ACCEPT;
349            else
350                return INITIATE_ONLY;
351        } else
352            return ACCEPT_ONLY;
353    }
354
355    public int getUsage(Oid mech) throws GSSException {
356
357        if (destroyed) {
358            throw new IllegalStateException("This credential is " +
359                                        "no longer valid");
360        }
361
362        GSSCredentialSpi element = null;
363        SearchKey key = null;
364        boolean initiate = false;
365        boolean accept = false;
366
367        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
368
369        key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
370        element = hashtable.get(key);
371
372        if (element != null) {
373            initiate = true;
374        }
375
376        key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
377        element = hashtable.get(key);
378
379        if (element != null) {
380            accept = true;
381        }
382
383        key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
384        element = hashtable.get(key);
385
386        if (element != null) {
387            initiate = true;
388            accept = true;
389        }
390
391        if (initiate && accept)
392            return GSSCredential.INITIATE_AND_ACCEPT;
393        else if (initiate)
394            return GSSCredential.INITIATE_ONLY;
395        else if (accept)
396            return GSSCredential.ACCEPT_ONLY;
397        else {
398            throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
399        }
400    }
401
402    public Oid[] getMechs() throws GSSException {
403
404        if (destroyed) {
405            throw new IllegalStateException("This credential is " +
406                                        "no longer valid");
407        }
408        Vector<Oid> result = new Vector<Oid>(hashtable.size());
409
410        for (Enumeration<SearchKey> e = hashtable.keys();
411                                                e.hasMoreElements(); ) {
412            SearchKey tempKey = e.nextElement();
413            result.addElement(tempKey.getMech());
414        }
415        return result.toArray(new Oid[0]);
416    }
417
418    public void add(GSSName name, int initLifetime, int acceptLifetime,
419                    Oid mech, int usage) throws GSSException {
420
421        if (destroyed) {
422            throw new IllegalStateException("This credential is " +
423                                        "no longer valid");
424        }
425        if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
426
427        SearchKey key = new SearchKey(mech, usage);
428        if (hashtable.containsKey(key)) {
429            throw new GSSExceptionImpl(GSSException.DUPLICATE_ELEMENT,
430                                       "Duplicate element found: " +
431                                       getElementStr(mech, usage));
432        }
433
434        // XXX If not instance of GSSNameImpl then throw exception
435        // Application mixing GSS implementations
436        GSSNameSpi nameElement = (name == null ? null :
437                                  ((GSSNameImpl)name).getElement(mech));
438
439        tempCred = gssManager.getCredentialElement(nameElement,
440                                                   initLifetime,
441                                                   acceptLifetime,
442                                                   mech,
443                                                   usage);
444        /*
445         * Not all mechanisms support the concept of one credential element
446         * that can be used for both initiating and accepting a context. In
447         * the event that an application requests usage INITIATE_AND_ACCEPT
448         * for a credential from such a mechanism, the GSS framework will
449         * need to obtain two different credential elements from the
450         * mechanism, one that will have usage INITIATE_ONLY and another
451         * that will have usage ACCEPT_ONLY. The mechanism will help the
452         * GSS-API realize this by returning a credential element with
453         * usage INITIATE_ONLY or ACCEPT_ONLY prompting it to make another
454         * call to getCredentialElement, this time with the other usage
455         * mode.
456         */
457
458        if (tempCred != null) {
459            if (usage == GSSCredential.INITIATE_AND_ACCEPT &&
460                (!tempCred.isAcceptorCredential() ||
461                !tempCred.isInitiatorCredential())) {
462
463                int currentUsage;
464                int desiredUsage;
465
466                if (!tempCred.isInitiatorCredential()) {
467                    currentUsage = GSSCredential.ACCEPT_ONLY;
468                    desiredUsage = GSSCredential.INITIATE_ONLY;
469                } else {
470                    currentUsage = GSSCredential.INITIATE_ONLY;
471                    desiredUsage = GSSCredential.ACCEPT_ONLY;
472                }
473
474                key = new SearchKey(mech, currentUsage);
475                hashtable.put(key, tempCred);
476
477                tempCred = gssManager.getCredentialElement(nameElement,
478                                                        initLifetime,
479                                                        acceptLifetime,
480                                                        mech,
481                                                        desiredUsage);
482
483                key = new SearchKey(mech, desiredUsage);
484                hashtable.put(key, tempCred);
485            } else {
486                hashtable.put(key, tempCred);
487            }
488        }
489    }
490
491    public boolean equals(Object another) {
492
493        if (destroyed) {
494            throw new IllegalStateException("This credential is " +
495                                        "no longer valid");
496        }
497
498        if (this == another) {
499            return true;
500        }
501
502        if (!(another instanceof GSSCredentialImpl)) {
503            return false;
504        }
505
506        // NOTE: The specification does not define the criteria to compare
507        // credentials.
508        /*
509         * XXX
510         * The RFC says: "Tests if this GSSCredential refers to the same
511         * entity as the supplied object.  The two credentials must be
512         * acquired over the same mechanisms and must refer to the same
513         * principal.  Returns "true" if the two GSSCredentials refer to
514         * the same entity; "false" otherwise."
515         *
516         * Well, when do two credentials refer to the same principal? Do
517         * they need to have one GSSName in common for the different
518         * GSSName's that the credential elements return? Or do all
519         * GSSName's have to be in common when the names are exported with
520         * their respective mechanisms for the credential elements?
521         */
522        return false;
523
524    }
525
526    /**
527     * Returns a hashcode value for this GSSCredential.
528     *
529     * @return a hashCode value
530     */
531    public int hashCode() {
532
533        if (destroyed) {
534            throw new IllegalStateException("This credential is " +
535                                        "no longer valid");
536        }
537
538        // NOTE: The specification does not define the criteria to compare
539        // credentials.
540        /*
541         * XXX
542         * Decide on a criteria for equals first then do this.
543         */
544        return 1;
545    }
546
547    /**
548     * Returns the specified mechanism's credential-element.
549     *
550     * @param mechOid the oid for mechanism to retrieve
551     * @param initiate boolean indicating if the function is
552     *    to throw exception or return null when element is not
553     *    found.
554     * @return mechanism credential object
555     * @exception GSSException of invalid mechanism
556     */
557    public GSSCredentialSpi getElement(Oid mechOid, boolean initiate)
558        throws GSSException {
559
560        if (destroyed) {
561            throw new IllegalStateException("This credential is " +
562                                        "no longer valid");
563        }
564
565        SearchKey key;
566        GSSCredentialSpi element;
567
568        if (mechOid == null) {
569            /*
570             * First see if the default mechanism satisfies the
571             * desired usage.
572             */
573            mechOid = ProviderList.DEFAULT_MECH_OID;
574            key = new SearchKey(mechOid,
575                                 initiate? INITIATE_ONLY : ACCEPT_ONLY);
576            element = hashtable.get(key);
577            if (element == null) {
578                key = new SearchKey(mechOid, INITIATE_AND_ACCEPT);
579                element = hashtable.get(key);
580                if (element == null) {
581                    /*
582                     * Now just return any element that satisfies the
583                     * desired usage.
584                     */
585                    Object[] elements = hashtable.entrySet().toArray();
586                    for (int i = 0; i < elements.length; i++) {
587                        element = (GSSCredentialSpi)
588                            ((Map.Entry)elements[i]).getValue();
589                        if (element.isInitiatorCredential() == initiate)
590                            break;
591                    } // for loop
592                }
593            }
594        } else {
595
596            if (initiate)
597                key = new SearchKey(mechOid, INITIATE_ONLY);
598            else
599                key = new SearchKey(mechOid, ACCEPT_ONLY);
600
601            element = hashtable.get(key);
602
603            if (element == null) {
604                key = new SearchKey(mechOid, INITIATE_AND_ACCEPT);
605                element = hashtable.get(key);
606            }
607        }
608
609        if (element == null)
610            throw new GSSExceptionImpl(GSSException.NO_CRED,
611                                       "No credential found for: " +
612                                       getElementStr(mechOid,
613                                       initiate? INITIATE_ONLY : ACCEPT_ONLY));
614        return element;
615    }
616
617    Set<GSSCredentialSpi> getElements() {
618        HashSet<GSSCredentialSpi> retVal =
619                        new HashSet<GSSCredentialSpi>(hashtable.size());
620        Enumeration<GSSCredentialSpi> values = hashtable.elements();
621        while (values.hasMoreElements()) {
622            GSSCredentialSpi o = values.nextElement();
623            retVal.add(o);
624        }
625        return retVal;
626    }
627
628    private static String getElementStr(Oid mechOid, int usage) {
629        String displayString = mechOid.toString();
630        if (usage == GSSCredential.INITIATE_ONLY) {
631            displayString =
632                displayString.concat(" usage: Initiate");
633        } else if (usage == GSSCredential.ACCEPT_ONLY) {
634            displayString =
635                displayString.concat(" usage: Accept");
636        } else {
637            displayString =
638                displayString.concat(" usage: Initiate and Accept");
639        }
640        return displayString;
641    }
642
643    public String toString() {
644
645        if (destroyed) {
646            throw new IllegalStateException("This credential is " +
647                                        "no longer valid");
648        }
649
650        GSSCredentialSpi element = null;
651        StringBuilder sb = new StringBuilder("[GSSCredential: ");
652        Object[] elements = hashtable.entrySet().toArray();
653        for (int i = 0; i < elements.length; i++) {
654            try {
655                sb.append('\n');
656                element = (GSSCredentialSpi)
657                    ((Map.Entry)elements[i]).getValue();
658                sb.append(element.getName());
659                sb.append(' ');
660                sb.append(element.getMechanism());
661                sb.append(element.isInitiatorCredential() ?
662                          " Initiate" : "");
663                sb.append(element.isAcceptorCredential() ?
664                          " Accept" : "");
665                sb.append(" [");
666                sb.append(element.getClass());
667                sb.append(']');
668            } catch (GSSException e) {
669                // skip to next element
670            }
671        }
672        sb.append(']');
673        return sb.toString();
674    }
675
676    static class SearchKey {
677        private Oid mechOid = null;
678        private int usage = GSSCredential.INITIATE_AND_ACCEPT;
679        public SearchKey(Oid mechOid, int usage) {
680
681            this.mechOid = mechOid;
682            this.usage = usage;
683        }
684        public Oid getMech() {
685            return mechOid;
686        }
687        public int getUsage() {
688            return usage;
689        }
690        public boolean equals(Object other) {
691            if (! (other instanceof SearchKey))
692                return false;
693            SearchKey that = (SearchKey) other;
694            return ((this.mechOid.equals(that.mechOid)) &&
695                    (this.usage == that.usage));
696        }
697        public int hashCode() {
698            return mechOid.hashCode();
699        }
700    }
701
702}
703