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