https://issues.shibboleth.net/jira/browse/SSPCPP-398
authorScott Cantor <cantor.2@osu.edu>
Fri, 2 Mar 2012 18:18:07 +0000 (18:18 +0000)
committerScott Cantor <cantor.2@osu.edu>
Fri, 2 Mar 2012 18:18:07 +0000 (18:18 +0000)
schemas/shibboleth-2.0-native-sp-config.xsd
shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp
shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp
shibsp/handler/impl/SAML2Consumer.cpp

index 31a2441..c7ec932 100644 (file)
     </sequence>
     <attribute name="id" type="conf:string" fixed="default"/>
     <attribute name="entityID" type="anyURI" use="required"/>
-    <attribute name="policyId" type="conf:string"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
     </sequence>
     <attribute name="id" type="conf:string" use="required"/>
     <attribute name="entityID" type="anyURI"/>
-    <attribute name="policyId" type="conf:string"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
 
   <attributeGroup name="ApplicationGroup">
     <attribute name="homeURL" type="anyURI"/>
+    <attribute name="policyId" type="conf:string"/>
     <attribute name="REMOTE_USER" type="conf:listOfStrings"/>
     <attribute name="unsetHeaders" type="conf:listOfStrings"/>
     <attribute name="metadataAttributePrefix" type="conf:string"/>
     <attribute name="attributePrefix" type="conf:string"/>
+    <attribute name="requireAuthenticatedCipher" type="boolean"/>
   </attributeGroup>
 
   <attributeGroup name="RelyingPartyGroup">
index 56cedf4..dc5570e 100644 (file)
@@ -533,16 +533,26 @@ void QueryResolver::SAML2Query(QueryContext& ctx) const
             m_log.warn("simple resolver only supports one assertion in the query response");
         }
 
-        CredentialResolver* cr=application.getCredentialResolver();
+        CredentialResolver* cr = application.getCredentialResolver();
         if (!cr) {
             m_log.warn("found encrypted assertion, but no CredentialResolver was available");
             throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available.");
         }
 
+        // With this flag on, we block unauthenticated ciphertext when decrypting,
+        // unless the protocol was authenticated.
+        pair<bool,bool> authenticatedCipher = application.getBool("requireAuthenticatedCipher");
+        if (policy->isAuthenticated())
+            authenticatedCipher.second = false;
+
         // Attempt to decrypt it.
         try {
             Locker credlocker(cr);
-            auto_ptr<XMLObject> tokenwrapper(encassertions.front()->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc));
+            auto_ptr<XMLObject> tokenwrapper(
+                encassertions.front()->decrypt(
+                    *cr, relyingParty->getXMLString("entityID").second, &mcc, authenticatedCipher.first && authenticatedCipher.second
+                    )
+                );
             newtoken = dynamic_cast<saml2::Assertion*>(tokenwrapper.get());
             if (newtoken) {
                 tokenwrapper.release();
index b4aef5b..7e2d1d0 100644 (file)
@@ -418,10 +418,20 @@ void SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha
             throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available.");
         }
 
