[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