From c3cd4ec3fa87d0ad3c6f65c1a5e15f548b1b6cc2 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Fri, 20 Feb 2009 20:07:37 +0000 Subject: [PATCH] New policy rules for handling conditions. --- .cproject | 10 +- saml/Makefile.am | 7 +- saml/binding/SecurityPolicy.h | 51 +++++- saml/binding/SecurityPolicyRule.h | 49 +++-- saml/binding/impl/MessageFlowRule.cpp | 12 +- saml/binding/impl/SecurityPolicy.cpp | 30 ++++ saml/profile/impl/AudienceRestrictionRule.cpp | 114 ++++++++++++ saml/profile/impl/ConditionsRule.cpp | 227 ++++++++++++++++++++++++ saml/profile/impl/IgnoreRule.cpp | 75 ++++++++ saml/saml.vcproj | 28 +++ saml/saml1/profile/AssertionValidator.h | 21 +-- saml/saml1/profile/BrowserSSOProfileValidator.h | 15 +- saml/saml2/profile/AssertionValidator.h | 19 +- saml/saml2/profile/BrowserSSOProfileValidator.h | 19 +- saml/saml2/profile/SAML2AssertionPolicy.cpp | 33 ++++ saml/saml2/profile/SAML2AssertionPolicy.h | 99 +++++++++++ samltest/Makefile.am | 4 +- samltest/data/saml1/profile/SAML1Assertion.xml | 12 ++ samltest/data/saml2/profile/SAML2Assertion.xml | 13 ++ samltest/saml1/profile/.gitignore | 1 + samltest/saml1/profile/SAML1PolicyTest.h | 65 +++++++ samltest/saml2/profile/.gitignore | 1 + samltest/saml2/profile/SAML2PolicyTest.h | 65 +++++++ samltest/samltest.vcproj | 104 +++++++++++ 24 files changed, 1006 insertions(+), 68 deletions(-) create mode 100644 saml/profile/impl/AudienceRestrictionRule.cpp create mode 100644 saml/profile/impl/ConditionsRule.cpp create mode 100644 saml/profile/impl/IgnoreRule.cpp create mode 100644 saml/saml2/profile/SAML2AssertionPolicy.cpp create mode 100644 saml/saml2/profile/SAML2AssertionPolicy.h create mode 100644 samltest/data/saml1/profile/SAML1Assertion.xml create mode 100644 samltest/data/saml2/profile/SAML2Assertion.xml create mode 100644 samltest/saml1/profile/.gitignore create mode 100644 samltest/saml1/profile/SAML1PolicyTest.h create mode 100644 samltest/saml2/profile/.gitignore create mode 100644 samltest/saml2/profile/SAML2PolicyTest.h diff --git a/.cproject b/.cproject index 29c81c1..4bb7304 100644 --- a/.cproject +++ b/.cproject @@ -62,11 +62,12 @@ + - + @@ -78,7 +79,7 @@ - + @@ -97,7 +98,7 @@ - + @@ -107,6 +108,9 @@ + + + diff --git a/saml/Makefile.am b/saml/Makefile.am index 69e778a..eb19340 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -96,7 +96,8 @@ saml2mdinclude_HEADERS = \ saml2profinclude_HEADERS = \ saml2/profile/AssertionValidator.h \ - saml2/profile/BrowserSSOProfileValidator.h + saml2/profile/BrowserSSOProfileValidator.h \ + saml2/profile/SAML2AssertionPolicy.h noinst_HEADERS = \ internal.h @@ -114,6 +115,9 @@ libsaml_la_SOURCES = \ binding/impl/SimpleSigningRule.cpp \ binding/impl/SOAPClient.cpp \ binding/impl/XMLSigningRule.cpp \ + profile/impl/AudienceRestrictionRule.cpp \ + profile/impl/ConditionsRule.cpp \ + profile/impl/IgnoreRule.cpp \ saml1/core/impl/AssertionsImpl.cpp \ saml1/core/impl/AssertionsSchemaValidators.cpp \ saml1/core/impl/ProtocolsImpl.cpp \ @@ -164,6 +168,7 @@ libsaml_la_SOURCES = \ saml2/binding/impl/SAML2SOAPClient.cpp \ saml2/profile/Assertion20Validator.cpp \ saml2/profile/BrowserSSOProfile20Validator.cpp \ + saml2/profile/SAML2AssertionPolicy.cpp \ encryption/EncryptedKeyResolver.cpp \ signature/ContentReference.cpp \ signature/SignatureProfileValidator.cpp \ diff --git a/saml/binding/SecurityPolicy.h b/saml/binding/SecurityPolicy.h index 72d2464..b655d01 100644 --- a/saml/binding/SecurityPolicy.h +++ b/saml/binding/SecurityPolicy.h @@ -70,11 +70,7 @@ namespace opensaml { const xmltooling::QName* role=NULL, const xmltooling::TrustEngine* trustEngine=NULL, bool validate=true - ) : m_metadataCriteria(NULL), m_messageID(NULL), m_issueInstant(0), m_issuer(NULL), m_issuerRole(NULL), m_authenticated(false), - m_matchingPolicy(NULL), m_metadata(metadataProvider), m_role(NULL), m_trust(trustEngine), m_validate(validate), m_entityOnly(true) { - if (role) - m_role = new xmltooling::QName(*role); - } + ); virtual ~SecurityPolicy(); @@ -135,6 +131,26 @@ namespace opensaml { } /** + * Returns the entityID of the receiving entity. + * + * @return entityID of the peer processing the message + */ + const XMLCh* getRecipient() { + return m_recipient; + } + + /** + * Gets the effective time of message processing. + * + * @return the time at which the message is being processed + */ + time_t getTime() { + if (m_ts == 0) + return m_ts = time(NULL); + return m_ts; + } + + /** * Gets a mutable array of installed policy rules. * *

If adding rules, their lifetime must be at least as long as the policy object. @@ -205,6 +221,27 @@ namespace opensaml { } /** + * Sets entityID of receiving entity. + * + * @param recipient the entityID of the peer processing the message + */ + void setRecipient(const XMLCh* recipient) { + m_recipient = recipient; + } + + /** + * Sets effective time of message processing. + * + *

Assumed to be the time of policy instantiation, can be adjusted to pre- or post-date + * message processing. + * + * @param ts the time at which the message is being processed + */ + void setTime(time_t ts) { + m_ts = ts; + } + + /** * Evaluates the policy against the given request and message, * possibly populating message information in the policy object. * @@ -410,6 +447,10 @@ namespace opensaml { const xmltooling::TrustEngine* m_trust; bool m_validate; bool m_entityOnly; + + // contextual information + const XMLCh* m_recipient; + time_t m_ts; }; }; diff --git a/saml/binding/SecurityPolicyRule.h b/saml/binding/SecurityPolicyRule.h index ae17ba4..003e7ec 100644 --- a/saml/binding/SecurityPolicyRule.h +++ b/saml/binding/SecurityPolicyRule.h @@ -1,6 +1,6 @@ /* * Copyright 2001-2009 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 @@ -16,7 +16,7 @@ /** * @file saml/binding/SecurityPolicyRule.h - * + * * Policy rules that secure and authenticate bindings. */ @@ -26,13 +26,13 @@ #include namespace opensaml { - + /** * 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). + * (e.g. particular security mechanisms that are not present). */ class SAML_API SecurityPolicyRule { @@ -51,14 +51,14 @@ namespace opensaml { /** * Evaluates the rule against the given request and message. - * + * *

