From: Scott Cantor Date: Wed, 27 Sep 2006 03:22:43 +0000 (+0000) Subject: Factor out XML signing code, add SAML 2 Artifact/POST X-Git-Tag: 2.0-alpha1~186 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fcpp-opensaml.git;a=commitdiff_plain;h=6dc8738c279c46c5951e87d5e8424683e657f513 Factor out XML signing code, add SAML 2 Artifact/POST --- diff --git a/saml/Makefile.am b/saml/Makefile.am index 40d2744..1aa5ff4 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -58,10 +58,10 @@ saml1coreinclude_HEADERS = \ saml1/core/Protocols.h saml1bindinclude_HEADERS = \ - saml1/binding/SAML1ArtifactEncoder.h \ - saml1/binding/SAML1POSTEncoder.h \ saml1/binding/SAMLArtifactType0001.h \ - saml1/binding/SAMLArtifactType0002.h + saml1/binding/SAMLArtifactType0002.h \ + saml1/binding/SAML1ArtifactEncoder.h \ + saml1/binding/SAML1POSTEncoder.h saml2coreinclude_HEADERS = \ saml2/core/Assertions.h \ @@ -69,7 +69,9 @@ saml2coreinclude_HEADERS = \ saml2bindinclude_HEADERS = \ saml2/binding/SAML2Artifact.h \ - saml2/binding/SAML2ArtifactType0004.h + saml2/binding/SAML2ArtifactType0004.h \ + saml1/binding/SAML2ArtifactEncoder.h \ + saml1/binding/SAML2POSTEncoder.h saml2mdinclude_HEADERS = \ saml2/metadata/AbstractMetadataProvider.h \ @@ -90,10 +92,10 @@ libsaml_la_SOURCES = \ saml1/core/impl/AssertionsSchemaValidators.cpp \ saml1/core/impl/ProtocolsImpl.cpp \ saml1/core/impl/ProtocolsSchemaValidators.cpp \ - saml1/binding/impl/SAML1ArtifactEncoder.cpp \ - saml1/binding/impl/SAML1POSTEncoder.cpp \ saml1/binding/impl/SAMLArtifactType0001.cpp \ saml1/binding/impl/SAMLArtifactType0002.cpp \ + saml1/binding/impl/SAML1ArtifactEncoder.cpp \ + saml1/binding/impl/SAML1POSTEncoder.cpp \ saml2/core/impl/Assertions20Impl.cpp \ saml2/core/impl/Assertions20SchemaValidators.cpp \ saml2/core/impl/Protocols20Impl.cpp \ @@ -110,6 +112,8 @@ libsaml_la_SOURCES = \ saml2/metadata/impl/WhitelistMetadataFilter.cpp \ saml2/binding/impl/SAML2Artifact.cpp \ saml2/binding/impl/SAML2ArtifactType0004.cpp \ + saml2/binding/impl/SAML2ArtifactEncoder.cpp \ + saml2/binding/impl/SAML2POSTEncoder.cpp \ encryption/EncryptedKeyResolver.cpp \ security/impl/TrustEngine.cpp \ security/impl/AbstractPKIXTrustEngine.cpp \ diff --git a/saml/binding/MessageEncoder.h b/saml/binding/MessageEncoder.h index ed976ce..81e39d2 100644 --- a/saml/binding/MessageEncoder.h +++ b/saml/binding/MessageEncoder.h @@ -164,6 +164,19 @@ namespace opensaml { protected: MessageEncoder() : m_urlEncoder(NULL), m_artifactGenerator(NULL) {} + /** + * Helper function to build a new XML Signature with KeyInfo, based + * on the supplied CredentialResolver. + * + * @param credResolver CredentialResolver instance to supply signing material + * @param sigAlgorithm optional signature algorithm identifier + * @return a new Signature object + */ + xmlsignature::Signature* buildSignature( + const xmlsignature::CredentialResolver* credResolver, + const XMLCh* sigAlgorithm=NULL + ) const; + /** Pointer to a URLEncoder implementation. */ const URLEncoder* m_urlEncoder; @@ -181,6 +194,15 @@ namespace opensaml { /** MessageEncoder for SAML 1.x Browser/POST "binding" (really part of profile) */ #define SAML1_POST_ENCODER "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" + + /** MessageEncoder for SAML 2.0 HTTP-Artifact binding */ + #define SAML2_ARTIFACT_ENCODER "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + + /** MessageEncoder for SAML 2.0 HTTP-POST binding */ + #define SAML2_POST_ENCODER "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + + /** MessageEncoder for SAML 2.0 HTTP-Redirect binding */ + #define SAML2_REDIRECT_ENCODER "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }; #endif /* __saml_encoder_h__ */ diff --git a/saml/binding/impl/MessageEncoder.cpp b/saml/binding/impl/MessageEncoder.cpp index 7510254..825f5f7 100644 --- a/saml/binding/impl/MessageEncoder.cpp +++ b/saml/binding/impl/MessageEncoder.cpp @@ -23,7 +23,11 @@ #include "internal.h" #include "binding/MessageEncoder.h" +#include +#include + using namespace opensaml; +using namespace xmlsignature; using namespace xmltooling; using namespace std; @@ -32,6 +36,11 @@ namespace opensaml { SAML_DLLLOCAL PluginManager::Factory SAML1ArtifactEncoderFactory; SAML_DLLLOCAL PluginManager::Factory SAML1POSTEncoderFactory; }; + + namespace saml2p { + SAML_DLLLOCAL PluginManager::Factory SAML2ArtifactEncoderFactory; + SAML_DLLLOCAL PluginManager::Factory SAML2POSTEncoderFactory; + }; }; void SAML_API opensaml::registerMessageEncoders() @@ -39,4 +48,39 @@ void SAML_API opensaml::registerMessageEncoders() SAMLConfig& conf=SAMLConfig::getConfig(); conf.MessageEncoderManager.registerFactory(SAML1_ARTIFACT_ENCODER, saml1p::SAML1ArtifactEncoderFactory); conf.MessageEncoderManager.registerFactory(SAML1_POST_ENCODER, saml1p::SAML1POSTEncoderFactory); + conf.MessageEncoderManager.registerFactory(SAML2_ARTIFACT_ENCODER, saml2p::SAML2ArtifactEncoderFactory); + conf.MessageEncoderManager.registerFactory(SAML2_POST_ENCODER, saml2p::SAML2POSTEncoderFactory); +} + +namespace { + class SAML_DLLLOCAL _addcert : public binary_function { + public: + void operator()(X509Data* bag, XSECCryptoX509* cert) const { + safeBuffer& buf=cert->getDEREncodingSB(); + X509Certificate* x=X509CertificateBuilder::buildX509Certificate(); + x->setValue(buf.sbStrToXMLCh()); + bag->getX509Certificates().push_back(x); + } + }; +}; + +Signature* MessageEncoder::buildSignature(const CredentialResolver* credResolver, const XMLCh* sigAlgorithm) const +{ + // Build a Signature. + Signature* sig = SignatureBuilder::buildSignature(); + if (sigAlgorithm) + sig->setSignatureAlgorithm(sigAlgorithm); + sig->setSigningKey(credResolver->getKey()); + + // Build KeyInfo. + const vector& certs = credResolver->getCertificates(); + if (!certs.empty()) { + KeyInfo* keyInfo=KeyInfoBuilder::buildKeyInfo(); + X509Data* x509Data=X509DataBuilder::buildX509Data(); + keyInfo->getX509Datas().push_back(x509Data); + for_each(certs.begin(),certs.end(),bind1st(_addcert(),x509Data)); + sig->setKeyInfo(keyInfo); + } + + return sig; } diff --git a/saml/saml.vcproj b/saml/saml.vcproj index 1c47f4e..625c424 100644 --- a/saml/saml.vcproj +++ b/saml/saml.vcproj @@ -369,6 +369,10 @@ + + + + @@ -586,9 +594,17 @@ > + + + + { - public: - void operator()(X509Data* bag, XSECCryptoX509* cert) const { - safeBuffer& buf=cert->getDEREncodingSB(); - X509Certificate* x=X509CertificateBuilder::buildX509Certificate(); - x->setValue(buf.sbStrToXMLCh()); - bag->getX509Certificates().push_back(x); - } - }; }; }; @@ -92,22 +82,12 @@ void SAML1POSTEncoder::encode( else { log.debug("signing and marshalling the response"); - // Append a Signature. - response->setSignature(SignatureBuilder::buildSignature()); - response->getSignature()->setSigningKey(credResolver->getKey()); - - // Build KeyInfo. - const vector& certs = credResolver->getCertificates(); - if (!certs.empty()) { - KeyInfo* keyInfo=KeyInfoBuilder::buildKeyInfo(); - X509Data* x509Data=X509DataBuilder::buildX509Data(); - keyInfo->getX509Datas().push_back(x509Data); - for_each(certs.begin(),certs.end(),bind1st(_addcert(),x509Data)); - response->getSignature()->setKeyInfo(keyInfo); - } + // Build a Signature. + Signature* sig = buildSignature(credResolver, sigAlgorithm); + response->setSignature(sig); // Sign response while marshalling. - vector sigs(1,response->getSignature()); + vector sigs(1,sig); rootElement = response->marshall((DOMDocument*)NULL,&sigs); } } diff --git a/saml/saml2/binding/SAML2ArtifactEncoder.h b/saml/saml2/binding/SAML2ArtifactEncoder.h new file mode 100644 index 0000000..ecbdae2 --- /dev/null +++ b/saml/saml2/binding/SAML2ArtifactEncoder.h @@ -0,0 +1,49 @@ +/* + * 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/SAML2ArtifactEncoder.h + * + * SAML 2.0 HTTP-Artifact binding message encoder + */ + +#include + + +namespace opensaml { + namespace saml2p { + + /** + * SAML 2.0 HTTP-Artifact binding message encoder + */ + class SAML_API SAML2ArtifactEncoder : public MessageEncoder + { + public: + SAML2ArtifactEncoder(const DOMElement* e); + virtual ~SAML2ArtifactEncoder(); + + void encode( + std::map& outputFields, + xmltooling::XMLObject* xmlObject, + const char* recipientID=NULL, + const char* relayState=NULL, + const xmlsignature::CredentialResolver* credResolver=NULL, + const XMLCh* sigAlgorithm=NULL + ) const; + }; + + }; +}; diff --git a/saml/saml2/binding/SAML2POSTEncoder.h b/saml/saml2/binding/SAML2POSTEncoder.h new file mode 100644 index 0000000..6074890 --- /dev/null +++ b/saml/saml2/binding/SAML2POSTEncoder.h @@ -0,0 +1,49 @@ +/* + * 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/SAML2POSTEncoder.h + * + * SAML 2.0 HTTP-POST binding message encoder + */ + +#include + + +namespace opensaml { + namespace saml2p { + + /** + * SAML 2.0 HTTP-POST binding message encoder + */ + class SAML_API SAML2POSTEncoder : public MessageEncoder + { + public: + SAML2POSTEncoder(const DOMElement* e); + virtual ~SAML2POSTEncoder(); + + void encode( + std::map& outputFields, + xmltooling::XMLObject* xmlObject, + 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/SAML2ArtifactEncoder.cpp b/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp new file mode 100644 index 0000000..386b07c --- /dev/null +++ b/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp @@ -0,0 +1,118 @@ +/* + * 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. + */ + +/** + * SAML2ArtifactEncoder.cpp + * + * SAML 2.0 HTTP-Artifact binding message encoder + */ + +#include "internal.h" +#include "exceptions.h" +#include "saml2/binding/SAML2Artifact.h" +#include "saml2/binding/SAML2ArtifactEncoder.h" +#include "saml2/core/Protocols.h" + +#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 SAML2ArtifactEncoderFactory(const DOMElement* const & e) + { + return new SAML2ArtifactEncoder(e); + } + }; +}; + +SAML2ArtifactEncoder::SAML2ArtifactEncoder(const DOMElement* e) {} + +SAML2ArtifactEncoder::~SAML2ArtifactEncoder() {} + +void SAML2ArtifactEncoder::encode( + map& outputFields, + XMLObject* xmlObject, + 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.SAML2Artifact"); + log.debug("validating input"); + + outputFields.clear(); + 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-Artifact Encoder must be a SAML 2.0 protocol message."); + + ArtifactMap* mapper = SAMLConfig::getConfig().getArtifactMap(); + if (!mapper) + throw BindingException("SAML 2.0 HTTP-Artifact Encoder requires ArtifactMap be set in configuration."); + + // Obtain a fresh artifact. + if (!m_artifactGenerator) + throw BindingException("SAML 2.0 HTTP-Artifact Encoder requires an ArtifactGenerator instance."); + log.debug("obtaining new artifact for relying party (%s)", recipientID ? recipientID : "unknown"); + auto_ptr artifact(m_artifactGenerator->generateSAML2Artifact(recipientID)); + + if (credResolver) { + // Signature based on native XML signing. + if (request ? request->getSignature() : response->getSignature()) { + log.debug("message already signed, skipping signature operation"); + } + else { + log.debug("signing the message"); + + // Build a Signature. + Signature* sig = buildSignature(credResolver, sigAlgorithm); + + // Append Signature. + request ? request->setSignature(sig) : response->setSignature(sig); + + // Sign response while marshalling. + vector sigs(1,sig); + xmlObject->marshall((DOMDocument*)NULL,&sigs); + } + } + + // Pass back output fields. + outputFields["SAMLart"] = artifact->encode(); + if (relayState) + outputFields["RelayState"] = relayState; + + // Store the message. Last step in storage will be to delete the XML. + log.debug("storing artifact and content in map"); + mapper->storeContent(xmlObject, artifact.get(), recipientID); + + log.debug("message encoded"); +} diff --git a/saml/saml2/binding/impl/SAML2POSTEncoder.cpp b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp new file mode 100644 index 0000000..9dace84 --- /dev/null +++ b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp @@ -0,0 +1,125 @@ +/* + * 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. + */ + +/** + * SAML2POSTEncoder.cpp + * + * SAML 2.0 HTTP-POST binding message encoder + */ + +#include "internal.h" +#include "exceptions.h" +#include "saml2/binding/SAML2POSTEncoder.h" +#include "saml2/core/Protocols.h" + +#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 SAML2POSTEncoderFactory(const DOMElement* const & e) + { + return new SAML2POSTEncoder(e); + } + }; +}; + +SAML2POSTEncoder::SAML2POSTEncoder(const DOMElement* e) {} + +SAML2POSTEncoder::~SAML2POSTEncoder() {} + +void SAML2POSTEncoder::encode( + map& outputFields, + XMLObject* xmlObject, + 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"); + + outputFields.clear(); + 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."); + + DOMElement* rootElement = NULL; + if (credResolver) { + // Signature based on native XML signing. + if (request ? request->getSignature() : response->getSignature()) { + log.debug("message already signed, skipping signature operation"); + } + else { + log.debug("signing and marshalling the message"); + + // Build a Signature. + Signature* sig = buildSignature(credResolver, sigAlgorithm); + + // Append Signature. + request ? request->setSignature(sig) : response->setSignature(sig); + + // Sign response while marshalling. + vector sigs(1,sig); + rootElement = xmlObject->marshall((DOMDocument*)NULL,&sigs); + } + } + else { + log.debug("marshalling the message"); + rootElement = xmlObject->marshall(); + } + + string xmlbuf; + XMLHelper::serialize(rootElement, xmlbuf); + 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 { + throw BindingException("Base64 encoding of XML failed."); + } + + // Pass back output fields. + outputFields[request ? "SAMLRequest" : "SAMLResponse"] = xmlbuf; + if (relayState) + outputFields["RelayState"] = relayState; + + // Cleanup by destroying XML. + delete xmlObject; + + log.debug("message encoded"); +}