use the new session manager interface
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / provider / ShibbolethTrustEngine.java
1
2 package edu.internet2.middleware.shibboleth.common.provider;
3
4 import java.security.cert.X509CRL;
5 import java.security.cert.X509Certificate;
6 import java.util.HashSet;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.NoSuchElementException;
10 import java.util.Set;
11
12 import javax.xml.namespace.QName;
13
14 import org.apache.log4j.Logger;
15 import org.opensaml.saml2.common.Extensions;
16 import org.opensaml.saml2.metadata.EntitiesDescriptor;
17 import org.opensaml.saml2.metadata.EntityDescriptor;
18 import org.opensaml.saml2.metadata.RoleDescriptor;
19 import org.opensaml.security.TrustEngine;
20 import org.opensaml.security.X509EntityCredential;
21 import org.opensaml.security.impl.AbstractPKIXTrustEngine;
22 import org.opensaml.security.impl.InlinePKIKeyTrustEngine;
23 import org.opensaml.xml.ElementProxy;
24 import org.opensaml.xml.XMLObject;
25 import org.opensaml.xml.signature.KeyInfo;
26
27 /**
28  * <code>TrustEngine</code> implementation that first attempts to do standard SAML2 inline key validation and then
29  * falls back on PKIX validation against key authorities included in shibboleth-specific extensions to SAML 2 metadata.
30  * 
31  * @author Walter Hoehn
32  */
33 public class ShibbolethTrustEngine extends InlinePKIKeyTrustEngine implements TrustEngine<X509EntityCredential> {
34
35         private static Logger log = Logger.getLogger(ShibbolethTrustEngine.class.getName());
36         private static final String KEY_AUTHORITY_LOCAL_NAME = "KeyAuthority";
37         private static final String CUSTOM_METADATA_NS = "urn:mace:shibboleth:metadata:1.0";
38         private static final QName KEY_AUTHORITY = new QName(CUSTOM_METADATA_NS, KEY_AUTHORITY_LOCAL_NAME);
39
40         @Override
41         public boolean validate(X509EntityCredential entityCredential, RoleDescriptor descriptor) {
42
43                 // If we can successfully validate with an inline key, that's fine
44                 boolean defaultValidation = super.validate(entityCredential, descriptor);
45                 if (defaultValidation == true) { return true; }
46
47                 // Make sure we have the data we need
48                 if (descriptor == null || entityCredential == null) {
49                         log.error("Appropriate data was not supplied for trust evaluation.");
50                         return false;
51                 }
52
53                 // If not, try PKIX validation against the shib-custom metadata extensions
54                 if (descriptor.getParent() == null || !(descriptor.getParent() instanceof EntityDescriptor)) {
55                         log.debug("Inline validation was unsuccessful.  Unable to attempt PKIX validation "
56                                         + "because we don't have a complete metadata tree.");
57                         return false;
58
59                 } else {
60                         log.debug("Inline validation was unsuccessful.  Attmping PKIX...");
61                         boolean pkixValid = new ShibbolethPKIXEngine((EntityDescriptor) descriptor.getParent()).validate(
62                                         entityCredential, descriptor);
63                         if (pkixValid) {
64                                 log.debug("PKIX validation was successful.");
65                         } else {
66                                 log.debug("PKIX validation was unsuccessful.");
67                         }
68                         return pkixValid;
69                 }
70         }
71
72         /**
73          * Pulls <code>PKIXValidationInformation</code> out of Shibboleth-specific metadata extensions and runs the
74          * results against OpenSAML's PKIX trust engine. Recurses backwards through the metadata tree, attempting PKIX
75          * validation at each level that contains a <KeyAuthority/> element. Metadata is evaluated in a lazy fashion.
76          */
77         private class ShibbolethPKIXEngine extends AbstractPKIXTrustEngine implements TrustEngine<X509EntityCredential> {
78
79                 EntityDescriptor entity;
80
81                 private ShibbolethPKIXEngine(EntityDescriptor entity) {
82
83                         this.entity = entity;
84                 }
85
86                 @Override
87                 protected Iterator<PKIXValidationInformation> getValidationInformation(RoleDescriptor descriptor) {
88
89                         return new ShibPKIXMetadata(entity);
90                 }
91
92                 private class ShibPKIXMetadata implements Iterator<PKIXValidationInformation> {
93
94                         private ExtensionPoint currentExtensionPointRoot;
95
96                         private ShibPKIXMetadata(EntityDescriptor entity) {
97
98                                 currentExtensionPointRoot = new ExtensionPoint(entity);
99                         }
100
101                         public boolean hasNext() {
102
103                                 return (getNextKeyAuthority(currentExtensionPointRoot, false) != null);
104                         }
105
106                         public PKIXValidationInformation next() {
107
108                                 // Construct PKIX validation information from Shib metadata
109                                 ElementProxy keyAuthority = getNextKeyAuthority(currentExtensionPointRoot, true);
110                                 if (keyAuthority == null) { throw new NoSuchElementException(); }
111
112                                 return convertMetadatatoValidationInfo(keyAuthority);
113
114                         }
115
116                         public void remove() {
117
118                                 throw new UnsupportedOperationException();
119                         }
120
121                         private PKIXValidationInformation convertMetadatatoValidationInfo(ElementProxy keyAuthority) {
122
123                                 // Find the verification depth for all anchors in this set
124                                 int verifyDepth = 1;
125                                 String rawVerifyDepth = keyAuthority.getUnknownAttributes().get(new QName("VerifyDepth"));
126                                 if (rawVerifyDepth != null && !rawVerifyDepth.equals("")) {
127                                         try {
128                                                 verifyDepth = Integer.parseInt(rawVerifyDepth);
129                                         } catch (NumberFormatException nfe) {
130                                                 log.error("<KeyAuthority/> attribute (VerifyDepth) is not an "
131                                                                 + "integer, defaulting to most strict depth of (1).");
132                                                 verifyDepth = 1;
133                                         }
134                                 }
135
136                                 // Find all trust anchors and revocation lists in the KeyInfo
137                                 Set<X509Certificate> trustAnchors = new HashSet<X509Certificate>();
138                                 Set<X509CRL> revocationLists = new HashSet<X509CRL>();
139                                 for (XMLObject subExtension : keyAuthority.getUnknownXMLObjects()) {
140                                         if (subExtension instanceof KeyInfo) {
141                                                 trustAnchors.addAll(((KeyInfo) subExtension).getCertificates());
142                                                 revocationLists.addAll(((KeyInfo) subExtension).getCRLs());
143                                         }
144                                 }
145
146                                 log.debug("Found Shibboleth Key Authority Metadata: Verification depth: " + verifyDepth
147                                                 + "   Trust Anchors: " + trustAnchors.size() + "   Revocation Lists: " + revocationLists.size()
148                                                 + ".");
149                                 return new PKIXValidationInformation(verifyDepth, trustAnchors, revocationLists);
150                         }
151
152                         private ElementProxy getNextKeyAuthority(ExtensionPoint extensionPoint, boolean consume) {
153
154                                 if (extensionPoint == null) { return null; } // Can't recurse further
155
156                                 Extensions extensions = extensionPoint.getExtensions();
157
158                                 // Check this descriptor for a key authority
159                                 // The complication is that a particular descriptor can have more than one key authority, which is why
160                                 // we need this "consumedIndex" business to keep track of which ones we've processed so far
161                                 if (extensions != null) {
162                                         List<XMLObject> xmlObjects = extensions.getUnknownXMLObjects();
163                                         for (int i = extensionPoint.consumedIndex; i < xmlObjects.size(); i++) {
164
165                                                 if (xmlObjects.get(i).getElementQName().equals(KEY_AUTHORITY)
166                                                                 && xmlObjects.get(i) instanceof ElementProxy) {
167                                                         log.debug("Found Key Authority element in metadata.");
168                                                         if (consume && extensionPoint.consumedIndex == xmlObjects.size()) {
169                                                                 currentExtensionPointRoot = new ExtensionPoint(extensionPoint.getParent());
170
171                                                         } else if (consume) {
172                                                                 currentExtensionPointRoot = extensionPoint;
173                                                                 extensionPoint.consumedIndex = i + 1;
174                                                         }
175                                                         return (ElementProxy) xmlObjects.get(i);
176                                                 }
177                                         }
178                                 }
179
180                                 // If we don't find anything, recurse back through the parentso
181                                 if (extensionPoint.getParent() != null) { return getNextKeyAuthority(new ExtensionPoint(extensionPoint
182                                                 .getParent()), consume); }
183
184                                 return null;
185                         }
186
187                         class ExtensionPoint {
188
189                                 private EntityDescriptor entity;
190                                 private EntitiesDescriptor entities;
191                                 int consumedIndex = 0;
192
193                                 private ExtensionPoint(EntityDescriptor entity) {
194
195                                         this.entity = entity;
196                                 }
197
198                                 private ExtensionPoint(EntitiesDescriptor entities) {
199
200                                         this.entities = entities;
201                                 }
202
203                                 private Extensions getExtensions() {
204
205                                         if (entity != null) {
206                                                 return entity.getExtensions();
207                                         } else if (entities != null) {
208                                                 return entities.getExtensions();
209                                         } else {
210                                                 return null;
211                                         }
212                                 }
213
214                                 private EntitiesDescriptor getParent() {
215
216                                         if (entity != null && entity.getParent() instanceof EntitiesDescriptor) {
217                                                 return (EntitiesDescriptor) entity.getParent();
218                                         } else if (entities != null && entities.getParent() instanceof EntitiesDescriptor) {
219                                                 return (EntitiesDescriptor) entities.getParent();
220                                         } else {
221                                                 return null;
222                                         }
223                                 }
224
225                         }
226
227                 }
228         }
229 }