[SAML] Saml metadata signing (elastic/x-pack-elasticsearch#4184)
Adds option to sign generated Service Provider SAML metadata - Using a (possibly password protected) PEM encoded keypair - Using a keypair stored in a (possibly password protected) PKCSelastic/x-pack-elasticsearch#12 keystore Resolves elastic/x-pack-elasticsearch#3982 Original commit: elastic/x-pack-elasticsearch@7b806d76f8
This commit is contained in:
parent
c63d32482f
commit
febb46b702
|
@ -396,7 +396,8 @@ public class CertUtils {
|
|||
* @param keyPassword A supplier for the password for each key. The key alias is supplied as an argument to the function, and it should
|
||||
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
|
||||
*/
|
||||
static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword, Environment env)
|
||||
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword, Environment
|
||||
env)
|
||||
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
|
||||
final KeyStore store = readKeyStore(path, "PKCS12", password);
|
||||
final Enumeration<String> enumeration = store.aliases();
|
||||
|
|
|
@ -6,9 +6,17 @@
|
|||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -16,6 +24,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
|
@ -28,6 +37,7 @@ import org.elasticsearch.cli.ExitCodes;
|
|||
import org.elasticsearch.cli.SuppressForbidden;
|
||||
import org.elasticsearch.cli.Terminal;
|
||||
import org.elasticsearch.cli.UserException;
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
|
@ -38,10 +48,18 @@ import org.elasticsearch.env.Environment;
|
|||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
|
||||
import org.elasticsearch.xpack.core.ssl.CertUtils;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlSpMetadataBuilder.ContactInfo;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.core.xml.io.MarshallingException;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.x509.BasicX509Credential;
|
||||
import org.opensaml.xmlsec.signature.Signature;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||
import org.opensaml.xmlsec.signature.support.Signer;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
@ -65,6 +83,10 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
private final OptionSpec<String> orgDisplayNameSpec;
|
||||
private final OptionSpec<String> orgUrlSpec;
|
||||
private final OptionSpec<Void> contactsSpec;
|
||||
private final OptionSpec<String> signingPkcs12PathSpec;
|
||||
private final OptionSpec<String> signingCertPathSpec;
|
||||
private final OptionSpec<String> signingKeyPathSpec;
|
||||
private final OptionSpec<String> keyPasswordSpec;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new SamlMetadataCommand().main(args, Terminal.DEFAULT);
|
||||
|
@ -84,6 +106,18 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
orgUrlSpec = parser.accepts("organisation-url", "the URL of the organisation operating this service")
|
||||
.requiredIf(orgNameSpec).withRequiredArg();
|
||||
contactsSpec = parser.accepts("contacts", "Include contact information in metadata").availableUnless(batchSpec);
|
||||
signingPkcs12PathSpec = parser.accepts("signing-bundle", "path to an existing key pair (in PKCS#12 format) to be used for " +
|
||||
"signing ")
|
||||
.withRequiredArg();
|
||||
signingCertPathSpec = parser.accepts("signing-cert", "path to an existing signing certificate")
|
||||
.availableUnless(signingPkcs12PathSpec)
|
||||
.withRequiredArg();
|
||||
signingKeyPathSpec = parser.accepts("signing-key", "path to an existing signing private key")
|
||||
.availableIf(signingCertPathSpec)
|
||||
.requiredIf(signingCertPathSpec)
|
||||
.withRequiredArg();
|
||||
keyPasswordSpec = parser.accepts("signing-key-password", "password for an existing signing private key or keypair")
|
||||
.withOptionalArg();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,7 +129,9 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
SamlUtils.initialize(logger);
|
||||
|
||||
final EntityDescriptor descriptor = buildEntityDescriptor(terminal, options, env);
|
||||
final Path xml = writeOutput(terminal, options, descriptor);
|
||||
Element element = possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
|
||||
final Path xml = writeOutput(terminal, options, element);
|
||||
validateXml(terminal, xml);
|
||||
}
|
||||
|
||||
|
@ -181,9 +217,42 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
private Path writeOutput(Terminal terminal, OptionSet options, EntityDescriptor descriptor) throws Exception {
|
||||
// package-protected for testing
|
||||
Element possiblySignDescriptor(Terminal terminal, OptionSet options, EntityDescriptor descriptor, Environment env)
|
||||
throws UserException {
|
||||
try {
|
||||
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller();
|
||||
final Element element = marshaller.marshall(descriptor);
|
||||
if (options.has(signingPkcs12PathSpec) || (options.has(signingCertPathSpec) && options.has(signingKeyPathSpec))) {
|
||||
Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
||||
.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(Signature.DEFAULT_ELEMENT_NAME);
|
||||
signature.setSigningCredential(buildSigningCredential(terminal, options, env));
|
||||
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
|
||||
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
|
||||
descriptor.setSignature(signature);
|
||||
Element element = marshaller.marshall(descriptor);
|
||||
Signer.signObject(signature);
|
||||
return element;
|
||||
} else {
|
||||
return marshaller.marshall(descriptor);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errorMessage;
|
||||
if (e instanceof MarshallingException) {
|
||||
errorMessage = "Error serializing Metadata to file";
|
||||
} else if (e instanceof org.opensaml.xmlsec.signature.support.SignatureException) {
|
||||
errorMessage = "Error attempting to sign Metadata";
|
||||
} else {
|
||||
errorMessage = "Error building signing credentials from provided keyPair";
|
||||
}
|
||||
terminal.println(Terminal.Verbosity.SILENT, errorMessage);
|
||||
terminal.println("The following errors were found:");
|
||||
printExceptions(terminal, e);
|
||||
throw new UserException(ExitCodes.CANT_CREATE, "Unable to create metadata document");
|
||||
}
|
||||
}
|
||||
|
||||
private Path writeOutput(Terminal terminal, OptionSet options, Element element) throws Exception {
|
||||
final Path outputFile = resolvePath(option(outputPathSpec, options, "saml-elasticsearch-metadata.xml"));
|
||||
final Writer writer = Files.newBufferedWriter(outputFile);
|
||||
SamlUtils.print(element, writer, true);
|
||||
|
@ -191,7 +260,74 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
return outputFile;
|
||||
}
|
||||
|
||||
private Credential buildSigningCredential(Terminal terminal, OptionSet options, Environment env) throws
|
||||
Exception {
|
||||
X509Certificate signingCertificate;
|
||||
PrivateKey signingKey;
|
||||
char[] password = getChars(keyPasswordSpec.value(options));
|
||||
if (options.has(signingPkcs12PathSpec)) {
|
||||
Path p12Path = resolvePath(signingPkcs12PathSpec.value(options));
|
||||
Map<Certificate, Key> keys = withPassword("certificate bundle (" + p12Path + ")", password,
|
||||
terminal, keyPassword -> CertUtils.readPkcs12KeyPairs(p12Path, keyPassword, a -> keyPassword, env));
|
||||
|
||||
if (keys.size() != 1) {
|
||||
throw new IllegalArgumentException("expected a single key in file [" + p12Path.toAbsolutePath() + "] but found [" +
|
||||
keys.size() + "]");
|
||||
}
|
||||
final Map.Entry<Certificate, Key> pair = keys.entrySet().iterator().next();
|
||||
signingCertificate = (X509Certificate) pair.getKey();
|
||||
signingKey = (PrivateKey) pair.getValue();
|
||||
} else {
|
||||
Path cert = resolvePath(signingCertPathSpec.value(options));
|
||||
Path key = resolvePath(signingKeyPathSpec.value(options));
|
||||
final String resolvedSigningCertPath = cert.toAbsolutePath().toString();
|
||||
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(resolvedSigningCertPath), env);
|
||||
if (certificates.length != 1) {
|
||||
throw new IllegalArgumentException("expected a single certificate in file [" + resolvedSigningCertPath + "] but found [" +
|
||||
certificates.length + "]");
|
||||
}
|
||||
signingCertificate = (X509Certificate) certificates[0];
|
||||
signingKey = readSigningKey(key, password, terminal);
|
||||
}
|
||||
return new BasicX509Credential(signingCertificate, signingKey);
|
||||
}
|
||||
|
||||
private static <T, E extends Exception> T withPassword(String description, char[] password, Terminal terminal,
|
||||
CheckedFunction<char[], T, E> body) throws E {
|
||||
if (password == null) {
|
||||
char[] promptedValue = terminal.readSecret("Enter password for " + description + " : ");
|
||||
try {
|
||||
return body.apply(promptedValue);
|
||||
} finally {
|
||||
Arrays.fill(promptedValue, (char) 0);
|
||||
}
|
||||
} else {
|
||||
return body.apply(password);
|
||||
}
|
||||
}
|
||||
|
||||
private static char[] getChars(String password) {
|
||||
return password == null ? null : password.toCharArray();
|
||||
}
|
||||
|
||||
private static PrivateKey readSigningKey(Path path, char[] password, Terminal terminal)
|
||||
throws Exception {
|
||||
AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
return CertUtils.readPrivateKey(reader, () -> {
|
||||
if (password != null) {
|
||||
return password;
|
||||
}
|
||||
char[] promptedValue = terminal.readSecret("Enter password for the signing key (" + path.getFileName() + ") : ");
|
||||
passwordReference.set(promptedValue);
|
||||
return promptedValue;
|
||||
});
|
||||
} finally {
|
||||
if (passwordReference.get() != null) {
|
||||
Arrays.fill(passwordReference.get(), (char) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void validateXml(Terminal terminal, Path xml) throws Exception {
|
||||
try (InputStream xmlInput = Files.newInputStream(xml)) {
|
||||
SamlUtils.validate(xmlInput, METADATA_SCHEMA);
|
||||
|
|
|
@ -18,12 +18,22 @@ import org.opensaml.saml.common.xml.SAMLConstants;
|
|||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.RequestedAttribute;
|
||||
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
|
||||
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.security.x509.BasicX509Credential;
|
||||
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
||||
import org.opensaml.xmlsec.signature.Signature;
|
||||
import org.opensaml.xmlsec.signature.X509Certificate;
|
||||
import org.opensaml.xmlsec.signature.X509Data;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -267,4 +277,270 @@ public class SamlMetadataCommandTests extends SamlTestCase {
|
|||
assertThat(attributes.get(1).getFriendlyName(), equalTo("principal"));
|
||||
assertThat(attributes.get(1).getName(), equalTo("urn:oid:0.9.2342.19200300.100.1.1"));
|
||||
}
|
||||
|
||||
public void testSigningMetadataWithPfx() throws Exception {
|
||||
final Path certPath = getDataPath("saml.crt");
|
||||
final Path keyPath = getDataPath("saml.key");
|
||||
final Path p12Path = getDataPath("saml.p12");
|
||||
final SamlMetadataCommand command = new SamlMetadataCommand();
|
||||
final OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-bundle", p12Path.toString()
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
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("");
|
||||
terminal.addSecretInput("");
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
command.possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
assertThat(descriptor, notNullValue());
|
||||
// Verify generated signature
|
||||
assertThat(descriptor.getSignature(), notNullValue());
|
||||
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
|
||||
// Make sure that Signing didn't mangle the XML at all and we can still read metadata
|
||||
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));
|
||||
|
||||
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.isAuthnRequestsSigned(), equalTo(useSigningCredentials));
|
||||
assertThat(spDescriptor.getWantAssertionsSigned(), equalTo(true));
|
||||
}
|
||||
|
||||
public void testSigningMetadataWithPasswordProtectedPfx() throws Exception {
|
||||
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 OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-bundle", p12Path.toString(),
|
||||
"-signing-key-password", "saml"
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
final Settings settings = settingsBuilder.build();
|
||||
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
Element e = command.possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
String a = SamlUtils.toString(e);
|
||||
assertThat(descriptor, notNullValue());
|
||||
// Verify generated signature
|
||||
assertThat(descriptor.getSignature(), notNullValue());
|
||||
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
|
||||
}
|
||||
|
||||
public void testErrorSigningMetadataWithWrongPassword() throws Exception {
|
||||
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 OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-bundle", p12Path.toString(),
|
||||
"-signing-key-password", "wrong_password"
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
final Settings settings = settingsBuilder.build();
|
||||
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
final UserException userException = expectThrows(UserException.class, () -> command.possiblySignDescriptor(terminal, options,
|
||||
descriptor, env));
|
||||
assertThat(userException.getMessage(), containsString("Unable to create metadata document"));
|
||||
assertThat(terminal.getOutput(), containsString("keystore password was incorrect"));
|
||||
}
|
||||
|
||||
public void testSigningMetadataWithPem() throws Exception {
|
||||
//Use this keypair for signing the metadata also
|
||||
final Path certPath = getDataPath("saml.crt");
|
||||
final Path keyPath = getDataPath("saml.key");
|
||||
|
||||
final SamlMetadataCommand command = new SamlMetadataCommand();
|
||||
final OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-cert", certPath.toString(),
|
||||
"-signing-key", keyPath.toString()
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
final Settings settings = settingsBuilder.build();
|
||||
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
command.possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
assertThat(descriptor, notNullValue());
|
||||
// Verify generated signature
|
||||
assertThat(descriptor.getSignature(), notNullValue());
|
||||
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
|
||||
}
|
||||
|
||||
public void testSigningMetadataWithPasswordProtectedPem() throws Exception {
|
||||
//Use same keypair for signing the metadata
|
||||
final Path signingKeyPath = getDataPath("saml_with_password.key");
|
||||
|
||||
final Path certPath = getDataPath("saml.crt");
|
||||
final Path keyPath = getDataPath("saml.key");
|
||||
|
||||
final SamlMetadataCommand command = new SamlMetadataCommand();
|
||||
final OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-cert", certPath.toString(),
|
||||
"-signing-key", signingKeyPath.toString(),
|
||||
"-signing-key-password", "saml"
|
||||
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
final Settings settings = settingsBuilder.build();
|
||||
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
command.possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
assertThat(descriptor, notNullValue());
|
||||
// Verify generated signature
|
||||
assertThat(descriptor.getSignature(), notNullValue());
|
||||
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
|
||||
}
|
||||
|
||||
public void testSigningMetadataWithPasswordProtectedPemInTerminal() throws Exception {
|
||||
//Use same keypair for signing the metadata
|
||||
final Path signingKeyPath = getDataPath("saml_with_password.key");
|
||||
|
||||
final Path certPath = getDataPath("saml.crt");
|
||||
final Path keyPath = getDataPath("saml.key");
|
||||
|
||||
final SamlMetadataCommand command = new SamlMetadataCommand();
|
||||
final OptionSet options = command.getParser().parse(new String[]{
|
||||
"-signing-cert", certPath.toString(),
|
||||
"-signing-key", signingKeyPath.toString()
|
||||
|
||||
});
|
||||
|
||||
final boolean useSigningCredentials = randomBoolean();
|
||||
final Settings.Builder settingsBuilder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.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");
|
||||
if (useSigningCredentials) {
|
||||
settingsBuilder.put(RealmSettings.PREFIX + "my_saml.signing.certificate", certPath.toString())
|
||||
.put(RealmSettings.PREFIX + "my_saml.signing.key", keyPath.toString());
|
||||
}
|
||||
final Settings settings = settingsBuilder.build();
|
||||
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
terminal.addSecretInput("saml");
|
||||
|
||||
final EntityDescriptor descriptor = command.buildEntityDescriptor(terminal, options, env);
|
||||
command.possiblySignDescriptor(terminal, options, descriptor, env);
|
||||
assertThat(descriptor, notNullValue());
|
||||
// Verify generated signature
|
||||
assertThat(descriptor.getSignature(), notNullValue());
|
||||
assertThat(validateSignature(descriptor.getSignature()), equalTo(true));
|
||||
}
|
||||
|
||||
private boolean validateSignature(Signature signature) {
|
||||
try {
|
||||
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(getDataPath("saml.crt").toString()), null);
|
||||
PrivateKey key = CertUtils.readPrivateKey(Files.newBufferedReader(getDataPath("saml.key"), StandardCharsets.UTF_8),
|
||||
""::toCharArray);
|
||||
Credential verificationCredential = new BasicX509Credential((java.security.cert.X509Certificate) certificates[0], key);
|
||||
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
|
||||
profileValidator.validate(signature);
|
||||
SignatureValidator.validate(signature, verificationCredential);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,E5CF40A599E7BBB7
|
||||
|
||||
TnxQWgs1sKEW5OIFMWQS/Iyqz25nIf7321MiEa/Z8lakv2bh8MBJCIp7r3pN4viJ
|
||||
K5bMJMIsjqUdolcvbuPXgtA6uuhnf/X4o7sU/fZhsgOiBkoj3r2pWOpHHx+am3G7
|
||||
uzrVlDgc36X5lVRUZLdnIeT3aLAk8+ObXF624TxISCUPZluGC0NwJlNZ5yxIeSTv
|
||||
47hd1OVfdKH+IYoa8Illt10njcl398z5AUWiX+j6ozlJnnufi0kRdQJ3Da0q5rUg
|
||||
IlUpqNmPwG2tl8Ys1tNaU8fpf22rLDo7S2P4S+SxzMvDYXFr/VDy4nFtWDuP6hTQ
|
||||
VA51txLKOR0+FhZNOUOZH6YjFv4LOUS3/doonPrp/7z4C6cnf3MECtnhOG2zVcBX
|
||||
FKCg7iKBmml92tOsCIAXCjUlpTHQniNqxNtOiWySlt/83XPLPQSpeNuSismWSHlN
|
||||
lVUGkcysjEaZu86DVc6NN91s7oG0x/R0UKU17NUZFtCaYCXYtdtRlpgpmMOD8KFp
|
||||
3NdZUFPQ0zetXqS/skdg1tKd49amKO7Qj+V9nWMzFnwPTM+LD3hV4Ehb2D41nTxz
|
||||
b0UFNb/vcYUZzP6+OvgrSyhH9f7pYmyt+Ky80wJ1eOB1ZRReo0iXYPZZo4G8spJy
|
||||
SHc18HswxU+ICMB77tHHDIJXnGQr9yTDYph3ZEs4m/NIP91f0XjeexsRcSDrKImD
|
||||
+UY6cY8a76HOT70Wl+Na0ZCKE9BkpWLgbkZbH9arhIbW12wSdvO6oPGFU9FOVL2V
|
||||
L0RMGSYQPowXyktBer+b1ZPrijOYWLqkn/S5prOjCjD/qxnWts9DGeNixrw3F7HY
|
||||
yEUbl3amc1/Zxi60CsNHmV6wvQIzoz3Pz+U6foIeJxLj5glFQiA1Yhivd1YGWeFk
|
||||
t3uqNGRe6C6sNzpF4LCKpxnEbZdAM6QzcmHIAcfINuc00zNjV6B3o1L91wXO/bd/
|
||||
VJ4zCcO0UxipLhaiXvMpjMOW3uJri5nlm1cWnGWH3M20l7hLQ4sUw0IUh9wA412K
|
||||
6muTqlvwYQtYwwG0nz99bnKwHIO01vbAQACYk13j37wUsG57DFygxs+fcITV0AfB
|
||||
PKwAKISmlr45PztXybEXg92mzkBsdXFMhpHeDhYDeC3g7DGXI5cEqbiATu8Gvsy2
|
||||
F4ylZMl2gHG4/2GSjEdHLv2uXPmZYFFeoJY+9GdNS6yz0ncn9yahG57OawbXR/fS
|
||||
NPfLyDg8C+fRC2O0TI4/a51sXy2bEE0NBRdrY1VZp9sc4nsRvUdvxvdd++R+PeNt
|
||||
oAqRHqTTksRJqBwy3+KMerGk0z9RLPzzLQkBVeH5bQtqhZq43bI6Zf8jDqKlb1H+
|
||||
YhkgU4DsO0iPKvtIMEa31U9Qc2nQiIWjSk4v8gr5tcqoekynV4rKosTV1+GSRlkn
|
||||
l245GuOOE2PKVYn0jrUIn/IGzcMfORRH4/Sl/gy9ikYS70tykyJVoMplIeb1awMa
|
||||
+FNAD17iNhaLOvuEfL5zCQtjXHyCRzReGxxmO9F2lN26Lr0MzM8k3bIrFTBVL/+n
|
||||
5pg6I6i8CXFWfpi08fP5KDU447AaBvdozm9L2JWIKaxjHev+NIy2Og2qtR34nBPH
|
||||
-----END RSA PRIVATE KEY-----
|
Binary file not shown.
Loading…
Reference in New Issue