From 1bc8e721db3a50294df852662e1eddcdbdae8f9f Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Fri, 3 Nov 2006 05:10:46 +0000 Subject: [PATCH] Revamped binding classes with security policy layer. --- saml/Makefile.am | 12 + saml/RootObject.h | 67 ++++ saml/SAMLConfig.cpp | 2 + saml/SAMLConfig.h | 28 +- saml/binding/GenericRequest.h | 127 ++++++ saml/binding/GenericResponse.h | 62 +++ saml/binding/HTTPRequest.h | 79 ++++ saml/binding/HTTPResponse.h | 79 ++++ saml/binding/MessageDecoder.h | 136 +------ saml/binding/MessageEncoder.h | 63 +-- saml/binding/MessageFlowRule.h | 63 +++ saml/binding/MessageSigningRule.h | 62 +++ saml/binding/SecurityPolicy.h | 174 +++++++++ saml/binding/SecurityPolicyRule.h | 103 +++++ saml/binding/impl/MessageFlowRule.cpp | 107 +++++ saml/binding/impl/MessageSigningRule.cpp | 177 +++++++++ saml/binding/impl/SecurityPolicy.cpp | 122 ++++++ saml/saml.vcproj | 124 ++++++ saml/saml1/binding/SAML1ArtifactDecoder.h | 8 +- saml/saml1/binding/SAML1ArtifactEncoder.h | 2 +- saml/saml1/binding/SAML1POSTDecoder.h | 8 +- saml/saml1/binding/SAML1POSTEncoder.h | 2 +- saml/saml1/binding/impl/SAML1ArtifactDecoder.cpp | 63 ++- saml/saml1/binding/impl/SAML1ArtifactEncoder.cpp | 10 +- saml/saml1/binding/impl/SAML1POSTDecoder.cpp | 129 ++---- saml/saml1/binding/impl/SAML1POSTEncoder.cpp | 11 +- saml/saml1/core/Assertions.h | 8 +- saml/saml1/core/Protocols.h | 14 +- saml/saml1/core/impl/AssertionsImpl.cpp | 3 + saml/saml1/core/impl/ProtocolsImpl.cpp | 6 + saml/saml2/binding/SAML2ArtifactDecoder.h | 11 +- saml/saml2/binding/SAML2ArtifactEncoder.h | 2 +- saml/saml2/binding/SAML2POSTDecoder.h | 12 +- saml/saml2/binding/SAML2POSTEncoder.h | 2 +- saml/saml2/binding/SAML2Redirect.h | 32 ++ saml/saml2/binding/SAML2RedirectDecoder.h | 45 +++ saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp | 120 ++---- saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp | 13 +- saml/saml2/binding/impl/SAML2POSTDecoder.cpp | 166 +++----- saml/saml2/binding/impl/SAML2POSTEncoder.cpp | 11 +- saml/saml2/binding/impl/SAML2Redirect.cpp | 105 +++++ saml/saml2/binding/impl/SAML2RedirectDecoder.cpp | 242 ++++++++++++ saml/saml2/core/Assertions.h | 33 +- saml/saml2/core/Protocols.h | 27 +- saml/saml2/metadata/Metadata.h | 8 +- saml/signature/SignableObject.h | 7 + saml/util/SAMLConstants.cpp | 2 + saml/util/SAMLConstants.h | 3 + samltest/binding.h | 474 ++++++++++++----------- samltest/saml1/binding/SAML1ArtifactTest.h | 267 ++++++------- samltest/saml1/binding/SAML1POSTTest.h | 83 +--- samltest/saml2/binding/SAML2ArtifactTest.h | 261 ++++++------- samltest/saml2/binding/SAML2POSTTest.h | 83 +--- 53 files changed, 2593 insertions(+), 1267 deletions(-) create mode 100644 saml/RootObject.h create mode 100644 saml/binding/GenericRequest.h create mode 100644 saml/binding/GenericResponse.h create mode 100644 saml/binding/HTTPRequest.h create mode 100644 saml/binding/HTTPResponse.h create mode 100644 saml/binding/MessageFlowRule.h create mode 100644 saml/binding/MessageSigningRule.h create mode 100644 saml/binding/SecurityPolicy.h create mode 100644 saml/binding/SecurityPolicyRule.h create mode 100644 saml/binding/impl/MessageFlowRule.cpp create mode 100644 saml/binding/impl/MessageSigningRule.cpp create mode 100644 saml/binding/impl/SecurityPolicy.cpp create mode 100644 saml/saml2/binding/SAML2Redirect.h create mode 100644 saml/saml2/binding/SAML2RedirectDecoder.h create mode 100644 saml/saml2/binding/impl/SAML2Redirect.cpp create mode 100644 saml/saml2/binding/impl/SAML2RedirectDecoder.cpp diff --git a/saml/Makefile.am b/saml/Makefile.am index 636ffac..ae7c2d1 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -28,13 +28,22 @@ libsamlinclude_HEADERS = \ base.h \ exceptions.h \ version.h \ + RootObject.h \ SAMLConfig.h samlbindinclude_HEADERS = \ binding/ArtifactMap.h \ + binding/GenericRequest.h \ + binding/GenericResponse.h \ + binding/HTTPRequest.h \ + binding/HTTPResponse.h \ binding/MessageDecoder.h \ binding/MessageEncoder.h \ + binding/MessageFlowRule.h \ + binding/MessageSigningRule.h \ binding/SAMLArtifact.h \ + binding/SecurityPolicy.h \ + binding/SecurityPolicyRule.h \ binding/URLEncoder.h encinclude_HEADERS = \ @@ -95,7 +104,10 @@ libsaml_la_SOURCES = \ binding/impl/ArtifactMap.cpp \ binding/impl/MessageDecoder.cpp \ binding/impl/MessageEncoder.cpp \ + binding/impl/MessageFlowRule.cpp \ + binding/impl/MessageSigningRule.cpp \ binding/impl/SAMLArtifact.cpp \ + binding/impl/SecurityPolicy.cpp \ binding/impl/URLEncoder.cpp \ saml1/core/impl/AssertionsImpl.cpp \ saml1/core/impl/AssertionsSchemaValidators.cpp \ diff --git a/saml/RootObject.h b/saml/RootObject.h new file mode 100644 index 0000000..5ce98ec --- /dev/null +++ b/saml/RootObject.h @@ -0,0 +1,67 @@ +/* + * 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/RootObject.h + * + * Base class for SAML objects at the root of core schemas + */ + +#ifndef __saml_root_h__ +#define __saml_root_h__ + +#include +#include + +namespace opensaml { + + /** + * Base class for SAML objects at the root of core schemas. + * Root objects are signable, and have message identifiers and timestamps. + */ + class SAML_API RootObject : public virtual SignableObject + { + public: + virtual ~RootObject() {} + + /** + * Returns the unique SAML ID of the object. + * + * @return the unique SAML ID + */ + virtual const XMLCh* getID() const=0; + + /** + * Returns the timestamp of the object + * + * @return the timestamp + */ + virtual const xmltooling::DateTime* getIssueInstant() const=0; + + /** + * Returns the timestamp of the object as an epoch + * + * @return the timestamp + */ + virtual time_t getIssueInstantEpoch() const=0; + + protected: + RootObject() {} + }; + +}; + +#endif /* __saml_root_h__ */ diff --git a/saml/SAMLConfig.cpp b/saml/SAMLConfig.cpp index 709850e..ef24319 100644 --- a/saml/SAMLConfig.cpp +++ b/saml/SAMLConfig.cpp @@ -125,6 +125,7 @@ bool SAMLInternalConfig::init(bool initXMLTooling) registerTrustEngines(); registerMessageEncoders(); registerMessageDecoders(); + registerSecurityPolicyRules(); m_urlEncoder = new URLEncoder(); @@ -142,6 +143,7 @@ void SAMLInternalConfig::term(bool termXMLTooling) MessageDecoderManager.deregisterFactories(); MessageEncoderManager.deregisterFactories(); TrustEngineManager.deregisterFactories(); + SecurityPolicyRuleManager.deregisterFactories(); SAMLArtifactManager.deregisterFactories(); MetadataFilterManager.deregisterFactories(); MetadataProviderManager.deregisterFactories(); diff --git a/saml/SAMLConfig.h b/saml/SAMLConfig.h index 566c2d1..b6f620a 100644 --- a/saml/SAMLConfig.h +++ b/saml/SAMLConfig.h @@ -40,6 +40,7 @@ namespace opensaml { class SAML_API MessageEncoder; class SAML_API MessageDecoder; class SAML_API SAMLArtifact; + class SAML_API SecurityPolicyRule; class SAML_API TrustEngine; class SAML_API URLEncoder; @@ -164,34 +165,25 @@ namespace opensaml { */ virtual std::string hashSHA1(const char* s, bool toHex=false)=0; - /** - * Manages factories for MessageDecoder plugins. - */ + /** Manages factories for MessageDecoder plugins. */ xmltooling::PluginManager MessageDecoderManager; - /** - * Manages factories for MessageEncoder plugins. - */ + /** Manages factories for MessageEncoder plugins. */ xmltooling::PluginManager MessageEncoderManager; - /** - * Manages factories for SAMLArtifact plugins. - */ + /** Manages factories for SAMLArtifact plugins. */ xmltooling::PluginManager SAMLArtifactManager; - /** - * Manages factories for TrustEngine plugins. - */ + /** Manages factories for SecurityPolicyRule plugins. */ + xmltooling::PluginManager SecurityPolicyRuleManager; + + /** Manages factories for TrustEngine plugins. */ xmltooling::PluginManager TrustEngineManager; - /** - * Manages factories for MetadataProvider plugins. - */ + /** Manages factories for MetadataProvider plugins. */ xmltooling::PluginManager MetadataProviderManager; - /** - * Manages factories for MetadataFilter plugins. - */ + /** Manages factories for MetadataFilter plugins. */ xmltooling::PluginManager MetadataFilterManager; protected: diff --git a/saml/binding/GenericRequest.h b/saml/binding/GenericRequest.h new file mode 100644 index 0000000..0acfd4d --- /dev/null +++ b/saml/binding/GenericRequest.h @@ -0,0 +1,127 @@ +/* + * 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/GenericRequest.h + * + * Interface to generic protocol requests that transport SAML messages. + */ + +#ifndef __saml_genreq_h__ +#define __saml_genreq_h__ + +#include +#include +#include +#include + +namespace opensaml { + + /** + * Interface to caller-supplied shim for accessing generic transport + * request context. + * + *

This interface need not be threadsafe. + */ + class SAML_API GenericRequest { + MAKE_NONCOPYABLE(GenericRequest); + protected: + GenericRequest() {} + public: + virtual ~GenericRequest() {} + + /** + * Returns the URL scheme of the request (http, https, ftp, ldap, etc.) + * + * @return the URL scheme + */ + virtual const char* getScheme() const=0; + + /** + * Returns true iff the request is over a confidential channel. + * + * @return confidential channel indicator + */ + virtual bool isSecure() const=0; + + /** + * Returns the MIME type of the request, if known. + * + * @return the MIME type, or an empty string + */ + virtual std::string getContentType() const=0; + + /** + * Returns the length of the request body, if known. + * + * @return the content length, or -1 if unknown + */ + virtual long getContentLength() const=0; + + /** + * Returns the raw request body. + * + * @return the request body, or NULL + */ + virtual const char* getRequestBody() const=0; + + /** + * Returns a decoded named parameter value from the request. + * If a parameter has multiple values, only one will be returned. + * + * @param name the name of the parameter to return + * @return a single parameter value or NULL + */ + virtual const char* getParameter(const char* name) const=0; + + /** + * Returns all of the decoded values of a named parameter from the request. + * All values found will be returned. + * + * @param name the name of the parameter to return + * @param values a vector in which to return pointers to the decoded values + * @return the number of values returned + */ + virtual std::vector::size_type getParameters( + const char* name, std::vector& values + ) const=0; + + /** + * Returns the transport-authenticated identity associated with the request, + * if authentication is solely handled by the transport. + * + * @return the authenticated username or an empty string + */ + virtual std::string getRemoteUser() const=0; + + /** + * Returns the IP address of the client. + * + * @return the client's IP address + */ + virtual std::string getRemoteAddr() const=0; + + /** + * Returns the chain of certificates sent by the client. + * They are not guaranteed to be valid according to any particular definition. + * + * @return the client's certificate chain + */ + virtual const std::vector& getClientCertificates() const=0; + }; +}; + +#endif /* __saml_genreq_h__ */ diff --git a/saml/binding/GenericResponse.h b/saml/binding/GenericResponse.h new file mode 100644 index 0000000..f955e5c --- /dev/null +++ b/saml/binding/GenericResponse.h @@ -0,0 +1,62 @@ +/* + * 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/GenericResponse.h + * + * Interface to generic protocol responses that transport SAML messages. + */ + +#ifndef __saml_genres_h__ +#define __saml_genres_h__ + +#include +#include + +namespace opensaml { + + /** + * Interface to caller-supplied shim for accessing generic transport + * request context. + * + *

This interface need not be threadsafe. + */ + class SAML_API GenericResponse { + MAKE_NONCOPYABLE(GenericResponse); + protected: + GenericResponse() {} + public: + virtual ~GenericResponse() {} + + /** + * Sets or clears the MIME type of the response. + * + * @param type the MIME type, or NULL to clear + */ + virtual void setContentType(const char* type=NULL)=0; + + /** + * Sends a completed response to the client. + * + * @param inputStream reference to source of response data + * @param status transport-specific status to return + * @return a result code to return from the calling MessageEncoder + */ + virtual long sendResponse(std::istream& inputStream, long status)=0; + }; +}; + +#endif /* __saml_genres_h__ */ diff --git a/saml/binding/HTTPRequest.h b/saml/binding/HTTPRequest.h new file mode 100644 index 0000000..2c34a83 --- /dev/null +++ b/saml/binding/HTTPRequest.h @@ -0,0 +1,79 @@ +/* + * 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/HTTPRequest.h + * + * Interface to HTTP requests + */ + +#ifndef __saml_httpreq_h__ +#define __saml_httpreq_h__ + +#include + +namespace opensaml { + + /** + * Interface to caller-supplied shim for accessing HTTP request context. + * + *

To supply information from the surrounding web server environment, + * a shim must be supplied in the form of this interface to adapt the + * library to different proprietary server APIs. + * + *

This interface need not be threadsafe. + */ + class SAML_API HTTPRequest : public GenericRequest { + MAKE_NONCOPYABLE(HTTPRequest); + protected: + HTTPRequest() {} + public: + virtual ~HTTPRequest() {} + + /** + * Returns the HTTP method of the request (GET, POST, etc.) + * + * @return the HTTP method + */ + virtual const char* getMethod() const=0; + + /** + * Returns the complete request URL, including scheme, host, port. + * + * @return the request URL + */ + virtual const char* getRequestURL() const=0; + + /** + * Returns the HTTP query string appened to the request. The query + * string is returned without any decoding applied, everything found + * after the ? delimiter. + * + * @return the query string + */ + virtual const char* getQueryString() const=0; + + /** + * Returns a request header value. + * + * @param name the name of the header to return + * @return the header's value, or an empty string + */ + virtual std::string getHeader(const char* name) const=0; + }; +}; + +#endif /* __saml_httpreq_h__ */ diff --git a/saml/binding/HTTPResponse.h b/saml/binding/HTTPResponse.h new file mode 100644 index 0000000..e77e17d --- /dev/null +++ b/saml/binding/HTTPResponse.h @@ -0,0 +1,79 @@ +/* + * 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/HTTPResponse.h + * + * Interface to HTTP requests + */ + +#ifndef __saml_httpres_h__ +#define __saml_httpres_h__ + +#include + +namespace opensaml { + + /** + * Interface to caller-supplied shim for issuing an HTTP response. + * + *

To supply information to the surrounding web server environment, + * a shim must be supplied in the form of this interface to adapt the + * library to different proprietary server APIs. + * + *

This interface need not be threadsafe. + */ + class SAML_API HTTPResponse : public GenericResponse { + MAKE_NONCOPYABLE(HTTPResponse); + protected: + HTTPResponse() {} + public: + virtual ~HTTPResponse() {} + + /** + * Sets or clears a response header. + * + * @param name header name + * @param value value to set, or NULL to clear + */ + virtual void setHeader(const char* name, const char* value)=0; + + /** + * Sets a client cookie. + * + * @param name cookie name + * @param value value to set, or NULL to clear + */ + virtual void setCookie(const char* name, const char* value)=0; + + /** + * Redirect the client to the specified URL and complete the response. + * Any headers previously set will be sent ahead of the redirect. + * + * @param url location to redirect client + * @return a result code to return from the calling MessageEncoder + */ + virtual long sendRedirect(const char* url)=0; + + /** Some common HTTP status codes. */ + enum status_t { + SAML_HTTP_STATUS_OK = 200, + SAML_HTTP_STATUS_MOVED = 302 + }; + }; +}; + +#endif /* __saml_httpres_h__ */ diff --git a/saml/binding/MessageDecoder.h b/saml/binding/MessageDecoder.h index 70a4768..ef69d61 100644 --- a/saml/binding/MessageDecoder.h +++ b/saml/binding/MessageDecoder.h @@ -23,8 +23,8 @@ #ifndef __saml_decoder_h__ #define __saml_decoder_h__ -#include - +#include +#include #include namespace opensaml { @@ -43,7 +43,7 @@ namespace opensaml { class SAML_API IDPSSODescriptor; class SAML_API RoleDescriptor; class SAML_API SSODescriptorType; - } + }; /** * Interface to SAML protocol binding message decoders. @@ -55,90 +55,6 @@ namespace opensaml { virtual ~MessageDecoder() {} /** - * Interface to caller-supplied shim for accessing HTTP request context. - * - *

