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.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
24 * Shibboleth-specific PKIX-validation TrustEngine.
28 #include "metadata/MetadataExt.h"
29 #include "security/PKIXTrustEngine.h"
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>
40 using namespace shibsp;
41 using namespace opensaml::saml2md;
42 using namespace xmlsignature;
43 using namespace xmltooling;
48 class SHIBSP_DLLLOCAL PKIXTrustEngine : public AbstractPKIXTrustEngine, public ObservableMetadataProvider::Observer
51 PKIXTrustEngine(const DOMElement* e=nullptr) : AbstractPKIXTrustEngine(e), m_credLock(RWLock::create()) {
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>());
62 AbstractPKIXTrustEngine::PKIXValidationInfoIterator* getPKIXValidationInfoIterator(
63 const CredentialResolver& pkixSource, CredentialCriteria* criteria=nullptr
66 void onEvent(const ObservableMetadataProvider& metadata) const {
67 // Destroy credentials we cached from this provider.
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>());
76 const KeyInfoResolver* getKeyInfoResolver() const {
77 return m_keyInfoResolver ? m_keyInfoResolver : XMLToolingConfig::getConfig().getKeyInfoResolver();
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;
87 SHIBSP_DLLLOCAL PluginManager<TrustEngine,string,const DOMElement*>::Factory PKIXTrustEngineFactory;
89 TrustEngine* SHIBSP_DLLLOCAL PKIXTrustEngineFactory(const DOMElement* const & e)
91 return new PKIXTrustEngine(e);
94 class SHIBSP_DLLLOCAL MetadataPKIXIterator : public AbstractPKIXTrustEngine::PKIXValidationInfoIterator
97 MetadataPKIXIterator(const PKIXTrustEngine& engine, const MetadataProvider& pkixSource, MetadataCredentialCriteria& criteria);
99 virtual ~MetadataPKIXIterator() {
101 m_engine.m_credLock->unlock();
102 for_each(m_ownedCreds.begin(), m_ownedCreds.end(), xmltooling::cleanup<Credential>());
107 int getVerificationDepth() const {
108 pair<bool,int> vd = m_current->getVerifyDepth();
109 return vd.first ? vd.second : 1;
112 const vector<XSECCryptoX509*>& getTrustAnchors() const {
116 const vector<XSECCryptoX509CRL*>& getCRLs() const {
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;
135 void shibsp::registerPKIXTrustEngine()
137 XMLToolingConfig::getConfig().TrustEngineManager.registerFactory(SHIBBOLETH_PKIX_TRUSTENGINE, PKIXTrustEngineFactory);
140 AbstractPKIXTrustEngine::PKIXValidationInfoIterator* PKIXTrustEngine::getPKIXValidationInfoIterator(
141 const CredentialResolver& pkixSource, CredentialCriteria* criteria
144 // Make sure these are metadata objects.
145 const MetadataProvider& metadata = dynamic_cast<const MetadataProvider&>(pkixSource);
146 MetadataCredentialCriteria* metacrit = dynamic_cast<MetadataCredentialCriteria*>(criteria);
148 throw MetadataException("Cannot obtain PKIX information without a MetadataCredentialCriteria object.");
150 return new MetadataPKIXIterator(*this, metadata,*metacrit);
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)
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);
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()) {
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()) {
173 // It's still brand new, so hook it for cache activation.
174 observable->addObserver(&m_engine);
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;
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();
186 // We've hooked the metadata for changes, and we know we can cache against it.
191 bool MetadataPKIXIterator::next()
193 // If we had an active block, look for another in the same block.
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++)) {
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();
209 m_extBlock = nullptr;
212 // If we get here, we try and find an Extensions block.
214 const EntityDescriptor* entity = dynamic_cast<const EntityDescriptor*>(m_obj);
216 m_extBlock = entity->getExtensions();
219 const EntitiesDescriptor* entities = dynamic_cast<const EntitiesDescriptor*>(m_obj);
221 m_extBlock = entities->getExtensions();
226 m_iter = m_extBlock->getUnknownXMLObjects().begin();
230 // Jump a level and try again.
231 m_obj = m_obj->getParent();
237 void MetadataPKIXIterator::populate()
239 // Dump anything old.
242 for_each(m_ownedCreds.begin(), m_ownedCreds.end(), xmltooling::cleanup<Credential>());
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());
252 m_crls.push_back((*c)->getCRL());
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());
264 m_ownedCreds.push_back(xcred);
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());
273 m_crls.push_back((*c)->getCRL());
276 // As a last step, if we're caching, try and elevate to a write lock for cache insertion.
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();
285 m_engine.m_credLock->unlock();
286 m_engine.m_credLock->rdlock();
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.