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 032package org.apache.commons.ssl; 033 034import java.io.FileInputStream; 035import java.security.InvalidKeyException; 036import java.security.NoSuchAlgorithmException; 037import java.security.NoSuchProviderException; 038import java.security.SignatureException; 039import java.security.cert.Certificate; 040import java.security.cert.CertificateException; 041import java.security.cert.CertificateFactory; 042import java.security.cert.X509Certificate; 043import java.util.Arrays; 044import java.util.Collection; 045import java.util.Iterator; 046import 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 */ 056public 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}