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