https://bugs.internet2.edu/jira/browse/SSPCPP-304
[shibboleth/sp.git] / shibsp / binding / impl / SOAPClient.cpp
1 /*
2  *  Copyright 2001-2010 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 /**
18  * SOAPClient.cpp
19  * 
20  * Specialized SOAPClient for SP environment.
21  */
22
23 #include "internal.h"
24 #include "Application.h"
25 #include "ServiceProvider.h"
26 #include "binding/SOAPClient.h"
27 #include "security/SecurityPolicy.h"
28
29 #include <saml/exceptions.h>
30 #include <saml/saml2/metadata/Metadata.h>
31 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
32 #include <saml/signature/ContentReference.h>
33 #include <xmltooling/security/Credential.h>
34 #include <xmltooling/signature/Signature.h>
35 #include <xmltooling/soap/SOAP.h>
36 #include <xmltooling/soap/HTTPSOAPTransport.h>
37 #include <xmltooling/util/NDC.h>
38
39 using namespace shibsp;
40 using namespace opensaml::saml2md;
41 using namespace xmlsignature;
42 using namespace xmltooling;
43 using namespace std;
44
45 SOAPClient::SOAPClient(SecurityPolicy& policy)
46     : opensaml::SOAPClient(policy), m_app(policy.getApplication()), m_relyingParty(nullptr), m_credResolver(nullptr)
47 {
48 }
49
50 SOAPClient::~SOAPClient()
51 {
52     if (m_credResolver)
53         m_credResolver->unlock();
54 }
55
56 void SOAPClient::send(const soap11::Envelope& env, const char* from, MetadataCredentialCriteria& to, const char* endpoint)
57 {
58     // Check for message signing requirements.   
59     m_relyingParty = m_app.getRelyingParty(dynamic_cast<const EntityDescriptor*>(to.getRole().getParent()));
60     pair<bool,const char*> flag = m_relyingParty->getString("signing");
61     if (flag.first && (!strcmp(flag.second, "true") || !strcmp(flag.second, "back"))) {
62         m_credResolver=m_app.getCredentialResolver();
63         if (m_credResolver) {
64             m_credResolver->lock();
65             const Credential* cred = nullptr;
66
67             // Fill in criteria to use.
68             to.setUsage(Credential::SIGNING_CREDENTIAL);
69             pair<bool,const char*> keyName = m_relyingParty->getString("keyName");
70             if (keyName.first)
71                 to.getKeyNames().insert(keyName.second);
72
73             // Check for an explicit algorithm, in which case resolve a credential directly.
74             pair<bool,const XMLCh*> sigalg = m_relyingParty->getXMLString("signingAlg");
75             if (sigalg.first) {
76                 to.setXMLAlgorithm(sigalg.second);
77                 cred = m_credResolver->resolve(&to);
78             }
79             else {
80                 // Prefer credential based on peer's requirements.
81                 pair<const SigningMethod*,const Credential*> p = to.getRole().getSigningMethod(*m_credResolver, to);
82                 if (p.first)
83                     sigalg = make_pair(true, p.first->getAlgorithm());
84                 if (p.second)
85                     cred = p.second;
86             }
87
88             // Reset criteria back.
89             to.reset();
90
91             if (cred) {
92                 // Check for message.
93                 const vector<XMLObject*>& bodies=const_cast<const soap11::Body*>(env.getBody())->getUnknownXMLObjects();
94                 if (!bodies.empty()) {
95                     opensaml::SignableObject* msg = dynamic_cast<opensaml::SignableObject*>(bodies.front());
96                     if (msg) {
97                         // Build a Signature.
98                         Signature* sig = SignatureBuilder::buildSignature();
99                         msg->setSignature(sig);
100                         if (sigalg.first)
101                             sig->setSignatureAlgorithm(sigalg.second);
102                         sigalg = m_relyingParty->getXMLString("digestAlg");
103                         if (!sigalg.first) {
104                             const DigestMethod* dm = to.getRole().getDigestMethod();
105                             if (dm)
106                                 sigalg = make_pair(true, dm->getAlgorithm());
107                         }
108                         if (sigalg.first)
109                             dynamic_cast<opensaml::ContentReference*>(sig->getContentReference())->setDigestAlgorithm(sigalg.second);
110
111                         // Sign it. The marshalling step in the base class should be a no-op.
112                         vector<Signature*> sigs(1,sig);
113                         env.marshall((DOMDocument*)nullptr,&sigs,cred);
114                     }
115                 }
116             }
117             else {
118                 Category::getInstance(SHIBSP_LOGCAT".SOAPClient").warn("no signing credential resolved, leaving message unsigned");
119             }
120         }
121         else {
122             Category::getInstance(SHIBSP_LOGCAT".SOAPClient").warn("no CredentialResolver available, leaving unsigned");
123         }
124     }
125     
126     opensaml::SOAPClient::send(env, from, to, endpoint);
127 }
128
129 void SOAPClient::prepareTransport(SOAPTransport& transport)
130 {
131 #ifdef _DEBUG
132     xmltooling::NDC("prepareTransport");
133 #endif
134     Category& log=Category::getInstance(SHIBSP_LOGCAT".SOAPClient");
135     log.debug("prepping SOAP transport for use by application (%s)", m_app.getId());
136
137     pair<bool,bool> flag = m_relyingParty->getBool("requireConfidentiality");
138     if ((!flag.first || flag.second) && !transport.isConfidential())
139         throw opensaml::BindingException("Transport confidentiality required, but not available."); 
140
141     setValidating(getPolicy().getValidating());
142     flag = m_relyingParty->getBool("requireTransportAuth");
143     forceTransportAuthentication(!flag.first || flag.second);
144
145     opensaml::SOAPClient::prepareTransport(transport);
146
147     pair<bool,const char*> authType=m_relyingParty->getString("authType");
148     if (!authType.first || !strcmp(authType.second,"TLS")) {
149         if (!m_credResolver) {
150             m_credResolver = m_app.getCredentialResolver();
151             if (m_credResolver)
152                 m_credResolver->lock();
153         }
154         if (m_credResolver) {
155             m_criteria->setUsage(Credential::TLS_CREDENTIAL);
156             authType = m_relyingParty->getString("keyName");
157             if (authType.first)
158                 m_criteria->getKeyNames().insert(authType.second);
159             const Credential* cred = m_credResolver->resolve(m_criteria);
160             m_criteria->getKeyNames().clear();
161             if (cred) {
162                 if (!transport.setCredential(cred))
163                     log.error("failed to load Credential into SOAPTransport");
164             }
165             else {
166                 log.error("no TLS credential supplied");
167             }
168         }
169         else {
170             log.error("no CredentialResolver available for TLS");
171         }
172     }
173     else {
174         SOAPTransport::transport_auth_t type=SOAPTransport::transport_auth_none;
175         pair<bool,const char*> username=m_relyingParty->getString("authUsername");
176         pair<bool,const char*> password=m_relyingParty->getString("authPassword");
177         if (!username.first || !password.first)
178             log.error("transport authType (%s) specified but authUsername or authPassword was missing", authType.second);
179         else if (!strcmp(authType.second,"basic"))
180             type = SOAPTransport::transport_auth_basic;
181         else if (!strcmp(authType.second,"digest"))
182             type = SOAPTransport::transport_auth_digest;
183         else if (!strcmp(authType.second,"ntlm"))
184             type = SOAPTransport::transport_auth_ntlm;
185         else if (!strcmp(authType.second,"gss"))
186             type = SOAPTransport::transport_auth_gss;
187         else if (strcmp(authType.second,"none"))
188             log.error("unknown authType (%s) specified for RelyingParty", authType.second);
189         if (type > SOAPTransport::transport_auth_none) {
190             if (transport.setAuth(type,username.second,password.second))
191                 log.debug("configured for transport authentication (method=%s, username=%s)", authType.second, username.second);
192             else
193                 log.error("failed to configure transport authentication (method=%s)", authType.second);
194         }
195     }
196     
197     pair<bool,unsigned int> timeout = m_relyingParty->getUnsignedInt("connectTimeout"); 
198     transport.setConnectTimeout(timeout.first ? timeout.second : 10);
199     timeout = m_relyingParty->getUnsignedInt("timeout");
200     transport.setTimeout(timeout.first ? timeout.second : 20);
201     m_app.getServiceProvider().setTransportOptions(transport);
202
203     HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(&transport);
204     if (http) {
205         flag = m_relyingParty->getBool("chunkedEncoding");
206         http->useChunkedEncoding(flag.first && flag.second);
207         http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
208     }
209 }
210
211 void SOAPClient::reset()
212 {
213     m_relyingParty = nullptr;
214     if (m_credResolver)
215         m_credResolver->unlock();
216     m_credResolver = nullptr;
217     opensaml::SOAPClient::reset();
218 }
219