View Javadoc

1   /*
2    * The MIT License
3    *
4    * Original work sponsored and donated by National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
5    *
6    * Copyright (C) 2011 National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   *
26   * $HeadURL: https://svn.softwareborsen.dk/sosi/trunk/modules/seal/src/main/java/dk/sosi/seal/ssl/SOSITrustManager.java $
27   * $Id: SOSITrustManager.java 8697 2011-09-02 10:33:55Z chg@lakeside.dk $
28   */
29  
30  package dk.sosi.seal.ssl;
31  
32  import javax.net.ssl.X509TrustManager;
33  import java.security.InvalidKeyException;
34  import java.security.NoSuchAlgorithmException;
35  import java.security.NoSuchProviderException;
36  import java.security.SignatureException;
37  import java.security.cert.CertificateEncodingException;
38  import java.security.cert.CertificateException;
39  import java.security.cert.X509Certificate;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.Iterator;
43  import java.util.List;
44  
45  /**
46   * SOSI implementation of JSSE X509TrustManager. Handles ordered chains in arbitrary direction. 
47   * Handles termination of chains by root certificate or termination by chain certificate just above the root.
48   * </p>
49   * 
50   * @author thomas@signaturgruppen.dk
51   * @author $LastChangedBy: chg@lakeside.dk $
52   * @version $Revision: 8697 $
53   * @since 1.0
54   */
55  public class SOSITrustManager implements X509TrustManager {
56  
57  	private List<X509Certificate> trustedCertificates;
58  
59  	public SOSITrustManager() {
60  		super();
61  		trustedCertificates = new ArrayList<X509Certificate>();
62  	}
63  
64  	/**
65  	 * 
66  	 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],
67  	 *      java.lang.String)
68  	 */
69  	public void checkClientTrusted(X509Certificate[] chain, String authType) {
70  	}
71  
72  
73  	/**
74  	 * 
75  	 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],
76  	 *      java.lang.String)
77  	 */
78  	public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
79  		
80  		if (!isChainValid(chain)) {
81  			throw new CertificateException("Presented chain is not valid.");
82  		}
83  
84  		if (!isTrustedCertificate(getTerminatingCertificate(chain))) {
85  			throw new CertificateException("Presented chain is not trusted");
86  		}
87  
88  	}
89  	
90  	
91  	/**
92  	 * 
93  	 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
94  	 */
95  	public X509Certificate[] getAcceptedIssuers() {
96  		return trustedCertificates.toArray(new X509Certificate[0]);
97  	}
98  	
99  	/**
100 	 * Adds a CA certificate to be trusted by the trust manager.
101 	 * @param trusted
102 	 */
103 	public void addTrustedCertificate(X509Certificate trusted) {
104 		trustedCertificates.add(trusted);
105 	}
106 
107 	
108 	private boolean isChainValid(X509Certificate[] chain) throws CertificateException {
109 		//Chains of length 1 are trivially valid
110 		if (chain.length == 1) {
111 			return true; // NOPMD
112 		}
113 
114 		boolean issuerFirst = isIssuerFirstOrdering(chain);
115 		for (int i = 0; i < chain.length - 1; i++) {
116 			chain[i].checkValidity();
117 			
118 			X509Certificate issuer;
119 			X509Certificate subject;
120 
121 			if (issuerFirst) {
122 				issuer = chain[i];
123 				subject = chain[i + 1];
124 			} else {
125 				issuer = chain[i + 1];
126 				subject = chain[i];
127 			}
128 
129 			if (!isIssuerSubjectPair(issuer, subject)) {
130 				return false;   // NOPMD
131 			}
132 
133 		}
134 
135 		return true;
136 	}
137 
138 
139 	private boolean isIssuerFirstOrdering(X509Certificate[] chain) {
140 		if(chain.length == 1) return true; //NOPMD
141 		return chain[0].getSubjectDN().equals(chain[1].getIssuerDN());
142 	}
143 
144 	private boolean isIssuerSubjectPair(X509Certificate issuer, X509Certificate subject) throws CertificateException {
145 		try {
146 			subject.verify(issuer.getPublicKey());
147 			return true; //NOPMD
148 		} catch (InvalidKeyException e) {
149 			//Thrown when signature verification fails for BouncyCastle, caught...
150 		} catch (NoSuchAlgorithmException e) {
151 			throw new RuntimeException(e);
152 		} catch (NoSuchProviderException e) {
153 			throw new RuntimeException(e);
154 		} catch (SignatureException e) { //NOPMD
155 			//Thrown when signature verification fails, caught...
156 		}
157 		return false;
158 
159 	}
160 
161 	private X509Certificate getTerminatingCertificate(X509Certificate[] chain) {
162 		if (isIssuerFirstOrdering(chain)) {
163 			return chain[0];  //NOPMD
164 		}
165 		return chain[chain.length - 1];
166 	}
167 
168 	private boolean isTrustedCertificate(X509Certificate candidate) throws CertificateException {
169 
170 		boolean isCACert = isCACertificate(candidate);
171 		for (Iterator<X509Certificate> iter = trustedCertificates.iterator(); iter.hasNext();) {
172 			X509Certificate trusted = iter.next();
173 			if (isCACert) {
174 				//The candidate certificate must be in the trusted list
175 				try {
176 					if (Arrays.equals(trusted.getEncoded(), candidate.getEncoded())) {
177 						return true;  // NOPMD
178 					}
179 				} catch (CertificateEncodingException e) {
180 					return false;  // NOPMD
181 				}
182 			} else {
183 				//The candidate must be issued under a root in the trusted list
184 				if (isIssuerSubjectPair(trusted, candidate)) {
185 					return true; // NOPMD
186 				}
187 			}
188 		}
189 
190 		return false;
191 	}
192 
193 	private boolean isCACertificate(X509Certificate candidate) {
194 		return candidate.getIssuerDN().equals(candidate.getSubjectDN());
195 	}
196 
197 }