/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.auth.client;
import static javax.xml.stream.XMLStreamConstants.COMMENT;
import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.PROCESSING_INSTRUCTION;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security._private.ElytronMessages.xmlLog;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.wildfly.client.config.ClientConfiguration;
import org.wildfly.client.config.ConfigXMLParseException;
import org.wildfly.client.config.ConfigurationXMLStreamReader;
import org.wildfly.client.config.XMLLocation;
import org.wildfly.common.Assert;
import org.wildfly.common.function.ExceptionBiFunction;
import org.wildfly.common.function.ExceptionSupplier;
import org.wildfly.common.function.ExceptionUnaryOperator;
import org.wildfly.security.FixedSecurityFactory;
import org.wildfly.security.SecurityFactory;
import org.wildfly.security._private.ElytronMessages;
import org.wildfly.security.auth.server.IdentityCredentials;
import org.wildfly.security.auth.server.NameRewriter;
import org.wildfly.security.auth.util.ElytronAuthenticator;
import org.wildfly.security.auth.util.RegexNameRewriter;
import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.KeyPairCredential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.credential.PublicKeyCredential;
import org.wildfly.security.credential.X509CertificateChainPrivateCredential;
import org.wildfly.security.credential.source.CredentialSource;
import org.wildfly.security.credential.source.CredentialStoreCredentialSource;
import org.wildfly.security.credential.source.KeyStoreCredentialSource;
import org.wildfly.security.credential.source.OAuth2CredentialSource;
import org.wildfly.security.credential.store.CredentialStore;
import org.wildfly.security.keystore.PasswordEntry;
import org.wildfly.security.keystore.WrappingPasswordKeyStore;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
import org.wildfly.security.password.spec.HashPasswordSpec;
import org.wildfly.security.password.spec.IteratedHashPasswordSpec;
import org.wildfly.security.password.spec.IteratedSaltedHashPasswordSpec;
import org.wildfly.security.password.spec.PasswordSpec;
import org.wildfly.security.password.spec.SaltedHashPasswordSpec;
import org.wildfly.security.password.util.ModularCrypt;
import org.wildfly.security.pem.Pem;
import org.wildfly.security.pem.PemEntry;
import org.wildfly.security.sasl.SaslMechanismSelector;
import org.wildfly.security.sasl.util.ServiceLoaderSaslClientFactory;
import org.wildfly.security.ssl.CipherSuiteSelector;
import org.wildfly.security.ssl.ProtocolSelector;
import org.wildfly.security.ssl.SSLContextBuilder;
import org.wildfly.security.util.CodePointIterator;
import org.wildfly.security.util.ServiceLoaderSupplier;
import org.wildfly.security.x500.X500;
/**
* A parser for the Elytron XML schema.
*
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public final class ElytronXmlParser {
static final String NS_ELYTRON_1_0 = "urn:elytron:1.0";
private ElytronXmlParser() {
}
// authentication client document
/**
* Parse an Elytron authentication client configuration from a configuration discovered using the default wildfly-client-config discovery rules.
*
* @return the authentication context factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
public static SecurityFactory<AuthenticationContext> parseAuthenticationClientConfiguration() throws ConfigXMLParseException {
final ClientConfiguration clientConfiguration = ClientConfiguration.getInstance();
if (clientConfiguration != null) try (final ConfigurationXMLStreamReader streamReader = clientConfiguration.readConfiguration(Collections.singleton(NS_ELYTRON_1_0))) {
if (streamReader != null) {
return parseAuthenticationClientConfiguration(streamReader);
}
}
// Try legacy configuration next
return parseLegacyConfiguration();
}
/**
* Parse an Elytron authentication client configuration from a resource located at a specified {@link URI}.
*
* @param uri the {@link URI} of the configuration.
* @return the authentication context factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
public static SecurityFactory<AuthenticationContext> parseAuthenticationClientConfiguration(URI uri) throws ConfigXMLParseException {
final ClientConfiguration clientConfiguration = ClientConfiguration.getInstance(uri);
if (clientConfiguration != null) try (final ConfigurationXMLStreamReader streamReader = clientConfiguration.readConfiguration(Collections.singleton(NS_ELYTRON_1_0))) {
if (streamReader != null) {
return parseAuthenticationClientConfiguration(streamReader);
}
}
// Try legacy configuration next
return parseLegacyConfiguration();
}
/**
* Parse a Elytron authentication client configuration from a configuration XML reader.
*
* @param reader the XML stream reader
* @return the authentication context factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static SecurityFactory<AuthenticationContext> parseAuthenticationClientConfiguration(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
if (reader.hasNext()) {
switch (reader.nextTag()) {
case START_ELEMENT: {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "authentication-client": {
SecurityFactory<AuthenticationContext> futureContext = parseAuthenticationClientType(reader);
while (reader.hasNext()) {
switch (reader.next()) {
case COMMENT:
case PROCESSING_INSTRUCTION: {
break;
}
case END_DOCUMENT: {
return futureContext;
}
default: {
if (reader.isWhiteSpace()) break;
throw reader.unexpectedElement();
}
}
}
return futureContext;
}
default: {
throw reader.unexpectedElement();
}
}
}
default: {
throw reader.unexpectedContent();
}
}
}
throw reader.unexpectedDocumentEnd();
}
private static SecurityFactory<AuthenticationContext> parseLegacyConfiguration() {
final ServiceLoader<LegacyConfiguration> loader = ServiceLoader.load(LegacyConfiguration.class, ElytronXmlParser.class.getClassLoader());
final Iterator<LegacyConfiguration> iterator = loader.iterator();
final List<LegacyConfiguration> configs = new ArrayList<>();
for (;;) try {
if (! iterator.hasNext()) break;
configs.add(iterator.next());
} catch (ServiceConfigurationError ignored) {}
return () -> {
for (LegacyConfiguration config : configs) {
final AuthenticationContext context = config.getConfiguredAuthenticationContext();
if (context != null) return context;
}
return AuthenticationContext.EMPTY;
};
}
// authentication client types
/**
* Parse an XML element of type {@code authentication-client-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the authentication context factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static SecurityFactory<AuthenticationContext> parseAuthenticationClientType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
requireNoAttributes(reader);
ExceptionSupplier<RuleNode<AuthenticationConfiguration>, ConfigXMLParseException> authFactory = () -> null;
ExceptionSupplier<RuleNode<SecurityFactory<SSLContext>>, ConfigXMLParseException> sslFactory = () -> null;
Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap = new HashMap<>();
Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap = new HashMap<>();
Map<String, ExceptionSupplier<SecurityFactory<SSLContext>, ConfigXMLParseException>> sslContextsMap = new HashMap<>();
Map<String, ExceptionSupplier<AuthenticationConfiguration, ConfigXMLParseException>> authenticationConfigurationsMap = new HashMap<>();
boolean netAuthenticator = false;
int foundBits = 0;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "authentication-rules": {
if (isSet(foundBits, 0)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 0);
authFactory = parseRulesType(reader, authenticationConfigurationsMap, ElytronXmlParser::parseAuthenticationRuleType);
break;
}
case "ssl-context-rules": {
if (isSet(foundBits, 1)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 1);
sslFactory = parseRulesType(reader, sslContextsMap, ElytronXmlParser::parseSslContextRuleType);
break;
}
case "authentication-configurations": {
if (isSet(foundBits, 2)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 2);
parseAuthenticationConfigurationsType(reader, authenticationConfigurationsMap, keyStoresMap, credentialStoresMap);
break;
}
case "ssl-contexts": {
if (isSet(foundBits, 3)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 3);
parseSslContextsType(reader, sslContextsMap, keyStoresMap);
break;
}
case "key-stores": {
if (isSet(foundBits, 4)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 4);
parseKeyStoresType(reader, keyStoresMap);
break;
}
case "net-authenticator": {
if (isSet(foundBits, 5)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 5);
netAuthenticator = true;
parseEmptyType(reader);
break;
}
case "credential-stores": {
if (isSet(foundBits, 6)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 5);
parseCredentialStoresType(reader, keyStoresMap, credentialStoresMap);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
if (netAuthenticator) {
Authenticator.setDefault(new ElytronAuthenticator());
}
// validate key and credential stores...
for (ExceptionSupplier<KeyStore, ConfigXMLParseException> supplier : keyStoresMap.values()) {
supplier.get();
}
for (ExceptionSupplier<CredentialStore, ConfigXMLParseException> supplier : credentialStoresMap.values()) {
supplier.get();
}
final RuleNode<AuthenticationConfiguration> authNode = authFactory.get();
final RuleNode<SecurityFactory<SSLContext>> sslNode = sslFactory.get();
return () -> new AuthenticationContext(authNode, sslNode);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static void parseAuthenticationConfigurationsType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<AuthenticationConfiguration, ConfigXMLParseException>> authenticationConfigurationsMap, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) throws ConfigXMLParseException {
requireNoAttributes(reader);
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "configuration": {
parseAuthenticationConfigurationType(reader, authenticationConfigurationsMap, keyStoresMap, credentialStoresMap);
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static void parseSslContextsType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<SecurityFactory<SSLContext>, ConfigXMLParseException>> sslContextsMap, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
requireNoAttributes(reader);
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "ssl-context": {
parseSslContextType(reader, sslContextsMap, keyStoresMap);
break;
}
case "default-ssl-context": {
final String name = parseNameType(reader);
sslContextsMap.put(name, () -> SSLContext::getDefault);
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static void parseSslContextType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<SecurityFactory<SSLContext>, ConfigXMLParseException>> sslContextsMap, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
final String name = requireSingleAttribute(reader, "name");
if (sslContextsMap.containsKey(name)) {
throw xmlLog.xmlDuplicateSslContextName(name, reader);
}
final XMLLocation location = reader.getLocation();
int foundBits = 0;
Supplier<Provider[]> providersSupplier = Security::getProviders;
String providerName = null;
CipherSuiteSelector cipherSuiteSelector = null;
ProtocolSelector protocolSelector = null;
PrivateKeyKeyStoreEntryCredentialFactory credentialFactory = null;
ExceptionSupplier<KeyStore, ConfigXMLParseException> trustStoreSupplier = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "key-store-ssl-certificate": {
if (isSet(foundBits, 0)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 0);
credentialFactory = new PrivateKeyKeyStoreEntryCredentialFactory(parseKeyStoreRefType(reader, keyStoresMap), location);
break;
}
case "cipher-suite": {
if (isSet(foundBits, 1)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 1);
cipherSuiteSelector = parseCipherSuiteSelectorType(reader);
break;
}
case "protocol": {
if (isSet(foundBits, 2)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 2);
protocolSelector = parseProtocolSelectorNamesType(reader);
break;
}
case "provider-name": {
if (isSet(foundBits, 3)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 3);
providerName = parseNameType(reader);
break;
}
// these two are a <choice> which is why they share a bit #; you can have only one of them
case "use-system-providers": {
if (isSet(foundBits, 4)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 4);
parseEmptyType(reader);
// no action; this is a way of explicitly specifying the default
break;
}
case "use-service-loader-providers": {
if (isSet(foundBits, 4)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 4);
final String moduleName = parseModuleRefType(reader);
final ClassLoader classLoader = (moduleName == null) ? ElytronXmlParser.class.getClassLoader() : ModuleLoader.getClassLoaderFromModule(reader, moduleName);
providersSupplier = new ServiceLoaderSupplier<>(Provider.class, classLoader);
break;
}
case "trust-store": {
if (isSet(foundBits, 5)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 5);
trustStoreSupplier = parseTrustStoreRefType(reader, keyStoresMap);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag != END_ELEMENT) {
throw reader.unexpectedContent();
} else {
// ready to register!
final Supplier<Provider[]> finalProvidersSupplier = providersSupplier;
final ProtocolSelector finalProtocolSelector = protocolSelector;
final CipherSuiteSelector finalCipherSuiteSelector = cipherSuiteSelector;
final String finalProviderName = providerName;
final PrivateKeyKeyStoreEntryCredentialFactory finalCredentialFactory = credentialFactory;
final ExceptionSupplier<KeyStore, ConfigXMLParseException> finalTrustStoreSupplier = trustStoreSupplier;
sslContextsMap.putIfAbsent(name, () -> {
final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.setClientMode(true);
if (finalCipherSuiteSelector != null) {
sslContextBuilder.setCipherSuiteSelector(finalCipherSuiteSelector);
}
if (finalProtocolSelector != null) {
sslContextBuilder.setProtocolSelector(finalProtocolSelector);
}
if (finalCredentialFactory != null) {
final ConfigurationKeyManager.Builder builder = new ConfigurationKeyManager.Builder();
final X509CertificateChainPrivateCredential privateCredential;
privateCredential = finalCredentialFactory.get();
builder.addCredential(privateCredential);
sslContextBuilder.setKeyManager(builder.build());
}
if (finalTrustStoreSupplier != null) {
KeyStore trustStore = finalTrustStoreSupplier.get();
sslContextBuilder.setTrustManager(createX509TrustManager(trustStore));
}
sslContextBuilder.setProviderName(finalProviderName);
sslContextBuilder.setProviderSupplier(finalProvidersSupplier);
sslContextBuilder.setUseCipherSuitesOrder(true);
return sslContextBuilder.build();
});
return;
}
}
throw reader.unexpectedDocumentEnd();
}
private static X509TrustManager createX509TrustManager(KeyStore keyStore) throws ConfigXMLParseException {
try {
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
throw ElytronMessages.log.noDefaultTrustManager();
} catch (GeneralSecurityException e) {
throw new ConfigXMLParseException(e);
}
}
static ExceptionUnaryOperator<RuleNode<SecurityFactory<SSLContext>>, ConfigXMLParseException> parseSslContextRuleType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<SecurityFactory<SSLContext>, ConfigXMLParseException>> sslContextsMap) throws ConfigXMLParseException {
final String attributeName = "use-ssl-context";
final String name = requireSingleAttribute(reader, attributeName);
final XMLLocation location = reader.getLocation();
final MatchRule rule = parseAbstractMatchRuleType(reader);
return next -> {
final ExceptionSupplier<SecurityFactory<SSLContext>, ConfigXMLParseException> factory = sslContextsMap.get(name);
if (factory == null) throw xmlLog.xmlUnknownSslContextSpecified(location, name);
return new RuleNode<>(next, rule, factory.get());
};
}
static ExceptionUnaryOperator<RuleNode<AuthenticationConfiguration>, ConfigXMLParseException> parseAuthenticationRuleType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<AuthenticationConfiguration, ConfigXMLParseException>> authenticationConfigurationsMap) throws ConfigXMLParseException {
final String attributeName = "use-configuration";
final String name = requireSingleAttribute(reader, attributeName);
final XMLLocation location = reader.getLocation();
final MatchRule rule = parseAbstractMatchRuleType(reader);
return next -> {
final ExceptionSupplier<AuthenticationConfiguration, ConfigXMLParseException> factory = authenticationConfigurationsMap.get(name);
if (factory == null) throw xmlLog.xmlUnknownAuthenticationConfigurationSpecified(location, name);
return new RuleNode<>(next, rule, factory.get());
};
}
static <C> ExceptionSupplier<RuleNode<C>, ConfigXMLParseException> parseRulesType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<C, ConfigXMLParseException>> configurations, ExceptionBiFunction<ConfigurationXMLStreamReader, Map<String, ExceptionSupplier<C, ConfigXMLParseException>>, ExceptionUnaryOperator<RuleNode<C>, ConfigXMLParseException>, ConfigXMLParseException> ruleParseFunction) throws ConfigXMLParseException {
requireNoAttributes(reader);
final List<ExceptionUnaryOperator<RuleNode<C>, ConfigXMLParseException>> rulesList = new ArrayList<>();
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "rule": {
rulesList.add(ruleParseFunction.apply(reader, configurations));
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return () -> {
RuleNode<C> node = null;
final ListIterator<ExceptionUnaryOperator<RuleNode<C>, ConfigXMLParseException>> iterator = rulesList.listIterator(rulesList.size());
// iterate backwards to build the singly-linked list in constant time
while (iterator.hasPrevious()) {
node = iterator.previous().apply(node);
}
return node;
};
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
static void parseAuthenticationConfigurationType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<AuthenticationConfiguration, ConfigXMLParseException>> authenticationConfigurationsMap, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) throws ConfigXMLParseException {
final String name = requireSingleAttribute(reader, "name");
if (authenticationConfigurationsMap.containsKey(name)) {
throw xmlLog.xmlDuplicateAuthenticationConfigurationName(name, reader);
}
ExceptionUnaryOperator<AuthenticationConfiguration, ConfigXMLParseException> configuration = ignored -> AuthenticationConfiguration.EMPTY;
DeferredSupplier<Provider[]> providerSupplier = new DeferredSupplier<>(Security::getProviders);
int foundBits = 0;
if (! reader.hasNext()) {
throw reader.unexpectedDocumentEnd();
}
while (reader.hasNext()) {
int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
// -- set --
case "set-host": {
if (isSet(foundBits, 0)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 0);
final String hostName = parseNameType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useHost(hostName));
break;
}
case "set-port": {
if (isSet(foundBits, 1)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 1);
final int port = parsePortType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.usePort(port));
break;
}
// these two are a <choice> which is why they share a bit #; you can have only one of them
case "set-user-name": {
if (isSet(foundBits, 2)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 2);
final String userName = parseNameType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useName(userName));
break;
}
case "set-anonymous": {
if (isSet(foundBits, 2)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 2);
parseEmptyType(reader);
configuration = andThenOp(configuration, AuthenticationConfiguration::useAnonymous);
break;
}
case "set-mechanism-realm": {
if (isSet(foundBits, 3)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 3);
final String realm = parseNameType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useRealm(realm));
break;
}
case "rewrite-user-name-regex": {
if (isSet(foundBits, 4)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 4);
final NameRewriter nameRewriter = parseRegexSubstitutionType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.rewriteUser(nameRewriter));
break;
}
case "set-mechanism-properties": {
if (isSet(foundBits, 5)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 5);
final Map<String, String> mechanismProperties = parsePropertiesType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useMechanismProperties(mechanismProperties));
break;
}
case "allow-all-sasl-mechanisms": {
if (isSet(foundBits, 6)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 6);
parseEmptyType(reader);
configuration = andThenOp(configuration, AuthenticationConfiguration::allowAllSaslMechanisms);
break;
}
case "allow-sasl-mechanisms": {
if (isSet(foundBits, 7)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 7);
final String[] names = parseNamesType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.allowSaslMechanisms(names));
break;
}
case "forbid-sasl-mechanisms": {
if (isSet(foundBits, 8)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 8);
final String[] names = parseNamesType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.forbidSaslMechanisms(names));
break;
}
case "sasl-mechanism-selector": {
if (isSet(foundBits, 14)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 14);
final SaslMechanismSelector selector = parseSaslMechanismSelectorType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.setSaslMechanismSelector(selector));
break;
}
case "credentials": {
if (isSet(foundBits, 9)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 9);
ExceptionSupplier<CredentialSource, ConfigXMLParseException> credentialSource = parseCredentialsType(reader, keyStoresMap, credentialStoresMap, providerSupplier);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useCredentials(credentialSource.get()));
break;
}
case "set-authorization-name": {
if (isSet(foundBits, 10)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 10);
final String authName = parseNameType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useAuthorizationName(authName));
break;
}
// these two are a <choice> which is why they share a bit #; you can have only one of them
case "use-system-providers": {
if (isSet(foundBits, 11)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 11);
parseEmptyType(reader);
providerSupplier.setSupplier(Security::getProviders);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useProviders(providerSupplier));
break;
}
case "use-service-loader-providers": {
if (isSet(foundBits, 11)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 11);
final String moduleName = parseModuleRefType(reader);
final ClassLoader classLoader = (moduleName == null) ? ElytronXmlParser.class.getClassLoader() : ModuleLoader.getClassLoaderFromModule(reader, moduleName);
providerSupplier.setSupplier(new ServiceLoaderSupplier<>(Provider.class, classLoader));
configuration = andThenOp(configuration, parentConfig -> parentConfig.useProviders(providerSupplier));
break;
}
// these two are a <choice> which is why they share a bit #; you can have only one of them
case "use-provider-sasl-factory": {
if (isSet(foundBits, 12)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 12);
parseEmptyType(reader);
configuration = andThenOp(configuration, AuthenticationConfiguration::useSaslClientFactoryFromProviders);
break;
}
case "use-service-loader-sasl-factory": {
if (isSet(foundBits, 12)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 12);
final String moduleName = parseModuleRefType(reader);
final ClassLoader classLoader = (moduleName == null) ? ElytronXmlParser.class.getClassLoader() : ModuleLoader.getClassLoaderFromModule(reader, moduleName);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useSaslClientFactory(new ServiceLoaderSaslClientFactory(classLoader)));
break;
}
case "set-protocol": {
if (isSet(foundBits, 13)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 13);
final String protocol = parseNameType(reader);
configuration = andThenOp(configuration, parentConfig -> parentConfig.useProtocol(protocol));
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
final ExceptionUnaryOperator<AuthenticationConfiguration, ConfigXMLParseException> finalConfiguration = configuration;
authenticationConfigurationsMap.put(name, () -> finalConfiguration.apply(AuthenticationConfiguration.EMPTY));
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse the XML match-rule group. On return, the reader will be positioned either at a start tag for an element
* that is not included in this group, or at an end tag.
*
* @param reader the XML reader
* @return the parsed match rule
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static MatchRule parseAbstractMatchRuleType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
MatchRule rule = MatchRule.ALL;
int foundBits = 0;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
// -- match --
case "match-no-userinfo": {
if (isSet(foundBits, 0)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 0);
parseEmptyType(reader);
rule = rule.matchNoUser();
break;
}
case "match-userinfo": {
if (isSet(foundBits, 0)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 0);
rule = rule.matchUser(parseNameType(reader));
break;
}
case "match-protocol": {
if (isSet(foundBits, 1)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 1);
rule = rule.matchProtocol(parseNameType(reader));
break;
}
case "match-host": {
if (isSet(foundBits, 2)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 2);
rule = rule.matchHost(parseNameType(reader));
break;
}
case "match-path": {
if (isSet(foundBits, 3)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 3);
rule = rule.matchPath(parseNameType(reader));
break;
}
case "match-port": {
if (isSet(foundBits, 4)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 4);
rule = rule.matchPort(parsePortType(reader));
break;
}
case "match-urn": {
if (isSet(foundBits, 5)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 5);
rule = rule.matchUrnName(parseNameType(reader));
break;
}
case "match-domain": {
if (isSet(foundBits, 6)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 6);
rule = rule.matchLocalSecurityDomain(parseNameType(reader));
break;
}
case "match-abstract-type": {
if (isSet(foundBits, 7)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 7);
rule = parseMatchAbstractType(rule, reader);
break;
}
case "match-purpose": {
if (isSet(foundBits, 8)) throw reader.unexpectedElement();
foundBits = setBit(foundBits, 8);
rule = rule.matchPurposes(parseNamesType(reader));
break;
}
default: {
return rule;
}
}
} else {
return rule;
}
}
throw reader.unexpectedDocumentEnd();
}
private static MatchRule parseMatchAbstractType(final MatchRule rule, final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String name = null;
String authority = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "name": name = reader.getAttributeValueResolved(i); break;
case "authority": authority = reader.getAttributeValueResolved(i); break;
default: throw reader.unexpectedAttribute(i);
}
}
if (! reader.hasNext()) throw reader.unexpectedDocumentEnd();
if (reader.nextTag() != END_ELEMENT) throw reader.unexpectedElement();
return name == null && authority == null ? rule : rule.matchAbstractType(name, authority);
}
private static boolean isSet(int var, int bit) {
return (var & 1 << bit) != 0;
}
private static int setBit(int var, int bit) {
return var | 1 << bit;
}
private static <T> UnaryOperator<T> andThenOp(UnaryOperator<T> first, UnaryOperator<T> second) {
return t -> second.apply(first.apply(t));
}
private static <T, E extends Exception> ExceptionUnaryOperator<T, E> andThenOp(ExceptionUnaryOperator<T, E> first, ExceptionUnaryOperator<T, E> second) {
return t -> second.apply(first.apply(t));
}
private static ExceptionSupplier<CredentialSource, ConfigXMLParseException> parseCredentialsType(final ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap, Supplier<Provider[]> providers) throws ConfigXMLParseException {
ExceptionUnaryOperator<CredentialSource, ConfigXMLParseException> function = parent -> CredentialSource.NONE;
requireNoAttributes(reader);
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "key-store-reference": {
final ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> supplier = parseKeyStoreRefType(reader, keyStoresMap);
function = andThenOp(function, credentialSource -> credentialSource.with(new KeyStoreCredentialSource(new FixedSecurityFactory<KeyStore.Entry>(supplier.get()))));
break;
}
case "credential-store-reference": {
final ExceptionSupplier<CredentialSource, ConfigXMLParseException> supplier = parseCredentialStoreRefType(reader, credentialStoresMap);
function = andThenOp(function, credentialSource -> credentialSource.with(supplier.get()));
break;
}
case "clear-password": {
ExceptionSupplier<Password, ConfigXMLParseException> password = parseClearPassword(reader, providers);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(new PasswordCredential(password.get()))));
break;
}
case "hashed-password": {
ExceptionSupplier<Password, ConfigXMLParseException> password = parseHashedPassword(reader, providers);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(new PasswordCredential(password.get()))));
break;
}
case "crypt-password": {
Password password = parseCryptPassword(reader);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(new PasswordCredential(password))));
break;
}
case "key-pair": {
KeyPair keyPair = parseKeyPair(reader);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(new KeyPairCredential(keyPair))));
break;
}
case "certificate": {
X509CertificateChainPrivateCredential credential = parseCertificateType(reader);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(credential)));
break;
}
case "public-key-pem": {
PublicKey publicKey = parsePem(reader, PublicKey.class);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(new PublicKeyCredential(publicKey))));
break;
}
case "bearer-token": {
BearerTokenCredential bearerToken = parseBearerTokenType(reader);
function = andThenOp(function, credentialSource -> credentialSource.with(IdentityCredentials.NONE.withCredential(bearerToken)));
break;
}
case "oauth2-bearer-token": {
CredentialSource credentialStore = parseOAuth2BearerTokenType(reader);
function = andThenOp(function, credentialSource -> credentialSource.with(credentialStore));
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
final ExceptionUnaryOperator<CredentialSource, ConfigXMLParseException> finalFunction = function;
return () -> finalFunction.apply(null);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static ExceptionSupplier<Password, ConfigXMLParseException> parseHashedPassword(final ConfigurationXMLStreamReader reader, Supplier<Provider[]> providers) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String algorithm = null;
String hash = null;
String salt = null;
int iterationCount = -1;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "algorithm": {
algorithm = reader.getAttributeValueResolved(i);
break;
}
case "hash": {
hash = reader.getAttributeValueResolved(i);
break;
}
case "salt": {
salt = reader.getAttributeValueResolved(i);
break;
}
case "iteration-count": {
iterationCount = reader.getIntAttributeValueResolved(i);
if (iterationCount < 1) {
throw xmlLog.xmlInvalidIterationCount(reader, iterationCount);
}
break;
}
default: {
throw reader.unexpectedAttribute(i);
}
}
}
if (algorithm == null) throw reader.missingRequiredAttribute("", "algorithm");
if (hash == null) throw reader.missingRequiredAttribute("", "hash");
byte[] hashBytes = CodePointIterator.ofString(hash).base64Decode().drain();
final PasswordSpec passwordSpec;
if (salt != null) {
byte[] saltBytes = CodePointIterator.ofString(salt).base64Decode().drain();
if (iterationCount != -1) {
passwordSpec = new IteratedSaltedHashPasswordSpec(hashBytes, saltBytes, iterationCount);
} else {
passwordSpec = new SaltedHashPasswordSpec(hashBytes, saltBytes);
}
} else {
if (iterationCount != -1) {
passwordSpec = new IteratedHashPasswordSpec(hashBytes, iterationCount);
} else {
passwordSpec = new HashPasswordSpec(hashBytes);
}
}
final XMLLocation location = reader.getLocation();
final String finalAlgorithm = algorithm;
return () -> {
try {
final PasswordFactory instance = PasswordFactory.getInstance(finalAlgorithm, providers);
return instance.generatePassword(passwordSpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw xmlLog.xmlFailedToCreateCredential(location, e);
}
};
}
private static Password parseCryptPassword(final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final String crypt = requireSingleAttribute(reader, "crypt");
final Password password;
try {
password = ModularCrypt.decode(crypt);
} catch (InvalidKeySpecException e) {
throw xmlLog.xmlFailedToCreateCredential(reader.getLocation(), e);
}
if (! reader.hasNext()) throw reader.unexpectedDocumentEnd();
if (reader.nextTag() != END_ELEMENT) throw reader.unexpectedContent();
return password;
}
private static KeyPair parseKeyPair(final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
requireNoAttributes(reader);
PrivateKey privateKey = null;
PublicKey publicKey = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "private-key-pem": {
if (privateKey != null) throw reader.unexpectedElement();
privateKey = parsePem(reader, PrivateKey.class);
break;
}
case "public-key-pem": {
if (publicKey != null) throw reader.unexpectedElement();
publicKey = parsePem(reader, PublicKey.class);
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
if (privateKey == null) throw reader.missingRequiredElement(NS_ELYTRON_1_0, "private-key-pem");
if (publicKey == null) throw reader.missingRequiredElement(NS_ELYTRON_1_0, "public-key-pem");
return new KeyPair(publicKey, privateKey);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static X509CertificateChainPrivateCredential parseCertificateType(final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
requireNoAttributes(reader);
PrivateKey privateKey = null;
X509Certificate[] certificates = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "private-key-pem": {
if (privateKey != null) throw reader.unexpectedElement();
privateKey = parsePem(reader, PrivateKey.class);
break;
}
case "pem": {
if (certificates != null) throw reader.unexpectedElement();
certificates = parseMultiPem(reader, X509Certificate.class, X509Certificate[]::new);
break;
}
default: {
throw reader.unexpectedElement();
}
}
} else if (tag == END_ELEMENT) {
if (privateKey == null) throw reader.missingRequiredElement(NS_ELYTRON_1_0, "private-key-pem");
if (certificates == null) throw reader.missingRequiredElement(NS_ELYTRON_1_0, "pem");
return new X509CertificateChainPrivateCredential(privateKey, certificates);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
private static <P> P[] parseMultiPem(final ConfigurationXMLStreamReader reader, final Class<P> pemType, final IntFunction<P[]> ctor) throws ConfigXMLParseException {
requireNoAttributes(reader);
if (reader.hasNext()) {
final int next = reader.next();
if (reader.hasText()) {
final Iterator<PemEntry<?>> pemContent = Pem.parsePemContent(CodePointIterator.ofString(reader.getElementText()).skip(Character::isWhitespace));
if (! reader.hasNext()) throw reader.unexpectedDocumentEnd();
if (reader.nextTag() != END_ELEMENT) throw reader.unexpectedContent();
final ArrayList<P> arrayList = new ArrayList<>();
while (pemContent.hasNext()) {
final PemEntry<?> pemEntry = pemContent.next();
final P pem = pemEntry.tryCast(pemType);
if (pem == null) throw xmlLog.xmlWrongPemType(reader, pemType, pemEntry.getEntry().getClass());
arrayList.add(pem);
}
if (arrayList.isEmpty()) throw xmlLog.xmlNoPemContent(reader);
return arrayList.toArray(ctor.apply(arrayList.size()));
} else {
throw reader.unexpectedContent();
}
} else {
throw reader.unexpectedDocumentEnd();
}
}
private static <P> P parsePem(final ConfigurationXMLStreamReader reader, final Class<P> pemType) throws ConfigXMLParseException {
requireNoAttributes(reader);
if (reader.hasNext()) {
final int next = reader.next();
if (reader.hasText()) {
final Iterator<PemEntry<?>> pemContent = Pem.parsePemContent(CodePointIterator.ofString(reader.getElementText()).skip(Character::isWhitespace));
if (! reader.hasNext()) throw reader.unexpectedDocumentEnd();
if (reader.nextTag() != END_ELEMENT) throw reader.unexpectedContent();
if (! pemContent.hasNext()) throw xmlLog.xmlNoPemContent(reader);
final PemEntry<?> pemEntry = pemContent.next();
final P pem = pemEntry.tryCast(pemType);
if (pem == null) throw xmlLog.xmlWrongPemType(reader, pemType, pemEntry.getEntry().getClass());
return pem;
} else {
throw reader.unexpectedContent();
}
} else {
throw reader.unexpectedDocumentEnd();
}
}
/**
* Parse an XML element of type {@code key-stores-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the map of key stores to use
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static void parseKeyStoresType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
requireNoAttributes(reader);
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "key-store": {
parseKeyStoreType(reader, keyStoresMap);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code key-store-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the map of key stores to use
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static void parseKeyStoreType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String name = null;
String type = null;
String provider = null;
Boolean wrap = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "type": {
if (type != null) throw reader.unexpectedAttribute(i);
type = reader.getAttributeValueResolved(i);
break;
}
case "provider": {
if (provider != null) throw reader.unexpectedAttribute(i);
provider = reader.getAttributeValueResolved(i);
break;
}
case "name": {
if (name != null) throw reader.unexpectedAttribute(i);
name = reader.getAttributeValueResolved(i);
break;
}
case "wrap-passwords": {
if (wrap != null) throw reader.unexpectedAttribute(i);
wrap = Boolean.valueOf(Boolean.parseBoolean(reader.getAttributeValueResolved(i)));
break;
}
default:
throw reader.unexpectedAttribute(i);
}
}
if (type == null) {
throw missingAttribute(reader, "type");
}
if (name == null) {
throw missingAttribute(reader, "name");
}
final XMLLocation location = reader.getLocation();
ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory = null;
boolean gotSource = false;
boolean gotCredential = false;
String fileSource = null;
String resourceSource = null;
URI uriSource = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "key-store-credential": {
// group 2
if (gotCredential) {
throw reader.unexpectedElement();
}
gotCredential = true;
final XMLLocation nestedLocation = reader.getLocation();
final ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> entryFactory = parseKeyStoreRefType(reader, keyStoresMap);
passwordFactory = () -> {
final KeyStore.Entry entry = entryFactory.get();
if (entry instanceof PasswordEntry) try {
final Password password = ((PasswordEntry) entry).getPassword();
final PasswordFactory passwordFactory1 = PasswordFactory.getInstance(password.getAlgorithm());
final ClearPasswordSpec passwordSpec = passwordFactory1.getKeySpec(password, ClearPasswordSpec.class);
return passwordSpec.getEncodedPassword();
} catch (GeneralSecurityException e) {
throw xmlLog.xmlFailedToCreateCredential(nestedLocation, e);
}
return null;
};
break;
}
case "key-store-clear-password": {
// group 2
if (gotCredential) {
throw reader.unexpectedElement();
}
gotCredential = true;
final char[] clearPassword = ((ClearPassword) parseClearPassword(reader, Security::getProviders).get()).getPassword();
passwordFactory = () -> clearPassword;
break;
}
case "file": {
// group 1
if (gotSource || gotCredential) {
throw reader.unexpectedElement();
}
gotSource = true;
fileSource = parseNameType(reader);
break;
}
case "resource": {
// group 1
if (gotSource || gotCredential) {
throw reader.unexpectedElement();
}
gotSource = true;
resourceSource = parseNameType(reader);
break;
}
case "uri": {
// group 1
if (gotSource || gotCredential) {
throw reader.unexpectedElement();
}
gotSource = true;
uriSource = parseUriType(reader);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
ExceptionSupplier<KeyStore, ConfigXMLParseException> keyStoreFactory = new KeyStoreCreateFactory(provider, type, location);
if (wrap == Boolean.TRUE) {
keyStoreFactory = new PasswordKeyStoreFactory(keyStoreFactory);
}
if (fileSource != null) {
keyStoreFactory = new FileLoadingKeyStoreFactory(keyStoreFactory, passwordFactory, fileSource, location);
} else if (resourceSource != null) {
keyStoreFactory = new ResourceLoadingKeyStoreFactory(keyStoreFactory, passwordFactory, resourceSource, location);
} else if (uriSource != null) {
keyStoreFactory = new URILoadingKeyStoreFactory(keyStoreFactory, passwordFactory, uriSource, location);
} else {
keyStoreFactory = new NullLoadingKeyStoreFactory(keyStoreFactory, passwordFactory, location);
}
keyStoresMap.put(name, keyStoreFactory);
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code key-store-ref-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the map of key stores to use
* @return the key store entry factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> parseKeyStoreRefType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
final XMLLocation location = reader.getLocation();
String keyStoreName = null;
String alias = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "key-store-name": {
if (keyStoreName != null) throw reader.unexpectedAttribute(i);
keyStoreName = reader.getAttributeValueResolved(i);
break;
}
case "alias": {
if (alias != null) throw reader.unexpectedAttribute(i);
alias = reader.getAttributeValueResolved(i);
break;
}
default: throw reader.unexpectedElement();
}
}
if (keyStoreName == null) {
throw missingAttribute(reader, "key-store-name");
}
ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> keyStoreCredential = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "key-store-credential": {
if (keyStoreCredential != null) throw reader.unexpectedElement();
keyStoreCredential = parseKeyStoreRefType(reader, keyStoresMap);
break;
}
case "key-store-clear-password": {
if (keyStoreCredential != null) throw reader.unexpectedElement();
ExceptionSupplier<Password, ConfigXMLParseException> credential = parseClearPassword(reader, Security::getProviders);
keyStoreCredential = () -> new PasswordEntry(credential.get());
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
final ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> finalKeyStoreCredential = keyStoreCredential;
final String finalKeyStoreName = keyStoreName;
final String finalAlias = alias;
return () -> {
try {
final ExceptionSupplier<KeyStore, ConfigXMLParseException> keyStoreSupplier = keyStoresMap.get(finalKeyStoreName);
if (keyStoreSupplier == null) {
throw xmlLog.xmlUnknownKeyStoreSpecified(location);
}
final KeyStore.ProtectionParameter protectionParameter;
final KeyStore.Entry entry = finalKeyStoreCredential == null ? null : finalKeyStoreCredential.get();
if (entry instanceof PasswordEntry) {
final Password password = ((PasswordEntry) entry).getPassword();
final PasswordFactory passwordFactory = PasswordFactory.getInstance(password.getAlgorithm());
final ClearPasswordSpec spec = passwordFactory.getKeySpec(password, ClearPasswordSpec.class);
protectionParameter = new KeyStore.PasswordProtection(spec.getEncodedPassword());
} else if (entry instanceof KeyStore.SecretKeyEntry) {
final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey();
final SecretKeyFactory instance = SecretKeyFactory.getInstance(secretKey.getAlgorithm());
final SecretKeySpec keySpec = (SecretKeySpec) instance.getKeySpec(secretKey, SecretKeySpec.class);
final byte[] encoded = keySpec.getEncoded();
protectionParameter = encoded == null ? null : new KeyStore.PasswordProtection(new String(encoded, StandardCharsets.UTF_8).toCharArray());
} else {
protectionParameter = null;
}
if (finalAlias != null) {
return keyStoreSupplier.get().getEntry(finalAlias, protectionParameter == null ? null : protectionParameter);
} else {
// allow to retrieve entry without providing alias only if keystore includes one and only entry.
if (keyStoreSupplier.get().size() > 1) {
throw xmlLog.missingAlias(location);
} else if (keyStoreSupplier.get().aliases().hasMoreElements()) {
return keyStoreSupplier.get().getEntry(keyStoreSupplier.get().aliases().nextElement(), protectionParameter == null ? null : protectionParameter);
} else {
return null;
}
}
} catch (GeneralSecurityException e) {
throw xmlLog.xmlFailedToLoadKeyStoreData(location, e);
}
};
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code trust-store-ref-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the map of key stores to use
* @return the key store entry factory
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static ExceptionSupplier<KeyStore, ConfigXMLParseException> parseTrustStoreRefType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
final XMLLocation location = reader.getLocation();
String keyStoreName = null;
String alias = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "key-store-name": {
if (keyStoreName != null) throw reader.unexpectedAttribute(i);
keyStoreName = reader.getAttributeValueResolved(i);
break;
}
default: throw reader.unexpectedElement();
}
}
if (keyStoreName == null) {
throw missingAttribute(reader, "key-store-name");
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
final String finalKeyStoreName = keyStoreName;
final ExceptionSupplier<KeyStore, ConfigXMLParseException> keyStoreSupplier = keyStoresMap.get(finalKeyStoreName);
if (keyStoreSupplier == null) {
throw xmlLog.xmlUnknownKeyStoreSpecified(location);
}
return keyStoreSupplier;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
static ExceptionSupplier<CredentialSource, ConfigXMLParseException> parseCredentialStoreRefType(ConfigurationXMLStreamReader reader, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String storeName = null;
String alias = null;
String clearText = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "store": {
if (storeName != null) throw reader.unexpectedAttribute(i);
storeName = reader.getAttributeValueResolved(i);
break;
}
case "alias": {
if (alias != null) throw reader.unexpectedAttribute(i);
alias = reader.getAttributeValueResolved(i);
break;
}
case "clear-text": {
if (clearText != null) throw reader.unexpectedAttribute(i);
clearText = reader.getAttributeValueResolved(i);
break;
}
default:
throw reader.unexpectedAttribute(i);
}
}
if (! reader.hasNext()) throw reader.unexpectedDocumentEnd();
if (reader.nextTag() != END_ELEMENT) throw reader.unexpectedContent();
return createCredentialStoreSupplier(reader.getLocation(), storeName, alias, clearText, credentialStoresMap);
}
private static ExceptionSupplier<CredentialSource, ConfigXMLParseException> createCredentialStoreSupplier(final XMLLocation location, final String storeName, final String alias, final String clearText, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) {
return () -> {
final ExceptionSupplier<CredentialStore, ConfigXMLParseException> supplier = credentialStoresMap.get(storeName);
if (supplier == null) {
throw xmlLog.xmlCredentialStoreNameNotDefined(location, storeName);
}
final CredentialStore credentialStore = supplier.get();
return new CredentialStoreCredentialSource(credentialStore, alias);
};
}
/**
* Parse an XML element of type {@code credential-stores-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the key stores map
* @param credentialStoresMap the map of credential stores to use @throws ConfigXMLParseException if the resource failed to be parsed
*/
private static void parseCredentialStoresType(ConfigurationXMLStreamReader reader, Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
if (attributeCount > 0) {
throw reader.unexpectedAttribute(0);
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
switch (reader.getNamespaceURI()) {
case NS_ELYTRON_1_0: break;
default: throw reader.unexpectedElement();
}
switch (reader.getLocalName()) {
case "credential-store": {
parseCredentialStoreType(reader, keyStoresMap, credentialStoresMap);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code key-store-type} from an XML reader.
*
* @param reader the XML stream reader
* @param keyStoresMap the key stores map
* @param credentialStoresMap the map of credential stores to fill @throws ConfigXMLParseException if the resource failed to be parsed
*/
private static void parseCredentialStoreType(ConfigurationXMLStreamReader reader, Map<String, ExceptionSupplier<KeyStore, ConfigXMLParseException>> keyStoresMap, final Map<String, ExceptionSupplier<CredentialStore, ConfigXMLParseException>> credentialStoresMap) throws ConfigXMLParseException {
final XMLLocation location = reader.getLocation();
final int attributeCount = reader.getAttributeCount();
String name = null;
String type = null;
String provider = null;
for (int i = 0; i < attributeCount; i ++) {
final String attributeNamespace = reader.getAttributeNamespace(i);
if (attributeNamespace != null && ! attributeNamespace.isEmpty()) {
throw reader.unexpectedAttribute(i);
}
switch (reader.getAttributeLocalName(i)) {
case "type": {
if (type != null) throw reader.unexpectedAttribute(i);
type = reader.getAttributeValueResolved(i);
break;
}
case "provider": {
if (provider != null) throw reader.unexpectedAttribute(i);
provider = reader.getAttributeValueResolved(i);
break;
}
case "name": {
if (name != null) throw reader.unexpectedAttribute(i);
name = reader.getAttributeValueResolved(i);
break;
}
default:
throw reader.unexpectedAttribute(i);
}
}
if (name == null) {
throw missingAttribute(reader, "name");
}
final Map<String, String> attributesMap = new HashMap<>();
int attributesSectionCount = 0;
ExceptionSupplier<CredentialSource, ConfigXMLParseException> credentialSourceSupplier = null;
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
switch (reader.getNamespaceURI()) {
case NS_ELYTRON_1_0: break;
default: throw reader.unexpectedElement();
}
switch (reader.getLocalName()) {
case "attributes": {
if (++attributesSectionCount > 2) throw reader.unexpectedContent();
parseAttributesType(reader, attributesMap);
break;
}
case "protection-parameter-credentials": {
if (++attributesSectionCount > 2) throw reader.unexpectedContent();
credentialSourceSupplier = parseCredentialsType(reader, keyStoresMap, credentialStoresMap, Security::getProviders);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
if (!credentialStoresMap.containsKey(name)) {
ExceptionSupplier<CredentialStore, ConfigXMLParseException> credentialStoreSecurityFactory = new CredentialStoreFactory(name, type, attributesMap, provider, location, credentialSourceSupplier);
credentialStoresMap.put(name, credentialStoreSecurityFactory);
} else {
throw xmlLog.duplicateCredentialStoreName(reader, name);
}
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
// common types
/**
* Parse attributes {@code attributes-type} from an XML reader.
*
* @param reader the XML stream reader
* @param attributesMap the map to put attributes to.
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
private static void parseAttributesType(ConfigurationXMLStreamReader reader, final Map<String, String> attributesMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
if (attributeCount > 0) {
throw reader.unexpectedAttribute(0);
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
switch (reader.getNamespaceURI()) {
case NS_ELYTRON_1_0: break;
default: throw reader.unexpectedElement();
}
switch (reader.getLocalName()) {
case "attribute": {
parseAttributeType(reader, attributesMap);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an attribute {@code attribute-type} from an XML reader.
*
* @param reader the XML stream reader
* @param attributesMap the map to put attributes to.
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
private static void parseAttributeType(ConfigurationXMLStreamReader reader, final Map<String, String> attributesMap) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String name = null;
String value = null;
for (int i = 0; i < attributeCount; i ++) {
final String attributeNamespace = reader.getAttributeNamespace(i);
if (attributeNamespace != null && ! attributeNamespace.isEmpty()) {
throw reader.unexpectedAttribute(i);
}
switch (reader.getAttributeLocalName(i)) {
case "name": {
if (name != null) throw reader.unexpectedAttribute(i);
name = reader.getAttributeValueResolved(i);
break;
}
case "value": {
if (value != null) throw reader.unexpectedAttribute(i);
value = reader.getAttributeValueResolved(i);
break;
}
default:
throw reader.unexpectedAttribute(i);
}
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedContent();
} else if (tag == END_ELEMENT) {
if (!attributesMap.containsKey(name)) {
attributesMap.put(name, value);
} else {
throw xmlLog.duplicateAttributeFound(reader, name);
}
return;
}
throw reader.unexpectedContent();
}
throw reader.unexpectedContent();
}
/**
* Parse an XML element of type {@code empty-type} from an XML reader.
*
* @param reader the XML stream reader
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static void parseEmptyType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
requireNoAttributes(reader);
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code name-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the parsed name
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static String parseNameType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
return parseNameType(reader, false);
}
/**
* Parse an XML element of type {@code name-type} from an XML reader.
*
* @param reader the XML stream reader
* @param optional is the name attribute optional?
* @return the parsed name
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static String parseNameType(ConfigurationXMLStreamReader reader, boolean optional) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String name = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("name")) {
name = reader.getAttributeValueResolved(i);
} else {
throw reader.unexpectedAttribute(i);
}
}
if (name == null && !optional) {
throw missingAttribute(reader, "name");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return name;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code port-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the port number (1-65535 inclusive)
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static int parsePortType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
int number = -1;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("number")) {
String s = reader.getAttributeValueResolved(i);
try {
number = Integer.parseInt(s);
} catch (NumberFormatException ignored) {
throw invalidPortNumber(reader, i);
}
if (number < 1 || number > 65535) {
throw invalidPortNumber(reader, i);
}
} else {
throw reader.unexpectedAttribute(i);
}
}
if (number == -1) {
throw missingAttribute(reader, "number");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return number;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code regex-substitution-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the regular expression based name rewriter
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static NameRewriter parseRegexSubstitutionType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
Pattern pattern = null;
String replacement = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("pattern")) {
pattern = Pattern.compile(reader.getAttributeValueResolved(i));
} else if (reader.getAttributeLocalName(i).equals("replacement")) {
replacement = reader.getAttributeValueResolved(i);
} else {
throw reader.unexpectedAttribute(i);
}
}
if (pattern == null) {
throw missingAttribute(reader, "pattern");
}
if (replacement == null) {
throw missingAttribute(reader, "replacement");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return new RegexNameRewriter(pattern, replacement, true);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code names-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the array of parsed names
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static String[] parseNamesType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String[] names = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("names")) {
String s = reader.getAttributeValueResolved(i);
names = s.trim().split("\\s+");
} else {
throw reader.unexpectedAttribute(i);
}
}
if (names == null) {
throw missingAttribute(reader, "names");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return names;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code uri-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the parsed URI
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static URI parseUriType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
URI uri = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("uri")) {
uri = reader.getURIAttributeValueResolved(i);
} else {
throw reader.unexpectedAttribute(i);
}
}
if (uri == null) {
throw missingAttribute(reader, "uri");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return uri;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
static SaslMechanismSelector parseSaslMechanismSelectorType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
SaslMechanismSelector selector = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("selector")) {
selector = SaslMechanismSelector.fromString(reader.getAttributeValueResolved(i));
} else {
throw reader.unexpectedAttribute(i);
}
}
if (selector == null) {
throw missingAttribute(reader, "selector");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return selector;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code ssl-cipher-selector-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the parsed cipher suite selector
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static CipherSuiteSelector parseCipherSuiteSelectorType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
CipherSuiteSelector selector = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("selector")) {
selector = CipherSuiteSelector.fromString(reader.getAttributeValueResolved(i));
} else {
throw reader.unexpectedAttribute(i);
}
}
if (selector == null) {
throw missingAttribute(reader, "selector");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return selector;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code names} which yields a protocol selector from an XML reader.
*
* @param reader the XML stream reader
* @return the parsed protocol selector
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static ProtocolSelector parseProtocolSelectorNamesType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
ProtocolSelector selector = ProtocolSelector.empty();
for (String name : parseNamesType(reader)) {
selector = selector.add(name);
}
return selector;
}
/**
* Parse an XML element of type {@code module-ref-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the corresponding module name
* @throws ConfigXMLParseException if the resource failed to be parsed or the module is not found
*/
static String parseModuleRefType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String moduleName = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("module-name")) {
moduleName = reader.getAttributeValueResolved(i);
} else {
throw reader.unexpectedAttribute(i);
}
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return moduleName;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code clear-password-type} from an XML reader.
*
* @param reader the XML stream reader
* @return the clear password characters
* @throws ConfigXMLParseException if the resource failed to be parsed or the module is not found
*/
static ExceptionSupplier<Password, ConfigXMLParseException> parseClearPassword(ConfigurationXMLStreamReader reader, Supplier<Provider[]> providers) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
char[] password = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
if (reader.getAttributeLocalName(i).equals("password")) {
password = reader.getAttributeValueResolved(i).toCharArray();
} else {
throw reader.unexpectedAttribute(i);
}
}
if (password == null) {
throw missingAttribute(reader, "password");
}
if (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
final XMLLocation location = reader.getLocation();
final char[] finalPassword = password;
return () -> {
try {
PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, providers);
return Assert.assertNotNull(factory.generatePassword(new ClearPasswordSpec(finalPassword)).castAs(ClearPassword.class));
} catch (InvalidKeySpecException | NoSuchAlgorithmException cause) {
throw xmlLog.xmlFailedToCreateCredential(location, cause);
}
};
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
static Map<String, String> parsePropertiesType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
if (reader.getAttributeCount() > 0) {
throw reader.unexpectedAttribute(0);
}
Map<String, String> propertiesMap = new HashMap<>();
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "property":
final int attributeCount = reader.getAttributeCount();
String key = null;
String value = null;
for (int i = 0; i < attributeCount; i++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "key":
if (key != null)
throw reader.unexpectedAttribute(i);
key = reader.getAttributeValueResolved(i);
break;
case "value":
if (value != null)
throw reader.unexpectedAttribute(i);
value = reader.getAttributeValueResolved(i);
break;
default:
throw reader.unexpectedAttribute(i);
}
}
if (key == null) {
throw missingAttribute(reader, "key");
}
if (value == null) {
throw missingAttribute(reader, "value");
}
propertiesMap.put(key, value);
if (reader.hasNext()) {
final int innerTag = reader.nextTag();
if (innerTag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (innerTag == END_ELEMENT) {
} else {
throw reader.unexpectedContent();
}
} else {
throw reader.unexpectedDocumentEnd();
}
break;
default:
throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return propertiesMap;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code bearer-token-type} from an XML reader.
*
* @param reader the XML stream reader
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static BearerTokenCredential parseBearerTokenType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
String value = requireSingleAttribute(reader, "value");
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
return new BearerTokenCredential(value);
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code oauth2-bearer-token-type} from an XML reader.
*
* @param reader the XML stream reader
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static CredentialSource parseOAuth2BearerTokenType(ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
URI tokenEndpointUri = requireSingleURIAttribute(reader, "token-endpoint-uri");
OAuth2CredentialSource.Builder builder;
try {
builder = OAuth2CredentialSource.builder(tokenEndpointUri.toURL());
} catch (MalformedURLException e) {
throw xmlLog.xmlInvalidUrl(tokenEndpointUri.toString());
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
checkElementNamespace(reader);
switch (reader.getLocalName()) {
case "resource-owner-credentials": {
builder = parseOAuth2ResourceOwnerCredentials(reader, builder);
break;
}
case "client-credentials": {
builder = parseOAuth2ClientCredentials(reader, builder);
break;
}
default: throw reader.unexpectedElement();
}
} else if (tag == END_ELEMENT) {
return builder.build();
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code oauth2-bearer-token-type} from an XML reader.
*
* @param reader the XML stream reader
* @param builder the builder
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static OAuth2CredentialSource.Builder parseOAuth2ResourceOwnerCredentials(ConfigurationXMLStreamReader reader, OAuth2CredentialSource.Builder builder) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
String userName = null;
String password = null;
for (int i = 0; i < attributeCount; i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "name": {
if (userName != null) throw reader.unexpectedAttribute(i);
userName = reader.getAttributeValueResolved(i);
break;
}
case "password": {
if (password != null) throw reader.unexpectedAttribute(i);
password = reader.getAttributeValueResolved(i);
break;
}
default: throw reader.unexpectedAttribute(i);
}
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
if (userName != null && password != null) {
return builder.useResourceOwnerPassword(userName, password);
}
return builder;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
/**
* Parse an XML element of type {@code oauth2-client-credentials-type} from an XML reader.
*
* @param reader the XML stream reader
* @param builder the builder
* @throws ConfigXMLParseException if the resource failed to be parsed
*/
static OAuth2CredentialSource.Builder parseOAuth2ClientCredentials(ConfigurationXMLStreamReader reader, OAuth2CredentialSource.Builder builder) throws ConfigXMLParseException {
String id = null;
String secret = null;
for (int i = 0; i < reader.getAttributeCount(); i ++) {
checkAttributeNamespace(reader, i);
switch (reader.getAttributeLocalName(i)) {
case "client-id": {
if (id != null) throw reader.unexpectedAttribute(i);
id = reader.getAttributeValueResolved(i);
break;
}
case "client-secret": {
if (secret != null) throw reader.unexpectedAttribute(i);
secret = reader.getAttributeValueResolved(i);
break;
}
default: throw reader.unexpectedAttribute(i);
}
}
while (reader.hasNext()) {
final int tag = reader.nextTag();
if (tag == START_ELEMENT) {
throw reader.unexpectedElement();
} else if (tag == END_ELEMENT) {
if (id != null && secret != null) {
return builder.clientCredentials(id, secret);
}
return builder;
} else {
throw reader.unexpectedContent();
}
}
throw reader.unexpectedDocumentEnd();
}
// util
private static void checkElementNamespace(final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
if (! reader.getNamespaceURI().equals(NS_ELYTRON_1_0)) {
throw reader.unexpectedElement();
}
}
private static void checkAttributeNamespace(final ConfigurationXMLStreamReader reader, final int idx) throws ConfigXMLParseException {
final String attributeNamespace = reader.getAttributeNamespace(idx);
if (attributeNamespace != null && ! attributeNamespace.isEmpty()) {
throw reader.unexpectedAttribute(idx);
}
}
private static void requireNoAttributes(final ConfigurationXMLStreamReader reader) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
if (attributeCount > 0) {
throw reader.unexpectedAttribute(0);
}
}
private static String requireSingleAttribute(final ConfigurationXMLStreamReader reader, final String attributeName) throws ConfigXMLParseException {
return requireSingleAttribute(reader, attributeName, (ExceptionSupplier<String, ConfigXMLParseException>) () -> reader.getAttributeValueResolved(0));
}
private static URI requireSingleURIAttribute(final ConfigurationXMLStreamReader reader, final String attributeName) throws ConfigXMLParseException {
return requireSingleAttribute(reader, attributeName, () -> reader.getURIAttributeValueResolved(0));
}
private static <A> A requireSingleAttribute(final ConfigurationXMLStreamReader reader, final String attributeName, ExceptionSupplier<A, ConfigXMLParseException> attributeFunction) throws ConfigXMLParseException {
final int attributeCount = reader.getAttributeCount();
if (attributeCount < 1) {
throw reader.missingRequiredAttribute("", attributeName);
}
checkAttributeNamespace(reader, 0);
if (! reader.getAttributeLocalName(0).equals(attributeName)) {
throw reader.unexpectedAttribute(0);
}
if (attributeCount > 1) {
throw reader.unexpectedAttribute(1);
}
return attributeFunction.get();
}
private static ConfigXMLParseException missingAttribute(final ConfigurationXMLStreamReader reader, final String name) {
return reader.missingRequiredAttribute(null, name);
}
private static ConfigXMLParseException invalidPortNumber(final ConfigurationXMLStreamReader reader, final int index) throws ConfigXMLParseException {
return xmlLog.xmlInvalidPortNumber(reader, reader.getAttributeValueResolved(index), reader.getAttributeLocalName(index), reader.getName());
}
static final class KeyStoreCreateFactory implements ExceptionSupplier<KeyStore, ConfigXMLParseException> {
private final String provider;
private final String type;
private final XMLLocation location;
KeyStoreCreateFactory(final String provider, final String type, final XMLLocation location) {
this.provider = provider;
this.type = type;
this.location = location;
}
public KeyStore get() throws ConfigXMLParseException {
try {
return provider == null ? KeyStore.getInstance(type) : KeyStore.getInstance(type, provider);
} catch (GeneralSecurityException e) {
throw xmlLog.xmlFailedToCreateKeyStore(location, e);
}
}
}
static final class PasswordKeyStoreFactory implements ExceptionSupplier<KeyStore, ConfigXMLParseException> {
private final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory;
PasswordKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory) {
this.delegateFactory = delegateFactory;
}
public KeyStore get() throws ConfigXMLParseException {
return new WrappingPasswordKeyStore(delegateFactory.get());
}
}
abstract static class AbstractLoadingKeyStoreFactory implements ExceptionSupplier<KeyStore, ConfigXMLParseException> {
protected final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory;
protected final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory;
protected final XMLLocation location;
protected AbstractLoadingKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory, final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory, final XMLLocation location) {
this.delegateFactory = delegateFactory;
this.passwordFactory = passwordFactory;
this.location = location;
}
public KeyStore get() throws ConfigXMLParseException {
try {
KeyStore keyStore = delegateFactory.get();
try (InputStream fis = createStream()) {
keyStore.load(fis, passwordFactory == null ? null : passwordFactory.get());
}
return keyStore;
} catch (GeneralSecurityException | IOException e) {
throw xmlLog.xmlFailedToLoadKeyStoreData(location, e);
}
}
abstract InputStream createStream() throws IOException;
}
static final class FileLoadingKeyStoreFactory extends AbstractLoadingKeyStoreFactory {
private final String fileName;
FileLoadingKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory, final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory, final String fileName, final XMLLocation location) {
super(delegateFactory, passwordFactory, location);
this.fileName = fileName;
}
InputStream createStream() throws FileNotFoundException {
return new FileInputStream(fileName);
}
}
static final class ResourceLoadingKeyStoreFactory extends AbstractLoadingKeyStoreFactory {
private final String resourceName;
ResourceLoadingKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory, final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory, final String resourceName, final XMLLocation location) {
super(delegateFactory, passwordFactory, location);
this.resourceName = resourceName;
}
InputStream createStream() throws IOException {
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
final InputStream stream = contextClassLoader.getResourceAsStream(resourceName);
if (stream == null) throw new FileNotFoundException(resourceName);
return stream;
}
}
static final class URILoadingKeyStoreFactory extends AbstractLoadingKeyStoreFactory {
private final URI uri;
URILoadingKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory, final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory, final URI uri, final XMLLocation location) {
super(delegateFactory, passwordFactory, location);
this.uri = uri;
}
InputStream createStream() throws IOException {
return uri.toURL().openStream();
}
}
static final class NullLoadingKeyStoreFactory extends AbstractLoadingKeyStoreFactory {
NullLoadingKeyStoreFactory(final ExceptionSupplier<KeyStore, ConfigXMLParseException> delegateFactory, final ExceptionSupplier<char[], ConfigXMLParseException> passwordFactory, final XMLLocation location) {
super(delegateFactory, passwordFactory, location);
}
@Override
InputStream createStream() throws IOException {
return null;
}
}
static final class PrivateKeyKeyStoreEntryCredentialFactory implements ExceptionSupplier<X509CertificateChainPrivateCredential, ConfigXMLParseException> {
private final ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> entrySupplier;
private final XMLLocation location;
PrivateKeyKeyStoreEntryCredentialFactory(final ExceptionSupplier<KeyStore.Entry, ConfigXMLParseException> entrySupplier, final XMLLocation location) {
this.entrySupplier = entrySupplier;
this.location = location;
}
public X509CertificateChainPrivateCredential get() throws ConfigXMLParseException {
final KeyStore.Entry entry = entrySupplier.get();
if (entry == null) {
throw xmlLog.keyStoreEntryMissing(location, "unknown");
}
if (entry instanceof KeyStore.PrivateKeyEntry) {
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;
final X509Certificate[] certificateChain = X500.asX509CertificateArray(privateKeyEntry.getCertificateChain());
return new X509CertificateChainPrivateCredential(privateKeyEntry.getPrivateKey(), certificateChain);
}
throw xmlLog.xmlInvalidKeyStoreEntryType(location, "unknown", KeyStore.PrivateKeyEntry.class, entry.getClass());
}
}
static final class DeferredSupplier<T> implements Supplier<T> {
private volatile Supplier<T> supplier;
private T value;
DeferredSupplier(Supplier<T> supplier) {
checkNotNullParam("supplier", supplier);
this.supplier = supplier;
}
void setSupplier(Supplier<T> supplier) {
checkNotNullParam("supplier", supplier);
this.supplier = supplier;
}
@Override
public T get() {
return supplier.get();
}
}
}