[java-identity-provider] branch master updated: IDP-1215 - Revocation and iterative seeds in pairwise ID generation

Scott Cantor cantor.2 at osu.edu
Thu Sep 7 20:46:00 EDT 2017


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=2c979aa31ebdd0dfd587355d2886166192471fa5

The following commit(s) were added to refs/heads/master by this push:
       new  2c979aa   IDP-1215 - Revocation and iterative seeds in pairwise ID generation
2c979aa is described below

commit 2c979aa31ebdd0dfd587355d2886166192471fa5
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Sep 7 20:45:57 2017 -0400

    IDP-1215 - Revocation and iterative seeds in pairwise ID generation
    
    https://issues.shibboleth.net/jira/browse/IDP-1215
---
 .../src/main/resources/conf/saml-nameid.properties |   4 +-
 .../resources/system/conf/saml-nameid-system.xml   |   1 +
 .../ComputedPersistentIdGenerationStrategy.java    |  99 +++++++++++++++++++-
 .../impl/PersistentSAML2NameIDGeneratorTest.java   | 100 ++++++++++++++++++++-
 4 files changed, 198 insertions(+), 6 deletions(-)

diff --git a/idp-conf/src/main/resources/conf/saml-nameid.properties b/idp-conf/src/main/resources/conf/saml-nameid.properties
index bbc1597..25a78bc 100644
--- a/idp-conf/src/main/resources/conf/saml-nameid.properties
+++ b/idp-conf/src/main/resources/conf/saml-nameid.properties
@@ -19,11 +19,11 @@
 # Persistent IDs can be computed on the fly with a hash, or managed in a database
 
 # For computed IDs, set a source attribute and a secret salt:
-#idp.persistentId.sourceAttribute = changethistosomethingreal
+idp.persistentId.sourceAttribute = changethistosomethingreal
 #idp.persistentId.useUnfilteredAttributes = true
 # Do *NOT* share the salt with other people, it's like divulging your private key.
 #idp.persistentId.algorithm = SHA
-#idp.persistentId.salt = changethistosomethingrandom
+idp.persistentId.salt = changethistosomethingrandom
 # BASE64 will match V2 values, we recommend BASE32 encoding for new installs.
 idp.persistentId.encoding = BASE32
 
diff --git a/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml b/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
index 4834b10..93b56a2 100644
--- a/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
+++ b/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
@@ -61,6 +61,7 @@
         class="net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy"
         p:salt="%{idp.persistentId.salt:}"
         p:encodedSalt="%{idp.persistentId.encodedSalt:}"
+        p:exceptionMap="#{getObject('%{idp.persistentId.exceptionMap:shibboleth.ComputedIdExceptionMap}'.trim())}"
         p:algorithm="%{idp.persistentId.algorithm:SHA}"
         p:encoding="#{ T(net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy.Encoding).%{idp.persistentId.encoding:BASE64} }" />
 
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java
index b4d6eb8..4f63f73 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java
@@ -19,6 +19,9 @@ package net.shibboleth.idp.saml.nameid.impl;
 
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -48,6 +51,9 @@ import org.slf4j.LoggerFactory;
 public class ComputedPersistentIdGenerationStrategy extends AbstractInitializableComponent
         implements PersistentIdGenerationStrategy {
 
+    /** An override trigger to apply to all relying parties. */
+    @Nonnull @NotEmpty public static final String WILDCARD_OVERRIDE = "*";
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ComputedPersistentIdGenerationStrategy.class);
 
@@ -69,10 +75,14 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
     /** The encoding to apply to the digest. */
     @Nonnull private Encoding encoding;
     
