[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:
Ioannis Kakavas 2018-03-28 13:43:29 +03:00 committed by GitHub
parent c63d32482f
commit febb46b702
6 changed files with 448 additions and 5 deletions

View File

@ -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 * @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. * 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 { throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
final KeyStore store = readKeyStore(path, "PKCS12", password); final KeyStore store = readKeyStore(path, "PKCS12", password);
final Enumeration<String> enumeration = store.aliases(); final Enumeration<String> enumeration = store.aliases();

View File

@ -6,9 +6,17 @@
package org.elasticsearch.xpack.security.authc.saml; package org.elasticsearch.xpack.security.authc.saml;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; 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.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -16,6 +24,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import joptsimple.OptionParser; import joptsimple.OptionParser;
@ -28,6 +37,7 @@ import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.SuppressForbidden; import org.elasticsearch.cli.SuppressForbidden;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException; import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.Loggers; 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.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings; 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.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.core.AuthnRequest;
import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller; 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.w3c.dom.Element;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -65,6 +83,10 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
private final OptionSpec<String> orgDisplayNameSpec; private final OptionSpec<String> orgDisplayNameSpec;
private final OptionSpec<String> orgUrlSpec; private final OptionSpec<String> orgUrlSpec;
private final OptionSpec<Void> contactsSpec; 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 { public static void main(String[] args) throws Exception {
new SamlMetadataCommand().main(args, Terminal.DEFAULT); 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") orgUrlSpec = parser.accepts("organisation-url", "the URL of the organisation operating this service")
.requiredIf(orgNameSpec).withRequiredArg(); .requiredIf(orgNameSpec).withRequiredArg();
contactsSpec = parser.accepts("contacts", "Include contact information in metadata").availableUnless(batchSpec); 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 @Override
@ -95,7 +129,9 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
SamlUtils.initialize(logger); SamlUtils.initialize(logger);
final EntityDescriptor descriptor = buildEntityDescriptor(terminal, options, env); 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); validateXml(terminal, xml);
} }
@ -181,9 +217,42 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
return builder.build(); return builder.build();
} }
private Path writeOutput(Terminal terminal, OptionSet options, EntityDescriptor descriptor) throws Exception { // package-protected for testing
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller(); Element possiblySignDescriptor(Terminal terminal, OptionSet options, EntityDescriptor descriptor, Environment env)
final Element element = marshaller.marshall(descriptor); throws UserException {
try {
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller();
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 Path outputFile = resolvePath(option(outputPathSpec, options, "saml-elasticsearch-metadata.xml"));
final Writer writer = Files.newBufferedWriter(outputFile); final Writer writer = Files.newBufferedWriter(outputFile);
SamlUtils.print(element, writer, true); SamlUtils.print(element, writer, true);
@ -191,7 +260,74 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
return outputFile; 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 { private void validateXml(Terminal terminal, Path xml) throws Exception {
try (InputStream xmlInput = Files.newInputStream(xml)) { try (InputStream xmlInput = Files.newInputStream(xml)) {
SamlUtils.validate(xmlInput, METADATA_SCHEMA); SamlUtils.validate(xmlInput, METADATA_SCHEMA);

View File

@ -18,12 +18,22 @@ import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.RequestedAttribute; import org.opensaml.saml.saml2.metadata.RequestedAttribute;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor; 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.credential.UsageType;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.X509Certificate; import org.opensaml.xmlsec.signature.X509Certificate;
import org.opensaml.xmlsec.signature.X509Data; 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.nio.file.Path;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -267,4 +277,270 @@ public class SamlMetadataCommandTests extends SamlTestCase {
assertThat(attributes.get(1).getFriendlyName(), equalTo("principal")); assertThat(attributes.get(1).getFriendlyName(), equalTo("principal"));
assertThat(attributes.get(1).getName(), equalTo("urn:oid:0.9.2342.19200300.100.1.1")); 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;
}
}
} }

View File

@ -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-----