Next integration phase, metadata and trust conversion.
[shibboleth/cpp-sp.git] / shib / ShibBrowserProfile.cpp
1 /*
2  *  Copyright 2001-2005 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 /* ShibBrowserProfile.cpp - Shibboleth-specific wrapper around SAML browser profile
18
19    Scott Cantor
20    2/6/05
21
22    $History:$
23 */
24
25 #include "internal.h"
26
27 #include <ctime>
28 #include <openssl/x509v3.h>
29 #include <saml/saml1/core/Protocols.h>
30 #include <xmltooling/XMLToolingConfig.h>
31 #include <xmltooling/util/NDC.h>
32
33 using namespace shibboleth;
34 using namespace saml;
35 using namespace opensaml::saml1p;
36 using namespace opensaml::saml2md;
37 using namespace xmltooling;
38 using namespace log4cpp;
39 using namespace std;
40
41 ShibBrowserProfile::ShibBrowserProfile(
42     const ITokenValidator* validator, MetadataProvider* metadata, TrustEngine* trust
43     ) : m_validator(validator), m_metadata(metadata), m_trust(trust)
44 {
45     m_profile=SAMLBrowserProfile::getInstance();
46 }
47
48 ShibBrowserProfile::~ShibBrowserProfile()
49 {
50     delete m_profile;
51 }
52
53 SAMLBrowserProfile::BrowserProfileResponse ShibBrowserProfile::receive(
54     const char* samlResponse,
55     const XMLCh* recipient,
56     saml::IReplayCache* replayCache,
57     int minorVersion
58     ) const
59 {
60 #ifdef _DEBUG
61     xmltooling::NDC("recieve");
62 #endif
63     Category& log=Category::getInstance(SHIB_LOGCAT".ShibBrowserProfile");
64  
65     // The built-in SAML functionality will do most of the basic non-crypto checks.
66     // Note that if the response only contains a status error, it gets tossed out
67     // as an exception.
68     SAMLBrowserProfile::BrowserProfileResponse bpr=m_profile->receive(samlResponse, recipient, replayCache, minorVersion);
69     
70     try {
71         postprocess(bpr,minorVersion);
72         return bpr;
73     }
74     catch (...) {
75         bpr.clear();
76         throw;
77     }
78 }
79
80 SAMLBrowserProfile::BrowserProfileResponse ShibBrowserProfile::receive(
81     Iterator<const char*> artifacts,
82     const XMLCh* recipient,
83     SAMLBrowserProfile::ArtifactMapper* artifactMapper,
84     IReplayCache* replayCache,
85     int minorVersion
86     ) const
87 {
88     // The built-in SAML functionality will do most of the basic non-crypto checks.
89     // Note that if the response only contains a status error, it gets tossed out
90     // as an exception.
91     SAMLBrowserProfile::BrowserProfileResponse bpr=m_profile->receive(artifacts, recipient, artifactMapper, replayCache, minorVersion);
92     
93     try {
94         postprocess(bpr,minorVersion);
95         return bpr;
96     }
97     catch (...) {
98         bpr.clear();
99         throw;
100     }
101 }
102
103 void ShibBrowserProfile::postprocess(SAMLBrowserProfile::BrowserProfileResponse& bpr, int minorVersion) const
104 {
105 #ifdef _DEBUG
106     xmltooling::NDC("postprocess");
107 #endif
108     Category& log=Category::getInstance(SHIB_LOGCAT".ShibBrowserProfile");
109
110     if (!m_metadata)
111         throw MetadataException("No metadata found, unable to process assertion.");
112
113     // Try and locate metadata for the IdP. We try Issuer first.
114     log.debug("searching metadata for assertion issuer...");
115     xmltooling::Locker locker(m_metadata);
116     const EntityDescriptor* provider=m_metadata->getEntityDescriptor(bpr.assertion->getIssuer());
117     if (provider)
118         log.debug("matched assertion issuer against metadata");
119     else if (bpr.authnStatement->getSubject()->getNameIdentifier() &&
120              bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier()) {
121         // Might be a down-level origin.
122         provider=m_metadata->getEntityDescriptor(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
123         if (provider)
124             log.debug("matched subject name qualifier against metadata");
125     }
126
127     // No metadata at all.
128     if (!provider) {
129         xmltooling::auto_ptr_char issuer(bpr.assertion->getIssuer());
130         xmltooling::auto_ptr_char nq(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
131         log.error("assertion issuer not found in metadata (Issuer='%s', NameQualifier='%s')",
132             issuer.get(), (nq.get() ? nq.get() : "none"));
133         
134         // Try a non-strict lookup for more contact info.
135         const EntityDescriptor* provider=m_metadata->getEntityDescriptor(bpr.assertion->getIssuer(),false);
136         if (provider) {
137                 log.debug("found invalid metadata for assertion issuer, using for contact info");
138             MetadataException ex("metadata lookup failed, unable to process assertion");
139             annotateException(&ex,provider);  // throws it
140         }
141         throw MetadataException("Metadata lookup failed, unable to process assertion",xmltooling::namedparams(1,"issuer",issuer.get()));
142     }
143
144     // Is this provider an IdP?
145     const IDPSSODescriptor* role=provider->getIDPSSODescriptor(
146         minorVersion==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
147         );
148     if (!role) {
149         xmltooling::auto_ptr_char issuer(bpr.assertion->getIssuer());
150         xmltooling::auto_ptr_char nq(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
151         log.error("metadata for assertion issuer indicates no SAML 1.%d identity provider role (Issuer='%s', NameQualifier='%s'",
152             minorVersion, issuer.get(), (nq.get() ? nq.get() : "none"));
153         MetadataException ex("Metadata lookup failed, issuer not registered as SAML 1.x identity provider");
154         annotateException(&ex,provider); // throws it
155     }
156
157     // Use this role to evaluate the signature(s). If the response is unsigned, we know
158     // it was an artifact profile run.
159     if (bpr.response->isSigned()) {
160         log.debug("passing signed response to trust layer");
161         if (!m_trust) {
162             XMLSecurityException ex("No trust provider, unable to verify signed profile response.");
163             annotateException(&ex,role); // throws it
164         }
165         
166         // This will all change, but for fun, we'll port the object from OS1->OS2 for validation.
167         stringstream s;
168         s << *bpr.response;
169         DOMDocument* doc = XMLToolingConfig::getConfig().getValidatingParser().parse(s);
170         XercesJanitor<DOMDocument> jdoc(doc);
171         auto_ptr<Response> os2resp(ResponseBuilder::buildResponse());
172         os2resp->unmarshall(doc->getDocumentElement(),true);
173         jdoc.release();
174
175         if (!m_trust->validate(*(os2resp->getSignature()),*role,m_metadata->getKeyResolver())) {
176             log.error("unable to verify signed profile response");
177             XMLSecurityException ex("Unable to verify signed profile response.");
178             annotateException(&ex,role); // throws it
179         }
180     }
181
182     time_t now=time(NULL);
183     Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
184     for (unsigned int a=0; a<assertions.size();) {
185         // Discard any assertions not issued by the same entity that issued the authn.
186         if (bpr.assertion!=assertions[a] && XMLString::compareString(bpr.assertion->getIssuer(),assertions[a]->getIssuer())) {
187             xmltooling::auto_ptr_char bad(assertions[a]->getIssuer());
188             log.warn("discarding assertion not issued by authenticating IdP, instead by (%s)",bad.get());
189             bpr.response->removeAssertion(a);
190             continue;
191         }
192
193         // Validate the token.
194         try {
195             m_validator->validateToken(assertions[a],now,role,m_trust);
196             a++;
197         }
198         catch (SAMLException&) {
199             if (assertions[a]==bpr.assertion) {
200                 // If the authn token fails, we have to fail the whole profile run.
201                 log.error("authentication assertion failed to validate");
202                 //annotateException(&e,role,false);
203                 throw;
204             }
205             log.warn("token failed to validate, removing it from response");
206             bpr.response->removeAssertion(a);
207         }
208     }
209 }