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:
parent
0e89f07c3f
commit
1701934dd4
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 + "')");
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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-----
|
Loading…
Reference in New Issue