From 711d5c48e0f6cec9e5c7639907285617c6a18a51 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 9 Oct 2024 20:06:51 +0200 Subject: [PATCH 01/27] Make Dispatchers and Sources self described Also introduce two "test" ones, one dispatcher and one source, usable ONLY for testing, as those two have nothing with "encryption". --- .../components/secdispatcher/MasterMeta.java | 13 ++ .../plexus/components/secdispatcher/Meta.java | 91 ++++++++++++ .../secdispatcher/SecDispatcher.java | 11 +- .../internal/DefaultSecDispatcher.java | 22 ++- .../secdispatcher/internal/Dispatcher.java | 6 + ...rPasswordSource.java => MasterSource.java} | 8 +- .../internal/dispatchers/TestDispatcher.java | 74 ++++++++++ ...sswordSource.java => EnvMasterSource.java} | 41 +++++- ...dSource.java => GpgAgentMasterSource.java} | 51 ++++++- ...eSupport.java => MasterSourceSupport.java} | 6 +- ...rt.java => PrefixMasterSourceSupport.java} | 4 +- ...e.java => SystemPropertyMasterSource.java} | 41 +++++- .../internal/sources/TestMasterSource.java | 72 ++++++++++ .../internal/DefaultSecDispatcherTest.java | 132 ++++++++---------- .../internal/dispatcher/StaticDispatcher.java | 43 ------ .../sources/StaticMasterPasswordSource.java | 32 ----- 16 files changed, 470 insertions(+), 177 deletions(-) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/{MasterPasswordSource.java => MasterSource.java} (88%) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/{EnvMasterPasswordSource.java => EnvMasterSource.java} (56%) rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/{GpgAgentMasterPasswordSource.java => GpgAgentMasterSource.java} (73%) rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/{MasterPasswordSourceSupport.java => MasterSourceSupport.java} (89%) rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/{PrefixMasterPasswordSourceSupport.java => PrefixMasterSourceSupport.java} (90%) rename src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/{SystemPropertyMasterPasswordSource.java => SystemPropertyMasterSource.java} (55%) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java delete mode 100644 src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatcher/StaticDispatcher.java delete mode 100644 src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java new file mode 100644 index 0000000..85dfd28 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java @@ -0,0 +1,13 @@ +package org.codehaus.plexus.components.secdispatcher; + +import java.util.Map; + +/** + * Meta description of master password source. + */ +public interface MasterMeta extends Meta { + /** + * Creates source configuration that can be used as "masterSource". + */ + String createConfig(Map data); +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java new file mode 100644 index 0000000..2fe4544 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java @@ -0,0 +1,91 @@ +package org.codehaus.plexus.components.secdispatcher; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +/** + * Meta description of dispatcher. + */ +public interface Meta { + class Field { + private final String key; + private final boolean optional; + private final String defaultValue; + private final String description; + + private Field(String key, boolean optional, String defaultValue, String description) { + this.key = requireNonNull(key); + this.optional = optional; + this.defaultValue = defaultValue; + this.description = requireNonNull(description); + } + + public String getKey() { + return key; + } + + public boolean isOptional() { + return optional; + } + + public Optional getDefaultValue() { + return Optional.ofNullable(defaultValue); + } + + public String getDescription() { + return description; + } + + public static Builder builder(String key) { + return new Builder(key); + } + + public static class Builder { + private final String key; + private boolean optional; + private String defaultValue; + private String description; + + private Builder(String key) { + this.key = requireNonNull(key); + } + + public Builder optional(boolean optional) { + this.optional = optional; + return this; + } + + public Builder defaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + public Builder description(String description) { + this.description = requireNonNull(description); + return this; + } + + public Field build() { + return new Field(key, optional, defaultValue, description); + } + } + } + + /** + * The key of the item. + */ + String id(); + + /** + * Returns the display (human) name of the item. + */ + String displayName(); + + /** + * Returns the configuration fields of the item. + */ + Collection fields(); +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 04ef9dd..c7061e3 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -46,15 +46,20 @@ public interface SecDispatcher { String DISPATCHER_NAME_ATTR = "name"; /** - * Returns the set of available dispatcher names, never {@code null}. + * Returns the set of available dispatcher metadata, never {@code null}. */ - Set availableDispatchers(); + Set availableDispatchers(); /** * Returns the set of available ciphers, never {@code null}. */ Set availableCiphers(); + /** + * Returns the set of available master password sources metadata, never {@code null}. + */ + Set availableMasterSourcesMetadata(); + /** * Encrypt given plaintext string. * @@ -87,7 +92,7 @@ public interface SecDispatcher { * Writes the effective configuration. * * @param configuration The configuration to write, may not be {@code null} - * @throws IOException In case of IO problem + * x * @throws IOException In case of IO problem */ void writeConfiguration(SettingsSecurity configuration) throws IOException; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 131c0de..bf3a110 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -28,6 +28,8 @@ import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; +import org.codehaus.plexus.components.secdispatcher.MasterMeta; +import org.codehaus.plexus.components.secdispatcher.Meta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; @@ -44,25 +46,25 @@ public class DefaultSecDispatcher implements SecDispatcher { public static final String ATTR_STOP = "]"; protected final PlexusCipher cipher; - protected final Map masterPasswordSources; + protected final Map masterSources; protected final Map dispatchers; protected final String configurationFile; @Inject public DefaultSecDispatcher( PlexusCipher cipher, - Map masterPasswordSources, + Map masterSources, Map dispatchers, @Named("${configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String configurationFile) { this.cipher = requireNonNull(cipher); - this.masterPasswordSources = requireNonNull(masterPasswordSources); + this.masterSources = requireNonNull(masterSources); this.dispatchers = requireNonNull(dispatchers); this.configurationFile = requireNonNull(configurationFile); } @Override - public Set availableDispatchers() { - return Set.copyOf(dispatchers.keySet()); + public Set availableDispatchers() { + return Set.copyOf(dispatchers.values().stream().map(Dispatcher::meta).collect(Collectors.toSet())); } @Override @@ -70,6 +72,12 @@ public Set availableCiphers() { return cipher.availableCiphers(); } + @Override + public Set availableMasterSourcesMetadata() { + return Set.copyOf( + masterSources.values().stream().map(MasterSource::meta).collect(Collectors.toSet())); + } + @Override public String encrypt(String str, Map attr) throws SecDispatcherException { if (isEncryptedString(str)) return str; @@ -204,12 +212,12 @@ private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatche } private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException { - if (sec == null && !mandatory) { + if ((sec == null || sec.getMasterSource() == null) && !mandatory) { return null; } requireNonNull(sec, "configuration is null"); String masterSource = requireNonNull(sec.getMasterSource(), "masterSource is null"); - for (MasterPasswordSource masterPasswordSource : masterPasswordSources.values()) { + for (MasterSource masterPasswordSource : masterSources.values()) { String masterPassword = masterPasswordSource.handle(masterSource); if (masterPassword != null) return masterPassword; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java index de030a8..7fc495a 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java @@ -15,6 +15,7 @@ import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.Meta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -32,6 +33,11 @@ public interface Dispatcher { */ String CONF_MASTER_PASSWORD = "masterPassword"; + /** + * The metadata of this dispatcher. + */ + Meta meta(); + /** * encrypt given plaintext string * diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java similarity index 88% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java index e5704fd..c216a2c 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java @@ -13,12 +13,18 @@ package org.codehaus.plexus.components.secdispatcher.internal; +import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** * Source of master password. */ -public interface MasterPasswordSource { +public interface MasterSource { + /** + * Returns the "meta" of this master source, never {@code null}. + */ + MasterMeta meta(); + /** * Handles the URI to get master password. Implementation may do one of the following things: *
    diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java new file mode 100644 index 0000000..6c1bbec --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * This dispatcher is purely for test purposes, is obviously NOT a true encryption implementation. + */ +@Singleton +@Named(TestDispatcher.NAME) +public class TestDispatcher implements Dispatcher { + public static final String NAME = "test"; + + @Override + public Meta meta() { + return new Meta() { + @Override + public String id() { + return NAME; + } + + @Override + public String displayName() { + return "Test Dispatcher (for testing only)"; + } + + @Override + public Collection fields() { + return List.of(Field.builder("salt") + .optional(false) + .description("The salt for testing") + .build()); + } + }; + } + + protected String getSalt(Map config) throws SecDispatcherException { + String salt = config.get("salt"); + if (salt == null) { + throw new SecDispatcherException("The configuration is incomplete; missing salt"); + } + return salt; + } + + @Override + public String encrypt(String str, Map attributes, Map config) throws SecDispatcherException { + return new StringBuilder(str).reverse() + "@" + getSalt(config); + } + + @Override + public String decrypt(String str, Map attributes, Map config) throws SecDispatcherException { + return new StringBuilder(str).reverse().substring(getSalt(config).length() + 1); + } +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java similarity index 56% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterPasswordSource.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index ede5ce1..185e317 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterPasswordSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -21,20 +21,55 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import static java.util.Objects.requireNonNull; + /** * Password source that uses env. */ @Singleton -@Named(EnvMasterPasswordSource.NAME) -public final class EnvMasterPasswordSource extends PrefixMasterPasswordSourceSupport { +@Named(EnvMasterSource.NAME) +public final class EnvMasterSource extends PrefixMasterSourceSupport { public static final String NAME = "env"; - public EnvMasterPasswordSource() { + public EnvMasterSource() { super(NAME + ":"); } + @Override + public MasterMeta meta() { + return new MasterMeta() { + @Override + public String id() { + return NAME; + } + + @Override + public String displayName() { + return "Environment Variable Source"; + } + + @Override + public Collection fields() { + return List.of(Field.builder("name") + .optional(false) + .description("Name of the environment variable") + .build()); + } + + @Override + public String createConfig(Map data) { + return NAME + ":" + requireNonNull(data.get("name"), "Config incomplete"); + } + }; + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getenv(transformed); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java similarity index 73% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterPasswordSource.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index afe2ffa..f4645e9 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterPasswordSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -31,22 +31,67 @@ import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import static java.util.Objects.requireNonNull; + /** * Password source that uses GnuPG Agent. */ @Singleton -@Named(GpgAgentMasterPasswordSource.NAME) -public final class GpgAgentMasterPasswordSource extends PrefixMasterPasswordSourceSupport { +@Named(GpgAgentMasterSource.NAME) +public final class GpgAgentMasterSource extends PrefixMasterSourceSupport { public static final String NAME = "gpg-agent"; - public GpgAgentMasterPasswordSource() { + public GpgAgentMasterSource() { super(NAME + ":"); } + @Override + public MasterMeta meta() { + return new MasterMeta() { + @Override + public String id() { + return NAME; + } + + @Override + public String displayName() { + return "GPG Agent Source"; + } + + @Override + public Collection fields() { + return List.of( + Field.builder("socketPath") + .optional(false) + .defaultValue(".gnupg/S.gpg-agent") + .description("The GPG Agent socket path (if relative, resolved from user home)") + .build(), + Field.builder("non-interactive") + .optional(true) + .description( + "Whether to forbid GPG interaction (then only cached passphrase can be used)") + .build()); + } + + @Override + public String createConfig(Map data) { + String result = NAME + ":" + requireNonNull(data.get("socketPath"), "Incomplete config"); + if (data.containsKey("interactive")) { + result += "?non-interactive"; + } + return result; + } + }; + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String extra = ""; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterPasswordSourceSupport.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java similarity index 89% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterPasswordSourceSupport.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java index 7b19876..e811ca8 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterPasswordSourceSupport.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java @@ -22,18 +22,18 @@ import java.util.function.Predicate; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource; +import org.codehaus.plexus.components.secdispatcher.internal.MasterSource; import static java.util.Objects.requireNonNull; /** * Master password source support class. */ -public abstract class MasterPasswordSourceSupport implements MasterPasswordSource { +public abstract class MasterSourceSupport implements MasterSource { private final Predicate matcher; private final Function transformer; - public MasterPasswordSourceSupport(Predicate matcher, Function transformer) { + public MasterSourceSupport(Predicate matcher, Function transformer) { this.matcher = requireNonNull(matcher); this.transformer = requireNonNull(transformer); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterPasswordSourceSupport.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterSourceSupport.java similarity index 90% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterPasswordSourceSupport.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterSourceSupport.java index 3d2d6b3..926ce87 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterPasswordSourceSupport.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterSourceSupport.java @@ -26,8 +26,8 @@ /** * Master password source support class for simple "prefix" use case. */ -public abstract class PrefixMasterPasswordSourceSupport extends MasterPasswordSourceSupport { - public PrefixMasterPasswordSourceSupport(String prefix) { +public abstract class PrefixMasterSourceSupport extends MasterSourceSupport { + public PrefixMasterSourceSupport(String prefix) { super(prefixMatcher(prefix), prefixRemover(prefix)); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java similarity index 55% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterPasswordSource.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 58b08b8..87d1755 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterPasswordSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -21,20 +21,55 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import static java.util.Objects.requireNonNull; + /** * Password source that uses env. */ @Singleton -@Named(SystemPropertyMasterPasswordSource.NAME) -public final class SystemPropertyMasterPasswordSource extends PrefixMasterPasswordSourceSupport { +@Named(SystemPropertyMasterSource.NAME) +public final class SystemPropertyMasterSource extends PrefixMasterSourceSupport { public static final String NAME = "prop"; - public SystemPropertyMasterPasswordSource() { + public SystemPropertyMasterSource() { super(NAME + ":"); } + @Override + public MasterMeta meta() { + return new MasterMeta() { + @Override + public String id() { + return NAME; + } + + @Override + public String displayName() { + return "Java System Property Source"; + } + + @Override + public Collection fields() { + return List.of(Field.builder("name") + .optional(false) + .description("Name of the Java System property") + .build()); + } + + @Override + public String createConfig(Map data) { + return NAME + ":" + requireNonNull(data.get("name"), "Config incomplete"); + } + }; + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getProperty(transformed); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java new file mode 100644 index 0000000..4e4e90f --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.sources; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.components.secdispatcher.MasterMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; + +import javax.inject.Named; +import javax.inject.Singleton; + +import static java.util.Objects.requireNonNull; + +/** + * Master source for testing purposes, stores the master password plaintext in configuration. + */ +@Singleton +@Named(TestMasterSource.NAME) +public class TestMasterSource extends PrefixMasterSourceSupport { + public static final String NAME = "test"; + + public TestMasterSource() { + super(NAME + ":"); + } + + @Override + public MasterMeta meta() { + return new MasterMeta() { + @Override + public String id() { + return NAME; + } + + @Override + public String displayName() { + return "Test Source (for testing only)"; + } + + @Override + public Collection fields() { + return List.of(Field.builder("password") + .optional(false) + .description("The password for testing") + .build()); + } + + @Override + public String createConfig(Map data) { + return NAME + ":" + requireNonNull(data.get("password"), "Config incomplete"); + } + }; + } + + @Override + protected String doHandle(String transformed) throws SecDispatcherException { + return transformed; + } +} diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 5ecb58d..bcaca4f 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -25,11 +25,13 @@ import org.codehaus.plexus.components.cipher.internal.DefaultPlexusCipher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.dispatcher.StaticDispatcher; -import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterPasswordSource; -import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterPasswordSource; -import org.codehaus.plexus.components.secdispatcher.internal.sources.StaticMasterPasswordSource; -import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterPasswordSource; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.TestDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.TestMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; +import org.codehaus.plexus.components.secdispatcher.model.Config; +import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxWriter; import org.junit.jupiter.api.BeforeEach; @@ -49,6 +51,28 @@ private void saveSec(String masterSource) throws Exception { sec.setModelEncoding(StandardCharsets.UTF_8.name()); sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); sec.setMasterSource(masterSource); + saveSec(sec); + } + + private void saveSec(String dispatcher, Map config) throws Exception { + SettingsSecurity sec = new SettingsSecurity(); + sec.setModelEncoding(StandardCharsets.UTF_8.name()); + sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); + Config conf = new Config(); + conf.setName(dispatcher); + for (Map.Entry entry : config.entrySet()) { + ConfigProperty prop = new ConfigProperty(); + prop.setName(entry.getKey()); + prop.setValue(entry.getValue()); + conf.addProperty(prop); + } + sec.getConfigurations().add(conf); + saveSec(sec); + } + + private void saveSec(SettingsSecurity sec) throws Exception { + sec.setModelEncoding(StandardCharsets.UTF_8.name()); + sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); sec.setMasterCipher(AESGCMNoPadding.CIPHER_ALG); try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { @@ -64,9 +88,10 @@ public void prepare() throws Exception { @Test void testEncrypt() throws Exception { + saveSec("test:" + masterPassword); DefaultSecDispatcher sd = new DefaultSecDispatcher( new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new StaticMasterPasswordSource(masterPassword)), + Map.of("static", new TestMasterSource()), Map.of(), DefaultSecDispatcher.DEFAULT_CONFIGURATION); String enc = sd.encrypt(password, null); @@ -77,9 +102,10 @@ void testEncrypt() throws Exception { @Test void testDecrypt() throws Exception { + saveSec("test:" + masterPassword); DefaultSecDispatcher sd = new DefaultSecDispatcher( new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new StaticMasterPasswordSource(masterPassword)), + Map.of("static", new TestMasterSource()), Map.of(), DefaultSecDispatcher.DEFAULT_CONFIGURATION); String encrypted = sd.encrypt(password, null); @@ -96,11 +122,11 @@ void testDecryptSystemProperty() throws Exception { new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), Map.of( "prop", - new SystemPropertyMasterPasswordSource(), + new SystemPropertyMasterSource(), "env", - new EnvMasterPasswordSource(), + new EnvMasterSource(), "gpg", - new GpgAgentMasterPasswordSource()), + new GpgAgentMasterSource()), Map.of(), DefaultSecDispatcher.DEFAULT_CONFIGURATION); String encrypted = sd.encrypt(password, null); @@ -116,11 +142,11 @@ void testDecryptEnv() throws Exception { new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), Map.of( "prop", - new SystemPropertyMasterPasswordSource(), + new SystemPropertyMasterSource(), "env", - new EnvMasterPasswordSource(), + new EnvMasterSource(), "gpg", - new GpgAgentMasterPasswordSource()), + new GpgAgentMasterSource()), Map.of(), DefaultSecDispatcher.DEFAULT_CONFIGURATION); String encrypted = sd.encrypt(password, null); @@ -137,11 +163,11 @@ void testDecryptGpg() throws Exception { new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), Map.of( "prop", - new SystemPropertyMasterPasswordSource(), + new SystemPropertyMasterSource(), "env", - new EnvMasterPasswordSource(), + new EnvMasterSource(), "gpg", - new GpgAgentMasterPasswordSource()), + new GpgAgentMasterSource()), Map.of(), DefaultSecDispatcher.DEFAULT_CONFIGURATION); String encrypted = sd.encrypt(password, null); @@ -151,70 +177,22 @@ void testDecryptGpg() throws Exception { } @Test - void testEncryptWithDispatcher() throws Exception { + void testRoundTripWithDispatcher() throws Exception { + saveSec("magic", Map.of("salt", "foobar")); DefaultSecDispatcher sd = new DefaultSecDispatcher( new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new StaticMasterPasswordSource(masterPassword)), - Map.of("magic", new StaticDispatcher("decrypted", "encrypted")), + Map.of("static", new TestMasterSource()), + Map.of("magic", new TestDispatcher()), DefaultSecDispatcher.DEFAULT_CONFIGURATION); - assertEquals(Set.of("magic"), sd.availableDispatchers()); - String enc = sd.encrypt("whatever", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "magic", "a", "b")); - assertNotNull(enc); - assertTrue(enc.contains("encrypted")); - assertTrue(enc.contains(SecDispatcher.DISPATCHER_NAME_ATTR + "=magic")); - String password1 = sd.decrypt(enc); - assertEquals("decrypted", password1); - } - - @Test - void testDecryptWithDispatcher() throws Exception { - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new StaticMasterPasswordSource(masterPassword)), - Map.of("magic", new StaticDispatcher("decrypted", "encrypted")), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - - assertEquals(Set.of("magic"), sd.availableDispatchers()); - String pass = sd.decrypt("{" + "[a=b," + SecDispatcher.DISPATCHER_NAME_ATTR + "=magic]" - + Base64.getEncoder().encodeToString("whatever".getBytes(StandardCharsets.UTF_8)) + "}"); - assertNotNull(pass); - assertEquals("decrypted", pass); - } - - @Test - void testDecryptWithDispatcherConf() throws Exception { - String bare = Base64.getEncoder().encodeToString("whatever".getBytes(StandardCharsets.UTF_8)); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new StaticMasterPasswordSource(masterPassword)), - Map.of("magic", new Dispatcher() { - @Override - public String encrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - throw new IllegalStateException("should not be called"); - } - - @Override - public String decrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - assertEquals(bare, str); - assertEquals(2, attributes.size()); - assertEquals("magic", attributes.get(SecDispatcher.DISPATCHER_NAME_ATTR)); - assertEquals("value", attributes.get("key")); - - assertEquals(1, config.size()); - assertEquals(masterPassword, config.get(Dispatcher.CONF_MASTER_PASSWORD)); - - return "magic"; - } - }), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - - assertEquals(Set.of("magic"), sd.availableDispatchers()); - String pass = sd.decrypt("{" + "[key=value," + SecDispatcher.DISPATCHER_NAME_ATTR + "=magic]" - + Base64.getEncoder().encodeToString("whatever".getBytes(StandardCharsets.UTF_8)) + "}"); - assertNotNull(pass); - assertEquals("magic", pass); + assertEquals(1, sd.availableDispatchers().size()); + String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "magic", "a", "b")); + assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); + assertTrue(encrypted.contains("name=magic")); + assertTrue(encrypted.contains("a=b")); + assertTrue(encrypted.contains("tercesrepus@foobar")); + assertEquals("{[name=magic,a=b]tercesrepus@foobar}", encrypted); + String pass = sd.decrypt(encrypted); + assertEquals("supersecret", pass); } } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatcher/StaticDispatcher.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatcher/StaticDispatcher.java deleted file mode 100644 index 4088212..0000000 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatcher/StaticDispatcher.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2008 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.codehaus.plexus.components.secdispatcher.internal.dispatcher; - -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; - -import static java.util.Objects.requireNonNull; - -public class StaticDispatcher implements Dispatcher { - private final String decrypted; - private final String encrypted; - - public StaticDispatcher(String decrypted, String encrypted) { - this.decrypted = requireNonNull(decrypted); - this.encrypted = requireNonNull(encrypted); - } - - @Override - public String encrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - return encrypted; - } - - @Override - public String decrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - return decrypted; - } -} diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java deleted file mode 100644 index 7ef6d89..0000000 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2008 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.codehaus.plexus.components.secdispatcher.internal.sources; - -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource; - -import static java.util.Objects.requireNonNull; - -public class StaticMasterPasswordSource implements MasterPasswordSource { - private final String masterPassword; - - public StaticMasterPasswordSource(String masterPassword) { - this.masterPassword = requireNonNull(masterPassword); - } - - @Override - public String handle(String masterSource) throws SecDispatcherException { - return masterPassword; - } -} From 7c4f27bfcc78f6c02286e1d382055564fa60bccd Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 9 Oct 2024 20:09:02 +0200 Subject: [PATCH 02/27] Reformat --- .../plexus/components/secdispatcher/Meta.java | 1 - .../internal/dispatchers/TestDispatcher.java | 15 +++++++++------ .../internal/sources/TestMasterSource.java | 6 +++--- .../internal/DefaultSecDispatcherTest.java | 7 ++----- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java index 2fe4544..2c820f1 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java @@ -1,7 +1,6 @@ package org.codehaus.plexus.components.secdispatcher; import java.util.Collection; -import java.util.Map; import java.util.Optional; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java index 6c1bbec..9dabfd5 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java @@ -13,16 +13,17 @@ package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; -import org.codehaus.plexus.components.secdispatcher.Meta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; - import javax.inject.Named; import javax.inject.Singleton; + import java.util.Collection; import java.util.List; import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; + /** * This dispatcher is purely for test purposes, is obviously NOT a true encryption implementation. */ @@ -63,12 +64,14 @@ protected String getSalt(Map config) throws SecDispatcherExcepti } @Override - public String encrypt(String str, Map attributes, Map config) throws SecDispatcherException { + public String encrypt(String str, Map attributes, Map config) + throws SecDispatcherException { return new StringBuilder(str).reverse() + "@" + getSalt(config); } @Override - public String decrypt(String str, Map attributes, Map config) throws SecDispatcherException { + public String decrypt(String str, Map attributes, Map config) + throws SecDispatcherException { return new StringBuilder(str).reverse().substring(getSalt(config).length() + 1); } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java index 4e4e90f..ebb4fba 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java @@ -13,6 +13,9 @@ package org.codehaus.plexus.components.secdispatcher.internal.sources; +import javax.inject.Named; +import javax.inject.Singleton; + import java.util.Collection; import java.util.List; import java.util.Map; @@ -20,9 +23,6 @@ import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import javax.inject.Named; -import javax.inject.Singleton; - import static java.util.Objects.requireNonNull; /** diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index bcaca4f..6b5669f 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -17,19 +17,16 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Base64; import java.util.Map; -import java.util.Set; import org.codehaus.plexus.components.cipher.internal.AESGCMNoPadding; import org.codehaus.plexus.components.cipher.internal.DefaultPlexusCipher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.TestDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; -import org.codehaus.plexus.components.secdispatcher.internal.sources.TestMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.TestMasterSource; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; @@ -191,7 +188,7 @@ void testRoundTripWithDispatcher() throws Exception { assertTrue(encrypted.contains("name=magic")); assertTrue(encrypted.contains("a=b")); assertTrue(encrypted.contains("tercesrepus@foobar")); - assertEquals("{[name=magic,a=b]tercesrepus@foobar}", encrypted); + // assertEquals("{[name=magic,a=b]tercesrepus@foobar}", encrypted); String pass = sd.decrypt(encrypted); assertEquals("supersecret", pass); } From 35e76ab4eb7bc2cb9666a2e583d985730b5bf248 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 9 Oct 2024 22:05:48 +0200 Subject: [PATCH 03/27] Dispatcher prevails --- pom.xml | 7 +- .../components/secdispatcher/MasterMeta.java | 13 -- .../plexus/components/secdispatcher/Meta.java | 2 +- .../secdispatcher/SecDispatcher.java | 12 +- .../internal/DefaultSecDispatcher.java | 100 ++++-------- .../secdispatcher/internal/Dispatcher.java | 7 - .../secdispatcher/internal/MasterSource.java | 6 - .../dispatchers/MasterDispatcher.java | 115 ++++++++++++++ .../internal/dispatchers/TestDispatcher.java | 77 ---------- .../internal/sources/EnvMasterSource.java | 37 +---- .../sources/GpgAgentMasterSource.java | 47 +----- .../sources/SystemPropertyMasterSource.java | 39 +---- .../internal/sources/TestMasterSource.java | 72 --------- src/main/mdo/settings-security.mdo | 11 +- .../internal/DefaultSecDispatcherTest.java | 144 +++--------------- .../secdispatcher/internal/SecUtilTest.java | 12 +- .../internal/sources/SourcesTest.java | 44 ++++++ 17 files changed, 240 insertions(+), 505 deletions(-) delete mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java delete mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java delete mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java create mode 100644 src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java diff --git a/pom.xml b/pom.xml index f42e98f..f7aec3a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ plexus-sec-dispatcher - 3.0.1-SNAPSHOT + 4.0.0-SNAPSHOT Plexus Security Dispatcher Component @@ -75,7 +75,7 @@ modello-maven-plugin 2.4.0 - 3.0.0 + 4.0.0 src/main/mdo/settings-security.mdo @@ -96,6 +96,9 @@ org.apache.maven.plugins maven-surefire-plugin + + masterPw + masterPw diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java deleted file mode 100644 index 85dfd28..0000000 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterMeta.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.codehaus.plexus.components.secdispatcher; - -import java.util.Map; - -/** - * Meta description of master password source. - */ -public interface MasterMeta extends Meta { - /** - * Creates source configuration that can be used as "masterSource". - */ - String createConfig(Map data); -} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java index 2c820f1..2db7e20 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java @@ -76,7 +76,7 @@ public Field build() { /** * The key of the item. */ - String id(); + String name(); /** * Returns the display (human) name of the item. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index c7061e3..3995e4b 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -20,7 +20,7 @@ import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; /** - * This component decrypts a string, passed to it + * This component decrypts a string, passed to it using various dispatchers. * * @author Oleg Gusakov */ @@ -50,16 +50,6 @@ public interface SecDispatcher { */ Set availableDispatchers(); - /** - * Returns the set of available ciphers, never {@code null}. - */ - Set availableCiphers(); - - /** - * Returns the set of available master password sources metadata, never {@code null}. - */ - Set availableMasterSourcesMetadata(); - /** * Encrypt given plaintext string. * diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index bf3a110..7ace5fb 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -28,7 +28,6 @@ import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; -import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.Meta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; @@ -46,18 +45,15 @@ public class DefaultSecDispatcher implements SecDispatcher { public static final String ATTR_STOP = "]"; protected final PlexusCipher cipher; - protected final Map masterSources; protected final Map dispatchers; protected final String configurationFile; @Inject public DefaultSecDispatcher( PlexusCipher cipher, - Map masterSources, Map dispatchers, @Named("${configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String configurationFile) { this.cipher = requireNonNull(cipher); - this.masterSources = requireNonNull(masterSources); this.dispatchers = requireNonNull(dispatchers); this.configurationFile = requireNonNull(configurationFile); } @@ -67,38 +63,28 @@ public Set availableDispatchers() { return Set.copyOf(dispatchers.values().stream().map(Dispatcher::meta).collect(Collectors.toSet())); } - @Override - public Set availableCiphers() { - return cipher.availableCiphers(); - } - - @Override - public Set availableMasterSourcesMetadata() { - return Set.copyOf( - masterSources.values().stream().map(MasterSource::meta).collect(Collectors.toSet())); - } - @Override public String encrypt(String str, Map attr) throws SecDispatcherException { if (isEncryptedString(str)) return str; try { - String res; - if (attr == null || attr.get(DISPATCHER_NAME_ATTR) == null) { - SettingsSecurity sec = getConfiguration(true); - String master = getMasterPassword(sec, true); - res = cipher.encrypt(getMasterCipher(sec), str, master); + if (attr == null) { + attr = new HashMap<>(); } else { - String type = attr.get(DISPATCHER_NAME_ATTR); - Dispatcher dispatcher = dispatchers.get(type); - if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + type); - res = ATTR_START - + attr.entrySet().stream() - .map(e -> e.getKey() + "=" + e.getValue()) - .collect(Collectors.joining(",")) - + ATTR_STOP; - res += dispatcher.encrypt(str, attr, prepareDispatcherConfig(type)); + attr = new HashMap<>(attr); } + if (attr.get(DISPATCHER_NAME_ATTR) == null) { + attr.put(DISPATCHER_NAME_ATTR, getConfiguration().getDefaultDispatcher()); + } + String name = attr.get(DISPATCHER_NAME_ATTR); + Dispatcher dispatcher = dispatchers.get(name); + if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); + String res = ATTR_START + + attr.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining(",")) + + ATTR_STOP; + res += dispatcher.encrypt(str, attr, prepareDispatcherConfig(name)); return cipher.decorate(res); } catch (PlexusCipherException e) { throw new SecDispatcherException(e.getMessage(), e); @@ -111,16 +97,13 @@ public String decrypt(String str) throws SecDispatcherException { try { String bare = cipher.unDecorate(str); Map attr = stripAttributes(bare); - if (attr == null || attr.get(DISPATCHER_NAME_ATTR) == null) { - SettingsSecurity sec = getConfiguration(true); - String master = getMasterPassword(sec, true); - return cipher.decrypt(getMasterCipher(sec), bare, master); - } else { - String type = attr.get(DISPATCHER_NAME_ATTR); - Dispatcher dispatcher = dispatchers.get(type); - if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + type); - return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(type)); + if (attr.get(DISPATCHER_NAME_ATTR) == null) { + attr.put(DISPATCHER_NAME_ATTR, getConfiguration().getDefaultDispatcher()); } + String name = attr.get(DISPATCHER_NAME_ATTR); + Dispatcher dispatcher = dispatchers.get(name); + if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); + return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(name)); } catch (PlexusCipherException e) { throw new SecDispatcherException(e.getMessage(), e); } @@ -143,12 +126,7 @@ public void writeConfiguration(SettingsSecurity configuration) throws IOExceptio private Map prepareDispatcherConfig(String type) { HashMap dispatcherConf = new HashMap<>(); - SettingsSecurity sec = getConfiguration(false); - String master = getMasterPassword(sec, false); - if (master != null) { - dispatcherConf.put(Dispatcher.CONF_MASTER_PASSWORD, master); - } - Map conf = SecUtil.getConfig(sec, type); + Map conf = SecUtil.getConfig(getConfiguration(), type); if (conf != null) { dispatcherConf.putAll(conf); } @@ -165,6 +143,7 @@ private String strip(String str) { } private Map stripAttributes(String str) { + HashMap result = new HashMap<>(); int start = str.indexOf(ATTR_START); int stop = str.indexOf(ATTR_STOP); if (start != -1 && stop != -1 && stop > start) { @@ -172,20 +151,17 @@ private Map stripAttributes(String str) { if (stop == start + 1) return null; String attrs = str.substring(start + 1, stop).trim(); if (attrs.isEmpty()) return null; - Map res = null; StringTokenizer st = new StringTokenizer(attrs, ","); while (st.hasMoreTokens()) { - if (res == null) res = new HashMap<>(st.countTokens()); String pair = st.nextToken(); int pos = pair.indexOf('='); if (pos == -1) throw new SecDispatcherException("Attribute malformed: " + pair); String key = pair.substring(0, pos).trim(); String val = pair.substring(pos + 1).trim(); - res.put(key, val); + result.put(key, val); } - return res; } - return null; + return result; } private boolean isEncryptedString(String str) { @@ -199,11 +175,11 @@ private Path getConfigurationPath() { return Paths.get(location); } - private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException { + private SettingsSecurity getConfiguration() throws SecDispatcherException { Path path = getConfigurationPath(); try { SettingsSecurity sec = SecUtil.read(path); - if (mandatory && sec == null) + if (sec == null) throw new SecDispatcherException("Please check that configuration file on path " + path + " exists"); return sec; } catch (IOException e) { @@ -211,28 +187,6 @@ private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatche } } - private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException { - if ((sec == null || sec.getMasterSource() == null) && !mandatory) { - return null; - } - requireNonNull(sec, "configuration is null"); - String masterSource = requireNonNull(sec.getMasterSource(), "masterSource is null"); - for (MasterSource masterPasswordSource : masterSources.values()) { - String masterPassword = masterPasswordSource.handle(masterSource); - if (masterPassword != null) return masterPassword; - } - if (mandatory) { - throw new SecDispatcherException("master password could not be fetched"); - } else { - return null; - } - } - - private String getMasterCipher(SettingsSecurity sec) throws SecDispatcherException { - requireNonNull(sec, "configuration is null"); - return requireNonNull(sec.getMasterCipher(), "masterCipher is null"); - } - public String getConfigurationFile() { return configurationFile; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java index 7fc495a..2415a93 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java @@ -26,13 +26,6 @@ * */ public interface Dispatcher { - /** - * Configuration key for masterPassword. It may be present, if SecDispatcher could - * obtain it, but presence is optional. Still, dispatcher may throw and fail the operation - * if it requires it. - */ - String CONF_MASTER_PASSWORD = "masterPassword"; - /** * The metadata of this dispatcher. */ diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java index c216a2c..f1d884f 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java @@ -13,18 +13,12 @@ package org.codehaus.plexus.components.secdispatcher.internal; -import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** * Source of master password. */ public interface MasterSource { - /** - * Returns the "meta" of this master source, never {@code null}. - */ - MasterMeta meta(); - /** * Handles the URI to get master password. Implementation may do one of the following things: *
      diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java new file mode 100644 index 0000000..334496a --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.components.cipher.PlexusCipher; +import org.codehaus.plexus.components.cipher.PlexusCipherException; +import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.MasterSource; + +/** + * This dispatcher is logically equivalent (but much more secure) that Maven3 "master password" encryption. + */ +@Singleton +@Named(MasterDispatcher.NAME) +public class MasterDispatcher implements Dispatcher { + public static final String NAME = "master"; + + private final PlexusCipher cipher; + protected final Map masterSources; + + @Inject + public MasterDispatcher(PlexusCipher cipher, Map masterSources) { + this.cipher = cipher; + this.masterSources = masterSources; + } + + @Override + public Meta meta() { + return new Meta() { + @Override + public String name() { + return NAME; + } + + @Override + public String displayName() { + return "Master Password Dispatcher"; + } + + @Override + public Collection fields() { + return List.of( + Field.builder("masterSource") + .optional(false) + .description("The source of master password") + .build(), + Field.builder("masterCipher") + .optional(false) + .description("The cipher to use") + .build()); + } + }; + } + + @Override + public String encrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + try { + return cipher.encrypt(getMasterCipher(config), str, getMasterPassword(config)); + } catch (PlexusCipherException e) { + throw new SecDispatcherException("Encrypt failed", e); + } + } + + @Override + public String decrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + try { + return cipher.decrypt(getMasterCipher(config), str, getMasterPassword(config)); + } catch (PlexusCipherException e) { + throw new SecDispatcherException("Decrypt failed", e); + } + } + + private String getMasterPassword(Map config) throws SecDispatcherException { + String masterSource = config.get("masterSource"); + if (masterSource == null) { + throw new SecDispatcherException("Illegal configuration; masterSource is null"); + } + for (MasterSource masterPasswordSource : masterSources.values()) { + String masterPassword = masterPasswordSource.handle(masterSource); + if (masterPassword != null) return masterPassword; + } + throw new SecDispatcherException("No source handled the masterSource"); + } + + private String getMasterCipher(Map config) throws SecDispatcherException { + String masterCipher = config.get("masterCipher"); + if (masterCipher == null) { + throw new SecDispatcherException("Illegal configuration; masterCipher is null"); + } + return masterCipher; + } +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java deleted file mode 100644 index 9dabfd5..0000000 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/TestDispatcher.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2008 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; - -import javax.inject.Named; -import javax.inject.Singleton; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.Meta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; - -/** - * This dispatcher is purely for test purposes, is obviously NOT a true encryption implementation. - */ -@Singleton -@Named(TestDispatcher.NAME) -public class TestDispatcher implements Dispatcher { - public static final String NAME = "test"; - - @Override - public Meta meta() { - return new Meta() { - @Override - public String id() { - return NAME; - } - - @Override - public String displayName() { - return "Test Dispatcher (for testing only)"; - } - - @Override - public Collection fields() { - return List.of(Field.builder("salt") - .optional(false) - .description("The salt for testing") - .build()); - } - }; - } - - protected String getSalt(Map config) throws SecDispatcherException { - String salt = config.get("salt"); - if (salt == null) { - throw new SecDispatcherException("The configuration is incomplete; missing salt"); - } - return salt; - } - - @Override - public String encrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - return new StringBuilder(str).reverse() + "@" + getSalt(config); - } - - @Override - public String decrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - return new StringBuilder(str).reverse().substring(getSalt(config).length() + 1); - } -} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index 185e317..5b8008b 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -21,17 +21,12 @@ import javax.inject.Named; import javax.inject.Singleton; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import static java.util.Objects.requireNonNull; - /** * Password source that uses env. + *

      + * Config: {@code env:$ENVIRONMENT_VARIABLE_NAME} */ @Singleton @Named(EnvMasterSource.NAME) @@ -42,34 +37,6 @@ public EnvMasterSource() { super(NAME + ":"); } - @Override - public MasterMeta meta() { - return new MasterMeta() { - @Override - public String id() { - return NAME; - } - - @Override - public String displayName() { - return "Environment Variable Source"; - } - - @Override - public Collection fields() { - return List.of(Field.builder("name") - .optional(false) - .description("Name of the environment variable") - .build()); - } - - @Override - public String createConfig(Map data) { - return NAME + ":" + requireNonNull(data.get("name"), "Config incomplete"); - } - }; - } - @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getenv(transformed); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index f4645e9..e85af94 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -31,18 +31,14 @@ import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collection; import java.util.HexFormat; -import java.util.List; -import java.util.Map; -import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import static java.util.Objects.requireNonNull; - /** * Password source that uses GnuPG Agent. + *

      + * Config: {@code gpg-agent:$agentSocketPath[?non-interactive]} */ @Singleton @Named(GpgAgentMasterSource.NAME) @@ -53,45 +49,6 @@ public GpgAgentMasterSource() { super(NAME + ":"); } - @Override - public MasterMeta meta() { - return new MasterMeta() { - @Override - public String id() { - return NAME; - } - - @Override - public String displayName() { - return "GPG Agent Source"; - } - - @Override - public Collection fields() { - return List.of( - Field.builder("socketPath") - .optional(false) - .defaultValue(".gnupg/S.gpg-agent") - .description("The GPG Agent socket path (if relative, resolved from user home)") - .build(), - Field.builder("non-interactive") - .optional(true) - .description( - "Whether to forbid GPG interaction (then only cached passphrase can be used)") - .build()); - } - - @Override - public String createConfig(Map data) { - String result = NAME + ":" + requireNonNull(data.get("socketPath"), "Incomplete config"); - if (data.containsKey("interactive")) { - result += "?non-interactive"; - } - return result; - } - }; - } - @Override protected String doHandle(String transformed) throws SecDispatcherException { String extra = ""; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 87d1755..ef92df2 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -21,55 +21,22 @@ import javax.inject.Named; import javax.inject.Singleton; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.MasterMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import static java.util.Objects.requireNonNull; - /** * Password source that uses env. + *

      + * Config: {@code system-property:$systemPropertyName} */ @Singleton @Named(SystemPropertyMasterSource.NAME) public final class SystemPropertyMasterSource extends PrefixMasterSourceSupport { - public static final String NAME = "prop"; + public static final String NAME = "system-property"; public SystemPropertyMasterSource() { super(NAME + ":"); } - @Override - public MasterMeta meta() { - return new MasterMeta() { - @Override - public String id() { - return NAME; - } - - @Override - public String displayName() { - return "Java System Property Source"; - } - - @Override - public Collection fields() { - return List.of(Field.builder("name") - .optional(false) - .description("Name of the Java System property") - .build()); - } - - @Override - public String createConfig(Map data) { - return NAME + ":" + requireNonNull(data.get("name"), "Config incomplete"); - } - }; - } - @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getProperty(transformed); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java deleted file mode 100644 index ebb4fba..0000000 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/TestMasterSource.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2008 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.codehaus.plexus.components.secdispatcher.internal.sources; - -import javax.inject.Named; -import javax.inject.Singleton; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.MasterMeta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; - -import static java.util.Objects.requireNonNull; - -/** - * Master source for testing purposes, stores the master password plaintext in configuration. - */ -@Singleton -@Named(TestMasterSource.NAME) -public class TestMasterSource extends PrefixMasterSourceSupport { - public static final String NAME = "test"; - - public TestMasterSource() { - super(NAME + ":"); - } - - @Override - public MasterMeta meta() { - return new MasterMeta() { - @Override - public String id() { - return NAME; - } - - @Override - public String displayName() { - return "Test Source (for testing only)"; - } - - @Override - public Collection fields() { - return List.of(Field.builder("password") - .optional(false) - .description("The password for testing") - .build()); - } - - @Override - public String createConfig(Map data) { - return NAME + ":" + requireNonNull(data.get("password"), "Config incomplete"); - } - }; - } - - @Override - protected String doHandle(String transformed) throws SecDispatcherException { - return transformed; - } -} diff --git a/src/main/mdo/settings-security.mdo b/src/main/mdo/settings-security.mdo index 336c179..f16b58a 100644 --- a/src/main/mdo/settings-security.mdo +++ b/src/main/mdo/settings-security.mdo @@ -56,18 +56,25 @@ masterSource - 3.0.0+ + 3.0.0/3.0.0 String true The masterSource describes the source of the master password masterCipher - 3.0.0+ + 3.0.0/3.0.0 String true The Cipher to be used for master password + + defaultDispatcher + 4.0.0+ + String + true + The default dispatcher to be used when no dispatcher name provided + configurations 1.0.0+ diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 6b5669f..67ade82 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -22,35 +22,23 @@ import org.codehaus.plexus.components.cipher.internal.AESGCMNoPadding; import org.codehaus.plexus.components.cipher.internal.DefaultPlexusCipher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.TestDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; -import org.codehaus.plexus.components.secdispatcher.internal.sources.TestMasterSource; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxWriter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class DefaultSecDispatcherTest { String masterPassword = "masterPw"; String password = "somePassword"; - private void saveSec(String masterSource) throws Exception { - SettingsSecurity sec = new SettingsSecurity(); - sec.setModelEncoding(StandardCharsets.UTF_8.name()); - sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - sec.setMasterSource(masterSource); - saveSec(sec); - } - private void saveSec(String dispatcher, Map config) throws Exception { SettingsSecurity sec = new SettingsSecurity(); sec.setModelEncoding(StandardCharsets.UTF_8.name()); @@ -70,7 +58,6 @@ private void saveSec(String dispatcher, Map config) throws Excep private void saveSec(SettingsSecurity sec) throws Exception { sec.setModelEncoding(StandardCharsets.UTF_8.name()); sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - sec.setMasterCipher(AESGCMNoPadding.CIPHER_ALG); try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { new SecurityConfigurationStaxWriter().write(fos, sec); @@ -78,118 +65,37 @@ private void saveSec(SettingsSecurity sec) throws Exception { System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml"); } - @BeforeEach - public void prepare() throws Exception { - saveSec("magic:might"); - } - - @Test - void testEncrypt() throws Exception { - saveSec("test:" + masterPassword); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new TestMasterSource()), - Map.of(), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - String enc = sd.encrypt(password, null); - assertNotNull(enc); - String password1 = sd.decrypt(enc); - assertEquals(password, password1); - } - - @Test - void testDecrypt() throws Exception { - saveSec("test:" + masterPassword); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new TestMasterSource()), - Map.of(), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - String encrypted = sd.encrypt(password, null); - String pass = sd.decrypt(encrypted); - assertNotNull(pass); - assertEquals(password, pass); - } - - @Test - void testDecryptSystemProperty() throws Exception { - System.setProperty("foobar", masterPassword); - saveSec("prop:foobar"); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of( - "prop", - new SystemPropertyMasterSource(), - "env", - new EnvMasterSource(), - "gpg", - new GpgAgentMasterSource()), - Map.of(), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - String encrypted = sd.encrypt(password, null); - String pass = sd.decrypt(encrypted); - assertNotNull(pass); - assertEquals(password, pass); - } - - @Test - void testDecryptEnv() throws Exception { - saveSec("env:MASTER_PASSWORD"); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of( - "prop", - new SystemPropertyMasterSource(), - "env", - new EnvMasterSource(), - "gpg", - new GpgAgentMasterSource()), - Map.of(), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - String encrypted = sd.encrypt(password, null); - String pass = sd.decrypt(encrypted); - assertNotNull(pass); - assertEquals(password, pass); - } - - @Disabled("triggers GPG agent: remove this and type in 'masterPw'") - @Test - void testDecryptGpg() throws Exception { - saveSec("gpg-agent:/run/user/1000/gnupg/S.gpg-agent"); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of( - "prop", - new SystemPropertyMasterSource(), - "env", - new EnvMasterSource(), - "gpg", - new GpgAgentMasterSource()), - Map.of(), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); - String encrypted = sd.encrypt(password, null); - String pass = sd.decrypt(encrypted); - assertNotNull(pass); - assertEquals(password, pass); - } - @Test void testRoundTripWithDispatcher() throws Exception { - saveSec("magic", Map.of("salt", "foobar")); - DefaultSecDispatcher sd = new DefaultSecDispatcher( - new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())), - Map.of("static", new TestMasterSource()), - Map.of("magic", new TestDispatcher()), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); + saveSec( + "master", + Map.of("masterSource", "system-property:masterPassword", "masterCipher", AESGCMNoPadding.CIPHER_ALG)); + DefaultSecDispatcher sd = construct(); assertEquals(1, sd.availableDispatchers().size()); - String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "magic", "a", "b")); + String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); - assertTrue(encrypted.contains("name=magic")); + assertTrue(encrypted.contains("name=master")); assertTrue(encrypted.contains("a=b")); - assertTrue(encrypted.contains("tercesrepus@foobar")); - // assertEquals("{[name=magic,a=b]tercesrepus@foobar}", encrypted); String pass = sd.decrypt(encrypted); assertEquals("supersecret", pass); } + + protected DefaultSecDispatcher construct() { + DefaultPlexusCipher dpc = new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())); + return new DefaultSecDispatcher( + dpc, + Map.of( + "master", + new MasterDispatcher( + dpc, + Map.of( + EnvMasterSource.NAME, + new EnvMasterSource(), + SystemPropertyMasterSource.NAME, + new SystemPropertyMasterSource(), + GpgAgentMasterSource.NAME, + new GpgAgentMasterSource()))), + DefaultSecDispatcher.DEFAULT_CONFIGURATION); + } } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index f98dccb..94fe8e4 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -41,13 +41,13 @@ public class SecUtilTest { String _propName = "pname"; String _propVal = "pval"; - private void saveSec(String masterSource) throws IOException { - saveSec("./target/sec.xml", masterSource); + private void saveSec(String defaultDispatcher) throws IOException { + saveSec("./target/sec.xml", defaultDispatcher); } - private void saveSec(String path, String masterSource) throws IOException { + private void saveSec(String path, String defaultDispatcher) throws IOException { SettingsSecurity sec = new SettingsSecurity(); - sec.setMasterSource(masterSource); + sec.setDefaultDispatcher(defaultDispatcher); ConfigProperty cp = new ConfigProperty(); cp.setName(_propName); cp.setValue(_propVal); @@ -70,7 +70,7 @@ void readWrite() throws IOException { assertNotNull(config); assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); - assertEquals("magic:mighty", config.getMasterSource()); + assertEquals("magic:mighty", config.getDefaultDispatcher()); SecUtil.write(path, config, false); } @@ -81,7 +81,7 @@ void readWriteWithBackup() throws IOException { assertNotNull(config); assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); - assertEquals("magic:mighty", config.getMasterSource()); + assertEquals("magic:mighty", config.getDefaultDispatcher()); SecUtil.write(path, config, true); assertTrue(Files.exists(path)); assertTrue(Files.exists(path.getParent().resolve(path.getFileName() + ".bak"))); diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java new file mode 100644 index 0000000..068f3a4 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.sources; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * surefire plugin set system property and env. + */ +public class SourcesTest { + @Test + void systemProperty() { + SystemPropertyMasterSource source = new SystemPropertyMasterSource(); + assertEquals("masterPw", source.handle("system-property:masterPassword")); + } + + @Test + void env() { + EnvMasterSource source = new EnvMasterSource(); + assertEquals("masterPw", source.handle("env:MASTER_PASSWORD")); + } + + @Disabled("enable and type in 'masterPw'") + @Test + void gpgAgent() { + GpgAgentMasterSource source = new GpgAgentMasterSource(); + // ypu may adjust path, this is Fedora40 WS. Ubuntu does `.gpg/S.gpg-agent` + assertEquals("masterPw", source.handle("gpg-agent:/run/user/1000/gnupg/S.gpg-agent")); + } +} From 6aac500340e03489a68ad31bb917fa1d89bb5ffa Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 9 Oct 2024 22:15:56 +0200 Subject: [PATCH 04/27] Reformat --- .../components/secdispatcher/SecDispatcher.java | 2 +- .../internal/DefaultSecDispatcherTest.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 3995e4b..6f55039 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -82,7 +82,7 @@ public interface SecDispatcher { * Writes the effective configuration. * * @param configuration The configuration to write, may not be {@code null} - * x * @throws IOException In case of IO problem + * @throws IOException In case of IO problem */ void writeConfiguration(SettingsSecurity configuration) throws IOException; } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 67ade82..b769809 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -66,7 +66,21 @@ private void saveSec(SettingsSecurity sec) throws Exception { } @Test - void testRoundTripWithDispatcher() throws Exception { + void masterWithEnvRoundTrip() throws Exception { + saveSec("master", Map.of("masterSource", "env:MASTER_PASSWORD", "masterCipher", AESGCMNoPadding.CIPHER_ALG)); + DefaultSecDispatcher sd = construct(); + + assertEquals(1, sd.availableDispatchers().size()); + String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); + assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); + assertTrue(encrypted.contains("name=master")); + assertTrue(encrypted.contains("a=b")); + String pass = sd.decrypt(encrypted); + assertEquals("supersecret", pass); + } + + @Test + void masterWithSystemPropertyRoundTrip() throws Exception { saveSec( "master", Map.of("masterSource", "system-property:masterPassword", "masterCipher", AESGCMNoPadding.CIPHER_ALG)); From eae95a31c766b2e21a42171dd00b8f8cb3b1e598 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 09:45:25 +0200 Subject: [PATCH 05/27] Final touches --- .../{Meta.java => DispatcherMeta.java} | 43 +++++++++++-- .../secdispatcher/SecDispatcher.java | 2 +- .../internal/DefaultSecDispatcher.java | 23 ++++--- .../secdispatcher/internal/Dispatcher.java | 40 +++++++++--- .../secdispatcher/internal/MasterSource.java | 27 +++++--- .../dispatchers/MasterDispatcher.java | 62 ++++++++++++++----- .../internal/sources/EnvMasterSource.java | 12 ++++ .../sources/GpgAgentMasterSource.java | 11 ++++ .../sources/SystemPropertyMasterSource.java | 12 ++++ .../internal/DefaultSecDispatcherTest.java | 26 +++----- 10 files changed, 198 insertions(+), 60 deletions(-) rename src/main/java/org/codehaus/plexus/components/secdispatcher/{Meta.java => DispatcherMeta.java} (60%) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java similarity index 60% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java index 2db7e20..e222df0 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/Meta.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java @@ -1,6 +1,7 @@ package org.codehaus.plexus.components.secdispatcher; import java.util.Collection; +import java.util.List; import java.util.Optional; import static java.util.Objects.requireNonNull; @@ -8,36 +9,60 @@ /** * Meta description of dispatcher. */ -public interface Meta { +public interface DispatcherMeta { class Field { private final String key; private final boolean optional; private final String defaultValue; private final String description; + private final List options; - private Field(String key, boolean optional, String defaultValue, String description) { + private Field(String key, boolean optional, String defaultValue, String description, List options) { this.key = requireNonNull(key); this.optional = optional; this.defaultValue = defaultValue; this.description = requireNonNull(description); + this.options = options; } + /** + * The key to be used in configuration map for field. + */ public String getKey() { return key; } + /** + * Is configuration optional? + */ public boolean isOptional() { return optional; } + /** + * Optional default value of the configuration. + */ public Optional getDefaultValue() { return Optional.ofNullable(defaultValue); } + /** + * The human description of the configuration. + */ public String getDescription() { return description; } + /** + * Optional list of options, if this configuration accepts limited values. Each option is represented + * as field, where {@link #getKey()} represents the value to be used, and {@link #displayName()} represents + * the description of option. The {@link #getDefaultValue()}, if present represents the value to be used + * instead of {@link #getKey()}. + */ + public Optional> getOptions() { + return Optional.ofNullable(options); + } + public static Builder builder(String key) { return new Builder(key); } @@ -47,6 +72,7 @@ public static class Builder { private boolean optional; private String defaultValue; private String description; + private List options; private Builder(String key) { this.key = requireNonNull(key); @@ -67,24 +93,29 @@ public Builder description(String description) { return this; } + public Builder options(List options) { + this.options = requireNonNull(options); + return this; + } + public Field build() { - return new Field(key, optional, defaultValue, description); + return new Field(key, optional, defaultValue, description, options); } } } /** - * The key of the item. + * The name of the dispatcher. */ String name(); /** - * Returns the display (human) name of the item. + * Returns the display (human) name of the dispatcher. */ String displayName(); /** - * Returns the configuration fields of the item. + * Returns the configuration fields of the dispatcher. */ Collection fields(); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 6f55039..62ea046 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -48,7 +48,7 @@ public interface SecDispatcher { /** * Returns the set of available dispatcher metadata, never {@code null}. */ - Set availableDispatchers(); + Set availableDispatchers(); /** * Encrypt given plaintext string. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 7ace5fb..bcef8a8 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -22,13 +22,14 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; import java.util.stream.Collectors; import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; -import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; @@ -59,7 +60,7 @@ public DefaultSecDispatcher( } @Override - public Set availableDispatchers() { + public Set availableDispatchers() { return Set.copyOf(dispatchers.values().stream().map(Dispatcher::meta).collect(Collectors.toSet())); } @@ -74,17 +75,25 @@ public String encrypt(String str, Map attr) throws SecDispatcher attr = new HashMap<>(attr); } if (attr.get(DISPATCHER_NAME_ATTR) == null) { - attr.put(DISPATCHER_NAME_ATTR, getConfiguration().getDefaultDispatcher()); + attr.put( + DISPATCHER_NAME_ATTR, + requireNonNull( + getConfiguration().getDefaultDispatcher(), + "no default dispatcher set in configuration")); } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); + Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name)); + if (!Objects.equals(payload.getAttributes().get(DISPATCHER_NAME_ATTR), name)) { + throw new SecDispatcherException("Dispatcher " + name + " bug: mismatched name attribute"); + } String res = ATTR_START - + attr.entrySet().stream() + + payload.getAttributes().entrySet().stream() .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining(",")) + ATTR_STOP; - res += dispatcher.encrypt(str, attr, prepareDispatcherConfig(name)); + res += payload.getEncrypted(); return cipher.decorate(res); } catch (PlexusCipherException e) { throw new SecDispatcherException(e.getMessage(), e); @@ -96,9 +105,9 @@ public String decrypt(String str) throws SecDispatcherException { if (!isEncryptedString(str)) return str; try { String bare = cipher.unDecorate(str); - Map attr = stripAttributes(bare); + Map attr = requireNonNull(stripAttributes(bare)); if (attr.get(DISPATCHER_NAME_ATTR) == null) { - attr.put(DISPATCHER_NAME_ATTR, getConfiguration().getDefaultDispatcher()); + throw new SecDispatcherException("malformed password: no attribute with name"); } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java index 2415a93..ba4d5bf 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java @@ -15,9 +15,11 @@ import java.util.Map; -import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import static java.util.Objects.requireNonNull; + /** * Dispatcher. * @@ -26,26 +28,48 @@ * */ public interface Dispatcher { + /** + * The "encrypt payload" prepared by dispatcher. + */ + final class EncryptPayload { + private final Map attributes; + private final String encrypted; + + public EncryptPayload(Map attributes, String encrypted) { + this.attributes = requireNonNull(attributes); + this.encrypted = requireNonNull(encrypted); + } + + public Map getAttributes() { + return attributes; + } + + public String getEncrypted() { + return encrypted; + } + } + /** * The metadata of this dispatcher. */ - Meta meta(); + DispatcherMeta meta(); /** - * encrypt given plaintext string + * Encrypt given plaintext string. Implementation must return at least same attributes it got, but may add more + * attributes to returned payload. * - * @param str string to encrypt + * @param str string to encrypt, never {@code null} * @param attributes attributes, never {@code null} * @param config configuration from settings-security.xml, never {@code null} - * @return encrypted string + * @return encrypted string and attributes in {@link EncryptPayload} */ - String encrypt(String str, Map attributes, Map config) + EncryptPayload encrypt(String str, Map attributes, Map config) throws SecDispatcherException; /** - * decrypt given encrypted string + * Decrypt given encrypted string. * - * @param str string to decrypt + * @param str string to decrypt, never {@code null} * @param attributes attributes, never {@code null} * @param config configuration from settings-security.xml, never {@code null} * @return decrypted string diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java index f1d884f..6f4bd9c 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java @@ -13,6 +13,8 @@ package org.codehaus.plexus.components.secdispatcher.internal; +import java.util.Optional; + import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -20,16 +22,27 @@ */ public interface MasterSource { /** - * Handles the URI to get master password. Implementation may do one of the following things: + * String describing what this source does. + */ + String description(); + + /** + * Optional "config template" that may serve as basis to configure this master source. The template cannot be + * "reused" as is as configuration. + */ + Optional configTemplate(); + + /** + * Handles the config to get master password. Implementation may do one of the following things: *

        - *
      • if the URI cannot be handled by given source, return {@code null}
      • - *
      • if master password retrieval was attempted, but failed throw {@link SecDispatcherException}
      • + *
      • if the config cannot be handled by given source, return {@code null}
      • + *
      • otherwise, if master password retrieval based on config was attempted but failed, throw {@link SecDispatcherException}
      • *
      • happy path: return the master password.
      • *
      * - * @param masterSource the source of master password, and opaque string. - * @return the master password, or {@code null} if implementation does not handle this masterSource - * @throws SecDispatcherException If implementation does handle this masterSource, but cannot obtain it + * @param config the source of master password, and opaque string. + * @return the master password, or {@code null} if implementation does not handle this config + * @throws SecDispatcherException If implementation does handle this masterSource, but cannot obtain master password */ - String handle(String masterSource) throws SecDispatcherException; + String handle(String config) throws SecDispatcherException; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index 334496a..e58f426 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -18,12 +18,14 @@ import javax.inject.Singleton; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; -import org.codehaus.plexus.components.secdispatcher.Meta; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; import org.codehaus.plexus.components.secdispatcher.internal.MasterSource; @@ -36,6 +38,9 @@ public class MasterDispatcher implements Dispatcher { public static final String NAME = "master"; + private static final String MASTER_CIPHER = "cipher"; + private static final String MASTER_SOURCE = "source"; + private final PlexusCipher cipher; protected final Map masterSources; @@ -46,8 +51,8 @@ public MasterDispatcher(PlexusCipher cipher, Map masterSou } @Override - public Meta meta() { - return new Meta() { + public DispatcherMeta meta() { + return new DispatcherMeta() { @Override public String name() { return NAME; @@ -61,23 +66,45 @@ public String displayName() { @Override public Collection fields() { return List.of( - Field.builder("masterSource") + Field.builder(MASTER_SOURCE) .optional(false) .description("The source of master password") + .options(masterSources.entrySet().stream() + .map(e -> { + Field.Builder b = Field.builder(e.getKey()) + .description(e.getValue().description()); + if (e.getValue().configTemplate().isPresent()) { + b = b.defaultValue(e.getValue() + .configTemplate() + .get()); + } + return b.build(); + }) + .toList()) .build(), - Field.builder("masterCipher") + Field.builder(MASTER_CIPHER) .optional(false) - .description("The cipher to use") + .description("The cipher to use with master password") + .options(cipher.availableCiphers().stream() + .map(c -> Field.builder(c) + .description("Cipher implementation " + c) + .build()) + .toList()) .build()); } }; } @Override - public String encrypt(String str, Map attributes, Map config) + public EncryptPayload encrypt(String str, Map attributes, Map config) throws SecDispatcherException { try { - return cipher.encrypt(getMasterCipher(config), str, getMasterPassword(config)); + String masterCipher = getMasterCipher(config, true); + String encrypted = cipher.encrypt(masterCipher, str, getMasterPassword(config)); + HashMap attr = new HashMap<>(attributes); + attr.put(SecDispatcher.DISPATCHER_NAME_ATTR, NAME); + attr.put(MASTER_CIPHER, masterCipher); + return new EncryptPayload(attr, encrypted); } catch (PlexusCipherException e) { throw new SecDispatcherException("Encrypt failed", e); } @@ -87,28 +114,33 @@ public String encrypt(String str, Map attributes, Map attributes, Map config) throws SecDispatcherException { try { - return cipher.decrypt(getMasterCipher(config), str, getMasterPassword(config)); + String masterCipher = getMasterCipher(attributes, false); + return cipher.decrypt(masterCipher, str, getMasterPassword(config)); } catch (PlexusCipherException e) { throw new SecDispatcherException("Decrypt failed", e); } } private String getMasterPassword(Map config) throws SecDispatcherException { - String masterSource = config.get("masterSource"); + String masterSource = config.get(MASTER_SOURCE); if (masterSource == null) { - throw new SecDispatcherException("Illegal configuration; masterSource is null"); + throw new SecDispatcherException("Invalid configuration: Missing configuration " + MASTER_SOURCE); } for (MasterSource masterPasswordSource : masterSources.values()) { String masterPassword = masterPasswordSource.handle(masterSource); if (masterPassword != null) return masterPassword; } - throw new SecDispatcherException("No source handled the masterSource"); + throw new SecDispatcherException("No source handled the given masterSource: " + masterSource); } - private String getMasterCipher(Map config) throws SecDispatcherException { - String masterCipher = config.get("masterCipher"); + private String getMasterCipher(Map source, boolean config) throws SecDispatcherException { + String masterCipher = source.get(MASTER_CIPHER); if (masterCipher == null) { - throw new SecDispatcherException("Illegal configuration; masterCipher is null"); + if (config) { + throw new SecDispatcherException("Invalid configuration: Missing configuration " + MASTER_CIPHER); + } else { + throw new SecDispatcherException("Malformed attributes: Missing attribute " + MASTER_CIPHER); + } } return masterCipher; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index 5b8008b..df54dc0 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -21,6 +21,8 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.Optional; + import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -37,6 +39,16 @@ public EnvMasterSource() { super(NAME + ":"); } + @Override + public String description() { + return "Extracts the master password out of environment variables with configured name"; + } + + @Override + public Optional configTemplate() { + return Optional.of(NAME + ":" + "ENV_VARIABLE"); + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getenv(transformed); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index e85af94..c3b521d 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HexFormat; +import java.util.Optional; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; @@ -49,6 +50,16 @@ public GpgAgentMasterSource() { super(NAME + ":"); } + @Override + public String description() { + return "Interacts with GPG Agent via domain socket to get the master password"; + } + + @Override + public Optional configTemplate() { + return Optional.of(NAME + ":" + "$agentSocketPath"); + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String extra = ""; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index ef92df2..1ffc879 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -21,6 +21,8 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.Optional; + import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -37,6 +39,16 @@ public SystemPropertyMasterSource() { super(NAME + ":"); } + @Override + public String description() { + return "Extracts the master password out of Java System properties with configured key"; + } + + @Override + public Optional configTemplate() { + return Optional.of(NAME + ":" + "$systemProperty"); + } + @Override protected String doHandle(String transformed) throws SecDispatcherException { String value = System.getProperty(transformed); diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index b769809..0693de0 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -36,9 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class DefaultSecDispatcherTest { - String masterPassword = "masterPw"; - String password = "somePassword"; - private void saveSec(String dispatcher, Map config) throws Exception { SettingsSecurity sec = new SettingsSecurity(); sec.setModelEncoding(StandardCharsets.UTF_8.name()); @@ -67,29 +64,26 @@ private void saveSec(SettingsSecurity sec) throws Exception { @Test void masterWithEnvRoundTrip() throws Exception { - saveSec("master", Map.of("masterSource", "env:MASTER_PASSWORD", "masterCipher", AESGCMNoPadding.CIPHER_ALG)); - DefaultSecDispatcher sd = construct(); - - assertEquals(1, sd.availableDispatchers().size()); - String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); - assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); - assertTrue(encrypted.contains("name=master")); - assertTrue(encrypted.contains("a=b")); - String pass = sd.decrypt(encrypted); - assertEquals("supersecret", pass); + saveSec("master", Map.of("source", "env:MASTER_PASSWORD", "cipher", AESGCMNoPadding.CIPHER_ALG)); + roundtrip(); } @Test void masterWithSystemPropertyRoundTrip() throws Exception { - saveSec( - "master", - Map.of("masterSource", "system-property:masterPassword", "masterCipher", AESGCMNoPadding.CIPHER_ALG)); + saveSec("master", Map.of("source", "system-property:masterPassword", "cipher", AESGCMNoPadding.CIPHER_ALG)); + roundtrip(); + } + + protected void roundtrip() { DefaultSecDispatcher sd = construct(); assertEquals(1, sd.availableDispatchers().size()); String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); + // example: + // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); assertTrue(encrypted.contains("name=master")); + assertTrue(encrypted.contains("cipher=" + AESGCMNoPadding.CIPHER_ALG)); assertTrue(encrypted.contains("a=b")); String pass = sd.decrypt(encrypted); assertEquals("supersecret", pass); From 8904363731a1a6fe00df21c748c7f0f22c5da889 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 12:57:52 +0200 Subject: [PATCH 06/27] Split meta and the thing --- .../{internal => }/Dispatcher.java | 10 +-- .../{internal => }/MasterSource.java | 17 +--- .../secdispatcher/MasterSourceMeta.java | 32 +++++++ .../internal/DefaultSecDispatcher.java | 29 ++++++- .../dispatchers/MasterDispatcher.java | 85 +++++++++---------- .../internal/sources/EnvMasterSource.java | 3 +- .../sources/GpgAgentMasterSource.java | 3 +- .../internal/sources/MasterSourceSupport.java | 2 +- .../sources/SystemPropertyMasterSource.java | 3 +- 9 files changed, 111 insertions(+), 73 deletions(-) rename src/main/java/org/codehaus/plexus/components/secdispatcher/{internal => }/Dispatcher.java (89%) rename src/main/java/org/codehaus/plexus/components/secdispatcher/{internal => }/MasterSource.java (76%) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSourceMeta.java diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java similarity index 89% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java index ba4d5bf..0630547 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java @@ -11,13 +11,10 @@ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.codehaus.plexus.components.secdispatcher.internal; +package org.codehaus.plexus.components.secdispatcher; import java.util.Map; -import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; - import static java.util.Objects.requireNonNull; /** @@ -49,11 +46,6 @@ public String getEncrypted() { } } - /** - * The metadata of this dispatcher. - */ - DispatcherMeta meta(); - /** * Encrypt given plaintext string. Implementation must return at least same attributes it got, but may add more * attributes to returned payload. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java similarity index 76% rename from src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java rename to src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java index 6f4bd9c..f9350dc 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java @@ -11,27 +11,12 @@ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.codehaus.plexus.components.secdispatcher.internal; - -import java.util.Optional; - -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +package org.codehaus.plexus.components.secdispatcher; /** * Source of master password. */ public interface MasterSource { - /** - * String describing what this source does. - */ - String description(); - - /** - * Optional "config template" that may serve as basis to configure this master source. The template cannot be - * "reused" as is as configuration. - */ - Optional configTemplate(); - /** * Handles the config to get master password. Implementation may do one of the following things: *
        diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSourceMeta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSourceMeta.java new file mode 100644 index 0000000..4e112c1 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSourceMeta.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher; + +import java.util.Optional; + +/** + * Source of master password. + */ +public interface MasterSourceMeta { + /** + * String describing what this source does. + */ + String description(); + + /** + * Optional "config template" that may serve as basis to configure this master source. The template cannot be + * "reused" as is as configuration. + */ + Optional configTemplate(); +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index bcef8a8..a0d9dcb 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -20,7 +20,9 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -29,6 +31,7 @@ import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; +import org.codehaus.plexus.components.secdispatcher.Dispatcher; import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; @@ -61,7 +64,31 @@ public DefaultSecDispatcher( @Override public Set availableDispatchers() { - return Set.copyOf(dispatchers.values().stream().map(Dispatcher::meta).collect(Collectors.toSet())); + return Set.copyOf( + dispatchers.entrySet().stream().map(this::dispatcherMeta).collect(Collectors.toSet())); + } + + private DispatcherMeta dispatcherMeta(Map.Entry dispatcher) { + if (dispatcher instanceof DispatcherMeta) { + return (DispatcherMeta) dispatcher; + } else { + return new DispatcherMeta() { + @Override + public String name() { + return dispatcher.getKey(); + } + + @Override + public String displayName() { + return dispatcher.getKey() + " (needs manual configuration)"; + } + + @Override + public Collection fields() { + return List.of(); + } + }; + } } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index e58f426..462e453 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -24,18 +24,19 @@ import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; +import org.codehaus.plexus.components.secdispatcher.Dispatcher; import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.Dispatcher; -import org.codehaus.plexus.components.secdispatcher.internal.MasterSource; /** * This dispatcher is logically equivalent (but much more secure) that Maven3 "master password" encryption. */ @Singleton @Named(MasterDispatcher.NAME) -public class MasterDispatcher implements Dispatcher { +public class MasterDispatcher implements Dispatcher, DispatcherMeta { public static final String NAME = "master"; private static final String MASTER_CIPHER = "cipher"; @@ -51,48 +52,46 @@ public MasterDispatcher(PlexusCipher cipher, Map masterSou } @Override - public DispatcherMeta meta() { - return new DispatcherMeta() { - @Override - public String name() { - return NAME; - } + public String name() { + return NAME; + } - @Override - public String displayName() { - return "Master Password Dispatcher"; - } + @Override + public String displayName() { + return "Master Password Dispatcher"; + } - @Override - public Collection fields() { - return List.of( - Field.builder(MASTER_SOURCE) - .optional(false) - .description("The source of master password") - .options(masterSources.entrySet().stream() - .map(e -> { - Field.Builder b = Field.builder(e.getKey()) - .description(e.getValue().description()); - if (e.getValue().configTemplate().isPresent()) { - b = b.defaultValue(e.getValue() - .configTemplate() - .get()); - } - return b.build(); - }) - .toList()) - .build(), - Field.builder(MASTER_CIPHER) - .optional(false) - .description("The cipher to use with master password") - .options(cipher.availableCiphers().stream() - .map(c -> Field.builder(c) - .description("Cipher implementation " + c) - .build()) - .toList()) - .build()); - } - }; + @Override + public Collection fields() { + return List.of( + Field.builder(MASTER_SOURCE) + .optional(false) + .description("The source of master password") + .options(masterSources.entrySet().stream() + .map(e -> { + if (e instanceof MasterSourceMeta m) { + Field.Builder b = + Field.builder(e.getKey()).description(m.description()); + if (m.configTemplate().isPresent()) { + b = b.defaultValue( + m.configTemplate().get()); + } + return b.build(); + } else { + return Field.builder(e.getKey()) + .description("Field not described (needs manual configuration)") + .build(); + } + }) + .toList()) + .build(), + Field.builder(MASTER_CIPHER) + .optional(false) + .description("The cipher to use with master password") + .options(cipher.availableCiphers().stream() + .map(c -> Field.builder(c).description(c).build()) + .toList()) + .build()); } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index df54dc0..f835889 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -23,6 +23,7 @@ import java.util.Optional; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -32,7 +33,7 @@ */ @Singleton @Named(EnvMasterSource.NAME) -public final class EnvMasterSource extends PrefixMasterSourceSupport { +public final class EnvMasterSource extends PrefixMasterSourceSupport implements MasterSourceMeta { public static final String NAME = "env"; public EnvMasterSource() { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index c3b521d..62dda36 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -34,6 +34,7 @@ import java.util.HexFormat; import java.util.Optional; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -43,7 +44,7 @@ */ @Singleton @Named(GpgAgentMasterSource.NAME) -public final class GpgAgentMasterSource extends PrefixMasterSourceSupport { +public final class GpgAgentMasterSource extends PrefixMasterSourceSupport implements MasterSourceMeta { public static final String NAME = "gpg-agent"; public GpgAgentMasterSource() { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java index e811ca8..9bd13a5 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java @@ -21,8 +21,8 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.codehaus.plexus.components.secdispatcher.MasterSource; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.MasterSource; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 1ffc879..593b91b 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -23,6 +23,7 @@ import java.util.Optional; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -32,7 +33,7 @@ */ @Singleton @Named(SystemPropertyMasterSource.NAME) -public final class SystemPropertyMasterSource extends PrefixMasterSourceSupport { +public final class SystemPropertyMasterSource extends PrefixMasterSourceSupport implements MasterSourceMeta { public static final String NAME = "system-property"; public SystemPropertyMasterSource() { From b02edda237c35c3b23aaa598257deb50e7a8d3f1 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 13:38:53 +0200 Subject: [PATCH 07/27] Lazy! --- .../secdispatcher/internal/DefaultSecDispatcher.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index a0d9dcb..c405d59 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -69,8 +69,10 @@ public Set availableDispatchers() { } private DispatcherMeta dispatcherMeta(Map.Entry dispatcher) { - if (dispatcher instanceof DispatcherMeta) { - return (DispatcherMeta) dispatcher; + // sisu components are lazy! + Dispatcher d = dispatcher.getValue(); + if (d instanceof DispatcherMeta meta) { + return meta; } else { return new DispatcherMeta() { @Override From 1bcbbc20ea175f3b619d79f2a7df3b475ea94ce7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 14:24:14 +0200 Subject: [PATCH 08/27] WIP --- .../internal/dispatchers/MasterDispatcher.java | 10 ++++++---- .../internal/sources/EnvMasterSource.java | 2 +- .../internal/sources/GpgAgentMasterSource.java | 2 +- .../internal/sources/SystemPropertyMasterSource.java | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index 462e453..e8737cc 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -66,10 +66,11 @@ public Collection fields() { return List.of( Field.builder(MASTER_SOURCE) .optional(false) - .description("The source of master password") + .description("Source of the master password") .options(masterSources.entrySet().stream() .map(e -> { - if (e instanceof MasterSourceMeta m) { + MasterSource ms = e.getValue(); + if (ms instanceof MasterSourceMeta m) { Field.Builder b = Field.builder(e.getKey()).description(m.description()); if (m.configTemplate().isPresent()) { @@ -79,7 +80,8 @@ public Collection fields() { return b.build(); } else { return Field.builder(e.getKey()) - .description("Field not described (needs manual configuration)") + .description(e.getKey() + + "(Field not described, needs manual configuration)") .build(); } }) @@ -87,7 +89,7 @@ public Collection fields() { .build(), Field.builder(MASTER_CIPHER) .optional(false) - .description("The cipher to use with master password") + .description("Cipher to use with master password") .options(cipher.availableCiphers().stream() .map(c -> Field.builder(c).description(c).build()) .toList()) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index f835889..ffdca96 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -42,7 +42,7 @@ public EnvMasterSource() { @Override public String description() { - return "Extracts the master password out of environment variables with configured name"; + return "Environment variable"; } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index 62dda36..c822224 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -53,7 +53,7 @@ public GpgAgentMasterSource() { @Override public String description() { - return "Interacts with GPG Agent via domain socket to get the master password"; + return "GPG Agent"; } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 593b91b..96ee4bf 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -42,7 +42,7 @@ public SystemPropertyMasterSource() { @Override public String description() { - return "Extracts the master password out of Java System properties with configured key"; + return "Java System properties"; } @Override From 90831d499e1ec29caa7ac8112dffc80a9fed2159 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 16:10:17 +0200 Subject: [PATCH 09/27] Add pinentry --- pom.xml | 12 + .../components/secdispatcher/PinEntry.java | 229 ++++++++++++++++++ .../sources/PinEntryMasterSource.java | 80 ++++++ .../internal/sources/SourcesTest.java | 10 +- 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java diff --git a/pom.xml b/pom.xml index f7aec3a..bce66f0 100644 --- a/pom.xml +++ b/pom.xml @@ -35,9 +35,16 @@ 17 2024-09-29T15:16:00Z + + 2.0.16 + + org.slf4j + slf4j-api + ${version.slf4j} + org.codehaus.plexus plexus-cipher @@ -62,6 +69,11 @@ junit-jupiter test + + org.slf4j + slf4j-simple + ${version.slf4j} + diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java new file mode 100644 index 0000000..b4e6bef --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.codehaus.plexus.components.secdispatcher; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.Objects.requireNonNull; + +/** + * Inspired by A peek inside pinentry + */ +public class PinEntry { + public enum Outcome { + SUCCESS, + TIMEOUT, + NOT_CONFIRMED, + CANCELED, + FAILED; + } + + public record Result(Outcome outcome, T payload) {} + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final String cmd; + private final LinkedHashMap commands; + + public PinEntry(String cmd) { + this.cmd = requireNonNull(cmd); + this.commands = new LinkedHashMap<>(); + } + + public PinEntry setKeyInfo(String keyInfo) { + requireNonNull(keyInfo); + commands.put("SETKEYINFO", keyInfo); + commands.put("OPTION", "allow-external-password-cache"); + return this; + } + + public PinEntry setOk(String msg) { + requireNonNull(msg); + commands.put("SETOK", msg); + return this; + } + + public PinEntry setCancel(String msg) { + requireNonNull(msg); + commands.put("SETCANCEL", msg); + return this; + } + + public PinEntry setTitle(String title) { + requireNonNull(title); + commands.put("SETTITLE", title); + return this; + } + + public PinEntry setDescription(String desc) { + requireNonNull(desc); + commands.put("SETDESC", desc); + return this; + } + + public PinEntry setPrompt(String prompt) { + requireNonNull(prompt); + commands.put("SETPROMPT", prompt); + return this; + } + + public PinEntry confirmPin() { + commands.put("SETREPEAT", cmd); + return this; + } + + public PinEntry setTimeout(Duration timeout) { + long seconds = timeout.toSeconds(); + if (seconds < 0) { + throw new IllegalArgumentException("Set timeout is 0 seconds"); + } + commands.put("SETTIMEOUT", String.valueOf(seconds)); + return this; + } + + public Result getPin() throws IOException { + commands.put("GETPIN", null); + return execute(); + } + + public Result confirm() throws IOException { + commands.put("CONFIRM", null); + return execute(); + } + + public Result message() throws IOException { + commands.put("MESSAGE", null); + return execute(); + } + + private Result execute() throws IOException { + Process process = new ProcessBuilder(cmd).start(); + BufferedReader reader = process.inputReader(); + BufferedWriter writer = process.outputWriter(); + expectOK(process.inputReader()); + Map.Entry lastEntry = commands.entrySet().iterator().next(); + for (Map.Entry entry : commands.entrySet()) { + String cmd; + if (entry.getValue() != null) { + cmd = entry.getKey() + " " + entry.getValue(); + } else { + cmd = entry.getKey(); + } + logger.debug("> {}", cmd); + writer.write(cmd); + writer.newLine(); + writer.flush(); + if (entry != lastEntry) { + expectOK(reader); + } + } + Result result = lastExpect(reader); + writer.write("BYE"); + writer.newLine(); + writer.flush(); + try { + process.waitFor(5, TimeUnit.SECONDS); + int exitCode = process.exitValue(); + if (exitCode != 0) { + return new Result<>(Outcome.FAILED, "Exit code: " + exitCode); + } else { + return result; + } + } catch (Exception e) { + return new Result<>(Outcome.FAILED, e.getMessage()); + } + } + + private void expectOK(BufferedReader in) throws IOException { + String response = in.readLine(); + logger.debug("< {}", response); + if (!response.startsWith("OK")) { + throw new IOException("Expected OK but got this instead: " + response); + } + } + + private Result lastExpect(BufferedReader in) throws IOException { + while (true) { + String response = in.readLine(); + logger.debug("< {}", response); + if (response.startsWith("#")) { + continue; + } + if (response.startsWith("S")) { + continue; + } + if (response.startsWith("ERR")) { + if (response.contains("83886142")) { + return new Result<>(Outcome.TIMEOUT, response); + } + if (response.contains("83886179")) { + return new Result<>(Outcome.CANCELED, response); + } + if (response.contains("83886194")) { + return new Result<>(Outcome.NOT_CONFIRMED, response); + } + } + if (response.startsWith("D")) { + return new Result<>(Outcome.SUCCESS, response.substring(2)); + } + if (response.startsWith("OK")) { + return new Result<>(Outcome.SUCCESS, response); + } + } + } + + public static void main(String[] args) throws IOException { + Result pinResult = new PinEntry("/usr/bin/pinentry-gnome3") + .setTimeout(Duration.ofSeconds(5)) + .setKeyInfo("maven:masterPassword") + .setTitle("Maven Master Password") + .setDescription("Please enter the Maven master password") + .setPrompt("Master password") + .setOk("Her you go!") + .setCancel("Uh oh, rather not") + // .confirmPin() (will not let you through if you cannot type same thing twice) + .getPin(); + if (pinResult.outcome() == Outcome.SUCCESS) { + Result confirmResult = new PinEntry("/usr/bin/pinentry-gnome3") + .setTitle("Password confirmation") + .setDescription("Please confirm that the password you entered is correct") + .setPrompt("Is the password '" + pinResult.payload() + "' the one you want?") + .confirm(); + if (confirmResult.outcome() == Outcome.SUCCESS) { + new PinEntry("/usr/bin/pinentry-gnome3") + .setTitle("Password confirmed") + .setDescription("You confirmed your password") + .setPrompt("The password '" + pinResult.payload() + "' is confirmed.") + .confirm(); + } else { + System.out.println(confirmResult); + } + } else { + System.out.println(pinResult); + } + } +} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java new file mode 100644 index 0000000..b71b645 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.codehaus.plexus.components.secdispatcher.internal.sources; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; + +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.PinEntry; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; + +/** + * Inspired by A peek inside pinentry + */ +@Singleton +@Named(PinEntryMasterSource.NAME) +public class PinEntryMasterSource extends PrefixMasterSourceSupport implements MasterSource, MasterSourceMeta { + public static final String NAME = "pinentry-prompt"; + + public PinEntryMasterSource() { + super(NAME + ":"); + } + + @Override + public String description() { + return "Secure PinEntry prompt"; + } + + @Override + public Optional configTemplate() { + return Optional.of(NAME + ":" + "$pinentryPath"); + } + + @Override + public String doHandle(String s) throws SecDispatcherException { + try { + PinEntry.Result result = new PinEntry(s) + .setTimeout(Duration.ofSeconds(30)) + .setKeyInfo("maven:masterPassword") + .setTitle("Maven Master Password") + .setDescription("Please enter the Maven master password") + .setPrompt("Maven master password") + .setOk("Ok") + .setCancel("Cancel") + .getPin(); + if (result.outcome() == PinEntry.Outcome.SUCCESS) { + return result.payload(); + } else if (result.outcome() == PinEntry.Outcome.CANCELED) { + throw new SecDispatcherException("User canceled the operation"); + } else if (result.outcome() == PinEntry.Outcome.TIMEOUT) { + throw new SecDispatcherException("Timeout"); + } else { + throw new SecDispatcherException("Failure: " + result.payload()); + } + } catch (IOException e) { + throw new SecDispatcherException("Could not collect the password", e); + } + } +} diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java index 068f3a4..76bdde5 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SourcesTest.java @@ -38,7 +38,15 @@ void env() { @Test void gpgAgent() { GpgAgentMasterSource source = new GpgAgentMasterSource(); - // ypu may adjust path, this is Fedora40 WS. Ubuntu does `.gpg/S.gpg-agent` + // you may adjust path, this is Fedora40 WS. Ubuntu does `.gpg/S.gpg-agent` assertEquals("masterPw", source.handle("gpg-agent:/run/user/1000/gnupg/S.gpg-agent")); } + + @Disabled("enable and type in 'masterPw'") + @Test + void pinEntry() { + PinEntryMasterSource source = new PinEntryMasterSource(); + // ypu may adjust path, this is Fedora40 WS + gnome + assertEquals("masterPw", source.handle("pinentry-prompt:/usr/bin/pinentry-gnome3")); + } } From 3c5a2c24a67a80439b5645cde4551b0d1b86ed35 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 10 Oct 2024 16:19:01 +0200 Subject: [PATCH 10/27] Fix scope --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index bce66f0..b792b11 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ org.slf4j slf4j-simple ${version.slf4j} + test From 45096886ac57bf55d101090555df38f201ace275 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 12:34:30 +0200 Subject: [PATCH 11/27] Some slight improvements --- .../components/secdispatcher/PinEntry.java | 104 +++++++++++++----- .../sources/PinEntryMasterSource.java | 6 +- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java index b4e6bef..35b8765 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/PinEntry.java @@ -32,7 +32,9 @@ import static java.util.Objects.requireNonNull; /** - * Inspired by A peek inside pinentry + * Inspired by A peek inside pinentry. + * Also look at Pinentry Documentation. + * Finally, source mirror is at gpg/pinentry. */ public class PinEntry { public enum Outcome { @@ -43,59 +45,96 @@ public enum Outcome { FAILED; } - public record Result(Outcome outcome, T payload) {} + public record Result(Outcome outcome, String payload) {} private final Logger logger = LoggerFactory.getLogger(getClass()); private final String cmd; private final LinkedHashMap commands; + /** + * Creates pin entry instance that will use the passed in cmd executable. + */ public PinEntry(String cmd) { this.cmd = requireNonNull(cmd); this.commands = new LinkedHashMap<>(); } + /** + * Sets a "stable key handle" for caching purposes. Optional. + */ public PinEntry setKeyInfo(String keyInfo) { requireNonNull(keyInfo); - commands.put("SETKEYINFO", keyInfo); commands.put("OPTION", "allow-external-password-cache"); + commands.put("SETKEYINFO", keyInfo); return this; } + /** + * Sets the OK button label, by default "Ok". + */ public PinEntry setOk(String msg) { requireNonNull(msg); commands.put("SETOK", msg); return this; } + /** + * Sets the CANCEL button label, by default "Cancel". + */ public PinEntry setCancel(String msg) { requireNonNull(msg); commands.put("SETCANCEL", msg); return this; } + /** + * Sets the window title. + */ public PinEntry setTitle(String title) { requireNonNull(title); commands.put("SETTITLE", title); return this; } + /** + * Sets additional test in window. + */ public PinEntry setDescription(String desc) { requireNonNull(desc); commands.put("SETDESC", desc); return this; } + /** + * Sets the prompt. + */ public PinEntry setPrompt(String prompt) { requireNonNull(prompt); commands.put("SETPROMPT", prompt); return this; } + /** + * If set, window will show "Error: xxx", usable for second attempt (ie "bad password"). + */ + public PinEntry setError(String error) { + requireNonNull(error); + commands.put("SETERROR", error); + return this; + } + + /** + * Usable with {@link #getPin()}, window will contain two input fields and will force user to type in same + * input in both fields, ie to "confirm" the pin. + */ public PinEntry confirmPin() { - commands.put("SETREPEAT", cmd); + commands.put("SETREPEAT", null); return this; } + /** + * Sets the window timeout, if no button pressed and timeout passes, Result will by {@link Outcome#TIMEOUT}. + */ public PinEntry setTimeout(Duration timeout) { long seconds = timeout.toSeconds(); if (seconds < 0) { @@ -105,22 +144,31 @@ public PinEntry setTimeout(Duration timeout) { return this; } - public Result getPin() throws IOException { + /** + * Initiates a "get pin" dialogue with input field(s) using previously set options. + */ + public Result getPin() throws IOException { commands.put("GETPIN", null); return execute(); } - public Result confirm() throws IOException { + /** + * Initiates a "confirmation" dialogue (no input) using previously set options. + */ + public Result confirm() throws IOException { commands.put("CONFIRM", null); return execute(); } - public Result message() throws IOException { + /** + * Initiates a "message" dialogue (no input) using previously set options. + */ + public Result message() throws IOException { commands.put("MESSAGE", null); return execute(); } - private Result execute() throws IOException { + private Result execute() throws IOException { Process process = new ProcessBuilder(cmd).start(); BufferedReader reader = process.inputReader(); BufferedWriter writer = process.outputWriter(); @@ -141,7 +189,7 @@ private Result execute() throws IOException { expectOK(reader); } } - Result result = lastExpect(reader); + Result result = lastExpect(reader); writer.write("BYE"); writer.newLine(); writer.flush(); @@ -149,12 +197,12 @@ private Result execute() throws IOException { process.waitFor(5, TimeUnit.SECONDS); int exitCode = process.exitValue(); if (exitCode != 0) { - return new Result<>(Outcome.FAILED, "Exit code: " + exitCode); + return new Result(Outcome.FAILED, "Exit code: " + exitCode); } else { return result; } } catch (Exception e) { - return new Result<>(Outcome.FAILED, e.getMessage()); + return new Result(Outcome.FAILED, e.getMessage()); } } @@ -166,7 +214,7 @@ private void expectOK(BufferedReader in) throws IOException { } } - private Result lastExpect(BufferedReader in) throws IOException { + private Result lastExpect(BufferedReader in) throws IOException { while (true) { String response = in.readLine(); logger.debug("< {}", response); @@ -178,47 +226,49 @@ private Result lastExpect(BufferedReader in) throws IOException { } if (response.startsWith("ERR")) { if (response.contains("83886142")) { - return new Result<>(Outcome.TIMEOUT, response); + return new Result(Outcome.TIMEOUT, response); } if (response.contains("83886179")) { - return new Result<>(Outcome.CANCELED, response); + return new Result(Outcome.CANCELED, response); } if (response.contains("83886194")) { - return new Result<>(Outcome.NOT_CONFIRMED, response); + return new Result(Outcome.NOT_CONFIRMED, response); } } if (response.startsWith("D")) { - return new Result<>(Outcome.SUCCESS, response.substring(2)); + return new Result(Outcome.SUCCESS, response.substring(2)); } if (response.startsWith("OK")) { - return new Result<>(Outcome.SUCCESS, response); + return new Result(Outcome.SUCCESS, response); } } } public static void main(String[] args) throws IOException { - Result pinResult = new PinEntry("/usr/bin/pinentry-gnome3") - .setTimeout(Duration.ofSeconds(5)) + // check what pinentry apps you have and replace the execName + String cmd = "/usr/bin/pinentry-gnome3"; + Result pinResult = new PinEntry(cmd) + .setTimeout(Duration.ofSeconds(15)) .setKeyInfo("maven:masterPassword") .setTitle("Maven Master Password") .setDescription("Please enter the Maven master password") - .setPrompt("Master password") - .setOk("Her you go!") + .setPrompt("Password") + .setOk("Here you go!") .setCancel("Uh oh, rather not") // .confirmPin() (will not let you through if you cannot type same thing twice) .getPin(); if (pinResult.outcome() == Outcome.SUCCESS) { - Result confirmResult = new PinEntry("/usr/bin/pinentry-gnome3") + Result confirmResult = new PinEntry(cmd) .setTitle("Password confirmation") - .setDescription("Please confirm that the password you entered is correct") - .setPrompt("Is the password '" + pinResult.payload() + "' the one you want?") + .setPrompt("Please confirm the password") + .setDescription("Is the password '" + pinResult.payload() + "' the one you want?") .confirm(); if (confirmResult.outcome() == Outcome.SUCCESS) { - new PinEntry("/usr/bin/pinentry-gnome3") + new PinEntry(cmd) .setTitle("Password confirmed") - .setDescription("You confirmed your password") .setPrompt("The password '" + pinResult.payload() + "' is confirmed.") - .confirm(); + .setDescription("You confirmed your password") + .message(); } else { System.out.println(confirmResult); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java index b71b645..6447de1 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java @@ -31,7 +31,7 @@ import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** - * Inspired by A peek inside pinentry + * Master source using {@link PinEntry} */ @Singleton @Named(PinEntryMasterSource.NAME) @@ -55,9 +55,9 @@ public Optional configTemplate() { @Override public String doHandle(String s) throws SecDispatcherException { try { - PinEntry.Result result = new PinEntry(s) + PinEntry.Result result = new PinEntry(s) .setTimeout(Duration.ofSeconds(30)) - .setKeyInfo("maven:masterPassword") + .setKeyInfo("Maven: n/masterPassword") .setTitle("Maven Master Password") .setDescription("Please enter the Maven master password") .setPrompt("Maven master password") From 0aaf0151608caa736e4c38683d9e9be8d7e3dacf Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 13:09:04 +0200 Subject: [PATCH 12/27] Simplify it more Do not provide "bad" and "never used" defaults as before as it was just the source of confusion, as old component had to be "redefined" in Plexus XML or alike anyway, to be usable in Maven. --- .../secdispatcher/SecDispatcher.java | 17 +---- .../internal/DefaultSecDispatcher.java | 67 +++++++------------ .../secdispatcher/internal/SecUtil.java | 3 - .../internal/DefaultSecDispatcherTest.java | 10 +-- 4 files changed, 34 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 62ea046..09ec886 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -25,19 +25,6 @@ * @author Oleg Gusakov */ public interface SecDispatcher { - /** - * The default path of configuration. - *

        - * The character {@code ~} (tilde) may be present as first character ONLY and is - * interpreted as "user.home" system property, and it MUST be followed by path separator. - */ - String DEFAULT_CONFIGURATION = "~/.m2/settings-security.xml"; - - /** - * Java System Property that may be set, to override configuration path. - */ - String SYSTEM_PROPERTY_CONFIGURATION_LOCATION = "settings.security"; - /** * Attribute that selects a dispatcher. * @@ -58,7 +45,7 @@ public interface SecDispatcher { * @return encrypted string * @throws SecDispatcherException in case of problem */ - String encrypt(String str, Map attr) throws SecDispatcherException; + String encrypt(String str, Map attr) throws SecDispatcherException, IOException; /** * Decrypt given encrypted string. @@ -67,7 +54,7 @@ public interface SecDispatcher { * @return decrypted string * @throws SecDispatcherException in case of problem */ - String decrypt(String str) throws SecDispatcherException; + String decrypt(String str) throws SecDispatcherException, IOException; /** * Reads the effective configuration, eventually creating new instance if not present. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index c405d59..3d97480 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -13,13 +13,9 @@ package org.codehaus.plexus.components.secdispatcher.internal; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -40,26 +36,33 @@ import static java.util.Objects.requireNonNull; /** + * Note: this implementation is NOT a JSR330 component. Integrating apps anyway want to customize it (at least + * the name and location of configuration file), so instead as before (providing "bad" configuration file just + * to have one), it is the duty of integrator to wrap and "finish" the implementation in a way it suits the + * integrator. Also, using "globals" like Java System Properties are bad thing, and it is integrator who knows + * what is needed anyway. + *

        + * Recommended way for integration is to create JSR330 {@link javax.inject.Provider}. + * * @author Oleg Gusakov */ -@Singleton -@Named public class DefaultSecDispatcher implements SecDispatcher { public static final String ATTR_START = "["; public static final String ATTR_STOP = "]"; protected final PlexusCipher cipher; protected final Map dispatchers; - protected final String configurationFile; + protected final Path configurationFile; - @Inject - public DefaultSecDispatcher( - PlexusCipher cipher, - Map dispatchers, - @Named("${configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String configurationFile) { + public DefaultSecDispatcher(PlexusCipher cipher, Map dispatchers, Path configurationFile) { this.cipher = requireNonNull(cipher); this.dispatchers = requireNonNull(dispatchers); this.configurationFile = requireNonNull(configurationFile); + + // file may or may not exist, but one thing is certain: it cannot be an exiting directory + if (Files.isDirectory(configurationFile)) { + throw new IllegalArgumentException("configurationFile cannot be a directory"); + } } @Override @@ -94,7 +97,7 @@ public Collection fields() { } @Override - public String encrypt(String str, Map attr) throws SecDispatcherException { + public String encrypt(String str, Map attr) throws SecDispatcherException, IOException { if (isEncryptedString(str)) return str; try { @@ -130,7 +133,7 @@ public String encrypt(String str, Map attr) throws SecDispatcher } @Override - public String decrypt(String str) throws SecDispatcherException { + public String decrypt(String str) throws SecDispatcherException, IOException { if (!isEncryptedString(str)) return str; try { String bare = cipher.unDecorate(str); @@ -149,7 +152,7 @@ public String decrypt(String str) throws SecDispatcherException { @Override public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException { - SettingsSecurity configuration = SecUtil.read(getConfigurationPath()); + SettingsSecurity configuration = SecUtil.read(configurationFile); if (configuration == null && createIfMissing) { configuration = new SettingsSecurity(); } @@ -159,10 +162,10 @@ public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOExce @Override public void writeConfiguration(SettingsSecurity configuration) throws IOException { requireNonNull(configuration, "configuration is null"); - SecUtil.write(getConfigurationPath(), configuration, true); + SecUtil.write(configurationFile, configuration, true); } - private Map prepareDispatcherConfig(String type) { + protected Map prepareDispatcherConfig(String type) throws IOException { HashMap dispatcherConf = new HashMap<>(); Map conf = SecUtil.getConfig(getConfiguration(), type); if (conf != null) { @@ -171,7 +174,7 @@ private Map prepareDispatcherConfig(String type) { return dispatcherConf; } - private String strip(String str) { + protected String strip(String str) { int start = str.indexOf(ATTR_START); int stop = str.indexOf(ATTR_STOP); if (start != -1 && stop != -1 && stop > start) { @@ -180,7 +183,7 @@ private String strip(String str) { return str; } - private Map stripAttributes(String str) { + protected Map stripAttributes(String str) { HashMap result = new HashMap<>(); int start = str.indexOf(ATTR_START); int stop = str.indexOf(ATTR_STOP); @@ -202,30 +205,12 @@ private Map stripAttributes(String str) { return result; } - private boolean isEncryptedString(String str) { + protected boolean isEncryptedString(String str) { if (str == null) return false; return cipher.isEncryptedString(str); } - private Path getConfigurationPath() { - String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile()); - location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location; - return Paths.get(location); - } - - private SettingsSecurity getConfiguration() throws SecDispatcherException { - Path path = getConfigurationPath(); - try { - SettingsSecurity sec = SecUtil.read(path); - if (sec == null) - throw new SecDispatcherException("Please check that configuration file on path " + path + " exists"); - return sec; - } catch (IOException e) { - throw new SecDispatcherException(e.getMessage(), e); - } - } - - public String getConfigurationFile() { - return configurationFile; + protected SettingsSecurity getConfiguration() throws SecDispatcherException, IOException { + return SecUtil.read(configurationFile); } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 0338683..9fba038 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -20,7 +20,6 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.HashMap; @@ -58,8 +57,6 @@ public static SettingsSecurity read(Path configurationFile) throws IOException { sec = new SecurityConfigurationStaxReader().read(in); } return sec; - } catch (NoSuchFileException e) { - return null; } catch (XMLStreamException e) { throw new IOException("Parsing error", e); } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 0693de0..962b58c 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -16,6 +16,7 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; @@ -36,6 +37,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class DefaultSecDispatcherTest { + private final Path CONFIG_PATH = Paths.get("./target/sec.xml"); + private void saveSec(String dispatcher, Map config) throws Exception { SettingsSecurity sec = new SettingsSecurity(); sec.setModelEncoding(StandardCharsets.UTF_8.name()); @@ -56,10 +59,9 @@ private void saveSec(SettingsSecurity sec) throws Exception { sec.setModelEncoding(StandardCharsets.UTF_8.name()); sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { + try (OutputStream fos = Files.newOutputStream(CONFIG_PATH)) { new SecurityConfigurationStaxWriter().write(fos, sec); } - System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml"); } @Test @@ -74,7 +76,7 @@ void masterWithSystemPropertyRoundTrip() throws Exception { roundtrip(); } - protected void roundtrip() { + protected void roundtrip() throws Exception { DefaultSecDispatcher sd = construct(); assertEquals(1, sd.availableDispatchers().size()); @@ -104,6 +106,6 @@ protected DefaultSecDispatcher construct() { new SystemPropertyMasterSource(), GpgAgentMasterSource.NAME, new GpgAgentMasterSource()))), - DefaultSecDispatcher.DEFAULT_CONFIGURATION); + CONFIG_PATH); } } From 5a578bfcc7048b3f9ffcdb231ee8289cf91e8d89 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 13:15:19 +0200 Subject: [PATCH 13/27] Move off from deprecated config --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b792b11..0e92784 100644 --- a/pom.xml +++ b/pom.xml @@ -109,9 +109,9 @@ org.apache.maven.plugins maven-surefire-plugin - + masterPw - + masterPw From b5bd3a4208114cc0083cfe1e465b9f1a60a2e343 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 16:13:09 +0200 Subject: [PATCH 14/27] Make clear the intent --- .../secdispatcher/internal/sources/EnvMasterSource.java | 4 ++-- .../secdispatcher/internal/sources/GpgAgentMasterSource.java | 4 ++-- .../secdispatcher/internal/sources/PinEntryMasterSource.java | 4 ++-- .../internal/sources/SystemPropertyMasterSource.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index ffdca96..289a189 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -42,12 +42,12 @@ public EnvMasterSource() { @Override public String description() { - return "Environment variable"; + return "Environment variable (variable name should be edited)"; } @Override public Optional configTemplate() { - return Optional.of(NAME + ":" + "ENV_VARIABLE"); + return Optional.of(NAME + ":$VARIABLE_NAME"); } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index c822224..541a142 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -53,12 +53,12 @@ public GpgAgentMasterSource() { @Override public String description() { - return "GPG Agent"; + return "GPG Agent (agent socket path should be edited)"; } @Override public Optional configTemplate() { - return Optional.of(NAME + ":" + "$agentSocketPath"); + return Optional.of(NAME + ":$agentSocketPath"); } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java index 6447de1..e2e59d3 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java @@ -44,12 +44,12 @@ public PinEntryMasterSource() { @Override public String description() { - return "Secure PinEntry prompt"; + return "Secure PinEntry prompt (pinentry path should be edited)"; } @Override public Optional configTemplate() { - return Optional.of(NAME + ":" + "$pinentryPath"); + return Optional.of(NAME + ":$pinentryPath"); } @Override diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 96ee4bf..622dccc 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -42,12 +42,12 @@ public SystemPropertyMasterSource() { @Override public String description() { - return "Java System properties"; + return "Java System properties (property name should be edited)"; } @Override public Optional configTemplate() { - return Optional.of(NAME + ":" + "$systemProperty"); + return Optional.of(NAME + ":$systemProperty"); } @Override From ce4458144a1ec48a955aff1912d355c41f6adc9d Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 18:21:34 +0200 Subject: [PATCH 15/27] Support legacy --- .../secdispatcher/DispatcherMeta.java | 7 + .../secdispatcher/SecDispatcher.java | 5 + .../internal/DefaultSecDispatcher.java | 11 +- .../dispatchers/LegacyDispatcher.java | 190 ++++++++++++++++++ .../dispatchers/LegacyDispatcherTest.java | 44 ++++ .../legacy/legacy-settings-security-1.xml | 3 + .../legacy/legacy-settings-security-2.xml | 4 + 7 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java create mode 100644 src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcherTest.java create mode 100644 src/test/legacy/legacy-settings-security-1.xml create mode 100644 src/test/legacy/legacy-settings-security-2.xml diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java index e222df0..b99b5df 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java @@ -104,6 +104,13 @@ public Field build() { } } + /** + * Option to hide this instance from users, like for migration or legacy purposes. + */ + default boolean isHidden() { + return false; + } + /** * The name of the dispatcher. */ diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 09ec886..e047e80 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -56,6 +56,11 @@ public interface SecDispatcher { */ String decrypt(String str) throws SecDispatcherException, IOException; + /** + * Returns {@code true} if passed in string contains "legacy" password (Maven3 kind). + */ + boolean isLegacyPassword(String str); + /** * Reads the effective configuration, eventually creating new instance if not present. * diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 3d97480..39ff4c9 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -31,6 +31,7 @@ import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; import static java.util.Objects.requireNonNull; @@ -139,7 +140,8 @@ public String decrypt(String str) throws SecDispatcherException, IOException { String bare = cipher.unDecorate(str); Map attr = requireNonNull(stripAttributes(bare)); if (attr.get(DISPATCHER_NAME_ATTR) == null) { - throw new SecDispatcherException("malformed password: no attribute with name"); + // TODO: log? + attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME); } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); @@ -150,6 +152,13 @@ public String decrypt(String str) throws SecDispatcherException, IOException { } } + @Override + public boolean isLegacyPassword(String str) { + if (!isEncryptedString(str)) return false; + Map attr = requireNonNull(stripAttributes(cipher.unDecorate(str))); + return !attr.containsKey(DISPATCHER_NAME_ATTR); + } + @Override public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException { SettingsSecurity configuration = SecUtil.read(configurationFile); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java new file mode 100644 index 0000000..ec76a91 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.components.cipher.PlexusCipher; +import org.codehaus.plexus.components.cipher.PlexusCipherException; +import org.codehaus.plexus.components.secdispatcher.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.xml.sax.InputSource; + +/** + * This dispatcher is legacy, serves the purpose of migration only. Should not be used. + */ +@Singleton +@Named(LegacyDispatcher.NAME) +public class LegacyDispatcher implements Dispatcher, DispatcherMeta { + public static final String NAME = "legacy"; + + private static final String MASTER_MASTER_PASSWORD = "settings.security"; + + private final PlexusCipher plexusCipher; + private final LegacyCipher legacyCipher; + + @Inject + public LegacyDispatcher(PlexusCipher plexusCipher) { + this.plexusCipher = plexusCipher; + this.legacyCipher = new LegacyCipher(); + } + + @Override + public boolean isHidden() { + return true; + } + + @Override + public String name() { + return NAME; + } + + @Override + public String displayName() { + return "LEGACY (for migration purposes; is hidden)"; + } + + @Override + public Collection fields() { + return List.of(); + } + + @Override + public EncryptPayload encrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + throw new SecDispatcherException( + NAME + " dispatcher MUST not be used for encryption; is inherently insecure and broken"); + } + + @Override + public String decrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + try { + return legacyCipher.decrypt64(str, getMasterPassword()); + } catch (PlexusCipherException e) { + throw new SecDispatcherException("Decrypt failed", e); + } + } + + private String getMasterPassword() throws SecDispatcherException { + String encryptedMasterPassword = getMasterMasterPasswordFromSettingsSecurityXml(); + return legacyCipher.decrypt64(plexusCipher.unDecorate(encryptedMasterPassword), MASTER_MASTER_PASSWORD); + } + + private String getMasterMasterPasswordFromSettingsSecurityXml() { + Path xml; + String override = System.getProperty(MASTER_MASTER_PASSWORD); + if (override != null) { + xml = Paths.get(override); + } else { + xml = Paths.get(System.getProperty("user.home"), ".m2", "settings-security.xml"); + } + if (Files.exists(xml)) { + try (InputStream is = Files.newInputStream(xml)) { + return (String) XPathFactory.newInstance() + .newXPath() + .evaluate("//master", new InputSource(is), XPathConstants.STRING); + } catch (Exception e) { + // just ignore whatever it is + } + } + throw new SecDispatcherException("Could not locate legacy master password: " + xml); + } + + private static final class LegacyCipher { + private static final String STRING_ENCODING = "UTF8"; + private static final int SPICE_SIZE = 16; + private static final int SALT_SIZE = 8; + private static final String DIGEST_ALG = "SHA-256"; + private static final String KEY_ALG = "AES"; + private static final String CIPHER_ALG = "AES/CBC/PKCS5Padding"; + + private String decrypt64(final String encryptedText, final String password) throws PlexusCipherException { + try { + byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes()); + int totalLen = allEncryptedBytes.length; + byte[] salt = new byte[SALT_SIZE]; + System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE); + byte padLen = allEncryptedBytes[SALT_SIZE]; + byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen]; + System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length); + Cipher cipher = createCipher(password.getBytes(STRING_ENCODING), salt, Cipher.DECRYPT_MODE); + byte[] clearBytes = cipher.doFinal(encryptedBytes); + return new String(clearBytes, STRING_ENCODING); + } catch (Exception e) { + throw new PlexusCipherException("Error decrypting", e); + } + } + + private Cipher createCipher(final byte[] pwdAsBytes, byte[] salt, final int mode) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException { + MessageDigest _digester = MessageDigest.getInstance(DIGEST_ALG); + byte[] keyAndIv = new byte[SPICE_SIZE * 2]; + if (salt == null || salt.length == 0) { + salt = null; + } + byte[] result; + int currentPos = 0; + while (currentPos < keyAndIv.length) { + _digester.update(pwdAsBytes); + if (salt != null) { + _digester.update(salt, 0, 8); + } + result = _digester.digest(); + int stillNeed = keyAndIv.length - currentPos; + if (result.length > stillNeed) { + byte[] b = new byte[stillNeed]; + System.arraycopy(result, 0, b, 0, b.length); + result = b; + } + System.arraycopy(result, 0, keyAndIv, currentPos, result.length); + currentPos += result.length; + if (currentPos < keyAndIv.length) { + _digester.reset(); + _digester.update(result); + } + } + byte[] key = new byte[SPICE_SIZE]; + byte[] iv = new byte[SPICE_SIZE]; + System.arraycopy(keyAndIv, 0, key, 0, key.length); + System.arraycopy(keyAndIv, key.length, iv, 0, iv.length); + Cipher cipher = Cipher.getInstance(CIPHER_ALG); + cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv)); + return cipher; + } + } +} diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcherTest.java new file mode 100644 index 0000000..f02e758 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcherTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import java.util.Map; + +import org.codehaus.plexus.components.cipher.internal.DefaultPlexusCipher; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LegacyDispatcherTest { + /** + * Test values created with Maven 3.9.9. + *

        + * master password: "masterpassword" + * password: "password" + */ + @ParameterizedTest + @ValueSource( + strings = { + "src/test/legacy/legacy-settings-security-1.xml", + "src/test/legacy/legacy-settings-security-2.xml" + }) + void smoke(String xml) { + System.setProperty("settings.security", xml); + LegacyDispatcher legacyDispatcher = new LegacyDispatcher(new DefaultPlexusCipher(Map.of())); + // SecDispatcher "un decorates" the PW + String cleartext = legacyDispatcher.decrypt("L6L/HbmrY+cH+sNkphnq3fguYepTpM04WlIXb8nB1pk=", Map.of(), Map.of()); + assertEquals("password", cleartext); + } +} diff --git a/src/test/legacy/legacy-settings-security-1.xml b/src/test/legacy/legacy-settings-security-1.xml new file mode 100644 index 0000000..eb4ff1e --- /dev/null +++ b/src/test/legacy/legacy-settings-security-1.xml @@ -0,0 +1,3 @@ + + {KDvsYOFLlXgH4LU8tvpzAGg5otiosZXvfdQq0yO86LU=} + diff --git a/src/test/legacy/legacy-settings-security-2.xml b/src/test/legacy/legacy-settings-security-2.xml new file mode 100644 index 0000000..0f7b33d --- /dev/null +++ b/src/test/legacy/legacy-settings-security-2.xml @@ -0,0 +1,4 @@ + + to the moon + {KDvsYOFLlXgH4LU8tvpzAGg5otiosZXvfdQq0yO86LU=} + From 1cd393b636f0fbf13eb1c017360f85955012028b Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 18:37:54 +0200 Subject: [PATCH 16/27] Finish up legacy --- .../secdispatcher/internal/DefaultSecDispatcher.java | 9 +++------ .../components/secdispatcher/internal/SecUtil.java | 8 +++++++- .../internal/dispatchers/LegacyDispatcher.java | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 39ff4c9..93d29bc 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -111,7 +111,8 @@ public String encrypt(String str, Map attr) throws SecDispatcher attr.put( DISPATCHER_NAME_ATTR, requireNonNull( - getConfiguration().getDefaultDispatcher(), + requireNonNull(SecUtil.read(configurationFile), "no configuration") + .getDefaultDispatcher(), "no default dispatcher set in configuration")); } String name = attr.get(DISPATCHER_NAME_ATTR); @@ -176,7 +177,7 @@ public void writeConfiguration(SettingsSecurity configuration) throws IOExceptio protected Map prepareDispatcherConfig(String type) throws IOException { HashMap dispatcherConf = new HashMap<>(); - Map conf = SecUtil.getConfig(getConfiguration(), type); + Map conf = SecUtil.getConfig(SecUtil.read(configurationFile), type); if (conf != null) { dispatcherConf.putAll(conf); } @@ -218,8 +219,4 @@ protected boolean isEncryptedString(String str) { if (str == null) return false; return cipher.isEncryptedString(str); } - - protected SettingsSecurity getConfiguration() throws SecDispatcherException, IOException { - return SecUtil.read(configurationFile); - } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 9fba038..f448e04 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -20,6 +20,7 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.HashMap; @@ -47,7 +48,7 @@ public final class SecUtil { private SecUtil() {} /** - * Reads the configuration model up, optionally resolving relocation too. + * Reads the configuration model up, if exists, otherwise returns {@code null}. */ public static SettingsSecurity read(Path configurationFile) throws IOException { requireNonNull(configurationFile, "configurationFile must not be null"); @@ -57,11 +58,16 @@ public static SettingsSecurity read(Path configurationFile) throws IOException { sec = new SecurityConfigurationStaxReader().read(in); } return sec; + } catch (NoSuchFileException e) { + return null; } catch (XMLStreamException e) { throw new IOException("Parsing error", e); } } + /** + * Returns config with given name, or {@code null} if not exist. + */ public static Map getConfig(SettingsSecurity sec, String name) { if (sec != null && name != null) { List cl = sec.getConfigurations(); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java index ec76a91..01112a4 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -74,7 +74,7 @@ public String name() { @Override public String displayName() { - return "LEGACY (for migration purposes; is hidden)"; + return "LEGACY (for migration purposes only; can only decrypt)"; } @Override From c52346138c8208b8b1c99121c929294757cb8c17 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 11 Oct 2024 22:15:15 +0200 Subject: [PATCH 17/27] Add all attributes and shorten them --- .../secdispatcher/SecDispatcher.java | 7 +++- .../secdispatcher/internal/SecUtil.java | 10 +++++- .../dispatchers/MasterDispatcher.java | 35 +++++++++++-------- .../internal/DefaultSecDispatcherTest.java | 6 ++-- .../secdispatcher/internal/SecUtilTest.java | 4 +-- 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index e047e80..38b015c 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -30,7 +30,12 @@ public interface SecDispatcher { * * @see #availableDispatchers() */ - String DISPATCHER_NAME_ATTR = "name"; + String DISPATCHER_NAME_ATTR = "n"; + + /** + * Attribute for version that dispatcher should use. + */ + String DISPATCHER_VERSION_ATTR = "v"; /** * Returns the set of available dispatcher metadata, never {@code null}. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index f448e04..5d93c19 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -91,6 +91,14 @@ public static Map getConfig(SettingsSecurity sec, String name) { return null; } + public static String specVersion() { + String specVer = SecDispatcher.class.getPackage().getSpecificationVersion(); + if (specVer == null) { + specVer = "test"; // in UT + } + return specVer; + } + private static final boolean IS_WINDOWS = System.getProperty("os.name", "unknown").startsWith("Windows"); @@ -102,7 +110,7 @@ public static void write(Path target, SettingsSecurity configuration, boolean do Path tempFile = parent.resolve(target.getFileName() + "." + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); - configuration.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); + configuration.setModelVersion(specVersion()); configuration.setModelEncoding(StandardCharsets.UTF_8.name()); try { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index e8737cc..0363d3a 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -30,6 +30,7 @@ import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.SecUtil; /** * This dispatcher is logically equivalent (but much more secure) that Maven3 "master password" encryption. @@ -39,8 +40,9 @@ public class MasterDispatcher implements Dispatcher, DispatcherMeta { public static final String NAME = "master"; - private static final String MASTER_CIPHER = "cipher"; - private static final String MASTER_SOURCE = "source"; + private static final String MASTER_CIPHER_ATTR = "c"; + private static final String CONF_MASTER_CIPHER = "cipher"; + private static final String CONF_MASTER_SOURCE = "source"; private final PlexusCipher cipher; protected final Map masterSources; @@ -64,7 +66,7 @@ public String displayName() { @Override public Collection fields() { return List.of( - Field.builder(MASTER_SOURCE) + Field.builder(CONF_MASTER_SOURCE) .optional(false) .description("Source of the master password") .options(masterSources.entrySet().stream() @@ -87,7 +89,7 @@ public Collection fields() { }) .toList()) .build(), - Field.builder(MASTER_CIPHER) + Field.builder(CONF_MASTER_CIPHER) .optional(false) .description("Cipher to use with master password") .options(cipher.availableCiphers().stream() @@ -104,7 +106,8 @@ public EncryptPayload encrypt(String str, Map attributes, Map attr = new HashMap<>(attributes); attr.put(SecDispatcher.DISPATCHER_NAME_ATTR, NAME); - attr.put(MASTER_CIPHER, masterCipher); + attr.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion()); + attr.put(MASTER_CIPHER_ATTR, masterCipher); return new EncryptPayload(attr, encrypted); } catch (PlexusCipherException e) { throw new SecDispatcherException("Encrypt failed", e); @@ -123,9 +126,9 @@ public String decrypt(String str, Map attributes, Map config) throws SecDispatcherException { - String masterSource = config.get(MASTER_SOURCE); + String masterSource = config.get(CONF_MASTER_SOURCE); if (masterSource == null) { - throw new SecDispatcherException("Invalid configuration: Missing configuration " + MASTER_SOURCE); + throw new SecDispatcherException("Invalid configuration: Missing configuration " + CONF_MASTER_SOURCE); } for (MasterSource masterPasswordSource : masterSources.values()) { String masterPassword = masterPasswordSource.handle(masterSource); @@ -135,14 +138,18 @@ private String getMasterPassword(Map config) throws SecDispatche } private String getMasterCipher(Map source, boolean config) throws SecDispatcherException { - String masterCipher = source.get(MASTER_CIPHER); - if (masterCipher == null) { - if (config) { - throw new SecDispatcherException("Invalid configuration: Missing configuration " + MASTER_CIPHER); - } else { - throw new SecDispatcherException("Malformed attributes: Missing attribute " + MASTER_CIPHER); + if (config) { + String masterCipher = source.get(CONF_MASTER_CIPHER); + if (masterCipher == null) { + throw new SecDispatcherException("Invalid configuration: Missing configuration " + CONF_MASTER_CIPHER); } + return masterCipher; + } else { + String masterCipher = source.get(MASTER_CIPHER_ATTR); + if (masterCipher == null) { + throw new SecDispatcherException("Malformed attributes: Missing attribute " + MASTER_CIPHER_ATTR); + } + return masterCipher; } - return masterCipher; } } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 962b58c..d135f4e 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -83,9 +83,11 @@ protected void roundtrip() throws Exception { String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); // example: // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} + System.out.println(encrypted); assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); - assertTrue(encrypted.contains("name=master")); - assertTrue(encrypted.contains("cipher=" + AESGCMNoPadding.CIPHER_ALG)); + assertTrue(encrypted.contains("n=master")); + assertTrue(encrypted.contains("c=" + AESGCMNoPadding.CIPHER_ALG)); + assertTrue(encrypted.contains("v=test")); assertTrue(encrypted.contains("a=b")); String pass = sd.decrypt(encrypted); assertEquals("supersecret", pass); diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index 94fe8e4..7773ad9 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -68,7 +68,7 @@ void readWrite() throws IOException { Path path = Path.of("./target/sec.xml"); SettingsSecurity config = SecUtil.read(path); assertNotNull(config); - assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); + assertEquals(SecUtil.specVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); assertEquals("magic:mighty", config.getDefaultDispatcher()); SecUtil.write(path, config, false); @@ -79,7 +79,7 @@ void readWriteWithBackup() throws IOException { Path path = Path.of("./target/sec.xml"); SettingsSecurity config = SecUtil.read(path); assertNotNull(config); - assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); + assertEquals(SecUtil.specVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); assertEquals("magic:mighty", config.getDefaultDispatcher()); SecUtil.write(path, config, true); From 433f8d4c293b96ba51f61d73868f6b49e45941a3 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 11:38:33 +0200 Subject: [PATCH 18/27] Add "deep validation" --- .../components/secdispatcher/Dispatcher.java | 5 ++ .../secdispatcher/DispatcherMeta.java | 4 +- .../secdispatcher/MasterSource.java | 9 +++ .../secdispatcher/SecDispatcher.java | 47 +++++++++++++++ .../internal/DefaultSecDispatcher.java | 57 +++++++++++++++++-- .../dispatchers/LegacyDispatcher.java | 12 ++++ .../dispatchers/MasterDispatcher.java | 49 ++++++++++++++++ .../internal/sources/EnvMasterSource.java | 25 ++++++++ .../sources/GpgAgentMasterSource.java | 38 +++++++++++++ .../internal/sources/MasterSourceSupport.java | 10 ++++ .../sources/PinEntryMasterSource.java | 29 ++++++++++ .../sources/SystemPropertyMasterSource.java | 25 ++++++++ .../internal/DefaultSecDispatcherTest.java | 31 +++++++++- 13 files changed, 334 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java index 0630547..059e1e3 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/Dispatcher.java @@ -68,4 +68,9 @@ EncryptPayload encrypt(String str, Map attributes, Map attributes, Map config) throws SecDispatcherException; + + /** + * Validates dispatcher configuration. + */ + SecDispatcher.ValidationResponse validateConfiguration(Map config); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java index b99b5df..219ec46 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/DispatcherMeta.java @@ -10,7 +10,7 @@ * Meta description of dispatcher. */ public interface DispatcherMeta { - class Field { + final class Field { private final String key; private final boolean optional; private final String defaultValue; @@ -67,7 +67,7 @@ public static Builder builder(String key) { return new Builder(key); } - public static class Builder { + public static final class Builder { private final String key; private boolean optional; private String defaultValue; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java index f9350dc..d5a754b 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/MasterSource.java @@ -30,4 +30,13 @@ public interface MasterSource { * @throws SecDispatcherException If implementation does handle this masterSource, but cannot obtain master password */ String handle(String config) throws SecDispatcherException; + + /** + * Validates master source configuration. + *

          + *
        • if the config cannot be handled by given source, return {@code null}
        • + *
        • otherwise, implementation performs validation and returns non-{@code null} validation response
        • + *
        + */ + SecDispatcher.ValidationResponse validateConfiguration(String config); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 38b015c..b231989 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -14,6 +14,7 @@ package org.codehaus.plexus.components.secdispatcher; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Set; @@ -82,4 +83,50 @@ public interface SecDispatcher { * @throws IOException In case of IO problem */ void writeConfiguration(SettingsSecurity configuration) throws IOException; + + /** + * The validation response. + */ + final class ValidationResponse { + public enum Level { + INFO, + WARNING, + ERROR + }; + + private final String source; + private final boolean valid; + private final Map> report; + private final List subsystems; + + public ValidationResponse( + String source, boolean valid, Map> report, List subsystems) { + this.source = source; + this.valid = valid; + this.report = report; + this.subsystems = subsystems; + } + + public String getSource() { + return source; + } + + public boolean isValid() { + return valid; + } + + public Map> getReport() { + return report; + } + + public List getSubsystems() { + return subsystems; + } + } + + /** + * Performs a "deep validation" and reports the status. If return instance {@link ValidationResponse#isValid()} + * is {@code true}, configuration is usable. + */ + ValidationResponse validateConfiguration(); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 93d29bc..ef87081 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -140,10 +141,12 @@ public String decrypt(String str) throws SecDispatcherException, IOException { try { String bare = cipher.unDecorate(str); Map attr = requireNonNull(stripAttributes(bare)); - if (attr.get(DISPATCHER_NAME_ATTR) == null) { - // TODO: log? + if (isLegacyPassword(str)) { attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME); } + if (attr.get(DISPATCHER_NAME_ATTR) == null) { + throw new SecDispatcherException("Invalid encrypted string; mandatory attributes missing"); + } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); @@ -175,9 +178,55 @@ public void writeConfiguration(SettingsSecurity configuration) throws IOExceptio SecUtil.write(configurationFile, configuration, true); } - protected Map prepareDispatcherConfig(String type) throws IOException { + @Override + public ValidationResponse validateConfiguration() { + HashMap> report = new HashMap<>(); + ArrayList subsystems = new ArrayList<>(); + boolean valid = false; + try { + SettingsSecurity config = readConfiguration(false); + if (config == null) { + report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("No configuration file found on path " + configurationFile); + } else { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Configuration file present on path " + configurationFile); + String defaultDispatcher = config.getDefaultDispatcher(); + if (defaultDispatcher == null) { + report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("No default dispatcher set in configuration"); + } else { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Default dispatcher set to " + defaultDispatcher); + Dispatcher dispatcher = dispatchers.get(defaultDispatcher); + if (dispatcher == null) { + report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Default dispatcher " + defaultDispatcher + " not present in system"); + } else { + ValidationResponse dispatcherResponse = + dispatcher.validateConfiguration(prepareDispatcherConfig(defaultDispatcher)); + subsystems.add(dispatcherResponse); + if (!dispatcherResponse.isValid()) { + report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Default dispatcher " + defaultDispatcher + " configuration is invalid"); + } else { + valid = true; + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Default dispatcher " + defaultDispatcher + " configuration is valid"); + } + } + } + } + } catch (IOException e) { + report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add(e.getMessage()); + } + return new ValidationResponse(getClass().getSimpleName(), valid, report, subsystems); + } + + protected Map prepareDispatcherConfig(String name) throws IOException { HashMap dispatcherConf = new HashMap<>(); - Map conf = SecUtil.getConfig(SecUtil.read(configurationFile), type); + Map conf = SecUtil.getConfig(SecUtil.read(configurationFile), name); if (conf != null) { dispatcherConf.putAll(conf); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java index 01112a4..58b3382 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -40,6 +40,7 @@ import org.codehaus.plexus.components.cipher.PlexusCipherException; import org.codehaus.plexus.components.secdispatcher.Dispatcher; import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.xml.sax.InputSource; @@ -99,6 +100,17 @@ public String decrypt(String str, Map attributes, Map config) { + return new SecDispatcher.ValidationResponse( + getClass().getSimpleName(), + false, + Map.of( + SecDispatcher.ValidationResponse.Level.ERROR, + List.of("This dispatcher cannot and must not be directly used via configuration")), + List.of()); + } + private String getMasterPassword() throws SecDispatcherException { String encryptedMasterPassword = getMasterMasterPasswordFromSettingsSecurityXml(); return legacyCipher.decrypt64(plexusCipher.unDecorate(encryptedMasterPassword), MASTER_MASTER_PASSWORD); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index 0363d3a..bc13fd2 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -17,6 +17,7 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -125,6 +126,54 @@ public String decrypt(String str, Map attributes, Map config) { + HashMap> report = new HashMap<>(); + ArrayList subsystems = new ArrayList<>(); + boolean valid = false; + String masterCipher = config.get(CONF_MASTER_CIPHER); + if (masterCipher == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Cipher configuration missing"); + } else { + if (!cipher.availableCiphers().contains(masterCipher)) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Cipher " + masterCipher + " not supported"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Master Cipher " + masterCipher + " supported"); + } + } + String masterSource = config.get(CONF_MASTER_SOURCE); + if (masterSource == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Source configuration missing"); + } else { + SecDispatcher.ValidationResponse masterSourceResponse = null; + for (MasterSource masterPasswordSource : masterSources.values()) { + masterSourceResponse = masterPasswordSource.validateConfiguration(masterSource); + if (masterSourceResponse != null) { + break; + } + } + if (masterSourceResponse == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Source configuration `" + masterSource + "` not handled"); + } else { + subsystems.add(masterSourceResponse); + if (!masterSourceResponse.isValid()) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Source configuration `" + masterSource + "` invalid"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Master Source configuration `" + masterSource + "` valid"); + valid = true; + } + } + } + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, subsystems); + } + private String getMasterPassword(Map config) throws SecDispatcherException { String masterSource = config.get(CONF_MASTER_SOURCE); if (masterSource == null) { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java index 289a189..431a204 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java @@ -21,9 +21,12 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.List; +import java.util.Map; import java.util.Optional; import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -58,4 +61,26 @@ protected String doHandle(String transformed) throws SecDispatcherException { } return value; } + + @Override + protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) { + String value = System.getenv(transformed); + if (value == null) { + return new SecDispatcher.ValidationResponse( + getClass().getSimpleName(), + true, + Map.of( + SecDispatcher.ValidationResponse.Level.WARNING, + List.of("Configured environment variable not exist")), + List.of()); + } else { + return new SecDispatcher.ValidationResponse( + getClass().getSimpleName(), + true, + Map.of( + SecDispatcher.ValidationResponse.Level.INFO, + List.of("Configured environment variable exist")), + List.of()); + } + } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java index 541a142..9a0b486 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java @@ -29,12 +29,17 @@ import java.net.UnixDomainSocketAddress; import java.nio.channels.Channels; import java.nio.channels.SocketChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HexFormat; +import java.util.List; import java.util.Optional; import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -83,6 +88,39 @@ protected String doHandle(String transformed) throws SecDispatcherException { } } + @Override + protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) { + HashMap> report = new HashMap<>(); + boolean valid = false; + + String extra = ""; + if (transformed.contains("?")) { + extra = transformed.substring(transformed.indexOf("?")); + transformed = transformed.substring(0, transformed.indexOf("?")); + } + Path socketLocation = Paths.get(transformed); + if (!socketLocation.isAbsolute()) { + socketLocation = Paths.get(System.getProperty("user.home")) + .resolve(socketLocation) + .toAbsolutePath(); + } + if (Files.exists(socketLocation)) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Unix domain socket for GPG Agent does not exist. Maybe you need to start gpg-agent?"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Unix domain socket for GPG Agent exist"); + valid = true; + } + boolean interactive = !extra.contains("non-interactive"); + if (!interactive) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.WARNING, k -> new ArrayList<>()) + .add( + "Non-interactive flag found, gpg-agent will not ask for passphrase, it can use only cached ones"); + } + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of()); + } + private String load(Path socketPath, boolean interactive) throws IOException { try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) { sock.connect(UnixDomainSocketAddress.of(socketPath)); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java index 9bd13a5..56be402 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import static java.util.Objects.requireNonNull; @@ -47,4 +48,13 @@ public String handle(String masterSource) throws SecDispatcherException { } protected abstract String doHandle(String transformed) throws SecDispatcherException; + + public SecDispatcher.ValidationResponse validateConfiguration(String masterSource) { + if (matcher.test(masterSource)) { + return doValidateConfiguration(transformer.apply(masterSource)); + } + return null; + } + + protected abstract SecDispatcher.ValidationResponse doValidateConfiguration(String transformed); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java index e2e59d3..b0f6c0e 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PinEntryMasterSource.java @@ -22,12 +22,19 @@ import javax.inject.Singleton; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Optional; import org.codehaus.plexus.components.secdispatcher.MasterSource; import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.PinEntry; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -77,4 +84,26 @@ public String doHandle(String s) throws SecDispatcherException { throw new SecDispatcherException("Could not collect the password", e); } } + + @Override + protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) { + HashMap> report = new HashMap<>(); + boolean valid = false; + + Path pinentry = Paths.get(transformed); + if (!Files.exists(pinentry)) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Configured pinentry command not found"); + } else { + if (!Files.isExecutable(pinentry)) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Configured pinentry command is not executable"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Configured pinentry command exists and is executable"); + valid = true; + } + } + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of()); + } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java index 622dccc..9644bb4 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterSource.java @@ -21,9 +21,12 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.List; +import java.util.Map; import java.util.Optional; import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; /** @@ -58,4 +61,26 @@ protected String doHandle(String transformed) throws SecDispatcherException { } return value; } + + @Override + protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) { + String value = System.getProperty(transformed); + if (value == null) { + return new SecDispatcher.ValidationResponse( + getClass().getSimpleName(), + true, + Map.of( + SecDispatcher.ValidationResponse.Level.WARNING, + List.of("Configured Java System Property not exist")), + List.of()); + } else { + return new SecDispatcher.ValidationResponse( + getClass().getSimpleName(), + true, + Map.of( + SecDispatcher.ValidationResponse.Level.INFO, + List.of("Configured Java System Property exist")), + List.of()); + } + } } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index d135f4e..bd86f0f 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -43,6 +43,7 @@ private void saveSec(String dispatcher, Map config) throws Excep SettingsSecurity sec = new SettingsSecurity(); sec.setModelEncoding(StandardCharsets.UTF_8.name()); sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); + sec.setDefaultDispatcher(dispatcher); Config conf = new Config(); conf.setName(dispatcher); for (Map.Entry entry : config.entrySet()) { @@ -76,6 +77,35 @@ void masterWithSystemPropertyRoundTrip() throws Exception { roundtrip(); } + @Test + void validate() throws Exception { + saveSec("master", Map.of("source", "system-property:masterPassword", "cipher", AESGCMNoPadding.CIPHER_ALG)); + SecDispatcher secDispatcher = construct(); + SecDispatcher.ValidationResponse response = secDispatcher.validateConfiguration(); + assertTrue(response.isValid()); + // secDispatcher + assertTrue(response.getReport().size() == 1); + assertTrue(response.getSubsystems().size() == 1); + // master dispatcher + assertTrue(response.getSubsystems().get(0).getReport().size() == 1); + assertTrue(response.getSubsystems().get(0).getSubsystems().size() == 1); + // master source + assertTrue(response.getSubsystems() + .get(0) + .getSubsystems() + .get(0) + .getReport() + .size() + == 1); + assertTrue(response.getSubsystems() + .get(0) + .getSubsystems() + .get(0) + .getSubsystems() + .size() + == 0); + } + protected void roundtrip() throws Exception { DefaultSecDispatcher sd = construct(); @@ -83,7 +113,6 @@ protected void roundtrip() throws Exception { String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); // example: // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} - System.out.println(encrypted); assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); assertTrue(encrypted.contains("n=master")); assertTrue(encrypted.contains("c=" + AESGCMNoPadding.CIPHER_ALG)); From 98231b38a2c71f3adf2a1d1da25f3d6e9711e07b Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 11:49:37 +0200 Subject: [PATCH 19/27] Dispatcher cares only about its own required attributes, SecDispatcher handles the 'n' and 'v'. --- .../secdispatcher/internal/DefaultSecDispatcher.java | 9 ++++----- .../internal/dispatchers/MasterDispatcher.java | 3 --- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index ef87081..1514866 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; import java.util.stream.Collectors; @@ -120,11 +119,11 @@ public String encrypt(String str, Map attr) throws SecDispatcher Dispatcher dispatcher = dispatchers.get(name); if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name)); - if (!Objects.equals(payload.getAttributes().get(DISPATCHER_NAME_ATTR), name)) { - throw new SecDispatcherException("Dispatcher " + name + " bug: mismatched name attribute"); - } + HashMap resultAttributes = new HashMap<>(payload.getAttributes()); + resultAttributes.put(SecDispatcher.DISPATCHER_NAME_ATTR, name); + resultAttributes.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion()); String res = ATTR_START - + payload.getAttributes().entrySet().stream() + + resultAttributes.entrySet().stream() .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining(",")) + ATTR_STOP; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index bc13fd2..eab1cf7 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -31,7 +31,6 @@ import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; -import org.codehaus.plexus.components.secdispatcher.internal.SecUtil; /** * This dispatcher is logically equivalent (but much more secure) that Maven3 "master password" encryption. @@ -106,8 +105,6 @@ public EncryptPayload encrypt(String str, Map attributes, Map attr = new HashMap<>(attributes); - attr.put(SecDispatcher.DISPATCHER_NAME_ATTR, NAME); - attr.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion()); attr.put(MASTER_CIPHER_ATTR, masterCipher); return new EncryptPayload(attr, encrypted); } catch (PlexusCipherException e) { From 945d2e660631e3526726a739f0c36c27211c49c7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 12:38:38 +0200 Subject: [PATCH 20/27] Report is fallback possible --- .../internal/DefaultSecDispatcher.java | 21 +++++++++++ .../dispatchers/LegacyDispatcher.java | 37 ++++++++++++++----- .../internal/DefaultSecDispatcherTest.java | 15 +++++--- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 1514866..e9d873c 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -212,6 +212,27 @@ public ValidationResponse validateConfiguration() { valid = true; report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) .add("Default dispatcher " + defaultDispatcher + " configuration is valid"); + + Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME); + if (legacy == null) { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher not present in system"); + } else { + // legacy is just "informational" does not affect overall status; merely allows fallback + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher present in system"); + ValidationResponse legacyResponse = + legacy.validateConfiguration(prepareDispatcherConfig(LegacyDispatcher.NAME)); + subsystems.add(legacyResponse); + if (!legacyResponse.isValid()) { + report.computeIfAbsent(ValidationResponse.Level.WARNING, k -> new ArrayList<>()) + .add( + "Legacy dispatcher not operational; transparent fallback not possible"); + } else { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher is operational; transparent fallback possible"); + } + } } } } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java index 58b3382..2122e5e 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -31,8 +31,10 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Base64; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -94,7 +96,11 @@ public EncryptPayload encrypt(String str, Map attributes, Map attributes, Map config) throws SecDispatcherException { try { - return legacyCipher.decrypt64(str, getMasterPassword()); + String masterPassword = getMasterPassword(); + if (masterPassword == null) { + throw new SecDispatcherException("Master password could not be obtained"); + } + return legacyCipher.decrypt64(str, masterPassword); } catch (PlexusCipherException e) { throw new SecDispatcherException("Decrypt failed", e); } @@ -102,17 +108,30 @@ public String decrypt(String str, Map attributes, Map config) { - return new SecDispatcher.ValidationResponse( - getClass().getSimpleName(), - false, - Map.of( - SecDispatcher.ValidationResponse.Level.ERROR, - List.of("This dispatcher cannot and must not be directly used via configuration")), - List.of()); + HashMap> report = new HashMap<>(); + boolean valid = false; + try { + String mp = getMasterPassword(); + if (mp == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Password not found"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Master Password found and decrypted"); + valid = true; + } + } catch (PlexusCipherException e) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Password could not be decrypted"); + } + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of()); } private String getMasterPassword() throws SecDispatcherException { String encryptedMasterPassword = getMasterMasterPasswordFromSettingsSecurityXml(); + if (encryptedMasterPassword == null) { + return null; + } return legacyCipher.decrypt64(plexusCipher.unDecorate(encryptedMasterPassword), MASTER_MASTER_PASSWORD); } @@ -133,7 +152,7 @@ private String getMasterMasterPasswordFromSettingsSecurityXml() { // just ignore whatever it is } } - throw new SecDispatcherException("Could not locate legacy master password: " + xml); + return null; } private static final class LegacyCipher { diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index bd86f0f..c09fe63 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -23,6 +23,7 @@ import org.codehaus.plexus.components.cipher.internal.AESGCMNoPadding; import org.codehaus.plexus.components.cipher.internal.DefaultPlexusCipher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; @@ -84,11 +85,11 @@ void validate() throws Exception { SecDispatcher.ValidationResponse response = secDispatcher.validateConfiguration(); assertTrue(response.isValid()); // secDispatcher - assertTrue(response.getReport().size() == 1); - assertTrue(response.getSubsystems().size() == 1); + assertEquals(1, response.getReport().size()); + assertEquals(2, response.getSubsystems().size()); // master dispatcher - assertTrue(response.getSubsystems().get(0).getReport().size() == 1); - assertTrue(response.getSubsystems().get(0).getSubsystems().size() == 1); + assertEquals(1, response.getSubsystems().get(0).getReport().size()); + assertEquals(1, response.getSubsystems().get(0).getSubsystems().size()); // master source assertTrue(response.getSubsystems() .get(0) @@ -109,7 +110,7 @@ void validate() throws Exception { protected void roundtrip() throws Exception { DefaultSecDispatcher sd = construct(); - assertEquals(1, sd.availableDispatchers().size()); + assertEquals(2, sd.availableDispatchers().size()); String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); // example: // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} @@ -136,7 +137,9 @@ protected DefaultSecDispatcher construct() { SystemPropertyMasterSource.NAME, new SystemPropertyMasterSource(), GpgAgentMasterSource.NAME, - new GpgAgentMasterSource()))), + new GpgAgentMasterSource())), + "legacy", + new LegacyDispatcher(dpc)), CONFIG_PATH); } } From c32d6186def7c05a66b1904a5150322779beae88 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 12:47:50 +0200 Subject: [PATCH 21/27] Improve legacy report --- .../internal/DefaultSecDispatcher.java | 2 +- .../dispatchers/LegacyDispatcher.java | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index e9d873c..12e7ead 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -213,12 +213,12 @@ public ValidationResponse validateConfiguration() { report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) .add("Default dispatcher " + defaultDispatcher + " configuration is valid"); + // below is legacy check, that does not affect validity of config, is merely informational Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME); if (legacy == null) { report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) .add("Legacy dispatcher not present in system"); } else { - // legacy is just "informational" does not affect overall status; merely allows fallback report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) .add("Legacy dispatcher present in system"); ValidationResponse legacyResponse = diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java index 2122e5e..9e8e476 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -111,18 +111,27 @@ public SecDispatcher.ValidationResponse validateConfiguration(Map> report = new HashMap<>(); boolean valid = false; try { - String mp = getMasterPassword(); - if (mp == null) { + String mpe = getMasterMasterPasswordFromSettingsSecurityXml(); + if (mpe == null) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Password not found"); + .add("Legacy configuration not found or does not contain encrypted master password"); } else { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Master Password found and decrypted"); - valid = true; + .add("Legacy configuration found and contain encrypted master password"); + + String mp = getMasterPassword(); + if (mp == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Master Password not found"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Master Password successfully decrypted"); + valid = true; + } } } catch (PlexusCipherException e) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Password could not be decrypted"); + .add("Master Password decryption failed"); } return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of()); } From ac0d93ce0fb00d4cfb33c9bea652f8529238ffde Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 12:55:05 +0200 Subject: [PATCH 22/27] Fix validation on CI --- .../secdispatcher/internal/DefaultSecDispatcherTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index c09fe63..4db6dd8 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -81,6 +81,8 @@ void masterWithSystemPropertyRoundTrip() throws Exception { @Test void validate() throws Exception { saveSec("master", Map.of("source", "system-property:masterPassword", "cipher", AESGCMNoPadding.CIPHER_ALG)); + System.setProperty("settings.security", "src/test/legacy/legacy-settings-security-1.xml"); + SecDispatcher secDispatcher = construct(); SecDispatcher.ValidationResponse response = secDispatcher.validateConfiguration(); assertTrue(response.isValid()); From 67f78ab1797962506383d70a5a577285890fbccd Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 13:22:49 +0200 Subject: [PATCH 23/27] Tidy up messages --- .../internal/DefaultSecDispatcher.java | 8 ++++---- .../internal/dispatchers/LegacyDispatcher.java | 10 +++++----- .../internal/dispatchers/MasterDispatcher.java | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 12e7ead..22e6f3d 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -196,22 +196,22 @@ public ValidationResponse validateConfiguration() { .add("No default dispatcher set in configuration"); } else { report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Default dispatcher set to " + defaultDispatcher); + .add("Default dispatcher configured"); Dispatcher dispatcher = dispatchers.get(defaultDispatcher); if (dispatcher == null) { report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Default dispatcher " + defaultDispatcher + " not present in system"); + .add("Configured default dispatcher not present in system"); } else { ValidationResponse dispatcherResponse = dispatcher.validateConfiguration(prepareDispatcherConfig(defaultDispatcher)); subsystems.add(dispatcherResponse); if (!dispatcherResponse.isValid()) { report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Default dispatcher " + defaultDispatcher + " configuration is invalid"); + .add("Configured default dispatcher configuration is invalid"); } else { valid = true; report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Default dispatcher " + defaultDispatcher + " configuration is valid"); + .add("Configured default dispatcher configuration is valid"); // below is legacy check, that does not affect validity of config, is merely informational Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java index 9e8e476..74daabe 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java @@ -114,24 +114,24 @@ public SecDispatcher.ValidationResponse validateConfiguration(Map new ArrayList<>()) - .add("Legacy configuration not found or does not contain encrypted master password"); + .add("Legacy configuration not found or does not contains encrypted master password"); } else { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Legacy configuration found and contain encrypted master password"); + .add("Legacy configuration found with encrypted master password"); String mp = getMasterPassword(); if (mp == null) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Password not found"); + .add("Legacy master password not found"); } else { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Master Password successfully decrypted"); + .add("Legacy master password successfully decrypted"); valid = true; } } } catch (PlexusCipherException e) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Password decryption failed"); + .add("Legacy master password decryption failed"); } return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of()); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index eab1cf7..31ba402 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -131,20 +131,20 @@ public SecDispatcher.ValidationResponse validateConfiguration(Map new ArrayList<>()) - .add("Master Cipher configuration missing"); + .add("Cipher configuration missing"); } else { if (!cipher.availableCiphers().contains(masterCipher)) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Cipher " + masterCipher + " not supported"); + .add("Configured Cipher not supported"); } else { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Master Cipher " + masterCipher + " supported"); + .add("Configured Cipher supported"); } } String masterSource = config.get(CONF_MASTER_SOURCE); if (masterSource == null) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Source configuration missing"); + .add("Source configuration missing"); } else { SecDispatcher.ValidationResponse masterSourceResponse = null; for (MasterSource masterPasswordSource : masterSources.values()) { @@ -155,15 +155,15 @@ public SecDispatcher.ValidationResponse validateConfiguration(Map new ArrayList<>()) - .add("Master Source configuration `" + masterSource + "` not handled"); + .add("Configured Source configuration not handled"); } else { subsystems.add(masterSourceResponse); if (!masterSourceResponse.isValid()) { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Master Source configuration `" + masterSource + "` invalid"); + .add("Configured Source configuration invalid"); } else { report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Master Source configuration `" + masterSource + "` valid"); + .add("Configured Source configuration valid"); valid = true; } } From 5c1e252890454494f874afde06c729116cd13f40 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 13:45:34 +0200 Subject: [PATCH 24/27] Move out legacy, is independent --- .../internal/DefaultSecDispatcher.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 22e6f3d..140cba6 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -212,35 +212,35 @@ public ValidationResponse validateConfiguration() { valid = true; report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) .add("Configured default dispatcher configuration is valid"); - - // below is legacy check, that does not affect validity of config, is merely informational - Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME); - if (legacy == null) { - report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Legacy dispatcher not present in system"); - } else { - report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Legacy dispatcher present in system"); - ValidationResponse legacyResponse = - legacy.validateConfiguration(prepareDispatcherConfig(LegacyDispatcher.NAME)); - subsystems.add(legacyResponse); - if (!legacyResponse.isValid()) { - report.computeIfAbsent(ValidationResponse.Level.WARNING, k -> new ArrayList<>()) - .add( - "Legacy dispatcher not operational; transparent fallback not possible"); - } else { - report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Legacy dispatcher is operational; transparent fallback possible"); - } - } } } } } + + // below is legacy check, that does not affect validity of config, is merely informational + Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME); + if (legacy == null) { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher not present in system"); + } else { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher present in system"); + ValidationResponse legacyResponse = + legacy.validateConfiguration(prepareDispatcherConfig(LegacyDispatcher.NAME)); + subsystems.add(legacyResponse); + if (!legacyResponse.isValid()) { + report.computeIfAbsent(ValidationResponse.Level.WARNING, k -> new ArrayList<>()) + .add("Legacy dispatcher not operational; transparent fallback not possible"); + } else { + report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Legacy dispatcher is operational; transparent fallback possible"); + } + } } catch (IOException e) { report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>()) .add(e.getMessage()); } + return new ValidationResponse(getClass().getSimpleName(), valid, report, subsystems); } From 69e3c314bc637b07f0e2ab8c5b4572f9d9c4c1da Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 14 Oct 2024 11:31:10 +0200 Subject: [PATCH 25/27] Do not use single char attributes Space is not a problem, these passwords are in Maven settings only, so "space saver short attributes" are meaningless. --- .../plexus/components/secdispatcher/SecDispatcher.java | 4 ++-- .../internal/dispatchers/MasterDispatcher.java | 5 ++--- .../secdispatcher/internal/DefaultSecDispatcherTest.java | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index b231989..ecc0452 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -31,12 +31,12 @@ public interface SecDispatcher { * * @see #availableDispatchers() */ - String DISPATCHER_NAME_ATTR = "n"; + String DISPATCHER_NAME_ATTR = "name"; /** * Attribute for version that dispatcher should use. */ - String DISPATCHER_VERSION_ATTR = "v"; + String DISPATCHER_VERSION_ATTR = "version"; /** * Returns the set of available dispatcher metadata, never {@code null}. diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index 31ba402..6c3e2be 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -40,9 +40,9 @@ public class MasterDispatcher implements Dispatcher, DispatcherMeta { public static final String NAME = "master"; - private static final String MASTER_CIPHER_ATTR = "c"; private static final String CONF_MASTER_CIPHER = "cipher"; private static final String CONF_MASTER_SOURCE = "source"; + private static final String MASTER_CIPHER_ATTR = CONF_MASTER_CIPHER; private final PlexusCipher cipher; protected final Map masterSources; @@ -76,8 +76,7 @@ public Collection fields() { Field.Builder b = Field.builder(e.getKey()).description(m.description()); if (m.configTemplate().isPresent()) { - b = b.defaultValue( - m.configTemplate().get()); + b.defaultValue(m.configTemplate().get()); } return b.build(); } else { diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 4db6dd8..d1e947b 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -117,9 +117,9 @@ protected void roundtrip() throws Exception { // example: // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} assertTrue(encrypted.startsWith("{") && encrypted.endsWith("}")); - assertTrue(encrypted.contains("n=master")); - assertTrue(encrypted.contains("c=" + AESGCMNoPadding.CIPHER_ALG)); - assertTrue(encrypted.contains("v=test")); + assertTrue(encrypted.contains("name=master")); + assertTrue(encrypted.contains("cipher=" + AESGCMNoPadding.CIPHER_ALG)); + assertTrue(encrypted.contains("version=test")); assertTrue(encrypted.contains("a=b")); String pass = sd.decrypt(encrypted); assertEquals("supersecret", pass); From 1ae037a40b762dec8e0a2cf3955d33d69bd921b8 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 14 Oct 2024 12:28:29 +0200 Subject: [PATCH 26/27] Tidy up --- .../internal/DefaultSecDispatcher.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 140cba6..37f4dbd 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -108,16 +108,19 @@ public String encrypt(String str, Map attr) throws SecDispatcher attr = new HashMap<>(attr); } if (attr.get(DISPATCHER_NAME_ATTR) == null) { - attr.put( - DISPATCHER_NAME_ATTR, - requireNonNull( - requireNonNull(SecUtil.read(configurationFile), "no configuration") - .getDefaultDispatcher(), - "no default dispatcher set in configuration")); + SettingsSecurity conf = readConfiguration(false); + if (conf == null) { + throw new SecDispatcherException("No configuration found"); + } + String defaultDispatcher = conf.getDefaultDispatcher(); + if (defaultDispatcher == null) { + throw new SecDispatcherException("No defaultDispatcher set in configuration"); + } + attr.put(DISPATCHER_NAME_ATTR, defaultDispatcher); } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); - if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); + if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name); Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name)); HashMap resultAttributes = new HashMap<>(payload.getAttributes()); resultAttributes.put(SecDispatcher.DISPATCHER_NAME_ATTR, name); @@ -143,12 +146,9 @@ public String decrypt(String str) throws SecDispatcherException, IOException { if (isLegacyPassword(str)) { attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME); } - if (attr.get(DISPATCHER_NAME_ATTR) == null) { - throw new SecDispatcherException("Invalid encrypted string; mandatory attributes missing"); - } String name = attr.get(DISPATCHER_NAME_ATTR); Dispatcher dispatcher = dispatchers.get(name); - if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name); + if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name); return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(name)); } catch (PlexusCipherException e) { throw new SecDispatcherException(e.getMessage(), e); @@ -285,7 +285,6 @@ protected Map stripAttributes(String str) { } protected boolean isEncryptedString(String str) { - if (str == null) return false; return cipher.isEncryptedString(str); } } From 56bcc6c77911fc862de3668dfe604eb328b6a7a1 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 14 Oct 2024 12:32:44 +0200 Subject: [PATCH 27/27] Sprinkle some javadoc --- .../plexus/components/secdispatcher/SecDispatcher.java | 5 +++-- .../secdispatcher/internal/dispatchers/MasterDispatcher.java | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index ecc0452..fde9fa9 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -27,14 +27,15 @@ */ public interface SecDispatcher { /** - * Attribute that selects a dispatcher. + * Attribute that selects a dispatcher. If not present in {@link #encrypt(String, Map)} attributes, the + * configured "default dispatcher" is used. * * @see #availableDispatchers() */ String DISPATCHER_NAME_ATTR = "name"; /** - * Attribute for version that dispatcher should use. + * Attribute for version, added by SecDispatcher for possible upgrade path. */ String DISPATCHER_VERSION_ATTR = "version"; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java index 6c3e2be..ce1fa46 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java @@ -42,6 +42,9 @@ public class MasterDispatcher implements Dispatcher, DispatcherMeta { private static final String CONF_MASTER_CIPHER = "cipher"; private static final String CONF_MASTER_SOURCE = "source"; + /** + * Attribute holding the Cipher name used to encrypt the password. + */ private static final String MASTER_CIPHER_ATTR = CONF_MASTER_CIPHER; private final PlexusCipher cipher;