diff --git a/documentation/src/main/docs/config/secret-keys.md b/documentation/src/main/docs/config/secret-keys.md index 3f00002c1..00e088a4f 100644 --- a/documentation/src/main/docs/config/secret-keys.md +++ b/documentation/src/main/docs/config/secret-keys.md @@ -38,9 +38,9 @@ the following dependency: !!! example ```properties title="application.properties" - smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=somearbitrarycrazystringthatdoesnotmatter + smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=DDne5obnfH1RSeTg71xSZg - my.secret=${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg} + my.secret=${aes-gcm-nopadding::DLTb_9zxThxeT5iAQqswEl5Dn1ju4FdM9hIyVip35t5V} ``` The `${aes-gcm-nopadding::...}` `SecretKeyHandler` requires @@ -60,9 +60,10 @@ the following dependency: ##### Configuration -| Configuration Property | Type | Default | -|--- |--- |--- | -| `smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`
The encription key to use to decode secrets encoded by the `AES/GCM/NoPadding` algorithm. | String | | +| Configuration Property | Type | Default | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-----------| +| `smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`
The encryption key to use to decode secrets encoded by the `AES/GCM/NoPadding` algorithm. | String | | +| `"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode"`
Decode the encryption key in Base64. | boolean | false | ### Jasypt diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java index 248d26009..84b5f5a17 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java @@ -18,8 +18,6 @@ interface KeyStore { @WithDefault("PKCS12") String type(); - String password(); - Optional handler(); Map aliases(); @@ -27,8 +25,6 @@ interface KeyStore { interface Alias { Optional name(); - Optional password(); - Optional handler(); } } diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java index f81066321..9b4b7c97f 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java @@ -13,47 +13,116 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import org.eclipse.microprofile.config.spi.ConfigSource; import io.smallrye.config.AbstractLocationConfigSourceFactory; +import io.smallrye.config.ConfigMessages; import io.smallrye.config.ConfigSourceContext; -import io.smallrye.config.ConfigSourceFactory.ConfigurableConfigSourceFactory; -import io.smallrye.config.ConfigurableConfigSource; +import io.smallrye.config.ConfigSourceFactory; +import io.smallrye.config.ConfigValue; import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; import io.smallrye.config.source.keystore.KeyStoreConfig.KeyStore.Alias; -public class KeyStoreConfigSourceFactory implements ConfigurableConfigSourceFactory { +public class KeyStoreConfigSourceFactory implements ConfigSourceFactory { @Override - public Iterable getConfigSources(final ConfigSourceContext context, KeyStoreConfig keyStoreConfig) { + public Iterable getConfigSources(final ConfigSourceContext context) { + KeyStoreConfig keyStoreConfig = getKeyStoreConfig(context); + + // A keystore may contain the encryption key for a handler, so we load keystore that do not have handlers + Map prioritized = new HashMap<>(); + Map late = new HashMap<>(); + for (final Map.Entry keyStoreEntry : keyStoreConfig.keystores().entrySet()) { + if (keyStoreEntry.getValue().handler().isEmpty()) { + prioritized.put(keyStoreEntry.getKey(), keyStoreEntry.getValue()); + } else { + late.put(keyStoreEntry.getKey(), keyStoreEntry.getValue()); + } + } + List keyStoreSources = new ArrayList<>(); - for (Map.Entry keyStoreEntry : keyStoreConfig.keystores().entrySet()) { - KeyStoreConfig.KeyStore keyStore = keyStoreEntry.getValue(); - keyStoreSources.add(new ConfigurableConfigSource(new AbstractLocationConfigSourceFactory() { - @Override - protected String[] getFileExtensions() { - return new String[0]; - } + for (Map.Entry keyStoreEntry : prioritized.entrySet()) { + for (final ConfigSource configSource : loadKeyStoreSources(context, keyStoreEntry.getKey(), + keyStoreEntry.getValue())) { + keyStoreSources.add(configSource); + } + } - @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { - return new UrlKeyStoreConfigSource(url, ordinal).loadKeyStore(keyStore); - } + ConfigSourceContext keyStoreContext = new ConfigSourceContext() { + final SmallRyeConfig contextConfig = new SmallRyeConfigBuilder() + .withSources(new ConfigSourceContext.ConfigSourceContextConfigSource(context)) + .withSources(keyStoreSources).build(); - @Override - public Iterable getConfigSources(final ConfigSourceContext context) { - return loadConfigSources(keyStore.path(), 100); - } - })); + @Override + public ConfigValue getValue(final String name) { + return contextConfig.getConfigValue(name); + } + + @Override + public List getProfiles() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterateNames() { + return contextConfig.getPropertyNames().iterator(); + } + }; + + for (Map.Entry keyStoreEntry : late.entrySet()) { + for (final ConfigSource configSource : loadKeyStoreSources(keyStoreContext, keyStoreEntry.getKey(), + keyStoreEntry.getValue())) { + keyStoreSources.add(configSource); + } } return keyStoreSources; } + private static KeyStoreConfig getKeyStoreConfig(final ConfigSourceContext context) { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(new ConfigSourceContext.ConfigSourceContextConfigSource(context)) + .withMapping(KeyStoreConfig.class) + .withMappingIgnore("smallrye.config.source.keystore.*.password") + .build(); + return config.getConfigMapping(KeyStoreConfig.class); + } + + private static Iterable loadKeyStoreSources(final ConfigSourceContext context, final String name, + final KeyStoreConfig.KeyStore keyStore) { + return new AbstractLocationConfigSourceFactory() { + @Override + protected String[] getFileExtensions() { + return new String[0]; + } + + @Override + protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { + // Avoid caching the keystore password + String passwordName = "smallrye.config.source.keystore." + name + ".password"; + ConfigValue password = context.getValue(passwordName); + if (password == null || password.getValue() == null) { + throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name)); + } + return new UrlKeyStoreConfigSource(url, ordinal).loadKeyStore(keyStore, password.getValue().toCharArray()); + } + + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + return loadConfigSources(keyStore.path(), 100); + } + + }.getConfigSources(context); + } + private static class UrlKeyStoreConfigSource implements ConfigSource { private final URL url; private final int ordinal; @@ -63,10 +132,10 @@ private static class UrlKeyStoreConfigSource implements ConfigSource { this.ordinal = ordinal; } - ConfigSource loadKeyStore(KeyStoreConfig.KeyStore keyStoreConfig) throws IOException { + ConfigSource loadKeyStore(KeyStoreConfig.KeyStore keyStoreConfig, char[] password) throws IOException { try { KeyStore keyStore = KeyStore.getInstance(keyStoreConfig.type()); - keyStore.load(url.openStream(), keyStoreConfig.password().toCharArray()); + keyStore.load(url.openStream(), password); Map properties = new HashMap<>(); Enumeration aliases = keyStore.aliases(); @@ -78,11 +147,6 @@ public Optional name() { return Optional.of(alias); } - @Override - public Optional password() { - return Optional.of(keyStoreConfig.password()); - } - @Override public Optional handler() { return keyStoreConfig.handler(); @@ -90,8 +154,7 @@ public Optional handler() { }); if (keyStore.isKeyEntry(alias)) { - Key key = keyStore.getKey(alias, - aliasConfig.password().orElse(keyStoreConfig.password()).toCharArray()); + Key key = keyStore.getKey(alias, password); String encoded; Optional handler = aliasConfig.handler(); if (handler.isPresent()) { diff --git a/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java b/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java index 77ee3a60c..35ffde16c 100644 --- a/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java +++ b/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java @@ -16,6 +16,7 @@ void multipleHandlers() { Map properties = Map.of( "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI", + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "true", "aes-gcm-nopadding.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}", "smallrye.config.secret-handler.jasypt.password", "jasypt", "smallrye.config.secret-handler.jasypt.algorithm", "PBEWithHMACSHA512AndAES_256", diff --git a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java index dd1fcc50e..390184bf7 100644 --- a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java +++ b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java @@ -24,7 +24,7 @@ public SecretKeysHandler getSecretKeysHandler(final ConfigSourceContext context) throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(ENCRYPTION_KEY)); } - boolean decode = true; + boolean decode = false; ConfigValue plain = context.getValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode"); if (plain != null && plain.getValue() != null) { decode = Converters.getImplicitConverter(Boolean.class).convert(plain.getValue()); diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java index ebd521895..43a7ff3c6 100644 --- a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -19,13 +19,13 @@ class AESGCMNoPaddingSecretKeysHandlerTest { @Test void handler() { - SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() .addDiscoveredSecretKeysHandlers() .withDefaultValues(Map.of( "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI", + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "true", "my.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}", "my.expression", "${not.found:default}", "another.expression", "${my.expression}")) @@ -43,16 +43,15 @@ void keystore() { .addDiscoveredSources() .addDiscoveredSecretKeysHandlers() .withDefaultValues(Map.of( - "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", - "somearbitrarycrazystringthatdoesnotmatter", - "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "false", - "smallrye.config.source.keystore.test.path", "keystore", - "smallrye.config.source.keystore.test.password", "secret", - "smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding")) + "smallrye.config.source.keystore.\"properties\".path", "properties", + "smallrye.config.source.keystore.\"properties\".password", "arealpassword", + "smallrye.config.source.keystore.\"properties\".handler", "aes-gcm-nopadding", + "smallrye.config.source.keystore.\"key\".path", "key", + "smallrye.config.source.keystore.\"key\".password", "anotherpassword")) .build(); ConfigValue secret = config.getConfigValue("my.secret"); - assertEquals("decoded", secret.getValue()); + assertEquals("1234", secret.getValue()); } @Test @@ -86,8 +85,10 @@ void configurableSource() { "smallrye.config.source.keystore.test.password", "secret", "smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding")) .withSources((ConfigSourceFactory) context -> List.of( - new PropertiesConfigSource(Map.of("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", - "c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI"), "", 0))) + new PropertiesConfigSource(Map.of( + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", + "c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI", + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "true"), "", 0))) .build(); ConfigValue secret = config.getConfigValue("my.secret"); diff --git a/utils/crypto/src/test/resources/key b/utils/crypto/src/test/resources/key new file mode 100644 index 000000000..cfa938083 Binary files /dev/null and b/utils/crypto/src/test/resources/key differ diff --git a/utils/crypto/src/test/resources/properties b/utils/crypto/src/test/resources/properties new file mode 100644 index 000000000..679464277 Binary files /dev/null and b/utils/crypto/src/test/resources/properties differ