+    /** Override map to block or re-issue identifiers. */
+    @Nonnull private Map<String,Map<String,String>> exceptionMap;
+    
     /** Constructor. */
     public ComputedPersistentIdGenerationStrategy() {
         algorithm = "SHA";
         encoding = Encoding.BASE64;
+        exceptionMap = Collections.emptyMap();
     }
     
     /**
@@ -137,6 +147,40 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
         
         encoding = Constraint.isNotNull(enc, "Encoding cannot be null");
     }
+    
+    /**
+     * Install map of exceptions that override standard generation.
+     * 
+     * <p>The map is keyed by principal name (or '*' for all), and the values are a map of relying party
+     * to salt overrides. A relying party of '*' applies to all parties. A null mapped value implies that
+     * no value should be generated, while a string value is fed into the computation in place of the default
+     * salt. Specific rules trump wildcarded rules.</p> 
+     * 
+     * @param map exceptions to apply
+     */
+    public void setExceptionMap(@Nullable @NotEmpty final Map<String,Map<String,String>> map) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        if (map == null) {
+            exceptionMap = Collections.emptyMap();
+        } else {
+            exceptionMap = new HashMap<>(map.size());
+            for (final Map.Entry<String,Map<String,String>> entry : map.entrySet()) {
+                final String principal = StringSupport.trimOrNull(entry.getKey());
+                if (principal != null && entry.getValue() != null) {
+                    final Map<String,String> overrides = new HashMap<>(entry.getValue().size());
+                    for (final Map.Entry<String,String> subentry : entry.getValue().entrySet()) {
+                        final String rpname = StringSupport.trimOrNull(subentry.getKey());
+                        if (rpname != null) {
+                            final String override = StringSupport.trimOrNull(subentry.getValue());
+                            overrides.put(rpname, override);
+                        }
+                    }
+                    exceptionMap.put(principal, overrides);
+                }
+            }
+        }
+    }
 
     /** {@inheritDoc} */
     @Override
@@ -150,7 +194,6 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
         if (getSalt().length < 16) {
             throw new ComponentInitializationException("Salt must be at least 16 bytes in size");
         }
-
     }
     
     /** {@inheritDoc} */
@@ -159,6 +202,11 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
             @Nonnull @NotEmpty final String relyingPartyId, @Nonnull @NotEmpty final String principalName,
             @Nonnull @NotEmpty final String sourceId) throws SAMLException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+     
+        final byte[] effectiveSalt = getEffectiveSalt(principalName, relyingPartyId);
+        if (effectiveSalt == null) {
+            throw new SAMLException("Generation blocked by exception rule");
+        }
         
         try {
             final MessageDigest md = MessageDigest.getInstance(algorithm);
@@ -168,9 +216,9 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
             md.update((byte) '!');
 
             if (encoding == Encoding.BASE32) {
-                return Base32Support.encode(md.digest(salt), Base32Support.UNCHUNKED);
+                return Base32Support.encode(md.digest(effectiveSalt), Base32Support.UNCHUNKED);
             } else if (encoding == Encoding.BASE64) {
-                return Base64Support.encode(md.digest(salt), Base64Support.UNCHUNKED);
+                return Base64Support.encode(md.digest(effectiveSalt), Base64Support.UNCHUNKED);
             } else {
                 throw new SAMLException("Desired encoding was not recognized, unable to compute ID");
             }
@@ -180,4 +228,49 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
         }
     }
     
+    /**
+     * Get the effective salt to apply for a particular principal/RP pair, or null to refuse to generate one.
+     * 
+     * @param principalName name of subject
+     * @param relyingPartyId name of relying party scope
+     * 
+     * @return salt to use
+     */
+    @Nullable private byte[] getEffectiveSalt(@Nonnull @NotEmpty final String principalName,
+            @Nonnull @NotEmpty final String relyingPartyId) {
+        
+        Map<String,String> override = exceptionMap.get(principalName);
+        if (override == null) {
+            override = exceptionMap.get(WILDCARD_OVERRIDE);
+        }
+        
+        if (override != null) {
+            if (override.containsKey(relyingPartyId)) {
+                final String s = override.get(relyingPartyId);
+                if (s != null) {
+                    log.debug("Overriding salt for principal '{}' and relying party '{}'", principalName,
+                            relyingPartyId);
+                    return s.getBytes();
+                } else {
+                    log.debug("Blocked generation of ID for principal '{}' for relying party '{}'",
+                            principalName, relyingPartyId);
+                    return null;
+                }
+            } else if (override.containsKey(WILDCARD_OVERRIDE)) {
+                final String s = override.get(WILDCARD_OVERRIDE);
+                if (s != null) {
+                    log.debug("Overriding salt for principal '{}' and relying party '{}'", principalName,
+                            relyingPartyId);
+                    return s.getBytes();
+                } else {
+                    log.debug("Blocked generation of ID for principal '{}' for relying party '{}'",
+                            principalName, relyingPartyId);
+                    return null;
+                }
+            }
+        }
+        
+        return salt;
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
index 1b12be0..f07fce8 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
@@ -36,6 +36,7 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import org.opensaml.core.OpenSAMLInitBaseTestCase;
 import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLException;
 import org.opensaml.saml.saml2.core.AuthnRequest;
 import org.opensaml.saml.saml2.core.NameID;
 import org.opensaml.saml.saml2.core.NameIDPolicy;
@@ -52,11 +53,15 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
 
     /** Value calculated using V2 version. DO NOT CHANGE WITHOUT TESTING AGAINST 2.0 */
     private static final String RESULT = "Vl6z6K70iLc4AuBoNeb59Dj1rGw=";
-    
+
+    private static final String RESULT2 = "kLyH1uEvYigEvg1ZLh/QXeW1VAs=";
+
     private static final String B32RESULT = "KZPLH2FO6SELOOAC4BUDLZXZ6Q4PLLDM";
 
     private static final byte salt[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
 
+    private static final String salt2 = "thisisaspecialsalt";
+
     public static final String INIT_FILE = "/net/shibboleth/idp/saml/impl/nameid/StoredIdStore.sql";
     public static final String DELETE_FILE = "/net/shibboleth/idp/saml/impl/nameid/DeleteStore.sql";
     
@@ -146,6 +151,72 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
                 Collections.singleton(new IdPAttribute("SOURCE")));
         Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
     }
