/* * 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 com.sun.jini.discovery.internal; import com.sun.jini.discovery.DatagramBufferFactory; import com.sun.jini.logging.Levels; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UTFDataFormatException; import java.net.MalformedURLException; import java.net.URL; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.security.AccessControlContext; import java.security.AccessController; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.CertStore; import java.security.cert.CertStoreParameters; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.LDAPCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.security.auth.AuthPermission; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; import net.jini.io.UnsupportedConstraintException; import net.jini.security.AuthenticationPermission; /** * Superclass for providers for the net.jini.discovery.x500.* discovery * formats. */ class X500Provider extends BaseProvider { private static final String NAME = "com.sun.jini.discovery.x500"; private static final String JSSE = "javax.net.ssl"; private static final int INT_LEN = 4; private static final Pattern hostPortPattern = Pattern.compile("^(.+):(\\d+?)$"); private static final AuthPermission authPermission = new AuthPermission("getSubject"); static final Logger logger = Logger.getLogger(NAME); /** The signature algorithm (for example, "SHA1withDSA"). */ protected final String signatureAlgorithm; /** The maximum length of generated signatures, in bytes. */ protected final int maxSignatureLength; /** The key algorithm name (for example, "DSA"). */ protected final String keyAlgorithm; /** The key algorithm OID. */ protected final String keyAlgorithmOID; private KeyStore trustStore = null; private CertStore[] certStores = null; private final Object storeLock = new Object(); /** * Creates an instance with the given attributes. */ X500Provider(String formatName, String signatureAlgorithm, int maxSignatureLength, String keyAlgorithm, String keyAlgorithmOID) { super(formatName); if (maxSignatureLength < 0) { throw new IllegalArgumentException(); } if (keyAlgorithm == null || keyAlgorithmOID == null) { throw new NullPointerException(); } this.signatureAlgorithm = signatureAlgorithm; this.maxSignatureLength = maxSignatureLength; this.keyAlgorithm = keyAlgorithm; this.keyAlgorithmOID = keyAlgorithmOID; } /** * Returns certificate corresponding to the given principal, or null if no * matching certificate can be found. Subclasses can override this method * to customize the certificate search mechanism. * <p> * The default implementation of this method does the following: the first * time this method is called on this instance, a keystore containing trust * anchors for the certificate to return is loaded. The location of the * file to load the keystore from can be specified (in order of precedence) * by the com.sun.jini.discovery.x500.trustStore and * javax.net.ssl.trustStore system properties; if no location is specified, * then the cacerts file in the lib/security subdirectory of the JDK * installation directory is used. If specified, the location is treated as * a URL. If no protocol is specified in the URL or it is an unknown * protocol, then, the location is treated as a file name. * Depending on which system property is used to specify the keystore * location, the com.sun.jini.discovery.x500.trustStoreType and * com.sun.jini.discovery.x500.trustStorePassword or * javax.net.ssl.trustStoreType and javax.net.ssl.trustStorePassword system * properties can be used to specify the type of the keystore and the * password to use when loading it. If no keystore type is specified, then * the type returned by KeyStore.getDefaultType() is used; if no password * is specified, then no password is used when loading the keystore. * Additionally, if the com.sun.jini.discovery.x500.ldapCertStores system * property is set, its value is interpreted as a comma-separated list of * "host[:port]" elements which are used to obtain references to LDAP-based * CertStore instances. * <p> * For each call, the default implementation of this method creates a PKIX * CertPathBuilder and calls its build method, passing as the argument a * PKIXBuilderParameters instance initialized with the aforementioned * keystore, CertStores (if any), and a CertSelector based on the provided * X.500 principal and the key algorithm OID for this instance. If the * build operation succeeds, the resulting certificate is returned. */ protected Certificate getCertificate(final X500Principal principal) throws IOException, GeneralSecurityException { try { return (Certificate) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws IOException, GeneralSecurityException { return getCertificate0(principal); } }); } catch (PrivilegedActionException e) { Throwable t = e.getCause(); if (t instanceof IOException) { throw (IOException) t; } else { throw (GeneralSecurityException) t; } } } /** * Returns non-null array containing the usable X.500 private credentials * of the current subject (if any). This method does not check that the * caller has AuthenticationPermission to use the credentials. */ X500PrivateCredential[] getPrivateCredentials() { final AccessControlContext acc = AccessController.getContext(); Collection[] subjInfo = (Collection[]) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { Subject s = Subject.getSubject(acc); // filter principals & credentials manually, due to 4892841 return (s != null) ? new Collection[]{ syncGetInstances( s.getPrincipals(), X500Principal.class), syncGetInstances( s.getPrivateCredentials(), X500PrivateCredential.class) } : new Collection[]{ Collections.EMPTY_SET, Collections.EMPTY_SET }; } }); Collection ppals = subjInfo[0]; Collection creds = subjInfo[1]; List l = new ArrayList(); for (Iterator i = creds.iterator(); i.hasNext(); ) { X500PrivateCredential cred = (X500PrivateCredential) i.next(); X509Certificate cert = cred.getCertificate(); try { checkCertificate(cert); } catch (CertificateException e) { logger.log(Levels.HANDLED, "invalid certificate", e); continue; } if (keyAlgorithm.equals(cred.getPrivateKey().getAlgorithm()) && ppals.contains(cert.getSubjectX500Principal())) { l.add(cred); } } if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "obtained private credentials {0}", new Object[]{ l }); } return (X500PrivateCredential[]) l.toArray( new X500PrivateCredential[l.size()]); } /** * Test whether the caller has AuthPermission("getSubject"). * * @return true if the caller has AuthPermission("getSubject"), * false otherwise */ private static boolean canGetSubject() { try { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(authPermission); } return true; } catch (SecurityException e) { return false; } } /** * Only throw non-generic exception if caller has getSubject * permission. * * @param detailedException the real * <code>SecurityException</code> to be thrown if caller * has the "getSubject" <code>AuthPermission</code> * @param genericException the generic * <code>UnsupportedConstraintException</code> to be thrown * if caller does not have the "getSubject" * <code>AuthPermission</code> */ static void secureThrow(SecurityException detailedException, UnsupportedConstraintException genericException) throws UnsupportedConstraintException { if (canGetSubject()) { // has "getSubject" permission throw detailedException; } else { throw genericException; } } /** * If a security manager is installed, checks that the calling context has * AuthenticationPermission for the given principal and action (with no * peer principal specified). */ void checkAuthenticationPermission(X500Principal principal, String action) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new AuthenticationPermission( Collections.singleton(principal), null, action)); } } /** * Returns true if the sig buffer contains the signature of the contents of * the data buffer; returns false otherwise. The passed in buffers will be * modified in case they do not have a backing array. */ boolean verify(ByteBuffer data, ByteBuffer sig, PublicKey key) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException { data = ensureArrayBacking(data); sig = ensureArrayBacking(sig); Signature s = getSignature(); s.initVerify(key); s.update( data.array(), data.arrayOffset() + data.position(), data.remaining()); return s.verify( sig.array(), sig.arrayOffset() + sig.position(), sig.remaining()); } /** * Main body of getCertificate(), called from within a doPrivileged block. */ private Certificate getCertificate0(X500Principal principal) throws IOException, GeneralSecurityException { synchronized (storeLock) { if (trustStore == null) { initStores(); } } X509CertSelector selector = new X509CertSelector(); selector.setSubject(principal.getName()); selector.setSubjectPublicKeyAlgID(keyAlgorithmOID); selector.setCertificateValid(new Date()); // element 0 of keyUsage array is for digital signatures selector.setKeyUsage(new boolean[]{ true }); PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore, selector); for (int j = 0; j < certStores.length; j++) { params.addCertStore(certStores[j]); } PKIXCertPathBuilderResult result; try { result = (PKIXCertPathBuilderResult) CertPathBuilder.getInstance("PKIX").build(params); } catch (CertPathBuilderException e) { logger.log(Levels.HANDLED, "exception building certificate path", e); return null; } List certs = result.getCertPath().getCertificates(); return certs.isEmpty() ? result.getTrustAnchor().getTrustedCert() : (Certificate) certs.get(0); } /** * Initializes trust store and cert stores based on system property values. */ private void initStores() throws IOException, GeneralSecurityException { String path, type, passwd; if ((path = System.getProperty(NAME + ".trustStore")) != null) { type = System.getProperty( NAME + ".trustStoreType", KeyStore.getDefaultType()); passwd = System.getProperty(NAME + ".trustStorePassword"); } else if ((path = System.getProperty(JSSE + ".trustStore")) != null) { type = System.getProperty( JSSE + ".trustStoreType", KeyStore.getDefaultType()); passwd = System.getProperty(JSSE + ".trustStorePassword"); } else { path = System.getProperty("java.home") + "/lib/security/cacerts"; type = KeyStore.getDefaultType(); passwd = null; } KeyStore kstore = KeyStore.getInstance(type); InputStream in; URL url = null; try { url = new URL(path); } catch (MalformedURLException e) { } if (url != null) { in = url.openStream(); } else { in = new FileInputStream(path); } try { kstore.load(in, (passwd != null) ? passwd.toCharArray() : null); } finally { in.close(); } if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "loaded trust store from {0} ({1})", new Object[]{ path, type }); } String cstores = System.getProperty(NAME + ".ldapCertStores"); List l = new ArrayList(); if (cstores != null) { StringTokenizer tok = new StringTokenizer(cstores, ","); while (tok.hasMoreTokens()) { String s = tok.nextToken().trim(); Matcher m = hostPortPattern.matcher(s); try { CertStoreParameters params = m.matches() ? new LDAPCertStoreParameters( m.group(1), Integer.parseInt(m.group(2))) : new LDAPCertStoreParameters(s); l.add(CertStore.getInstance("LDAP", params)); } catch (Exception e) { logger.log(Level.WARNING, "exception initializing cert store", e); } } } if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "using cert stores {0}", new Object[]{ l }); } certStores = (CertStore[]) l.toArray(new CertStore[l.size()]); trustStore = kstore; } /** * Returns newly obtained Signature implementing the signature algorithm * for this instance. */ private Signature getSignature() throws NoSuchAlgorithmException { return Signature.getInstance(signatureAlgorithm); } /** * Returns a new collection containing all instances of the specified class * contained in the given collection. All operations on the given * collection are performed while synchronized on the collection. */ private static Collection syncGetInstances(Collection coll, Class cl) { synchronized (coll) { Collection c = new ArrayList(coll.size()); for (Iterator i = coll.iterator(); i.hasNext(); ) { Object obj = i.next(); if (cl.isInstance(obj)) { c.add(obj); } } return c; } } /** * Throws a CertificateException if the given certificate is not currently * valid, or specifies a KeyUsage extension which prohibits use in digital * signatures. */ private static void checkCertificate(X509Certificate cert) throws CertificateException { cert.checkValidity(); boolean[] keyUsage = cert.getKeyUsage(); // element 0 of keyUsage array is for digital signatures if (keyUsage != null && keyUsage.length > 0 && !keyUsage[0]) { throw new CertificateException( "certificate not permitted for digital signatures: " + cert); } } /** * Returns given buffer if it is backed by an array; otherwise, returns a * newly created array-backed buffer into which the remaining contents of * the given buffer have been transferred. */ private static ByteBuffer ensureArrayBacking(ByteBuffer buf) { return buf.hasArray() ? buf : (ByteBuffer) ByteBuffer.allocate(buf.remaining()).put(buf).flip(); } /** * Buffer factory which signs data written into the buffers it dispenses. */ class SigningBufferFactory implements DatagramBufferFactory { private final List buffers = new ArrayList(); private final DatagramBufferFactory factory; private final byte[] principalName; private final Signature signature; SigningBufferFactory(DatagramBufferFactory factory, X500PrivateCredential cred) throws InvalidKeyException, UTFDataFormatException, NoSuchAlgorithmException { this.factory = factory; principalName = Plaintext.toUtf( cred.getCertificate().getSubjectX500Principal().getName()); signature = getSignature(); signature.initSign(cred.getPrivateKey()); } public ByteBuffer newBuffer() { BufferInfo bi = new BufferInfo(factory.newBuffer()); buffers.add(bi); return bi.getDataBuffer(); } public void sign() throws SignatureException { for (Iterator i = buffers.iterator(); i.hasNext(); ) { ((BufferInfo) i.next()).sign(); } } private class BufferInfo { private final ByteBuffer buf; private final ByteBuffer data; private final boolean overflow; BufferInfo(ByteBuffer buf) { this.buf = buf; data = buf.duplicate(); int authBlockLen = principalName.length + maxSignatureLength; if (data.remaining() >= INT_LEN + authBlockLen) { data.position(data.position() + INT_LEN); data.limit(data.limit() - authBlockLen); overflow = false; } else { data.limit(data.position()); overflow = true; } } ByteBuffer getDataBuffer() { return data; } void sign() throws SignatureException { if (overflow) { throw new BufferOverflowException(); } buf.putInt(data.position() - (buf.position() + INT_LEN)); buf.position(data.position()); buf.put(principalName); ByteBuffer b = ensureArrayBacking((ByteBuffer) data.duplicate().flip()); signature.update( b.array(), b.arrayOffset() + b.position(), b.remaining()); buf.put(signature.sign()); } } } }