To supply information from the surrounding web server environment, - * a shim must be supplied in the form of this interface to adapt the - * library to different proprietary server APIs. - * - *

This interface need not be threadsafe. - */ - class SAML_API HTTPRequest { - MAKE_NONCOPYABLE(HTTPRequest); - protected: - HTTPRequest() {} - public: - virtual ~HTTPRequest() {} - - /** - * Returns the HTTP method of the request (GET, POST, etc.) - * - * @return the HTTP method - */ - virtual const char* getMethod() const=0; - - /** - * Returns the complete request URL, including scheme, host, port. - * - * @return the request URL - */ - virtual const char* getRequestURL() const=0; - - /** - * Returns the HTTP query string appened to the request. The query - * string is returned without any decoding applied, everything found - * after the ? delimiter. - * - * @return the query string - */ - virtual const char* getQueryString() const=0; - - /** - * Returns the raw HTTP request body. Used to access the body - * of a POST that is not in URL-encoded form. - * - * @return the request body, or NULL - */ - virtual const char* getRequestBody() const=0; - - /** - * Returns a decoded named parameter value from the query string or form body. - * If a parameter has multiple values, only one will be returned. - * - * @param name the name of the parameter to return - * @return a single parameter value or NULL - */ - virtual const char* getParameter(const char* name) const=0; - - /** - * Returns all of the decoded values of a named parameter from the query string - * or form body. All values found will be returned. - * - * @param name the name of the parameter to return - * @param values a vector in which to return pointers to the decoded values - * @return the number of values returned - */ - virtual std::vector::size_type getParameters( - const char* name, std::vector& values - ) const=0; - - /** - * Returns the authenticated identity associated with the request - * - * @return the authenticated username or an empty string - */ - virtual std::string getRemoteUser() const=0; - - /** - * Returns a request header value. - * - * @param name the name of the header to return - * @return the header's value, or an empty string - */ - virtual std::string getHeader(const char* name) const=0; - }; - - /** * Interface to caller-supplied artifact resolution mechanism. * * Resolving artifacts requires internally performing a SOAP-based @@ -173,36 +89,36 @@ namespace opensaml { /** * Resolves one or more SAML 1.x artifacts into a response containing a set of - * resolved Assertions. The caller is responsible for the resulting Response. + * resolved Assertions. The caller is responsible for the resulting Response. + * The supplied SecurityPolicy is used to access caller-supplied infrastructure + * and to pass back the result of authenticating the resolution process. * - * @param securityMech will be set to identifier of security mechanism that authenticated the resolution * @param artifacts one or more SAML 1.x artifacts * @param idpDescriptor reference to IdP role of artifact issuer - * @param trustEngine optional pointer to X509TrustEngine supplied to MessageDecoder + * @param policy reference to policy containing rules, MetadataProvider, TrustEngine, etc. * @return the corresponding SAML Assertions wrapped in a Response. */ virtual saml1p::Response* resolve( - const XMLCh*& securityMech, const std::vector& artifacts, const saml2md::IDPSSODescriptor& idpDescriptor, - const X509TrustEngine* trustEngine=NULL + SecurityPolicy& policy ) const=0; /** * Resolves a SAML 2.0 artifact into the corresponding SAML protocol message. * The caller is responsible for the resulting ArtifactResponse message. + * The supplied SecurityPolicy is used to access caller-supplied infrastructure + * and to pass back the result of authenticating the resolution process. * - * @param securityMech will be set to identifier of security mechanism that authenticated the resolution * @param artifact reference to a SAML 2.0 artifact * @param ssoDescriptor reference to SSO role of artifact issuer (may be SP or IdP) - * @param trustEngine optional pointer to X509TrustEngine supplied to MessageDecoder + * @param policy reference to policy containing rules, MetadataProvider, TrustEngine, etc. * @return the corresponding SAML protocol message or NULL */ virtual saml2p::ArtifactResponse* resolve( - const XMLCh*& securityMech, const saml2p::SAML2Artifact& artifact, const saml2md::SSODescriptorType& ssoDescriptor, - const X509TrustEngine* trustEngine=NULL + SecurityPolicy& policy ) const=0; }; @@ -233,35 +149,23 @@ namespace opensaml { } /** - * Decodes an HTTP request into a SAML protocol message, and returns related - * information about the issuer of the message and whether it can be trusted. - * If the HTTP request does not contain the information necessary to decode - * the request, a NULL will be returned. Errors during the decoding process - * will be raised as exceptions. + * Decodes a transport request into a SAML protocol message, and evaluates it + * against a supplied SecurityPolicy. If the transport request does not contain + * the information necessary to decode the request, NULL will be returned. + * Errors during the decoding process will be raised as exceptions. * *

Artifact-based bindings require an ArtifactResolver be set to * turn an artifact into the corresponding message. * - *

In some cases, a message may be returned but not authenticated. The caller - * should examine the issuerTrusted output value to establish this. - * * @param relayState will be set to RelayState/TARGET value accompanying message - * @param issuer will be set to role descriptor of issuing party, if known - * @param securityMech will be set to identifier of security mechanism that authenticates the message - * @param httpRequest reference to interface for accessing HTTP message to decode - * @param metadataProvider optional MetadataProvider instance to authenticate the message - * @param role optional, identifies the role (generally IdP or SP) of the peer who issued the message - * @param trustEngine optional TrustEngine to authenticate the message + * @param genericRequest reference to interface for accessing transport request to decode + * @param policy reference to policy containing rules, MetadataProvider, TrustEngine, etc. * @return the decoded message, or NULL if the decoder did not recognize the request content */ virtual xmltooling::XMLObject* decode( std::string& relayState, - const saml2md::RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const saml2md::MetadataProvider* metadataProvider=NULL, - const xmltooling::QName* role=NULL, - const TrustEngine* trustEngine=NULL + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const=0; protected: diff --git a/saml/binding/MessageEncoder.h b/saml/binding/MessageEncoder.h index 536acf4..9e611ff 100644 --- a/saml/binding/MessageEncoder.h +++ b/saml/binding/MessageEncoder.h @@ -23,10 +23,8 @@ #ifndef __saml_encoder_h__ #define __saml_encoder_h__ -#include +#include -#include -#include #include #include #include @@ -48,59 +46,6 @@ namespace opensaml { virtual ~MessageEncoder() {} /** - * Interface to caller-supplied shim for issuing an HTTP response. - * - *

To supply information to the surrounding web server environment, - * a shim must be supplied in the form of this interface to adapt the - * library to different proprietary server APIs. - * - *

This interface need not be threadsafe. - */ - class SAML_API HTTPResponse { - MAKE_NONCOPYABLE(HTTPResponse); - protected: - HTTPResponse() {} - public: - virtual ~HTTPResponse() {} - - /** - * Sets or clears a response header. - * - * @param name header name - * @param value value to set, or NULL to clear - */ - virtual void setHeader(const char* name, const char* value)=0; - - /** - * Sets a client cookie. - * - * @param name cookie name - * @param value value to set, or NULL to clear - */ - virtual void setCookie(const char* name, const char* value)=0; - - /** - * Redirect the client to the specified URL and complete the response. - * Any headers previously set will be sent ahead of the redirect. - * - * @param url location to redirect client - * @return a result code to return from the calling MessageEncoder - */ - virtual long sendRedirect(const char* url)=0; - - /** - * Sends a completed response to the client. Any headers previously set - * will be sent ahead of the data. - * - * @param inputStream reference to source of response data - * @param status HTTP status code to return - * @param contentType Content-Type header to return - * @return a result code to return from the calling MessageEncoder - */ - virtual long sendResponse(std::istream& inputStream, int status = 200, const char* contentType = "text/html")=0; - }; - - /** * Interface to caller-supplied artifact generation mechanism. * * Generating an artifact for storage and retrieval requires knowledge of @@ -146,7 +91,7 @@ namespace opensaml { } /** - * Encodes an XML object/message into a binding-specific HTTP response. + * Encodes an XML object/message into a binding- and transport-specific response. * The XML content cannot have a parent object, and any existing references to * the content will be invalidated if the encode method returns successfully. * @@ -157,7 +102,7 @@ namespace opensaml { *

Artifact-based bindings require an ArtifactGenerator be set to * produce an artifact suitable for the intended recipient. * - * @param httpResponse reference to interface for sending encoded response to client + * @param genericResponse reference to interface for sending transport response * @param xmlObject XML message to encode * @param destination destination URL for message * @param recipientID optional entityID of message recipient @@ -166,7 +111,7 @@ namespace opensaml { * @param sigAlgorithm optional signature algorithm identifier */ virtual long encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, xmltooling::XMLObject* xmlObject, const char* destination, const char* recipientID=NULL, diff --git a/saml/binding/MessageFlowRule.h b/saml/binding/MessageFlowRule.h new file mode 100644 index 0000000..97c9797 --- /dev/null +++ b/saml/binding/MessageFlowRule.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/MessageFlowRule.h + * + * SAML replay and freshness checking SecurityPolicyRule + */ + +#include + + +namespace opensaml { + /** + * SAML replay and freshness checking SecurityPolicyRule + * + * Subclasses can provide support for additional message types + * by overriding the main method and then calling the check method. + */ + class SAML_API MessageFlowRule : public SecurityPolicyRule + { + public: + MessageFlowRule(const DOMElement* e); + virtual ~MessageFlowRule() {} + + std::pair evaluate( + const GenericRequest& request, + const xmltooling::XMLObject& message, + const saml2md::MetadataProvider* metadataProvider, + const xmltooling::QName* role, + const TrustEngine* trustEngine + ) const; + + protected: + /** + * Performs the check. + * + * @param id message identifier + * @param issueInstant timestamp of protocol message + * + * @exception BindingException raised if a check fails + */ + void check(const XMLCh* id, time_t issueInstant) const; + + private: + bool m_checkReplay; + time_t m_expires; + }; + +}; diff --git a/saml/binding/MessageSigningRule.h b/saml/binding/MessageSigningRule.h new file mode 100644 index 0000000..1fa0581 --- /dev/null +++ b/saml/binding/MessageSigningRule.h @@ -0,0 +1,62 @@ +/* + * 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/MessageSigningRule.h + * + * XML Signature checking SecurityPolicyRule + */ + +#include + + +namespace opensaml { + /** + * XML Signature checking SecurityPolicyRule + * + * Subclasses can provide support for additional message types + * by overriding the issuer derivation method. + */ + class SAML_API MessageSigningRule : public SecurityPolicyRule + { + public: + MessageSigningRule(const DOMElement* e) {} + virtual ~MessageSigningRule() {} + + 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/SecurityPolicy.h b/saml/binding/SecurityPolicy.h new file mode 100644 index 0000000..67259ca --- /dev/null +++ b/saml/binding/SecurityPolicy.h @@ -0,0 +1,174 @@ +/* + * 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/SecurityPolicy.h + * + * Overall policy used to verify the security of an incoming message. + */ + +#ifndef __saml_secpol_h__ +#define __saml_secpol_h__ + +#include +#include + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4250 4251 ) +#endif + +namespace opensaml { + + namespace saml2md { + class SAML_API MetadataProvider; + }; + + /** + * A policy used to verify the security of an incoming message. + * + *

Its security mechanisms may be used to examine the transport layer + * (e.g client certificates and HTTP basic auth passwords) or to check the + * payload of a request to ensure it meets certain criteria (e.g. valid + * digital signature, freshness, replay). + * + *

Policy objects can be reused, but are not thread-safe. + */ + class SAML_API SecurityPolicy + { + MAKE_NONCOPYABLE(SecurityPolicy); + public: + /** + * Constructor for policy. + * + * @param rules reference to array of policy rules to use + * @param metadataProvider locked MetadataProvider instance + * @param role identifies the role (generally IdP or SP) of the policy peer + * @param trustEngine TrustEngine to authenticate policy peer + */ + SecurityPolicy( + const std::vector& rules, + const saml2md::MetadataProvider* metadataProvider=NULL, + const xmltooling::QName* role=NULL, + const TrustEngine* trustEngine=NULL + ) : m_issuer(NULL), m_issuerRole(NULL), m_rules(rules), m_metadata(metadataProvider), + m_role(role ? *role : xmltooling::QName()), m_trust(trustEngine) { + } + virtual ~SecurityPolicy(); + + /** + * Returns the locked MetadataProvider supplied to the policy. + * + * @return the supplied MetadataProvider or NULL + */ + const saml2md::MetadataProvider* getMetadataProvider() const { + return m_metadata; + } + + /** + * Returns the peer role element/type supplied to the policy. + * + * @return the peer role element/type, or an empty QName + */ + const xmltooling::QName* getRole() const { + return &m_role; + } + + /** + * Returns the TrustEngine supplied to the policy. + * + * @return the supplied TrustEngine or NULL + */ + const TrustEngine* getTrustEngine() const { + return m_trust; + } + + /** + * Evaluates the rule against the given request and message, + * possibly populating issuer information in the policy object. + * + * @param request the protocol request + * @param message the incoming message + * @return the identity of the message issuer, in one or more of two forms, or NULL + * + * @throws BindingException thrown if the request/message do not meet the requirements of this rule + */ + void evaluate(const GenericRequest& request, const xmltooling::XMLObject& message); + + /** + * Gets the issuer of the message as determined by the registered policies. + * + * @return issuer of the message as determined by the registered policies + */ + const saml2::Issuer* getIssuer() const { + return m_issuer; + } + + /** + * Gets the metadata for the role the issuer is operating in. + * + * @return metadata for the role the issuer is operating in + */ + const saml2md::RoleDescriptor* getIssuerMetadata() const { + return m_issuerRole; + } + + /** + * Sets the issuer of the message as determined by external factors. + * The policy object takes ownership of the Issuer object. + * + * @param issuer issuer of the message + */ + void setIssuer(saml2::Issuer* issuer); + + /** + * Sets the metadata for the role the issuer is operating in. + * + * @param issuerRole metadata for the role the issuer is operating in + */ + void setIssuerMetadata(const saml2md::RoleDescriptor* issuerRole); + + protected: + /** + * Returns true iff the two operands "match". Applications can override this method to + * support non-standard issuer matching for complex policies. + * + *

The default implementation does a basic comparison of the XML content, treating + * an unsupplied Format as "entityID". + * + * @param issuer1 the first Issuer to match + * @param issuer2 the second Issuer to match + * @return true iff the operands match + */ + virtual bool issuerMatches(const saml2::Issuer* issuer1, const saml2::Issuer* issuer2) const; + + private: + saml2::Issuer* m_issuer; + const saml2md::RoleDescriptor* m_issuerRole; + + std::vector m_rules; + const saml2md::MetadataProvider* m_metadata; + xmltooling::QName m_role; + const TrustEngine* m_trust; + }; + +}; + +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + +#endif /* __saml_secpol_h__ */ diff --git a/saml/binding/SecurityPolicyRule.h b/saml/binding/SecurityPolicyRule.h new file mode 100644 index 0000000..f547251 --- /dev/null +++ b/saml/binding/SecurityPolicyRule.h @@ -0,0 +1,103 @@ +/* + * 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/SecurityPolicyRule.h + * + * Policy rules that secure and authenticate bindings. + */ + +#ifndef __saml_secrule_h__ +#define __saml_secrule_h__ + +#include +#include + +namespace opensaml { + class SAML_API TrustEngine; + + namespace saml2 { + class SAML_API Issuer; + }; + namespace saml2md { + class SAML_API RoleDescriptor; + }; + + /** + * A rule that a protocol request and message must meet in order to be valid and secure. + * + *

