View Javadoc

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 }