SAML: Support multiple decryption keys for SP (elastic/x-pack-elasticsearch#4289)

- Changes in build SAML SP metadata to support multiple
  encryption keys.
- Changes in Saml metadata command to support the use of
   protected keystores.
- Changes to export and set proper usage type in key
   descriptors of SP saml metadata XML.
- Changes in SAML realm to create chaining key info
   credential resolver backed by Collection of encryption
   keys as per SP configuration.
- Unit tests and test enhancements

relates elastic/x-pack-elasticsearch#3980,elastic/x-pack-elasticsearch#4293

Original commit: elastic/x-pack-elasticsearch@e02ebcc9e6
This commit is contained in:
Yogesh Gaikwad 2018-04-16 17:17:39 +10:00 committed by GitHub
parent 0e89f07c3f
commit 1701934dd4
19 changed files with 623 additions and 129 deletions

View File

@ -187,11 +187,14 @@ Encryption can be configured using the following settings.
Defaults to "PKCS12" if the keystore path ends in ".p12", ".pfx" or
"pkcs12", otherwise uses "jks"
| `encryption.keystore.alias` | no | Specifies the alias of the key within the keystore that should be
used for SAML message decryption. Must be specified if the keystore
contains more than one private key.
used for SAML message decryption. If not specified, all compatible
key pairs from the keystore will be considered as candidate keys
for decryption.
| `encryption.keystore.secure_password` | no | ({ref}/secure-settings.html[Secure]) The password to the keystore.
| `encryption.keystore.secure_key_password` | no | ({ref}/secure-settings.html[Secure])
The password for the key in the keystore.
The password for the key in the keystore. Only a single password is
supported. If you are using multiple decryption keys, then they
cannot have individual passwords.
|=======================
===== SAML Realm SSL Settings

View File

@ -801,14 +801,16 @@ ends in ".p12", ".pfx" or "pkcs12", otherwise uses "jks".
`encryption.keystore.alias`::
Specifies the alias of the key within the keystore (`encryption.keystore.path`)
that should be used for SAML message decryption. Must be specified if the
keystore contains more than one private key.
that should be used for SAML message decryption. If not specified, all compatible
key pairs from the keystore will be considered as candidate keys for decryption.
`encryption.keystore.secure_password` (<<secure-settings,Secure>>)::
The password to the keystore (`encryption.keystore.path`).
`encryption.keystore.secure_key_password` (<<secure-settings,Secure>>)::
The password for the key in the keystore (`encryption.keystore.path`) .
The password for the key in the keystore (`encryption.keystore.path`). Only a
single password is supported. If you are using multiple decryption keys, then
they cannot have individual passwords.
`ssl.key`::
If retrieving IDP metadata via https (see `idp.metadata.path`), specifies the

View File

@ -198,7 +198,7 @@ class SamlAuthenticator extends SamlRequestHandler {
return decrypter.decrypt(encrypted);
} catch (DecryptionException e) {
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML assertion [{}] with [{}]",
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredentials())), e);
throw samlException("Failed to decrypt SAML assertion " + text(encrypted, 32), e);
}
}

View File

