SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-opensaml.git] / saml / saml2 / binding / impl / SAML2POSTEncoder.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  * SAML2POSTEncoder.cpp
23  * 
24  * SAML 2.0 HTTP-POST binding message encoder.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "binding/MessageEncoder.h"
30 #include "signature/ContentReference.h"
31 #include "saml2/core/Protocols.h"
32
33 #include <fstream>
34 #include <sstream>
35 #include <xercesc/util/Base64.hpp>
36 #include <xsec/dsig/DSIGConstants.hpp>
37 #include <xmltooling/logging.h>
38 #include <xmltooling/XMLToolingConfig.h>
39 #include <xmltooling/io/HTTPResponse.h>
40 #include <xmltooling/security/Credential.h>
41 #include <xmltooling/signature/KeyInfo.h>
42 #include <xmltooling/signature/Signature.h>
43 #include <xmltooling/util/NDC.h>
44 #include <xmltooling/util/PathResolver.h>
45 #include <xmltooling/util/TemplateEngine.h>
46
47 using namespace opensaml::saml2p;
48 using namespace opensaml::saml2md;
49 using namespace opensaml;
50 using namespace xmlsignature;
51 using namespace xmltooling::logging;
52 using namespace xmltooling;
53 using namespace std;
54
55 namespace opensaml {
56     namespace saml2p {              
57         class SAML_DLLLOCAL SAML2POSTEncoder : public MessageEncoder
58         {
59         public:
60             SAML2POSTEncoder(const DOMElement* e, const XMLCh* ns, bool simple=false);
61             virtual ~SAML2POSTEncoder() {}
62
63             const XMLCh* getProtocolFamily() const {
64                 return samlconstants::SAML20P_NS;
65             }
66
67             long encode(
68                 GenericResponse& genericResponse,
69                 XMLObject* xmlObject,
70                 const char* destination,
71                 const EntityDescriptor* recipient=nullptr,
72                 const char* relayState=nullptr,
73                 const ArtifactGenerator* artifactGenerator=nullptr,
74                 const Credential* credential=nullptr,
75                 const XMLCh* signatureAlg=nullptr,
76                 const XMLCh* digestAlg=nullptr
77                 ) const;
78
79         private:        
80             string m_template;
81             bool m_simple;
82         };
83
84         MessageEncoder* SAML_DLLLOCAL SAML2POSTEncoderFactory(const pair<const DOMElement*,const XMLCh*>& p)
85         {
86             return new SAML2POSTEncoder(p.first, p.second, false);
87         }
88
89         MessageEncoder* SAML_DLLLOCAL SAML2POSTSimpleSignEncoderFactory(const pair<const DOMElement*,const XMLCh*>& p)
90         {
91             return new SAML2POSTEncoder(p.first, p.second, true);
92         }
93     };
94 };
95
96 static const XMLCh _template[] = UNICODE_LITERAL_8(t,e,m,p,l,a,t,e);
97
98 SAML2POSTEncoder::SAML2POSTEncoder(const DOMElement* e, const XMLCh* ns, bool simple)
99     : m_template(XMLHelper::getAttrString(e, "bindingTemplate.html", _template, ns)), m_simple(simple)
100 {
101     if (m_template.empty())
102         throw XMLToolingException("SAML2POSTEncoder requires template XML attribute.");
103     XMLToolingConfig::getConfig().getPathResolver()->resolve(m_template, PathResolver::XMLTOOLING_CFG_FILE);
104 }
105
106 long SAML2POSTEncoder::encode(
107     GenericResponse& genericResponse,
108     XMLObject* xmlObject,
109     const char* destination,
110     const EntityDescriptor* recipient,
111     const char* relayState,
112     const ArtifactGenerator* artifactGenerator,
113     const Credential* credential,
114     const XMLCh* signatureAlg,
115     const XMLCh* digestAlg
116     ) const
117 {
118 #ifdef _DEBUG
119     xmltooling::NDC ndc("encode");
120 #endif
121     Category& log = Category::getInstance(SAML_LOGCAT ".MessageEncoder.SAML2POST");
122     log.debug("validating input");
123
124     TemplateEngine* engine = XMLToolingConfig::getConfig().getTemplateEngine();
125     if (!engine || !destination)
126         throw BindingException("Encoding message using POST requires a TemplateEngine instance and a destination.");
127     HTTPResponse::sanitizeURL(destination);
128     if (xmlObject->getParent())
129         throw BindingException("Cannot encode XML content with parent.");
130     
131     StatusResponseType* response = nullptr;
132     RequestAbstractType* request = dynamic_cast<RequestAbstractType*>(xmlObject);
133     if (!request) {
134         response = dynamic_cast<StatusResponseType*>(xmlObject);
135         if (!response)
136             throw BindingException("XML content for SAML 2.0 HTTP-POST Encoder must be a SAML 2.0 protocol message.");
137     }
138     
139     DOMElement* rootElement = nullptr;
140     if (credential && !m_simple) {
141         // Signature based on native XML signing.
142         if (request ? request->getSignature() : response->getSignature()) {
143             log.debug("message already signed, skipping signature operation");
144         }
145         else {
146             log.debug("signing and marshalling the message");
147
148             // Build a Signature.
149             Signature* sig = SignatureBuilder::buildSignature();
150             request ? request->setSignature(sig) : response->setSignature(sig);    
151             if (signatureAlg)
152                 sig->setSignatureAlgorithm(signatureAlg);
153             if (digestAlg) {
154                 opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
155                 if (cr)
156                     cr->setDigestAlgorithm(digestAlg);
157             }
158             
159             // Sign response while marshalling.
160             vector<Signature*> sigs(1,sig);
161             rootElement = xmlObject->marshall((DOMDocument*)nullptr,&sigs,credential);
162         }
163     }
164     else {
165         log.debug("marshalling the message");
166         rootElement = xmlObject->marshall((DOMDocument*)nullptr);
167     }
168     
169     // Serialize the message.
170     TemplateEngine::TemplateParameters pmap;
171     string& msg = pmap.m_map[(request ? "SAMLRequest" : "SAMLResponse")];
172     XMLHelper::serialize(rootElement, msg);
173     log.debug("marshalled message:\n%s", msg.c_str());
174     
175     // SimpleSign.
176     if (credential && m_simple) {
177         log.debug("applying simple signature to message data");
178         string input = (request ? "SAMLRequest=" : "SAMLResponse=") + msg;
179         if (relayState && *relayState)
180             input = input + "&RelayState=" + relayState;
181         if (!signatureAlg)
182             signatureAlg = DSIGConstants::s_unicodeStrURIRSA_SHA1;
183         auto_ptr_char alg(signatureAlg);
184         pmap.m_map["SigAlg"] = alg.get();
185         input = input + "&SigAlg=" + alg.get();
186
187         char sigbuf[1024];
188         memset(sigbuf,0,sizeof(sigbuf));
189         Signature::createRawSignature(credential->getPrivateKey(), signatureAlg, input.c_str(), input.length(), sigbuf, sizeof(sigbuf)-1);
190         pmap.m_map["Signature"] = sigbuf;
191
192         auto_ptr<KeyInfo> keyInfo(credential->getKeyInfo());
193         if (keyInfo.get()) {
194             string& kstring = pmap.m_map["KeyInfo"];
195             XMLHelper::serialize(keyInfo->marshall((DOMDocument*)nullptr), kstring);
196             xsecsize_t len=0;
197             XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(kstring.data()),kstring.size(),&len);
198             if (!out)
199                 throw BindingException("Base64 encoding of XML failed.");
200             kstring.erase();
201             kstring.append(reinterpret_cast<char*>(out),len);
202 #ifdef OPENSAML_XERCESC_HAS_XMLBYTE_RELEASE
203             XMLString::release(&out);
204 #else
205             XMLString::release((char**)&out);
206 #endif
207         }
208     }
209     
210     // Base64 the message.
211     xsecsize_t len=0;
212     XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(msg.data()),msg.size(),&len);
213     if (!out)
214         throw BindingException("Base64 encoding of XML failed.");
215     msg.erase();
216     msg.append(reinterpret_cast<char*>(out),len);
217 #ifdef OPENSAML_XERCESC_HAS_XMLBYTE_RELEASE
218     XMLString::release(&out);
219 #else
220     XMLString::release((char**)&out);
221 #endif
222     
223     // Push the rest of it into template and send result to client.
224     log.debug("message encoded, sending HTML form template to client");
225     ifstream infile(m_template.c_str());
226     if (!infile)
227         throw BindingException("Failed to open HTML template for POST message ($1).", params(1,m_template.c_str()));
228     pmap.m_map["action"] = destination;
229     if (relayState && *relayState)
230         pmap.m_map["RelayState"] = relayState;
231     stringstream s;
232     engine->run(infile, s, pmap);
233     genericResponse.setContentType("text/html");
234     HTTPResponse* httpResponse = dynamic_cast<HTTPResponse*>(&genericResponse);
235     if (httpResponse) {
236         httpResponse->setResponseHeader("Expires", "01-Jan-1997 12:00:00 GMT");
237         httpResponse->setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");
238         httpResponse->setResponseHeader("Pragma", "no-cache");
239     }
240     long ret = genericResponse.sendResponse(s);
241
242     // Cleanup by destroying XML.
243     delete xmlObject;
244     return ret;
245 }