/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jini.jeri.ssl; import com.sun.jini.action.GetPropertyAction; import com.sun.jini.collection.WeakSoftTable; import java.lang.ref.ReferenceQueue; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.security.auth.AuthPermission; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; import net.jini.core.constraint.ClientMaxPrincipal; import net.jini.core.constraint.ClientMinPrincipal; import net.jini.core.constraint.ConstraintAlternatives; import net.jini.core.constraint.Integrity; import net.jini.core.constraint.InvocationConstraint; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.ServerMinPrincipal; import net.jini.io.UnsupportedConstraintException; import net.jini.jeri.Endpoint; import net.jini.security.Security; /** * Provides miscellaneous utilities for the classes in this package. * * @author Sun Microsystems, Inc. */ abstract class Utilities { /* -- Fields -- */ /** * The names of JSSE key exchange algorithms used for anonymous * communication. */ private static final String[] ANONYMOUS_KEY_EXCHANGE_ALGORITHMS = { "DH_anon", "DH_anon_EXPORT" }; /** The names of JSSE key exchange algorithms that use RSA keys. */ private static final String[] RSA_KEY_EXCHANGE_ALGORITHMS = { "DHE_RSA", "DHE_RSA_EXPORT", "DH_RSA", "DH_RSA_EXPORT", "RSA", "RSA_EXPORT" }; /** The names of JSSE key exchange algorithms that use DSA keys. */ private static final String[] DSA_KEY_EXCHANGE_ALGORITHMS = { "DHE_DSS", "DHE_DSS_EXPORT", "DH_DSS", "DH_DSS_EXPORT" }; /** * The names of all the JSSE key exchange algorithms supported by this * provider. */ private static final String[] SUPPORTED_KEY_EXCHANGE_ALGORITHMS = { "DH_anon", "DH_anon_EXPORT", "DHE_RSA", "DHE_RSA_EXPORT", "DH_RSA", "DH_RSA_EXPORT", "RSA", "RSA_EXPORT", "DHE_DSS", "DHE_DSS_EXPORT", "DH_DSS", "DH_DSS_EXPORT" }; /** * The name of the JSSE message integrity code algorithm that does not * insure integrity. */ private static final String NO_INTEGRITY_MIC_ALGORITHM = "NULL"; /** The name of the JSSE cipher algorithm that provides no encryption */ private static final String NO_ENCRYPTION_CIPHER_ALGORITHM = "NULL"; /** The names of cipher algorithms that do strong encryption */ private static final String[] STRONG_ENCRYPTION_CIPHERS = { "3DES_EDE_CBC", "AES_128_CBC", "AES_256_CBC", "IDEA_CBC", "RC4_128" }; /** The names of all cipher algorithms supported by this provider. */ private static final String[] SUPPORTED_ENCRYPTION_CIPHERS = { "3DES_EDE_CBC", "AES_128_CBC", "AES_256_CBC", "DES40_CBC", "DES_CBC", "IDEA_CBC", "NULL", "RC2_CBC_40", "RC4_128", "RC4_40" }; /** Client logger */ static final Logger clientLogger = Logger.getLogger("net.jini.jeri.ssl.client"); /** Server logger */ static final Logger serverLogger = Logger.getLogger("net.jini.jeri.ssl.server"); /** Initialization logger */ static final Logger initLogger = Logger.getLogger("net.jini.jeri.ssl.init"); /** * Returned by getPermittedKeyAlgorithms when any key algorithm is * permitted. */ static final int ANY_KEY_ALGORITHM = 0xffffffff; /** * Or'ed into the value returned by getPermittedKeyAlgorithms when DSA keys * are permitted. */ static final int DSA_KEY_ALGORITHM = 1 << 0; /** * Or'ed into the value returned by getPermittedKeyAlgorithms when RSA keys * are permitted. */ static final int RSA_KEY_ALGORITHM = 1 << 1; /** Stores SSL contexts and auth managers. */ private static final WeakSoftTable sslContextMap = new WeakSoftTable(); /** * The cipher suites supported by the JSSE implementation, or null if not * set yet. */ private static String[] supportedCipherSuitesInternal = null; /** * The cipher suites specified by the user, or null if not specified. The * field is not final to aid testing. */ private static String[] requestedCipherSuites; static { String value = (String) Security.doPrivileged( new GetPropertyAction("com.sun.jini.jeri.ssl.cipherSuites")); if (value == null) { requestedCipherSuites = null; } else { StringTokenizer tokens = new StringTokenizer(value, ","); final int count = tokens.countTokens(); requestedCipherSuites = new String[count]; for (int i = 0; i < count; i++) { requestedCipherSuites[i] = tokens.nextToken(); } } } /** An X.509 certificate factory for creating CertPaths. */ private static CertificateFactory certFactory; /** * Represents a principal whose name is not known. Used, for example, to * represent the server principal when the server subject is not available. */ static final Principal UNKNOWN_PRINCIPAL = new Principal() { public String toString() { return "UNKNOWN_PRINCIPAL"; } public String getName() { return toString(); } }; /** Constraints that require Integrity.YES. */ static final InvocationConstraints INTEGRITY_REQUIRED = new InvocationConstraints(Integrity.YES, null); /** Constraints that prefer Integrity.YES. */ static final InvocationConstraints INTEGRITY_PREFERRED = new InvocationConstraints(null, Integrity.YES); /** The secure socket protocol used with JSSE. */ private static final String sslProtocol = (String) Security.doPrivileged( new GetPropertyAction("com.sun.jini.jeri.ssl.sslProtocol", "TLS")); /** Permission needed to access the current subject. */ static final AuthPermission getSubjectPermission = new AuthPermission("getSubject"); /* -- Methods -- */ /* -- getSupportedCipherSuites -- */ /** * Returns all the cipher suites supported by the JSSE implementation and * this provider. */ static String[] getSupportedCipherSuites() { synchronized (sslContextMap) { if (supportedCipherSuitesInternal == null) { SSLContextInfo info = getServerSSLContextInfo(null, null); SSLSocketFactory factory = info.sslContext.getSocketFactory(); supportedCipherSuitesInternal = getSupportedCipherSuites(factory); } return supportedCipherSuitesInternal; } } /** * Returns all the cipher suites supported by the socket factory and this * provider. Uses the requested cipher suites, if any. */ private static String[] getSupportedCipherSuites( SSLSocketFactory factory) { String[] suites; if (requestedCipherSuites == null) { suites = factory.getSupportedCipherSuites(); } else if (requestedCipherSuites.length == 0) { initLogger.log(Level.WARNING, "Problem with requested cipher suites: " + "No suites specified -- " + "using default suites"); suites = factory.getSupportedCipherSuites(); } else { /* * Set the enabled suites on a socket to make sure they are * supported by JSSE. */ try { SSLSocket socket = (SSLSocket) factory.createSocket(); socket.setEnabledCipherSuites(requestedCipherSuites); suites = requestedCipherSuites; } catch (Exception e) { initLogger.log(Level.WARNING, "Problem with requested cipher suites -- " + "using default suites", e); suites = factory.getSupportedCipherSuites(); } } return getSupportedCipherSuites(suites); } /** * Filters out unsupported suites, modifying the argument and maintaining * the original order. */ private static String[] getSupportedCipherSuites(String[] suites) { int max = suites.length; for (int i = suites.length; --i >= 0; ) { if (!supportedCipherSuite(suites[i])) { if (i < max - 1) { System.arraycopy(suites, i + 1, suites, i, max - i - 1); } max--; } } if (max == suites.length) { return suites; } else { String[] copy = new String[max]; System.arraycopy(suites, 0, copy, 0, max); return copy; } } /* -- principal constraints methods -- */ /** * Returns all client principals referred to by the constraints or null if * no client principal constraints are specified. Returns a new set if the * result is non-null. */ static Set getClientPrincipals(InvocationConstraints constraints) { return getPrincipals(constraints, true); } /** * Returns all client principals referred to by the constraints or null if * no client principal constraints are specified. Returns a new set if the * result is non-null. */ static Set getClientPrincipals(Set constraints) { return getPrincipals(constraints, true); } /** * Returns all server principals referred to by the constraints or null if * no server principal constraints are specified. Returns a new set if the * result is non-null. */ static Set getServerPrincipals(InvocationConstraints constraints) { return getPrincipals(constraints, false); } /** Implements getClientPrincipals or getServerPrincipals. */ private static Set getPrincipals(InvocationConstraints constraints, boolean client) { Set requirements = getPrincipals(constraints.requirements(), client); Set preferences = getPrincipals(constraints.preferences(), client); if (requirements == null) { return preferences; } else if (preferences == null) { return requirements; } else { requirements.addAll(preferences); return requirements; } } /** * Returns the client or server principals referred to by a set of * constraints. */ private static Set getPrincipals(Set constraints, boolean client) { Set result = null; for (Iterator i = constraints.iterator(); i.hasNext(); ) { Set eltResult = getPrincipals((InvocationConstraint) i.next(), client); if (eltResult != null) { if (result == null) { result = new HashSet(); } result.addAll(eltResult); } } return result; } /** * Returns the principals specified by a ClientMinPrincipal, * ClientMaxPrincipal, or ServerMinPrincipal constraint, or an alternatives * of one of those types. */ private static Set getPrincipals(InvocationConstraint constraint, boolean client) { Set principals; if (constraint instanceof ConstraintAlternatives) { Set alts = ((ConstraintAlternatives) constraint).elements(); return getPrincipals(alts, client); } else if (constraint instanceof ServerMinPrincipal) { if (client) { return null; } principals = ((ServerMinPrincipal) constraint).elements(); } else if (!client) { return null; } else if (constraint instanceof ClientMinPrincipal) { principals = ((ClientMinPrincipal) constraint).elements(); } else if (constraint instanceof ClientMaxPrincipal) { principals = ((ClientMaxPrincipal) constraint).elements(); } else { return null; } Set result = new HashSet(principals.size()); for (Iterator i = principals.iterator(); i.hasNext(); ) { Object elt = i.next(); if (elt instanceof X500Principal) { result.add(elt); } } return result; } /* -- get*SSLContextInfo -- */ /** Used to pass an SSLContext and AuthManager pair. */ static class SSLContextInfo { final SSLContext sslContext; final AuthManager authManager; SSLContextInfo(SSLContext sslContext, AuthManager authManager) { this.sslContext = sslContext; this.authManager = authManager; } } /** * WeakKey for looking up a server SSLContext. Stores a weak reference to * the subject, plus the permitted principals. */ private static class ServerKey extends WeakSoftTable.WeakKey { final Set permittedLocalPrincipals; /** Creates a key for the specified subject and local principals */ ServerKey(Subject subject, Set permittedLocalPrincipals) { super(subject); this.permittedLocalPrincipals = permittedLocalPrincipals; } /** Copies the key to the queue */ ServerKey(ServerKey serverKey, ReferenceQueue queue) { super(serverKey, queue); permittedLocalPrincipals = serverKey.permittedLocalPrincipals; } public WeakSoftTable.RemovableReference copy(ReferenceQueue queue) { return new ServerKey(this, queue); } public int hashCode() { return super.hashCode() ^ (permittedLocalPrincipals == null ? 1 : permittedLocalPrincipals.hashCode()); } public boolean equals(Object other) { return super.equals(other) && safeEquals(permittedLocalPrincipals, ((ServerKey) other).permittedLocalPrincipals); } } /** * WeakKey for looking up a client SSLContext. Stores a weak reference to * the subject, plus the permitted client and server principals, the * endpoint, and whether client authentication is required. */ private final static class ClientKey extends ServerKey { final Set permittedRemotePrincipals; final Endpoint endpoint; final boolean clientAuthRequired; final String[] cipherSuites; /** Creates a key for the specified client call context. */ ClientKey(CallContext callContext) { super(callContext.clientSubject, callContext.clientPrincipals); permittedRemotePrincipals = callContext.serverPrincipals; endpoint = callContext.endpoint; clientAuthRequired = callContext.clientAuthRequired; cipherSuites = callContext.cipherSuites; assert cipherSuites != null && cipherSuites.length > 0 && cipherSuites[0] != null; } /** Copies the key to the queue. */ private ClientKey(ClientKey clientKey, ReferenceQueue queue) { super(clientKey, queue); permittedRemotePrincipals = clientKey.permittedRemotePrincipals; endpoint = clientKey.endpoint; clientAuthRequired = clientKey.clientAuthRequired; cipherSuites = clientKey.cipherSuites; } public WeakSoftTable.RemovableReference copy(ReferenceQueue queue) { return new ClientKey(this, queue); } public int hashCode() { return super.hashCode() ^ (permittedRemotePrincipals == null ? 2 : permittedRemotePrincipals.hashCode()) ^ endpoint.hashCode() ^ (clientAuthRequired ? 4 : 0) ^ cipherSuites[0].hashCode(); } public boolean equals(Object other) { if (!super.equals(other)) { return false; } ClientKey clientKey = (ClientKey) other; return safeEquals(permittedRemotePrincipals, clientKey.permittedRemotePrincipals) && endpoint.equals(clientKey.endpoint) && clientAuthRequired == clientKey.clientAuthRequired && Arrays.equals(cipherSuites, clientKey.cipherSuites); } } /** * Used to store a soft reference to a SSLContext and the associated * AuthManager in the SSL context map. */ private final static class Value extends WeakSoftTable.SoftValue { final AuthManager authManager; /** * Creates a value for the associated key containing the specified SSL * context and auth manager. */ Value(ServerKey key, SSLContext sslContext, AuthManager authManager) { super(key, sslContext); this.authManager = authManager; } /** Copies the value to the queue. */ private Value(Value value, ReferenceQueue queue) { super(value, queue); this.authManager = value.authManager; } public WeakSoftTable.RemovableReference copy(ReferenceQueue queue) { return new Value(this, queue); } /** Returns the SSL context. */ SSLContext getSSLContext() { return (SSLContext) get(); } } /** * Returns the SSLContext and ClientAuthManager to use for creating client * socket factories. Each client connection has exclusive access to an * SSLContext while the opening an SSL connection and should return the * SSLContextInfo by calling releaseClientSSLContextInfo when the * connection handshake is done, unless the handshake fails. * * @param callContext the client call context * @return an SSLContextInfo containing an SSLContext and ClientAuthManager * to use for a connection described by the argument * @throws RuntimeException if an error occurs during initialization of * JSSE */ static SSLContextInfo getClientSSLContextInfo(CallContext callContext) { ClientKey key = new ClientKey(callContext); synchronized (sslContextMap) { for (int i = 0; true; i++) { Value value = (Value) sslContextMap.get(key, i); if (value == null) { break; } SSLContext sslContext = value.getSSLContext(); if (sslContext != null) { ClientAuthManager authManager = (ClientAuthManager) value.authManager; try { authManager.checkAuthentication(); } catch (SecurityException e) { continue; } catch (UnsupportedConstraintException e) { continue; } sslContextMap.remove(key, i); if (clientLogger.isLoggable(Level.FINEST)) { clientLogger.log( Level.FINEST, "get client SSL context for {0}\n" + "returns existing {1}", new Object[] { callContext, sslContext }); } return new SSLContextInfo(sslContext, authManager); } } } /* Create a new SSL context */ SSLContext sslContext; try { sslContext = SSLContext.getInstance(sslProtocol); } catch (NoSuchAlgorithmException e) { throw initializationError(e, "finding SSL context"); } ClientAuthManager authManager; try { authManager = new ClientAuthManager( callContext.clientSubject, callContext.clientPrincipals, callContext.serverPrincipals); } catch (NoSuchAlgorithmException e) { throw initializationError(e, "creating key or trust manager"); } /* Initialize SSL context */ try { sslContext.init(new KeyManager[] { authManager }, new TrustManager[] { authManager }, null); } catch (KeyManagementException e) { throw initializationError(e, "initializing SSL context"); } if (clientLogger.isLoggable(Level.FINEST)) { clientLogger.log(Level.FINEST, "get client SSL context for {0}\nreturns new {1}", new Object[] { callContext, sslContext }); } return new SSLContextInfo(sslContext, authManager); } /** * Returns the SSLContext and ServerAuthManager to use for creating server * socket factories. Server connections with the same subject and * principals share the same SSLContext. * * @param serverSubject the subject, or null * @param serverPrincipals the permitted principals, or null * @return an SSLContextInfo containing an SSLContext and ServerAuthManager * to use for a connection described by the arguments * @throws RuntimeException if an error occurs during initialization of * JSSE */ static SSLContextInfo getServerSSLContextInfo(Subject serverSubject, Set serverPrincipals) { ServerKey key = new ServerKey(serverSubject, serverPrincipals); synchronized (sslContextMap) { Value value = (Value) sslContextMap.get(key, 0); if (value != null) { SSLContext sslContext = value.getSSLContext(); if (sslContext != null) { if (serverLogger.isLoggable(Level.FINEST)) { serverLogger.log( Level.FINEST, "get server SSL context for {0}\n" + "and principals {1}\nreturns existing {2}", new Object[] { subjectString(serverSubject), serverPrincipals, sslContext }); } return new SSLContextInfo(sslContext, value.authManager); } } SSLContext sslContext; try { sslContext = SSLContext.getInstance(sslProtocol); } catch (NoSuchAlgorithmException e) { throw initializationError(e, "finding SSL context"); } ServerAuthManager authManager; try { authManager = new ServerAuthManager( serverSubject, serverPrincipals, sslContext.getServerSessionContext()); } catch (NoSuchAlgorithmException e) { throw initializationError(e, "creating key or trust manager"); } /* Initialize SSL context */ try { sslContext.init(new KeyManager[] { authManager }, new TrustManager[] { authManager }, null); } catch (KeyManagementException e) { throw initializationError(e, "initializing SSL context"); } sslContextMap.add(key, new Value(key, sslContext, authManager)); if (serverLogger.isLoggable(Level.FINEST)) { serverLogger.log( Level.FINEST, "get server SSL context for {0}\nand principals {1}\n" + "returns new {2}", new Object[] { subjectString(serverSubject), serverPrincipals, sslContext }); } return new SSLContextInfo(sslContext, authManager); } } /** * Returns the client's SSLContext and ClientAuthManager to the * SSLContextMap for use by another connection. */ static void releaseClientSSLContextInfo(CallContext callContext, SSLContext sslContext, ClientAuthManager authManager) { ClientKey key = new ClientKey(callContext); synchronized (sslContextMap) { sslContextMap.add(key, new Value(key, sslContext, authManager)); } } /** * Returns a <code>RuntimeException</code> for a problem initializing JSSE. * * @param error an <code>Exception</code> that describes the problem * @param contextString describes where the problem occurred * @return a <code>RuntimeException</code> describing the problem */ private static RuntimeException initializationError(Exception error, String contextString) { RuntimeException e = new RuntimeException( "Error during initialization of SSL or HTTPS provider, " + "while " + contextString + ": " + error.getMessage(), error); initLogger.log(Level.WARNING, "Initialization error", e); return e; } /** * Returns a CertificateFactory for generating a CertPath for X.509 * certificates. */ static CertificateFactory getCertFactory() { synchronized (sslContextMap) { if (certFactory == null) { try { certFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw initializationError( e, "getting certificate factory"); } } return certFactory; } } /* -- firstX509Cert -- */ /** * Returns the first X509Certificate from a CertPath known to contain them. */ static X509Certificate firstX509Cert(CertPath certPath) { return (X509Certificate) certPath.getCertificates().get(0); } /* -- checkValidity -- */ /** * Checks if the X.509 certificates in the CertPath are currently valid. * If issuers is non-null, insures the path contains one of the issuers. */ static void checkValidity(CertPath x509CertPath, X500Principal[] issuers) throws CertificateException { boolean issuerOK = issuers == null; List certs = x509CertPath.getCertificates(); for (int i = certs.size(); --i >= 0; ) { X509Certificate cert = (X509Certificate) certs.get(i); cert.checkValidity(); if (!issuerOK) { X500Principal certIssuer = cert.getIssuerX500Principal(); for (int j = issuers.length; --j >= 0; ) { if (certIssuer.equals(issuers[j])) { issuerOK = true; break; } } } } if (!issuerOK) { throw new CertificateException( "No match for permitted issuers: " + toString(issuers) + "\nCertificate chain: " + x509CertPath); } } /* -- Cipher suite info -- */ /** Determines if the cipher suite authenticates the server */ static boolean doesServerAuthentication(String cipherSuite) { String alg = getKeyExchangeAlgorithm(cipherSuite); return position(alg, ANONYMOUS_KEY_EXCHANGE_ALGORITHMS) == -1; } /** Determines if the cipher suite does encryption */ static boolean doesEncryption(String cipherSuite) { return !getCipherAlgorithm(cipherSuite).equals( NO_ENCRYPTION_CIPHER_ALGORITHM); } /** Determines if the cipher suite maintains integrity */ static boolean maintainsIntegrity(String cipherSuite) { return !getMICAlgorithm(cipherSuite).equals( NO_INTEGRITY_MIC_ALGORITHM); } /** * Returns the key exchange algorithm for the specified cipher suite. <p> * * The key exchange algorithm is found following the first underscore and * up to the first occurrence of "_WITH_". */ static String getKeyExchangeAlgorithm(String cipherSuite) { int start = cipherSuite.indexOf('_') + 1; if (start >= 1) { int end = cipherSuite.indexOf("_WITH_", start); if (end >= start) { return cipherSuite.substring(start, end); } } return "NULL"; } /** * Returns the key algorithm for the specified cipher suite, one of "RSA", * "DSA", or "NULL". Throws an IllegalArgumentException if the algorithm * is not recognized. <p> * * The key algorithm is specified by the key exchange algorithm. */ static String getKeyAlgorithm(String cipherSuite) { String alg = getKeyExchangeAlgorithm(cipherSuite); if (position(alg, RSA_KEY_EXCHANGE_ALGORITHMS) != -1) { return "RSA"; } else if (position(alg, DSA_KEY_EXCHANGE_ALGORITHMS) != -1) { return "DSA"; } else if (position(alg, ANONYMOUS_KEY_EXCHANGE_ALGORITHMS) != -1) { return "NULL"; } else { throw new IllegalArgumentException( "Unrecognized key exchange algorithm: " + alg); } } /** * Returns the algorithms permitted for keys used with this cipher suite. * Note that the result can be different for client and server sides. * * @param cipherSuite the cipher suite * @param client true to get results for the client side, false for the * server side * @return the permitted key algorithms, an OR of some set of the values * DSA_KEY_ALGORITHM and RSA_KEY_ALGORITHM * @throws IllegalArgumentException if the key exchange algorithm is not * recognized */ static int getPermittedKeyAlgorithms(String cipherSuite, boolean client) { String keyAlgorithm = getKeyAlgorithm(cipherSuite); if (keyAlgorithm.equals("RSA")) { /* * For these suites, the server must use an RSA key, but the client * may use either an RSA or DSA key. */ return (client) ? DSA_KEY_ALGORITHM | RSA_KEY_ALGORITHM : RSA_KEY_ALGORITHM; } else if (keyAlgorithm.equals("DSA")) { /* Same here, but server must use a DSA key */ return (client) ? DSA_KEY_ALGORITHM | RSA_KEY_ALGORITHM : DSA_KEY_ALGORITHM; } else if (keyAlgorithm.equals("NULL")) { return 0; } else { throw new AssertionError( "Unrecognized key algorithm: " + keyAlgorithm); } } /** * Returns true if the algorithm is one of the permitted algorithms, * otherwise false. */ static boolean permittedKeyAlgorithm(String keyAlgorithm, int permittedKeyAlgorithms) { if (permittedKeyAlgorithms == ANY_KEY_ALGORITHM) { return true; } else if ("DSA".equals(keyAlgorithm)) { return (permittedKeyAlgorithms & DSA_KEY_ALGORITHM) != 0; } else if ("RSA".equals(keyAlgorithm)) { return (permittedKeyAlgorithms & RSA_KEY_ALGORITHM) != 0; } else { return false; } } /** * Returns the cipher algorithm for the specified cipher suite. <p> * * The cipher algorithm is found following the first occurrence of "_WITH_" * and up to the last underscore. */ static String getCipherAlgorithm(String cipherSuite) { int start = cipherSuite.indexOf("_WITH_") + 6; if (start >= 6) { int end = cipherSuite.lastIndexOf('_'); if (end >= start) { return cipherSuite.substring(start, end); } } return "NULL"; } /** * Returns true if the cipher algorithm for the specified cipher suite is * considered a strong cipher, otherwise false. */ static boolean hasStrongCipherAlgorithm(String cipherSuite) { String cipher = getCipherAlgorithm(cipherSuite); return position(cipher, STRONG_ENCRYPTION_CIPHERS) != -1; } /** * Returns the message integrity code algorithm for the specified cipher * suite. <p> * * The message integrity algorithm is found after the last underscore. */ private static String getMICAlgorithm(String cipherSuite) { return cipherSuite.substring(cipherSuite.lastIndexOf('_')); } /** * Checks if the suite is supported by this provider. The suite can only * be supported if its security characteristics can be determined, meaning * its key exchange and cipher algorithms must be known. */ private static boolean supportedCipherSuite(String cipherSuite) { return position(getKeyExchangeAlgorithm(cipherSuite), SUPPORTED_KEY_EXCHANGE_ALGORITHMS) != -1 && position(getCipherAlgorithm(cipherSuite), SUPPORTED_ENCRYPTION_CIPHERS) != -1; } /* -- subjectString -- */ /** Returns a String that includes relevant information about a Subject */ static String subjectString(Subject subject) { if (subject == null) { return "null subject"; } else { return "Subject@" + Integer.toHexString(System.identityHashCode(subject)) + "{\n" + SubjectCredentials.credentialsString(subject) + "}"; } } /* -- safeEquals -- */ /** Same as equals(), but allows either argument to be null */ static boolean safeEquals(Object x, Object y) { return (x == null) ? y == null : x.equals(y); } /* -- contains -- */ /** * Returns true if the array contains an equal element, which may be null. */ static boolean contains(Object[] array, Object element) { for (int i = array.length; --i >= 0; ) { if (safeEquals(array[i], element)) { return true; } } return false; } /* -- toString -- */ /** Converts the contents of an Object array to a String. */ static String toString(Object[] array) { if (array == null) { return "null"; } StringBuffer buf = new StringBuffer("["); for (int i = 0; i < array.length; i++) { if (i != 0) { buf.append(", "); } buf.append(array[i]); } buf.append("]"); return buf.toString(); } /* -- equals -- */ /** * Checks if the elements of two arrays are equal. * * @param x the first array * @param y the second array * @return true if both arguments are null or both are non-null, are the * same length, and, for each array index, the elements of each * array either both null or both non-null and equal */ static boolean equals(Object[] x, Object[] y) { if (x == null) { return y == null; } else if (y == null) { return false; } else if (x.length != y.length) { return false; } else { for (int i = x.length; --i >= 0; ) { if (!safeEquals(x[i], y[i])) { return false; } } return true; } } /* -- getClassName -- */ /** * Returns the class name of an object, without the package or enclosing * class prefix. */ static String getClassName(Object object) { String className = object.getClass().getName(); className = className.substring(className.lastIndexOf('.') + 1); className = className.substring(className.lastIndexOf('$') + 1); return className; } /* -- position -- */ /** * Returns the offset of a string in an array of strings. Returns -1 if * the string is not found. */ static int position(String string, String[] list) { for (int i = list.length; --i >= 0; ) { if (string.equals(list[i])) { return i; } } return -1; } /* -- Logging -- */ /** * Logs a throw. Use this method to log a throw when the log message needs * parameters. * * @param logger logger to log to * @param level the log level * @param sourceClass class where throw occurred * @param sourceMethod name of the method where throw occurred * @param msg log message * @param params log message parameters * @param e exception thrown */ static void logThrow(Logger logger, Level level, Class sourceClass, String sourceMethod, String msg, Object[] params, Throwable e) { LogRecord r = new LogRecord(level, msg); r.setLoggerName(logger.getName()); r.setSourceClassName(sourceClass.getName()); r.setSourceMethodName(sourceMethod); r.setParameters(params); r.setThrown(e); logger.log(r); } }