@ -172,7 +172,7 @@ public class SamlLogoutRequestHandler extends SamlRequestHandler {
return decrypter.decrypt(encrypted);
} catch (DecryptionException e) {
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML EncryptedID [{}] with [{}]",
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredentials())), e);
throw samlException("Failed to decrypt SAML EncryptedID " + text(encrypted, 32), e);
}
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
@ -15,6 +16,7 @@ import java.security.Key;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
@ -41,6 +43,7 @@ import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.LocaleUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -87,12 +90,21 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
private final OptionSpec<String> signingCertPathSpec;
private final OptionSpec<String> signingKeyPathSpec;
private final OptionSpec<String> keyPasswordSpec;
private final CheckedFunction<Environment, KeyStoreWrapper, Exception> keyStoreFunction;
private KeyStoreWrapper keyStoreWrapper;
public static void main(String[] args) throws Exception {
new SamlMetadataCommand().main(args, Terminal.DEFAULT);
}
public SamlMetadataCommand() {
this((environment) -> {
KeyStoreWrapper ksWrapper = KeyStoreWrapper.load(environment.configFile());
return ksWrapper;
});
}
public SamlMetadataCommand(CheckedFunction<Environment, KeyStoreWrapper, Exception> keyStoreFunction) {
super("Generate Service Provider Metadata for a SAML realm");
outputPathSpec = parser.accepts("out", "path of the xml file that should be generated").withRequiredArg();
batchSpec = parser.accepts("batch", "Do not prompt");
@ -118,6 +130,15 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
.withRequiredArg();
keyPasswordSpec = parser.accepts("signing-key-password", "password for an existing signing private key or keypair")
.withOptionalArg();
this.keyStoreFunction = keyStoreFunction;
}
@Override
public void close() throws IOException {
super.close();
if (keyStoreWrapper != null) {
keyStoreWrapper.close();
}
}
@Override
@ -149,7 +170,7 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(locale, spConfig.getEntityId())
.assertionConsumerServiceUrl(spConfig.getAscUrl())
.singleLogoutServiceUrl(spConfig.getLogoutUrl())
.encryptionCredential(spConfig.getEncryptionCredential())
.encryptionCredentials(spConfig.getEncryptionCredentials())
.signingCredential(spConfig.getSigningConfiguration().getCredential())
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
.nameIdFormat(SamlRealmSettings.NAMEID_FORMAT.get(realm.settings()))
@ -392,17 +413,34 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
return new TreeSet<>(strings);
}
private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment env) throws UserException {
final Map<String, Settings> realms = RealmSettings.getRealmSettings(env.settings());
private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment env) throws UserException, IOException, Exception {
keyStoreWrapper = keyStoreFunction.apply(env);
final Settings settings;
if (keyStoreWrapper != null) {
// TODO: We currently do not support keystore passwords
keyStoreWrapper.decrypt(new char[0]);
final Settings.Builder settingsBuilder = Settings.builder();
settingsBuilder.put(env.settings(), true);
if (settingsBuilder.getSecureSettings() == null) {
settingsBuilder.setSecureSettings(keyStoreWrapper);
}
settings = settingsBuilder.build();
} else {
settings = env.settings();
}
final Map<String, Settings> realms = RealmSettings.getRealmSettings(settings);
if (options.has(realmSpec)) {
final String name = realmSpec.value(options);
final Settings settings = realms.get(name);
if (settings == null) {
final Settings realmSettings = realms.get(name);
if (realmSettings == null) {
throw new UserException(ExitCodes.CONFIG, "No such realm '" + name + "' defined in " + env.configFile());
}
final String realmType = getRealmType(settings);
final String realmType = getRealmType(realmSettings);
if (isSamlRealm(realmType)) {
return buildRealm(name, settings, env);
return buildRealm(name, realmSettings, env);
} else {
throw new UserException(ExitCodes.CONFIG, "Realm '" + name + "' is not a SAML realm (is '" + realmType + "')");
}

View File

@ -279,13 +279,14 @@ public final class SamlRealm extends Realm implements Releasable {
// Package-private for testing
static X509Credential buildEncryptionCredential(RealmConfig config) throws IOException, GeneralSecurityException {
return buildCredential(config, ENCRYPTION_SETTINGS, ENCRYPTION_KEY_ALIAS);
static List<X509Credential> buildEncryptionCredential(RealmConfig config) throws IOException, GeneralSecurityException {
return buildCredential(config, ENCRYPTION_SETTINGS, ENCRYPTION_KEY_ALIAS, true);
}
static SigningConfiguration buildSigningConfiguration(RealmConfig config) throws IOException, GeneralSecurityException {
final X509Credential credential = buildCredential(config, SIGNING_SETTINGS, SIGNING_KEY_ALIAS);
if (credential == null) {
final List<X509Credential> credentials = buildCredential(config, SIGNING_SETTINGS, SIGNING_KEY_ALIAS, false);
if (credentials == null || credentials.isEmpty()) {
if (SIGNING_MESSAGE_TYPES.exists(config.settings())) {
throw new IllegalArgumentException("The setting [" + RealmSettings.getFullSettingKey(config, SIGNING_MESSAGE_TYPES)
+ "] cannot be specified if there are no signing credentials");
@ -294,21 +295,21 @@ public final class SamlRealm extends Realm implements Releasable {
}
} else {
final List<String> types = SIGNING_MESSAGE_TYPES.get(config.settings());
return new SigningConfiguration(Sets.newHashSet(types), credential);
return new SigningConfiguration(Sets.newHashSet(types), credentials.get(0));
}
}
private static X509Credential buildCredential(RealmConfig config, X509KeyPairSettings keyPairSettings,
Setting<String> aliasSetting) {
private static List<X509Credential> buildCredential(RealmConfig config, X509KeyPairSettings keyPairSettings,
Setting<String> aliasSetting, final boolean allowMultiple) {
final X509KeyManager keyManager = CertUtils.getKeyManager(keyPairSettings, config.settings(), null, config.env());
if (keyManager == null) {
return null;
}
String alias = aliasSetting.get(config.settings());
if (Strings.isNullOrEmpty(alias)) {
final Set<String> aliases = new HashSet<>();
final String configuredAlias = aliasSetting.get(config.settings());
if (Strings.isNullOrEmpty(configuredAlias)) {
final Set<String> aliases = new HashSet<>();
final String[] serverAliases = keyManager.getServerAliases("RSA", null);
if (serverAliases != null) {
aliases.addAll(Arrays.asList(serverAliases));
@ -318,35 +319,37 @@ public final class SamlRealm extends Realm implements Releasable {
throw new IllegalArgumentException(
"The configured key store for " + RealmSettings.getFullSettingKey(config, keyPairSettings.getPrefix())
+ " does not contain any RSA key pairs");
} else if (aliases.size() > 1) {
/*
* TODO bizybot : We need to fix this, for encryption we want to support
* multiple keys Refer: #3980
*/
} else if (allowMultiple == false && aliases.size() > 1) {
throw new IllegalArgumentException(
"The configured key store for " + RealmSettings.getFullSettingKey(config, keyPairSettings.getPrefix())
+ " has multiple keys but no alias has been specified (from setting "
+ RealmSettings.getFullSettingKey(config, aliasSetting) + ")");
} else {
alias = aliases.iterator().next();
}
} else {
aliases.add(configuredAlias);
}
if (keyManager.getPrivateKey(alias) == null) {
throw new IllegalArgumentException(
"The configured key store for " + RealmSettings.getFullSettingKey(config, keyPairSettings.getPrefix())
+ " does not have a certificate key pair associated with alias [" + alias + "] " + "(from setting "
+ RealmSettings.getFullSettingKey(config, aliasSetting) + ")");
final List<X509Credential> credentials = new ArrayList<>();
for (String alias : aliases) {
if (keyManager.getPrivateKey(alias) == null) {
throw new IllegalArgumentException(
"The configured key store for " + RealmSettings.getFullSettingKey(config, keyPairSettings.getPrefix())
+ " does not have a key associated with alias [" + alias + "] "
+ ((Strings.isNullOrEmpty(configuredAlias) == false)
? "(from setting " + RealmSettings.getFullSettingKey(config, aliasSetting) + ")"
: ""));
}
final String keyType = keyManager.getPrivateKey(alias).getAlgorithm();
if (keyType.equals("RSA") == false) {
throw new IllegalArgumentException("The key associated with alias [" + alias + "] " + "(from setting "
+ RealmSettings.getFullSettingKey(config, aliasSetting) + ") uses unsupported key algorithm type [" + keyType
+ "], only RSA is supported");
}
credentials.add(new X509KeyManagerX509CredentialAdapter(keyManager, alias));
}
final String keyType = keyManager.getPrivateKey(alias).getAlgorithm();
if (keyType.equals("RSA") == false) {
throw new IllegalArgumentException("The key associated with alias [" + alias + "] " + "(from setting "
+ RealmSettings.getFullSettingKey(config, aliasSetting) + ") uses unsupported key algorithm type [" + keyType
+ "], only RSA is supported");
}
return new X509KeyManagerX509CredentialAdapter(keyManager, alias);
return credentials;
}
public static List<SamlRealm> findSamlRealms(Realms realms, String realmName, String acsUrl) {

View File

@ -5,20 +5,6 @@
*/
package org.elasticsearch.xpack.security.authc.saml;
import javax.xml.parsers.DocumentBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchSecurityException;
@ -44,7 +30,14 @@ import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.SimpleKeyInfoReferenceEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.ChainingKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.LocalKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.KeyInfoReferenceProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
@ -52,6 +45,24 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getUnmarshallerFactory;
@ -90,17 +101,26 @@ public class SamlRequestHandler {
this.sp = sp;
this.maxSkew = maxSkew;
this.unmarshallerFactory = getUnmarshallerFactory();
if (sp.getEncryptionCredential() == null) {
if (sp.getEncryptionCredentials().isEmpty()) {
this.decrypter = null;
} else {
StaticKeyInfoCredentialResolver keyInfoCredentialResolver = new StaticKeyInfoCredentialResolver(sp.getEncryptionCredential());
final EncryptedKeyResolver keyResolver = new ChainingEncryptedKeyResolver(Arrays.asList(
new InlineEncryptedKeyResolver(), new SimpleRetrievalMethodEncryptedKeyResolver(),
new SimpleKeyInfoReferenceEncryptedKeyResolver()));
this.decrypter = new Decrypter(null, keyInfoCredentialResolver, keyResolver);
this.decrypter = new Decrypter(null, createResolverForEncryptionKeys(), createResolverForEncryptedKeyElements());
}
}
private KeyInfoCredentialResolver createResolverForEncryptionKeys() {
final CollectionKeyInfoCredentialResolver collectionKeyInfoCredentialResolver =
new CollectionKeyInfoCredentialResolver(Collections.unmodifiableCollection(sp.getEncryptionCredentials()));
final LocalKeyInfoCredentialResolver localKeyInfoCredentialResolver =
new LocalKeyInfoCredentialResolver(Arrays.asList(new InlineX509DataProvider(), new KeyInfoReferenceProvider(),
new RSAKeyValueProvider(), new DEREncodedKeyValueProvider()), collectionKeyInfoCredentialResolver);
return new ChainingKeyInfoCredentialResolver(Arrays.asList(localKeyInfoCredentialResolver, collectionKeyInfoCredentialResolver));
}
private EncryptedKeyResolver createResolverForEncryptedKeyElements() {
return new ChainingEncryptedKeyResolver(Arrays.asList(new InlineEncryptedKeyResolver(),
new SimpleRetrievalMethodEncryptedKeyResolver(), new SimpleKeyInfoReferenceEncryptedKeyResolver()));
}
protected SpConfiguration getSpConfiguration() {
return sp;
@ -111,6 +131,10 @@ public class SamlRequestHandler {
certificate.getSerialNumber().toString(16) + "}";
}
protected String describe(Collection<X509Credential> credentials) {
return credentials.stream().map(credential -> describe(credential.getEntityCertificate())).collect(Collectors.joining(","));
}
void validateSignature(Signature signature) {
final String signatureText = text(signature, 32);
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();

View File

@ -53,12 +53,14 @@ import org.opensaml.xmlsec.signature.impl.KeyInfoBuilder;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Constructs SAML Metadata to describe a <em>Service Provider</em>.
@ -77,7 +79,7 @@ public class SamlSpMetadataBuilder {
private String singleLogoutServiceUrl;
private Boolean authnRequestsSigned;
private X509Certificate signingCertificate;
private X509Certificate encryptionCertificate;
private List<X509Certificate> encryptionCertificates = new ArrayList<>();
private OrganizationInfo organization;
/**
@ -163,16 +165,19 @@ public class SamlSpMetadataBuilder {
/**
* The certificate that should be used to send encrypted data to the service provider.
*/
public SamlSpMetadataBuilder encryptionCertificate(X509Certificate encryptionCertificate) {
this.encryptionCertificate = encryptionCertificate;
public SamlSpMetadataBuilder encryptionCertificates(Collection<X509Certificate> encryptionCertificates) {
if (encryptionCertificates != null) {
this.encryptionCertificates.addAll(encryptionCertificates);
}
return this;
}
/**
* The certificate credential that should be used to send encrypted data to the service provider.
*/
public SamlSpMetadataBuilder encryptionCredential(X509Credential credential) {
return encryptionCertificate(credential == null ? null : credential.getEntityCertificate());
public SamlSpMetadataBuilder encryptionCredentials(Collection<X509Credential> credentials) {
return encryptionCertificates(credentials == null ? Collections.emptyList()
: credentials.stream().map(credential -> credential.getEntityCertificate()).collect(Collectors.toList()));
}
/**
@ -298,18 +303,18 @@ public class SamlSpMetadataBuilder {
}
private List<? extends KeyDescriptor> buildKeyDescriptors() throws CertificateEncodingException {
if (encryptionCertificate == null && signingCertificate == null) {
if (encryptionCertificates.isEmpty() && signingCertificate == null) {
return Collections.emptyList();
}
if (Objects.equals(encryptionCertificate, signingCertificate)) {
return Collections.singletonList(buildKeyDescriptor(encryptionCertificate, UsageType.UNSPECIFIED));
if (encryptionCertificates.size() == 1 && Objects.equals(encryptionCertificates.get(0), signingCertificate)) {
return Collections.singletonList(buildKeyDescriptor(encryptionCertificates.get(0), UsageType.UNSPECIFIED));
}
List<KeyDescriptor> keys = new ArrayList<>();
if (signingCertificate != null) {
keys.add(buildKeyDescriptor(signingCertificate, UsageType.SIGNING));
}
if (encryptionCertificate != null) {
keys.add(buildKeyDescriptor(encryptionCertificate, UsageType.SIGNING));
for( X509Certificate encryptionCertificate : encryptionCertificates) {
keys.add(buildKeyDescriptor(encryptionCertificate, UsageType.ENCRYPTION));
}
return keys;
}

View File

@ -8,6 +8,9 @@ package org.elasticsearch.xpack.security.authc.saml;
import org.elasticsearch.common.Nullable;
import org.opensaml.security.x509.X509Credential;
import java.util.Collections;
import java.util.List;
/**
* A simple container class that holds all configuration related to a SAML Service Provider (SP).
*/
@ -17,15 +20,19 @@ public class SpConfiguration {
private final String ascUrl;
private final String logoutUrl;
private final SigningConfiguration signingConfiguration;
private final X509Credential encryptionCredential;
private final List<X509Credential> encryptionCredentials;
public SpConfiguration(String entityId, String ascUrl, String logoutUrl,
SigningConfiguration signingConfiguration, @Nullable X509Credential encryptionCredential) {
public SpConfiguration(final String entityId, final String ascUrl, final String logoutUrl,
final SigningConfiguration signingConfiguration, @Nullable final List<X509Credential> encryptionCredential) {
this.entityId = entityId;
this.ascUrl = ascUrl;
this.logoutUrl = logoutUrl;
this.signingConfiguration = signingConfiguration;
this.encryptionCredential = encryptionCredential;
if (encryptionCredential != null) {
this.encryptionCredentials = Collections.unmodifiableList(encryptionCredential);
} else {
this.encryptionCredentials = Collections.<X509Credential>emptyList();
}
}
/**
@ -43,8 +50,8 @@ public class SpConfiguration {
return logoutUrl;
}
X509Credential getEncryptionCredential() {
return encryptionCredential;
List<X509Credential> getEncryptionCredentials() {
return encryptionCredentials;
}
SigningConfiguration getSigningConfiguration() {

View File

@ -34,6 +34,7 @@ import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -72,6 +73,7 @@ import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -79,6 +81,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@ -111,7 +114,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
private static Tuple<X509Certificate, PrivateKey> idpSigningCertificatePair;
private static Tuple<X509Certificate, PrivateKey> spSigningCertificatePair;
private static Tuple<X509Certificate, PrivateKey> spEncryptionCertificatePair;
private static List<Tuple<X509Certificate, PrivateKey>> spEncryptionCertificatePairs;
private static List<Integer> supportedAesKeyLengths;
private static List<String> supportedAesTransformations;
@ -152,7 +155,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
public static void initCredentials() throws Exception {
idpSigningCertificatePair = createKeyPair(randomSigningAlgorithm());
spSigningCertificatePair = createKeyPair(randomSigningAlgorithm());
spEncryptionCertificatePair = createKeyPair("RSA");
spEncryptionCertificatePairs = Arrays.asList(createKeyPair("RSA"), createKeyPair("RSA"));
}
private static String randomSigningAlgorithm() {
@ -163,7 +166,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
public static void cleanup() {
idpSigningCertificatePair = null;
spSigningCertificatePair = null;
spEncryptionCertificatePair = null;
spEncryptionCertificatePairs = null;
supportedAesKeyLengths = null;
supportedAesTransformations = null;
}
@ -172,7 +175,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
public void setupAuthenticator() throws Exception {
this.clock = new ClockMock();
this.maxSkew = TimeValue.timeValueMinutes(1);
this.authenticator = buildAuthenticator(() -> singletonList(buildOpenSamlCredential(idpSigningCertificatePair)));
this.authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
this.requestId = randomId();
}
@ -181,10 +184,11 @@ public class SamlAuthenticatorTests extends SamlTestCase {
final Settings realmSettings = Settings.EMPTY;
final IdpConfiguration idp = new IdpConfiguration(IDP_ENTITY_ID, credentials);
final SigningConfiguration signingConfiguration =
new SigningConfiguration(Collections.singleton("*"), buildOpenSamlCredential(spSigningCertificatePair));
final SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, null, signingConfiguration,
buildOpenSamlCredential(spEncryptionCertificatePair));
final SigningConfiguration signingConfiguration = new SigningConfiguration(Collections.singleton("*"),
(X509Credential) buildOpenSamlCredential(spSigningCertificatePair).get(0));
final List<X509Credential> spEncryptionCredentials = buildOpenSamlCredential(spEncryptionCertificatePairs).stream()
.map((cred) -> (X509Credential) cred).collect(Collectors.<X509Credential>toList());
final SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, null, signingConfiguration, spEncryptionCredentials);
final Environment env = TestEnvironment.newEnvironment(globalSettings);
return new SamlAuthenticator(
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),
@ -293,7 +297,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
final Instant now = clock.instant();
final String xml = getSimpleResponse(now);
final String encrypted = encryptAssertions(xml, spEncryptionCertificatePair);
final String encrypted = encryptAssertions(xml, randomFrom(spEncryptionCertificatePairs));
assertThat(encrypted, not(equalTo(xml)));
final String signed = signDoc(encrypted);
@ -329,7 +333,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
});
assertThat(signed, not(equalTo(xml)));
final String encrypted = encryptAssertions(signed, spEncryptionCertificatePair);
final String encrypted = encryptAssertions(signed, randomFrom(spEncryptionCertificatePairs));
assertThat(encrypted, not(equalTo(signed)));
final SamlToken token = token(encrypted);
@ -348,7 +352,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
final Instant now = clock.instant();
final String xml = getSimpleResponse(now);
final String encrypted = encryptAttributes(xml, spEncryptionCertificatePair);
final String encrypted = encryptAttributes(xml, randomFrom(spEncryptionCertificatePairs));
assertThat(encrypted, not(equalTo(xml)));
final String signed = signer.transform(encrypted, idpSigningCertificatePair);
@ -1011,7 +1015,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
for (int i = 0; i < numberOfKeys; i++) {
final Tuple<X509Certificate, PrivateKey> key = createKeyPair(randomSigningAlgorithm());
keys.add(key);
credentials.add(buildOpenSamlCredential(key));
credentials.addAll(buildOpenSamlCredential(key));
}
this.authenticator = buildAuthenticator(() -> credentials);
final CryptoTransform signer = randomBoolean() ? this::signDoc : this::signAssertions;
@ -1092,6 +1096,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
}
}
final Tuple<X509Certificate, PrivateKey> spEncryptionCertificatePair = randomFrom(spEncryptionCertificatePairs);
final Response encryptedAssertion = toResponse(encryptAssertions(xml, spEncryptionCertificatePair));
// Expect EncryptedAssertion
assertThat(encryptedAssertion.getAssertions(), iterableWithSize(0));
@ -1588,7 +1593,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
assertThat(exception.getMessage(), containsString("SAML Signature"));
assertThat(exception.getMessage(), containsString("could not be validated"));
//Restore the authenticator with credentials for the rest of the test cases
authenticator = buildAuthenticator(() -> singletonList(buildOpenSamlCredential(idpSigningCertificatePair)));
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
}
public void testFailureWhenIdPCredentialsAreNull() throws Exception {
@ -1600,7 +1605,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
assertThat(exception.getMessage(), containsString("SAML Signature"));
assertThat(exception.getMessage(), containsString("could not be validated"));
//Restore the authenticator with credentials for the rest of the test cases
authenticator = buildAuthenticator(() -> singletonList(buildOpenSamlCredential(idpSigningCertificatePair)));
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
}
private interface CryptoTransform {

View File

@ -11,6 +11,7 @@ import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.util.Arrays;
import java.util.Collections;
import org.elasticsearch.ElasticsearchSecurityException;
@ -47,7 +48,7 @@ public class SamlLogoutRequestHandlerTests extends SamlTestCase {
@BeforeClass
public static void setupCredential() throws Exception {
credential = buildOpenSamlCredential(createKeyPair());
credential = (X509Credential)buildOpenSamlCredential(createKeyPair()).get(0);
}
@AfterClass
@ -209,10 +210,10 @@ public class SamlLogoutRequestHandlerTests extends SamlTestCase {
final Settings realmSettings = Settings.EMPTY;
final IdpConfiguration idp = new IdpConfiguration(IDP_ENTITY_ID, () -> Collections.singletonList(credential));
final X509Credential spCredential = buildOpenSamlCredential(createKeyPair());
final X509Credential spCredential = (X509Credential)buildOpenSamlCredential(createKeyPair()).get(0);
final SigningConfiguration signingConfiguration = new SigningConfiguration(Collections.singleton("*"), spCredential);
final SpConfiguration sp = new SpConfiguration("https://sp.test/", "https://sp.test/saml/asc", LOGOUT_URL,
signingConfiguration, spCredential);
signingConfiguration, Arrays.asList(spCredential));
final Environment env = TestEnvironment.newEnvironment(globalSettings);
return new SamlLogoutRequestHandler(
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),

View File

@ -6,8 +6,13 @@
package org.elasticsearch.xpack.security.authc.saml;
import joptsimple.OptionSet;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
@ -29,13 +34,19 @@ import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.w3c.dom.Element;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString;
@ -45,19 +56,25 @@ import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SamlMetadataCommandTests extends SamlTestCase {
private KeyStoreWrapper keyStore;
@Before
public void setup() throws Exception {
SamlUtils.initialize(logger);
this.keyStore = mock(KeyStoreWrapper.class);
when(keyStore.isLoaded()).thenReturn(true);
}
public void testDefaultOptions() throws Exception {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[0]);
final boolean useSigningCredentials = randomBoolean();
@ -144,7 +161,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
.build();
final Environment env = TestEnvironment.newEnvironment(settings);
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[0]);
final MockTerminal terminal = new MockTerminal();
@ -168,7 +185,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
.build();
final Environment env = TestEnvironment.newEnvironment(settings);
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> keyStore);
final OptionSet options = command.getParser().parse(new String[] {
"-realm", "saml_b"
});
@ -198,7 +215,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
.build();
final Environment env = TestEnvironment.newEnvironment(settings);
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[] {
"-attribute", "urn:oid:0.9.2342.19200300.100.1.3",
"-attribute", "groups"
@ -251,7 +268,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
.build();
final Environment env = TestEnvironment.newEnvironment(settings);
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[] {
"-attribute", "urn:oid:0.9.2342.19200300.100.1.3",
"-batch"
@ -282,7 +299,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final Path p12Path = getDataPath("saml.p12");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[]{
"-signing-bundle", p12Path.toString()
});
@ -341,7 +358,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final Path p12Path = getDataPath("saml_with_password.p12");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[]{
"-signing-bundle", p12Path.toString(),
"-signing-key-password", "saml"
@ -378,7 +395,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final Path p12Path = getDataPath("saml_with_password.p12");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[]{
"-signing-bundle", p12Path.toString(),
"-signing-key-password", "wrong_password"
@ -414,7 +431,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[]{
"-signing-cert", certPath.toString(),
"-signing-key", keyPath.toString()
@ -453,7 +470,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> keyStore);
final OptionSet options = command.getParser().parse(new String[]{
"-signing-cert", certPath.toString(),
"-signing-key", signingKeyPath.toString(),
@ -494,7 +511,7 @@ public class SamlMetadataCommandTests extends SamlTestCase {
final Path certPath = getDataPath("saml.crt");
final Path keyPath = getDataPath("saml.key");
final SamlMetadataCommand command = new SamlMetadataCommand();
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> randomFrom(keyStore, null));
final OptionSet options = command.getParser().parse(new String[]{
"-signing-cert", certPath.toString(),
"-signing-key", signingKeyPath.toString()
@ -529,6 +546,141 @@ public class SamlMetadataCommandTests extends SamlTestCase {
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
}
public void testDefaultOptionsWithSigningAndMultipleEncryptionKeys() throws Exception {
final Path dir = createTempDir();
final Path ksEncryptionFile = dir.resolve("saml-encryption.p12");
final Tuple<java.security.cert.X509Certificate, PrivateKey> certEncKeyPair1 = createKeyPair("RSA");
final Tuple<java.security.cert.X509Certificate, PrivateKey> certEncKeyPair2 = createKeyPair("RSA");
final KeyStore ksEncrypt = KeyStore.getInstance("PKCS12");
ksEncrypt.load(null);
ksEncrypt.setKeyEntry(getAliasName(certEncKeyPair1), certEncKeyPair1.v2(), "key-password".toCharArray(),
new Certificate[] { certEncKeyPair1.v1() });
ksEncrypt.setKeyEntry(getAliasName(certEncKeyPair2), certEncKeyPair2.v2(), "key-password".toCharArray(),
new Certificate[] { certEncKeyPair2.v1() });
try (OutputStream out = Files.newOutputStream(ksEncryptionFile)) {
ksEncrypt.store(out, "ks-password".toCharArray());
}
final Path ksSigningFile = dir.resolve("saml-signing.p12");
final Tuple<java.security.cert.X509Certificate, PrivateKey> certKeyPairSign = createKeyPair("RSA");
final KeyStore ksSign = KeyStore.getInstance("PKCS12");
ksSign.load(null);
ksSign.setKeyEntry(getAliasName(certKeyPairSign), certKeyPairSign.v2(), "key-password".toCharArray(),
new Certificate[] { certKeyPairSign.v1() });
try (OutputStream out = Files.newOutputStream(ksSigningFile)) {
ksSign.store(out, "ks-password".toCharArray());
}
final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(RealmSettings.PREFIX + "my_saml.signing.keystore.secure_password", "ks-password");
secureSettings.setString(RealmSettings.PREFIX + "my_saml.signing.keystore.secure_key_password", "key-password");
secureSettings.setString(RealmSettings.PREFIX + "my_saml.encryption.keystore.secure_password", "ks-password");
secureSettings.setString(RealmSettings.PREFIX + "my_saml.encryption.keystore.secure_key_password", "key-password");
final SamlMetadataCommand command = new SamlMetadataCommand((e) -> keyStore);
final OptionSet options = command.getParser().parse(new String[0]);
final boolean useSigningCredentials = randomBoolean();
final boolean useEncryptionCredentials = randomBoolean();
final Settings.Builder settingsBuilder = Settings.builder().put("path.home", dir).put(RealmSettings.PREFIX + "my_saml.type", "saml")
.put(RealmSettings.PREFIX + "my_saml.order", 1).put(RealmSettings.PREFIX + "my_saml.idp.entity_id", "https://okta.my.corp/")
.put(RealmSettings.PREFIX + "my_saml.sp.entity_id", "https://kibana.my.corp/")
.put(RealmSettings.PREFIX + "my_saml.sp.acs", "https://kibana.my.corp/saml/login")
.put(RealmSettings.PREFIX + "my_saml.sp.logout", "https://kibana.my.corp/saml/logout")
.put(RealmSettings.PREFIX + "my_saml.attributes.principal", "urn:oid:0.9.2342.19200300.100.1.1");
settingsBuilder.setSecureSettings(secureSettings);
if (useSigningCredentials) {
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.keystore.path", ksSigningFile.toString());
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.keystore.type", "PKCS12");
}
if (useEncryptionCredentials) {
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.encryption.keystore.path", ksEncryptionFile.toString());
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.encryption.keystore.type", "PKCS12");
}
final Settings settings = settingsBuilder.build();
final Environment env = TestEnvironment.newEnvironment(settings);
final MockTerminal terminal = new MockTerminal();
// What is the friendly name for "principal" attribute
// "urn:oid:0.9.2342.19200300.100.1.1" [default: principal]
terminal.addTextInput("");
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
assertThat(descriptor, notNullValue());
assertThat(descriptor.getEntityID(), equalTo("https://kibana.my.corp/"));
assertThat(descriptor.getRoleDescriptors(), iterableWithSize(1));
assertThat(descriptor.getRoleDescriptors().get(0), instanceOf(SPSSODescriptor.class));
final SPSSODescriptor spDescriptor = (SPSSODescriptor) descriptor.getRoleDescriptors().get(0);
assertThat(spDescriptor.getAssertionConsumerServices(), iterableWithSize(1));
assertThat(spDescriptor.getAssertionConsumerServices().get(0).getLocation(), equalTo("https://kibana.my.corp/saml/login"));
assertThat(spDescriptor.getAssertionConsumerServices().get(0).isDefault(), equalTo(true));
assertThat(spDescriptor.getAssertionConsumerServices().get(0).getIndex(), equalTo(1));
assertThat(spDescriptor.getAssertionConsumerServices().get(0).getBinding(), equalTo(SAMLConstants.SAML2_POST_BINDING_URI));
assertThat(spDescriptor.getAttributeConsumingServices(), iterableWithSize(1));
assertThat(spDescriptor.getAttributeConsumingServices().get(0).isDefault(), equalTo(true));
assertThat(spDescriptor.getAttributeConsumingServices().get(0).getIndex(), equalTo(1));
assertThat(spDescriptor.getAttributeConsumingServices().get(0).getRequestAttributes(), iterableWithSize(1));
final RequestedAttribute uidAttribute = spDescriptor.getAttributeConsumingServices().get(0).getRequestAttributes().get(0);
assertThat(uidAttribute.getName(), equalTo("urn:oid:0.9.2342.19200300.100.1.1"));
assertThat(uidAttribute.getFriendlyName(), equalTo("principal"));
assertThat(spDescriptor.getSingleLogoutServices(), iterableWithSize(1));
assertThat(spDescriptor.getSingleLogoutServices().get(0).getLocation(), equalTo("https://kibana.my.corp/saml/logout"));
assertThat(spDescriptor.getSingleLogoutServices().get(0).getBinding(), equalTo(SAMLConstants.SAML2_REDIRECT_BINDING_URI));
assertThat(spDescriptor.isAuthnRequestsSigned(), equalTo(useSigningCredentials));
assertThat(spDescriptor.getWantAssertionsSigned(), equalTo(true));
int expectedKeyDescriptorSize = (useSigningCredentials) ? 1 : 0;
expectedKeyDescriptorSize = (useEncryptionCredentials) ? expectedKeyDescriptorSize + 2 : expectedKeyDescriptorSize;
assertThat(spDescriptor.getKeyDescriptors(), iterableWithSize(expectedKeyDescriptorSize));
if (expectedKeyDescriptorSize > 0) {
final Set<java.security.cert.X509Certificate> encryptionCertificatesToMatch = new HashSet<>();
if (useEncryptionCredentials) {
encryptionCertificatesToMatch.add(certEncKeyPair1.v1());
encryptionCertificatesToMatch.add(certEncKeyPair2.v1());
}
spDescriptor.getKeyDescriptors().stream().forEach((keyDesc) -> {
UsageType usageType = keyDesc.getUse();
final List<X509Data> x509 = keyDesc.getKeyInfo().getX509Datas();
assertThat(x509, iterableWithSize(1));
assertThat(x509.get(0).getX509Certificates(), iterableWithSize(1));
final X509Certificate xmlCert = x509.get(0).getX509Certificates().get(0);
final java.security.cert.X509Certificate javaCert;
try {
// Verify that OpenSAML things the XML representation is the same as our input
javaCert = KeyInfoSupport.getCertificate(xmlCert);
} catch (CertificateException ce) {
throw ExceptionsHelper.convertToRuntime(ce);
}
if (usageType == UsageType.SIGNING) {
assertTrue("Found UsageType as SIGNING in SP metadata when not testing for signing credentials", useSigningCredentials);
assertEquals("Signing Certificate from SP metadata does not match", certKeyPairSign.v1(), javaCert);
} else if (usageType == UsageType.ENCRYPTION) {
assertTrue(useEncryptionCredentials);
assertTrue("Encryption Certificate was not found in encryption certificates",
encryptionCertificatesToMatch.remove(javaCert));
} else {
fail("Usage type should have been either SIGNING or ENCRYPTION");
}
});
if (useEncryptionCredentials) {
assertTrue("Did not find all encryption certificates in exported SP metadata", encryptionCertificatesToMatch.isEmpty());
}
}
}
private String getAliasName(final Tuple<java.security.cert.X509Certificate, PrivateKey> certKeyPair) {
return certKeyPair.v1().getSubjectX500Principal().getName().toLowerCase(Locale.US) + "-alias";
}
private boolean validateSignature(Signature signature) {
try {
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(getDataPath("saml.crt").toString()), null);

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.util.Arrays;
import java.util.Collections;
import org.elasticsearch.common.Nullable;
@ -37,7 +38,7 @@ public class SamlRealmTestHelper {
slo.setLocation(IDP_LOGOUT_URL);
final SpConfiguration spConfiguration = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, SP_LOGOUT_URL,
new SigningConfiguration(Collections.singleton("*"), credential), credential);
new SigningConfiguration(Collections.singleton("*"), credential), Arrays.asList(credential));
return new SamlRealm(realmConfig, mock(UserRoleMapper.class), mock(SamlAuthenticator.class),
mock(SamlLogoutRequestHandler.class), () -> idpDescriptor, spConfiguration);
}

View File

@ -38,6 +38,7 @@ import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.x509.X509Credential;
import javax.security.auth.x500.X500Principal;
@ -52,6 +53,7 @@ import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PrivilegedActionException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
@ -312,7 +314,7 @@ public class SamlRealmTests extends SamlTestCase {
builder.put(REALM_SETTINGS_PREFIX + ".encryption.certificate", writePemObject(dir, "enc.crt", cert).toString());
final Settings settings = builder.build();
final RealmConfig realmConfig = realmConfigFromGlobalSettings(settings);
final Credential credential = SamlRealm.buildEncryptionCredential(realmConfig);
final Credential credential = SamlRealm.buildEncryptionCredential(realmConfig).get(0);
assertThat(credential, notNullValue());
assertThat(credential.getPrivateKey(), equalTo(pair.getPrivate()));
@ -325,30 +327,47 @@ public class SamlRealmTests extends SamlTestCase {
.put(REALM_SETTINGS_PREFIX + ".type", "saml")
.put("path.home", dir);
final Path ksFile = dir.resolve("cred.p12");
final KeyPair pair = buildKeyPair();
final X509Certificate cert = buildCertificate(pair);
final boolean testMultipleEncryptionKeyPair = randomBoolean();
final Tuple<X509Certificate, PrivateKey> certKeyPair1 = createKeyPair("RSA");
final Tuple<X509Certificate, PrivateKey> certKeyPair2 = createKeyPair("RSA");
final KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null);
ks.setKeyEntry("the-alias", pair.getPrivate(), "key-password".toCharArray(), new Certificate[] { cert });
ks.setKeyEntry(getAliasName(certKeyPair1), certKeyPair1.v2(), "key-password".toCharArray(),
new Certificate[] { certKeyPair1.v1() });
if (testMultipleEncryptionKeyPair) {
ks.setKeyEntry(getAliasName(certKeyPair2), certKeyPair2.v2(), "key-password".toCharArray(),
new Certificate[] { certKeyPair2.v1() });
}
try (OutputStream out = Files.newOutputStream(ksFile)) {
ks.store(out, "ks-password".toCharArray());
}
builder.put(REALM_SETTINGS_PREFIX + ".encryption.keystore.path", ksFile.toString());
builder.put(REALM_SETTINGS_PREFIX + ".encryption.keystore.type", "PKCS12");
builder.put(REALM_SETTINGS_PREFIX + ".encryption.keystore.alias", "the-alias");
final boolean isEncryptionKeyStoreAliasSet = randomBoolean();
if (isEncryptionKeyStoreAliasSet) {
builder.put(REALM_SETTINGS_PREFIX + ".encryption.keystore.alias", getAliasName(certKeyPair1));
}
MockSecureSettings secureSettings = new MockSecureSettings();
final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(REALM_SETTINGS_PREFIX + ".encryption.keystore.secure_password", "ks-password");
secureSettings.setString(REALM_SETTINGS_PREFIX + ".encryption.keystore.secure_key_password", "key-password");
builder.setSecureSettings(secureSettings);
final Settings settings = builder.build();
final RealmConfig realmConfig = realmConfigFromGlobalSettings(settings);
final Credential credential = SamlRealm.buildEncryptionCredential(realmConfig);
final List<X509Credential> credentials = SamlRealm.buildEncryptionCredential(realmConfig);
assertThat(credential, notNullValue());
assertThat(credential.getPrivateKey(), equalTo(pair.getPrivate()));
assertThat(credential.getPublicKey(), equalTo(pair.getPublic()));
assertThat("Encryption Credentials should not be null", credentials, notNullValue());
final int expectedCredentials = (isEncryptionKeyStoreAliasSet) ? 1 : (testMultipleEncryptionKeyPair) ? 2 : 1;
assertEquals("Expected encryption credentials size does not match", expectedCredentials, credentials.size());
credentials.stream().forEach((credential) -> {
assertTrue("Unexpected private key in the list of encryption credentials",
Arrays.asList(new PrivateKey[] { certKeyPair1.v2(), certKeyPair2.v2() }).contains(credential.getPrivateKey()));
assertTrue("Unexpected public key in the list of encryption credentials",
Arrays.asList(new PublicKey[] { (certKeyPair1.v1()).getPublicKey(), certKeyPair2.v1().getPublicKey() })
.contains(credential.getPublicKey()));
});
}
public void testCreateSigningCredentialFromKeyStoreSuccessScenarios() throws Exception {
@ -445,7 +464,7 @@ public class SamlRealmTests extends SamlTestCase {
expectThrows(IllegalArgumentException.class, () -> SamlRealm.buildSigningConfiguration(realmConfig));
final String expectedErrorMessage = "The configured key store for "
+ RealmSettings.getFullSettingKey(realmConfig, SamlRealmSettings.SIGNING_SETTINGS.getPrefix())
+ " does not have a certificate key pair associated with alias [" + unknownAlias + "] " + "(from setting "
+ " does not have a key associated with alias [" + unknownAlias + "] " + "(from setting "
+ RealmSettings.getFullSettingKey(realmConfig, SamlRealmSettings.SIGNING_KEY_ALIAS) + ")";
assertEquals(expectedErrorMessage, illegalArgumentException.getLocalizedMessage());
} else {

View File

@ -10,6 +10,7 @@ import org.joda.time.DateTimeZone;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.security.x509.X509Credential;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
@ -33,7 +34,8 @@ public class SamlRedirectTests extends SamlTestCase {
}
public void testRedirectUrlWithRelayStateAndSigning() throws Exception {
final SigningConfiguration signing = new SigningConfiguration(singleton("*"), buildOpenSamlCredential(createKeyPair()));
final SigningConfiguration signing =
new SigningConfiguration(singleton("*"), (X509Credential) buildOpenSamlCredential(createKeyPair()).get(0));
final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL), signing);
final String url = redirect.getRedirectUrl("hello");
assertThat(url, startsWith(LOGOUT_URL + "?SAMLRequest=nZFBa4QwFIT%2FSnh3Naa2ax%2FqsiAFYdtDu91DLyVo2AY0cX2x9Oc36gpLC" +

View File

@ -18,13 +18,19 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class SamlSpMetadataBuilderTests extends SamlTestCase {
private X509Certificate certificate;
// 1st is for signing, followed by 2 for encryption
private X509Certificate[] threeCertificates;
@Before
public void setup() throws Exception {
SamlUtils.initialize(logger);
@ -38,6 +44,20 @@ public class SamlSpMetadataBuilderTests extends SamlTestCase {
} else {
fail("Expected exactly X509Certificate, but was " + certs[0].getClass());
}
final Path threeCertsPath = getDataPath("saml-three-certs.crt");
final Certificate[] threeCerts = CertUtils.readCertificates(Collections.singletonList(threeCertsPath));
if (threeCerts.length != 3) {
fail("Expected exactly 3 certificate in " + certPath);
}
List<Class> notX509Certificates = Arrays.stream(threeCerts).filter((cert) -> {
return !(cert instanceof X509Certificate);
}).map(cert -> cert.getClass()).collect(Collectors.toList());
if (notX509Certificates.isEmpty() == false) {
fail("Expected exactly X509Certificates, but found " + notX509Certificates);
} else {
this.threeCertificates = Arrays.asList(threeCerts).toArray(new X509Certificate[0]);
}
}
public void testBuildMinimalistMetadata() throws Exception {
@ -73,7 +93,7 @@ public class SamlSpMetadataBuilderTests extends SamlTestCase {
.singleLogoutServiceUrl("https://kibana.apps.hydra/saml/logout")
.authnRequestsSigned(true)
.signingCertificate(certificate)
.encryptionCertificate(certificate)
.encryptionCertificates(Arrays.asList(certificate))
.organization("Hydra", "Hydra", "https://hail.hydra/")
.withContact("administrative", "Wolfgang", "von Strucker", "baron.strucker@supreme.hydra")
.withContact("technical", "Paul", "Ebersol", "pne@tech.hydra")
@ -144,6 +164,134 @@ public class SamlSpMetadataBuilderTests extends SamlTestCase {
assertValidXml(xml);
}
public void testBuildFullMetadataWithSigningAndTwoEncryptionCerts() throws Exception {
final EntityDescriptor descriptor = new SamlSpMetadataBuilder(Locale.US, "https://kibana.apps.hydra/")
.serviceName("Hydra Kibana")
.nameIdFormat(NameID.PERSISTENT)
.withAttribute("uid", "urn:oid:0.9.2342.19200300.100.1.1")
.withAttribute("mail", "urn:oid:0.9.2342.19200300.100.1.3")
.withAttribute("groups", "urn:oid:1.3.6.1.4.1.5923.1.5.1.1")
.withAttribute(null, "urn:oid:2.16.840.1.113730.3.1.241")
.withAttribute(null, "urn:oid:1.3.6.1.4.1.5923.1.1.1.6")
.assertionConsumerServiceUrl("https://kibana.apps.hydra/saml/acs")
.singleLogoutServiceUrl("https://kibana.apps.hydra/saml/logout")
.authnRequestsSigned(true)
.signingCertificate(threeCertificates[0])
.encryptionCertificates(Arrays.asList(threeCertificates[1], threeCertificates[2]))
.organization("Hydra", "Hydra", "https://hail.hydra/")
.withContact("administrative", "Wolfgang", "von Strucker", "baron.strucker@supreme.hydra")
.withContact("technical", "Paul", "Ebersol", "pne@tech.hydra")
.build();
final Element element = new EntityDescriptorMarshaller().marshall(descriptor);
final String xml = SamlUtils.toString(element);
assertThat(xml, Matchers.equalTo("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"https://kibana.apps.hydra/\">" +
"<md:SPSSODescriptor AuthnRequestsSigned=\"true\" WantAssertionsSigned=\"true\"" +
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">" +
"<md:KeyDescriptor use=\"signing\">" +
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>" +
"MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEBCwUAMB0xGzAZ" + System.lineSeparator() +
"BgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3MjZaFw0yMDExMjgwMjQ3MjZa" + System.lineSeparator() +
"MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC" + System.lineSeparator() +
"AQoCggEBALHTuPGOieCbD2mZUdYrdH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3d" + System.lineSeparator() +
"dKzYo+yfC44AMXrrV+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdF" + System.lineSeparator() +
"ZwMYUdo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNwKC29AmXQ" + System.lineSeparator() +
"TwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSWxJddzxDGw6X7fOS7iuzB" + System.lineSeparator() +
"0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+es70CAwEAAaOBjjCBizAdBgNVHQ4EFgQU" + System.lineSeparator() +
"ILqVKGhIi8p5Xffsow/IKFLhRbIwWQYDVR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQf" + System.lineSeparator() +
"MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8G" + System.lineSeparator() +
"A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6k27K" + System.lineSeparator() +
"d0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPULamGdYXX4h0pFj5ld" + System.lineSeparator() +
"aubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi00AFYBoyvqfOu6nRLPnQr+rKVfdNO" + System.lineSeparator() +
"pWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9lL0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLP" + System.lineSeparator() +
"sL7n+PWby7LRX188dyS1dmKfQcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8" + System.lineSeparator() +
"IUldQE8=" +
"</ds:X509Certificate></ds:X509Data></ds:KeyInfo>" +
"</md:KeyDescriptor>" +
"<md:KeyDescriptor use=\"encryption\">" +
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>" +
"MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNVBAoTA29yZzEW" + System.lineSeparator() +
"MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3RpY3NlYXJjaCBUZXN0IE5vZGUw" + System.lineSeparator() +
"HhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsT" + System.lineSeparator() +
"DWVsYXN0aWNzZWFyY2gxIDAeBgNVBAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkq" + System.lineSeparator() +
"hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUK" + System.lineSeparator() +
"KNR1Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c7u0sLch9" + System.lineSeparator() +
"p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg/lATm8V71LMY68inht71" + System.lineSeparator() +
"/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5zJhn660es/1ZnR6nvwt6xnSTl/mNHMjk" + System.lineSeparator() +
"fv1bs4rJ/py3qPxicdoSIn/KyojUcgHVF38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQAB" + System.lineSeparator() +
"o4G/MIG8MAkGA1UdEwQCMAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREE" + System.lineSeparator() +
"gYcwgYSCCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghdsb2Nh" + System.lineSeparator() +
"bGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5sb2NhbGRvbWFpbjaH" + System.lineSeparator() +
"BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAMjGGXT8Nt1tbl2GkiKt" + System.lineSeparator() +
"miuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHek2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugo" + System.lineSeparator() +
"Q3wct0bQC5wEWYN+oMDvSyO6M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzq" + System.lineSeparator() +
"k/mQTug+Y8aEmVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z" + System.lineSeparator() +
"1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxCy4g6cTMM3S/U" + System.lineSeparator() +
"Mt5/+aIB2JAuMKyuD+A=" +
"</ds:X509Certificate></ds:X509Data></ds:KeyInfo>" +
"</md:KeyDescriptor>" +
"<md:KeyDescriptor use=\"encryption\">" +
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>" +
"MIID1zCCAr+gAwIBAgIJALnUl/KSS74pMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNVBAoTA29yZzEW" + System.lineSeparator() +
"MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVu" + System.lineSeparator() +
"dDAeFw0xNTA5MjMxODUyNTVaFw0xOTA5MjIxODUyNTVaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UE" + System.lineSeparator() +
"CxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIw" + System.lineSeparator() +
"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKm+P6vDAff0c6BWKGdhnYoNl9HijLIgfU3d9CQ" + System.lineSeparator() +
"cqKtwT+yUW3DPSVjIfaLmDIGj6Hl8jTHWPB7ZP4fzhrPi6m4qlRGclJMECBuNASZFiPDtEDv3mso" + System.lineSeparator() +
"eqOKQet6n7PZvgpWM7hxYZO4P1aMKJtRsFAdvBAdZUnv0spR5G4UZTHzSKmMeanIKFkLaD0XVKiL" + System.lineSeparator() +
"Qu9/z9M6roDQeAEoCJ/8JsanG8ih2ymfPHIZuNyYIOrVekHN2zU6bnVn8/PCeZSjS6h5xYw+Jl5g" + System.lineSeparator() +
"zGI/n+F5CZ+THoH8pM4pGp6xRVzpiH12gvERGwgSIDXdn/+uZZj+4lE7n2ENRSOt5KcOGG99r60C" + System.lineSeparator() +
"AwEAAaOBvzCBvDAJBgNVHRMEAjAAMB0GA1UdDgQWBBSSFhBXNp7AaNrHdlgCV0mCEzt7ajCBjwYD" + System.lineSeparator() +
"VR0RBIGHMIGEgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2NhbGRvbWFpboIKbG9jYWxob3N0NIIX" + System.lineSeparator() +
"bG9jYWxob3N0NC5sb2NhbGRvbWFpbjSCCmxvY2FsaG9zdDaCF2xvY2FsaG9zdDYubG9jYWxkb21h" + System.lineSeparator() +
"aW42hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQANvAkddfLxn4/B" + System.lineSeparator() +
"CY4LY/1ET3d7ZRldjFTyjjHRYJ3CYBXWVahMskLxIcFNca8YjKfXoX8mcK+NQK/dAbGHXqk76yMl" + System.lineSeparator() +
"krKjh1OQiZ1YAX5ryYerGrZ99N3E9wnbn72bW3iumoLlqmTWlHEpMI0Ql6J75BQLTgKHxCPupVA5" + System.lineSeparator() +
"sTbWkKwGjXXAi84rUlzhDJOR8jk3/7ct0iZO8Hk6AWMcNix5Wka3IDGUXuEVevYRlxgVyCxcnZWC" + System.lineSeparator() +
"7JWREpar5aIPQFkY6VCEglxwUyXbHZw5T/u6XaKKnS7gz8RiwRh68ddSQJeEHi5e4onUD7bOCJgf" + System.lineSeparator() +
"siUwdiCkDbfN9Yum8OIpmBRs" +
"</ds:X509Certificate></ds:X509Data></ds:KeyInfo>" +
"</md:KeyDescriptor>" +
"<md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"" +
" Location=\"https://kibana.apps.hydra/saml/logout\"/>" +
"<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>" +
"<md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"" +
" Location=\"https://kibana.apps.hydra/saml/acs\" index=\"1\" isDefault=\"true\"/>" +
"<md:AttributeConsumingService index=\"1\" isDefault=\"true\">" +
"<md:ServiceName xml:lang=\"en-US\">Hydra Kibana</md:ServiceName>" +
"<md:RequestedAttribute FriendlyName=\"uid\" Name=\"urn:oid:0.9.2342.19200300.100.1.1\"" +
" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"/>" +
"<md:RequestedAttribute FriendlyName=\"mail\" Name=\"urn:oid:0.9.2342.19200300.100.1.3\"" +
" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"/>" +
"<md:RequestedAttribute FriendlyName=\"groups\" Name=\"urn:oid:1.3.6.1.4.1.5923.1.5.1.1\"" +
" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"/>" +
"<md:RequestedAttribute Name=\"urn:oid:2.16.840.1.113730.3.1.241\"" +
" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"/>" +
"<md:RequestedAttribute Name=\"urn:oid:1.3.6.1.4.1.5923.1.1.1.6\"" +
" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"/>" +
"</md:AttributeConsumingService>" +
"</md:SPSSODescriptor>" +
"<md:Organization>" +
"<md:OrganizationName xml:lang=\"en-US\">Hydra</md:OrganizationName>" +
"<md:OrganizationDisplayName xml:lang=\"en-US\">Hydra</md:OrganizationDisplayName>" +
"<md:OrganizationURL xml:lang=\"en-US\">https://hail.hydra/</md:OrganizationURL>" +
"</md:Organization>" +
"<md:ContactPerson contactType=\"administrative\">" +
"<md:GivenName>Wolfgang</md:GivenName>" +
"<md:SurName>von Strucker</md:SurName>" +
"<md:EmailAddress>baron.strucker@supreme.hydra</md:EmailAddress>" +
"</md:ContactPerson>" +
"<md:ContactPerson contactType=\"technical\">" +
"<md:GivenName>Paul</md:GivenName>" +
"<md:SurName>Ebersol</md:SurName>" +
"<md:EmailAddress>pne@tech.hydra</md:EmailAddress>" +
"</md:ContactPerson>" +
"</md:EntityDescriptor>"
));
assertValidXml(xml);
}
public void testAssertionConsumerServiceIsRequired() {
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(Locale.US, "https://kibana.apps.hydra/");
final IllegalStateException exception = expectThrows(IllegalStateException.class, builder::build);

View File

@ -14,17 +14,24 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.ssl.CertUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.security.x509.impl.X509KeyManagerX509CredentialAdapter;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@ -100,16 +107,27 @@ public abstract class SamlTestCase extends ESTestCase {
return new Tuple<>(cert, pair.getPrivate());
}
protected static X509Credential buildOpenSamlCredential(Tuple<X509Certificate, PrivateKey> keyPair) {
protected static List<Credential> buildOpenSamlCredential(final Tuple<X509Certificate, PrivateKey> keyPair) {
try {
final Certificate[] certificateChain = { keyPair.v1() };
final PrivateKey privateKey = keyPair.v2();
return new X509KeyManagerX509CredentialAdapter(CertUtils.keyManager(certificateChain, privateKey, new char[0]), "key");
return Arrays.asList(new X509KeyManagerX509CredentialAdapter(
CertUtils.keyManager(new Certificate[] { keyPair.v1() }, keyPair.v2(), new char[0]), "key"));
} catch (Exception e) {
throw ExceptionsHelper.convertToRuntime(e);
}
}
protected static List<Credential> buildOpenSamlCredential(final List<Tuple<X509Certificate, PrivateKey>> keyPairs) {
final List<Credential> credentials = keyPairs.stream().map((keyPair) -> {
try {
return new X509KeyManagerX509CredentialAdapter(
CertUtils.keyManager(new Certificate[] { keyPair.v1() }, keyPair.v2(), new char[0]), "key");
} catch (Exception e) {
throw ExceptionsHelper.convertToRuntime(e);
}
}).collect(Collectors.toList());
return credentials;
}
protected ElasticsearchSecurityException expectSamlException(ThrowingRunnable runnable) {
final ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class, runnable);
assertThat("Exception " + exception + " should be a SAML exception", SamlUtils.isSamlException(exception), is(true));

View File

@ -21,7 +21,7 @@ public class SigningConfigurationTests extends SamlTestCase {
@BeforeClass
public static void setupCredential() throws Exception {
credential = buildOpenSamlCredential(createKeyPair());
credential = (X509Credential)buildOpenSamlCredential(createKeyPair()).get(0);
}
@AfterClass

View File

@ -0,0 +1,66 @@
-----BEGIN CERTIFICATE-----
MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEB
CwUAMB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3
MjZaFw0yMDExMjgwMjQ3MjZaMB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FN
TDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHTuPGOieCbD2mZUdYr
dH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3ddKzYo+yfC44AMXrr
V+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdFZwMY
Udo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNw
KC29AmXQTwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSW
xJddzxDGw6X7fOS7iuzB0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+e
s70CAwEAAaOBjjCBizAdBgNVHQ4EFgQUILqVKGhIi8p5Xffsow/IKFLhRbIwWQYD
VR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQfMB0xGzAZBgNVBAMTEkVs
YXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6
k27Kd0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPUL
amGdYXX4h0pFj5ldaubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi0
0AFYBoyvqfOu6nRLPnQr+rKVfdNOpWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9l
L0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLPsL7n+PWby7LRX188dyS1dmKf
QcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8IUldQE8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp
Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3
WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV
BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUKKNR1
Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c
7u0sLch9p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg
/lATm8V71LMY68inht71/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5
zJhn660es/1ZnR6nvwt6xnSTl/mNHMjkfv1bs4rJ/py3qPxicdoSIn/KyojUcgHV
F38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQABo4G/MIG8MAkGA1UdEwQC
MAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREEgYcwgYSC
CWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghds
b2NhbGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5s
b2NhbGRvbWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
BQADggEBAMjGGXT8Nt1tbl2GkiKtmiuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHe
k2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugoQ3wct0bQC5wEWYN+oMDvSyO6
M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzqk/mQTug+Y8aE
mVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z
1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxC
y4g6cTMM3S/UMt5/+aIB2JAuMKyuD+A=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIJALnUl/KSS74pMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3Rp
Y3NlYXJjaCBUZXN0IENsaWVudDAeFw0xNTA5MjMxODUyNTVaFw0xOTA5MjIxODUy
NTVaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAG
A1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMKm+P6vDAff0c6BWKGdhnYoNl9HijLIgfU3d9CQcqKt
wT+yUW3DPSVjIfaLmDIGj6Hl8jTHWPB7ZP4fzhrPi6m4qlRGclJMECBuNASZFiPD
tEDv3msoeqOKQet6n7PZvgpWM7hxYZO4P1aMKJtRsFAdvBAdZUnv0spR5G4UZTHz
SKmMeanIKFkLaD0XVKiLQu9/z9M6roDQeAEoCJ/8JsanG8ih2ymfPHIZuNyYIOrV
ekHN2zU6bnVn8/PCeZSjS6h5xYw+Jl5gzGI/n+F5CZ+THoH8pM4pGp6xRVzpiH12
gvERGwgSIDXdn/+uZZj+4lE7n2ENRSOt5KcOGG99r60CAwEAAaOBvzCBvDAJBgNV
HRMEAjAAMB0GA1UdDgQWBBSSFhBXNp7AaNrHdlgCV0mCEzt7ajCBjwYDVR0RBIGH
MIGEgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2NhbGRvbWFpboIKbG9jYWxob3N0
NIIXbG9jYWxob3N0NC5sb2NhbGRvbWFpbjSCCmxvY2FsaG9zdDaCF2xvY2FsaG9z
dDYubG9jYWxkb21haW42hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3
DQEBCwUAA4IBAQANvAkddfLxn4/BCY4LY/1ET3d7ZRldjFTyjjHRYJ3CYBXWVahM
skLxIcFNca8YjKfXoX8mcK+NQK/dAbGHXqk76yMlkrKjh1OQiZ1YAX5ryYerGrZ9
9N3E9wnbn72bW3iumoLlqmTWlHEpMI0Ql6J75BQLTgKHxCPupVA5sTbWkKwGjXXA
i84rUlzhDJOR8jk3/7ct0iZO8Hk6AWMcNix5Wka3IDGUXuEVevYRlxgVyCxcnZWC
7JWREpar5aIPQFkY6VCEglxwUyXbHZw5T/u6XaKKnS7gz8RiwRh68ddSQJeEHi5e
4onUD7bOCJgfsiUwdiCkDbfN9Yum8OIpmBRs
-----END CERTIFICATE-----