39cc2326e58e7d7196589caa24513714957ce16e
[shibboleth/cpp-sp.git] / shibsp / security / PKIXTrustEngine.cpp
1 /*
2  *  Copyright 2001-2010 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  * PKIXTrustEngine.cpp
19  * 
20  * Shibboleth-specific PKIX-validation TrustEngine.
21  */
22
23 #include "internal.h"
24 #include "metadata/MetadataExt.h"
25 #include "security/PKIXTrustEngine.h"
26
27 #include <saml/saml2/metadata/Metadata.h>
28 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
29 #include <saml/saml2/metadata/ObservableMetadataProvider.h>
30 #include <xmltooling/XMLToolingConfig.h>
31 #include <xmltooling/security/AbstractPKIXTrustEngine.h>
32 #include <xmltooling/security/KeyInfoResolver.h>
33 #include <xmltooling/security/X509Credential.h>
34 #include <xmltooling/util/Threads.h>
35
36 using namespace shibsp;
37 using namespace opensaml::saml2md;
38 using namespace xmlsignature;
39 using namespace xmltooling;
40 using namespace std;
41
42 namespace shibsp {
43
44     class SHIBSP_DLLLOCAL PKIXTrustEngine : public AbstractPKIXTrustEngine, public ObservableMetadataProvider::Observer
45     {
46     public:
47         PKIXTrustEngine(const DOMElement* e=nullptr) : AbstractPKIXTrustEngine(e), m_credLock(RWLock::create()) {
48         }
49         virtual ~PKIXTrustEngine() {
50             for (map<const ObservableMetadataProvider*,credmap_t>::iterator i=m_credentialMap.begin(); i!=m_credentialMap.end(); ++i) {
51                 i->first->removeObserver(this);
52                 for (credmap_t::iterator creds = i->second.begin(); creds!=i->second.end(); ++creds)
53                     for_each(creds->second.begin(), creds->second.end(), xmltooling::cleanup<X509Credential>());
54             }
55             delete m_credLock;
56         }
57         
58         AbstractPKIXTrustEngine::PKIXValidationInfoIterator* getPKIXValidationInfoIterator(
59             const CredentialResolver& pkixSource, CredentialCriteria* criteria=nullptr
60             ) const;
61
62         void onEvent(const ObservableMetadataProvider& metadata) const {
63             // Destroy credentials we cached from this provider.
64             m_credLock->wrlock();
65             credmap_t& cmap = m_credentialMap[&metadata];
66             for (credmap_t::iterator creds = cmap.begin(); creds!=cmap.end(); ++creds)
67                 for_each(creds->second.begin(), creds->second.end(), xmltooling::cleanup<X509Credential>());
68             cmap.clear();
69             m_credLock->unlock();
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         mutable RWLock* m_credLock;
79         typedef map< const KeyAuthority*,vector<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             for_each(m_ownedCreds.begin(), m_ownedCreds.end(), xmltooling::cleanup<Credential>());
99         }
100
101         bool next();
102
103         int getVerificationDepth() const {
104             pair<bool,int> vd = m_current->getVerifyDepth();
105             return vd.first ? vd.second : 1;
106         }
107         
108         const vector<XSECCryptoX509*>& getTrustAnchors() const {
109             return m_certs;
110         }
111
112         const vector<XSECCryptoX509CRL*>& getCRLs() const {
113             return m_crls;
114         }
115     
116     private:
117         void populate();
118         bool m_caching;
119         const PKIXTrustEngine& m_engine;
120         map<const ObservableMetadataProvider*,PKIXTrustEngine::credmap_t>::iterator m_credCache;
121         const XMLObject* m_obj;
122         const Extensions* m_extBlock;
123         const KeyAuthority* m_current;
124         vector<XMLObject*>::const_iterator m_iter;
125         vector<XSECCryptoX509*> m_certs;
126         vector<XSECCryptoX509CRL*> m_crls;
127         vector<X509Credential*> m_ownedCreds;
128     };
129 };
130
131 void shibsp::registerPKIXTrustEngine()
132 {
133     XMLToolingConfig::getConfig().TrustEngineManager.registerFactory(SHIBBOLETH_PKIX_TRUSTENGINE, PKIXTrustEngineFactory);
134 }
135
136 AbstractPKIXTrustEngine::PKIXValidationInfoIterator* PKIXTrustEngine::getPKIXValidationInfoIterator(
137     const CredentialResolver& pkixSource, CredentialCriteria* criteria
138     ) const
139 {
140     // Make sure these are metadata objects.
141     const MetadataProvider& metadata = dynamic_cast<const MetadataProvider&>(pkixSource);
142     MetadataCredentialCriteria* metacrit = dynamic_cast<MetadataCredentialCriteria*>(criteria);
143     if (!metacrit)
144         throw MetadataException("Cannot obtain PKIX information without a MetadataCredentialCriteria object.");
145
146     return new MetadataPKIXIterator(*this, metadata,*metacrit);
147 }
148
149 MetadataPKIXIterator::MetadataPKIXIterator(
150     const PKIXTrustEngine& engine, const MetadataProvider& pkixSource, MetadataCredentialCriteria& criteria
151     ) : m_caching(false), m_engine(engine), m_obj(criteria.getRole().getParent()), m_extBlock(nullptr), m_current(nullptr)
152 {
153     // If we can't hook the metadata for changes, then we can't do any caching and the rest of this is academic.
154     const ObservableMetadataProvider* observable = dynamic_cast<const ObservableMetadataProvider*>(&pkixSource);
155     if (!observable)
156         return;
157
158     // While holding read lock, see if this metadata plugin has been seen before.
159     m_engine.m_credLock->rdlock();
160     m_credCache = m_engine.m_credentialMap.find(observable);
161     if (m_credCache==m_engine.m_credentialMap.end()) {
162
163         // We need to elevate the lock and retry.
164         m_engine.m_credLock->unlock();
165         m_engine.m_credLock->wrlock();
166         m_credCache = m_engine.m_credentialMap.find(observable);
167         if (m_credCache==m_engine.m_credentialMap.end()) {
168
169             // It's still brand new, so hook it for cache activation.
170             observable->addObserver(&m_engine);
171
172             // Prime the map reference with an empty credential map.
173             m_credCache = m_engine.m_credentialMap.insert(make_pair(observable,PKIXTrustEngine::credmap_t())).first;
174             
175             // Downgrade the lock.
176             // We don't have to recheck because we never erase the master map entry entirely, even on changes.
177             m_engine.m_credLock->unlock();
178             m_engine.m_credLock->rdlock();
179         }
180     }
181     
182     // We've hooked the metadata for changes, and we know we can cache against it.
183     m_caching = true;
184 }
185
186
187 bool MetadataPKIXIterator::next()
188 {
189     // If we had an active block, look for another in the same block.
190     if (m_extBlock) {
191         // Keep going until we hit the end of the block.
192         vector<XMLObject*>::const_iterator end = m_extBlock->getUnknownXMLObjects().end();
193         while (m_iter != end) {
194             // If we hit a KeyAuthority, remember it and signal.
195             if (m_current=dynamic_cast<KeyAuthority*>(*m_iter++)) {
196                 populate();
197                 return true;
198             }
199         }
200         
201         // If we get here, we hit the end of this Extensions block.
202         // Climb a level, if possible.
203         m_obj = m_obj->getParent();
204         m_current = nullptr;
205         m_extBlock = nullptr;
206     }
207
208     // If we get here, we try and find an Extensions block.
209     while (m_obj) {
210         const EntityDescriptor* entity = dynamic_cast<const EntityDescriptor*>(m_obj);
211         if (entity) {
212             m_extBlock = entity->getExtensions();
213         }
214         else {
215             const EntitiesDescriptor* entities = dynamic_cast<const EntitiesDescriptor*>(m_obj);
216             if (entities) {
217                 m_extBlock = entities->getExtensions();
218             }
219         }
220         
221         if (m_extBlock) {
222             m_iter = m_extBlock->getUnknownXMLObjects().begin();
223             return next();
224         }
225         
226         // Jump a level and try again.
227         m_obj = m_obj->getParent();
228     }
229
230     return false;
231 }
232
233 void MetadataPKIXIterator::populate()
234 {
235     // Dump anything old.
236     m_certs.clear();
237     m_crls.clear();
238     for_each(m_ownedCreds.begin(), m_ownedCreds.end(), xmltooling::cleanup<Credential>());
239
240     if (m_caching) {
241         // We're holding a read lock. Search for "resolved" creds.
242         PKIXTrustEngine::credmap_t::iterator cached = m_credCache->second.find(m_current);
243         if (cached!=m_credCache->second.end()) {
244             // Copy over the information.
245             for (vector<X509Credential*>::const_iterator c=cached->second.begin(); c!=cached->second.end(); ++c) {
246                 m_certs.insert(m_certs.end(), (*c)->getEntityCertificateChain().begin(), (*c)->getEntityCertificateChain().end());
247                 if ((*c)->getCRL())
248                     m_crls.push_back((*c)->getCRL());
249             }
250             return;
251         }
252     }
253
254     // We're either not caching or didn't find the results we need, so we have to resolve them.
255     const vector<KeyInfo*>& keyInfos = m_current->getKeyInfos();
256     for (vector<KeyInfo*>::const_iterator k = keyInfos.begin(); k!=keyInfos.end(); ++k) {
257         auto_ptr<Credential> cred (m_engine.getKeyInfoResolver()->resolve(*k, X509Credential::RESOLVE_CERTS | X509Credential::RESOLVE_CRLS));
258         X509Credential* xcred = dynamic_cast<X509Credential*>(cred.get());
259         if (xcred) {
260             m_ownedCreds.push_back(xcred);
261             cred.release();
262         }
263     }
264
265     // Copy over the new information.
266     for (vector<X509Credential*>::const_iterator c=m_ownedCreds.begin(); c!=m_ownedCreds.end(); ++c) {
267         m_certs.insert(m_certs.end(), (*c)->getEntityCertificateChain().begin(), (*c)->getEntityCertificateChain().end());
268         if ((*c)->getCRL())
269             m_crls.push_back((*c)->getCRL());
270     }
271
272     // As a last step, if we're caching, try and elevate to a write lock for cache insertion.
273     if (m_caching) {
274         m_engine.m_credLock->unlock();
275         m_engine.m_credLock->wrlock();
276         if (m_credCache->second.count(m_current)==0) {
277             // Transfer objects into cache.
278             m_credCache->second[m_current] = m_ownedCreds;
279             m_ownedCreds.clear();
280         }
281         m_engine.m_credLock->unlock();
282         m_engine.m_credLock->rdlock();
283
284         // In theory we could have lost the objects but that shouldn't be possible
285         // since the metadata itself is locked and shouldn't change behind us.
286     }
287 }