View Javadoc

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