An exception will be raised if the message is invalid according to * a policy rule. * *

The return value is used to indicate whether a message was ignored or * successfully processed. A false value signals that the rule wasn't successful * but was also not unsuccessful, because the rule was inapplicable to the message. - * + * * @param message the incoming message * @param request the protocol request * @param policy SecurityPolicy to provide various components and track message data @@ -77,49 +77,64 @@ namespace opensaml { void SAML_API registerSecurityPolicyRules(); /** + * SecurityPolicyRule for evaluation of SAML AudienceRestriction Conditions. + */ + #define AUDIENCE_POLICY_RULE "Audience" + + /** * SecurityPolicyRule for TLS client certificate authentication. - * + * * Evaluates client certificates against the issuer's metadata. */ #define CLIENTCERTAUTH_POLICY_RULE "ClientCertAuth" /** + * SecurityPolicyRule for evaluation of SAML Conditions. + */ + #define CONDITIONS_POLICY_RULE "Conditions" + + /** + * SecurityPolicyRule for ignoring a SAML Condition. + */ + #define IGNORE_POLICY_RULE "Ignore" + + /** * 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 rule. - * + * *

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 rule. */ - #define MESSAGEFLOW_POLICY_RULE "MessageFlow" + #define MESSAGEFLOW_POLICY_RULE "MessageFlow" /** * SecurityPolicyRule for disabling security. - * + * * Allows the message issuer to be authenticated regardless of the message or * transport. Used mainly for debugging or in situations that I wouldn't care to * comment on. */ - #define NULLSECURITY_POLICY_RULE "NullSecurity" + #define NULLSECURITY_POLICY_RULE "NullSecurity" /** * SecurityPolicyRule for protocol message "blob" signing. - * + * * Allows the message issuer to be authenticated using a non-XML digital signature * over the message body. The transport layer is not considered. */ - #define SIMPLESIGNING_POLICY_RULE "SimpleSigning" + #define SIMPLESIGNING_POLICY_RULE "SimpleSigning" /** * SecurityPolicyRule for protocol message XML signing. - * + * * Allows the message issuer to be authenticated using an XML digital signature * over the message. The transport layer is not considered. */ - #define XMLSIGNING_POLICY_RULE "XMLSigning" + #define XMLSIGNING_POLICY_RULE "XMLSigning" }; #endif /* __saml_secrule_h__ */ diff --git a/saml/binding/impl/MessageFlowRule.cpp b/saml/binding/impl/MessageFlowRule.cpp index f371f1f..4ef7632 100644 --- a/saml/binding/impl/MessageFlowRule.cpp +++ b/saml/binding/impl/MessageFlowRule.cpp @@ -1,6 +1,6 @@ /* * Copyright 2001-2009 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 @@ -16,7 +16,7 @@ /** * MessageFlowRule.cpp - * + * * SAML replay and freshness checking SecurityPolicyRule */ @@ -39,12 +39,12 @@ namespace opensaml { public: MessageFlowRule(const DOMElement* e); virtual ~MessageFlowRule() {} - + const char* getType() const { return MESSAGEFLOW_POLICY_RULE; } bool evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const; - + private: bool m_checkReplay; time_t m_expires; @@ -77,7 +77,7 @@ bool MessageFlowRule::evaluate(const XMLObject& message, const GenericRequest* r 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 now = time(NULL); + time_t now = policy.getTime(); time_t skew = XMLToolingConfig::getConfig().clock_skew_secs; time_t issueInstant = policy.getIssueInstant(); if (issueInstant == 0) { @@ -95,7 +95,7 @@ bool MessageFlowRule::evaluate(const XMLObject& message, const GenericRequest* r throw SecurityPolicyException("Message expired, was issued too long ago."); } } - + // Check replay. if (m_checkReplay) { const XMLCh* id = policy.getMessageID(); diff --git a/saml/binding/impl/SecurityPolicy.cpp b/saml/binding/impl/SecurityPolicy.cpp index af93a86..d78e1df 100644 --- a/saml/binding/impl/SecurityPolicy.cpp +++ b/saml/binding/impl/SecurityPolicy.cpp @@ -32,7 +32,10 @@ using namespace xmltooling; using namespace std; namespace opensaml { + SAML_DLLLOCAL PluginManager::Factory AudienceRestrictionRuleFactory; SAML_DLLLOCAL PluginManager::Factory ClientCertAuthRuleFactory; + SAML_DLLLOCAL PluginManager::Factory ConditionsRuleFactory; + SAML_DLLLOCAL PluginManager::Factory IgnoreRuleFactory; SAML_DLLLOCAL PluginManager::Factory MessageFlowRuleFactory; SAML_DLLLOCAL PluginManager::Factory NullSecurityRuleFactory; SAML_DLLLOCAL PluginManager::Factory SimpleSigningRuleFactory; @@ -42,7 +45,10 @@ namespace opensaml { void SAML_API opensaml::registerSecurityPolicyRules() { SAMLConfig& conf=SAMLConfig::getConfig(); + conf.SecurityPolicyRuleManager.registerFactory(AUDIENCE_POLICY_RULE, AudienceRestrictionRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(CLIENTCERTAUTH_POLICY_RULE, ClientCertAuthRuleFactory); + conf.SecurityPolicyRuleManager.registerFactory(CONDITIONS_POLICY_RULE, ConditionsRuleFactory); + conf.SecurityPolicyRuleManager.registerFactory(IGNORE_POLICY_RULE, IgnoreRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(MESSAGEFLOW_POLICY_RULE, MessageFlowRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(NULLSECURITY_POLICY_RULE, NullSecurityRuleFactory); conf.SecurityPolicyRuleManager.registerFactory(SIMPLESIGNING_POLICY_RULE, SimpleSigningRuleFactory); @@ -51,6 +57,30 @@ void SAML_API opensaml::registerSecurityPolicyRules() SecurityPolicy::IssuerMatchingPolicy SecurityPolicy::m_defaultMatching; +SecurityPolicy::SecurityPolicy( + const saml2md::MetadataProvider* metadataProvider, + const xmltooling::QName* role, + const xmltooling::TrustEngine* trustEngine, + bool validate + ) : m_metadataCriteria(NULL), + m_messageID(NULL), + m_issueInstant(0), + m_issuer(NULL), + m_issuerRole(NULL), + m_authenticated(false), + m_matchingPolicy(NULL), + m_metadata(metadataProvider), + m_role(NULL), + m_trust(trustEngine), + m_validate(validate), + m_entityOnly(true), + m_recipient(NULL), + m_ts(0) +{ + if (role) + m_role = new xmltooling::QName(*role); +} + SecurityPolicy::~SecurityPolicy() { XMLString::release(&m_messageID); diff --git a/saml/profile/impl/AudienceRestrictionRule.cpp b/saml/profile/impl/AudienceRestrictionRule.cpp new file mode 100644 index 0000000..e4b03e4 --- /dev/null +++ b/saml/profile/impl/AudienceRestrictionRule.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2009 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. + */ + +/** + * AudienceRestrictionRule.cpp + * + * SAML AudienceRestriction SecurityPolicyRule + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/SecurityPolicyRule.h" +#include "saml1/core/Assertions.h" +#include "saml2/core/Assertions.h" + +#include + +using namespace opensaml; +using namespace xmltooling::logging; +using namespace xmltooling; +using namespace std; + +namespace opensaml { + class SAML_DLLLOCAL AudienceRestrictionRule : public SecurityPolicyRule + { + public: + AudienceRestrictionRule(const DOMElement* e); + + virtual ~AudienceRestrictionRule() { + } + const char* getType() const { + return AUDIENCE_POLICY_RULE; + } + bool evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const; + + private: + vector m_audiences; + }; + + SecurityPolicyRule* SAML_DLLLOCAL AudienceRestrictionRuleFactory(const DOMElement* const & e) + { + return new AudienceRestrictionRule(e); + } +}; + +AudienceRestrictionRule::AudienceRestrictionRule(const DOMElement* e) +{ + e = e ? XMLHelper::getFirstChildElement(e, saml2::Audience::LOCAL_NAME) : NULL; + while (e) { + if (e->hasChildNodes()) + m_audiences.push_back(e->getFirstChild()->getNodeValue()); + e = XMLHelper::getNextSiblingElement(e, saml2::Audience::LOCAL_NAME); + } +} + +bool AudienceRestrictionRule::evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const +{ + const saml2::AudienceRestriction* ac2=dynamic_cast(&message); + if (ac2) { + const vector& auds2 = ac2->getAudiences(); + for (vector::const_iterator a1 = auds2.begin(); a1!=auds2.end(); ++a1) { + if (XMLString::equals(policy.getRecipient(), (*a1)->getAudienceURI())) { + return true; + } + for (vector::const_iterator a2 = m_audiences.begin(); a2!=m_audiences.end(); ++a2) { + if (XMLString::equals((*a1)->getAudienceURI(), *a2)) + return true; + } + } + + ostringstream os; + os << *ac2; + Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.AudienceRestriction").error( + "unacceptable AudienceRestriction in assertion (%s)", os.str().c_str() + ); + throw SecurityPolicyException("Assertion contains an unacceptable AudienceRestriction."); + } + + const saml1::AudienceRestrictionCondition* ac1=dynamic_cast(&message); + if (ac1) { + const vector& auds1 = ac1->getAudiences(); + for (vector::const_iterator a1 = auds1.begin(); a1!=auds1.end(); ++a1) { + if (XMLString::equals(policy.getRecipient(), (*a1)->getAudienceURI())) { + return true; + } + for (vector::const_iterator a2 = m_audiences.begin(); a2!=m_audiences.end(); ++a2) { + if (XMLString::equals((*a1)->getAudienceURI(), *a2)) + return true; + } + } + + ostringstream os; + os << *ac1; + Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.AudienceRestriction").error( + "unacceptable AudienceRestrictionCondition in assertion (%s)", os.str().c_str() + ); + throw SecurityPolicyException("Assertion contains an unacceptable AudienceRestrictionCondition."); + } + + return false; +} diff --git a/saml/profile/impl/ConditionsRule.cpp b/saml/profile/impl/ConditionsRule.cpp new file mode 100644 index 0000000..933c61e --- /dev/null +++ b/saml/profile/impl/ConditionsRule.cpp @@ -0,0 +1,227 @@ +/* + * Copyright 2009 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. + */ + +/** + * ConditionsRule.cpp + * + * SAML Conditions SecurityPolicyRule + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/SecurityPolicyRule.h" +#include "saml1/core/Assertions.h" +#include "saml2/core/Assertions.h" + +#include + +using namespace opensaml; +using namespace xmltooling::logging; +using namespace xmltooling; +using namespace std; + +namespace opensaml { + class SAML_DLLLOCAL ConditionsRule : public SecurityPolicyRule + { + public: + ConditionsRule(const DOMElement* e); + + virtual ~ConditionsRule() { + for_each(m_rules.begin(), m_rules.end(), xmltooling::cleanup()); + if (m_doc) + m_doc->release(); + } + const char* getType() const { + return CONDITIONS_POLICY_RULE; + } + bool evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const; + + private: + DOMDocument* m_doc; + vector m_rules; + }; + + SecurityPolicyRule* SAML_DLLLOCAL ConditionsRuleFactory(const DOMElement* const & e) + { + return new ConditionsRule(e); + } + + static const XMLCh Rule[] = UNICODE_LITERAL_4(R,u,l,e); + static const XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e); + + const char config[] = + "" + "" + "saml:DoNotCacheCondition" + "saml2:OneTimeUse" + "saml2:ProxyRestriction" + ""; +}; + +ConditionsRule::ConditionsRule(const DOMElement* e) : m_doc(NULL) +{ + Category& log=Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.Conditions"); + + if (!e || !e->hasChildNodes()) { + // Default the configuration. + istringstream in(config); + m_doc = XMLToolingConfig::getConfig().getParser().parse(in); + e = m_doc->getDocumentElement(); + } + + e = XMLHelper::getFirstChildElement(e, Rule); + while (e) { + auto_ptr_char temp(e->getAttributeNS(NULL, type)); + if (temp.get() && *temp.get()) { + try { + log.info("building SecurityPolicyRule of type %s", temp.get()); + m_rules.push_back(SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(temp.get(),e)); + } + catch (exception& ex) { + log.crit("error building SecurityPolicyRule: %s", ex.what()); + } + } + e = XMLHelper::getNextSiblingElement(e, Rule); + } +} + +bool ConditionsRule::evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const +{ + const saml2::Assertion* a2=dynamic_cast(&message); + if (a2) { + const saml2::Conditions* conds = a2->getConditions(); + if (!conds) + return true; + + // First verify the time conditions, using the specified timestamp. + time_t now = policy.getTime(); + unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs; + time_t t = conds->getNotBeforeEpoch(); + if (now + skew < t) + throw SecurityPolicyException("Assertion is not yet valid."); + t = conds->getNotOnOrAfterEpoch(); + if (t <= now - skew) + throw SecurityPolicyException("Assertion is no longer valid."); + + // Now we process conditions, starting with the known types and then extensions. + + bool valid; + + const vector& acvec = conds->getAudienceRestrictions(); + for (vector::const_iterator ac = acvec.begin(); ac!=acvec.end(); ++ac) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*ac), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("AudienceRestriction was not understood by policy."); + } + + const vector& otvec = conds->getOneTimeUses(); + for (vector::const_iterator ot = otvec.begin(); ot!=otvec.end(); ++ot) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*ot), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("OneTimeUse was not understood by policy."); + } + + const vector pvec = conds->getProxyRestrictions(); + for (vector::const_iterator p = pvec.begin(); p!=pvec.end(); ++p) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*p), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("ProxyRestriction was not understood by policy."); + } + + const vector& convec = conds->getConditions(); + for (vector::const_iterator c = convec.begin(); c!=convec.end(); ++c) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*c), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("Condition ($1) was not understood by policy.", params(1,(*c)->getElementQName().toString().c_str())); + } + + return true; + } + + const saml1::Assertion* a1=dynamic_cast(&message); + if (a1) { + const saml1::Conditions* conds = a1->getConditions(); + if (!conds) + return true; + + // First verify the time conditions, using the specified timestamp. + time_t now = policy.getTime(); + unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs; + time_t t = conds->getNotBeforeEpoch(); + if (now + skew < t) + throw SecurityPolicyException("Assertion is not yet valid."); + t = conds->getNotOnOrAfterEpoch(); + if (t <= now - skew) + throw SecurityPolicyException("Assertion is no longer valid."); + + // Now we process conditions, starting with the known types and then extensions. + + bool valid; + + const vector& acvec = conds->getAudienceRestrictionConditions(); + for (vector::const_iterator ac = acvec.begin(); ac!=acvec.end(); ++ac) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*ac), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("AudienceRestrictionCondition was not understood by policy."); + } + + const vector& dncvec = conds->getDoNotCacheConditions(); + for (vector::const_iterator dnc = dncvec.begin(); dnc!=dncvec.end(); ++dnc) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*dnc), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("DoNotCacheCondition was not understood by policy."); + } + + const vector& convec = conds->getConditions(); + for (vector::const_iterator c = convec.begin(); c!=convec.end(); ++c) { + valid = false; + for (vector::const_iterator r = m_rules.begin(); r != m_rules.end(); ++r) { + if ((*r)->evaluate(*(*c), request, policy)) + valid = true; + } + if (!valid) + throw SecurityPolicyException("Condition ($1) was not understood by policy.", params(1,(*c)->getElementQName().toString().c_str())); + } + + return true; + } + + return false; +} diff --git a/saml/profile/impl/IgnoreRule.cpp b/saml/profile/impl/IgnoreRule.cpp new file mode 100644 index 0000000..f602424 --- /dev/null +++ b/saml/profile/impl/IgnoreRule.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2009 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. + */ + +/** + * IgnoreRule.cpp + * + * SecurityPolicyRule that ignores a message while indicating "success". + */ + +#include "internal.h" +#include "exceptions.h" +#include "binding/SecurityPolicyRule.h" + +#include +#include + +using namespace opensaml; +using namespace xmltooling::logging; +using namespace xmltooling; +using namespace std; + + +namespace opensaml { + class SAML_DLLLOCAL IgnoreRule : public SecurityPolicyRule + { + public: + IgnoreRule(const DOMElement* e) + : m_log(Category::getInstance(SAML_LOGCAT".SecurityPolicyRule.Ignore")), m_qname(XMLHelper::getNodeValueAsQName(e)) { + if (!m_qname) + throw SecurityPolicyException("No schema type or element name supplied to Ignore rule."); + } + virtual ~IgnoreRule() { + delete m_qname; + } + + const char* getType() const { + return IGNORE_POLICY_RULE; + } + bool evaluate(const XMLObject& message, const GenericRequest* request, SecurityPolicy& policy) const { + if (message.getSchemaType()) { + if (*m_qname != *(message.getSchemaType())) + return false; + m_log.info("ignoring condition with type (%s)", message.getSchemaType()->toString().c_str()); + } + else { + if (*m_qname != message.getElementQName()) + return false; + m_log.info("ignoring condition (%s)", message.getElementQName().toString().c_str()); + } + return true; + } + + private: + Category& m_log; + xmltooling::QName* m_qname; + }; + + SecurityPolicyRule* SAML_DLLLOCAL IgnoreRuleFactory(const DOMElement* const & e) + { + return new IgnoreRule(e); + } +}; diff --git a/saml/saml.vcproj b/saml/saml.vcproj index e2c0728..ab67f1f 100644 --- a/saml/saml.vcproj +++ b/saml/saml.vcproj @@ -768,6 +768,10 @@ /> + + + + + + + + + + + + + + The base class version only understands AudienceRestrictionConditions. * All other condition types will be rejected and require subclassing to * prevent validation failure. - * + * * @param condition condition to validate */ virtual void validateCondition(const Condition* condition) const; @@ -74,14 +75,14 @@ namespace opensaml { protected: /** Name of recipient (implicit audience). */ const XMLCh* m_recipient; - + /** Additional audience values. */ const std::vector* m_audiences; /** Timestamp to evaluate assertion conditions. */ time_t m_ts; }; - + }; }; diff --git a/saml/saml1/profile/BrowserSSOProfileValidator.h b/saml/saml1/profile/BrowserSSOProfileValidator.h index 65d304b..0580d14 100644 --- a/saml/saml1/profile/BrowserSSOProfileValidator.h +++ b/saml/saml1/profile/BrowserSSOProfileValidator.h @@ -1,6 +1,6 @@ /* * Copyright 2001-2007 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 @@ -16,8 +16,8 @@ /** * @file saml/saml1/profile/BrowserSSOProfileValidator.h - * - * SAML 1.x Browser SSO Profile Assertion Validator + * + * SAML 1.x Browser SSO Profile Assertion Validator */ #ifndef __saml1_ssoval_h__ @@ -27,8 +27,9 @@ namespace opensaml { namespace saml1 { - + /** + * @deprecated * SAML 1.x Browser SSO Profile Assertion Validator * *