+        // With this flag on, we block unauthenticated ciphertext when decrypting,
+        // unless the protocol was authenticated.
+        pair<bool,bool> authenticatedCipher = application.getBool("requireAuthenticatedCipher");
+        if (policy->isAuthenticated())
+            authenticatedCipher.second = false;
+
         // Attempt to decrypt it.
         try {
             Locker credlocker(cr);
-            auto_ptr<XMLObject> tokenwrapper(encassertions.front()->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc));
+            auto_ptr<XMLObject> tokenwrapper(
+                encassertions.front()->decrypt(
+                    *cr, relyingParty->getXMLString("entityID").second, &mcc, authenticatedCipher.first && authenticatedCipher.second
+                    )
+                );
             newtoken = dynamic_cast<saml2::Assertion*>(tokenwrapper.get());
             if (newtoken) {
                 tokenwrapper.release();
index 5dec9e4..be11ccd 100644 (file)
@@ -168,12 +168,19 @@ void SAML2Consumer::implementProtocol(
     // And also track "owned" tokens that we decrypt here.
     vector< boost::shared_ptr<saml2::Assertion> > ownedtokens;
 
+    // With this flag on, we block unauthenticated ciphertext when decrypting,
+    // unless the protocol was authenticated.
+    pair<bool,bool> requireAuthenticatedCipher = application.getBool("requireAuthenticatedCipher");
+    if (alreadySecured)
+        requireAuthenticatedCipher.second = false;
+
     // With this flag on, we ignore any unsigned assertions.
     const EntityDescriptor* entity = nullptr;
-    pair<bool,bool> flag = make_pair(false,false);
+    pair<bool,bool> requireSignedAssertions = make_pair(false,false);
     if (alreadySecured && policy.getIssuerMetadata()) {
         entity = dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent());
-        flag = application.getRelyingParty(entity)->getBool("requireSignedAssertions");
+        const PropertySet* rp = application.getRelyingParty(entity);
+        requireSignedAssertions = rp->getBool("requireSignedAssertions");
     }
 
     // authnskew allows rejection of SSO if AuthnInstant is too old.
@@ -195,7 +202,7 @@ void SAML2Consumer::implementProtocol(
             a != make_indirect_iterator(assertions.end()); ++a) {
         try {
             // Skip unsigned assertion?
-            if (!a->getSignature() && flag.first && flag.second)
+            if (!a->getSignature() && requireSignedAssertions.first && requireSignedAssertions.second)
                 throw SecurityPolicyException("The incoming assertion was unsigned, violating local security policy.");
 
             // We clear the security flag, so we can tell whether the token was secured on its own.
@@ -217,8 +224,8 @@ void SAML2Consumer::implementProtocol(
             // If we hadn't established Issuer yet, redo the signedAssertions check.
             if (!entity && policy.getIssuerMetadata()) {
                 entity = dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent());
-                flag = application.getRelyingParty(entity)->getBool("requireSignedAssertions");
-                if (!a->getSignature() && flag.first && flag.second)
+                requireSignedAssertions = application.getRelyingParty(entity)->getBool("requireSignedAssertions");
+                if (!a->getSignature() && requireSignedAssertions.first && requireSignedAssertions.second)
                     throw SecurityPolicyException("The incoming assertion was unsigned, violating local security policy.");
             }
 
@@ -266,7 +273,7 @@ void SAML2Consumer::implementProtocol(
     }
 
     // In case we need decryption...
-    CredentialResolver* cr=application.getCredentialResolver();
+    CredentialResolver* cr = application.getCredentialResolver();
     if (!cr && !encassertions.empty())
         m_log.warn("found encrypted assertions, but no CredentialResolver was available");
 
@@ -279,7 +286,14 @@ void SAML2Consumer::implementProtocol(
             scoped_ptr<MetadataCredentialCriteria> mcc(
                 policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : nullptr
                 );
-            boost::shared_ptr<XMLObject> wrapper(ea->decrypt(*cr, application.getRelyingParty(entity)->getXMLString("entityID").second, mcc.get()));
+            boost::shared_ptr<XMLObject> wrapper(
+                ea->decrypt(
+                    *cr,
+                    application.getRelyingParty(entity)->getXMLString("entityID").second,
+                    mcc.get(),
+                    requireAuthenticatedCipher.first && requireAuthenticatedCipher.second
+                    )
+                );
             decrypted = dynamic_pointer_cast<saml2::Assertion>(wrapper);
             if (decrypted) {
                 ownedtokens.push_back(decrypted);
@@ -294,6 +308,10 @@ void SAML2Consumer::implementProtocol(
             continue;
 
         try {
+            // Skip unsigned assertion?
+            if (!decrypted->getSignature() && requireSignedAssertions.first && requireSignedAssertions.second)
+                throw SecurityPolicyException("The incoming assertion was unsigned, violating local security policy.");
+
             // We clear the security flag, so we can tell whether the token was secured on its own.
             policy.setAuthenticated(false);
             policy.reset(true);
@@ -316,8 +334,8 @@ void SAML2Consumer::implementProtocol(
             // If we hadn't established Issuer yet, redo the signedAssertions check.
             if (!entity && policy.getIssuerMetadata()) {
                 entity = dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent());
-                flag = application.getRelyingParty(entity)->getBool("requireSignedAssertions");
-                if (!decrypted->getSignature() && flag.first && flag.second)
+                requireSignedAssertions = application.getRelyingParty(entity)->getBool("requireSignedAssertions");
+                if (!decrypted->getSignature() && requireSignedAssertions.first && requireSignedAssertions.second)
                     throw SecurityPolicyException("The decrypted assertion was unsigned, violating local security policy.");
             }