/*
* 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.logging.Levels;
import java.lang.ref.WeakReference;
import java.security.GeneralSecurityException;
import java.security.KeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.net.ssl.X509KeyManager;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
/**
* Implements X509KeyManager by getting its key and credential information from
* a JAAS subject, and X509TrustManager by using FilterX509TrustManager.
* Combines both the key and trust managers to enable subclasses to use
* information for both when checking permissions.
*
* @author Sun Microsystems, Inc.
*/
abstract class AuthManager extends FilterX509TrustManager
implements X509KeyManager
{
/* -- Fields -- */
/**
* The Subject from which to retrieve principals and credentials. Use a
* weak reference to insure that the subject can be GC'ed if it is only
* being referenced by the SSLContextMap that maps subjects to
* AuthManagers. The weak key map won't be weak if the value contains a
* strong reference to the key.
*/
private final WeakReference subjectRef;
/**
* Whether the subject was read-only when it was stored -- used to avoid
* checking for changes in the contents of the subject.
*/
final boolean subjectIsReadOnly;
/**
* If non-null, then only principals in this set may be used to
* authenticate the local end of the connection.
*/
private final Set permittedLocalPrincipals;
/* -- Constructors -- */
/**
* Creates an AuthManager that retrieves principals and credentials for
* authentication from the specified subject. If permittedLocalPrincipals
* or permittedRemotePrincipals are set, then only those principals may be
* used for authentication.
*
* @param subject the subject for retrieving principals and credentials
* @param permittedLocalPrincipals if non-null, then only principals in
* this set may be used to authenticate the local end of the
* connection
* @param permittedRemotePrincipals if non-null, then only principals in
* this set will be trusted to authenticate the peer
* @throws NoSuchAlgorithmException if the trust manager factory algorithm
* is not found
*/
AuthManager(Subject subject,
Set permittedLocalPrincipals,
Set permittedRemotePrincipals)
throws NoSuchAlgorithmException
{
super(permittedRemotePrincipals);
subjectRef = new WeakReference(subject);
subjectIsReadOnly = (subject == null) || subject.isReadOnly();
this.permittedLocalPrincipals = (permittedLocalPrincipals == null)
? null : new HashSet(permittedLocalPrincipals);
}
/* -- Methods -- */
/**
* Gets the private credential for the specified X.509 certificate.
*
* @param cert the X.509 certificate
* @return the associated X500PrivateCredential or null if it is not found
* @throws SecurityException if the current access control context does not
* have the proper AuthenticationPermission
*/
abstract X500PrivateCredential getPrivateCredential(X509Certificate cert);
/** Returns the logger to use for logging. */
abstract Logger getLogger();
/**
* Returns the Subject, or null if the subject was null or if the weak
* reference has been cleared.
*/
Subject getSubject() {
return (Subject) subjectRef.get();
}
/**
* Returns all the aliases that match the specified key type and issuers
* for which checkChain succeeds. Returns null if no matching aliases are
* found.
*/
String[] getAliases(String keyType, Principal[] issuers) {
List certPaths = SubjectCredentials.getCertificateChains(getSubject());
if (certPaths == null) {
return null;
}
Collection result = null;
for (int i = certPaths.size(); --i >= 0; ) {
CertPath chain = (CertPath) certPaths.get(i);
Exception exception;
try {
if (checkChain(chain, keyType, issuers) != null) {
if (result == null) {
result = new ArrayList(certPaths.size());
}
result.add(
SubjectCredentials.getCertificateName(
firstX509Cert(chain)));
}
continue;
} catch (GeneralSecurityException e) {
exception = e;
} catch (SecurityException e) {
exception = e;
}
Logger logger = getLogger();
if (logger.isLoggable(Levels.HANDLED)) {
logThrow(logger, Levels.HANDLED,
AuthManager.class, "getAliases",
"get aliases for key type {0}\nand issuers {1}\n" +
"caught exception",
new Object[] { keyType, toString(issuers) },
exception);
}
}
if (result == null) {
return null;
} else {
return (String[]) result.toArray(new String[result.size()]);
}
}
/**
* Returns a private credential that matches the specified key type and
* issuers for which checkChain returns a non-null value, or null if no
* matching credentials are found. Throws a GeneralSecurityException or
* SecurityException if a problem occurs with all matching credentials.
*/
X500PrivateCredential chooseCredential(String keyType,
Principal[] issuers)
throws GeneralSecurityException
{
List certPaths = SubjectCredentials.getCertificateChains(getSubject());
if (certPaths == null) {
return null;
}
List exceptions = null;
for (int i = certPaths.size(); --i >= 0; ) {
CertPath chain = (CertPath) certPaths.get(i);
Exception exception;
try {
X500PrivateCredential pc = checkChain(chain, keyType, issuers);
if (pc == null) {
continue;
} else {
return pc;
}
} catch (GeneralSecurityException e) {
exception = e;
} catch (SecurityException e) {
exception = e;
}
if (exceptions == null) {
exceptions = new ArrayList();
}
exceptions.add(exception);
Logger logger = getLogger();
if (logger.isLoggable(Levels.HANDLED)) {
logThrow(logger, Levels.HANDLED,
AuthManager.class, "chooseCredential",
"choose credential for key type {0}\n" +
"and issuers {1}\ncaught exception",
new Object[] { keyType, toString(issuers) },
exception);
}
}
if (exceptions == null) {
return null;
} else if (exceptions.size() > 1) {
for (int i = exceptions.size(); --i >= 0; ) {
Exception e = (Exception) exceptions.get(i);
if (!(e instanceof SecurityException)) {
throw new GeneralSecurityException(exceptions.toString());
}
}
throw new SecurityException(exceptions.toString());
} else if (exceptions.get(0) instanceof SecurityException) {
throw (SecurityException) exceptions.get(0);
} else {
throw (GeneralSecurityException) exceptions.get(0);
}
}
/**
* Checks if the specified certificate chain can be used for keys of the
* specified type and with the specified issuers. Returns null if the
* chain has the wrong key type, throws an exception if the credentials or
* subject has problems, and otherwise returns the associated private
* credential. <p>
*
* Checks that:
* <ul>
* <li> The key algorithm matches that of the certificate's public key
* <li> The subject contains the principal for the certificate's issuer DN
* <li> That principal is a permitted local principal
* <li> The certificate chain terminates with one of the issuers, if issuers
* are specified
* <li> The certificate chain is currently valid
* <li> If the certificate specifies a KeyUsage extension, that the
* extension permits use in digital signatures
* <li> The caller has the proper AuthenticationPermission
* </ul> <p>
*
* Because the following things should only occur because of a
* configuration problem, this method does not check for:
* <ul>
* <li> Gaps in certificate chains
* <li> CA extensions
* <li> Incorrect private key
* </ul>
*/
private X500PrivateCredential checkChain(CertPath chain,
String keyType,
Principal[] issuers)
throws GeneralSecurityException
{
X509Certificate head = firstX509Cert(chain);
String certKeyType = head.getPublicKey().getAlgorithm();
if (!certKeyType.equals(keyType)) {
return null;
}
Subject subject = getSubject();
X500Principal principal =
SubjectCredentials.getPrincipal(subject, head);
if (principal == null) {
throw new GeneralSecurityException(
"Principal not found: " + head.getSubjectDN());
} else if (permittedLocalPrincipals != null
&& !permittedLocalPrincipals.contains(principal))
{
throw new GeneralSecurityException(
"Local principal not permitted: " + head.getSubjectDN());
}
X500Principal[] x500Issuers = null;
if (issuers != null) {
x500Issuers = new X500Principal[issuers.length];
for (int i = issuers.length; --i >= 0; ) {
x500Issuers[i] = (issuers[i] instanceof X500Principal)
? (X500Principal) issuers[i]
: new X500Principal(issuers[i].getName());
}
}
checkValidity(chain, x500Issuers);
/*
* Check that a critical key usage extension, if present, permits use
* for digital signatures.
*/
boolean[] keyUsage = head.getKeyUsage();
if (keyUsage != null
&& keyUsage.length > 0
/* Element 0 is for digitalSignature */
&& !keyUsage[0])
{
throw new CertificateException(
"Certificate not permitted for digital signatures: " + head);
}
/* Also throws SecurityException */
X500PrivateCredential xpc = getPrivateCredential(head);
if (xpc == null) {
throw new KeyException(
"Private key not found for certificate: " + head);
}
return xpc;
}
/**
* Returns the latest time for which all of the X.509 certificates in the
* certificate chain are valid.
*/
static long certificatesValidUntil(CertPath chain) {
long result = Long.MAX_VALUE;
List certs = chain.getCertificates();
for (int i = certs.size(); --i >= 0; ) {
X509Certificate cert = (X509Certificate) certs.get(i);
long until = cert.getNotAfter().getTime();
if (until < result) {
result = until;
}
}
return result;
}
/**
* Returns the latest time for which all of the X.509 certificates in the
* certificate chain are valid.
*/
static long certificatesValidUntil(X509Certificate[] chain) {
long result = Long.MAX_VALUE;
for (int i = chain.length; --i >= 0; ) {
X509Certificate cert = chain[i];
long until = cert.getNotAfter().getTime();
if (until < result) {
result = until;
}
}
return result;
}
/**
* Checks if the two private credentials refer to the same principal and
* have the equivalent private key.
*/
boolean equalPrivateCredentials(X500PrivateCredential cred1,
X500PrivateCredential cred2)
{
if (cred1 == null || cred2 == null) {
return false;
}
X509Certificate cert1 = cred1.getCertificate();
X509Certificate cert2 = cred2.getCertificate();
if (cert1 == null
|| cert2 == null
|| !safeEquals(cert1.getSubjectDN(), cert2.getSubjectDN()))
{
return false;
}
/*
* I'm assuming I can depend on the equals method for private keys to
* check if the two objects represent the same key without being
* identical objects. Although that behavior isn't documented, at
* least the sun.security.pkcs.PKCS8Key class does that.
* -tjb[8.Jan.2001]
*/
PrivateKey key1 = cred1.getPrivateKey();
return key1 != null && key1.equals(cred2.getPrivateKey());
}
/* -- Implement X509KeyManager -- */
public X509Certificate[] getCertificateChain(String alias) {
CertPath chain =
SubjectCredentials.getCertificateChain(getSubject(), alias);
List certs = chain.getCertificates();
return (X509Certificate[]) certs.toArray(
new X509Certificate[certs.size()]);
}
public PrivateKey getPrivateKey(String alias) {
CertPath chain =
SubjectCredentials.getCertificateChain(getSubject(), alias);
if (chain != null) {
try {
X500PrivateCredential xpc =
getPrivateCredential(firstX509Cert(chain));
if (xpc != null) {
return xpc.getPrivateKey();
}
} catch (SecurityException e) {
Logger logger = getLogger();
if (logger.isLoggable(Levels.HANDLED)) {
logThrow(logger, Levels.HANDLED,
AuthManager.class, "getPrivateKey",
"get private key for alias {0}\n" +
"caught exception",
new Object[] { alias },
e);
}
}
}
return null;
}
}