| /* | |
| * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.security.jgss; | |
| import org.ietf.jgss.*; | |
| import sun.security.jgss.spi.*; | |
| import java.util.*; | |
| import sun.security.jgss.spnego.SpNegoCredElement; | |
| public class GSSCredentialImpl implements GSSCredential { | |
| private GSSManagerImpl gssManager = null; | |
| private boolean destroyed = false; | |
| /* | |
| * We store all elements in a hashtable, using <oid, usage> as the | |
| * key. This makes it easy to locate the specific kind of credential we | |
| * need. The implementation needs to be optimized for the case where | |
| * there is just one element (tempCred). | |
| */ | |
| private Hashtable<SearchKey, GSSCredentialSpi> hashtable = null; | |
| // XXX Optimization for single mech usage | |
| private GSSCredentialSpi tempCred = null; | |
| public GSSCredentialImpl() { | |
| // Useless | |
| } | |
| // Used by new ExtendedGSSCredential.ExtendedGSSCredentialImpl(cred) | |
| protected GSSCredentialImpl(GSSCredentialImpl src) { | |
| this.gssManager = src.gssManager; | |
| this.destroyed = src.destroyed; | |
| this.hashtable = src.hashtable; | |
| this.tempCred = src.tempCred; | |
| } | |
| GSSCredentialImpl(GSSManagerImpl gssManager, int usage) | |
| throws GSSException { | |
| this(gssManager, null, GSSCredential.DEFAULT_LIFETIME, | |
| (Oid[]) null, usage); | |
| } | |
| GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name, | |
| int lifetime, Oid mech, int usage) | |
| throws GSSException { | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| init(gssManager); | |
| add(name, lifetime, lifetime, mech, usage); | |
| } | |
| GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name, | |
| int lifetime, Oid[] mechs, int usage) | |
| throws GSSException { | |
| init(gssManager); | |
| boolean defaultList = false; | |
| if (mechs == null) { | |
| mechs = gssManager.getMechs(); | |
| defaultList = true; | |
| } | |
| for (int i = 0; i < mechs.length; i++) { | |
| try { | |
| add(name, lifetime, lifetime, mechs[i], usage); | |
| } catch (GSSException e) { | |
| if (defaultList) { | |
| // Try the next mechanism | |
| if (GSSUtil.DEBUG != null) { | |
| GSSUtil.debug("Ignore " + e + " while acquiring cred for " | |
| + mechs[i]); | |
| // e.printStackTrace(); | |
| } | |
| } else throw e; // else try the next mechanism | |
| } | |
| } | |
| if ((hashtable.size() == 0) || (usage != getUsage())) | |
| throw new GSSException(GSSException.NO_CRED); | |
| } | |
| // Wrap a mech cred into a GSS cred | |
| public GSSCredentialImpl(GSSManagerImpl gssManager, | |
| GSSCredentialSpi mechElement) throws GSSException { | |
| init(gssManager); | |
| int usage = GSSCredential.ACCEPT_ONLY; | |
| if (mechElement.isInitiatorCredential()) { | |
| if (mechElement.isAcceptorCredential()) { | |
| usage = GSSCredential.INITIATE_AND_ACCEPT; | |
| } else { | |
| usage = GSSCredential.INITIATE_ONLY; | |
| } | |
| } | |
| SearchKey key = new SearchKey(mechElement.getMechanism(), | |
| usage); | |
| tempCred = mechElement; | |
| hashtable.put(key, tempCred); | |
| // More mechs that can use this cred, say, SPNEGO | |
| if (!GSSUtil.isSpNegoMech(mechElement.getMechanism())) { | |
| key = new SearchKey(GSSUtil.GSS_SPNEGO_MECH_OID, usage); | |
| hashtable.put(key, new SpNegoCredElement(mechElement)); | |
| } | |
| } | |
| void init(GSSManagerImpl gssManager) { | |
| this.gssManager = gssManager; | |
| hashtable = new Hashtable<SearchKey, GSSCredentialSpi>( | |
| gssManager.getMechs().length); | |
| } | |
| public void dispose() throws GSSException { | |
| if (!destroyed) { | |
| for (GSSCredentialSpi element : hashtable.values()) { | |
| element.dispose(); | |
| } | |
| destroyed = true; | |
| } | |
| } | |
| public GSSCredential impersonate(GSSName name) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| Oid mech = tempCred.getMechanism(); | |
| GSSNameSpi nameElement = (name == null ? null : | |
| ((GSSNameImpl)name).getElement(mech)); | |
| GSSCredentialSpi cred = tempCred.impersonate(nameElement); | |
| return (cred == null ? | |
| null : GSSManagerImpl.wrap(new GSSCredentialImpl(gssManager, cred))); | |
| } | |
| public GSSName getName() throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| return GSSNameImpl.wrapElement(gssManager, tempCred.getName()); | |
| } | |
| public GSSName getName(Oid mech) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| SearchKey key; | |
| GSSCredentialSpi element; | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| key = new SearchKey(mech, GSSCredential.INITIATE_ONLY); | |
| element = hashtable.get(key); | |
| if (element == null) { | |
| key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY); | |
| element = hashtable.get(key); | |
| } | |
| if (element == null) { | |
| key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| } | |
| if (element == null) { | |
| throw new GSSExceptionImpl(GSSException.BAD_MECH, mech); | |
| } | |
| return GSSNameImpl.wrapElement(gssManager, element.getName()); | |
| } | |
| /** | |
| * Returns the remaining lifetime of this credential. The remaining | |
| * lifetime is defined as the minimum lifetime, either for initiate or | |
| * for accept, across all elements contained in it. Not terribly | |
| * useful, but required by GSS-API. | |
| */ | |
| public int getRemainingLifetime() throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| GSSCredentialSpi tempCred; | |
| int tempLife, tempInitLife, tempAcceptLife; | |
| int min = INDEFINITE_LIFETIME; | |
| for (SearchKey tempKey : hashtable.keySet()) { | |
| tempCred = hashtable.get(tempKey); | |
| if (tempKey.getUsage() == INITIATE_ONLY) | |
| tempLife = tempCred.getInitLifetime(); | |
| else if (tempKey.getUsage() == ACCEPT_ONLY) | |
| tempLife = tempCred.getAcceptLifetime(); | |
| else { | |
| tempInitLife = tempCred.getInitLifetime(); | |
| tempAcceptLife = tempCred.getAcceptLifetime(); | |
| tempLife = (Math.min(tempInitLife, tempAcceptLife)); | |
| } | |
| if (min > tempLife) | |
| min = tempLife; | |
| } | |
| return min; | |
| } | |
| public int getRemainingInitLifetime(Oid mech) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| GSSCredentialSpi element; | |
| SearchKey key; | |
| boolean found = false; | |
| int max = 0; | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| key = new SearchKey(mech, GSSCredential.INITIATE_ONLY); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| found = true; | |
| if (max < element.getInitLifetime()) | |
| max = element.getInitLifetime(); | |
| } | |
| key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| found = true; | |
| if (max < element.getInitLifetime()) | |
| max = element.getInitLifetime(); | |
| } | |
| if (!found) { | |
| throw new GSSExceptionImpl(GSSException.BAD_MECH, mech); | |
| } | |
| return max; | |
| } | |
| public int getRemainingAcceptLifetime(Oid mech) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| GSSCredentialSpi element; | |
| SearchKey key; | |
| boolean found = false; | |
| int max = 0; | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| found = true; | |
| if (max < element.getAcceptLifetime()) | |
| max = element.getAcceptLifetime(); | |
| } | |
| key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| found = true; | |
| if (max < element.getAcceptLifetime()) | |
| max = element.getAcceptLifetime(); | |
| } | |
| if (!found) { | |
| throw new GSSExceptionImpl(GSSException.BAD_MECH, mech); | |
| } | |
| return max; | |
| } | |
| /** | |
| * Returns the usage mode for this credential. Returns | |
| * INITIATE_AND_ACCEPT if any one element contained in it supports | |
| * INITIATE_AND_ACCEPT or if two different elements exist where one | |
| * support INITIATE_ONLY and the other supports ACCEPT_ONLY. | |
| */ | |
| public int getUsage() throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| boolean initiate = false; | |
| boolean accept = false; | |
| for (SearchKey tempKey : hashtable.keySet()) { | |
| if (tempKey.getUsage() == INITIATE_ONLY) | |
| initiate = true; | |
| else if (tempKey.getUsage() == ACCEPT_ONLY) | |
| accept = true; | |
| else | |
| return INITIATE_AND_ACCEPT; | |
| } | |
| if (initiate) { | |
| if (accept) | |
| return INITIATE_AND_ACCEPT; | |
| else | |
| return INITIATE_ONLY; | |
| } else | |
| return ACCEPT_ONLY; | |
| } | |
| public int getUsage(Oid mech) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| GSSCredentialSpi element; | |
| SearchKey key; | |
| boolean initiate = false; | |
| boolean accept = false; | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| key = new SearchKey(mech, GSSCredential.INITIATE_ONLY); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| initiate = true; | |
| } | |
| key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| accept = true; | |
| } | |
| key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| if (element != null) { | |
| initiate = true; | |
| accept = true; | |
| } | |
| if (initiate && accept) | |
| return GSSCredential.INITIATE_AND_ACCEPT; | |
| else if (initiate) | |
| return GSSCredential.INITIATE_ONLY; | |
| else if (accept) | |
| return GSSCredential.ACCEPT_ONLY; | |
| else { | |
| throw new GSSExceptionImpl(GSSException.BAD_MECH, mech); | |
| } | |
| } | |
| public Oid[] getMechs() throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| ArrayList<Oid> result = new ArrayList<Oid>(hashtable.size()); | |
| for (SearchKey tempKey : hashtable.keySet()) { | |
| result.add(tempKey.getMech()); | |
| } | |
| return result.toArray(new Oid[0]); | |
| } | |
| public void add(GSSName name, int initLifetime, int acceptLifetime, | |
| Oid mech, int usage) throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; | |
| SearchKey key = new SearchKey(mech, usage); | |
| if (hashtable.containsKey(key)) { | |
| throw new GSSExceptionImpl(GSSException.DUPLICATE_ELEMENT, | |
| "Duplicate element found: " + | |
| getElementStr(mech, usage)); | |
| } | |
| // XXX If not instance of GSSNameImpl then throw exception | |
| // Application mixing GSS implementations | |
| GSSNameSpi nameElement = (name == null ? null : | |
| ((GSSNameImpl)name).getElement(mech)); | |
| tempCred = gssManager.getCredentialElement(nameElement, | |
| initLifetime, | |
| acceptLifetime, | |
| mech, | |
| usage); | |
| /* | |
| * Not all mechanisms support the concept of one credential element | |
| * that can be used for both initiating and accepting a context. In | |
| * the event that an application requests usage INITIATE_AND_ACCEPT | |
| * for a credential from such a mechanism, the GSS framework will | |
| * need to obtain two different credential elements from the | |
| * mechanism, one that will have usage INITIATE_ONLY and another | |
| * that will have usage ACCEPT_ONLY. The mechanism will help the | |
| * GSS-API realize this by returning a credential element with | |
| * usage INITIATE_ONLY or ACCEPT_ONLY prompting it to make another | |
| * call to getCredentialElement, this time with the other usage | |
| * mode. | |
| */ | |
| if (tempCred != null) { | |
| if (usage == GSSCredential.INITIATE_AND_ACCEPT && | |
| (!tempCred.isAcceptorCredential() || | |
| !tempCred.isInitiatorCredential())) { | |
| int currentUsage; | |
| int desiredUsage; | |
| if (!tempCred.isInitiatorCredential()) { | |
| currentUsage = GSSCredential.ACCEPT_ONLY; | |
| desiredUsage = GSSCredential.INITIATE_ONLY; | |
| } else { | |
| currentUsage = GSSCredential.INITIATE_ONLY; | |
| desiredUsage = GSSCredential.ACCEPT_ONLY; | |
| } | |
| key = new SearchKey(mech, currentUsage); | |
| hashtable.put(key, tempCred); | |
| tempCred = gssManager.getCredentialElement(nameElement, | |
| initLifetime, | |
| acceptLifetime, | |
| mech, | |
| desiredUsage); | |
| key = new SearchKey(mech, desiredUsage); | |
| hashtable.put(key, tempCred); | |
| } else { | |
| hashtable.put(key, tempCred); | |
| } | |
| } | |
| } | |
| @Override | |
| public boolean equals(Object another) { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| if (this == another) { | |
| return true; | |
| } | |
| if (!(another instanceof GSSCredentialImpl)) { | |
| return false; | |
| } | |
| // NOTE: The specification does not define the criteria to compare | |
| // credentials. | |
| /* | |
| * XXX | |
| * The RFC says: "Tests if this GSSCredential refers to the same | |
| * entity as the supplied object. The two credentials must be | |
| * acquired over the same mechanisms and must refer to the same | |
| * principal. Returns "true" if the two GSSCredentials refer to | |
| * the same entity; "false" otherwise." | |
| * | |
| * Well, when do two credentials refer to the same principal? Do | |
| * they need to have one GSSName in common for the different | |
| * GSSName's that the credential elements return? Or do all | |
| * GSSName's have to be in common when the names are exported with | |
| * their respective mechanisms for the credential elements? | |
| */ | |
| return false; | |
| } | |
| /** | |
| * {@return a hashcode value for this GSSCredential} | |
| */ | |
| @Override | |
| public int hashCode() { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| // NOTE: The specification does not define the criteria to compare | |
| // credentials. | |
| /* | |
| * XXX | |
| * Decide on a criteria for equals first then do this. | |
| */ | |
| return 1; | |
| } | |
| /** | |
| * Returns the specified mechanism's credential-element. | |
| * | |
| * @param mechOid the oid for mechanism to retrieve | |
| * @param initiate boolean indicating if the function is | |
| * to throw exception or return null when element is not | |
| * found. | |
| * @return mechanism credential object | |
| * @exception GSSException of invalid mechanism | |
| */ | |
| public GSSCredentialSpi getElement(Oid mechOid, boolean initiate) | |
| throws GSSException { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| SearchKey key; | |
| GSSCredentialSpi element; | |
| if (mechOid == null) { | |
| /* | |
| * First see if the default mechanism satisfies the | |
| * desired usage. | |
| */ | |
| mechOid = ProviderList.DEFAULT_MECH_OID; | |
| key = new SearchKey(mechOid, | |
| initiate? INITIATE_ONLY : ACCEPT_ONLY); | |
| element = hashtable.get(key); | |
| if (element == null) { | |
| key = new SearchKey(mechOid, INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| if (element == null) { | |
| /* | |
| * Now just return any element that satisfies the | |
| * desired usage. | |
| */ | |
| Object[] elements = hashtable.entrySet().toArray(); | |
| for (int i = 0; i < elements.length; i++) { | |
| element = (GSSCredentialSpi) | |
| ((Map.Entry)elements[i]).getValue(); | |
| if (element.isInitiatorCredential() == initiate) | |
| break; | |
| } // for loop | |
| } | |
| } | |
| } else { | |
| if (initiate) | |
| key = new SearchKey(mechOid, INITIATE_ONLY); | |
| else | |
| key = new SearchKey(mechOid, ACCEPT_ONLY); | |
| element = hashtable.get(key); | |
| if (element == null) { | |
| key = new SearchKey(mechOid, INITIATE_AND_ACCEPT); | |
| element = hashtable.get(key); | |
| } | |
| } | |
| if (element == null) | |
| throw new GSSExceptionImpl(GSSException.NO_CRED, | |
| "No credential found for: " + | |
| getElementStr(mechOid, | |
| initiate? INITIATE_ONLY : ACCEPT_ONLY)); | |
| return element; | |
| } | |
| Set<GSSCredentialSpi> getElements() { | |
| return new HashSet<>(hashtable.values()); | |
| } | |
| private static String getElementStr(Oid mechOid, int usage) { | |
| String displayString = mechOid.toString(); | |
| if (usage == GSSCredential.INITIATE_ONLY) { | |
| displayString = | |
| displayString.concat(" usage: Initiate"); | |
| } else if (usage == GSSCredential.ACCEPT_ONLY) { | |
| displayString = | |
| displayString.concat(" usage: Accept"); | |
| } else { | |
| displayString = | |
| displayString.concat(" usage: Initiate and Accept"); | |
| } | |
| return displayString; | |
| } | |
| public String toString() { | |
| if (destroyed) { | |
| throw new IllegalStateException("This credential is " + | |
| "no longer valid"); | |
| } | |
| GSSCredentialSpi element; | |
| StringBuilder sb = new StringBuilder("[GSSCredential: "); | |
| Object[] elements = hashtable.entrySet().toArray(); | |
| for (int i = 0; i < elements.length; i++) { | |
| try { | |
| sb.append('\n'); | |
| element = (GSSCredentialSpi) | |
| ((Map.Entry)elements[i]).getValue(); | |
| sb.append(element.getName()); | |
| sb.append(' '); | |
| sb.append(element.getMechanism()); | |
| sb.append(element.isInitiatorCredential() ? | |
| " Initiate" : ""); | |
| sb.append(element.isAcceptorCredential() ? | |
| " Accept" : ""); | |
| sb.append(" ["); | |
| sb.append(element.getClass()); | |
| sb.append(']'); | |
| } catch (GSSException e) { | |
| // skip to next element | |
| } | |
| } | |
| sb.append(']'); | |
| return sb.toString(); | |
| } | |
| static class SearchKey { | |
| private final Oid mechOid; | |
| private int usage = GSSCredential.INITIATE_AND_ACCEPT; | |
| public SearchKey(Oid mechOid, int usage) { | |
| this.mechOid = mechOid; | |
| this.usage = usage; | |
| } | |
| public Oid getMech() { | |
| return mechOid; | |
| } | |
| public int getUsage() { | |
| return usage; | |
| } | |
| public boolean equals(Object other) { | |
| if (! (other instanceof SearchKey that)) | |
| return false; | |
| return ((this.mechOid.equals(that.mechOid)) && | |
| (this.usage == that.usage)); | |
| } | |
| public int hashCode() { | |
| return mechOid.hashCode(); | |
| } | |
| } | |
| } |