From ceba6432d156e82a9016190c06ae4640c651a257 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Tue, 7 Nov 2006 04:58:24 +0000 Subject: [PATCH] Raw signature trust support, Redirect binding, "simple" signing rule. --- saml/Makefile.am | 10 +- saml/SAMLConfig.cpp | 3 +- saml/binding/SecurityPolicyRule.h | 14 +- saml/binding/SimpleSigningRule.h | 63 ++++++++ saml/binding/impl/MessageEncoder.cpp | 2 + saml/binding/impl/SecurityPolicy.cpp | 2 + saml/binding/impl/SimpleSigningRule.cpp | 184 +++++++++++++++++++++++ saml/saml.vcproj | 42 +++++- saml/saml2/binding/SAML2Redirect.h | 22 ++- saml/saml2/binding/SAML2RedirectEncoder.h | 50 ++++++ saml/saml2/binding/impl/SAML2Redirect.cpp | 42 +++++- saml/saml2/binding/impl/SAML2RedirectEncoder.cpp | 148 ++++++++++++++++++ saml/security/AbstractPKIXTrustEngine.h | 10 ++ saml/security/ChainingTrustEngine.h | 9 ++ saml/security/TrustEngine.h | 28 ++++ saml/security/impl/AbstractPKIXTrustEngine.cpp | 48 ++++++ saml/security/impl/ChainingTrustEngine.cpp | 17 +++ saml/security/impl/ExplicitKeyTrustEngine.cpp | 23 +++ samltest/Makefile.am | 1 + samltest/binding.h | 9 +- samltest/saml2/binding/SAML2RedirectTest.h | 84 +++++++++++ samltest/samltest.vcproj | 26 ++++ 22 files changed, 824 insertions(+), 13 deletions(-) create mode 100644 saml/binding/SimpleSigningRule.h create mode 100644 saml/binding/impl/SimpleSigningRule.cpp create mode 100644 saml/saml2/binding/SAML2RedirectEncoder.h create mode 100644 saml/saml2/binding/impl/SAML2RedirectEncoder.cpp create mode 100644 samltest/saml2/binding/SAML2RedirectTest.h diff --git a/saml/Makefile.am b/saml/Makefile.am index 8c3e2f2..5556709 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -45,6 +45,7 @@ samlbindinclude_HEADERS = \ binding/SAMLArtifact.h \ binding/SecurityPolicy.h \ binding/SecurityPolicyRule.h \ + binding/SimpleSigningRule.h \ binding/URLEncoder.h encinclude_HEADERS = \ @@ -87,7 +88,10 @@ saml2bindinclude_HEADERS = \ saml2/binding/SAML2ArtifactDecoder.h \ saml2/binding/SAML2ArtifactEncoder.h \ saml2/binding/SAML2POSTDecoder.h \ - saml2/binding/SAML2POSTEncoder.h + saml2/binding/SAML2POSTEncoder.h \ + saml2/binding/SAML2Redirect.h \ + saml2/binding/SAML2RedirectDecoder.h \ + saml2/binding/SAML2RedirectEncoder.h saml2mdinclude_HEADERS = \ saml2/metadata/AbstractMetadataProvider.h \ @@ -110,6 +114,7 @@ libsaml_la_SOURCES = \ binding/impl/MessageSigningRule.cpp \ binding/impl/SAMLArtifact.cpp \ binding/impl/SecurityPolicy.cpp \ + binding/impl/SimpleSigningRule.cpp \ binding/impl/URLEncoder.cpp \ saml1/core/impl/AssertionsImpl.cpp \ saml1/core/impl/AssertionsSchemaValidators.cpp \ @@ -141,6 +146,9 @@ libsaml_la_SOURCES = \ saml2/binding/impl/SAML2ArtifactEncoder.cpp \ saml2/binding/impl/SAML2POSTDecoder.cpp \ saml2/binding/impl/SAML2POSTEncoder.cpp \ + saml2/binding/impl/SAML2Redirect.cpp \ + saml2/binding/impl/SAML2RedirectDecoder.cpp \ + saml2/binding/impl/SAML2RedirectEncoder.cpp \ encryption/EncryptedKeyResolver.cpp \ security/impl/TrustEngine.cpp \ security/impl/AbstractPKIXTrustEngine.cpp \ diff --git a/saml/SAMLConfig.cpp b/saml/SAMLConfig.cpp index ef24319..5d34221 100644 --- a/saml/SAMLConfig.cpp +++ b/saml/SAMLConfig.cpp @@ -199,9 +199,8 @@ string SAMLInternalConfig::hashSHA1(const char* s, bool toHex) auto_ptr hasher(XSECPlatformUtils::g_cryptoProvider->hashSHA1()); if (hasher.get()) { - auto_ptr dup(strdup(s)); unsigned char buf[21]; - hasher->hash(reinterpret_cast(dup.get()),strlen(dup.get())); + hasher->hash(reinterpret_cast(const_cast(s)),strlen(s)); if (hasher->finish(buf,20)==20) { string ret; if (toHex) { diff --git a/saml/binding/SecurityPolicyRule.h b/saml/binding/SecurityPolicyRule.h index 749c571..668d097 100644 --- a/saml/binding/SecurityPolicyRule.h +++ b/saml/binding/SecurityPolicyRule.h @@ -102,12 +102,20 @@ namespace opensaml { #define MESSAGEROUTING_POLICY_RULE "org.opensaml.binding.MessageRoutingRule" /** - * SecurityPolicyRule for protocol message signing. + * SecurityPolicyRule for protocol message XML signing. * - * Allows the message issuer to be authenticated using an XML or binding-specific - * digital signature over the message. The transport layer is not considered. + * Allows the message issuer to be authenticated using an XML digital signature + * over the message. The transport layer is not considered. */ #define MESSAGESIGNING_POLICY_RULE "org.opensaml.binding.MessageSigningRule" + + /** + * SecurityPolicyRule for protocol message "blob" signing. + * + * Allows the message issuer to be authenticated using a non-XML digital signature + * over the message body. The transport layer is not considered. + */ + #define SIMPLESIGNING_POLICY_RULE "org.opensaml.binding.SimpleSigningRule" }; #endif /* __saml_secrule_h__ */ diff --git a/saml/binding/SimpleSigningRule.h b/saml/binding/SimpleSigningRule.h new file mode 100644 index 0000000..5a2659c --- /dev/null +++ b/saml/binding/SimpleSigningRule.h @@ -0,0 +1,63 @@ +/* + * Copyright 2001-2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file saml/binding/SimpleSigningRule.h + * + * Blob-oriented signature checking SecurityPolicyRule + */ + +#include + + +namespace opensaml { + /** + * Blob-oriented signature checking SecurityPolicyRule for + * bindings that support non-XML signature techniques. + * + * Subclasses can provide support for additional message types + * by overriding the issuer derivation method. + */ + class SAML_API SimpleSigningRule : public SecurityPolicyRule + { + public: + SimpleSigningRule(const DOMElement* e) {} + virtual ~SimpleSigningRule() {} + + std::pair evaluate( + const GenericRequest& request, + const xmltooling::XMLObject& message, + const saml2md::MetadataProvider* metadataProvider, + const xmltooling::QName* role, + const TrustEngine* trustEngine + ) const; + + protected: + /** + * Examines the message and/or its contents and extracts the issuer's claimed + * identity along with a protocol identifier. The two together can be used to + * locate metadata to use in validating the signature. Conventions may be needed + * to properly encode non-SAML2 issuer information into a compatible form. + * + *

The caller is responsible for freeing the Issuer object. + * + * @param message message to examine + * @return a pair consisting of a SAML 2.0 Issuer object and a protocol constant. + */ + virtual std::pair getIssuerAndProtocol(const xmltooling::XMLObject& message) const; + }; + +}; diff --git a/saml/binding/impl/MessageEncoder.cpp b/saml/binding/impl/MessageEncoder.cpp index 16ca166..c12aa2e 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 SAML2RedirectEncoderFactory; }; }; @@ -51,6 +52,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_REDIRECT, saml2p::SAML2RedirectEncoderFactory); } namespace { diff --git a/saml/binding/impl/SecurityPolicy.cpp b/saml/binding/impl/SecurityPolicy.cpp index 6e9b6ae..882dfac 100644 --- a/saml/binding/impl/SecurityPolicy.cpp +++ b/saml/binding/impl/SecurityPolicy.cpp @@ -35,6 +35,7 @@ namespace opensaml { SAML_DLLLOCAL PluginManager::Factory MessageFlowRuleFactory; SAML_DLLLOCAL PluginManager::Factory MessageRoutingRuleFactory; SAML_DLLLOCAL PluginManager::Factory MessageSigningRuleFactory; + SAML_DLLLOCAL PluginManager::Factory SimpleSigningRuleFactory; }; void SAML_API opensaml::registerSecurityPolicyRules() @@ -43,6 +44,7 @@ void SAML_API opensaml::registerSecurityPolicyRules() conf.SecurityPolicyRuleManager.registerFactory(MESSAGEFLOW_POLICY_RULE, MessageFlowRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(MESSAGEROUTING_POLICY_RULE, MessageRoutingRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(MESSAGESIGNING_POLICY_RULE, MessageSigningRuleFactory); + conf.SecurityPolicyRuleManager.registerFactory(SIMPLESIGNING_POLICY_RULE, SimpleSigningRuleFactory); } SecurityPolicy::~SecurityPolicy() diff --git a/saml/binding/impl/SimpleSigningRule.cpp b/saml/binding/impl/SimpleSigningRule.cpp new file mode 100644 index 0000000..9641ab5 --- /dev/null +++ b/saml/binding/impl/SimpleSigningRule.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2001-2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * SimpleSigningRule.cpp + * + * Blob-oriented signature checking SecurityPolicyRule + */ + +#include "internal.h" +#include "exceptions.h" +#include "RootObject.h" +#include "binding/HTTPRequest.h" +#include "binding/SimpleSigningRule.h" +#include "saml2/core/Protocols.h" +#include "saml2/metadata/Metadata.h" +#include "saml2/metadata/MetadataProvider.h" +#include "security/TrustEngine.h" + +#include +#include +#include +#include +#include +#include + +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace opensaml { + SecurityPolicyRule* SAML_DLLLOCAL SimpleSigningRuleFactory(const DOMElement* const & e) + { + return new SimpleSigningRule(e); + } + + // Appends a raw parameter=value pair to the string. + static bool appendParameter(string& s, const char* data, const char* name) + { + const char* start = strstr(data,name); + if (!start) + return false; + if (!s.empty()) + s += '&'; + const char* end = strchr(start,'&'); + if (end) + s.append(start, end-start); + else + s.append(start); + return true; + } +}; + + +pair SimpleSigningRule::evaluate( + const GenericRequest& request, + const XMLObject& message, + const MetadataProvider* metadataProvider, + const QName* role, + const opensaml::TrustEngine* trustEngine + ) const +{ + Category& log=Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.SimpleSigning"); + log.debug("evaluating simple signing policy"); + + pair ret = pair(NULL,NULL); + + if (!metadataProvider || !role || !trustEngine) { + log.debug("ignoring message, no metadata supplied"); + return ret; + } + + const char* signature = request.getParameter("Signature"); + if (!signature) { + log.debug("ignoring unsigned message"); + return ret; + } + + const char* sigAlgorithm = request.getParameter("SigAlg"); + if (!sigAlgorithm) { + log.error("SigAlg parameter not found, no way to verify the signature"); + return ret; + } + + try { + log.debug("extracting issuer from message"); + pair issuerInfo = getIssuerAndProtocol(message); + + auto_ptr issuer(issuerInfo.first); + if (!issuerInfo.first || !issuerInfo.second || + (issuer->getFormat() && !XMLString::equals(issuer->getFormat(), saml2::NameIDType::ENTITY))) { + log.warn("issuer identity not estabished, or was not an entityID"); + return ret; + } + + log.debug("searching metadata for message issuer..."); + const EntityDescriptor* entity = metadataProvider->getEntityDescriptor(issuer->getName()); + if (!entity) { + auto_ptr_char temp(issuer->getName()); + log.warn("no metadata found, can't establish identity of issuer (%s)", temp.get()); + return ret; + } + + log.debug("matched assertion issuer against metadata, searching for applicable role..."); + const RoleDescriptor* roledesc=entity->getRoleDescriptor(*role, issuerInfo.second); + if (!roledesc) { + log.warn("unable to find compatible role (%s) in metadata", role->toString().c_str()); + 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 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="); + + auto_ptr_XMLCh alg(sigAlgorithm); + if (!trustEngine->validate(alg.get(), signature, NULL, input.c_str(), input.length(), *roledesc, metadataProvider->getKeyResolver())) { + log.error("unable to verify signature on message with supplied trust engine"); + return ret; + } + + if (log.isDebugEnabled()) { + auto_ptr_char iname(entity->getEntityID()); + log.debug("message from (%s), signature verified", iname.get()); + } + + ret.first = issuer.release(); + ret.second = roledesc; + } + catch (bad_cast&) { + // Just trap it. + log.warn("caught a bad_cast while extracting issuer"); + } + return ret; +} + +pair SimpleSigningRule::getIssuerAndProtocol(const XMLObject& message) const +{ + // We just let any bad casts throw here. + + // Shortcuts some of the casting. + const XMLCh* ns = message.getElementQName().getNamespaceURI(); + if (ns) { + if (XMLString::equals(ns, samlconstants::SAML20P_NS) || XMLString::equals(ns, samlconstants::SAML20_NS)) { + // 2.0 namespace should be castable to a specialized 2.0 root. + const saml2::RootObject& root = dynamic_cast(message); + saml2::Issuer* issuer = root.getIssuer(); + if (issuer && issuer->getName()) { + return make_pair(issuer->cloneIssuer(), samlconstants::SAML20P_NS); + } + + // No issuer in the message, so we have to try the Response approach. + const vector& assertions = dynamic_cast(message).getAssertions(); + if (!assertions.empty()) { + issuer = assertions.front()->getIssuer(); + if (issuer && issuer->getName()) + return make_pair(issuer->cloneIssuer(), samlconstants::SAML20P_NS); + } + } + } + return pair(NULL,NULL); +} diff --git a/saml/saml.vcproj b/saml/saml.vcproj index ed0106b..39548a4 100644 --- a/saml/saml.vcproj +++ b/saml/saml.vcproj @@ -63,10 +63,11 @@ + + @@ -496,6 +502,10 @@ > + + @@ -509,10 +519,18 @@ > + + + + @@ -525,6 +543,10 @@ > + + @@ -709,6 +731,10 @@ RelativePath=".\saml2\binding\SAML2RedirectDecoder.h" > + + + + @@ -811,6 +841,10 @@ > + + @@ -827,6 +861,10 @@ > + + diff --git a/saml/saml2/binding/SAML2Redirect.h b/saml/saml2/binding/SAML2Redirect.h index d6b8400..efca83a 100644 --- a/saml/saml2/binding/SAML2Redirect.h +++ b/saml/saml2/binding/SAML2Redirect.h @@ -25,8 +25,26 @@ namespace opensaml { namespace saml2p { + /** + * Deflates data in accordance with RFC1951. The caller must free the + * resulting buffer using delete[] + * + * @param in the data to compress + * @param in_len length of input data + * @param out_len will contain the length of the resulting data + * @return allocated buffer of out_len bytes containing deflated data + */ + char* deflate(char* in, unsigned int in_len, unsigned int* out_len); - unsigned int inflate(char* in, unsigned int inlen, std::ostream& out); - + /** + * Inflates data compressed in accordance with RFC1951 and sends the + * results to an output stream. + * + * @param in the data to inflate + * @param in_len length of input data + * @param out reference to output stream to receive data + * @return number of bytes written to stream + */ + unsigned int inflate(char* in, unsigned int in_len, std::ostream& out); }; }; diff --git a/saml/saml2/binding/SAML2RedirectEncoder.h b/saml/saml2/binding/SAML2RedirectEncoder.h new file mode 100644 index 0000000..36b7367 --- /dev/null +++ b/saml/saml2/binding/SAML2RedirectEncoder.h @@ -0,0 +1,50 @@ +/* + * Copyright 2001-2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file saml/saml2/binding/SAML2RedirectEncoder.h + * + * SAML 2.0 HTTP-Redirect binding message encoder + */ + +#include + + +namespace opensaml { + namespace saml2p { + + /** + * SAML 2.0 HTTP-Redirect binding message encoder + */ + class SAML_API SAML2RedirectEncoder : public MessageEncoder + { + public: + SAML2RedirectEncoder(const DOMElement* e) {} + virtual ~SAML2RedirectEncoder() {} + + long encode( + GenericResponse& genericResponse, + xmltooling::XMLObject* xmlObject, + const char* destination, + const char* recipientID=NULL, + const char* relayState=NULL, + const xmlsignature::CredentialResolver* credResolver=NULL, + const XMLCh* sigAlgorithm=NULL + ) const; + }; + + }; +}; diff --git a/saml/saml2/binding/impl/SAML2Redirect.cpp b/saml/saml2/binding/impl/SAML2Redirect.cpp index d221bf3..5da0c6c 100644 --- a/saml/saml2/binding/impl/SAML2Redirect.cpp +++ b/saml/saml2/binding/impl/SAML2Redirect.cpp @@ -44,6 +44,46 @@ namespace { }; }; +char* opensaml::saml2p::deflate(char* in, unsigned int in_len, unsigned int* out_len) +{ +#ifdef _DEBUG + xmltooling::NDC ndc("deflate"); +#endif + Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2Redirect.zlib"); + + z_stream z; + memset(&z, 0, sizeof(z_stream)); + + z.zalloc = saml_zalloc; + z.zfree = saml_zfree; + z.opaque = NULL; + z.next_in = (Bytef*)in; + z.avail_in = in_len; + *out_len = 0; + + int ret = deflateInit2(&z, 9, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) { + log.error("zlib deflateInit2 failed with error code (%d)", ret); + return NULL; + } + + int dlen = in_len + (in_len >> 8) + 12; /* orig_size * 1.001 + 12 */ + char* out = new char[dlen]; + z.next_out = (Bytef*)out; + z.avail_out = dlen; + + ret = deflate(&z, Z_FINISH); + if (ret != Z_STREAM_END) { + deflateEnd(&z); + log.error("zlib deflateInit2 failed with error code (%d)", ret); + delete[] out; + } + + *out_len = z.total_out; + deflateEnd(&z); + return out; +} + unsigned int opensaml::saml2p::inflate(char* in, unsigned int in_len, ostream& out) { #ifdef _DEBUG @@ -68,7 +108,7 @@ unsigned int opensaml::saml2p::inflate(char* in, unsigned int in_len, ostream& o int ret = inflateInit2(&z, -15); if (ret != Z_OK) { - log.error("zlib inflateInit failed with error code (%d)", ret); + log.error("zlib inflateInit2 failed with error code (%d)", ret); delete[] buf; return 0; } diff --git a/saml/saml2/binding/impl/SAML2RedirectEncoder.cpp b/saml/saml2/binding/impl/SAML2RedirectEncoder.cpp new file mode 100644 index 0000000..f345866 --- /dev/null +++ b/saml/saml2/binding/impl/SAML2RedirectEncoder.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2001-2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * SAML2RedirectEncoder.cpp + * + * SAML 2.0 HTTP-POST binding message encoder + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/HTTPResponse.h" +#include "binding/URLEncoder.h" +#include "saml2/binding/SAML2Redirect.h" +#include "saml2/binding/SAML2RedirectEncoder.h" +#include "saml2/core/Protocols.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace opensaml::saml2p; +using namespace opensaml; +using namespace xmlsignature; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace opensaml { + namespace saml2p { + MessageEncoder* SAML_DLLLOCAL SAML2RedirectEncoderFactory(const DOMElement* const & e) + { + return new SAML2RedirectEncoder(e); + } + }; +}; + +long SAML2RedirectEncoder::encode( + GenericResponse& genericResponse, + XMLObject* xmlObject, + const char* destination, + const char* recipientID, + const char* relayState, + const CredentialResolver* credResolver, + const XMLCh* sigAlgorithm + ) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("encode"); +#endif + Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML2POST"); + + log.debug("validating input"); + HTTPResponse* httpResponse=dynamic_cast(&genericResponse); + if (!httpResponse) + throw BindingException("Unable to cast response interface to HTTPResponse type."); + if (xmlObject->getParent()) + throw BindingException("Cannot encode XML content with parent."); + + StatusResponseType* response = NULL; + RequestAbstractType* request = dynamic_cast(xmlObject); + if (!request) { + response = dynamic_cast(xmlObject); + if (!response) + throw BindingException("XML content for SAML 2.0 HTTP-POST Encoder must be a SAML 2.0 protocol message."); + } + + // Check for XML signature. + if (request ? request->getSignature() : response->getSignature()) { + log.debug("message already signed, removing native signature due to size considerations"); + request ? request->setSignature(NULL) : response->setSignature(NULL); + } + + log.debug("marshalling, deflating, base64-encoding the message"); + DOMElement* rootElement = xmlObject->marshall(); + string xmlbuf; + XMLHelper::serialize(rootElement, xmlbuf); + + unsigned int len; + char* deflated = deflate(const_cast(xmlbuf.c_str()), xmlbuf.length(), &len); + if (!deflated) + throw BindingException("Failed to deflate message."); + + XMLByte* encoded=Base64::encode(reinterpret_cast(deflated),len,&len); + delete[] deflated; + if (!encoded) + throw BindingException("Base64 encoding of XML failed."); + + // Create beginnings of redirect query string. + URLEncoder* escaper = SAMLConfig::getConfig().getURLEncoder(); + xmlbuf.erase(); + xmlbuf.append(reinterpret_cast(encoded),len); + xmlbuf = (request ? "SAMLRequest=" : "SAMLResponse=") + escaper->encode(xmlbuf.c_str()); + if (relayState) + xmlbuf = xmlbuf + "&RelayState=" + escaper->encode(relayState); + + if (credResolver) { + // Sign the query string after adding the algorithm. + if (!sigAlgorithm) + sigAlgorithm = DSIGConstants::s_unicodeStrURIRSA_SHA1; + auto_ptr_char alg(sigAlgorithm); + xmlbuf = xmlbuf + "&SigAlg=" + escaper->encode(alg.get()); + + try { + char sigbuf[1024]; + memset(sigbuf,0,sizeof(sigbuf)); + auto_ptr key(credResolver->getKey()); + Signature::createRawSignature(key.get(), sigAlgorithm, xmlbuf.c_str(), xmlbuf.length(), sigbuf, sizeof(sigbuf)-1); + xmlbuf = xmlbuf + "&Signature=" + escaper->encode(sigbuf); + } + catch(XSECException& e) { + auto_ptr_char temp(e.getMsg()); + throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + temp.get()); + } + catch(XSECCryptoException& e) { + throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + e.getMsg()); + } + } + + // Generate redirect. + log.debug("message encoded, sending redirect to client"); + xmlbuf.insert(0,1,(strchr(destination,'?') ? '&' : '?')); + xmlbuf.insert(0,destination); + long ret = httpResponse->sendRedirect(xmlbuf.c_str()); + + // Cleanup by destroying XML. + delete xmlObject; + + return ret; +} diff --git a/saml/security/AbstractPKIXTrustEngine.h b/saml/security/AbstractPKIXTrustEngine.h index b73895e..4df5422 100644 --- a/saml/security/AbstractPKIXTrustEngine.h +++ b/saml/security/AbstractPKIXTrustEngine.h @@ -76,6 +76,16 @@ namespace opensaml { ) const; virtual bool validate( + const XMLCh* sigAlgorithm, + const char* sig, + xmlsignature::KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const saml2md::RoleDescriptor& role, + const xmlsignature::KeyResolver* keyResolver=NULL + ) const; + + virtual bool validate( XSECCryptoX509* certEE, const std::vector& certChain, const saml2md::RoleDescriptor& role, diff --git a/saml/security/ChainingTrustEngine.h b/saml/security/ChainingTrustEngine.h index 4551841..b4f9c78 100644 --- a/saml/security/ChainingTrustEngine.h +++ b/saml/security/ChainingTrustEngine.h @@ -83,6 +83,15 @@ namespace opensaml { const xmlsignature::KeyResolver* keyResolver=NULL ) const; virtual bool validate( + const XMLCh* sigAlgorithm, + const char* sig, + xmlsignature::KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const saml2md::RoleDescriptor& role, + const xmlsignature::KeyResolver* keyResolver=NULL + ) const; + virtual bool validate( XSECCryptoX509* certEE, const std::vector& certChain, const saml2md::RoleDescriptor& role, diff --git a/saml/security/TrustEngine.h b/saml/security/TrustEngine.h index 3ca02b4..aeb76d9 100644 --- a/saml/security/TrustEngine.h +++ b/saml/security/TrustEngine.h @@ -65,12 +65,40 @@ namespace opensaml { * @param sig reference to a signature object to validate * @param role metadata role supplying key information * @param keyResolver optional externally supplied KeyResolver, or NULL + * @return true iff the signature validates */ virtual bool validate( xmlsignature::Signature& sig, const saml2md::RoleDescriptor& role, const xmlsignature::KeyResolver* keyResolver=NULL ) const=0; + + /** + * Determines whether a raw signature is correct and valid with respect to + * the information known about the signer. + * + *

A custom KeyResolver can be supplied from outside the TrustEngine. + * Alternatively, one may be specified to the plugin constructor. + * A non-caching, inline resolver will be used as a fallback. + * + * @param sigAlgorithm XML Signature identifier for the algorithm used + * @param sig null-terminated base64-encoded signature value + * @param keyInfo KeyInfo object accompanying the signature, if any + * @param in the input data over which the signature was created + * @param in_len size of input data in bytes + * @param role metadata role supplying key information + * @param keyResolver optional externally supplied KeyResolver, or NULL + * @return true iff the signature validates + */ + virtual bool validate( + const XMLCh* sigAlgorithm, + const char* sig, + xmlsignature::KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const saml2md::RoleDescriptor& role, + const xmlsignature::KeyResolver* keyResolver=NULL + ) const=0; }; diff --git a/saml/security/impl/AbstractPKIXTrustEngine.cpp b/saml/security/impl/AbstractPKIXTrustEngine.cpp index f7f1fbc..717c438 100644 --- a/saml/security/impl/AbstractPKIXTrustEngine.cpp +++ b/saml/security/impl/AbstractPKIXTrustEngine.cpp @@ -370,3 +370,51 @@ bool AbstractPKIXTrustEngine::validate(Signature& sig, const RoleDescriptor& rol log.error("failed to verify signature with embedded certificates"); return false; } + +bool AbstractPKIXTrustEngine::validate( + const XMLCh* sigAlgorithm, + const char* sig, + KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const RoleDescriptor& role, + const KeyResolver* keyResolver + ) const +{ +#ifdef _DEBUG + NDC ndc("validate"); +#endif + Category& log=Category::getInstance(SAML_LOGCAT".TrustEngine"); + + // Pull the certificate chain out of the KeyInfo using an inline KeyResolver. + KeyResolver::ResolvedCertificates certs; + if (!keyInfo || 0==m_inlineResolver->resolveCertificates(keyInfo, certs)) { + log.error("unable to perform PKIX validation, KeyInfo does not contain any certificates"); + return false; + } + + log.debug("validating signature using certificate from within KeyInfo"); + + // Find and save off a pointer to the certificate that unlocks the object. + // Most of the time, this will be the first one anyway. + XSECCryptoX509* certEE=NULL; + SignatureValidator keyValidator; + for (vector::const_iterator i=certs.v().begin(); !certEE && i!=certs.v().end(); ++i) { + try { + auto_ptr key((*i)->clonePublicKey()); + if (Signature::verifyRawSignature(key.get(), sigAlgorithm, sig, in, in_len)) { + log.info("signature verified with key inside signature, attempting certificate validation..."); + certEE=(*i); + } + } + catch (SignatureException&) { + // trap failures + } + } + + if (certEE) + return validate(certEE,certs.v(),role,true,keyResolver); + + log.error("failed to verify signature with embedded certificates"); + return false; +} diff --git a/saml/security/impl/ChainingTrustEngine.cpp b/saml/security/impl/ChainingTrustEngine.cpp index f6b95cb..4d61107 100644 --- a/saml/security/impl/ChainingTrustEngine.cpp +++ b/saml/security/impl/ChainingTrustEngine.cpp @@ -84,6 +84,23 @@ bool ChainingTrustEngine::validate( } bool ChainingTrustEngine::validate( + const XMLCh* sigAlgorithm, + const char* sig, + KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const RoleDescriptor& role, + const KeyResolver* keyResolver + ) const +{ + for (vector::const_iterator i=m_engines.begin(); i!=m_engines.end(); ++i) { + if (static_cast(*i)->validate(sigAlgorithm, sig, keyInfo, in, in_len, role, keyResolver)) + return true; + } + return false; +} + +bool ChainingTrustEngine::validate( XSECCryptoX509* certEE, const vector& certChain, const RoleDescriptor& role, diff --git a/saml/security/impl/ExplicitKeyTrustEngine.cpp b/saml/security/impl/ExplicitKeyTrustEngine.cpp index 7249ee9..34219a6 100644 --- a/saml/security/impl/ExplicitKeyTrustEngine.cpp +++ b/saml/security/impl/ExplicitKeyTrustEngine.cpp @@ -60,6 +60,15 @@ namespace opensaml { const KeyResolver* keyResolver=NULL ) const; virtual bool validate( + const XMLCh* sigAlgorithm, + const char* sig, + KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const RoleDescriptor& role, + const KeyResolver* keyResolver=NULL + ) const; + virtual bool validate( XSECCryptoX509* certEE, const vector& certChain, const RoleDescriptor& role, @@ -106,6 +115,20 @@ bool ExplicitKeyTrustEngine::validate( } bool ExplicitKeyTrustEngine::validate( + const XMLCh* sigAlgorithm, + const char* sig, + KeyInfo* keyInfo, + const char* in, + unsigned int in_len, + const RoleDescriptor& role, + const KeyResolver* keyResolver + ) const +{ + MetadataKeyInfoIterator keys(role); + return static_cast(m_engine)->validate(sigAlgorithm,sig,keyInfo,in,in_len,keys,keyResolver); +} + +bool ExplicitKeyTrustEngine::validate( XSECCryptoX509* certEE, const vector& certChain, const RoleDescriptor& role, diff --git a/samltest/Makefile.am b/samltest/Makefile.am index cb82f02..681cb7a 100644 --- a/samltest/Makefile.am +++ b/samltest/Makefile.am @@ -91,6 +91,7 @@ samltest_h = \ saml2/core/impl/Terminate20Test.h \ saml2/binding/SAML2ArtifactTest.h \ saml2/binding/SAML2POSTTest.h \ + saml2/binding/SAML2RedirectTest.h \ saml2/metadata/FilesystemMetadataProviderTest.h noinst_HEADERS = \ diff --git a/samltest/binding.h b/samltest/binding.h index fdd0ce7..4e7117e 100644 --- a/samltest/binding.h +++ b/samltest/binding.h @@ -36,7 +36,7 @@ protected: opensaml::TrustEngine* m_trust; map m_fields; map m_headers; - string m_method,m_url; + string m_method,m_url,m_query; vector m_clientCerts; vector m_rules; @@ -49,6 +49,7 @@ public: m_headers.clear(); m_method.erase(); m_url.erase(); + m_query.erase(); try { string config = data_path + "binding/ExampleMetadataProvider.xml"; @@ -77,7 +78,9 @@ public: m_trust = SAMLConfig::getConfig().TrustEngineManager.newPlugin(EXPLICIT_KEY_SAMLTRUSTENGINE, NULL); m_rules.push_back(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(MESSAGEFLOW_POLICY_RULE,NULL)); + m_rules.push_back(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(MESSAGEROUTING_POLICY_RULE,NULL)); m_rules.push_back(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(MESSAGESIGNING_POLICY_RULE,NULL)); + m_rules.push_back(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(SIMPLESIGNING_POLICY_RULE,NULL)); } catch (XMLToolingException& ex) { TS_TRACE(ex.what()); @@ -99,6 +102,7 @@ public: m_headers.clear(); m_method.erase(); m_url.erase(); + m_query.erase(); } // HTTPRequest methods @@ -132,7 +136,7 @@ public: } const char* getQueryString() const { - return NULL; + return m_query.c_str(); } string getRemoteUser() const { @@ -188,6 +192,7 @@ public: char* pch = strchr(dup,'?'); if (pch) { *pch++=0; + m_query = pch; char* name=pch; while (name && *name) { pch=strchr(pch,'='); diff --git a/samltest/saml2/binding/SAML2RedirectTest.h b/samltest/saml2/binding/SAML2RedirectTest.h new file mode 100644 index 0000000..92d9741 --- /dev/null +++ b/samltest/saml2/binding/SAML2RedirectTest.h @@ -0,0 +1,84 @@ +/* + * Copyright 2001-2005 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "binding.h" + +#include + +using namespace opensaml::saml2p; +using namespace opensaml::saml2; + +class SAML2RedirectTest : public CxxTest::TestSuite, public SAMLBindingBaseTestCase { +public: + void setUp() { + SAMLBindingBaseTestCase::setUp(); + } + + void tearDown() { + SAMLBindingBaseTestCase::tearDown(); + } + + void testSAML2Redirect() { + 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 encoder( + SAMLConfig::getConfig().MessageEncoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_REDIRECT, NULL) + ); + 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_REDIRECT, 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; + } + } +}; diff --git a/samltest/samltest.vcproj b/samltest/samltest.vcproj index cd29fdf..71952f7 100644 --- a/samltest/samltest.vcproj +++ b/samltest/samltest.vcproj @@ -545,6 +545,10 @@ RelativePath=".\saml2\binding\SAML2POSTTest.cpp" > + + + + + + + + + +