From 0cd2ac24566bb0881143d8a9a3428292e6ad19a8 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Tue, 7 Nov 2006 22:21:39 +0000 Subject: [PATCH] Add simple signing support for POST binding. --- saml/binding/impl/MessageDecoder.cpp | 1 + saml/binding/impl/MessageEncoder.cpp | 2 + saml/binding/impl/SimpleSigningRule.cpp | 41 +++++++++++++----- saml/saml2/binding/SAML2POSTEncoder.h | 9 ++-- saml/saml2/binding/impl/SAML2POSTEncoder.cpp | 65 +++++++++++++++++++--------- saml/util/SAMLConstants.cpp | 2 + saml/util/SAMLConstants.h | 3 ++ samltest/binding.h | 1 + samltest/data/binding/template.html | 6 +++ samltest/saml2/binding/SAML2POSTTest.h | 61 ++++++++++++++++++++++++++ 10 files changed, 155 insertions(+), 36 deletions(-) diff --git a/saml/binding/impl/MessageDecoder.cpp b/saml/binding/impl/MessageDecoder.cpp index 983b3a3..7145b59 100644 --- a/saml/binding/impl/MessageDecoder.cpp +++ b/saml/binding/impl/MessageDecoder.cpp @@ -47,5 +47,6 @@ void SAML_API opensaml::registerMessageDecoders() conf.MessageDecoderManager.registerFactory(samlconstants::SAML1_PROFILE_BROWSER_POST, saml1p::SAML1POSTDecoderFactory); conf.MessageDecoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, saml2p::SAML2ArtifactDecoderFactory); conf.MessageDecoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_POST, saml2p::SAML2POSTDecoderFactory); + conf.MessageDecoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN, saml2p::SAML2POSTDecoderFactory); conf.MessageDecoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_REDIRECT, saml2p::SAML2RedirectDecoderFactory); } diff --git a/saml/binding/impl/MessageEncoder.cpp b/saml/binding/impl/MessageEncoder.cpp index c12aa2e..6da2d37 100644 --- a/saml/binding/impl/MessageEncoder.cpp +++ b/saml/binding/impl/MessageEncoder.cpp @@ -41,6 +41,7 @@ namespace opensaml { namespace saml2p { SAML_DLLLOCAL PluginManager::Factory SAML2ArtifactEncoderFactory; SAML_DLLLOCAL PluginManager::Factory SAML2POSTEncoderFactory; + SAML_DLLLOCAL PluginManager::Factory SAML2POSTSimpleSignEncoderFactory; SAML_DLLLOCAL PluginManager::Factory SAML2RedirectEncoderFactory; }; }; @@ -52,6 +53,7 @@ void SAML_API opensaml::registerMessageEncoders() conf.MessageEncoderManager.registerFactory(samlconstants::SAML1_PROFILE_BROWSER_POST, saml1p::SAML1POSTEncoderFactory); conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, saml2p::SAML2ArtifactEncoderFactory); conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_POST, saml2p::SAML2POSTEncoderFactory); + conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN, saml2p::SAML2POSTSimpleSignEncoderFactory); conf.MessageEncoderManager.registerFactory(samlconstants::SAML20_BINDING_HTTP_REDIRECT, saml2p::SAML2RedirectEncoderFactory); } diff --git a/saml/binding/impl/SimpleSigningRule.cpp b/saml/binding/impl/SimpleSigningRule.cpp index 3e7952d..481223d 100644 --- a/saml/binding/impl/SimpleSigningRule.cpp +++ b/saml/binding/impl/SimpleSigningRule.cpp @@ -125,24 +125,41 @@ pair SimpleSigningRule::evaluate( return ret; } - // We have to construct a string containing the signature input by accessing the - // request directly. We can't use the decoded parameters because we need the raw - // data and URL-encoding isn't canonical. string input; + const char* pch; const HTTPRequest& httpRequest = dynamic_cast(request); - const char* raw = - (!strcmp(httpRequest.getMethod(), "GET")) ? httpRequest.getQueryString() : httpRequest.getRequestBody(); - if (!appendParameter(input, raw, "SAMLRequest=")) - appendParameter(input, raw, "SAMLResponse="); - appendParameter(input, raw, "RelayState="); - appendParameter(input, raw, "SigAlg="); + if (!strcmp(httpRequest.getMethod(), "GET")) { + // We have to construct a string containing the signature input by accessing the + // request directly. We can't use the decoded parameters because we need the raw + // data and URL-encoding isn't canonical. + pch = httpRequest.getQueryString(); + if (!appendParameter(input, pch, "SAMLRequest=")) + appendParameter(input, pch, "SAMLResponse="); + appendParameter(input, pch, "RelayState="); + appendParameter(input, pch, "SigAlg="); + } + else { + // With POST, the input string is concatenated from the decoded form controls. + // GET should be this way too, but I messed up the spec, sorry. + pch = httpRequest.getParameter("SAMLRequest"); + if (pch) + input = string("SAMLRequest=") + pch; + else { + pch = httpRequest.getParameter("SAMLResponse"); + input = string("SAMLResponse=") + pch; + } + pch = httpRequest.getParameter("RelayState"); + if (pch) + input = input + "&RelayState=" + pch; + input = input + "&SigAlg=" + sigAlgorithm; + } // Check for KeyInfo, but defensively (we might be able to run without it). KeyInfo* keyInfo=NULL; - const char* k = request.getParameter("KeyInfo"); - if (k) { + pch = request.getParameter("KeyInfo"); + if (pch) { try { - istringstream kstrm(k); + istringstream kstrm(pch); DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(kstrm); XercesJanitor janitor(doc); XMLObject* kxml = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true); diff --git a/saml/saml2/binding/SAML2POSTEncoder.h b/saml/saml2/binding/SAML2POSTEncoder.h index 4417849..2c0c25d 100644 --- a/saml/saml2/binding/SAML2POSTEncoder.h +++ b/saml/saml2/binding/SAML2POSTEncoder.h @@ -17,7 +17,7 @@ /** * @file saml/saml2/binding/SAML2POSTEncoder.h * - * SAML 2.0 HTTP-POST binding message encoder + * SAML 2.0 HTTP-POST (and -SimpleSign) binding message encoder */ #include @@ -27,12 +27,12 @@ namespace opensaml { namespace saml2p { /** - * SAML 2.0 HTTP-POST binding message encoder + * SAML 2.0 HTTP-POST (and -SimpleSign) binding message encoder */ class SAML_API SAML2POSTEncoder : public MessageEncoder { public: - SAML2POSTEncoder(const DOMElement* e); + SAML2POSTEncoder(const DOMElement* e, bool simple=false); virtual ~SAML2POSTEncoder(); long encode( @@ -48,6 +48,9 @@ namespace opensaml { protected: /** Pathname of HTML template for transmission of message via POST. */ std::string m_template; + + /** Flag controls signing behavior (XML vs. "simple") */ + bool m_simple; }; }; diff --git a/saml/saml2/binding/impl/SAML2POSTEncoder.cpp b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp index ec73691..3a5fe25 100644 --- a/saml/saml2/binding/impl/SAML2POSTEncoder.cpp +++ b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp @@ -44,14 +44,19 @@ namespace opensaml { namespace saml2p { MessageEncoder* SAML_DLLLOCAL SAML2POSTEncoderFactory(const DOMElement* const & e) { - return new SAML2POSTEncoder(e); + return new SAML2POSTEncoder(e, false); + } + + MessageEncoder* SAML_DLLLOCAL SAML2POSTSimpleSignEncoderFactory(const DOMElement* const & e) + { + return new SAML2POSTEncoder(e, true); } }; }; static const XMLCh templat[] = UNICODE_LITERAL_8(t,e,m,p,l,a,t,e); -SAML2POSTEncoder::SAML2POSTEncoder(const DOMElement* e) +SAML2POSTEncoder::SAML2POSTEncoder(const DOMElement* e, bool simple) : m_simple(simple) { if (e) { auto_ptr_char t(e->getAttributeNS(NULL, templat)); @@ -95,7 +100,8 @@ long SAML2POSTEncoder::encode( } DOMElement* rootElement = NULL; - if (credResolver) { + vector sigs; + if (credResolver && !m_simple) { // Signature based on native XML signing. if (request ? request->getSignature() : response->getSignature()) { log.debug("message already signed, skipping signature operation"); @@ -110,26 +116,47 @@ long SAML2POSTEncoder::encode( request ? request->setSignature(sig) : response->setSignature(sig); // Sign response while marshalling. - vector sigs(1,sig); - rootElement = xmlObject->marshall((DOMDocument*)NULL,&sigs); + sigs.push_back(sig); } } else { log.debug("marshalling the message"); - rootElement = xmlObject->marshall(); } - string xmlbuf; - XMLHelper::serialize(rootElement, xmlbuf); + rootElement = xmlObject->marshall((DOMDocument*)NULL,&sigs); + + // Start tracking data. + map pmap; + if (relayState) + pmap["RelayState"] = relayState; + + // Base64 the message. + string& msg = pmap[(request ? "SAMLRequest" : "SAMLResponse")]; + XMLHelper::serialize(rootElement, msg); unsigned int len=0; - XMLByte* out=Base64::encode(reinterpret_cast(xmlbuf.data()),xmlbuf.size(),&len); - if (out) { - xmlbuf.erase(); - xmlbuf.append(reinterpret_cast(out),len); - XMLString::release(&out); - } - else { + XMLByte* out=Base64::encode(reinterpret_cast(msg.data()),msg.size(),&len); + if (!out) throw BindingException("Base64 encoding of XML failed."); + msg.erase(); + msg.append(reinterpret_cast(out),len); + XMLString::release(&out); + + if (credResolver && m_simple) { + log.debug("applying simple signature to message data"); + string input = (request ? "SAMLRequest=" : "SAMLResponse=") + msg; + if (relayState) + input = input + "&RelayState=" + relayState; + if (!sigAlgorithm) + sigAlgorithm = DSIGConstants::s_unicodeStrURIRSA_SHA1; + auto_ptr_char alg(sigAlgorithm); + pmap["SigAlg"] = alg.get(); + input = input + "&SigAlg=" + alg.get(); + + char sigbuf[1024]; + memset(sigbuf,0,sizeof(sigbuf)); + auto_ptr key(credResolver->getKey()); + Signature::createRawSignature(key.get(), sigAlgorithm, input.c_str(), input.length(), sigbuf, sizeof(sigbuf)-1); + pmap["Signature"] = sigbuf; } // Push message into template and send result to client. @@ -140,13 +167,9 @@ long SAML2POSTEncoder::encode( ifstream infile(m_template.c_str()); if (!infile) throw BindingException("Failed to open HTML template for POST message ($1).", params(1,m_template.c_str())); - map params; - params["action"] = destination; - params[request ? "SAMLRequest" : "SAMLResponse"] = xmlbuf; - if (relayState) - params["RelayState"] = relayState; + pmap["action"] = destination; stringstream s; - engine->run(infile, s, params); + engine->run(infile, s, pmap); httpResponse->setContentType("text/html"); long ret = httpResponse->sendResponse(s, HTTPResponse::SAML_HTTP_STATUS_OK); diff --git a/saml/util/SAMLConstants.cpp b/saml/util/SAMLConstants.cpp index f5a4db4..e18580e 100644 --- a/saml/util/SAMLConstants.cpp +++ b/saml/util/SAMLConstants.cpp @@ -185,6 +185,8 @@ const char samlconstants::SAML20_BINDING_HTTP_ARTIFACT[] = "urn:oasis:names:tc:S const char samlconstants::SAML20_BINDING_HTTP_POST[] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"; +const char samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN[] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"; + const char samlconstants::SAML20_BINDING_HTTP_REDIRECT[] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"; const char samlconstants::SAML20_BINDING_URL_ENCODING_DEFLATE[] = "urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE"; \ No newline at end of file diff --git a/saml/util/SAMLConstants.h b/saml/util/SAMLConstants.h index 1fc82ff..17de531 100644 --- a/saml/util/SAMLConstants.h +++ b/saml/util/SAMLConstants.h @@ -134,6 +134,9 @@ namespace samlconstants { /** SAML 2.0 HTTP-POST binding ("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") */ extern SAML_API const char SAML20_BINDING_HTTP_POST[]; + + /** SAML 2.0 HTTP-POST-SimpleSign binding ("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign") */ + extern SAML_API const char SAML20_BINDING_HTTP_POST_SIMPLESIGN[]; /** SAML 2.0 HTTP-Redirect binding ("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect") */ extern SAML_API const char SAML20_BINDING_HTTP_REDIRECT[]; diff --git a/samltest/binding.h b/samltest/binding.h index 4e7117e..d92280f 100644 --- a/samltest/binding.h +++ b/samltest/binding.h @@ -92,6 +92,7 @@ public: void tearDown() { for_each(m_rules.begin(), m_rules.end(), xmltooling::cleanup()); + m_rules.clear(); delete m_creds; delete m_metadata; delete m_trust; diff --git a/samltest/data/binding/template.html b/samltest/data/binding/template.html index 83215a8..224f40f 100644 --- a/samltest/data/binding/template.html +++ b/samltest/data/binding/template.html @@ -16,6 +16,12 @@ + + + + + + diff --git a/samltest/saml2/binding/SAML2POSTTest.h b/samltest/saml2/binding/SAML2POSTTest.h index 3f23b18..ada0f3e 100644 --- a/samltest/saml2/binding/SAML2POSTTest.h +++ b/samltest/saml2/binding/SAML2POSTTest.h @@ -91,4 +91,65 @@ public: throw; } } + + void testSAML2POSTSimpleSign() { + try { + QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); + SecurityPolicy policy(m_rules, m_metadata, &idprole, m_trust); + + // Read message to use from file. + string path = data_path + "saml2/binding/SAML2Response.xml"; + ifstream in(path.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + auto_ptr toSend( + dynamic_cast(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(),true)) + ); + janitor.release(); + + // Freshen timestamp and ID. + toSend->setIssueInstant(time(NULL)); + toSend->setID(NULL); + + // Encode message. + auto_ptr_XMLCh lit1("MessageEncoder"); + auto_ptr_XMLCh lit2("template"); + path = data_path + "binding/template.html"; + auto_ptr_XMLCh lit3(path.c_str()); + DOMDocument* encoder_config = XMLToolingConfig::getConfig().getParser().newDocument(); + XercesJanitor janitor2(encoder_config); + encoder_config->appendChild(encoder_config->createElementNS(NULL,lit1.get())); + encoder_config->getDocumentElement()->setAttributeNS(NULL,lit2.get(),lit3.get()); + auto_ptr encoder( + SAMLConfig::getConfig().MessageEncoderManager.newPlugin( + samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN, encoder_config->getDocumentElement() + ) + ); + encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/SSO","https://sp.example.org/","state",m_creds); + toSend.release(); + + // Decode message. + string relayState; + auto_ptr decoder( + SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN, NULL) + ); + Locker locker(m_metadata); + auto_ptr response(dynamic_cast(decoder->decode(relayState,*this,policy))); + + // Test the results. + TSM_ASSERT_EQUALS("RelayState was not the expected result.", relayState, "state"); + TSM_ASSERT("SAML Response not decoded successfully.", response.get()); + TSM_ASSERT("Message was not verified.", policy.getIssuer()!=NULL); + auto_ptr_char entityID(policy.getIssuer()->getName()); + TSM_ASSERT("Issuer was not expected.", !strcmp(entityID.get(),"https://idp.example.org/")); + TSM_ASSERT_EQUALS("Assertion count was not correct.", response->getAssertions().size(), 1); + + // Trigger a replay. + TSM_ASSERT_THROWS("Did not catch the replay.", decoder->decode(relayState,*this,policy), BindingException); + } + catch (XMLToolingException& ex) { + TS_TRACE(ex.what()); + throw; + } + } }; -- 2.1.4