Skip to content

Commit

Permalink
Implement configuration of SAML keys
Browse files Browse the repository at this point in the history
[#142082611] https://www.pivotaltracker.com/story/show/142082611

Signed-off-by: Helen Chung <hchung@pivotal.io>
  • Loading branch information
fhanik authored and Helen Chung committed Apr 12, 2017
1 parent 31e5661 commit 8910581
Show file tree
Hide file tree
Showing 12 changed files with 449 additions and 75 deletions.
Expand Up @@ -14,15 +14,16 @@

package org.cloudfoundry.identity.uaa.zone;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.cloudfoundry.identity.uaa.saml.SamlKey;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.util.StringUtils.hasText;

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -152,15 +153,15 @@ public void setActiveKeyId(String activeKeyId) {
}

public Map<String, SamlKey> getKeys() {
return keys;
return Collections.unmodifiableMap(keys);
}

public void setKeys(Map<String, SamlKey> keys) {
this.keys = keys;
this.keys = new HashMap<>(keys);
}

@JsonIgnore
public void addActiveKey(String keyId, SamlKey key) {
public void addAndActivateKey(String keyId, SamlKey key) {
addKey(keyId, key);
this.activeKeyId = keyId;
}
Expand Down
Expand Up @@ -17,24 +17,23 @@
import org.cloudfoundry.identity.uaa.saml.SamlKey;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.util.ReflectionUtils;
import org.junit.rules.ExpectedException;

import java.lang.reflect.Field;
import java.security.cert.CertificateException;
import java.util.Map;

import static java.util.Collections.EMPTY_MAP;
import static org.cloudfoundry.identity.uaa.zone.SamlConfig.LEGACY_KEY_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;

public class SamlConfigTest {


@Rule
public ExpectedException exception = ExpectedException.none();

String oldJson =
"{\n" +
" \"assertionSigned\": true,\n" +
Expand Down Expand Up @@ -117,7 +116,7 @@ public void legacy_key_is_part_of_map() {
public void addActiveKey() {
SamlKey key = new SamlKey(privateKey, passphrase, certificate);
String keyId = "testKeyId";
config.addActiveKey(keyId, key);
config.addAndActivateKey(keyId, key);
Map<String, SamlKey> keys = config.getKeys();
assertNotNull(keys);
assertEquals(1, keys.size());
Expand Down Expand Up @@ -188,5 +187,24 @@ public void to_json_ignores_legacy_values() throws Exception {
assertEquals(certificate, config.getCertificate());
}

@Test
public void keys_are_not_modifiable() {
read_json(oldJson);
exception.expect(UnsupportedOperationException.class);
config.getKeys().clear();
}

@Test
public void can_clear_keys() {
read_json(oldJson);
assertEquals(1, config.getKeys().size());
assertNotNull(config.getActiveKeyId());
config.setKeys(EMPTY_MAP);
assertEquals(0, config.getKeys().size());
assertNull(config.getActiveKeyId());
}




}
Expand Up @@ -13,21 +13,18 @@
package org.cloudfoundry.identity.uaa.impl.config;

import org.cloudfoundry.identity.uaa.login.Prompt;
import org.cloudfoundry.identity.uaa.saml.SamlKey;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.BrandingInformation;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneValidator;
import org.cloudfoundry.identity.uaa.zone.InvalidIdentityZoneDetailsException;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.cloudfoundry.identity.uaa.zone.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Map;

import static java.util.Collections.EMPTY_MAP;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import static org.springframework.util.StringUtils.hasText;

public class IdentityZoneConfigurationBootstrap implements InitializingBean {
Expand All @@ -46,6 +43,10 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean {
private String samlSpPrivateKey;
private String samlSpPrivateKeyPassphrase;
private String samlSpCertificate;

private Map<String, Map<String, String>> samlKeys;
private String activeKeyId;

private boolean idpDiscoveryEnabled = false;

private boolean accountChooserEnabled;
Expand Down Expand Up @@ -74,6 +75,13 @@ public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException {
definition.setIdpDiscoveryEnabled(idpDiscoveryEnabled);
definition.setAccountChooserEnabled(accountChooserEnabled);

samlKeys = ofNullable(samlKeys).orElse(EMPTY_MAP);
for (Map.Entry<String, Map<String,String>> entry : samlKeys.entrySet()) {
SamlKey samlKey = new SamlKey(entry.getValue().get("key"), entry.getValue().get("passphrase"), entry.getValue().get("certificate"));
definition.getSamlConfig().addKey(entry.getKey(), samlKey);
}
definition.getSamlConfig().setActiveKeyId(this.activeKeyId);

if (selfServiceLinks!=null) {
String signup = selfServiceLinks.get("signup");
String passwd = selfServiceLinks.get("passwd");
Expand Down Expand Up @@ -108,6 +116,15 @@ public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException {
}


public IdentityZoneConfigurationBootstrap setSamlKeys(Map<String, Map<String, String>> samlKeys) {
this.samlKeys = samlKeys;
return this;
}

public IdentityZoneConfigurationBootstrap setActiveKeyId(String activeKeyId) {
this.activeKeyId = activeKeyId;
return this;
}

public void setTokenPolicy(TokenPolicy tokenPolicy) {
this.tokenPolicy = tokenPolicy;
Expand Down
Expand Up @@ -12,8 +12,8 @@
*******************************************************************************/
package org.cloudfoundry.identity.uaa.provider.saml;

import org.cloudfoundry.identity.uaa.saml.SamlKey;
import org.cloudfoundry.identity.uaa.util.KeyWithCert;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.SamlConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -25,7 +25,10 @@
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static java.util.Optional.ofNullable;

public final class SamlKeyManagerFactory {

Expand All @@ -34,30 +37,38 @@ public final class SamlKeyManagerFactory {
private SamlKeyManagerFactory() {}

public static KeyManager getKeyManager(SamlConfig config) {
return getKeyManager(config.getPrivateKey(), config.getPrivateKeyPassword(), config.getCertificate());
return getKeyManager(config.getKeys(), config.getActiveKeyId());
}

public static KeyManager getKeyManager(String key, String password, String certificate) {
if(!StringUtils.hasText(key)) return null;
private static KeyManager getKeyManager(Map<String, SamlKey> keys, String activeKeyId) {
SamlKey activeKey = keys.get(activeKeyId);

if (null == password) {
password = "";
if(activeKey == null || !StringUtils.hasText(activeKey.getKey())) {
return null;
}

try {
KeyWithCert keyWithCert = new KeyWithCert(key, password, certificate);
X509Certificate cert = keyWithCert.getCert();
KeyPair pkey = keyWithCert.getPkey();

try {
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
String alias = "service-provider-cert-" + IdentityZoneHolder.get().getId();
keystore.setCertificateEntry(alias, cert);
keystore.setKeyEntry(alias, pkey.getPrivate(), password.toCharArray(),
new Certificate[] { cert });
Map<String, String> aliasPasswordMap = new HashMap<>();
for (Map.Entry<String, SamlKey> entry : keys.entrySet()) {


String password = ofNullable(entry.getValue().getPassphrase()).orElse("");
KeyWithCert keyWithCert = new KeyWithCert(entry.getValue().getKey(), password, entry.getValue().getCertificate());
X509Certificate cert = keyWithCert.getCert();
KeyPair pkey = keyWithCert.getPkey();


String alias = entry.getKey();
keystore.setCertificateEntry(alias, cert);
keystore.setKeyEntry(alias, pkey.getPrivate(), password.toCharArray(), new Certificate[]{cert});
aliasPasswordMap.put(alias, password);
}


JKSKeyManager keyManager = new JKSKeyManager(keystore, Collections.singletonMap(alias, password),
alias);
JKSKeyManager keyManager = new JKSKeyManager(keystore, aliasPasswordMap, activeKeyId);

if (null == keyManager) {
throw new IllegalArgumentException(
Expand Down
@@ -1,11 +1,3 @@
package org.cloudfoundry.identity.uaa.zone;

import org.cloudfoundry.identity.uaa.util.KeyWithCert;
import org.springframework.util.StringUtils;

import java.security.GeneralSecurityException;
import java.util.Map;

/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
Expand All @@ -18,6 +10,15 @@
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.zone;

import org.cloudfoundry.identity.uaa.util.KeyWithCert;
import org.springframework.util.StringUtils;

import java.security.GeneralSecurityException;
import java.util.Map;


public class GeneralIdentityZoneConfigurationValidator implements IdentityZoneConfigurationValidator {
@Override
public IdentityZoneConfiguration validate(IdentityZoneConfiguration config, Mode mode) throws InvalidIdentityZoneConfigurationException {
Expand Down
Expand Up @@ -61,6 +61,34 @@ public void configureProvisioning() {
bootstrap = new IdentityZoneConfigurationBootstrap(provisioning);
}

@Test
public void test_multiple_keys() throws InvalidIdentityZoneDetailsException {
bootstrap.setSamlSpPrivateKey(SamlTestUtils.PROVIDER_PRIVATE_KEY);
bootstrap.setSamlSpCertificate(SamlTestUtils.PROVIDER_CERTIFICATE);
bootstrap.setSamlSpPrivateKeyPassphrase(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD);
Map<String, Map<String, String>> keys = new HashMap<>();
Map<String, String> key1 = new HashMap<>();
key1.put("key", SamlTestUtils.PROVIDER_PRIVATE_KEY);
key1.put("passphrase", SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD);
key1.put("certificate", SamlTestUtils.PROVIDER_CERTIFICATE);
keys.put("key1", key1);
bootstrap.setActiveKeyId("key1");
bootstrap.setSamlKeys(keys);
bootstrap.afterPropertiesSet();
IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaa().getId());
SamlConfig config = uaa.getConfig().getSamlConfig();
assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY, config.getPrivateKey());
assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD, config.getPrivateKeyPassword());
assertEquals(SamlTestUtils.PROVIDER_CERTIFICATE, config.getCertificate());

assertEquals("key1", config.getActiveKeyId());
assertEquals(2, config.getKeys().size());

assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY, config.getKeys().get("key1").getKey());
assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD, config.getKeys().get("key1").getPassphrase());
assertEquals(SamlTestUtils.PROVIDER_CERTIFICATE, config.getKeys().get("key1").getCertificate());
}

@Test
public void testDefaultSamlKeys() throws Exception {
bootstrap.setSamlSpPrivateKey(SamlTestUtils.PROVIDER_PRIVATE_KEY);
Expand Down

0 comments on commit 8910581

Please sign in to comment.