In addition to standard core requirements for validity, SSO assertions @@ -40,7 +41,7 @@ namespace opensaml { public: /** * Constructor - * + * * @recipient name of assertion recipient (implicit audience) * @param audiences additional audience values * @param ts timestamp to evaluate assertion conditions, or 0 to bypass check @@ -49,10 +50,10 @@ namespace opensaml { : AssertionValidator(recipient, audiences, ts) { } virtual ~BrowserSSOProfileValidator() {} - + void validateAssertion(const Assertion& assertion) const; }; - + }; }; diff --git a/saml/saml2/profile/AssertionValidator.h b/saml/saml2/profile/AssertionValidator.h index 240fd9d..8a05e29 100644 --- a/saml/saml2/profile/AssertionValidator.h +++ b/saml/saml2/profile/AssertionValidator.h @@ -1,6 +1,6 @@ /* * Copyright 2001-2007 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 @@ -16,7 +16,7 @@ /** * @file saml/saml2/profile/AssertionValidator.h - * + * * SAML 2.0 basic assertion validator */ @@ -28,11 +28,12 @@ namespace opensaml { namespace saml2 { - + class SAML_API Assertion; class SAML_API Condition; - + /** + * @deprecated * SAML 2.0 basic assertion validator provides time and audience condition checking. */ class SAML_API AssertionValidator : public virtual xmltooling::Validator @@ -40,7 +41,7 @@ namespace opensaml { public: /** * Constructor - * + * * @param recipient name of assertion recipient (implicit audience) * @param audiences additional audience values * @param ts timestamp to evaluate assertion conditions, or 0 to bypass check @@ -50,12 +51,12 @@ namespace opensaml { } virtual ~AssertionValidator() {} - + void validate(const xmltooling::XMLObject* xmlObject) const; /** * Type-safe validation method. - * + * * @param assertion assertion to validate */ virtual void validateAssertion(const Assertion& assertion) const; @@ -66,7 +67,7 @@ namespace opensaml { *

The base class version only understands AudienceRestriction conditions. * All other condition types will be rejected and require subclassing to * prevent validation failure. - * + * * @param condition condition to validate */ virtual void validateCondition(const Condition* condition) const; @@ -81,7 +82,7 @@ namespace opensaml { /** Timestamp to evaluate assertion conditions. */ time_t m_ts; }; - + }; }; diff --git a/saml/saml2/profile/BrowserSSOProfileValidator.h b/saml/saml2/profile/BrowserSSOProfileValidator.h index 97318bd..460c99d 100644 --- a/saml/saml2/profile/BrowserSSOProfileValidator.h +++ b/saml/saml2/profile/BrowserSSOProfileValidator.h @@ -1,6 +1,6 @@ /* * Copyright 2001-2007 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 @@ -16,8 +16,8 @@ /** * @file saml/saml2/profile/BrowserSSOProfileValidator.h - * - * SAML 2.0 Browser SSO Profile Assertion Validator + * + * SAML 2.0 Browser SSO Profile Assertion Validator */ #ifndef __saml2_ssoval_h__ @@ -26,10 +26,11 @@ #include namespace opensaml { - + namespace saml2 { - + /** + * @deprecated * SAML 2.0 Browser SSO Profile Assertion Validator * *

In addition to standard core requirements for validity, SSO assertions @@ -41,7 +42,7 @@ namespace opensaml { public: /** * Constructor - * + * * @param recipient name of assertion recipient (implicit audience) * @param audiences additional audience values * @param ts timestamp to evaluate assertion conditions, or 0 to bypass check @@ -57,7 +58,7 @@ namespace opensaml { ) : AssertionValidator(recipient, audiences, ts), m_destination(destination), m_requestID(requestID) { } virtual ~BrowserSSOProfileValidator() {} - + void validateAssertion(const Assertion& assertion) const; /** @@ -68,7 +69,7 @@ namespace opensaml { const char* getAddress() const { return m_address.c_str(); } - + protected: /** Server location to which assertion was delivered. */ xmltooling::auto_ptr_XMLCh m_destination; @@ -80,7 +81,7 @@ namespace opensaml { /** Address in confirmed bearer SubjectConfirmationData. */ mutable std::string m_address; }; - + }; }; diff --git a/saml/saml2/profile/SAML2AssertionPolicy.cpp b/saml/saml2/profile/SAML2AssertionPolicy.cpp new file mode 100644 index 0000000..08e9768 --- /dev/null +++ b/saml/saml2/profile/SAML2AssertionPolicy.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2009 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. + */ + +/** + * SAML2AssertionPolicy.cpp + * + * Policy subclass to track SAML 2.0 Assertion SubjectConfirmation. + */ + +#include "internal.h" +#include "saml2/profile/SAML2AssertionPolicy.h" + +using namespace opensaml::saml2; +using namespace opensaml; + +void SAML2AssertionPolicy::reset(bool messageOnly) +{ + SecurityPolicy::reset(messageOnly); + SAML2AssertionPolicy::_reset(messageOnly); +} diff --git a/saml/saml2/profile/SAML2AssertionPolicy.h b/saml/saml2/profile/SAML2AssertionPolicy.h new file mode 100644 index 0000000..271d7e9 --- /dev/null +++ b/saml/saml2/profile/SAML2AssertionPolicy.h @@ -0,0 +1,99 @@ +/* + * Copyright 2009 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/profile/SAML2AssertionPolicy.h + * + * Policy subclass to track SAML 2.0 Assertion SubjectConfirmation. + */ + +#ifndef __saml_saml2asspol_h__ +#define __saml_saml2asspol_h__ + +#include + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4250 4251 ) +#endif + +namespace opensaml { + + namespace saml2 { + class SAML_API SubjectConfirmation; + }; + + /** + * Policy subclass to track SAML 2.0 Assertion SubjectConfirmation. + */ + class SAML_API SAML2AssertionPolicy : virtual public SecurityPolicy + { + public: + /** + * Constructor for policy. + * + * @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 + * @param validate true iff XML parsing should be done with validation + */ + SAML2AssertionPolicy( + const saml2md::MetadataProvider* metadataProvider=NULL, + const xmltooling::QName* role=NULL, + const xmltooling::TrustEngine* trustEngine=NULL, + bool validate=true + ) : SecurityPolicy(metadataProvider, role, trustEngine, validate) { + } + + virtual ~SAML2AssertionPolicy() {} + + virtual void reset(bool messageOnly=false); + void _reset(bool messageOnly=false) { + m_confirmation = false; + } + + /** + * Returns the subject confirmation that was successfully accepted by the policy. + * + * @return a successfully evaluated SubjectConfirmation + */ + const saml2::SubjectConfirmation* getSubjectConfirmation() const { + return m_confirmation; + } + + /** + * Sets the SubjectConfirmation that was successfully accepted by the policy. + * + *

The lifetime of the SubjectConfirmation object MUST be longer + * than the lifetime of the policy object. + * + * @param confirmation the successfully evaluated SubjectConfirmation + */ + void setSubjectConfirmation(const saml2::SubjectConfirmation* confirmation) { + m_confirmation = confirmation; + } + + private: + const saml2::SubjectConfirmation* m_confirmation; + }; + +}; + +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + +#endif /* __saml_saml2asspol_h__ */ diff --git a/samltest/Makefile.am b/samltest/Makefile.am index 3f9400d..25d89d5 100644 --- a/samltest/Makefile.am +++ b/samltest/Makefile.am @@ -33,6 +33,7 @@ samltest_h = \ saml1/core/impl/AuthenticationStatementTest.h \ saml1/binding/SAML1ArtifactTest.h \ saml1/binding/SAML1POSTTest.h \ + saml1/profile/SAML1PolicyTest.h \ saml2/core/impl/Action20Test.h \ saml2/core/impl/Advice20Test.h \ saml2/core/impl/Artifact20Test.h \ @@ -93,7 +94,8 @@ samltest_h = \ saml2/binding/SAML2ArtifactTest.h \ saml2/binding/SAML2POSTTest.h \ saml2/binding/SAML2RedirectTest.h \ - saml2/metadata/XMLMetadataProviderTest.h + saml2/metadata/XMLMetadataProviderTest.h \ + saml2/profile/SAML2PolicyTest.h noinst_HEADERS = \ binding.h \ diff --git a/samltest/data/saml1/profile/SAML1Assertion.xml b/samltest/data/saml1/profile/SAML1Assertion.xml new file mode 100644 index 0000000..01d7d96 --- /dev/null +++ b/samltest/data/saml1/profile/SAML1Assertion.xml @@ -0,0 +1,12 @@ + + + + https://sp.example.org + + + + + John Doe + + diff --git a/samltest/data/saml2/profile/SAML2Assertion.xml b/samltest/data/saml2/profile/SAML2Assertion.xml new file mode 100644 index 0000000..758e79e --- /dev/null +++ b/samltest/data/saml2/profile/SAML2Assertion.xml @@ -0,0 +1,13 @@ + + https://idp.example.org/ + John Doe + + + https://sp.example.org + + + + + foo + + diff --git a/samltest/saml1/profile/.gitignore b/samltest/saml1/profile/.gitignore new file mode 100644 index 0000000..e16b497 --- /dev/null +++ b/samltest/saml1/profile/.gitignore @@ -0,0 +1 @@ +/*.cpp diff --git a/samltest/saml1/profile/SAML1PolicyTest.h b/samltest/saml1/profile/SAML1PolicyTest.h new file mode 100644 index 0000000..82660b1 --- /dev/null +++ b/samltest/saml1/profile/SAML1PolicyTest.h @@ -0,0 +1,65 @@ +/* + * Copyright 2001-2007 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 + +using namespace opensaml; + +class SAML1PolicyTest : public CxxTest::TestSuite { + SecurityPolicy* m_policy; + SecurityPolicyRule* m_rule; +public: + void setUp() { + m_policy = NULL; + m_rule = NULL; + m_rule = SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(CONDITIONS_POLICY_RULE, NULL); + m_policy = new SecurityPolicy(); + m_policy->getRules().push_back(m_rule); + } + + void tearDown() { + delete m_rule; + delete m_policy; + } + + void testSAML1Policy() { + try { + // Read assertion to use from file. + string path = data_path + "saml1/profile/SAML1Assertion.xml"; + ifstream in(path.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + auto_ptr assertion( + dynamic_cast(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(),true)) + ); + janitor.release(); + + TSM_ASSERT_THROWS("Policy should have tripped on AudienceRestriction", m_policy->evaluate(*assertion.get()), SecurityPolicyException); + + auto_ptr_XMLCh recipient("https://sp.example.org"); + m_policy->setRecipient(recipient.get()); + m_policy->evaluate(*assertion.get()); + } + catch (exception& ex) { + TS_TRACE(ex.what()); + throw; + } + } +}; diff --git a/samltest/saml2/profile/.gitignore b/samltest/saml2/profile/.gitignore new file mode 100644 index 0000000..e16b497 --- /dev/null +++ b/samltest/saml2/profile/.gitignore @@ -0,0 +1 @@ +/*.cpp diff --git a/samltest/saml2/profile/SAML2PolicyTest.h b/samltest/saml2/profile/SAML2PolicyTest.h new file mode 100644 index 0000000..f4cc1d8 --- /dev/null +++ b/samltest/saml2/profile/SAML2PolicyTest.h @@ -0,0 +1,65 @@ +/* + * Copyright 2001-2007 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 + +using namespace opensaml; + +class SAML2PolicyTest : public CxxTest::TestSuite { + SecurityPolicy* m_policy; + SecurityPolicyRule* m_rule; +public: + void setUp() { + m_policy = NULL; + m_rule = NULL; + m_rule = SAMLConfig::getConfig().SecurityPolicyRuleManager.newPlugin(CONDITIONS_POLICY_RULE, NULL); + m_policy = new SecurityPolicy(); + m_policy->getRules().push_back(m_rule); + } + + void tearDown() { + delete m_rule; + delete m_policy; + } + + void testSAML2Policy() { + try { + // Read assertion to use from file. + string path = data_path + "saml2/profile/SAML2Assertion.xml"; + ifstream in(path.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + auto_ptr assertion( + dynamic_cast(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(),true)) + ); + janitor.release(); + + TSM_ASSERT_THROWS("Policy should have tripped on AudienceRestriction", m_policy->evaluate(*assertion.get()), SecurityPolicyException); + + auto_ptr_XMLCh recipient("https://sp.example.org"); + m_policy->setRecipient(recipient.get()); + m_policy->evaluate(*assertion.get()); + } + catch (exception& ex) { + TS_TRACE(ex.what()); + throw; + } + } +}; diff --git a/samltest/samltest.vcproj b/samltest/samltest.vcproj index f0061fb..89006d8 100644 --- a/samltest/samltest.vcproj +++ b/samltest/samltest.vcproj @@ -419,6 +419,14 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +