001    /*
002     * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.9/src/java/org/apache/commons/ssl/X509CertificateChainBuilder.java $
003     * $Revision: 121 $
004     * $Date: 2007-11-13 21:26:57 -0800 (Tue, 13 Nov 2007) $
005     *
006     * ====================================================================
007     * Licensed to the Apache Software Foundation (ASF) under one
008     * or more contributor license agreements.  See the NOTICE file
009     * distributed with this work for additional information
010     * regarding copyright ownership.  The ASF licenses this file
011     * to you under the Apache License, Version 2.0 (the
012     * "License"); you may not use this file except in compliance
013     * with the License.  You may obtain a copy of the License at
014     *
015     *   http://www.apache.org/licenses/LICENSE-2.0
016     *
017     * Unless required by applicable law or agreed to in writing,
018     * software distributed under the License is distributed on an
019     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020     * KIND, either express or implied.  See the License for the
021     * specific language governing permissions and limitations
022     * under the License.
023     * ====================================================================
024     *
025     * This software consists of voluntary contributions made by many
026     * individuals on behalf of the Apache Software Foundation.  For more
027     * information on the Apache Software Foundation, please see
028     * <http://www.apache.org/>.
029     *
030     */
031    
032    package org.apache.commons.ssl;
033    
034    import java.io.FileInputStream;
035    import java.security.InvalidKeyException;
036    import java.security.NoSuchAlgorithmException;
037    import java.security.NoSuchProviderException;
038    import java.security.SignatureException;
039    import java.security.cert.Certificate;
040    import java.security.cert.CertificateException;
041    import java.security.cert.CertificateFactory;
042    import java.security.cert.X509Certificate;
043    import java.util.Arrays;
044    import java.util.Collection;
045    import java.util.Iterator;
046    import java.util.LinkedList;
047    
048    /**
049     * Utility for building X509 certificate chains.
050     *
051     * @author Credit Union Central of British Columbia
052     * @author <a href="http://www.cucbc.com/">www.cucbc.com</a>
053     * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a>
054     * @since 16-Nov-2005
055     */
056    public class X509CertificateChainBuilder {
057        /**
058         * Builds the ordered certificate chain upwards from the startingPoint.
059         * Uses the supplied X509Certificate[] array to search for the parent,
060         * grandparent, and higher ancestor certificates.  Stops at self-signed
061         * certificates, or when no ancestor can be found.
062         * <p/>
063         * Thanks to Joe Whitney for helping me put together a Big-O( m * n )
064         * implementation where m = the length of the final certificate chain.
065         * For a while I was using a Big-O( n ^ 2 ) implementation!
066         *
067         * @param startingPoint the X509Certificate for which we want to find
068         *                      ancestors
069         * @param certificates  A pool of certificates in which we expect to find
070         *                      the startingPoint's ancestors.
071         * @return Array of X509Certificates, starting with the "startingPoint" and
072         *         ending with highest level ancestor we could find in the supplied
073         *         collection.
074         * @throws java.security.NoSuchAlgorithmException
075         *          on unsupported signature
076         *          algorithms.
077         * @throws java.security.InvalidKeyException
078         *          on incorrect key.
079         * @throws java.security.NoSuchProviderException
080         *          if there's no default provider.
081         * @throws java.security.cert.CertificateException
082         *          on encoding errors.
083         */
084        public static X509Certificate[] buildPath(X509Certificate startingPoint,
085                                                  Certificate[] certificates)
086            throws NoSuchAlgorithmException, InvalidKeyException,
087            NoSuchProviderException, CertificateException {
088            // Use a LinkedList, because we do lots of random it.remove() operations.
089            return buildPath(startingPoint,
090                new LinkedList(Arrays.asList(certificates)));
091        }
092    
093        /**
094         * Builds the ordered certificate chain upwards from the startingPoint.
095         * Uses the supplied collection to search for the parent, grandparent,
096         * and higher ancestor certificates.  Stops at self-signed certificates,
097         * or when no ancestor can be found.
098         * <p/>
099         * Thanks to Joe Whitney for helping me put together a Big-O( m * n )
100         * implementation where m = the length of the final certificate chain.
101         * For a while I was using a Big-O( n ^ 2 ) implementation!
102         *
103         * @param startingPoint the X509Certificate for which we want to find
104         *                      ancestors
105         * @param certificates  A pool of certificates in which we expect to find
106         *                      the startingPoint's ancestors.
107         * @return Array of X509Certificates, starting with the "startingPoint" and
108         *         ending with highest level ancestor we could find in the supplied
109         *         collection.
110         * @throws java.security.NoSuchAlgorithmException
111         *          on unsupported signature
112         *          algorithms.
113         * @throws java.security.InvalidKeyException
114         *          on incorrect key.
115         * @throws java.security.NoSuchProviderException
116         *          if there's no default provider.
117         * @throws java.security.cert.CertificateException
118         *          on encoding errors.
119         */
120        public static X509Certificate[] buildPath(X509Certificate startingPoint,
121                                                  Collection certificates)
122            throws NoSuchAlgorithmException, InvalidKeyException,
123            NoSuchProviderException, CertificateException {
124            LinkedList path = new LinkedList();
125            path.add(startingPoint);
126            boolean nodeAdded = true;
127            // Keep looping until an iteration happens where we don't add any nodes
128            // to our path.
129            while (nodeAdded) {
130                // We'll start out by assuming nothing gets added.  If something
131                // gets added, then nodeAdded will be changed to "true".
132                nodeAdded = false;
133                X509Certificate top = (X509Certificate) path.getLast();
134                try {
135                    top.verify(top.getPublicKey());
136                    // We're self-signed, so we're done!
137                    break;
138                }
139                catch (SignatureException se) {
140                    // Not self-signed.  Let's see if we're signed by anyone in the
141                    // collection.
142                    Iterator it = certificates.iterator();
143                    while (it.hasNext()) {
144                        X509Certificate x509 = (X509Certificate) it.next();
145                        try {
146                            top.verify(x509.getPublicKey());
147                            // No exception thrown, so we're signed by this guy!
148                            path.add(x509);
149                            nodeAdded = true;
150                            it.remove(); // Not interested in this guy anymore!
151                            break;
152                        }
153                        catch (SignatureException se2) {
154                            // Not signed by this guy, let's try the next guy.
155                        }
156                    }
157                }
158            }
159    
160            X509Certificate[] results = new X509Certificate[path.size()];
161            path.toArray(results);
162            return results;
163        }
164    
165        public static void main(String[] args) throws Exception {
166            if (args.length < 2) {
167                System.out.println("Usage: [special-one] [file-with-certs]");
168                System.exit(1);
169            }
170            FileInputStream f1 = new FileInputStream(args[0]);
171            FileInputStream f2 = new FileInputStream(args[1]);
172            CertificateFactory cf = CertificateFactory.getInstance("X.509");
173            X509Certificate theOne = (X509Certificate) cf.generateCertificate(f1);
174            Collection c = cf.generateCertificates(f2);
175    
176            X509Certificate[] path = buildPath(theOne, c);
177            for (int i = 0; i < path.length; i++) {
178                System.out.println(Certificates.getCN(path[i]));
179            }
180        }
181    }