1 /*
2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.opensaml.xml.security.x509;
18
19 import java.security.cert.X509Certificate;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23
24 import javax.security.auth.x500.X500Principal;
25
26 import org.opensaml.xml.security.SecurityException;
27 import org.opensaml.xml.util.DatatypeHelper;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32 * A basic implementaion of {@link X509CredentialNameEvaluator} which evaluates various identifiers
33 * extracted from an {@link X509Credential}'s entity certificate against a set of trusted names.
34 *
35 * <p>
36 * Supported types of entity certificate-derived names for name checking purposes are:
37 * <ol>
38 * <li>Subject alternative names.</li>
39 * <li>The first (i.e. most specific) common name (CN) from the subject distinguished name.</li>
40 * <li>The complete subject distinguished name.</li>
41 * </ol>
42 * </p>
43 *
44 * <p>
45 * Name checking is enabled by default for all of the supported name types. The types of subject alternative names to
46 * process are specified by using the appropriate constant values defined in {@link X509Util}. By default the following
47 * types of subject alternative names are checked: DNS ({@link X509Util#DNS_ALT_NAME})
48 * and URI ({@link X509Util#URI_ALT_NAME}).
49 * </p>
50 *
51 * <p>
52 * The subject distinguished name from the entity certificate is compared to the trusted key names for complete DN
53 * matching purposes by parsing each trusted key name into an {@link X500Principal} as returned by the configured
54 * instance of {@link X500DNHandler}. The resulting distinguished name is then compared with the certificate subject
55 * using {@link X500Principal#equals(Object)}. The default X500DNHandler used is {@link InternalX500DNHandler}.
56 * </p>
57 *
58 */
59 public class BasicX509CredentialNameEvaluator implements X509CredentialNameEvaluator {
60
61 /** Class logger. */
62 private final Logger log = LoggerFactory.getLogger(BasicX509CredentialNameEvaluator.class);
63
64 /** Flag as to whether to perform name checking using credential's subject alt names. */
65 private boolean checkSubjectAltNames;
66
67 /** Flag as to whether to perform name checking using credential's subject DN's common name (CN). */
68 private boolean checkSubjectDNCommonName;
69
70 /** Flag as to whether to perform name checking using credential's subject DN. */
71 private boolean checkSubjectDN;
72
73 /** The set of types of subject alternative names to process. */
74 private Set<Integer> subjectAltNameTypes;
75
76 /** Responsible for parsing and serializing X.500 names to/from {@link X500Principal} instances. */
77 private X500DNHandler x500DNHandler;
78
79 /** Constructor. */
80 public BasicX509CredentialNameEvaluator() {
81
82 x500DNHandler = new InternalX500DNHandler();
83 subjectAltNameTypes = new HashSet<Integer>(5);
84
85 // Add some defaults
86 setCheckSubjectAltNames(true);
87 setCheckSubjectDNCommonName(true);
88 setCheckSubjectDN(true);
89 subjectAltNameTypes.add(X509Util.DNS_ALT_NAME);
90 subjectAltNameTypes.add(X509Util.URI_ALT_NAME);
91 }
92
93 /**
94 * Gets whether any of the supported name type checking is currently enabled.
95 *
96 * @return true if any of the supported name type checking categories is currently enabled, false otherwise
97 */
98 public boolean isNameCheckingActive() {
99 return checkSubjectAltNames() || checkSubjectDNCommonName() || checkSubjectDN();
100 }
101
102 /**
103 * The set of types of subject alternative names to process.
104 *
105 * Name types are represented using the constant OID tag name values defined in {@link X509Util}.
106 *
107 *
108 * @return the modifiable set of alt name identifiers
109 */
110 public Set<Integer> getSubjectAltNameTypes() {
111 return subjectAltNameTypes;
112 }
113
114 /**
115 * Gets whether to check the credential's entity certificate subject alt names against the trusted key
116 * name values.
117 *
118 * @return whether to check the credential's entity certificate subject alt names against the trusted key
119 * names
120 */
121 public boolean checkSubjectAltNames() {
122 return checkSubjectAltNames;
123 }
124
125 /**
126 * Sets whether to check the credential's entity certificate subject alt names against the trusted key
127 * name values.
128 *
129 * @param check whether to check the credential's entity certificate subject alt names against the trusted
130 * key names
131 */
132 public void setCheckSubjectAltNames(boolean check) {
133 checkSubjectAltNames = check;
134 }
135
136 /**
137 * Gets whether to check the credential's entity certificate subject DN's common name (CN) against the
138 * trusted key name values.
139 *
140 * @return whether to check the credential's entity certificate subject DN's CN against the trusted key
141 * names
142 */
143 public boolean checkSubjectDNCommonName() {
144 return checkSubjectDNCommonName;
145 }
146
147 /**
148 * Sets whether to check the credential's entity certificate subject DN's common name (CN) against the
149 * trusted key name values.
150 *
151 * @param check whether to check the credential's entity certificate subject DN's CN against the trusted
152 * key names
153 */
154 public void setCheckSubjectDNCommonName(boolean check) {
155 checkSubjectDNCommonName = check;
156 }
157
158 /**
159 * Gets whether to check the credential's entity certificate subject DN against the trusted key name
160 * values.
161 *
162 * @return whether to check the credential's entity certificate subject DN against the trusted key names
163 */
164 public boolean checkSubjectDN() {
165 return checkSubjectDN;
166 }
167
168 /**
169 * Sets whether to check the credential's entity certificate subject DN against the trusted key name
170 * values.
171 *
172 * @param check whether to check the credential's entity certificate subject DN against the trusted key
173 * names
174 */
175 public void setCheckSubjectDN(boolean check) {
176 checkSubjectDN = check;
177 }
178
179 /**
180 * Get the handler which process X.500 distinguished names.
181 *
182 * Defaults to {@link InternalX500DNHandler}.
183 *
184 * @return returns the X500DNHandler instance
185 */
186 public X500DNHandler getX500DNHandler() {
187 return x500DNHandler;
188 }
189
190 /**
191 * Set the handler which process X.500 distinguished names.
192 *
193 * Defaults to {@link InternalX500DNHandler}.
194 *
195 * @param handler the new X500DNHandler instance
196 */
197 public void setX500DNHandler(X500DNHandler handler) {
198 if (handler == null) {
199 throw new IllegalArgumentException("X500DNHandler may not be null");
200 }
201 x500DNHandler = handler;
202 }
203
204 /**
205 * {@inheritDoc}
206 *
207 * <p>
208 * If the set of trusted names is null or empty, or if no supported name types are configured to be
209 * checked, then the evaluation is considered successful.
210 * </p>
211 *
212 */
213 @SuppressWarnings("unchecked")
214 public boolean evaluate(X509Credential credential, Set<String> trustedNames) throws SecurityException {
215 if (!isNameCheckingActive()) {
216 log.debug("No trusted name options are active, skipping name evaluation");
217 return true;
218 } else if (trustedNames == null || trustedNames.isEmpty()) {
219 log.debug("Supplied trusted names are null or empty, skipping name evaluation");
220 return true;
221 }
222
223 if (log.isDebugEnabled()) {
224 log.debug("Checking trusted names against credential: {}",
225 X509Util.getIdentifiersToken(credential, x500DNHandler));
226 log.debug("Trusted names being evaluated are: {}",
227 trustedNames.toString());
228 }
229 return processNameChecks(credential, trustedNames);
230 }
231
232 /**
233 * Process any name checks that are enabled.
234 *
235 * @param credential the credential for the entity to validate
236 * @param trustedNames trusted names against which the credential will be evaluated
237 * @return if true the name check succeeds, false if not
238 */
239 protected boolean processNameChecks(X509Credential credential, Set<String> trustedNames) {
240 X509Certificate entityCertificate = credential.getEntityCertificate();
241
242 if (checkSubjectAltNames()) {
243 if (processSubjectAltNames(entityCertificate, trustedNames)) {
244 if (log.isDebugEnabled()) {
245 log.debug("Credential {} passed name check based on subject alt names.",
246 X509Util.getIdentifiersToken(credential, x500DNHandler));
247 }
248 return true;
249 }
250 }
251
252 if (checkSubjectDNCommonName()) {
253 if (processSubjectDNCommonName(entityCertificate, trustedNames)) {
254 if (log.isDebugEnabled()) {
255 log.debug("Credential {} passed name check based on subject common name.",
256 X509Util.getIdentifiersToken(credential, x500DNHandler));
257 }
258 return true;
259 }
260 }
261
262 if (checkSubjectDN()) {
263 if (processSubjectDN(entityCertificate, trustedNames)) {
264 if (log.isDebugEnabled()) {
265 log.debug("Credential {} passed name check based on subject DN.",
266 X509Util.getIdentifiersToken(credential, x500DNHandler));
267 }
268 return true;
269 }
270 }
271
272 log.error("Credential failed name check: "
273 + X509Util.getIdentifiersToken(credential, x500DNHandler));
274 return false;
275 }
276
277 /**
278 * Process name checking for a certificate subject DN's common name.
279 *
280 * @param certificate the certificate to process
281 * @param trustedNames the set of trusted names
282 *
283 * @return true if the subject DN common name matches the set of trusted names, false otherwise
284 *
285 */
286 protected boolean processSubjectDNCommonName(X509Certificate certificate, Set<String> trustedNames) {
287 log.debug("Processing subject DN common name");
288 X500Principal subjectPrincipal = certificate.getSubjectX500Principal();
289 List<String> commonNames = X509Util.getCommonNames(subjectPrincipal);
290 if (commonNames == null || commonNames.isEmpty()) {
291 return false;
292 }
293 // TODO We only check the first one returned by X509Util. Maybe we should check all,
294 // if there are multiple CN AVA's from the same (first) RDN.
295 String commonName = commonNames.get(0);
296 log.debug("Extracted common name from certificate: {}", commonName);
297
298 if (DatatypeHelper.isEmpty(commonName)) {
299 return false;
300 }
301 if (trustedNames.contains(commonName)) {
302 log.debug("Matched subject DN common name to trusted names: {}", commonName);
303 return true;
304 } else {
305 return false;
306 }
307 }
308
309 /**
310 * Process name checking for the certificate subject DN.
311 *
312 * @param certificate the certificate to process
313 * @param trustedNames the set of trusted names
314 *
315 * @return true if the subject DN matches the set of trusted names, false otherwise
316 */
317 protected boolean processSubjectDN(X509Certificate certificate, Set<String> trustedNames) {
318 log.debug("Processing subject DN");
319 X500Principal subjectPrincipal = certificate.getSubjectX500Principal();
320
321 if (log.isDebugEnabled()) {
322 log.debug("Extracted X500Principal from certificate: {}", x500DNHandler.getName(subjectPrincipal));
323 }
324 for (String trustedName : trustedNames) {
325 X500Principal trustedNamePrincipal = null;
326 try {
327 trustedNamePrincipal = x500DNHandler.parse(trustedName);
328 log.debug("Evaluating principal successfully parsed from trusted name: {}", trustedName);
329 if (subjectPrincipal.equals(trustedNamePrincipal)) {
330 if (log.isDebugEnabled()) {
331 log.debug("Matched subject DN to trusted names: {}", x500DNHandler.getName(subjectPrincipal));
332 }
333 return true;
334 }
335 } catch (IllegalArgumentException e) {
336 // Do nothing, probably wasn't a distinguished name.
337 // TODO maybe try and match only the "suspected" DN values above
338 // - maybe match with regex for '='or something
339 log.debug("Trusted name was not a DN or could not be parsed: {}", trustedName);
340 continue;
341 }
342 }
343 return false;
344 }
345
346 /**
347 * Process name checking for the subject alt names within the certificate.
348 *
349 * @param certificate the certificate to process
350 * @param trustedNames the set of trusted names
351 *
352 * @return true if one of the subject alt names matches the set of trusted names, false otherwise
353 */
354 protected boolean processSubjectAltNames(X509Certificate certificate, Set<String> trustedNames) {
355 log.debug("Processing subject alt names");
356 Integer[] nameTypes = new Integer[subjectAltNameTypes.size()];
357 subjectAltNameTypes.toArray(nameTypes);
358 List altNames = X509Util.getAltNames(certificate, nameTypes);
359
360 log.debug("Extracted subject alt names from certificate: {}", altNames);
361
362 for (Object altName : altNames) {
363 if (trustedNames.contains(altName)) {
364 log.debug("Matched subject alt name to trusted names: {}", altName.toString());
365 return true;
366 }
367 }
368 return false;
369 }
370
371 }