86c67d063d605df21d51be2c71575dba28ab416f
[shibboleth/cpp-sp.git] / shib / ShibPOSTProfile.cpp
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /* ShibPOSTProfile.cpp - Shibboleth-specific wrapper around SAML POST profile
51
52    Scott Cantor
53    8/12/02
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <ctime>
61
62 #include <openssl/x509v3.h>
63
64 using namespace shibboleth;
65 using namespace saml;
66 using namespace log4cpp;
67 using namespace std;
68
69 ShibPOSTProfile::ShibPOSTProfile(
70     const Iterator<IMetadata*>& metadatas,
71     const Iterator<IRevocation*>& revocations,
72     const Iterator<ITrust*>& trusts,
73     const Iterator<ICredentials*>& creds
74     ) : m_metadatas(metadatas), m_revocations(revocations), m_trusts(trusts), m_creds(creds) {}
75
76 const SAMLAssertion* ShibPOSTProfile::getSSOAssertion(const SAMLResponse& r, const Iterator<const XMLCh*>& audiences)
77 {
78     return SAMLPOSTProfile::getSSOAssertion(r,audiences);
79 }
80
81 const SAMLAuthenticationStatement* ShibPOSTProfile::getSSOStatement(const SAMLAssertion& a)
82 {
83     return SAMLPOSTProfile::getSSOStatement(a);
84 }
85
86 const XMLCh* ShibPOSTProfile::getProviderId(const saml::SAMLResponse& r)
87 {
88     // Switch to Issuer by itself, stop using NameQualifier.
89     Iterator<SAMLAssertion*> ia=r.getAssertions();
90     if (ia.hasNext())
91         return (ia.next())->getIssuer();
92     return NULL;
93 }
94
95 SAMLResponse* ShibPOSTProfile::accept(
96     const XMLByte* buf,
97     const XMLCh* recipient,
98     int ttlSeconds,
99     const saml::Iterator<const XMLCh*>& audiences,
100     XMLCh** pproviderId)
101 {
102     saml::NDC("accept");
103     Category& log=Category::getInstance(SHIB_LOGCAT".ShibPOSTProfile");
104  
105     // The built-in SAML functionality will do most of the basic non-crypto checks.
106     // Note that if the response only contains a status error, it gets tossed out
107     // as an exception.
108     auto_ptr<SAMLResponse> r(SAMLPOSTProfile::accept(buf, recipient, ttlSeconds, false));
109
110     // Now we do some more non-crypto (ie. cheap) work to match up the origin site
111     // with its associated data.
112     const SAMLAssertion* assertion = NULL;
113     const SAMLAuthenticationStatement* sso = NULL;
114
115     try {
116         assertion = getSSOAssertion(*(r.get()),audiences);
117         sso = getSSOStatement(*assertion);
118     }
119     catch (...) {
120         // We want to try our best to locate an origin site name so we can fill it in.
121         if (pproviderId)
122             *pproviderId=XMLString::replicate(getProviderId(*(r.get())));
123         throw;
124     }
125     
126     // Finish SAML processing.
127     SAMLPOSTProfile::process(*(r.get()), recipient, ttlSeconds);
128
129     // Try and locate metadata for the IdP. With this new version, we try Issuer first.
130     log.debug("searching metadata for assertion issuer...");
131     Metadata m(m_metadatas);
132     const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
133     if (provider) {
134         if (pproviderId)
135             *pproviderId=XMLString::replicate(assertion->getIssuer());
136         log.debug("matched assertion issuer against metadata");
137     }
138     else {
139         // Might be a down-level origin.
140         provider=m.lookup(sso->getSubject()->getNameIdentifier()->getNameQualifier());
141         if (provider) {
142             if (pproviderId)
143                 *pproviderId=XMLString::replicate(sso->getSubject()->getNameIdentifier()->getNameQualifier());
144             log.debug("matched subject name qualifier against metadata");
145         }
146     }
147
148     // No metadata at all.        
149     if (!provider) {
150         auto_ptr_char issuer(assertion->getIssuer());
151         auto_ptr_char nq(sso->getSubject()->getNameIdentifier()->getNameQualifier());
152         log.error("assertion issuer not found in metadata (Issuer='%s', NameQualifier='%s'",
153             issuer.get(), (nq.get() ? nq.get() : "null"));
154         throw MetadataException("ShibPOSTProfile::accept() metadata lookup failed, unable to process assertion");
155     }
156
157     // Is this provider an IdP?
158     const IIDPSSODescriptor* role=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
159     if (role) {
160         log.debug("passing response to trust layer");
161         
162         // Use this role to evaluate the signature.
163         Trust t(m_trusts);
164         if (!t.validate(m_revocations,role,*r))
165             throw TrustException("ShibPOSTProfile::accept() unable to verify signed response");
166         
167         // Assertion(s) signed?
168         Iterator<SAMLAssertion*> itera=r->getAssertions();
169         while (itera.hasNext()) {
170             SAMLAssertion* _a=itera.next();
171             if (_a->isSigned()) {
172                 log.debug("passing signed assertion to trust layer"); 
173                 if (!t.validate(m_revocations,role,*_a))
174                     throw TrustException("ShibPOSTProfile::accept() unable to verify signed assertion");
175             }
176         }
177         return r.release();
178     }
179
180     auto_ptr_char issuer(assertion->getIssuer());
181     auto_ptr_char nq(sso->getSubject()->getNameIdentifier()->getNameQualifier());
182     log.error("metadata for assertion issuer indicates no SAML 1.x identity provider role (Issuer='%s', NameQualifier='%s'",
183         issuer.get(), (nq.get() ? nq.get() : "null"));
184     throw MetadataException("ShibPOSTProfile::accept() metadata lookup failed, issuer not registered as SAML 1.x identity provider");
185 }
186
187 SAMLResponse* ShibPOSTProfile::prepare(
188     const IIDPSSODescriptor* role,
189     const char* credResolverId,
190     const XMLCh* recipient,
191     const XMLCh* authMethod,
192     const SAMLDateTime& authInstant,
193     const XMLCh* name,
194     const XMLCh* format,
195     const XMLCh* nameQualifier,
196     const XMLCh* subjectIP,
197     const saml::Iterator<const XMLCh*>& audiences,
198     const saml::Iterator<saml::SAMLAuthorityBinding*>& bindings)
199 {
200     SAMLResponse* r = SAMLPOSTProfile::prepare(
201         recipient,
202         role->getEntityDescriptor()->getId(),
203         audiences,
204         name,
205         nameQualifier,
206         format,
207         subjectIP,
208         authMethod,
209         authInstant,
210         bindings
211         );
212
213     Credentials c(m_creds);
214     const ICredResolver* cr=c.lookup(credResolverId);
215     if (!cr) {
216         delete r;
217         throw CredentialException("ShibPOSTProfile::prepare() unable to access credential resolver");
218     }
219     XSECCryptoKey* key=cr->getKey();
220     if (!key) {
221         delete r;
222         throw CredentialException("ShibPOSTProfile::prepare() unable to resolve signing key");
223     }
224     
225     r->sign(SIGNATURE_RSA,key,cr->getCertificates());
226     return r;
227 }
228
229 bool ShibPOSTProfile::checkReplayCache(const SAMLAssertion& a)
230 {
231     // Default implementation uses the basic replay cache implementation.
232     return SAMLPOSTProfile::checkReplayCache(a);
233 }