+
+    @Test(expectedExceptions=SAMLException.class)
+    public void testRevoked() throws Exception {
+        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        strategy.setSalt(salt);
+        strategy.setExceptionMap(Collections.singletonMap("foo",
+                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, null)));
+        strategy.initialize();
+
+        generator.setPersistentIdGenerator(strategy);
+        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
+        generator.initialize();
+        
+        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
+        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
+        
+        final IdPAttribute source = new IdPAttribute("SOURCE");
+        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
+        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
+                Collections.singleton(source));
+        generator.generate(prc, NameID.PERSISTENT);
+    }
+    
+    @Test(expectedExceptions=SAMLException.class)
+    public void testRevokedWildcardRP() throws Exception {
+        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        strategy.setSalt(salt);
+        strategy.setExceptionMap(Collections.singletonMap("foo",
+                Collections.<String,String>singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE, null)));
+        strategy.initialize();
+
+        generator.setPersistentIdGenerator(strategy);
+        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
+        generator.initialize();
+        
+        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
+        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
+        
+        final IdPAttribute source = new IdPAttribute("SOURCE");
+        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
+        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
+                Collections.singleton(source));
+        generator.generate(prc, NameID.PERSISTENT);
+    }
+    
+    @Test(expectedExceptions=SAMLException.class)
+    public void testRevokedWildcardUser() throws Exception {
+        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        strategy.setSalt(salt);
+        strategy.setExceptionMap(Collections.singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE,
+                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, null)));
+        strategy.initialize();
+
+        generator.setPersistentIdGenerator(strategy);
+        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
+        generator.initialize();
+        
+        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
+        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
+        
+        final IdPAttribute source = new IdPAttribute("SOURCE");
+        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
+        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
+                Collections.singleton(source));
+        generator.generate(prc, NameID.PERSISTENT);
+    }
     
     @Test
     public void testComputedId() throws Exception {
@@ -171,6 +242,33 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
         Assert.assertEquals(id.getNameQualifier(), TestSources.IDP_ENTITY_ID);
         Assert.assertEquals(id.getSPNameQualifier(), TestSources.SP_ENTITY_ID);
     }
+    
+    @Test
+    public void testComputedIdOverride() throws Exception {
+        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        strategy.setSalt(salt);
+        strategy.setExceptionMap(Collections.singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE,
+                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, salt2)));
+        strategy.initialize();
+
+        generator.setPersistentIdGenerator(strategy);
+        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
+        generator.initialize();
+        
+        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
+        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
+        
+        final IdPAttribute source = new IdPAttribute("SOURCE");
+        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
+        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
+                Collections.singleton(source));
+        final NameID id = generator.generate(prc, NameID.PERSISTENT);
+        Assert.assertNotNull(id);
+        Assert.assertEquals(id.getValue(), RESULT2);
+        Assert.assertEquals(id.getFormat(), NameID.PERSISTENT);
+        Assert.assertEquals(id.getNameQualifier(), TestSources.IDP_ENTITY_ID);
+        Assert.assertEquals(id.getSPNameQualifier(), TestSources.SP_ENTITY_ID);
+    }
 
     @Test
     public void testBase32ComputedId() throws Exception {

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list