Reducing header overuse, non-inlining selected methods (CPPOST-35).
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / SignatureMetadataFilter.cpp
1 /*
2  *  Copyright 2001-2009 Internet2
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 /**
18  * SignatureMetadataFilter.cpp
19  *
20  * Filters out unsigned or mis-signed elements.
21  */
22
23 #include "internal.h"
24 #include "saml2/metadata/Metadata.h"
25 #include "saml2/metadata/MetadataFilter.h"
26 #include "signature/SignatureProfileValidator.h"
27
28 #include <xmltooling/logging.h>
29 #include <xmltooling/XMLToolingConfig.h>
30 #include <xmltooling/security/Credential.h>
31 #include <xmltooling/security/CredentialCriteria.h>
32 #include <xmltooling/security/CredentialResolver.h>
33 #include <xmltooling/security/SignatureTrustEngine.h>
34 #include <xmltooling/signature/SignatureValidator.h>
35 #include <xmltooling/util/NDC.h>
36
37 using namespace opensaml::saml2md;
38 using namespace opensaml;
39 using namespace xmlsignature;
40 using namespace xmltooling::logging;
41 using namespace xmltooling;
42 using namespace std;
43
44 namespace opensaml {
45     namespace saml2md {
46
47         class SAML_DLLLOCAL DummyCredentialResolver : public CredentialResolver
48         {
49         public:
50             DummyCredentialResolver() {}
51             ~DummyCredentialResolver() {}
52
53             Lockable* lock() {return this;}
54             void unlock() {}
55
56             const Credential* resolve(const CredentialCriteria* criteria=NULL) const {return NULL;}
57             vector<const Credential*>::size_type resolve(
58                 vector<const Credential*>& results, const CredentialCriteria* criteria=NULL
59                 ) const {return 0;}
60         };
61
62         class SAML_DLLLOCAL SignatureMetadataFilter : public MetadataFilter
63         {
64         public:
65             SignatureMetadataFilter(const DOMElement* e);
66             ~SignatureMetadataFilter() {
67                 delete m_credResolver;
68                 delete m_trust;
69             }
70
71             const char* getId() const { return SIGNATURE_METADATA_FILTER; }
72             void doFilter(XMLObject& xmlObject) const;
73
74         private:
75             void doFilter(EntitiesDescriptor& entities, bool rootObject=false) const;
76             void doFilter(EntityDescriptor& entity, bool rootObject=false) const;
77             void verifySignature(Signature* sig, const XMLCh* peerName) const;
78
79             bool m_verifyRoles,m_verifyName;
80             CredentialResolver* m_credResolver;
81             SignatureTrustEngine* m_trust;
82             SignatureProfileValidator m_profileValidator;
83             Category& m_log;
84         };
85
86         MetadataFilter* SAML_DLLLOCAL SignatureMetadataFilterFactory(const DOMElement* const & e)
87         {
88             return new SignatureMetadataFilter(e);
89         }
90
91     };
92 };
93
94 static const XMLCh _TrustEngine[] =         UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e);
95 static const XMLCh _CredentialResolver[] =  UNICODE_LITERAL_18(C,r,e,d,e,n,t,i,a,l,R,e,s,o,l,v,e,r);
96 static const XMLCh type[] =                 UNICODE_LITERAL_4(t,y,p,e);
97 static const XMLCh certificate[] =          UNICODE_LITERAL_11(c,e,r,t,i,f,i,c,a,t,e);
98 static const XMLCh Certificate[] =          UNICODE_LITERAL_11(C,e,r,t,i,f,i,c,a,t,e);
99 static const XMLCh Path[] =                 UNICODE_LITERAL_4(P,a,t,h);
100 static const XMLCh verifyRoles[] =          UNICODE_LITERAL_11(v,e,r,i,f,y,R,o,l,e,s);
101 static const XMLCh verifyName[] =           UNICODE_LITERAL_10(v,e,r,i,f,y,N,a,m,e);
102
103 SignatureMetadataFilter::SignatureMetadataFilter(const DOMElement* e)
104     : m_verifyRoles(false), m_verifyName(true), m_credResolver(NULL), m_trust(NULL), m_log(Category::getInstance(SAML_LOGCAT".MetadataFilter.Signature"))
105 {
106     const XMLCh* flag = e ? e->getAttributeNS(NULL,verifyRoles) : NULL;
107     m_verifyRoles = (flag && (*flag == chLatin_t || *flag == chDigit_1));
108
109     flag = e ? e->getAttributeNS(NULL,verifyName) : NULL;
110     m_verifyName = !(flag && (*flag == chLatin_f || *flag == chDigit_0));
111
112     if (e && e->hasAttributeNS(NULL,certificate)) {
113         // Use a file-based credential resolver rooted here.
114         m_credResolver = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin(FILESYSTEM_CREDENTIAL_RESOLVER,e);
115         return;
116     }
117
118     DOMElement* sub = e ? XMLHelper::getFirstChildElement(e, _CredentialResolver) : NULL;
119     auto_ptr_char t(sub ? sub->getAttributeNS(NULL,type) : NULL);
120     if (t.get()) {
121         m_credResolver = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin(t.get(),sub);
122         return;
123     }
124
125     sub = e ? XMLHelper::getFirstChildElement(e, _TrustEngine) : NULL;
126     auto_ptr_char t2(sub ? sub->getAttributeNS(NULL,type) : NULL);
127     if (t2.get()) {
128         TrustEngine* trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t2.get(),sub);
129         if (!(m_trust = dynamic_cast<SignatureTrustEngine*>(trust))) {
130             delete trust;
131             throw MetadataFilterException("TrustEngine-based SignatureMetadataFilter requires a SignatureTrustEngine plugin.");
132         }
133         return;
134     }
135
136     throw MetadataFilterException("SignatureMetadataFilter configuration requires <CredentialResolver> or <TrustEngine> element.");
137 }
138
139 void SignatureMetadataFilter::doFilter(XMLObject& xmlObject) const
140 {
141 #ifdef _DEBUG
142     NDC ndc("doFilter");
143 #endif
144
145     try {
146         EntitiesDescriptor& entities = dynamic_cast<EntitiesDescriptor&>(xmlObject);
147         doFilter(entities, true);
148         return;
149     }
150     catch (bad_cast) {
151     }
152     catch (exception& ex) {
153         m_log.warn("filtering out group at root of instance after failed signature check: %s", ex.what());
154         throw MetadataFilterException("SignatureMetadataFilter unable to verify signature at root of metadata instance.");
155     }
156
157     try {
158         EntityDescriptor& entity = dynamic_cast<EntityDescriptor&>(xmlObject);
159         doFilter(entity, true);
160         return;
161     }
162     catch (bad_cast) {
163     }
164     catch (exception& ex) {
165         m_log.warn("filtering out entity at root of instance after failed signature check: %s", ex.what());
166         throw MetadataFilterException("SignatureMetadataFilter unable to verify signature at root of metadata instance.");
167     }
168
169     throw MetadataFilterException("SignatureMetadataFilter was given an improper metadata instance to filter.");
170 }
171
172 void SignatureMetadataFilter::doFilter(EntitiesDescriptor& entities, bool rootObject) const
173 {
174     Signature* sig = entities.getSignature();
175     if (!sig && rootObject)
176         throw MetadataFilterException("Root metadata element was unsigned.");
177     verifySignature(sig, entities.getName());
178
179     VectorOf(EntityDescriptor) v=entities.getEntityDescriptors();
180     for (VectorOf(EntityDescriptor)::size_type i=0; i<v.size(); ) {
181         try {
182             doFilter(*(v[i]));
183             i++;
184         }
185         catch (exception& e) {
186             auto_ptr_char id(v[i]->getEntityID());
187             m_log.warn("filtering out entity (%s) after failed signature check: %s", id.get(), e.what());
188             v.erase(v.begin() + i);
189         }
190     }
191
192     VectorOf(EntitiesDescriptor) w=entities.getEntitiesDescriptors();
193     for (VectorOf(EntitiesDescriptor)::size_type j=0; j<w.size(); ) {
194         try {
195             doFilter(*w[j], false);
196             j++;
197         }
198         catch (exception& e) {
199             auto_ptr_char name(w[j]->getName());
200             m_log.warn("filtering out group (%s) after failed signature check: %s", name.get(), e.what());
201             w.erase(w.begin() + j);
202         }
203     }
204 }
205
206 void SignatureMetadataFilter::doFilter(EntityDescriptor& entity, bool rootObject) const
207 {
208     Signature* sig = entity.getSignature();
209     if (!sig && rootObject)
210         throw MetadataFilterException("Root metadata element was unsigned.");
211     verifySignature(sig, entity.getEntityID());
212
213     if (!m_verifyRoles)
214         return;
215
216     VectorOf(IDPSSODescriptor) idp=entity.getIDPSSODescriptors();
217     for (VectorOf(IDPSSODescriptor)::size_type i=0; i<idp.size(); ) {
218         try {
219             verifySignature(idp[i]->getSignature(), entity.getEntityID());
220             i++;
221         }
222         catch (exception& e) {
223             auto_ptr_char id(entity.getEntityID());
224             m_log.warn(
225                 "filtering out IDPSSODescriptor for entity (%s) after failed signature check: %s", id.get(), e.what()
226                 );
227             idp.erase(idp.begin() + i);
228         }
229     }
230
231     VectorOf(SPSSODescriptor) sp=entity.getSPSSODescriptors();
232     for (VectorOf(SPSSODescriptor)::size_type i=0; i<sp.size(); ) {
233         try {
234             verifySignature(sp[i]->getSignature(), entity.getEntityID());
235             i++;
236         }
237         catch (exception& e) {
238             auto_ptr_char id(entity.getEntityID());
239             m_log.warn(
240                 "filtering out SPSSODescriptor for entity (%s) after failed signature check: %s", id.get(), e.what()
241                 );
242             sp.erase(sp.begin() + i);
243         }
244     }
245
246     VectorOf(AuthnAuthorityDescriptor) authn=entity.getAuthnAuthorityDescriptors();
247     for (VectorOf(AuthnAuthorityDescriptor)::size_type i=0; i<authn.size(); ) {
248         try {
249             verifySignature(authn[i]->getSignature(), entity.getEntityID());
250             i++;
251         }
252         catch (exception& e) {
253             auto_ptr_char id(entity.getEntityID());
254             m_log.warn(
255                 "filtering out AuthnAuthorityDescriptor for entity (%s) after failed signature check: %s", id.get(), e.what()
256                 );
257             authn.erase(authn.begin() + i);
258         }
259     }
260
261     VectorOf(AttributeAuthorityDescriptor) aa=entity.getAttributeAuthorityDescriptors();
262     for (VectorOf(AttributeAuthorityDescriptor)::size_type i=0; i<aa.size(); ) {
263         try {
264             verifySignature(aa[i]->getSignature(), entity.getEntityID());
265             i++;
266         }
267         catch (exception& e) {
268             auto_ptr_char id(entity.getEntityID());
269             m_log.warn(
270                 "filtering out AttributeAuthorityDescriptor for entity (%s) after failed signature check: %s", id.get(), e.what()
271                 );
272             aa.erase(aa.begin() + i);
273         }
274     }
275
276     VectorOf(PDPDescriptor) pdp=entity.getPDPDescriptors();
277     for (VectorOf(AuthnAuthorityDescriptor)::size_type i=0; i<pdp.size(); ) {
278         try {
279             verifySignature(pdp[i]->getSignature(), entity.getEntityID());
280             i++;
281         }
282         catch (exception& e) {
283             auto_ptr_char id(entity.getEntityID());
284             m_log.warn(
285                 "filtering out PDPDescriptor for entity (%s) after failed signature check: %s", id.get(), e.what()
286                 );
287             pdp.erase(pdp.begin() + i);
288         }
289     }
290
291     VectorOf(AuthnQueryDescriptorType) authnq=entity.getAuthnQueryDescriptorTypes();
292     for (VectorOf(AuthnQueryDescriptorType)::size_type i=0; i<authnq.size(); ) {
293         try {
294             verifySignature(authnq[i]->getSignature(), entity.getEntityID());
295             i++;
296         }
297         catch (exception& e) {
298             auto_ptr_char id(entity.getEntityID());
299             m_log.warn(
300                 "filtering out AuthnQueryDescriptorType for entity (%s) after failed signature check: %s", id.get(), e.what()
301                 );
302             authnq.erase(authnq.begin() + i);
303         }
304     }
305
306     VectorOf(AttributeQueryDescriptorType) attrq=entity.getAttributeQueryDescriptorTypes();
307     for (VectorOf(AttributeQueryDescriptorType)::size_type i=0; i<attrq.size(); ) {
308         try {
309             verifySignature(attrq[i]->getSignature(), entity.getEntityID());
310             i++;
311         }
312         catch (exception& e) {
313             auto_ptr_char id(entity.getEntityID());
314             m_log.warn(
315                 "filtering out AttributeQueryDescriptorType for entity (%s) after failed signature check: %s", id.get(), e.what()
316                 );
317             attrq.erase(attrq.begin() + i);
318         }
319     }
320
321     VectorOf(AuthzDecisionQueryDescriptorType) authzq=entity.getAuthzDecisionQueryDescriptorTypes();
322     for (VectorOf(AuthzDecisionQueryDescriptorType)::size_type i=0; i<authzq.size(); ) {
323         try {
324             verifySignature(authzq[i]->getSignature(), entity.getEntityID());
325             i++;
326         }
327         catch (exception& e) {
328             auto_ptr_char id(entity.getEntityID());
329             m_log.warn(
330                 "filtering out AuthzDecisionQueryDescriptorType for entity (%s) after failed signature check: %s", id.get(), e.what()
331                 );
332             authzq.erase(authzq.begin() + i);
333         }
334     }
335
336     VectorOf(RoleDescriptor) v=entity.getRoleDescriptors();
337     for (VectorOf(RoleDescriptor)::size_type i=0; i<v.size(); ) {
338         try {
339             verifySignature(v[i]->getSignature(), entity.getEntityID());
340             i++;
341         }
342         catch (exception& e) {
343             auto_ptr_char id(entity.getEntityID());
344             m_log.warn(
345                 "filtering out role (%s) for entity (%s) after failed signature check: %s",
346                 v[i]->getElementQName().toString().c_str(), id.get(), e.what()
347                 );
348             v.erase(v.begin() + i);
349         }
350     }
351
352     if (entity.getAffiliationDescriptor()) {
353         try {
354             verifySignature(entity.getAffiliationDescriptor()->getSignature(), entity.getEntityID());
355         }
356         catch (exception& e) {
357             auto_ptr_char id(entity.getEntityID());
358             m_log.warn("filtering out affiliation from entity (%s) after failed signature check: %s", id.get(), e.what());
359             entity.setAffiliationDescriptor(NULL);
360         }
361     }
362 }
363
364 void SignatureMetadataFilter::verifySignature(Signature* sig, const XMLCh* peerName) const
365 {
366     if (!sig)
367         return;
368
369     m_profileValidator.validate(sig);
370
371     // Set up criteria.
372     CredentialCriteria cc;
373     cc.setUsage(Credential::SIGNING_CREDENTIAL);
374     cc.setSignature(*sig, CredentialCriteria::KEYINFO_EXTRACTION_KEY);
375
376     if (m_credResolver) {
377         if (peerName) {
378             auto_ptr_char pname(peerName);
379             cc.setPeerName(pname.get());
380         }
381         Locker locker(m_credResolver);
382         vector<const Credential*> creds;
383         if (m_credResolver->resolve(creds,&cc)) {
384             SignatureValidator sigValidator;
385             for (vector<const Credential*>::const_iterator i = creds.begin(); i != creds.end(); ++i) {
386                 try {
387                     sigValidator.setCredential(*i);
388                     sigValidator.validate(sig);
389                     return; // success!
390                 }
391                 catch (exception&) {
392                 }
393             }
394             throw MetadataFilterException("CredentialResolver did not supply a successful verification key.");
395         }
396         else {
397             throw MetadataFilterException("CredentialResolver did not supply a successful verification key.");
398         }
399     }
400     else if (m_trust) {
401         if (m_verifyName && peerName) {
402             auto_ptr_char pname(peerName);
403             cc.setPeerName(pname.get());
404         }
405         DummyCredentialResolver dummy;
406         if (m_trust->validate(*sig, dummy, &cc))
407             return;
408         throw MetadataFilterException("TrustEngine unable to verify signature.");
409     }
410
411     throw MetadataFilterException("Unable to verify signature.");
412 }