Remove extra header
[shibboleth/cpp-sp.git] / shibsp / security / PKIXTrustEngine.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * PKIXTrustEngine.cpp
23  * 
24  * Shibboleth-specific PKIX-validation TrustEngine.
25  */
26
27 #include "internal.h"
28 #include "metadata/MetadataExt.h"
29 #include "security/PKIXTrustEngine.h"
30
31 #include <boost/shared_ptr.hpp>
32 #include <saml/saml2/metadata/Metadata.h>
33 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
34 #include <saml/saml2/metadata/ObservableMetadataProvider.h>
35 #include <xmltooling/XMLToolingConfig.h>
36 #include <xmltooling/security/AbstractPKIXTrustEngine.h>
37 #include <xmltooling/security/KeyInfoResolver.h>
38 #include <xmltooling/security/X509Credential.h>
39 #include <xmltooling/util/Threads.h>
40
41 using namespace shibsp;
42 using namespace opensaml::saml2md;
43 using namespace xmlsignature;
44 using namespace xmltooling;
45 using namespace boost;
46 using namespace std;
47
48 namespace shibsp {
49
50     class SHIBSP_DLLLOCAL PKIXTrustEngine : public AbstractPKIXTrustEngine, public ObservableMetadataProvider::Observer
51     {
52     public:
53         PKIXTrustEngine(const DOMElement* e=nullptr) : AbstractPKIXTrustEngine(e), m_credLock(RWLock::create()) {
54         }
55         virtual ~PKIXTrustEngine() {
56             for (map<const ObservableMetadataProvider*,credmap_t>::iterator i=m_credentialMap.begin(); i!=m_credentialMap.end(); ++i) {
57                 i->first->removeObserver(this);
58             }
59         }
60         
61         AbstractPKIXTrustEngine::PKIXValidationInfoIterator* getPKIXValidationInfoIterator(
62             const CredentialResolver& pkixSource, CredentialCriteria* criteria=nullptr
63             ) const;
64
65         void onEvent(const ObservableMetadataProvider& metadata) const {
66             // Destroy credentials we cached from this provider.
67             m_credLock->wrlock();
68             SharedLock locker(m_credLock, false);
69             m_credentialMap[&metadata].clear();
70         }
71
72         const KeyInfoResolver* getKeyInfoResolver() const {
73             return m_keyInfoResolver ? m_keyInfoResolver : XMLToolingConfig::getConfig().getKeyInfoResolver();
74         }
75
76     private:
77         friend class SHIBSP_DLLLOCAL MetadataPKIXIterator;
78         scoped_ptr<RWLock> m_credLock;
79         typedef map< const KeyAuthority*,vector< boost::shared_ptr<X509Credential> > > credmap_t;
80         mutable map<const ObservableMetadataProvider*,credmap_t> m_credentialMap;
81     };
82     
83     SHIBSP_DLLLOCAL PluginManager<TrustEngine,string,const DOMElement*>::Factory PKIXTrustEngineFactory;
84
85     TrustEngine* SHIBSP_DLLLOCAL PKIXTrustEngineFactory(const DOMElement* const & e)
86     {
87         return new PKIXTrustEngine(e);
88     }
89
90     class SHIBSP_DLLLOCAL MetadataPKIXIterator : public AbstractPKIXTrustEngine::PKIXValidationInfoIterator
91     {
92     public:
93         MetadataPKIXIterator(const PKIXTrustEngine& engine, const MetadataProvider& pkixSource, MetadataCredentialCriteria& criteria);
94
95         virtual ~MetadataPKIXIterator() {
96             if (m_caching)
97                 m_engine.m_credLock->unlock();
98         }
99
100         bool next();
101
102         int getVerificationDepth() const {
103             pair<bool,int> vd = m_current->getVerifyDepth();
104             return vd.first ? vd.second : 1;
105         }
106         
107         const vector<XSECCryptoX509*>& getTrustAnchors() const {
108             return m_certs;
109         }
110
111         const vector<XSECCryptoX509CRL*>& getCRLs() const {
112             return m_crls;
113         }
114     
115     private:
116         void populate();
117         bool m_caching;
118         const PKIXTrustEngine& m_engine;
119         map<const ObservableMetadataProvider*,PKIXTrustEngine::credmap_t>::iterator m_credCache;
120         const XMLObject* m_obj;
121         const Extensions* m_extBlock;
122         const KeyAuthority* m_current;
123         vector<XMLObject*>::const_iterator m_iter;
124         vector<XSECCryptoX509*> m_certs;
125         vector<XSECCryptoX509CRL*> m_crls;
126         vector< boost::shared_ptr<X509Credential> > m_ownedCreds;
127     };
128 };
129
130 void shibsp::registerPKIXTrustEngine()
131 {
132     XMLToolingConfig::getConfig().TrustEngineManager.registerFactory(SHIBBOLETH_PKIX_TRUSTENGINE, PKIXTrustEngineFactory);
133 }
134
135 AbstractPKIXTrustEngine::PKIXValidationInfoIterator* PKIXTrustEngine::getPKIXValidationInfoIterator(
136     const CredentialResolver& pkixSource, CredentialCriteria* criteria
137     ) const
138 {
139     // Make sure these are metadata objects.
140     const MetadataProvider& metadata = dynamic_cast<const MetadataProvider&>(pkixSource);
141     MetadataCredentialCriteria* metacrit = dynamic_cast<MetadataCredentialCriteria*>(criteria);
142     if (!metacrit)
143         throw MetadataException("Cannot obtain PKIX information without a MetadataCredentialCriteria object.");
144
145     return new MetadataPKIXIterator(*this, metadata,*metacrit);
146 }
147
148 MetadataPKIXIterator::MetadataPKIXIterator(
149     const PKIXTrustEngine& engine, const MetadataProvider& pkixSource, MetadataCredentialCriteria& criteria
150     ) : m_caching(false), m_engine(engine), m_obj(criteria.getRole().getParent()), m_extBlock(nullptr), m_current(nullptr)
151 {
152     // If we can't hook the metadata for changes, then we can't do any caching and the rest of this is academic.
153     const ObservableMetadataProvider* observable = dynamic_cast<const ObservableMetadataProvider*>(&pkixSource);
154     if (!observable)
155         return;
156
157     // While holding read lock, see if this metadata plugin has been seen before.
158     m_engine.m_credLock->rdlock();
159     m_credCache = m_engine.m_credentialMap.find(observable);
160     if (m_credCache == m_engine.m_credentialMap.end()) {
161
162         // We need to elevate the lock and retry.
163         m_engine.m_credLock->unlock();
164         m_engine.m_credLock->wrlock();
165         m_credCache = m_engine.m_credentialMap.find(observable);
166         if (m_credCache == m_engine.m_credentialMap.end()) {
167             try {
168                 // It's still brand new, so hook it for cache activation.
169                 observable->addObserver(&m_engine);
170
171                 // Prime the map reference with an empty credential map.
172                 m_credCache = m_engine.m_credentialMap.insert(make_pair(observable,PKIXTrustEngine::credmap_t())).first;
173             }
174             catch (std::exception&) {
175                 // The destructor won't run if we throw here, so we need to unlock.
176                 m_engine.m_credLock->unlock();
177                 throw;
178             }
179             
180             // Downgrade the lock.
181             // We don't have to recheck because we never erase the master map entry entirely, even on changes.
182             m_engine.m_credLock->unlock();
183             m_engine.m_credLock->rdlock();
184         }
185     }
186     
187     // We've hooked the metadata for changes, and we know we can cache against it.
188     m_caching = true;
189 }
190
191
192 bool MetadataPKIXIterator::next()
193 {
194     // If we had an active block, look for another in the same block.
195     if (m_extBlock) {
196         // Keep going until we hit the end of the block.
197         vector<XMLObject*>::const_iterator end = m_extBlock->getUnknownXMLObjects().end();
198         while (m_iter != end) {
199             // If we hit a KeyAuthority, remember it and signal.
200             if (m_current=dynamic_cast<KeyAuthority*>(*m_iter++)) {
201                 populate();
202                 return true;
203             }
204         }
205         
206         // If we get here, we hit the end of this Extensions block.
207         // Climb a level, if possible.
208         m_obj = m_obj->getParent();
209         m_current = nullptr;
210         m_extBlock = nullptr;
211     }
212
213     // If we get here, we try and find an Extensions block.
214     while (m_obj) {
215         const EntityDescriptor* entity = dynamic_cast<const EntityDescriptor*>(m_obj);
216         if (entity) {
217             m_extBlock = entity->getExtensions();
218         }
219         else {
220             const EntitiesDescriptor* entities = dynamic_cast<const EntitiesDescriptor*>(m_obj);
221             if (entities) {
222                 m_extBlock = entities->getExtensions();
223             }
224         }
225         
226         if (m_extBlock) {
227             m_iter = m_extBlock->getUnknownXMLObjects().begin();
228             return next();
229         }
230         
231         // Jump a level and try again.
232         m_obj = m_obj->getParent();
233     }
234
235     return false;
236 }
237
238 void MetadataPKIXIterator::populate()
239 {
240     // Dump anything old.
241     m_certs.clear();
242     m_crls.clear();
243     m_ownedCreds.clear();
244
245     if (m_caching) {
246         // We're holding a read lock. Search for "resolved" creds.
247         PKIXTrustEngine::credmap_t::iterator cached = m_credCache->second.find(m_current);
248         if (cached != m_credCache->second.end()) {
249             // Copy over the information.
250             for (vector< boost::shared_ptr<X509Credential> >::const_iterator c = cached->second.begin(); c != cached->second.end(); ++c) {
251                 m_certs.insert(m_certs.end(), (*c)->getEntityCertificateChain().begin(), (*c)->getEntityCertificateChain().end());
252                 if ((*c)->getCRL())
253                     m_crls.push_back((*c)->getCRL());
254             }
255             return;
256         }
257     }
258
259     // We're either not caching or didn't find the results we need, so we have to resolve them.
260     const vector<KeyInfo*>& keyInfos = m_current->getKeyInfos();
261     for (vector<KeyInfo*>::const_iterator k = keyInfos.begin(); k != keyInfos.end(); ++k) {
262         boost::shared_ptr<Credential> cred(m_engine.getKeyInfoResolver()->resolve(*k, X509Credential::RESOLVE_CERTS | X509Credential::RESOLVE_CRLS));
263         boost::shared_ptr<X509Credential> xcred = boost::dynamic_pointer_cast<X509Credential>(cred);
264         if (xcred)
265             m_ownedCreds.push_back(xcred);
266     }
267
268     // Copy over the new information.
269     for (vector< boost::shared_ptr<X509Credential> >::const_iterator c = m_ownedCreds.begin(); c != m_ownedCreds.end(); ++c) {
270         m_certs.insert(m_certs.end(), (*c)->getEntityCertificateChain().begin(), (*c)->getEntityCertificateChain().end());
271         if ((*c)->getCRL())
272             m_crls.push_back((*c)->getCRL());
273     }
274
275     // As a last step, if we're caching, try and elevate to a write lock for cache insertion.
276     if (m_caching) {
277         m_engine.m_credLock->unlock();
278         m_engine.m_credLock->wrlock();
279         if (m_credCache->second.count(m_current) == 0) {
280             // Transfer objects into cache.
281             m_credCache->second[m_current] = m_ownedCreds;
282             m_ownedCreds.clear();
283         }
284         m_engine.m_credLock->unlock();
285         m_engine.m_credLock->rdlock();
286
287         // In theory we could have lost the objects but that shouldn't be possible
288         // since the metadata itself is locked and shouldn't change behind us.
289     }
290 }