View Javadoc

1   /*
2    * Licensed to the University Corporation for Advanced Internet Development, 
3    * Inc. (UCAID) under one or more contributor license agreements.  See the 
4    * NOTICE file distributed with this work for additional information regarding
5    * copyright ownership. The UCAID licenses this file to You under the Apache 
6    * License, Version 2.0 (the "License"); you may not use this file except in 
7    * compliance with the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package edu.internet2.middleware.shibboleth.common.security;
19  
20  import java.lang.ref.SoftReference;
21  import java.security.cert.CRLException;
22  import java.security.cert.CertificateException;
23  import java.security.cert.X509CRL;
24  import java.security.cert.X509Certificate;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.concurrent.locks.Lock;
32  import java.util.concurrent.locks.ReadWriteLock;
33  import java.util.concurrent.locks.ReentrantReadWriteLock;
34  
35  import javax.xml.namespace.QName;
36  
37  import org.opensaml.saml2.common.Extensions;
38  import org.opensaml.saml2.metadata.EntitiesDescriptor;
39  import org.opensaml.saml2.metadata.EntityDescriptor;
40  import org.opensaml.saml2.metadata.KeyDescriptor;
41  import org.opensaml.saml2.metadata.RoleDescriptor;
42  import org.opensaml.saml2.metadata.provider.MetadataProvider;
43  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
44  import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider;
45  import org.opensaml.security.MetadataCriteria;
46  import org.opensaml.xml.XMLObject;
47  import org.opensaml.xml.security.CriteriaSet;
48  import org.opensaml.xml.security.SecurityException;
49  import org.opensaml.xml.security.credential.UsageType;
50  import org.opensaml.xml.security.criteria.EntityIDCriteria;
51  import org.opensaml.xml.security.criteria.UsageCriteria;
52  import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
53  import org.opensaml.xml.security.x509.BasicPKIXValidationInformation;
54  import org.opensaml.xml.security.x509.PKIXValidationInformation;
55  import org.opensaml.xml.security.x509.PKIXValidationInformationResolver;
56  import org.opensaml.xml.signature.KeyInfo;
57  import org.opensaml.xml.signature.KeyName;
58  import org.opensaml.xml.util.DatatypeHelper;
59  import org.opensaml.xml.util.LazySet;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  import edu.internet2.middleware.shibboleth.common.xmlobject.ShibbolethMetadataKeyAuthority;
64  
65  /**
66   * An implementation of {@link PKIXValidationInformationResolver} which resolves {@link PKIXValidationInformation} based
67   * on information stored in SAML 2 metadata. Validation information is retrieved from Shibboleth-specific metadata
68   * extensions to {@link EntityDescriptor} and {@link EntitiesDescriptor} elements, represented by instances of
69   * {@link ShibbolethMetadataKeyAuthority}.
70   * 
71   * Resolution of trusted names for an entity is also supported, based on {@link KeyName} information contained within
72   * the {@link KeyInfo} of a role descriptor's {@link KeyDescriptor} element.
73   */
74  public class MetadataPKIXValidationInformationResolver implements PKIXValidationInformationResolver {
75  
76      /** Default value for Shibboleth KeyAuthority verify depth. */
77      public static final int KEY_AUTHORITY_VERIFY_DEPTH_DEFAULT = 1;
78      
79      /** Class logger. */
80      private final Logger log = LoggerFactory.getLogger(MetadataPKIXValidationInformationResolver.class);
81  
82      /** Metadata provider from which to fetch the credentials. */
83      private MetadataProvider metadata;
84  
85      /** Cache of resolved info. [MetadataCacheKey, Credentials] */
86      private Map<MetadataCacheKey, SoftReference<List<PKIXValidationInformation>>> entityPKIXCache;
87  
88      /** Cache of resolved info. [Extensions, Credentials] */
89      private Map<Extensions, SoftReference<List<PKIXValidationInformation>>> extensionsCache;
90  
91      /** Cache of resolved info. [MetadataCacheKey, Strings(trusted key names)] */
92      private Map<MetadataCacheKey, SoftReference<Set<String>>> entityNamesCache;
93      
94      /** Lock used to synchronize access to the caches. */
95      private ReadWriteLock rwlock;
96  
97      /**
98       * Constructor.
99       * 
100      * @param metadataProvider provider of the metadata
101      * 
102      * @throws IllegalArgumentException thrown if the supplied provider is null
103      */
104     public MetadataPKIXValidationInformationResolver(MetadataProvider metadataProvider) {
105         super();
106         if (metadataProvider == null) {
107             throw new IllegalArgumentException("Metadata provider may not be null");
108         }
109         metadata = metadataProvider;
110 
111         entityPKIXCache = new HashMap<MetadataCacheKey, SoftReference<List<PKIXValidationInformation>>>();
112         extensionsCache = new HashMap<Extensions, SoftReference<List<PKIXValidationInformation>>>();
113         entityNamesCache = new HashMap<MetadataCacheKey, SoftReference<Set<String>>>();
114         
115         rwlock = new ReentrantReadWriteLock();
116 
117         if (metadata instanceof ObservableMetadataProvider) {
118             ObservableMetadataProvider observable = (ObservableMetadataProvider) metadataProvider;
119             observable.getObservers().add(new MetadataProviderObserver());
120         }
121 
122     }
123 
124     /** {@inheritDoc} */
125     public PKIXValidationInformation resolveSingle(CriteriaSet criteriaSet) throws SecurityException {
126         Iterable<PKIXValidationInformation> pkixInfo = resolve(criteriaSet);
127         if (pkixInfo.iterator().hasNext()) {
128             return pkixInfo.iterator().next();
129         } else {
130             return null;
131         }
132     }
133 
134     /** {@inheritDoc} */
135     public Iterable<PKIXValidationInformation> resolve(CriteriaSet criteriaSet) throws SecurityException {
136 
137         checkCriteriaRequirements(criteriaSet);
138 
139         String entityID = criteriaSet.get(EntityIDCriteria.class).getEntityID();
140         MetadataCriteria mdCriteria = criteriaSet.get(MetadataCriteria.class);
141         QName role = mdCriteria.getRole();
142         String protocol = mdCriteria.getProtocol();
143         UsageCriteria usageCriteria = criteriaSet.get(UsageCriteria.class);
144         UsageType usage = null;
145         if (usageCriteria != null) {
146             usage = usageCriteria.getUsage();
147         } else {
148             usage = UsageType.UNSPECIFIED;
149         }
150         
151         // See Jira issue SIDP-229.
152         log.debug("Forcing on-demand metadata provider refresh if necessary");
153         try {
154             metadata.getMetadata();
155         } catch (MetadataProviderException e) {
156             // don't care about errors at this level
157         }
158 
159         MetadataCacheKey cacheKey = new MetadataCacheKey(entityID, role, protocol, usage);
160         List<PKIXValidationInformation> pkixInfoSet = retrievePKIXInfoFromCache(cacheKey);
161 
162         if (pkixInfoSet == null) {
163             pkixInfoSet = retrievePKIXInfoFromMetadata(entityID, role, protocol, usage);
164             cachePKIXInfo(cacheKey, pkixInfoSet);
165         }
166 
167         return pkixInfoSet;
168     }
169 
170     /** {@inheritDoc} */
171     public Set<String> resolveTrustedNames(CriteriaSet criteriaSet) throws SecurityException,
172             UnsupportedOperationException {
173 
174         checkCriteriaRequirements(criteriaSet);
175 
176         String entityID = criteriaSet.get(EntityIDCriteria.class).getEntityID();
177         MetadataCriteria mdCriteria = criteriaSet.get(MetadataCriteria.class);
178         QName role = mdCriteria.getRole();
179         String protocol = mdCriteria.getProtocol();
180         UsageCriteria usageCriteria = criteriaSet.get(UsageCriteria.class);
181         UsageType usage = null;
182         if (usageCriteria != null) {
183             usage = usageCriteria.getUsage();
184         } else {
185             usage = UsageType.UNSPECIFIED;
186         }
187         
188         // See Jira issue SIDP-229.
189         log.debug("Forcing on-demand metadata provider refresh if necessary");
190         try {
191             metadata.getMetadata();
192         } catch (MetadataProviderException e) {
193             // don't care about errors at this level
194         }
195 
196         MetadataCacheKey cacheKey = new MetadataCacheKey(entityID, role, protocol, usage);
197         Set<String> trustedNames = retrieveTrustedNamesFromCache(cacheKey);
198 
199         if (trustedNames == null) {
200             trustedNames = retrieveTrustedNamesFromMetadata(entityID, role, protocol, usage);
201             cacheTrustedNames(cacheKey, trustedNames);
202         }
203 
204         return trustedNames;
205     }
206 
207     /** {@inheritDoc} */
208     public boolean supportsTrustedNameResolution() {
209         return true;
210     }
211     
212     /**
213      * Get the lock instance used to synchronize access to the caches.
214      * 
215      * @return a read-write lock instance
216      */
217     protected ReadWriteLock getReadWriteLock() {
218         return rwlock;
219     }
220 
221     /**
222      * Check that all necessary criteria are available.
223      * 
224      * @param criteriaSet the criteria set to evaluate
225      */
226     protected void checkCriteriaRequirements(CriteriaSet criteriaSet) {
227         EntityIDCriteria entityCriteria = criteriaSet.get(EntityIDCriteria.class);
228         MetadataCriteria mdCriteria = criteriaSet.get(MetadataCriteria.class);
229         if (entityCriteria == null) {
230             throw new IllegalArgumentException("Entity criteria must be supplied");
231         }
232         if (mdCriteria == null) {
233             throw new IllegalArgumentException("SAML metadata criteria must be supplied");
234         }
235         if (DatatypeHelper.isEmpty(entityCriteria.getEntityID())) {
236             throw new IllegalArgumentException("Entity ID criteria value must be supplied");
237         }
238         if (mdCriteria.getRole() == null) {
239             throw new IllegalArgumentException("Metadata role criteria value must be supplied");
240         }
241     }
242 
243     /**
244      * Retrieves validation information from the provided metadata.
245      * 
246      * @param entityID entity ID for which to resolve validation information
247      * @param role role in which the entity is operating
248      * @param protocol protocol over which the entity is operating (may be null)
249      * @param usage usage specifier for role descriptor key descriptors to evaluate
250      * 
251      * @return collection of resolved validation information, possibly empty
252      * 
253      * @throws SecurityException thrown if the key, certificate, or CRL information is represented in an unsupported
254      *             format
255      */
256     protected List<PKIXValidationInformation> retrievePKIXInfoFromMetadata(String entityID, QName role,
257             String protocol, UsageType usage) throws SecurityException {
258 
259         log.debug("Attempting to retrieve PKIX validation info from metadata for entity: {}", entityID);
260         List<PKIXValidationInformation> pkixInfoSet = new ArrayList<PKIXValidationInformation>();
261         
262         List<RoleDescriptor> roleDescriptors = getRoleDescriptors(entityID, role, protocol);
263         if(roleDescriptors == null || roleDescriptors.isEmpty()){
264             return pkixInfoSet;
265         }
266 
267         for (RoleDescriptor roleDescriptor : roleDescriptors) {
268             List<PKIXValidationInformation> roleInfo = resolvePKIXInfo(roleDescriptor);
269             if (roleInfo != null && !roleInfo.isEmpty()) {
270                 pkixInfoSet.addAll(roleInfo);
271             }
272         }
273 
274         return pkixInfoSet;
275     }
276 
277     /**
278      * Retrieves validation information from the provided role descriptor.
279      * 
280      * @param roleDescriptor the role descriptor from which to resolve information.
281      * @return collection of resolved validation information, possibly empty
282      * @throws SecurityException thrown if the key, certificate, or CRL information is represented in an unsupported
283      *             format
284      * 
285      */
286     protected List<PKIXValidationInformation> resolvePKIXInfo(RoleDescriptor roleDescriptor)
287             throws SecurityException {
288 
289         List<PKIXValidationInformation> pkixInfoSet = new ArrayList<PKIXValidationInformation>();
290 
291         XMLObject current = roleDescriptor.getParent();
292         while (current != null) {
293             if (current instanceof EntityDescriptor) {
294                 pkixInfoSet.addAll(resolvePKIXInfo(((EntityDescriptor) current).getExtensions()));
295             } else if (current instanceof EntitiesDescriptor) {
296                 pkixInfoSet.addAll(resolvePKIXInfo(((EntitiesDescriptor) current).getExtensions()));
297             }
298             current = current.getParent();
299         }
300         return pkixInfoSet;
301     }
302 
303     /**
304      * Retrieves validation information from the metadata extension element.
305      * 
306      * @param extensions the extension element from which to resolve information
307      * @return collection of resolved validation information, possibly empty
308      * @throws SecurityException thrown if the key, certificate, or CRL information is represented in an unsupported
309      *             format
310      */
311     protected List<PKIXValidationInformation> resolvePKIXInfo(Extensions extensions) throws SecurityException {
312         if (extensions == null) {
313             return Collections.emptyList();
314         }
315 
316         List<PKIXValidationInformation> pkixInfoSet = retrieveExtensionsInfoFromCache(extensions);
317         if (pkixInfoSet != null) {
318             return pkixInfoSet;
319         }
320         
321         if (log.isDebugEnabled()) {
322             String parentName = getExtensionsParentName(extensions);
323             if (parentName != null) {
324                 if (extensions.getParent() instanceof EntityDescriptor) {
325                     log.debug("Resolving PKIX validation info for Extensions "
326                             + "with EntityDescriptor parent: {}", parentName);
327                 } else  if (extensions.getParent() instanceof EntitiesDescriptor) {
328                     log.debug("Resolving PKIX validation info for Extensions " 
329                             + "with EntitiesDescriptor parent: {}", parentName);
330                 }
331             } else {
332                 log.debug("Resolving PKIX validation info for Extensions " 
333                         + "with unidentified parent");
334             }
335         }
336 
337         pkixInfoSet = new ArrayList<PKIXValidationInformation>();
338         
339         List<XMLObject> authorities = 
340             extensions.getUnknownXMLObjects(ShibbolethMetadataKeyAuthority.DEFAULT_ELEMENT_NAME);
341         if (authorities == null || authorities.isEmpty()) {
342             return pkixInfoSet;
343         }
344         
345         for (XMLObject xmlObj : authorities) {
346             PKIXValidationInformation authoritySet = resolvePKIXInfo((ShibbolethMetadataKeyAuthority) xmlObj);
347             if (authoritySet != null) {
348                 pkixInfoSet.add(authoritySet);
349             }            
350         }
351         cacheExtensionsInfo(extensions, pkixInfoSet);
352         return pkixInfoSet;
353     }
354 
355     /**
356      * Retrieves validation information from the Shibboleth KeyAuthority metadata extension element.
357      * 
358      * @param keyAuthority the Shibboleth KeyAuthority element from which to resolve information
359      * @return an instance of resolved validation information
360      * @throws SecurityException thrown if the key, certificate, or CRL information is represented in an unsupported
361      *             format
362      */
363     protected PKIXValidationInformation resolvePKIXInfo(ShibbolethMetadataKeyAuthority keyAuthority)
364             throws SecurityException {
365 
366         List<X509Certificate> certs = new ArrayList<X509Certificate>();
367         List<X509CRL> crls = new ArrayList<X509CRL>();
368         Integer depth = keyAuthority.getVerifyDepth();
369         if (depth == null) {
370             depth = KEY_AUTHORITY_VERIFY_DEPTH_DEFAULT;
371         }
372         
373         List<KeyInfo> keyInfos = keyAuthority.getKeyInfos();
374         if (keyInfos == null || keyInfos.isEmpty()) {
375             return null;
376         }
377         
378         for (KeyInfo keyInfo : keyInfos) {
379             certs.addAll(getX509Certificates(keyInfo));
380             crls.addAll(getX509CRLs(keyInfo));
381         }
382         
383         // Unlikely, but go ahead and check.
384         if (certs.isEmpty() && crls.isEmpty()) {
385             return null;
386         }
387 
388         return new BasicPKIXValidationInformation(certs, crls, depth);
389     }
390 
391     /**
392      * Extract certificates from a KeyInfo element.
393      * 
394      * @param keyInfo the KeyInfo instance from which to extract certificates
395      * @return a collection of X509 certificates, possibly empty
396      * @throws SecurityException thrown if the certificate information is represented in an unsupported format
397      */
398     protected List<X509Certificate> getX509Certificates(KeyInfo keyInfo) throws SecurityException {
399         try {
400             return KeyInfoHelper.getCertificates(keyInfo);
401         } catch (CertificateException e) {
402             throw new SecurityException("Error extracting certificates from KeyAuthority KeyInfo", e);
403         }
404 
405     }
406 
407     /**
408      * Extract CRL's from a KeyInfo element.
409      * 
410      * @param keyInfo the KeyInfo instance from which to extract CRL's
411      * @return a collection of X509 CRL's, possibly empty
412      * @throws SecurityException thrown if the CRL information is represented in an unsupported format
413      */
414     protected List<X509CRL> getX509CRLs(KeyInfo keyInfo) throws SecurityException {
415         try {
416             return KeyInfoHelper.getCRLs(keyInfo);
417         } catch (CRLException e) {
418             throw new SecurityException("Error extracting CRL's from KeyAuthority KeyInfo", e);
419         }
420 
421     }
422 
423     /**
424      * Retrieves trusted name information from the provided metadata.
425      * 
426      * @param entityID entity ID for which to resolve trusted names
427      * @param role role in which the entity is operating
428      * @param protocol protocol over which the entity is operating (may be null)
429      * @param usage usage specifier for role descriptor key descriptors to evaluate
430      * 
431      * @return collection of resolved trusted name information, possibly empty
432      * 
433      * @throws SecurityException thrown if there is an error extracting trusted name information
434      */
435     protected Set<String> retrieveTrustedNamesFromMetadata(String entityID, QName role, String protocol,
436             UsageType usage) throws SecurityException {
437 
438         log.debug("Attempting to retrieve trusted names for PKIX validation from metadata for entity: {}", entityID);
439         Set<String> trustedNames = new LazySet<String>();
440         
441         List<RoleDescriptor> roleDescriptors = getRoleDescriptors(entityID, role, protocol);
442         if(roleDescriptors == null || roleDescriptors.isEmpty()){
443             return trustedNames;
444         }
445 
446         for (RoleDescriptor roleDescriptor : roleDescriptors) {
447             List<KeyDescriptor> keyDescriptors = roleDescriptor.getKeyDescriptors();
448             if(keyDescriptors == null || keyDescriptors.isEmpty()){
449                 return trustedNames;
450             }         
451             for (KeyDescriptor keyDescriptor : keyDescriptors) {
452                 UsageType mdUsage = keyDescriptor.getUse();
453                 if (mdUsage == null) {
454                     mdUsage = UsageType.UNSPECIFIED;
455                 }
456                 if (matchUsage(mdUsage, usage)) {
457                     if (keyDescriptor.getKeyInfo() != null) {
458                         trustedNames.addAll(getTrustedNames(keyDescriptor.getKeyInfo()));
459                     }
460                 }
461             }
462 
463         }
464 
465         return trustedNames;
466     }
467 
468     /**
469      * Extract trusted names from a KeyInfo element.
470      * 
471      * @param keyInfo the KeyInfo instance from which to extract trusted names
472      * @return set of trusted names, possibly empty
473      */
474     protected Set<String> getTrustedNames(KeyInfo keyInfo) {
475         // TODO return anything if there are things other than names in the KeyInfo ?
476         Set<String> names = new LazySet<String>();
477         names.addAll(KeyInfoHelper.getKeyNames(keyInfo));
478         return names;
479     }
480 
481     /**
482      * Match usage enum type values from metadata KeyDescriptor and from specified resolution criteria.
483      * 
484      * @param metadataUsage the value from the 'use' attribute of a metadata KeyDescriptor element
485      * @param criteriaUsage the value from specified criteria
486      * @return true if the two usage specifiers match for purposes of resolving validation information, false otherwise
487      */
488     protected boolean matchUsage(UsageType metadataUsage, UsageType criteriaUsage) {
489         if (metadataUsage == UsageType.UNSPECIFIED || criteriaUsage == UsageType.UNSPECIFIED) {
490             return true;
491         }
492         return metadataUsage == criteriaUsage;
493     }
494 
495     /**
496      * Get the list of metadata role descriptors which match the given entityID, role and protocol.
497      * 
498      * @param entityID entity ID of the metadata entity descriptor to resolve
499      * @param role role in which the entity is operating
500      * @param protocol protocol over which the entity is operating (may be null)
501      * @return a list of role descriptors matching the given parameters, or null
502      * @throws SecurityException thrown if there is an error retrieving role descriptors from the metadata provider
503      */
504     protected List<RoleDescriptor> getRoleDescriptors(String entityID, QName role, String protocol)
505             throws SecurityException {
506         try {
507             if (DatatypeHelper.isEmpty(protocol)) {
508                 return metadata.getRole(entityID, role);
509             } else {
510                 RoleDescriptor roleDescriptor = metadata.getRole(entityID, role, protocol);
511                 if (roleDescriptor == null) {
512                     return null;
513                 }
514                 List<RoleDescriptor> roles = new ArrayList<RoleDescriptor>();
515                 roles.add(roleDescriptor);
516                 return roles;
517             }
518         } catch (MetadataProviderException e) {
519             log.error("Unable to read metadata from provider", e);
520             throw new SecurityException("Unable to read metadata provider", e);
521         }
522     }
523 
524     /**
525      * Retrieves pre-resolved PKIX validation information from the cache.
526      * 
527      * @param cacheKey the key to the metadata cache
528      * @return the collection of cached info or null
529      */
530     protected List<PKIXValidationInformation> retrievePKIXInfoFromCache(MetadataCacheKey cacheKey) {
531         log.debug("Attempting to retrieve PKIX validation info from cache using index: {}", cacheKey);
532         Lock readLock = getReadWriteLock().readLock();
533         readLock.lock();
534         log.debug("Read lock over cache acquired");
535         try {
536             if (entityPKIXCache.containsKey(cacheKey)) {
537                 SoftReference<List<PKIXValidationInformation>> reference = entityPKIXCache.get(cacheKey);
538                 if (reference.get() != null) {
539                     log.debug("Retrieved PKIX validation info from cache using index: {}", cacheKey);
540                     return reference.get();
541                 }
542             }
543         } finally {
544             readLock.unlock();
545             log.debug("Read lock over cache released");
546         }
547 
548         log.debug("Unable to retrieve PKIX validation info from cache using index: {}", cacheKey);
549         return null;
550     }
551 
552     /**
553      * Retrieves pre-resolved PKIX validation information from the cache.
554      * 
555      * @param extensions the key to the metadata cache
556      * @return the collection of cached info or null
557      */
558     protected List<PKIXValidationInformation> retrieveExtensionsInfoFromCache(Extensions extensions) {
559         if (log.isDebugEnabled()) {
560             String parentName = getExtensionsParentName(extensions);
561             if (parentName != null) {
562                 if (extensions.getParent() instanceof EntityDescriptor) {
563                     log.debug("Attempting to retrieve PKIX validation info from cache for Extensions "
564                             + "with EntityDescriptor parent: {}", parentName);
565                 } else  if (extensions.getParent() instanceof EntitiesDescriptor) {
566                     log.debug("Attempting to retrieve PKIX validation info from cache for Extensions " 
567                             + "with EntitiesDescriptor parent: {}", parentName);
568                 }
569             } else {
570                 log.debug("Attempting to retrieve PKIX validation info from cache for Extensions " 
571                         + "with unidentified parent");
572             }
573         }
574         
575         Lock readLock = getReadWriteLock().readLock();
576         readLock.lock();
577         log.debug("Read lock over cache acquired");
578         try {
579             if (extensionsCache.containsKey(extensions)) {
580                 SoftReference<List<PKIXValidationInformation>> reference = extensionsCache.get(extensions);
581                 if (reference.get() != null) {
582                     log.debug("Retrieved PKIX validation info from cache using index: {}", extensions);
583                     return reference.get();
584                 }
585             }
586         } finally {
587             readLock.unlock();
588             log.debug("Read lock over cache released");
589         }
590         log.debug("Unable to retrieve PKIX validation info from cache using index: {}", extensions);
591         return null;
592     }
593     
594     /**
595      * Retrieves pre-resolved trusted names from the cache.
596      * 
597      * @param cacheKey the key to the metadata cache
598      * @return the set of cached info or null
599      */
600     protected Set<String> retrieveTrustedNamesFromCache(MetadataCacheKey cacheKey) {
601         log.debug("Attempting to retrieve trusted names from cache using index: {}", cacheKey);
602         Lock readLock = getReadWriteLock().readLock();
603         readLock.lock();
604         log.debug("Read lock over cache acquired");
605         try {
606             if (entityNamesCache.containsKey(cacheKey)) {
607                 SoftReference<Set<String>> reference = entityNamesCache.get(cacheKey);
608                 if (reference.get() != null) {
609                     log.debug("Retrieved trusted names from cache using index: {}", cacheKey);
610                     return reference.get();
611                 }
612             }
613         } finally {
614             readLock.unlock();
615             log.debug("Read lock over cache released");
616         }
617 
618         log.debug("Unable to retrieve trusted names from cache using index: {}", cacheKey);
619         return null;
620     }
621 
622     /**
623      * Adds resolved PKIX validation information to the cache.
624      * 
625      * @param cacheKey the key for caching the information
626      * @param pkixInfo collection of PKIX information to cache
627      */
628     protected void cachePKIXInfo(MetadataCacheKey cacheKey, List<PKIXValidationInformation> pkixInfo) {
629         Lock writeLock = getReadWriteLock().writeLock();
630         writeLock.lock();
631         log.debug("Write lock over cache acquired");
632         try {
633             entityPKIXCache.put(cacheKey, new SoftReference<List<PKIXValidationInformation>>(pkixInfo));
634             log.debug("Added new PKIX info to entity cache with key: {}", cacheKey);
635         } finally {
636             writeLock.unlock();
637             log.debug("Write lock over cache released"); 
638         }
639     }
640 
641     /**
642      * Adds resolved PKIX validation information to the cache.
643      * 
644      * @param extensions the key for caching the information
645      * @param pkixInfo collection of PKIX information to cache
646      */
647     protected void cacheExtensionsInfo(Extensions extensions, List<PKIXValidationInformation> pkixInfo) {
648         Lock writeLock = getReadWriteLock().writeLock();
649         writeLock.lock();
650         log.debug("Write lock over cache acquired");
651         try {
652             extensionsCache.put(extensions, new SoftReference<List<PKIXValidationInformation>>(pkixInfo));
653             if (log.isDebugEnabled()) {
654                 log.debug("Added new PKIX info to cache for Extensions with parent: {}",
655                         getExtensionsParentName(extensions));
656             }
657         } finally {
658             writeLock.unlock();
659             log.debug("Write lock over cache released"); 
660         }
661     }
662 
663     /**
664      * Adds resolved trusted name information to the cache.
665      * 
666      * @param cacheKey the key for caching the information
667      * @param names collection of names to cache
668      */
669     protected void cacheTrustedNames(MetadataCacheKey cacheKey, Set<String> names) {
670         Lock writeLock = getReadWriteLock().writeLock();
671         writeLock.lock();
672         log.debug("Write lock over cache acquired");
673         try {
674             entityNamesCache.put(cacheKey, new SoftReference<Set<String>>(names));
675             log.debug("Added new PKIX info to entity cache with key: {}", cacheKey);
676         } finally {
677             writeLock.unlock();
678             log.debug("Write lock over cache released"); 
679         }
680     }
681 
682     /**
683      * Get the name of the parent element of an {@link Extensions} element in metadata, mostly
684      * useful for logging purposes.
685      * 
686      * If the parent is an EntityDescriptor, return the entityID value.  If an EntitiesDescriptor,
687      * return the name value.
688      * 
689      * @param extensions the Extensions element
690      * @return the Extensions element's parent's name
691      */
692     protected String getExtensionsParentName(Extensions extensions) {
693         XMLObject parent = extensions.getParent();
694         if (parent == null) {
695             return null;
696         }
697         if (parent instanceof EntityDescriptor) {
698             return ((EntityDescriptor) parent).getEntityID();
699         } else  if (extensions.getParent() instanceof EntitiesDescriptor) {
700             return ((EntitiesDescriptor) parent).getName();
701         }
702         return null;
703     }
704 
705     /**
706      * A class which serves as the key into the cache of information previously resolved.
707      */
708     protected class MetadataCacheKey {
709 
710         /** Entity ID associated with resolved information. */
711         private String id;
712 
713         /** Role in which the entity is operating. */
714         private QName role;
715 
716         /** Protocol over which the entity is operating (may be null). */
717         private String protocol;
718 
719         /** Intended usage specifier of the role descriptor's key descriptor. */
720         private UsageType usage;
721 
722         /**
723          * Constructor.
724          * 
725          * @param entityID entity ID of the metadata entity descriptor to resolve
726          * @param entityRole role in which the entity is operating
727          * @param entityProtocol protocol over which the entity is operating (may be null)
728          * @param entityUsage usage specifier of the role descriptor's key descriptor
729          */
730         protected MetadataCacheKey(String entityID, QName entityRole, String entityProtocol, UsageType entityUsage) {
731             if (entityID == null) {
732                 throw new IllegalArgumentException("Entity ID may not be null");
733             }
734             if (entityRole == null) {
735                 throw new IllegalArgumentException("Entity role may not be null");
736             }
737             if (entityUsage == null) {
738                 throw new IllegalArgumentException("Usage may not be null");
739             }
740             id = entityID;
741             role = entityRole;
742             protocol = entityProtocol;
743             usage = entityUsage;
744         }
745 
746         /** {@inheritDoc} */
747         public boolean equals(Object obj) {
748             if (obj == this) {
749                 return true;
750             }
751             if (!(obj instanceof MetadataCacheKey)) {
752                 return false;
753             }
754             MetadataCacheKey other = (MetadataCacheKey) obj;
755             if (!this.id.equals(other.id) || !this.role.equals(other.role) || this.usage != other.usage) {
756                 return false;
757             }
758             if (this.protocol == null) {
759                 if (other.protocol != null) {
760                     return false;
761                 }
762             } else {
763                 if (!this.protocol.equals(other.protocol)) {
764                     return false;
765                 }
766             }
767             return true;
768         }
769 
770         /** {@inheritDoc} */
771         public int hashCode() {
772             int result = 17;
773             result = 37 * result + id.hashCode();
774             result = 37 * result + role.hashCode();
775             if (protocol != null) {
776                 result = 37 * result + protocol.hashCode();
777             }
778             result = 37 * result + usage.hashCode();
779             return result;
780         }
781 
782         /** {@inheritDoc} */
783         public String toString() {
784             return String.format("[%s,%s,%s,%s]", id, role, protocol, usage);
785         }
786 
787     }
788 
789     /**
790      * An observer that clears the credential cache if the underlying metadata changes.
791      */
792     protected class MetadataProviderObserver implements ObservableMetadataProvider.Observer {
793 
794         /** {@inheritDoc} */
795         public void onEvent(MetadataProvider provider) {
796             Lock writeLock = getReadWriteLock().writeLock();
797             writeLock.lock();
798             log.debug("Write lock over cache acquired");
799             try {
800                 entityPKIXCache.clear();
801                 extensionsCache.clear();
802                 entityNamesCache.clear();
803                 log.info("PKIX validation info cache cleared");
804             } finally {
805                 writeLock.unlock();
806                 log.debug("Write lock over cache released"); 
807             }
808         }
809     }
810 
811 }