Rules must be stateless and thread-safe across evaluations. Evaluation should not + * result in an exception if the request/message properties do not apply to the rule + * (e.g. particular security mechanisms that are not present). + */ + class SAML_API SecurityPolicyRule + { + MAKE_NONCOPYABLE(SecurityPolicyRule); + protected: + SecurityPolicyRule() {} + public: + virtual ~SecurityPolicyRule() {} + + /** + * Evaluates the rule against the given request and message. If an Issuer is + * returned, the caller is responsible for freeing the Issuer object. + * + * @param request the protocol request + * @param message the incoming message + * @param metadataProvider locked MetadataProvider instance to authenticate the message + * @param role identifies the role (generally IdP or SP) of the peer who issued the message + * @param trustEngine TrustEngine to authenticate the message + * @return the identity of the message issuer, in two forms, or NULL + * + * @throws BindingException thrown if the request/message do not meet the requirements of this rule + */ + virtual std::pair evaluate( + const GenericRequest& request, + const xmltooling::XMLObject& message, + const saml2md::MetadataProvider* metadataProvider, + const xmltooling::QName* role, + const TrustEngine* trustEngine + ) const=0; + }; + + /** + * Registers SecurityPolicyRule plugins into the runtime. + */ + void SAML_API registerSecurityPolicyRules(); + + /** + * SecurityPolicyRule for replay detection and freshness checking. + * + *

A ReplayCache instance must be available from the runtime, unless + * a "checkReplay" XML attribute is set to "0" or "false" when instantiating + * the policy. + * + *

Messages must have been issued in the past, but no more than 60 seconds ago, + * or up to a number of seconds set by an "expires" XML attribute when + * instantiating the policy. + */ + #define MESSAGEFLOW_POLICY_RULE "org.opensaml.binding.MessageFlowRule" + + /** + * SecurityPolicyRule for protocol message 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. + */ + #define MESSAGESIGNING_POLICY_RULE "org.opensaml.binding.MessageSigningRule" +}; + +#endif /* __saml_secrule_h__ */ diff --git a/saml/binding/impl/MessageFlowRule.cpp b/saml/binding/impl/MessageFlowRule.cpp new file mode 100644 index 0000000..0e8cea3 --- /dev/null +++ b/saml/binding/impl/MessageFlowRule.cpp @@ -0,0 +1,107 @@ +/* + * 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. + */ + +/** + * MessageFlowRule.cpp + * + * SAML replay and freshness checking SecurityPolicyRule + */ + +#include "internal.h" +#include "exceptions.h" +#include "RootObject.h" +#include "binding/MessageFlowRule.h" + +#include +#include +#include + +using namespace opensaml; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace opensaml { + SecurityPolicyRule* SAML_DLLLOCAL MessageFlowRuleFactory(const DOMElement* const & e) + { + return new MessageFlowRule(e); + } +}; + +static const XMLCh checkReplay[] = UNICODE_LITERAL_11(c,h,e,c,k,R,e,p,l,a,y); +static const XMLCh expires[] = UNICODE_LITERAL_7(e,x,p,i,r,e,s); + +MessageFlowRule::MessageFlowRule(const DOMElement* e) + : m_checkReplay(true), m_expires(XMLToolingConfig::getConfig().clock_skew_secs) +{ + if (e) { + const XMLCh* attr = e->getAttributeNS(NULL, checkReplay); + if (attr && (*attr==chLatin_f || *attr==chDigit_0)) + m_checkReplay = false; + attr = e->getAttributeNS(NULL, expires); + if (attr) + m_expires = XMLString::parseInt(attr); + } +} + +pair MessageFlowRule::evaluate( + const GenericRequest& request, + const XMLObject& message, + const saml2md::MetadataProvider* metadataProvider, + const QName* role, + const opensaml::TrustEngine* trustEngine + ) const +{ + try { + const RootObject& obj = dynamic_cast(message); + check(obj.getID(), obj.getIssueInstantEpoch()); + } + catch (bad_cast&) { + throw BindingException("Message was not of a recognized SAML root type."); + } + return pair(NULL,NULL); +} + +void MessageFlowRule::check(const XMLCh* id, time_t issueInstant) const +{ + Category& log=Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.MessageFlow"); + log.debug("evaluating message flow policy (replay checking %s, expiration %lu)", m_checkReplay ? "on" : "off", m_expires); + + time_t skew = XMLToolingConfig::getConfig().clock_skew_secs; + time_t now = time(NULL); + if (issueInstant > now + skew) { + log.error("rejected not-yet-valid message, timestamp (%lu), now (%lu)", issueInstant, now + skew); + throw BindingException("Message rejected, was issued in the future."); + } + else if (issueInstant < now - skew - m_expires) { + log.error("rejected expired message, timestamp (%lu), oldest allowed (%lu)", issueInstant, now - skew - m_expires); + throw BindingException("Message expired, was issued too long ago."); + } + + // Check replay. + if (m_checkReplay) { + ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); + if (!replayCache) + throw BindingException("No ReplayCache instance available."); + else if (!id) + throw BindingException("Message did not contain an identifier."); + auto_ptr_char temp(id); + if (!replayCache->check("SAML", temp.get(), issueInstant + skew + m_expires)) { + log.error("replay detected of message ID (%s)", temp.get()); + throw BindingException("Rejecting replayed message ID ($1).", params(1,temp.get())); + } + } +} diff --git a/saml/binding/impl/MessageSigningRule.cpp b/saml/binding/impl/MessageSigningRule.cpp new file mode 100644 index 0000000..6b9eedf --- /dev/null +++ b/saml/binding/impl/MessageSigningRule.cpp @@ -0,0 +1,177 @@ +/* + * 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. + */ + +/** + * MessageSigningRule.cpp + * + * XML Signature checking SecurityPolicyRule + */ + +#include "internal.h" +#include "exceptions.h" +#include "RootObject.h" +#include "binding/MessageSigningRule.h" +#include "saml1/core/Assertions.h" +#include "saml1/core/Protocols.h" +#include "saml2/core/Protocols.h" +#include "saml2/metadata/Metadata.h" +#include "saml2/metadata/MetadataProvider.h" +#include "security/TrustEngine.h" + +#include +#include +#include + +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace opensaml { + SecurityPolicyRule* SAML_DLLLOCAL MessageSigningRuleFactory(const DOMElement* const & e) + { + return new MessageSigningRule(e); + } +}; + +pair MessageSigningRule::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.MessageSigning"); + log.debug("evaluating message signing policy"); + + pair ret = pair(NULL,NULL); + + if (!metadataProvider || !role || !trustEngine) { + log.debug("ignoring message, no metadata or trust information supplied"); + return ret; + } + + try { + const RootObject& root = dynamic_cast(message); + if (!root.getSignature()) { + log.debug("ignoring unsigned message"); + return ret; + } + + 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; + } + + if (!trustEngine->validate(*(root.getSignature()), *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 MessageSigningRule::getIssuerAndProtocol(const XMLObject& message) const +{ + // We just let any bad casts throw here. + + saml2::Issuer* issuer; + + // 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); + 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); + } + } + else if (XMLString::equals(ns, samlconstants::SAML1P_NS)) { + // Should be a samlp:Response, at least in OpenSAML. + const vector& assertions = dynamic_cast(message).getAssertions(); + if (!assertions.empty()) { + const saml1::Assertion* a = assertions.front(); + if (a->getIssuer()) { + issuer = saml2::IssuerBuilder::buildIssuer(); + issuer->setName(a->getIssuer()); + pair minor = a->getMinorVersion(); + return make_pair( + issuer, + (minor.first && minor.second==0) ? samlconstants::SAML10_PROTOCOL_ENUM : samlconstants::SAML11_PROTOCOL_ENUM + ); + } + } + } + else if (XMLString::equals(ns, samlconstants::SAML1_NS)) { + // Should be a saml:Assertion. + const saml1::Assertion& a = dynamic_cast(message); + if (a.getIssuer()) { + issuer = saml2::IssuerBuilder::buildIssuer(); + issuer->setName(a.getIssuer()); + pair minor = a.getMinorVersion(); + return make_pair( + issuer, + (minor.first && minor.second==0) ? samlconstants::SAML10_PROTOCOL_ENUM : samlconstants::SAML11_PROTOCOL_ENUM + ); + } + } + } + return pair(NULL,NULL); +} diff --git a/saml/binding/impl/SecurityPolicy.cpp b/saml/binding/impl/SecurityPolicy.cpp new file mode 100644 index 0000000..0b30af3 --- /dev/null +++ b/saml/binding/impl/SecurityPolicy.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +/** + * SecurityPolicy.cpp + * + * Overall policy used to verify the security of an incoming message. + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/SecurityPolicy.h" +#include "saml2/core/Assertions.h" + +using namespace opensaml::saml2md; +using namespace opensaml::saml2; +using namespace opensaml; +using namespace xmltooling; +using namespace std; + +namespace opensaml { + SAML_DLLLOCAL PluginManager::Factory MessageFlowRuleFactory; + SAML_DLLLOCAL PluginManager::Factory MessageSigningRuleFactory; +}; + +void SAML_API opensaml::registerSecurityPolicyRules() +{ + SAMLConfig& conf=SAMLConfig::getConfig(); + conf.SecurityPolicyRuleManager.registerFactory(MESSAGEFLOW_POLICY_RULE, MessageFlowRuleFactory); + conf.SecurityPolicyRuleManager.registerFactory(MESSAGESIGNING_POLICY_RULE, MessageSigningRuleFactory); +} + +SecurityPolicy::~SecurityPolicy() +{ + delete m_issuer; +} + +void SecurityPolicy::evaluate(const GenericRequest& request, const XMLObject& message) +{ + for (vector::const_iterator i=m_rules.begin(); i!=m_rules.end(); ++i) { + + // Run the rule... + pair ident = (*i)->evaluate(request,message,m_metadata,&m_role,m_trust); + + // Make sure returned issuer doesn't conflict. + + if (ident.first) { + if (!issuerMatches(ident.first, m_issuer)) { + delete ident.first; + throw BindingException("Policy rules returned differing Issuers."); + } + delete m_issuer; + m_issuer=ident.first; + } + + if (ident.second) { + if (m_issuerRole && ident.second!=m_issuerRole) + throw BindingException("Policy rules returned differing issuer RoleDescriptors."); + m_issuerRole=ident.second; + } + } +} + +void SecurityPolicy::setIssuer(saml2::Issuer* issuer) +{ + if (!issuerMatches(issuer, m_issuer)) { + delete issuer; + throw BindingException("Externally provided Issuer conflicts with policy results."); + } + + delete m_issuer; + m_issuer=issuer; +} + +void SecurityPolicy::setIssuerMetadata(const saml2md::RoleDescriptor* issuerRole) +{ + if (issuerRole && m_issuerRole && issuerRole!=m_issuerRole) + throw BindingException("Externally provided RoleDescriptor conflicts with policy results."); + m_issuerRole=issuerRole; +} + +bool SecurityPolicy::issuerMatches(const Issuer* issuer1, const Issuer* issuer2) const +{ + // NULL matches anything for the purposes of this interface. + if (!issuer1 || !issuer2) + return true; + + const XMLCh* op1=issuer1->getName(); + const XMLCh* op2=issuer2->getName(); + if (!op1 || !op2 || !XMLString::equals(op1,op2)) + return false; + + op1=issuer1->getFormat(); + op2=issuer2->getFormat(); + if (!XMLString::equals(op1 ? op1 : NameIDType::ENTITY, op2 ? op2 : NameIDType::ENTITY)) + return false; + + op1=issuer1->getNameQualifier(); + op2=issuer2->getNameQualifier(); + if (!XMLString::equals(op1 ? op1 : &chNull, op2 ? op2 : &chNull)) + return false; + + op1=issuer1->getSPNameQualifier(); + op2=issuer2->getSPNameQualifier(); + if (!XMLString::equals(op1 ? op1 : &chNull, op2 ? op2 : &chNull)) + return false; + + return true; +} diff --git a/saml/saml.vcproj b/saml/saml.vcproj index 3f2c224..6a44790 100644 --- a/saml/saml.vcproj +++ b/saml/saml.vcproj @@ -414,6 +414,10 @@ RelativePath=".\saml2\binding\impl\SAML2POSTEncoder.cpp" > + + @@ -468,15 +472,55 @@ > + + + + + + + + + + + + + + + + + + + + + + @@ -645,6 +693,14 @@ RelativePath=".\saml2\binding\SAML2POSTEncoder.h" > + + + + + + + + + + + + @@ -695,14 +767,66 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + #include @@ -34,9 +34,7 @@ using namespace opensaml::saml2md; using namespace opensaml::saml1p; -using namespace opensaml::saml1; using namespace opensaml; -using namespace xmlsignature; using namespace xmltooling; using namespace log4cpp; using namespace std; @@ -56,12 +54,8 @@ SAML1ArtifactDecoder::~SAML1ArtifactDecoder() {} Response* SAML1ArtifactDecoder::decode( string& relayState, - const RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const MetadataProvider* metadataProvider, - const QName* role, - const opensaml::TrustEngine* trustEngine + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const { #ifdef _DEBUG @@ -70,16 +64,21 @@ Response* SAML1ArtifactDecoder::decode( Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML1Artifact"); log.debug("validating input"); - if (strcmp(httpRequest.getMethod(),"GET")) + const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); + if (!httpRequest) { + log.error("unable to cast request to HTTPRequest type"); + return NULL; + } + if (strcmp(httpRequest->getMethod(),"GET")) return NULL; vector SAMLart; - const char* TARGET = httpRequest.getParameter("TARGET"); - if (httpRequest.getParameters("SAMLart", SAMLart)==0 || !TARGET) + const char* TARGET = httpRequest->getParameter("TARGET"); + if (httpRequest->getParameters("SAMLart", SAMLart)==0 || !TARGET) return NULL; relayState = TARGET; - if (!m_artifactResolver || !metadataProvider) - throw BindingException("Artifact binding requires ArtifactResolver and MetadataProvider implementations be supplied."); + if (!m_artifactResolver || !policy.getMetadataProvider() || !policy.getRole()) + throw BindingException("Artifact profile requires ArtifactResolver and MetadataProvider implementations be supplied."); // Import the artifacts. vector artifacts; @@ -111,10 +110,8 @@ Response* SAML1ArtifactDecoder::decode( } } - issuer = NULL; - securityMech = false; log.debug("attempting to determine source of artifact(s)..."); - const EntityDescriptor* provider=metadataProvider->getEntityDescriptor(artifacts.front()); + const EntityDescriptor* provider=policy.getMetadataProvider()->getEntityDescriptor(artifacts.front()); if (!provider) { log.error( "metadata lookup failed, unable to determine issuer of artifact (0x%s)", @@ -130,11 +127,11 @@ Response* SAML1ArtifactDecoder::decode( } log.debug("attempting to find artifact issuing role..."); - issuer=provider->getRoleDescriptor(*role, samlconstants::SAML11_PROTOCOL_ENUM); - if (!issuer) - issuer=provider->getRoleDescriptor(*role, samlconstants::SAML10_PROTOCOL_ENUM); - if (!issuer || !dynamic_cast(issuer)) { - log.error("unable to find compatible SAML role (%s) in metadata", role->toString().c_str()); + const RoleDescriptor* roledesc=provider->getRoleDescriptor(*(policy.getRole()), samlconstants::SAML11_PROTOCOL_ENUM); + if (!roledesc) + roledesc=provider->getRoleDescriptor(*(policy.getRole()), samlconstants::SAML10_PROTOCOL_ENUM); + if (!roledesc || !dynamic_cast(roledesc)) { + log.error("unable to find compatible SAML role (%s) in metadata", policy.getRole()->toString().c_str()); for_each(artifacts.begin(), artifacts.end(), xmltooling::cleanup()); BindingException ex("Unable to find compatible metadata role for artifact issuer."); annotateException(&ex,provider); // throws it @@ -142,33 +139,17 @@ Response* SAML1ArtifactDecoder::decode( try { auto_ptr response( - m_artifactResolver->resolve( - securityMech, - artifacts, - dynamic_cast(*issuer), - dynamic_cast(trustEngine) - ) + m_artifactResolver->resolve(artifacts, dynamic_cast(*roledesc), policy) ); - if (trustEngine && response->getSignature()) { - if (!trustEngine->validate(*(response->getSignature()), *issuer, metadataProvider->getKeyResolver())) { - log.error("unable to verify signature on message with supplied trust engine"); - throw BindingException("Message signature failed verification."); - } - else if (!securityMech) { - securityMech = samlconstants::SAML1P_NS; - } - } - else if (!securityMech) { - log.warn("unable to authenticate the message, leaving untrusted"); - } + policy.evaluate(genericRequest, *(response.get())); for_each(artifacts.begin(), artifacts.end(), xmltooling::cleanup()); return response.release(); } catch (XMLToolingException& ex) { for_each(artifacts.begin(), artifacts.end(), xmltooling::cleanup()); - annotateException(&ex,issuer,false); + annotateException(&ex,roledesc,false); throw; } } diff --git a/saml/saml1/binding/impl/SAML1ArtifactEncoder.cpp b/saml/saml1/binding/impl/SAML1ArtifactEncoder.cpp index b747201..7252831 100644 --- a/saml/saml1/binding/impl/SAML1ArtifactEncoder.cpp +++ b/saml/saml1/binding/impl/SAML1ArtifactEncoder.cpp @@ -23,6 +23,7 @@ #include "internal.h" #include "exceptions.h" #include "binding/ArtifactMap.h" +#include "binding/HTTPResponse.h" #include "binding/SAMLArtifact.h" #include "binding/URLEncoder.h" #include "saml1/binding/SAML1ArtifactEncoder.h" @@ -53,7 +54,7 @@ SAML1ArtifactEncoder::SAML1ArtifactEncoder(const DOMElement* e) {} SAML1ArtifactEncoder::~SAML1ArtifactEncoder() {} long SAML1ArtifactEncoder::encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, XMLObject* xmlObject, const char* destination, const char* recipientID, @@ -66,8 +67,11 @@ long SAML1ArtifactEncoder::encode( xmltooling::NDC ndc("encode"); #endif Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML1Artifact"); + 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."); Assertion* assertion = dynamic_cast(xmlObject); @@ -98,5 +102,5 @@ long SAML1ArtifactEncoder::encode( URLEncoder* escaper = SAMLConfig::getConfig().getURLEncoder(); loc = loc + "SAMLart=" + escaper->encode(artifact->encode().c_str()) + "&TARGET=" + escaper->encode(relayState); log.debug("message encoded, sending redirect to client"); - return httpResponse.sendRedirect(loc.c_str()); + return httpResponse->sendRedirect(loc.c_str()); } diff --git a/saml/saml1/binding/impl/SAML1POSTDecoder.cpp b/saml/saml1/binding/impl/SAML1POSTDecoder.cpp index ebdb2a4..33ac05f 100644 --- a/saml/saml1/binding/impl/SAML1POSTDecoder.cpp +++ b/saml/saml1/binding/impl/SAML1POSTDecoder.cpp @@ -22,23 +22,21 @@ #include "internal.h" #include "exceptions.h" +#include "binding/HTTPRequest.h" #include "saml1/core/Assertions.h" #include "saml1/binding/SAML1POSTDecoder.h" #include "saml2/metadata/Metadata.h" #include "saml2/metadata/MetadataProvider.h" -#include "security/X509TrustEngine.h" #include #include #include -#include #include using namespace opensaml::saml2md; using namespace opensaml::saml1p; using namespace opensaml::saml1; using namespace opensaml; -using namespace xmlsignature; using namespace xmltooling; using namespace log4cpp; using namespace std; @@ -58,12 +56,8 @@ SAML1POSTDecoder::~SAML1POSTDecoder() {} Response* SAML1POSTDecoder::decode( string& relayState, - const RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const MetadataProvider* metadataProvider, - const QName* role, - const opensaml::TrustEngine* trustEngine + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const { #ifdef _DEBUG @@ -72,10 +66,15 @@ Response* SAML1POSTDecoder::decode( Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML1POST"); log.debug("validating input"); - if (strcmp(httpRequest.getMethod(),"POST")) + const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); + if (!httpRequest) { + log.error("unable to cast request to HTTPRequest type"); return NULL; - const char* samlResponse = httpRequest.getParameter("SAMLResponse"); - const char* TARGET = httpRequest.getParameter("TARGET"); + } + if (strcmp(httpRequest->getMethod(),"POST")) + return NULL; + const char* samlResponse = httpRequest->getParameter("SAMLResponse"); + const char* TARGET = httpRequest->getParameter("TARGET"); if (!samlResponse || !TARGET) return NULL; relayState = TARGET; @@ -100,14 +99,13 @@ Response* SAML1POSTDecoder::decode( if (!response) throw BindingException("Decoded message was not a SAML 1.x Response."); - const EntityDescriptor* provider=NULL; try { if (!m_validate) SchemaValidators.validate(xmlObject.get()); // Check recipient URL. auto_ptr_char recipient(response->getRecipient()); - const char* recipient2 = httpRequest.getRequestURL(); + const char* recipient2 = httpRequest->getRequestURL(); if (!recipient.get() || !*(recipient.get())) { log.error("response missing Recipient attribute"); throw BindingException("SAML response did not contain Recipient attribute identifying intended destination."); @@ -117,98 +115,33 @@ Response* SAML1POSTDecoder::decode( throw BindingException("SAML message delivered with POST to incorrect server URL."); } - // Check freshness. - time_t now = time(NULL); - if (response->getIssueInstant()->getEpoch() < now-(2*XMLToolingConfig::getConfig().clock_skew_secs)) - throw BindingException("Detected expired POST profile response."); - - // Check replay. - ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); - if (replayCache) { - auto_ptr_char id(response->getResponseID()); - if (!replayCache->check("SAML1POST", id.get(), response->getIssueInstant()->getEpoch() + (2*XMLToolingConfig::getConfig().clock_skew_secs))) { - log.error("replay detected of response ID (%s)", id.get()); - throw BindingException("Rejecting replayed response ID ($1).", params(1,id.get())); - } - } - else - log.warn("replay cache was not provided, this is a serious security risk!"); - - /* For SAML 1, the issuer can only be established from any assertions in the message. - * Generally, errors aren't delivered like this, so there should be one. - * The Issuer attribute is matched against metadata, and then trust checking can be - * applied. - */ - issuer = NULL; - securityMech = NULL; - log.debug("attempting to establish issuer and integrity of message..."); - const vector& assertions=const_cast(response)->getAssertions(); - if (!assertions.empty()) { - log.debug("searching metadata for assertion issuer..."); - provider=metadataProvider ? metadataProvider->getEntityDescriptor(assertions.front()->getIssuer()) : NULL; - if (provider) { - log.debug("matched assertion issuer against metadata, searching for applicable role..."); - pair minor = response->getMinorVersion(); - issuer=provider->getRoleDescriptor( - *role, - (minor.first && minor.second==0) ? samlconstants::SAML10_PROTOCOL_ENUM : samlconstants::SAML11_PROTOCOL_ENUM - ); - if (issuer) { - if (trustEngine && response->getSignature()) { - if (trustEngine->validate(*(response->getSignature()), *issuer, metadataProvider->getKeyResolver())) { - securityMech = samlconstants::SAML1P_NS; - } - else { - log.error("unable to verify signature on message with supplied trust engine"); - throw BindingException("Message signature failed verification."); - } - } - else { - log.warn("unable to authenticate the message, leaving untrusted"); - } - } - else { - log.warn( - "unable to find compatible SAML 1.%d role (%s) in metadata", - (minor.first && minor.second==0) ? 0 : 1, - role->toString().c_str() - ); - } - if (log.isDebugEnabled()) { - auto_ptr_char iname(assertions.front()->getIssuer()); - log.debug("message from (%s), integrity %sverified", iname.get(), securityMech ? "" : "NOT "); - } - } - else { - auto_ptr_char temp(assertions.front()->getIssuer()); - log.warn("no metadata found, can't establish identity of issuer (%s)", temp.get()); - } - } - else { - log.warn("no assertions found, can't establish identity of issuer"); - } + // Run through the policy. + policy.evaluate(genericRequest, *response); } catch (XMLToolingException& ex) { + // This is just to maximize the likelihood of attaching a source to the message for support purposes. + if (policy.getIssuerMetadata()) + annotateException(&ex,policy.getIssuerMetadata()); // throws it + // Check for an Issuer. - if (!provider) { - const vector& assertions=const_cast(response)->getAssertions(); - if (!assertions.empty() || !metadataProvider || - !(provider=metadataProvider->getEntityDescriptor(assertions.front()->getIssuer(), false))) { - // Just record it. - auto_ptr_char iname(assertions.front()->getIssuer()); - if (iname.get()) - ex.addProperty("entityID", iname.get()); - throw; - } + const EntityDescriptor* provider=NULL; + const vector& assertions=const_cast(response)->getAssertions(); + if (!assertions.empty() || !policy.getMetadataProvider() || + !(provider=policy.getMetadataProvider()->getEntityDescriptor(assertions.front()->getIssuer(), false))) { + // Just record it. + auto_ptr_char iname(assertions.front()->getIssuer()); + if (iname.get()) + ex.addProperty("entityID", iname.get()); + throw; } - if (!issuer) { + if (policy.getRole()) { pair minor = response->getMinorVersion(); - issuer=provider->getRoleDescriptor( - *role, + const RoleDescriptor* roledesc=provider->getRoleDescriptor( + *(policy.getRole()), (minor.first && minor.second==0) ? samlconstants::SAML10_PROTOCOL_ENUM : samlconstants::SAML11_PROTOCOL_ENUM ); + if (roledesc) annotateException(&ex,roledesc); // throws it } - if (issuer) annotateException(&ex,issuer); // throws it annotateException(&ex,provider); // throws it } diff --git a/saml/saml1/binding/impl/SAML1POSTEncoder.cpp b/saml/saml1/binding/impl/SAML1POSTEncoder.cpp index ccc5510..ce99ce1 100644 --- a/saml/saml1/binding/impl/SAML1POSTEncoder.cpp +++ b/saml/saml1/binding/impl/SAML1POSTEncoder.cpp @@ -22,6 +22,7 @@ #include "internal.h" #include "exceptions.h" +#include "binding/HTTPResponse.h" #include "saml1/binding/SAML1POSTEncoder.h" #include "saml1/core/Protocols.h" @@ -64,7 +65,7 @@ SAML1POSTEncoder::SAML1POSTEncoder(const DOMElement* e) SAML1POSTEncoder::~SAML1POSTEncoder() {} long SAML1POSTEncoder::encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, XMLObject* xmlObject, const char* destination, const char* recipientID, @@ -77,8 +78,11 @@ long SAML1POSTEncoder::encode( xmltooling::NDC ndc("encode"); #endif Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML1POST"); + 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."); Response* response = dynamic_cast(xmlObject); @@ -137,7 +141,8 @@ long SAML1POSTEncoder::encode( params["TARGET"] = relayState; stringstream s; engine->run(infile, s, params); - long ret = httpResponse.sendResponse(s); + httpResponse->setContentType("text/html"); + long ret = httpResponse->sendResponse(s, HTTPResponse::SAML_HTTP_STATUS_OK); // Cleanup by destroying XML. delete xmlObject; diff --git a/saml/saml1/core/Assertions.h b/saml/saml1/core/Assertions.h index b4cf753..0da3ecf 100644 --- a/saml/saml1/core/Assertions.h +++ b/saml/saml1/core/Assertions.h @@ -23,7 +23,7 @@ #ifndef __saml1_assertions_h__ #define __saml1_assertions_h__ -#include +#include #include #include @@ -217,11 +217,11 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,Assertion,SignableObject,SAML 1.x Assertion element); + BEGIN_XMLOBJECT(SAML_API,Assertion,RootObject,SAML 1.x Assertion element); DECL_INTEGER_ATTRIB(MinorVersion,MINORVERSION); DECL_STRING_ATTRIB(AssertionID,ASSERTIONID); DECL_STRING_ATTRIB(Issuer,ISSUER); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); DECL_TYPED_CHILD(Conditions); DECL_TYPED_CHILD(Advice); DECL_TYPED_CHILDREN(Statement); @@ -229,7 +229,7 @@ namespace opensaml { DECL_TYPED_CHILDREN(AuthenticationStatement); DECL_TYPED_CHILDREN(AttributeStatement); DECL_TYPED_CHILDREN(AuthorizationDecisionStatement); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); /** AssertionType local name */ static const XMLCh TYPE_NAME[]; END_XMLOBJECT; diff --git a/saml/saml1/core/Protocols.h b/saml/saml1/core/Protocols.h index 074edea..2201d09 100644 --- a/saml/saml1/core/Protocols.h +++ b/saml/saml1/core/Protocols.h @@ -23,7 +23,7 @@ #ifndef __saml1_protocols_h__ #define __saml1_protocols_h__ -#include +#include #include #include @@ -91,12 +91,12 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,RequestAbstractType,SignableObject,SAML 1.x RequestAbstractType base type); + BEGIN_XMLOBJECT(SAML_API,RequestAbstractType,RootObject,SAML 1.x RequestAbstractType base type); DECL_INTEGER_ATTRIB(MinorVersion,MINORVERSION); DECL_STRING_ATTRIB(RequestID,REQUESTID); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); DECL_TYPED_CHILDREN(RespondWith); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); /** RequestAbstractType local name */ static const XMLCh TYPE_NAME[]; END_XMLOBJECT; @@ -142,13 +142,13 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,ResponseAbstractType,SignableObject,SAML 1.x ResponseAbstractType base type); + BEGIN_XMLOBJECT(SAML_API,ResponseAbstractType,RootObject,SAML 1.x ResponseAbstractType base type); DECL_INTEGER_ATTRIB(MinorVersion,MINORVERSION); DECL_STRING_ATTRIB(ResponseID,RESPONSEID); DECL_STRING_ATTRIB(InResponseTo,INRESPONSETO); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); DECL_STRING_ATTRIB(Recipient,RECIPIENT); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); /** ResponseAbstractType local name */ static const XMLCh TYPE_NAME[]; END_XMLOBJECT; diff --git a/saml/saml1/core/impl/AssertionsImpl.cpp b/saml/saml1/core/impl/AssertionsImpl.cpp index 6d6bb1c..a8127ab 100644 --- a/saml/saml1/core/impl/AssertionsImpl.cpp +++ b/saml/saml1/core/impl/AssertionsImpl.cpp @@ -1014,6 +1014,9 @@ namespace opensaml { pair v = getMinorVersion(); return (!v.first || v.second > 0) ? m_AssertionID : NULL; } + const XMLCh* getID() const { + return getAssertionID(); + } IMPL_STRING_ATTRIB(Issuer); IMPL_DATETIME_ATTRIB(IssueInstant,0); IMPL_TYPED_CHILD(Conditions); diff --git a/saml/saml1/core/impl/ProtocolsImpl.cpp b/saml/saml1/core/impl/ProtocolsImpl.cpp index a823253..550367c 100644 --- a/saml/saml1/core/impl/ProtocolsImpl.cpp +++ b/saml/saml1/core/impl/ProtocolsImpl.cpp @@ -361,6 +361,9 @@ namespace opensaml { pair v = getMinorVersion(); return (!v.first || v.second > 0) ? m_RequestID : NULL; } + const XMLCh* getID() const { + return getRequestID(); + } IMPL_DATETIME_ATTRIB(IssueInstant,0); IMPL_TYPED_CHILDREN(RespondWith,m_pos_Signature); @@ -680,6 +683,9 @@ namespace opensaml { pair v = getMinorVersion(); return (!v.first || v.second > 0) ? m_ResponseID : NULL; } + const XMLCh* getID() const { + return getResponseID(); + } IMPL_STRING_ATTRIB(InResponseTo); IMPL_DATETIME_ATTRIB(IssueInstant,0); IMPL_STRING_ATTRIB(Recipient); diff --git a/saml/saml2/binding/SAML2ArtifactDecoder.h b/saml/saml2/binding/SAML2ArtifactDecoder.h index 9c3cd96..bf297c0 100644 --- a/saml/saml2/binding/SAML2ArtifactDecoder.h +++ b/saml/saml2/binding/SAML2ArtifactDecoder.h @@ -41,16 +41,9 @@ namespace opensaml { xmltooling::XMLObject* decode( std::string& relayState, - const saml2md::RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const saml2md::MetadataProvider* metadataProvider=NULL, - const xmltooling::QName* role=NULL, - const TrustEngine* trustEngine=NULL + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const; - - protected: - bool issuerMatches(const saml2::Issuer* messageIssuer, const XMLCh* expectedIssuer) const; }; }; diff --git a/saml/saml2/binding/SAML2ArtifactEncoder.h b/saml/saml2/binding/SAML2ArtifactEncoder.h index 01057f8..d3f5f3d 100644 --- a/saml/saml2/binding/SAML2ArtifactEncoder.h +++ b/saml/saml2/binding/SAML2ArtifactEncoder.h @@ -36,7 +36,7 @@ namespace opensaml { virtual ~SAML2ArtifactEncoder(); long encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, xmltooling::XMLObject* xmlObject, const char* destination, const char* recipientID=NULL, diff --git a/saml/saml2/binding/SAML2POSTDecoder.h b/saml/saml2/binding/SAML2POSTDecoder.h index 31c2f6a..00edb7a 100644 --- a/saml/saml2/binding/SAML2POSTDecoder.h +++ b/saml/saml2/binding/SAML2POSTDecoder.h @@ -21,8 +21,10 @@ */ #include +#include namespace opensaml { + namespace saml2p { /** @@ -34,14 +36,10 @@ namespace opensaml { SAML2POSTDecoder(const DOMElement* e); virtual ~SAML2POSTDecoder(); - xmltooling::XMLObject* decode( + saml2::RootObject* decode( std::string& relayState, - const saml2md::RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const saml2md::MetadataProvider* metadataProvider=NULL, - const xmltooling::QName* role=NULL, - const TrustEngine* trustEngine=NULL + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const; }; diff --git a/saml/saml2/binding/SAML2POSTEncoder.h b/saml/saml2/binding/SAML2POSTEncoder.h index 3f325e4..4417849 100644 --- a/saml/saml2/binding/SAML2POSTEncoder.h +++ b/saml/saml2/binding/SAML2POSTEncoder.h @@ -36,7 +36,7 @@ namespace opensaml { virtual ~SAML2POSTEncoder(); long encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, xmltooling::XMLObject* xmlObject, const char* destination, const char* recipientID=NULL, diff --git a/saml/saml2/binding/SAML2Redirect.h b/saml/saml2/binding/SAML2Redirect.h new file mode 100644 index 0000000..d6b8400 --- /dev/null +++ b/saml/saml2/binding/SAML2Redirect.h @@ -0,0 +1,32 @@ +/* + * 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/SAML2Redirect.h + * + * SAML 2.0 HTTP Redirect compression functionality + */ + +#include +#include + +namespace opensaml { + namespace saml2p { + + unsigned int inflate(char* in, unsigned int inlen, std::ostream& out); + + }; +}; diff --git a/saml/saml2/binding/SAML2RedirectDecoder.h b/saml/saml2/binding/SAML2RedirectDecoder.h new file mode 100644 index 0000000..f0ced2b --- /dev/null +++ b/saml/saml2/binding/SAML2RedirectDecoder.h @@ -0,0 +1,45 @@ +/* + * 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/SAML2RedirectDecoder.h + * + * SAML 2.0 HTTP Redirect binding message decoder + */ + +#include + +namespace opensaml { + namespace saml2p { + + /** + * SAML 2.0 HTTP Redirect binding message decoder + */ + class SAML_API SAML2RedirectDecoder : public MessageDecoder + { + public: + SAML2RedirectDecoder(const DOMElement* e); + virtual ~SAML2RedirectDecoder(); + + xmltooling::XMLObject* decode( + std::string& relayState, + const GenericRequest& genericRequest, + SecurityPolicy& policy + ) const; + }; + + }; +}; diff --git a/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp b/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp index 846254a..1cd434e 100644 --- a/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp +++ b/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp @@ -22,13 +22,13 @@ #include "internal.h" #include "exceptions.h" +#include "binding/HTTPRequest.h" #include "saml/binding/SAMLArtifact.h" +#include "saml2/binding/SAML2Artifact.h" +#include "saml2/binding/SAML2ArtifactDecoder.h" #include "saml2/core/Protocols.h" #include "saml2/metadata/Metadata.h" #include "saml2/metadata/MetadataProvider.h" -#include "saml2/binding/SAML2Artifact.h" -#include "saml2/binding/SAML2ArtifactDecoder.h" -#include "security/X509TrustEngine.h" #include #include @@ -38,7 +38,6 @@ using namespace opensaml::saml2md; using namespace opensaml::saml2p; using namespace opensaml::saml2; using namespace opensaml; -using namespace xmlsignature; using namespace xmltooling; using namespace log4cpp; using namespace std; @@ -58,12 +57,8 @@ SAML2ArtifactDecoder::~SAML2ArtifactDecoder() {} XMLObject* SAML2ArtifactDecoder::decode( string& relayState, - const RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const MetadataProvider* metadataProvider, - const QName* role, - const opensaml::TrustEngine* trustEngine + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const { #ifdef _DEBUG @@ -72,23 +67,28 @@ XMLObject* SAML2ArtifactDecoder::decode( Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2Artifact"); log.debug("validating input"); - const char* SAMLart = httpRequest.getParameter("SAMLart"); + const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); + if (!httpRequest) { + log.error("unable to cast request to HTTPRequest type"); + return NULL; + } + const char* SAMLart = httpRequest->getParameter("SAMLart"); if (!SAMLart) return NULL; - const char* state = httpRequest.getParameter("RelayState"); + const char* state = httpRequest->getParameter("RelayState"); if (state) relayState = state; - if (!m_artifactResolver || !metadataProvider) + if (!m_artifactResolver || !policy.getMetadataProvider() || !policy.getRole()) throw BindingException("Artifact binding requires ArtifactResolver and MetadataProvider implementations be supplied."); // Import the artifact. SAMLArtifact* artifact=NULL; - ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); try { log.debug("processing encoded artifact (%s)", SAMLart); // Check replay. + ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); if (replayCache) { if (!replayCache->check("SAML2Artifact", SAMLart, time(NULL) + (2*XMLToolingConfig::getConfig().clock_skew_secs))) { log.error("replay detected of artifact (%s)", SAMLart); @@ -112,10 +112,8 @@ XMLObject* SAML2ArtifactDecoder::decode( delete artifact; } - issuer = NULL; - securityMech = NULL; log.debug("attempting to determine source of artifact..."); - const EntityDescriptor* provider=metadataProvider->getEntityDescriptor(artifact); + const EntityDescriptor* provider=policy.getMetadataProvider()->getEntityDescriptor(artifact); if (!provider) { log.error( "metadata lookup failed, unable to determine issuer of artifact (0x%s)", @@ -130,103 +128,31 @@ XMLObject* SAML2ArtifactDecoder::decode( } log.debug("attempting to find artifact issuing role..."); - issuer=provider->getRoleDescriptor(*role, samlconstants::SAML20P_NS); - if (!issuer || !dynamic_cast(issuer)) { - log.error("unable to find compatible SAML role (%s) in metadata", role->toString().c_str()); + const RoleDescriptor* roledesc=provider->getRoleDescriptor(*(policy.getRole()), samlconstants::SAML20P_NS); + if (!roledesc || !dynamic_cast(roledesc)) { + log.error("unable to find compatible SAML role (%s) in metadata", policy.getRole()->toString().c_str()); BindingException ex("Unable to find compatible metadata role for artifact issuer."); annotateException(&ex,provider); // throws it } try { auto_ptr response( - m_artifactResolver->resolve( - securityMech, - *(artifact2.get()), - dynamic_cast(*issuer), - dynamic_cast(trustEngine) - ) + m_artifactResolver->resolve(*(artifact2.get()), dynamic_cast(*roledesc), policy) ); - // Check Issuer of outer message. - if (!issuerMatches(response->getIssuer(), provider->getEntityID())) { - log.error("issuer of ArtifactResponse did not match source of artifact"); - throw BindingException("Issuer of ArtifactResponse did not match source of artifact."); - } + policy.evaluate(genericRequest, *(response.get())); - // Extract payload and check that Issuer. + // Extract payload and check that message. XMLObject* payload = response->getPayload(); - RequestAbstractType* req = NULL; - StatusResponseType* res = dynamic_cast(payload); - if (!res) - req = dynamic_cast(payload); - if (!res && !req) - throw BindingException("ArtifactResponse payload was not a recognized SAML 2.0 protocol message."); - - if (!issuerMatches(res ? res->getIssuer() : req->getIssuer(), provider->getEntityID())) { - log.error("issuer of ArtifactResponse payload did not match source of artifact"); - throw BindingException("Issuer of ArtifactResponse payload did not match source of artifact."); - } - - // Check payload freshness. - time_t now = time(NULL); - if ((res ? res->getIssueInstant() : req->getIssueInstant())->getEpoch() < now-(2*XMLToolingConfig::getConfig().clock_skew_secs)) - throw BindingException("Detected expired ArtifactResponse payload."); + policy.evaluate(genericRequest, *payload); - // Check replay. - if (replayCache) { - auto_ptr_char mid(res ? res->getID() : req->getID()); - if (!replayCache->check("SAML2ArtifactPayload", mid.get(), now + (2*XMLToolingConfig::getConfig().clock_skew_secs))) { - log.error("replay detected of ArtifactResponse payload message ID (%s)", mid.get()); - throw BindingException("Rejecting replayed ArtifactResponse payload ($1).", params(1,mid.get())); - } - } - - // Check signatures. - if (trustEngine) { - if (response->getSignature()) { - if (!trustEngine->validate(*(response->getSignature()), *issuer, metadataProvider->getKeyResolver())) { - log.error("unable to verify signature on ArtifactResponse message with supplied trust engine"); - throw BindingException("Message signature failed verification."); - } - else if (!securityMech) { - securityMech = samlconstants::SAML20P_NS; - } - } - Signature* sig = (res ? res->getSignature() : req->getSignature()); - if (sig) { - if (!trustEngine->validate(*sig, *issuer, metadataProvider->getKeyResolver())) { - log.error("unable to verify signature on ArtifactResponse payload with supplied trust engine"); - throw BindingException("Message signature failed verification."); - } - else if (!securityMech) { - securityMech = samlconstants::SAML20P_NS; - } - } - } - - if (!securityMech) { - log.warn("unable to authenticate ArtifactResponse message or payload, leaving untrusted"); - } - // Return the payload only. response.release(); payload->detach(); return payload; } catch (XMLToolingException& ex) { - annotateException(&ex,issuer,false); + annotateException(&ex,roledesc,false); throw; } } - -bool SAML2ArtifactDecoder::issuerMatches(const Issuer* messageIssuer, const XMLCh* expectedIssuer) const -{ - if (messageIssuer && messageIssuer->getName()) { - if (messageIssuer->getFormat() && !XMLString::equals(messageIssuer->getFormat(), NameIDType::ENTITY)) - return false; - else if (!XMLString::equals(expectedIssuer, messageIssuer->getName())) - return false; - } - return true; -} - diff --git a/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp b/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp index 048eeb7..2ce48a3 100644 --- a/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp +++ b/saml/saml2/binding/impl/SAML2ArtifactEncoder.cpp @@ -23,6 +23,7 @@ #include "internal.h" #include "exceptions.h" #include "binding/ArtifactMap.h" +#include "binding/HTTPResponse.h" #include "binding/URLEncoder.h" #include "saml2/binding/SAML2Artifact.h" #include "saml2/binding/SAML2ArtifactEncoder.h" @@ -64,7 +65,7 @@ SAML2ArtifactEncoder::SAML2ArtifactEncoder(const DOMElement* e) SAML2ArtifactEncoder::~SAML2ArtifactEncoder() {} long SAML2ArtifactEncoder::encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, xmltooling::XMLObject* xmlObject, const char* destination, const char* recipientID, @@ -77,8 +78,11 @@ long SAML2ArtifactEncoder::encode( xmltooling::NDC ndc("encode"); #endif Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML2Artifact"); + log.debug("validating input"); - + HTTPResponse* httpResponse=dynamic_cast(&genericResponse); + if (!httpResponse) + throw BindingException("Unable to cast response interface to HTTPResponse type."); if (relayState && strlen(relayState)>80) throw BindingException("RelayState cannot exceed 80 bytes in length."); @@ -135,7 +139,7 @@ long SAML2ArtifactEncoder::encode( if (relayState) loc = loc + "&RelayState=" + escaper->encode(relayState); log.debug("message encoded, sending redirect to client"); - return httpResponse.sendRedirect(loc.c_str()); + return httpResponse->sendRedirect(loc.c_str()); } else { // Push message into template and send result to client. @@ -153,6 +157,7 @@ long SAML2ArtifactEncoder::encode( params["RelayState"] = relayState; stringstream s; engine->run(infile, s, params); - return httpResponse.sendResponse(s); + httpResponse->setContentType("text/html"); + return httpResponse->sendResponse(s, HTTPResponse::SAML_HTTP_STATUS_OK); } } diff --git a/saml/saml2/binding/impl/SAML2POSTDecoder.cpp b/saml/saml2/binding/impl/SAML2POSTDecoder.cpp index 2258304..a7f1cda 100644 --- a/saml/saml2/binding/impl/SAML2POSTDecoder.cpp +++ b/saml/saml2/binding/impl/SAML2POSTDecoder.cpp @@ -22,23 +22,21 @@ #include "internal.h" #include "exceptions.h" +#include "binding/HTTPRequest.h" #include "saml2/binding/SAML2POSTDecoder.h" #include "saml2/core/Protocols.h" #include "saml2/metadata/Metadata.h" #include "saml2/metadata/MetadataProvider.h" -#include "security/X509TrustEngine.h" #include #include #include -#include #include using namespace opensaml::saml2md; using namespace opensaml::saml2p; using namespace opensaml::saml2; using namespace opensaml; -using namespace xmlsignature; using namespace xmltooling; using namespace log4cpp; using namespace std; @@ -56,14 +54,10 @@ SAML2POSTDecoder::SAML2POSTDecoder(const DOMElement* e) {} SAML2POSTDecoder::~SAML2POSTDecoder() {} -XMLObject* SAML2POSTDecoder::decode( - string& relayState, - const RoleDescriptor*& issuer, - const XMLCh*& securityMech, - const HTTPRequest& httpRequest, - const MetadataProvider* metadataProvider, - const QName* role, - const opensaml::TrustEngine* trustEngine +saml2::RootObject* SAML2POSTDecoder::decode( + std::string& relayState, + const GenericRequest& genericRequest, + SecurityPolicy& policy ) const { #ifdef _DEBUG @@ -72,14 +66,19 @@ XMLObject* SAML2POSTDecoder::decode( Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2POST"); log.debug("validating input"); - if (strcmp(httpRequest.getMethod(),"POST")) + const HTTPRequest* httpRequest=dynamic_cast(&genericRequest); + if (!httpRequest) { + log.error("unable to cast request to HTTPRequest type"); return NULL; - const char* msg = httpRequest.getParameter("SAMLResponse"); + } + if (strcmp(httpRequest->getMethod(),"POST")) + return NULL; + const char* msg = httpRequest->getParameter("SAMLResponse"); if (!msg) - msg = httpRequest.getParameter("SAMLRequest"); + msg = httpRequest->getParameter("SAMLRequest"); if (!msg) return NULL; - const char* state = httpRequest.getParameter("RelayState"); + const char* state = httpRequest->getParameter("RelayState"); if (state) relayState = state; else @@ -101,40 +100,27 @@ XMLObject* SAML2POSTDecoder::decode( auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true)); janitor.release(); + saml2::RootObject* root = NULL; StatusResponseType* response = NULL; RequestAbstractType* request = dynamic_cast(xmlObject.get()); if (!request) { response = dynamic_cast(xmlObject.get()); if (!response) throw BindingException("XML content for SAML 2.0 HTTP-POST Decoder must be a SAML 2.0 protocol message."); + root = static_cast(response); } - - /* For SAML 2, the issuer can be established either from the message, or in some profiles - * it's possible to omit it and defer to assertions in a Response. - * The Issuer is later matched against metadata, and then trust checking can be applied. - */ - const Issuer* claimedIssuer = request ? request->getIssuer() : response->getIssuer(); - if (!claimedIssuer) { - // Check assertion option. I cannot resist the variable name, for the sake of google. - const Response* assbag = dynamic_cast(response); - if (assbag) { - const vector& assertions=assbag->getAssertions(); - if (!assertions.empty()) - claimedIssuer = assertions.front()->getIssuer(); - } + else { + root = static_cast(request); } - - const EntityDescriptor* provider=NULL; + try { if (!m_validate) SchemaValidators.validate(xmlObject.get()); - Signature* signature = request ? request->getSignature() : response->getSignature(); - // Check destination URL. auto_ptr_char dest(request ? request->getDestination() : response->getDestination()); - const char* dest2 = httpRequest.getRequestURL(); - if (signature && !dest.get() || !*(dest.get())) { + const char* dest2 = httpRequest->getRequestURL(); + if (root->getSignature() && !dest.get() || !*(dest.get())) { log.error("signed SAML message missing Destination attribute"); throw BindingException("Signed SAML message missing Destination attribute identifying intended destination."); } @@ -143,88 +129,44 @@ XMLObject* SAML2POSTDecoder::decode( throw BindingException("SAML message delivered with POST to incorrect server URL."); } - // Check freshness. - time_t now = time(NULL); - if ((request ? request->getIssueInstant()->getEpoch() : response->getIssueInstant()->getEpoch()) - < now-(2*XMLToolingConfig::getConfig().clock_skew_secs)) - throw BindingException("Detected expired POST binding message."); - - // Check replay. - ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); - if (replayCache) { - auto_ptr_char id(xmlObject->getXMLID()); - if (!replayCache->check("SAML2POST", id.get(), response->getIssueInstant()->getEpoch() + (2*XMLToolingConfig::getConfig().clock_skew_secs))) { - log.error("replay detected of response ID (%s)", id.get()); - throw BindingException("Rejecting replayed response ID ($1).", params(1,id.get())); - } - } - else - log.warn("replay cache was not provided, this is a serious security risk!"); - - issuer = NULL; - securityMech = false; - log.debug("attempting to establish issuer and integrity of message..."); - - // If we can't identify the issuer, we're done, since we can't lookup or verify anything. - if (!claimedIssuer || !claimedIssuer->getName()) { - log.warn("unable to establish identity of message issuer"); - return xmlObject.release(); - } - else if (claimedIssuer->getFormat() && !XMLString::equals(claimedIssuer->getFormat(), NameIDType::ENTITY)) { - auto_ptr_char iformat(claimedIssuer->getFormat()); - log.warn("message issuer was in an unsupported format (%s)", iformat.get()); - return xmlObject.release(); - } - - log.debug("searching metadata for assertion issuer..."); - provider=metadataProvider ? metadataProvider->getEntityDescriptor(claimedIssuer->getName()) : NULL; - if (provider) { - log.debug("matched assertion issuer against metadata, searching for applicable role..."); - issuer=provider->getRoleDescriptor(*role, samlconstants::SAML20P_NS); - if (issuer) { - if (trustEngine && signature) { - if (!trustEngine->validate(*signature, *issuer, metadataProvider->getKeyResolver())) { - log.error("unable to verify signature on message with supplied trust engine"); - throw BindingException("Message signature failed verification."); - } - else { - securityMech = samlconstants::SAML20P_NS; - } - } - else { - log.warn("unable to authenticate the message, leaving untrusted"); - } - } - else { - log.warn("unable to find compatible SAML 2.0 role (%s) in metadata", role->toString().c_str()); - } - if (log.isDebugEnabled()) { - auto_ptr_char iname(provider->getEntityID()); - log.debug("message from (%s), integrity %sverified", iname.get(), securityMech ? "" : "NOT "); - } - } - else { - auto_ptr_char temp(claimedIssuer->getName()); - log.warn("no metadata found, can't establish identity of issuer (%s)", temp.get()); - } + // Run through the policy. + policy.evaluate(genericRequest, *response); } catch (XMLToolingException& ex) { - if (!provider) { - if (!claimedIssuer || !claimedIssuer->getName()) - throw; - if (!metadataProvider || !(provider=metadataProvider->getEntityDescriptor(claimedIssuer->getName(), false))) { - // Just record it. - auto_ptr_char iname(claimedIssuer->getName()); - if (iname.get()) - ex.addProperty("entityID", iname.get()); - throw; + // This is just to maximize the likelihood of attaching a source to the message for support purposes. + if (policy.getIssuerMetadata()) + annotateException(&ex,policy.getIssuerMetadata()); // throws it + + const Issuer* claimedIssuer = root->getIssuer(); + if (!claimedIssuer) { + // Check for assertions. + const Response* assbag = dynamic_cast(response); + if (assbag) { + const vector& assertions=assbag->getAssertions(); + if (!assertions.empty()) + claimedIssuer = assertions.front()->getIssuer(); } } - if (!issuer) - issuer=provider->getRoleDescriptor(*role, samlconstants::SAML20P_NS); - if (issuer) annotateException(&ex,issuer); // throws it + + if (!claimedIssuer || !claimedIssuer->getName()) + throw; + const EntityDescriptor* provider=NULL; + if (!policy.getMetadataProvider() || + !(provider=policy.getMetadataProvider()->getEntityDescriptor(claimedIssuer->getName(), false))) { + // Just record it. + auto_ptr_char iname(claimedIssuer->getName()); + if (iname.get()) + ex.addProperty("entityID", iname.get()); + throw; + } + + if (policy.getRole()) { + const RoleDescriptor* roledesc=provider->getRoleDescriptor(*(policy.getRole()), samlconstants::SAML20P_NS); + if (roledesc) annotateException(&ex,roledesc); // throws it + } annotateException(&ex,provider); // throws it } - return xmlObject.release(); + xmlObject.release(); + return root; } diff --git a/saml/saml2/binding/impl/SAML2POSTEncoder.cpp b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp index 9a4e7df..ec73691 100644 --- a/saml/saml2/binding/impl/SAML2POSTEncoder.cpp +++ b/saml/saml2/binding/impl/SAML2POSTEncoder.cpp @@ -22,6 +22,7 @@ #include "internal.h" #include "exceptions.h" +#include "binding/HTTPResponse.h" #include "saml2/binding/SAML2POSTEncoder.h" #include "saml2/core/Protocols.h" @@ -64,7 +65,7 @@ SAML2POSTEncoder::SAML2POSTEncoder(const DOMElement* e) SAML2POSTEncoder::~SAML2POSTEncoder() {} long SAML2POSTEncoder::encode( - HTTPResponse& httpResponse, + GenericResponse& genericResponse, XMLObject* xmlObject, const char* destination, const char* recipientID, @@ -77,8 +78,11 @@ long SAML2POSTEncoder::encode( 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."); @@ -143,7 +147,8 @@ long SAML2POSTEncoder::encode( params["RelayState"] = relayState; stringstream s; engine->run(infile, s, params); - long ret = httpResponse.sendResponse(s); + httpResponse->setContentType("text/html"); + long ret = httpResponse->sendResponse(s, HTTPResponse::SAML_HTTP_STATUS_OK); // Cleanup by destroying XML. delete xmlObject; diff --git a/saml/saml2/binding/impl/SAML2Redirect.cpp b/saml/saml2/binding/impl/SAML2Redirect.cpp new file mode 100644 index 0000000..b90a89b --- /dev/null +++ b/saml/saml2/binding/impl/SAML2Redirect.cpp @@ -0,0 +1,105 @@ +/* + * 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/SAML2Redirect.h + * + * SAML 2.0 HTTP Redirect compression functionality + */ + +#include "internal.h" +#include "saml2/binding/SAML2Redirect.h" +#include "zlib/zlib.h" + +#include +#include + +using namespace log4cpp; +using namespace std; + +namespace { + extern "C" { + voidpf saml_zalloc(void* opaque, uInt items, uInt size) + { + return malloc(items*size); + } + + void saml_zfree(void* opaque, voidpf addr) + { + free(addr); + } + }; +}; + +unsigned int opensaml::saml2p::inflate(char* in, unsigned int in_len, ostream& out) +{ +#ifdef _DEBUG + xmltooling::NDC ndc("inflate"); +#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; + + int dlen = in_len << 3; /* guess inflated size: orig_size * 8 */ + Byte* buf = new Byte[dlen]; + memset(buf, 0, dlen); + z.next_out = buf; + z.avail_out = dlen; + + int ret = inflateInit2(&z, -15); + if (ret != Z_OK) { + log.error("zlib inflateInit failed with error code (%d)", ret); + delete[] buf; + return 0; + } + + int iter = 30; + while (--iter) { /* Make sure we can never be caught in infinite loop */ + ret = inflate(&z, Z_SYNC_FLUSH); + switch (ret) { + case Z_STREAM_END: + goto done; + + case Z_OK: /* avail_out should be 0 now. Time to dump the buffer. */ + ret = z.next_out - buf; + z.next_out = buf; + while (ret--) + out << *(z.next_out++); + memset(buf, 0, dlen); + z.next_out = buf; + z.avail_out = dlen; + break; + + default: + delete[] buf; + inflateEnd(&z); + log.error("zlib inflate failed with error code (%d)", ret); + return 0; + } + } +done: + delete[] buf; + int out_len = z.total_out; + inflateEnd(&z); + return out_len; +} diff --git a/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp b/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp new file mode 100644 index 0000000..6afb59a --- /dev/null +++ b/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp @@ -0,0 +1,242 @@ +/* + * 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. + */ + +/** + * SAML2RedirectDecoder.cpp + * + * SAML 2.0 HTTP Redirect binding message encoder + */ + +#include "internal.h" +#include "exceptions.h" +#include "saml2/binding/SAML2Redirect.h" +#include "saml2/binding/SAML2RedirectDecoder.h" +#include "saml2/core/Protocols.h" +#include "saml2/metadata/Metadata.h" +#include "saml2/metadata/MetadataProvider.h" +#include "security/X509TrustEngine.h" + +#include +#include +#include +#include +#include + +using namespace opensaml::saml2md; +using namespace opensaml::saml2p; +using namespace opensaml::saml2; +using namespace opensaml; +using namespace xmlsignature; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +namespace opensaml { + namespace saml2p { + MessageDecoder* SAML_DLLLOCAL SAML2RedirectDecoderFactory(const DOMElement* const & e) + { + return new SAML2RedirectDecoder(e); + } + }; +}; + +SAML2RedirectDecoder::SAML2RedirectDecoder(const DOMElement* e) {} + +SAML2RedirectDecoder::~SAML2RedirectDecoder() {} + +XMLObject* SAML2RedirectDecoder::decode( + string& relayState, + const RoleDescriptor*& issuer, + const XMLCh*& securityMech, + const HTTPRequest& httpRequest, + const MetadataProvider* metadataProvider, + const QName* role, + const opensaml::TrustEngine* trustEngine + ) const +{ +#ifdef _DEBUG + xmltooling::NDC ndc("decode"); +#endif + Category& log = Category::getInstance(SAML_LOGCAT".MessageDecoder.SAML2Redirect"); + + log.debug("validating input"); + if (strcmp(httpRequest.getMethod(),"GET")) + return NULL; + const char* msg = httpRequest.getParameter("SAMLResponse"); + if (!msg) + msg = httpRequest.getParameter("SAMLRequest"); + if (!msg) + return NULL; + const char* state = httpRequest.getParameter("RelayState"); + if (state) + relayState = state; + else + relayState.erase(); + state = httpRequest.getParameter("SAMLEncoding"); + if (state && strcmp(state,samlconstants::SAML20_BINDING_URL_ENCODING_DEFLATE)) { + log.warn("SAMLEncoding (%s) was not recognized", state); + return NULL; + } + + // Decode the compressed message into SAML. First we base64-decode it. + unsigned int x; + XMLByte* decoded=Base64::decode(reinterpret_cast(msg),&x); + if (!decoded) + throw BindingException("Unable to decode base64 in Redirect binding message."); + + // Now we have to inflate it. + stringstream str; + if (inflate((char*)decoded, x, str)==0) { + XMLString::release(&decoded); + throw BindingException("Unable to inflate Redirect binding message."); + } + + XMLString::release(&decoded); + + // Parse and bind the document into an XMLObject. + DOMDocument* doc = (m_validate ? XMLToolingConfig::getConfig().getValidatingParser() + : XMLToolingConfig::getConfig().getParser()).parse(str); + XercesJanitor janitor(doc); + auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true)); + janitor.release(); + + StatusResponseType* response = NULL; + RequestAbstractType* request = dynamic_cast(xmlObject.get()); + if (!request) { + response = dynamic_cast(xmlObject.get()); + if (!response) + throw BindingException("XML content for SAML 2.0 HTTP-Redirect Decoder must be a SAML 2.0 protocol message."); + } + + /* For SAML 2, the issuer can be established either from the message, or in some profiles + * it's possible to omit it and defer to assertions in a Response. + * The Issuer is later matched against metadata, and then trust checking can be applied. + */ + const Issuer* claimedIssuer = request ? request->getIssuer() : response->getIssuer(); + if (!claimedIssuer) { + // Check assertion option. I cannot resist the variable name, for the sake of google. + const Response* assbag = dynamic_cast(response); + if (assbag) { + const vector& assertions=assbag->getAssertions(); + if (!assertions.empty()) + claimedIssuer = assertions.front()->getIssuer(); + } + } + + const EntityDescriptor* provider=NULL; + try { + if (!m_validate) + SchemaValidators.validate(xmlObject.get()); + + // Check destination URL. + auto_ptr_char dest(request ? request->getDestination() : response->getDestination()); + const char* dest2 = httpRequest.getRequestURL(); + if (!dest.get() || !*(dest.get())) { + log.error("signed SAML message missing Destination attribute"); + throw BindingException("Signed SAML message missing Destination attribute identifying intended destination."); + } + else if (dest.get() && (!dest2 || !*dest2 || strcmp(dest.get(),dest2))) { + log.error("Redirect targeted at (%s), but delivered to (%s)", dest.get(), dest2 ? dest2 : "none"); + throw BindingException("SAML message delivered with Redirect to incorrect server URL."); + } + + // Check freshness. + time_t now = time(NULL); + if ((request ? request->getIssueInstant()->getEpoch() : response->getIssueInstant()->getEpoch()) + < now-(2*XMLToolingConfig::getConfig().clock_skew_secs)) + throw BindingException("Detected expired Redirect binding message."); + + // Check replay. + ReplayCache* replayCache = XMLToolingConfig::getConfig().getReplayCache(); + if (replayCache) { + auto_ptr_char id(xmlObject->getXMLID()); + if (!replayCache->check("SAML2Redirect", id.get(), response->getIssueInstant()->getEpoch() + (2*XMLToolingConfig::getConfig().clock_skew_secs))) { + log.error("replay detected of message ID (%s)", id.get()); + throw BindingException("Rejecting replayed message ID ($1).", params(1,id.get())); + } + } + else + log.warn("replay cache was not provided, this is a serious security risk!"); + + issuer = NULL; + securityMech = false; + log.debug("attempting to establish issuer and integrity of message..."); + + // If we can't identify the issuer, we're done, since we can't lookup or verify anything. + if (!claimedIssuer || !claimedIssuer->getName()) { + log.warn("unable to establish identity of message issuer"); + return xmlObject.release(); + } + else if (claimedIssuer->getFormat() && !XMLString::equals(claimedIssuer->getFormat(), NameIDType::ENTITY)) { + auto_ptr_char iformat(claimedIssuer->getFormat()); + log.warn("message issuer was in an unsupported format (%s)", iformat.get()); + return xmlObject.release(); + } + + log.debug("searching metadata for assertion issuer..."); + provider=metadataProvider ? metadataProvider->getEntityDescriptor(claimedIssuer->getName()) : NULL; + if (provider) { + log.debug("matched assertion issuer against metadata, searching for applicable role..."); + issuer=provider->getRoleDescriptor(*role, samlconstants::SAML20P_NS); + if (issuer) { + /* + if (trustEngine && signature) { + if (!trustEngine->validate(*signature, *issuer, metadataProvider->getKeyResolver())) { + log.error("unable to verify signature on message with supplied trust engine"); + throw BindingException("Message signature failed verification."); + } + else { + securityMech = samlconstants::SAML20P_NS; + } + } + else { + log.warn("unable to authenticate the message, leaving untrusted"); + } + */ + } + else { + log.warn("unable to find compatible SAML 2.0 role (%s) in metadata", role->toString().c_str()); + } + if (log.isDebugEnabled()) { + auto_ptr_char iname(provider->getEntityID()); + log.debug("message from (%s), integrity %sverified", iname.get(), securityMech ? "" : "NOT "); + } + } + else { + auto_ptr_char temp(claimedIssuer->getName()); + log.warn("no metadata found, can't establish identity of issuer (%s)", temp.get()); + } + } + catch (XMLToolingException& ex) { + if (!provider) { + if (!claimedIssuer || !claimedIssuer->getName()) + throw; + if (!metadataProvider || !(provider=metadataProvider->getEntityDescriptor(claimedIssuer->getName(), false))) { + // Just record it. + auto_ptr_char iname(claimedIssuer->getName()); + if (iname.get()) + ex.addProperty("entityID", iname.get()); + throw; + } + } + if (!issuer) + issuer=provider->getRoleDescriptor(*role, samlconstants::SAML20P_NS); + if (issuer) annotateException(&ex,issuer); // throws it + annotateException(&ex,provider); // throws it + } + + return xmlObject.release(); +} diff --git a/saml/saml2/core/Assertions.h b/saml/saml2/core/Assertions.h index e4cd74a..ae5018d 100644 --- a/saml/saml2/core/Assertions.h +++ b/saml/saml2/core/Assertions.h @@ -23,7 +23,7 @@ #ifndef __saml2_assertions_h__ #define __saml2_assertions_h__ -#include +#include #include #include @@ -79,7 +79,7 @@ namespace opensaml { BEGIN_XMLOBJECT(SAML_API,EncryptedID,EncryptedElementType,SAML 2.0 EncryptedID element); END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,BaseID,xmltooling::XMLObject,SAML 2.0 BaseIDAbstractType abstract type); + BEGIN_XMLOBJECT(SAML_API,BaseID,xmltooling::XMLObject,SAML 2.0 BaseID abstract element); DECL_STRING_ATTRIB(NameQualifier,NAMEQUALIFIER); DECL_STRING_ATTRIB(SPNameQualifier,SPNAMEQUALIFIER); END_XMLOBJECT; @@ -306,12 +306,29 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,Assertion,SignableObject,SAML 2.0 Assertion element); - DECL_STRING_ATTRIB(Version,VER); - DECL_STRING_ATTRIB(ID,ID); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); - DECL_TYPED_CHILD(Issuer); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + /** + * SAML 2.0 assertion or protocol message. + */ + class SAML_API RootObject : virtual public opensaml::RootObject + { + protected: + RootObject() {} + public: + virtual ~RootObject() {} + + /** Gets the Version attribute. */ + virtual const XMLCh* getVersion() const=0; + + /** Gets the Issuer. */ + virtual Issuer* getIssuer() const=0; + }; + + BEGIN_XMLOBJECT(SAML_API,Assertion,saml2::RootObject,SAML 2.0 Assertion element); + DECL_INHERITED_STRING_ATTRIB(Version,VER); + DECL_INHERITED_STRING_ATTRIB(ID,ID); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + DECL_INHERITED_TYPED_CHILD(Issuer); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Subject); DECL_TYPED_CHILD(Conditions); DECL_TYPED_CHILD(Advice); diff --git a/saml/saml2/core/Protocols.h b/saml/saml2/core/Protocols.h index 440032f..c93c758 100644 --- a/saml/saml2/core/Protocols.h +++ b/saml/saml2/core/Protocols.h @@ -50,14 +50,14 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,RequestAbstractType,SignableObject,SAML 2.0 RequestAbstractType base type); - DECL_STRING_ATTRIB(ID,ID); - DECL_STRING_ATTRIB(Version,VER); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + BEGIN_XMLOBJECT(SAML_API,RequestAbstractType,saml2::RootObject,SAML 2.0 RequestAbstractType base type); + DECL_INHERITED_STRING_ATTRIB(ID,ID); + DECL_INHERITED_STRING_ATTRIB(Version,VER); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); DECL_STRING_ATTRIB(Destination,DESTINATION); DECL_STRING_ATTRIB(Consent,CONSENT); - DECL_TYPED_FOREIGN_CHILD(Issuer,saml2); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Issuer,saml2); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); /** RequestAbstractType local name */ static const XMLCh TYPE_NAME[]; @@ -140,19 +140,17 @@ namespace opensaml { static const XMLCh TYPE_NAME[]; END_XMLOBJECT; - BEGIN_XMLOBJECT(SAML_API,StatusResponseType,SignableObject,SAML 2.0 StatusResponseType base type); - DECL_STRING_ATTRIB(ID,ID); + BEGIN_XMLOBJECT(SAML_API,StatusResponseType,saml2::RootObject,SAML 2.0 StatusResponseType base type); + DECL_INHERITED_STRING_ATTRIB(ID,ID); DECL_STRING_ATTRIB(InResponseTo,INRESPONSETO); - DECL_STRING_ATTRIB(Version,VER); - DECL_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); + DECL_INHERITED_STRING_ATTRIB(Version,VER); + DECL_INHERITED_DATETIME_ATTRIB(IssueInstant,ISSUEINSTANT); DECL_STRING_ATTRIB(Destination,DESTINATION); DECL_STRING_ATTRIB(Consent,CONSENT); - - DECL_TYPED_FOREIGN_CHILD(Issuer,saml2); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Issuer,saml2); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); DECL_TYPED_CHILD(Status); - /** StatusResponseType local name */ static const XMLCh TYPE_NAME[]; END_XMLOBJECT; @@ -174,7 +172,6 @@ namespace opensaml { DECL_STRING_ATTRIB(Comparison,COMPARISON); DECL_TYPED_FOREIGN_CHILDREN(AuthnContextClassRef,saml2); DECL_TYPED_FOREIGN_CHILDREN(AuthnContextDeclRef,saml2); - /** RequestedAuthnContextType local name */ static const XMLCh TYPE_NAME[]; diff --git a/saml/saml2/metadata/Metadata.h b/saml/saml2/metadata/Metadata.h index ee6863b..457afc2 100644 --- a/saml/saml2/metadata/Metadata.h +++ b/saml/saml2/metadata/Metadata.h @@ -163,7 +163,7 @@ namespace opensaml { /** Searches the ProtocolSupportEnumeration attribute for the indicated protocol. */ virtual bool hasSupport(const XMLCh* protocol) const=0; DECL_STRING_ATTRIB(ErrorURL,ERRORURL); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); DECL_TYPED_CHILDREN(KeyDescriptor); DECL_TYPED_CHILD(Organization); @@ -322,7 +322,7 @@ namespace opensaml { CacheableSAMLObject,TimeBoundSAMLObject,SAML 2.0 AffiliationDescriptor element); DECL_STRING_ATTRIB(ID,ID); DECL_STRING_ATTRIB(AffiliationOwnerID,AFFILIATIONOWNERID); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); DECL_TYPED_CHILDREN(AffiliateMember); DECL_TYPED_CHILDREN(KeyDescriptor); @@ -334,7 +334,7 @@ namespace opensaml { CacheableSAMLObject,TimeBoundSAMLObject,SAML 2.0 EntityDescriptor element); DECL_STRING_ATTRIB(ID,ID); DECL_STRING_ATTRIB(EntityID,ENTITYID); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); DECL_TYPED_CHILD(AffiliationDescriptor); DECL_TYPED_CHILDREN(RoleDescriptor); @@ -372,7 +372,7 @@ namespace opensaml { TimeBoundSAMLObject,SAML 2.0 EntitiesDescriptor element); DECL_STRING_ATTRIB(ID,ID); DECL_STRING_ATTRIB(Name,NAME); - DECL_TYPED_FOREIGN_CHILD(Signature,xmlsignature); + DECL_INHERITED_TYPED_FOREIGN_CHILD(Signature,xmlsignature); DECL_TYPED_CHILD(Extensions); DECL_TYPED_CHILDREN(EntityDescriptor); DECL_TYPED_CHILDREN(EntitiesDescriptor); diff --git a/saml/signature/SignableObject.h b/saml/signature/SignableObject.h index a1d6521..1e0119b 100644 --- a/saml/signature/SignableObject.h +++ b/saml/signature/SignableObject.h @@ -48,6 +48,13 @@ namespace opensaml { return new ContentReference(*this); } + /** + * Returns the enveloped Signature from the object. + * + * @return the enveloped Signature, or NULL + */ + virtual xmlsignature::Signature* getSignature() const=0; + protected: SignableObject() {} }; diff --git a/saml/util/SAMLConstants.cpp b/saml/util/SAMLConstants.cpp index 7c790d9..f5a4db4 100644 --- a/saml/util/SAMLConstants.cpp +++ b/saml/util/SAMLConstants.cpp @@ -186,3 +186,5 @@ 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_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 17eade2..1fc82ff 100644 --- a/saml/util/SAMLConstants.h +++ b/saml/util/SAMLConstants.h @@ -137,6 +137,9 @@ namespace samlconstants { /** 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[]; + + /** SAML 2.0 HTTP-Redirect DEFLATE URL encoding ("urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE") */ + extern SAML_API const char SAML20_BINDING_URL_ENCODING_DEFLATE[]; }; #endif /* __saml_xmlconstants_h__ */ diff --git a/samltest/binding.h b/samltest/binding.h index 15aa249..fdd0ce7 100644 --- a/samltest/binding.h +++ b/samltest/binding.h @@ -1,220 +1,254 @@ -/* - * 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. - */ - -#include "internal.h" - -#include -#include -#include -#include -#include -#include - -using namespace saml2md; -using namespace xmlsignature; - -class SAMLBindingBaseTestCase : public MessageDecoder::HTTPRequest, public MessageEncoder::HTTPResponse -{ -protected: - CredentialResolver* m_creds; - MetadataProvider* m_metadata; - opensaml::X509TrustEngine* m_trust; - map m_fields; - map m_headers; - string m_method,m_url; - -public: - void setUp() { - m_creds=NULL; - m_metadata=NULL; - m_trust=NULL; - m_fields.clear(); - m_headers.clear(); - m_method.erase(); - m_url.erase(); - - try { - string config = data_path + "binding/ExampleMetadataProvider.xml"; - ifstream in(config.c_str()); - DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); - XercesJanitor janitor(doc); - - auto_ptr_XMLCh path("path"); - string s = data_path + "binding/example-metadata.xml"; - auto_ptr_XMLCh file(s.c_str()); - doc->getDocumentElement()->setAttributeNS(NULL,path.get(),file.get()); - - m_metadata = SAMLConfig::getConfig().MetadataProviderManager.newPlugin( - FILESYSTEM_METADATA_PROVIDER,doc->getDocumentElement() - ); - m_metadata->init(); - - config = data_path + "FilesystemCredentialResolver.xml"; - ifstream in2(config.c_str()); - DOMDocument* doc2=XMLToolingConfig::getConfig().getParser().parse(in2); - XercesJanitor janitor2(doc2); - m_creds = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin( - FILESYSTEM_CREDENTIAL_RESOLVER,doc2->getDocumentElement() - ); - - m_trust = dynamic_cast( - SAMLConfig::getConfig().TrustEngineManager.newPlugin(EXPLICIT_KEY_SAMLTRUSTENGINE, NULL) - ); - } - catch (XMLToolingException& ex) { - TS_TRACE(ex.what()); - tearDown(); - throw; - } - - } - - void tearDown() { - delete m_creds; - delete m_metadata; - delete m_trust; - m_creds=NULL; - m_metadata=NULL; - m_trust=NULL; - m_fields.clear(); - m_headers.clear(); - m_method.erase(); - m_url.erase(); - } - - // HTTPRequest methods - - const char* getMethod() const { - return m_method.c_str(); - } - - const char* getRequestURL() const { - return m_url.c_str(); - } - - const char* getRequestBody() const { - return NULL; - } - - const char* getQueryString() const { - return NULL; - } - - string getRemoteUser() const { - return ""; - } - - string getHeader(const char* name) const { - map::const_iterator i=m_headers.find(name); - return i==m_headers.end() ? "" : i->second; - } - - const char* getParameter(const char* name) const { - map::const_iterator i=m_fields.find(name); - return i==m_fields.end() ? NULL : i->second.c_str(); - } - - vector::size_type getParameters(const char* name, vector& values) const { - values.clear(); - map::const_iterator i=m_fields.find(name); - if (i!=m_fields.end()) - values.push_back(i->second.c_str()); - return values.size(); - } - - // HTTPResponse methods - - void setHeader(const char* name, const char* value) { - m_headers[name] = value ? value : ""; - } - - void setCookie(const char* name, const char* value) { - m_headers["Set-Cookie"] = string(name) + "=" + (value ? value : ""); - } - - // The amount of error checking missing from this is incredible, but as long - // as the test data isn't unexpected or malformed, it should work. - - long sendRedirect(const char* url) { - m_method = "GET"; - char* dup = strdup(url); - char* pch = strchr(dup,'?'); - if (pch) { - *pch++=0; - char* name=pch; - while (name && *name) { - pch=strchr(pch,'='); - *pch++=0; - char* value=pch; - pch=strchr(pch,'&'); - if (pch) - *pch++=0; - SAMLConfig::getConfig().getURLEncoder()->decode(value); - m_fields[name] = value; - name = pch; - } - } - m_url = dup; - free(dup); - return m_fields.size(); - } - - string html_decode(const string& s) const { - string decoded; - const char* ch=s.c_str(); - while (*ch) { - if (*ch=='&') { - if (!strncmp(ch,"<",4)) { - decoded+='<'; ch+=4; - } - else if (!strncmp(ch,">",4)) { - decoded+='>'; ch+=4; - } - else if (!strncmp(ch,""",6)) { - decoded+='"'; ch+=6; - } - else if (*++ch=='#') { - decoded+=(char)atoi(++ch); - ch=strchr(ch,';')+1; - } - } - else { - decoded+=*ch++; - } - } - return decoded; - } - - long sendResponse(std::istream& inputStream, int status = 200, const char* contentType = "text/html") { - m_method="POST"; - string page,line; - while (getline(inputStream,line)) - page += line + '\n'; - - const char* pch=strstr(page.c_str(),"action=\""); - pch+=strlen("action=\""); - m_url = html_decode(page.substr(pch-page.c_str(),strchr(pch,'"')-pch)); - - while (pch=strstr(pch," +#include +#include +#include +#include +#include +#include +#include + +using namespace saml2md; +using namespace xmlsignature; + +class SAMLBindingBaseTestCase : public HTTPRequest, public HTTPResponse +{ +protected: + CredentialResolver* m_creds; + MetadataProvider* m_metadata; + opensaml::TrustEngine* m_trust; + map m_fields; + map m_headers; + string m_method,m_url; + vector m_clientCerts; + vector m_rules; + +public: + void setUp() { + m_creds=NULL; + m_metadata=NULL; + m_trust=NULL; + m_fields.clear(); + m_headers.clear(); + m_method.erase(); + m_url.erase(); + + try { + string config = data_path + "binding/ExampleMetadataProvider.xml"; + ifstream in(config.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + + auto_ptr_XMLCh path("path"); + string s = data_path + "binding/example-metadata.xml"; + auto_ptr_XMLCh file(s.c_str()); + doc->getDocumentElement()->setAttributeNS(NULL,path.get(),file.get()); + + m_metadata = SAMLConfig::getConfig().MetadataProviderManager.newPlugin( + FILESYSTEM_METADATA_PROVIDER,doc->getDocumentElement() + ); + m_metadata->init(); + + config = data_path + "FilesystemCredentialResolver.xml"; + ifstream in2(config.c_str()); + DOMDocument* doc2=XMLToolingConfig::getConfig().getParser().parse(in2); + XercesJanitor janitor2(doc2); + m_creds = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin( + FILESYSTEM_CREDENTIAL_RESOLVER,doc2->getDocumentElement() + ); + + 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(MESSAGESIGNING_POLICY_RULE,NULL)); + } + catch (XMLToolingException& ex) { + TS_TRACE(ex.what()); + tearDown(); + throw; + } + + } + + void tearDown() { + for_each(m_rules.begin(), m_rules.end(), xmltooling::cleanup()); + delete m_creds; + delete m_metadata; + delete m_trust; + m_creds=NULL; + m_metadata=NULL; + m_trust=NULL; + m_fields.clear(); + m_headers.clear(); + m_method.erase(); + m_url.erase(); + } + + // HTTPRequest methods + + const char* getMethod() const { + return m_method.c_str(); + } + + const char* getScheme() const { + return "https"; + } + + bool isSecure() const { + return true; + } + + string getContentType() const { + return "application/x-www-form-urlencoded"; + } + + long getContentLength() const { + return -1; + } + + const char* getRequestURL() const { + return m_url.c_str(); + } + + const char* getRequestBody() const { + return NULL; + } + + const char* getQueryString() const { + return NULL; + } + + string getRemoteUser() const { + return ""; + } + + string getRemoteAddr() const { + return "127.0.0.1"; + } + + const std::vector& getClientCertificates() const { + return m_clientCerts; + } + + string getHeader(const char* name) const { + map::const_iterator i=m_headers.find(name); + return i==m_headers.end() ? "" : i->second; + } + + const char* getParameter(const char* name) const { + map::const_iterator i=m_fields.find(name); + return i==m_fields.end() ? NULL : i->second.c_str(); + } + + vector::size_type getParameters(const char* name, vector& values) const { + values.clear(); + map::const_iterator i=m_fields.find(name); + if (i!=m_fields.end()) + values.push_back(i->second.c_str()); + return values.size(); + } + + // HTTPResponse methods + + void setHeader(const char* name, const char* value) { + m_headers[name] = value ? value : ""; + } + + void setContentType(const char* type) { + setHeader("Content-Type", type); + } + + void setCookie(const char* name, const char* value) { + m_headers["Set-Cookie"] = string(name) + "=" + (value ? value : ""); + } + + // The amount of error checking missing from this is incredible, but as long + // as the test data isn't unexpected or malformed, it should work. + + long sendRedirect(const char* url) { + m_method = "GET"; + char* dup = strdup(url); + char* pch = strchr(dup,'?'); + if (pch) { + *pch++=0; + char* name=pch; + while (name && *name) { + pch=strchr(pch,'='); + *pch++=0; + char* value=pch; + pch=strchr(pch,'&'); + if (pch) + *pch++=0; + SAMLConfig::getConfig().getURLEncoder()->decode(value); + m_fields[name] = value; + name = pch; + } + } + m_url = dup; + free(dup); + return m_fields.size(); + } + + string html_decode(const string& s) const { + string decoded; + const char* ch=s.c_str(); + while (*ch) { + if (*ch=='&') { + if (!strncmp(ch,"<",4)) { + decoded+='<'; ch+=4; + } + else if (!strncmp(ch,">",4)) { + decoded+='>'; ch+=4; + } + else if (!strncmp(ch,""",6)) { + decoded+='"'; ch+=6; + } + else if (*++ch=='#') { + decoded+=(char)atoi(++ch); + ch=strchr(ch,';')+1; + } + } + else { + decoded+=*ch++; + } + } + return decoded; + } + + long sendResponse(std::istream& inputStream, long status) { + m_method="POST"; + string page,line; + while (getline(inputStream,line)) + page += line + '\n'; + + const char* pch=strstr(page.c_str(),"action=\""); + pch+=strlen("action=\""); + m_url = html_decode(page.substr(pch-page.c_str(),strchr(pch,'"')-pch)); + + while (pch=strstr(pch," -#include -#include -#include -#include - -using namespace opensaml::saml1p; -using namespace opensaml::saml1; - +/* + * 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 +#include +#include +#include +#include + +using namespace opensaml::saml1p; +using namespace opensaml::saml1; + namespace { class SAML_DLLLOCAL _addcert : public binary_function { public: @@ -36,81 +36,75 @@ namespace { } }; }; - -class SAML1ArtifactTest : public CxxTest::TestSuite, - public SAMLBindingBaseTestCase, public MessageEncoder::ArtifactGenerator, public MessageDecoder::ArtifactResolver { -public: - void setUp() { - SAMLBindingBaseTestCase::setUp(); - } - - void tearDown() { - SAMLBindingBaseTestCase::tearDown(); - } - - void testSAML1Artifact() { - try { - // Read message to use from file. - string path = data_path + "saml1/binding/SAML1Assertion.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(); - - // Encode message. - auto_ptr encoder( - SAMLConfig::getConfig().MessageEncoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT, NULL) - ); - encoder->setArtifactGenerator(this); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/Artifact","https://sp.example.org/","state",m_creds); - toSend.release(); - - // Decode message. - string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; - QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); - auto_ptr decoder( - SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT, NULL) - ); - decoder->setArtifactResolver(this); - Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole,m_trust) - ) - ); - - // Test the results. - TSM_ASSERT_EQUALS("TARGET was not the expected result.", relayState, "state"); - TSM_ASSERT("SAML Response not decoded successfully.", response.get()); - TSM_ASSERT("Message was not verified.", issuer && securityMech && securityMech==samlconstants::SAML1P_NS); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); - 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,issuer,securityMech,*this,m_metadata,&idprole,m_trust), - BindingException); - } - catch (XMLToolingException& ex) { - TS_TRACE(ex.what()); - throw; - } - } - - SAMLArtifact* generateSAML1Artifact(const char* relyingParty) const { - return new SAMLArtifactType0001(SAMLConfig::getConfig().hashSHA1("https://idp.example.org/")); - } - - saml2p::SAML2Artifact* generateSAML2Artifact(const char* relyingParty) const { - throw BindingException("Not implemented."); - } - + +class SAML1ArtifactTest : public CxxTest::TestSuite, + public SAMLBindingBaseTestCase, public MessageEncoder::ArtifactGenerator, public MessageDecoder::ArtifactResolver { +public: + void setUp() { + SAMLBindingBaseTestCase::setUp(); + } + + void tearDown() { + SAMLBindingBaseTestCase::tearDown(); + } + + void testSAML1Artifact() { + 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 + "saml1/binding/SAML1Assertion.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(); + + // Encode message. + auto_ptr encoder( + SAMLConfig::getConfig().MessageEncoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT, NULL) + ); + encoder->setArtifactGenerator(this); + encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/Artifact","https://sp.example.org/","state",m_creds); + toSend.release(); + + // Decode message. + string relayState; + auto_ptr decoder( + SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT, NULL) + ); + decoder->setArtifactResolver(this); + Locker locker(m_metadata); + auto_ptr response(dynamic_cast(decoder->decode(relayState,*this,policy))); + + // Test the results. + TSM_ASSERT_EQUALS("TARGET 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; + } + } + + SAMLArtifact* generateSAML1Artifact(const char* relyingParty) const { + return new SAMLArtifactType0001(SAMLConfig::getConfig().hashSHA1("https://idp.example.org/")); + } + + saml2p::SAML2Artifact* generateSAML2Artifact(const char* relyingParty) const { + throw BindingException("Not implemented."); + } + Signature* buildSignature(const CredentialResolver* credResolver) const { // Build a Signature. @@ -129,39 +123,36 @@ public: return sig; } - - Response* resolve( - const XMLCh*& securityMech, - const vector& artifacts, - const IDPSSODescriptor& idpDescriptor, - const X509TrustEngine* trustEngine=NULL - ) const { - TSM_ASSERT_EQUALS("Too many artifacts.", artifacts.size(), 1); - XMLObject* xmlObject = - SAMLConfig::getConfig().getArtifactMap()->retrieveContent(artifacts.front(), "https://sp.example.org/"); - Assertion* assertion = dynamic_cast(xmlObject); - TSM_ASSERT("Not an assertion.", assertion!=NULL); - auto_ptr response(ResponseBuilder::buildResponse()); - response->getAssertions().push_back(assertion); - Status* status = StatusBuilder::buildStatus(); - response->setStatus(status); - StatusCode* sc = StatusCodeBuilder::buildStatusCode(); - status->setStatusCode(sc); - sc->setValue(&StatusCode::SUCCESS); - response->setSignature(buildSignature(m_creds)); - vector sigs(1,response->getSignature()); - response->marshall((DOMDocument*)NULL,&sigs); - SchemaValidators.validate(response.get()); - securityMech = NULL; - return response.release(); - } - - saml2p::ArtifactResponse* resolve( - const XMLCh*& securityMech, - const saml2p::SAML2Artifact& artifact, - const SSODescriptorType& ssoDescriptor, - const X509TrustEngine* trustEngine=NULL - ) const { - throw BindingException("Not implemented."); - } -}; + + Response* resolve( + const vector& artifacts, + const IDPSSODescriptor& idpDescriptor, + SecurityPolicy& policy + ) const { + TSM_ASSERT_EQUALS("Too many artifacts.", artifacts.size(), 1); + XMLObject* xmlObject = + SAMLConfig::getConfig().getArtifactMap()->retrieveContent(artifacts.front(), "https://sp.example.org/"); + Assertion* assertion = dynamic_cast(xmlObject); + TSM_ASSERT("Not an assertion.", assertion!=NULL); + auto_ptr response(ResponseBuilder::buildResponse()); + response->getAssertions().push_back(assertion); + Status* status = StatusBuilder::buildStatus(); + response->setStatus(status); + StatusCode* sc = StatusCodeBuilder::buildStatusCode(); + status->setStatusCode(sc); + sc->setValue(&StatusCode::SUCCESS); + response->setSignature(buildSignature(m_creds)); + vector sigs(1,response->getSignature()); + response->marshall((DOMDocument*)NULL,&sigs); + SchemaValidators.validate(response.get()); + return response.release(); + } + + saml2p::ArtifactResponse* resolve( + const saml2p::SAML2Artifact& artifact, + const SSODescriptorType& ssoDescriptor, + SecurityPolicy& policy + ) const { + throw BindingException("Not implemented."); + } +}; diff --git a/samltest/saml1/binding/SAML1POSTTest.h b/samltest/saml1/binding/SAML1POSTTest.h index f84bf8a..e485a76 100644 --- a/samltest/saml1/binding/SAML1POSTTest.h +++ b/samltest/saml1/binding/SAML1POSTTest.h @@ -31,69 +31,11 @@ public: SAMLBindingBaseTestCase::tearDown(); } - void testSAML1POSTTrusted() { + void testSAML1POST() { try { - // Read message to use from file. - string path = data_path + "saml1/binding/SAML1Response.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. - toSend->setIssueInstant(time(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::SAML1_PROFILE_BROWSER_POST, encoder_config->getDocumentElement() - ) - ); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state",m_creds); - toSend.release(); - - // Decode message. - string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); - auto_ptr decoder( - SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_POST, NULL) - ); - Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole,m_trust) - ) - ); - - // Test the results. - TSM_ASSERT_EQUALS("TARGET was not the expected result.", relayState, "state"); - TSM_ASSERT("SAML Response not decoded successfully.", response.get()); - TSM_ASSERT("Message was not verified.", issuer && securityMech && securityMech==samlconstants::SAML1P_NS); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); - 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); - } - catch (XMLToolingException& ex) { - TS_TRACE(ex.what()); - throw; - } - } + SecurityPolicy policy(m_rules, m_metadata, &idprole, m_trust); - void testSAML1POSTUntrusted() { - try { // Read message to use from file. string path = data_path + "saml1/binding/SAML1Response.xml"; ifstream in(path.c_str()); @@ -104,7 +46,7 @@ public: ); janitor.release(); - // Freshen timestamp and clear ID. + // Freshen timestamp and ID. toSend->setIssueInstant(time(NULL)); toSend->setResponseID(NULL); @@ -122,36 +64,27 @@ public: samlconstants::SAML1_PROFILE_BROWSER_POST, encoder_config->getDocumentElement() ) ); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state"); + encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state",m_creds); toSend.release(); // Decode message. string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; - QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); auto_ptr decoder( SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML1_PROFILE_BROWSER_POST, NULL) ); Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole) - ) - ); + auto_ptr response(dynamic_cast(decoder->decode(relayState,*this,policy))); // Test the results. TSM_ASSERT_EQUALS("TARGET was not the expected result.", relayState, "state"); TSM_ASSERT("SAML Response not decoded successfully.", response.get()); - TSM_ASSERT("Message was verified.", issuer && !securityMech); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); + 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,issuer,securityMech,*this,m_metadata,&idprole,m_trust), - BindingException); + TSM_ASSERT_THROWS("Did not catch the replay.", decoder->decode(relayState,*this,policy), BindingException); } catch (XMLToolingException& ex) { TS_TRACE(ex.what()); diff --git a/samltest/saml2/binding/SAML2ArtifactTest.h b/samltest/saml2/binding/SAML2ArtifactTest.h index 958e5ed..a6e4449 100644 --- a/samltest/saml2/binding/SAML2ArtifactTest.h +++ b/samltest/saml2/binding/SAML2ArtifactTest.h @@ -1,135 +1,126 @@ -/* - * 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 -#include -#include -#include - -using namespace opensaml::saml2p; -using namespace opensaml::saml2; - -class SAML2ArtifactTest : public CxxTest::TestSuite, - public SAMLBindingBaseTestCase, public MessageEncoder::ArtifactGenerator, public MessageDecoder::ArtifactResolver { -public: - void setUp() { - SAMLBindingBaseTestCase::setUp(); - } - - void tearDown() { - SAMLBindingBaseTestCase::tearDown(); - } - - void testSAML2Artifact() { - try { - // 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. - toSend->setIssueInstant(time(NULL)); - - // Encode message. - auto_ptr encoder( - SAMLConfig::getConfig().MessageEncoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, NULL) - ); - encoder->setArtifactGenerator(this); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/Artifact","https://sp.example.org/","state",m_creds); - toSend.release(); - - // Decode message. - string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; - QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); - auto_ptr decoder( - SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, NULL) - ); - decoder->setArtifactResolver(this); - Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole,m_trust) - ) - ); - - // 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.", issuer && securityMech && securityMech==samlconstants::SAML20P_NS); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); - 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,issuer,securityMech,*this,m_metadata,&idprole,m_trust), - BindingException); - } - catch (XMLToolingException& ex) { - TS_TRACE(ex.what()); - throw; - } - } - - SAMLArtifact* generateSAML1Artifact(const char* relyingParty) const { - throw BindingException("Not implemented."); - } - - saml2p::SAML2Artifact* generateSAML2Artifact(const char* relyingParty) const { - return new SAML2ArtifactType0004(SAMLConfig::getConfig().hashSHA1("https://idp.example.org/"),1); - } - - saml1p::Response* resolve( - const XMLCh*& securityMech, - const vector& artifacts, - const IDPSSODescriptor& idpDescriptor, - const X509TrustEngine* trustEngine=NULL - ) const { - throw BindingException("Not implemented."); - } - - ArtifactResponse* resolve( - const XMLCh*& securityMech, - const SAML2Artifact& artifact, - const SSODescriptorType& ssoDescriptor, - const X509TrustEngine* trustEngine=NULL - ) const { - XMLObject* xmlObject = - SAMLConfig::getConfig().getArtifactMap()->retrieveContent(&artifact, "https://sp.example.org/"); - Response* payload = dynamic_cast(xmlObject); - TSM_ASSERT("Not a response.", payload!=NULL); - auto_ptr response(ArtifactResponseBuilder::buildArtifactResponse()); - response->setPayload(payload); - Status* status = StatusBuilder::buildStatus(); - response->setStatus(status); - StatusCode* sc = StatusCodeBuilder::buildStatusCode(); - status->setStatusCode(sc); - sc->setValue(StatusCode::SUCCESS); - response->marshall(); - SchemaValidators.validate(response.get()); - securityMech = NULL; - return response.release(); - } -}; +/* + * 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 +#include +#include +#include + +using namespace opensaml::saml2p; +using namespace opensaml::saml2; + +class SAML2ArtifactTest : public CxxTest::TestSuite, + public SAMLBindingBaseTestCase, public MessageEncoder::ArtifactGenerator, public MessageDecoder::ArtifactResolver { +public: + void setUp() { + SAMLBindingBaseTestCase::setUp(); + } + + void tearDown() { + SAMLBindingBaseTestCase::tearDown(); + } + + void testSAML2Artifact() { + 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. + toSend->setIssueInstant(time(NULL)); + + // Encode message. + auto_ptr encoder( + SAMLConfig::getConfig().MessageEncoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, NULL) + ); + encoder->setArtifactGenerator(this); + encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/Artifact","https://sp.example.org/","state",m_creds); + toSend.release(); + + // Decode message. + string relayState; + auto_ptr decoder( + SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_ARTIFACT, NULL) + ); + decoder->setArtifactResolver(this); + 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; + } + } + + SAMLArtifact* generateSAML1Artifact(const char* relyingParty) const { + throw BindingException("Not implemented."); + } + + saml2p::SAML2Artifact* generateSAML2Artifact(const char* relyingParty) const { + return new SAML2ArtifactType0004(SAMLConfig::getConfig().hashSHA1("https://idp.example.org/"),1); + } + + saml1p::Response* resolve( + const vector& artifacts, + const IDPSSODescriptor& idpDescriptor, + SecurityPolicy& policy + ) const { + throw BindingException("Not implemented."); + } + + ArtifactResponse* resolve( + const SAML2Artifact& artifact, + const SSODescriptorType& ssoDescriptor, + SecurityPolicy& policy + ) const { + XMLObject* xmlObject = + SAMLConfig::getConfig().getArtifactMap()->retrieveContent(&artifact, "https://sp.example.org/"); + Response* payload = dynamic_cast(xmlObject); + TSM_ASSERT("Not a response.", payload!=NULL); + auto_ptr response(ArtifactResponseBuilder::buildArtifactResponse()); + response->setPayload(payload); + Status* status = StatusBuilder::buildStatus(); + response->setStatus(status); + StatusCode* sc = StatusCodeBuilder::buildStatusCode(); + status->setStatusCode(sc); + sc->setValue(StatusCode::SUCCESS); + response->marshall(); + SchemaValidators.validate(response.get()); + return response.release(); + } +}; diff --git a/samltest/saml2/binding/SAML2POSTTest.h b/samltest/saml2/binding/SAML2POSTTest.h index 6245b6e..7d6ec3e 100644 --- a/samltest/saml2/binding/SAML2POSTTest.h +++ b/samltest/saml2/binding/SAML2POSTTest.h @@ -31,69 +31,11 @@ public: SAMLBindingBaseTestCase::tearDown(); } - void testSAML2POSTTrusted() { + void testSAML2POST() { try { - // 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. - toSend->setIssueInstant(time(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, encoder_config->getDocumentElement() - ) - ); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state",m_creds); - toSend.release(); - - // Decode message. - string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); - auto_ptr decoder( - SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_POST, NULL) - ); - Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole,m_trust) - ) - ); - - // 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.", issuer && securityMech && securityMech==samlconstants::SAML20P_NS); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); - 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); - } - catch (XMLToolingException& ex) { - TS_TRACE(ex.what()); - throw; - } - } + SecurityPolicy policy(m_rules, m_metadata, &idprole, m_trust); - void testSAML2POSTUntrusted() { - try { // Read message to use from file. string path = data_path + "saml2/binding/SAML2Response.xml"; ifstream in(path.c_str()); @@ -104,7 +46,7 @@ public: ); janitor.release(); - // Freshen timestamp and clear ID. + // Freshen timestamp and ID. toSend->setIssueInstant(time(NULL)); toSend->setID(NULL); @@ -122,36 +64,27 @@ public: samlconstants::SAML20_BINDING_HTTP_POST, encoder_config->getDocumentElement() ) ); - encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state"); + encoder->encode(*this,toSend.get(),"https://sp.example.org/SAML/POST","https://sp.example.org/","state",m_creds); toSend.release(); // Decode message. string relayState; - const RoleDescriptor* issuer=NULL; - const XMLCh* securityMech=NULL; - QName idprole(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME); auto_ptr decoder( SAMLConfig::getConfig().MessageDecoderManager.newPlugin(samlconstants::SAML20_BINDING_HTTP_POST, NULL) ); Locker locker(m_metadata); - auto_ptr response( - dynamic_cast( - decoder->decode(relayState,issuer,securityMech,*this,m_metadata,&idprole) - ) - ); + 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 verified.", issuer && !securityMech); - auto_ptr_char entityID(dynamic_cast(issuer->getParent())->getEntityID()); + 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,issuer,securityMech,*this,m_metadata,&idprole,m_trust), - BindingException); + TSM_ASSERT_THROWS("Did not catch the replay.", decoder->decode(relayState,*this,policy), BindingException); } catch (XMLToolingException& ex) { TS_TRACE(ex.what()); -- 2.1.4