3459b61f1da5385f3346db5e53bc7168295edfd3
[shibboleth/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     // Favor an AuthnStatement Subject NameQualifier, but use Issuer if need be.
89     const XMLCh* ret=NULL;
90     Iterator<SAMLAssertion*> ia=r.getAssertions();
91     while (ia.hasNext()) {
92         SAMLAssertion* a=ia.next();
93         ret=a->getIssuer();
94         Iterator<SAMLStatement*> is=a->getStatements();
95         while (is.hasNext()) {
96             SAMLAuthenticationStatement* as=dynamic_cast<SAMLAuthenticationStatement*>(is.next());
97             if (as && as->getSubject()->getNameQualifier())
98                 return as->getSubject()->getNameQualifier();
99         }
100     }
101     return ret;
102 }
103
104 SAMLResponse* ShibPOSTProfile::accept(
105     const XMLByte* buf,
106     const XMLCh* recipient,
107     int ttlSeconds,
108     const saml::Iterator<const XMLCh*>& audiences,
109     XMLCh** pproviderId)
110 {
111     saml::NDC("accept");
112     Category& log=Category::getInstance(SHIB_LOGCAT".ShibPOSTProfile");
113  
114     // The built-in SAML functionality will do most of the basic non-crypto checks.
115     // Note that if the response only contains a status error, it gets tossed out
116     // as an exception.
117     auto_ptr<SAMLResponse> r(SAMLPOSTProfile::accept(buf, recipient, ttlSeconds, false));
118
119     // Now we do some more non-crypto (ie. cheap) work to match up the origin site
120     // with its associated data.
121     const SAMLAssertion* assertion = NULL;
122     const SAMLAuthenticationStatement* sso = NULL;
123
124     try {
125         assertion = getSSOAssertion(*(r.get()),audiences);
126         sso = getSSOStatement(*assertion);
127     }
128     catch (...) {
129         // We want to try our best to locate an origin site name so we can fill it in.
130         if (pproviderId)
131             *pproviderId=XMLString::replicate(getProviderId(*(r.get())));
132         throw;
133     }
134     
135     // Finish SAML processing.
136     SAMLPOSTProfile::process(*(r.get()), recipient, ttlSeconds);
137
138     // Try and locate metadata for the IdP. With this new version, we try Issuer first.
139     log.debug("searching metadata for assertion issuer...");
140     Metadata m(m_metadatas);
141     const IProvider* provider=m.lookup(assertion->getIssuer());
142     if (provider) {
143         if (pproviderId)
144             *pproviderId=XMLString::replicate(assertion->getIssuer());
145         log.debug("matched assertion issuer against metadata");
146     }
147     else {
148         // Might be a down-level origin.
149         provider=m.lookup(sso->getSubject()->getNameQualifier());
150         if (provider) {
151             if (pproviderId)
152                 *pproviderId=XMLString::replicate(sso->getSubject()->getNameQualifier());
153             log.debug("matched subject name qualifier against metadata");
154         }
155     }
156
157     // No metadata at all.        
158     if (!provider) {
159         auto_ptr_char issuer(assertion->getIssuer());
160         auto_ptr_char nq(sso->getSubject()->getNameQualifier());
161         log.error("assertion issuer not found in metadata (Issuer='%s', NameQualifier='%s'",
162             issuer.get(), (nq.get() ? nq.get() : "null"));
163         throw MetadataException("ShibPOSTProfile::accept() metadata lookup failed, unable to process assertion");
164     }
165
166     // Is this provider an IdP?
167     Iterator<const IProviderRole*> roles=provider->getRoles();
168     while (roles.hasNext()) {
169         const IProviderRole* role=roles.next();
170         if (dynamic_cast<const IIDPProviderRole*>(role)) {
171             // Check for Shibboleth 1.x protocol support.
172             if (role->hasSupport(Constants::SHIB_NS)) {
173                 log.debug("passing response to trust layer");
174                 
175                 // Use this role to evaluate the signature.
176                 Trust t(m_trusts);
177                 if (!t.validate(m_revocations,role,*r))
178                     throw TrustException("ShibPOSTProfile::accept() unable to verify signed response");
179                 
180                 // Assertion(s) signed?
181                 Iterator<SAMLAssertion*> itera=r->getAssertions();
182                 while (itera.hasNext()) {
183                     SAMLAssertion* _a=itera.next();
184                     if (_a->isSigned()) {
185                         log.debug("passing signed assertion to trust layer"); 
186                         if (!t.validate(m_revocations,role,*_a))
187                             throw TrustException("ShibPOSTProfile::accept() unable to verify signed assertion");
188                     }
189                 }
190                 return r.release();
191             }
192         }
193     }
194
195     auto_ptr_char issuer(assertion->getIssuer());
196     auto_ptr_char nq(sso->getSubject()->getNameQualifier());
197     log.error("metadata for assertion issuer indicates no SAML 1.x identity provider role (Issuer='%s', NameQualifier='%s'",
198         issuer.get(), (nq.get() ? nq.get() : "null"));
199     throw MetadataException("ShibPOSTProfile::accept() metadata lookup failed, issuer not registered as SAML identity provider");
200 }
201
202 SAMLResponse* ShibPOSTProfile::prepare(
203     const IIDPProviderRole* role,
204     const char* credResolverId,
205     const XMLCh* recipient,
206     const XMLCh* authMethod,
207     time_t authInstant,
208     const XMLCh* name,
209     const XMLCh* format,
210     const XMLCh* nameQualifier,
211     const XMLCh* subjectIP,
212     const saml::Iterator<const XMLCh*>& audiences,
213     const saml::Iterator<saml::SAMLAuthorityBinding*>& bindings)
214 {
215 #ifdef WIN32
216     struct tm* ptime=gmtime(&authInstant);
217 #else
218     struct tm res;
219     struct tm* ptime=gmtime_r(&authInstant,&res);
220 #endif
221     char timebuf[32];
222     strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
223     auto_ptr_XMLCh timeptr(timebuf);
224     XMLDateTime authDateTime(timeptr.get());
225     authDateTime.parseDateTime();
226
227     SAMLResponse* r = SAMLPOSTProfile::prepare(
228         recipient,
229         role->getProvider()->getId(),
230         audiences,
231         name,
232         nameQualifier,
233         format,
234         subjectIP,
235         authMethod,
236         authDateTime,
237         bindings
238         );
239
240     Credentials c(m_creds);
241     const ICredResolver* cr=c.lookup(credResolverId);
242     if (!cr) {
243         delete r;
244         throw CredentialException("ShibPOSTProfile::prepare() unable to access credential resolver");
245     }
246     XSECCryptoKey* key=cr->getKey();
247     if (!key) {
248         delete r;
249         throw CredentialException("ShibPOSTProfile::prepare() unable to resolve signing key");
250     }
251     
252     r->sign(SIGNATURE_RSA,key,cr->getCertificates());
253     return r;
254 }
255
256 bool ShibPOSTProfile::checkReplayCache(const SAMLAssertion& a)
257 {
258     // Default implementation uses the basic replay cache implementation.
259     return SAMLPOSTProfile::checkReplayCache(a);
260 }