- 1.0.0
+ 3.0.0
src/main/mdo/settings-security.mdo
@@ -86,12 +85,22 @@
standard
java
- xpp3-reader
- xpp3-writer
+ xsd
+ stax-reader
+ stax-writer
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ masterPw
+
+
+
diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java
new file mode 100644
index 0000000..4e88a46
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java
@@ -0,0 +1,68 @@
+/*
+ * 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.Map;
+import java.util.Set;
+
+/**
+ * This component decrypts a string, passed to it
+ *
+ * @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".
+ */
+ 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.
+ *
+ * @see #availableDispatchers()
+ */
+ String DISPATCHER_NAME_ATTR = "name";
+
+ /**
+ * Returns the set of available dispatcher names, never {@code null}.
+ */
+ Set availableDispatchers();
+
+ /**
+ * encrypt given plaintext string
+ *
+ * @param str the plaintext to encrypt
+ * @param attr the attributes, may be {@code null}
+ * @return encrypted string
+ * @throws SecDispatcherException in case of problem
+ */
+ String encrypt(String str, Map attr) throws SecDispatcherException;
+
+ /**
+ * decrypt given encrypted string
+ *
+ * @param str the encrypted string
+ * @return plaintext string
+ * @throws SecDispatcherException in case of problem
+ */
+ String decrypt(String str) throws SecDispatcherException;
+}
diff --git a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcherException.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcherException.java
similarity index 82%
rename from src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcherException.java
rename to src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcherException.java
index c7b8ad1..a83c70e 100644
--- a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcherException.java
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcherException.java
@@ -11,17 +11,13 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
-package org.sonatype.plexus.components.sec.dispatcher;
+package org.codehaus.plexus.components.secdispatcher;
-public class SecDispatcherException extends Exception {
+public class SecDispatcherException extends RuntimeException {
public SecDispatcherException(String message) {
super(message);
}
- public SecDispatcherException(Throwable cause) {
- super(cause);
- }
-
public SecDispatcherException(String message, Throwable cause) {
super(message, cause);
}
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
new file mode 100644
index 0000000..25dd6fc
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java
@@ -0,0 +1,200 @@
+/*
+ * 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;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.HashMap;
+import java.util.Map;
+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.SecDispatcher;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * @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 masterPasswordSources;
+ protected final Map dispatchers;
+ protected final String configurationFile;
+
+ @Inject
+ public DefaultSecDispatcher(
+ PlexusCipher cipher,
+ Map masterPasswordSources,
+ Map dispatchers,
+ @Named("${configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String configurationFile) {
+ this.cipher = requireNonNull(cipher);
+ this.masterPasswordSources = requireNonNull(masterPasswordSources);
+ this.dispatchers = requireNonNull(dispatchers);
+ this.configurationFile = requireNonNull(configurationFile);
+ }
+
+ @Override
+ public Set availableDispatchers() {
+ return Set.copyOf(dispatchers.keySet());
+ }
+
+ @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);
+ } 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));
+ }
+ return cipher.decorate(res);
+ } catch (PlexusCipherException e) {
+ throw new SecDispatcherException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String decrypt(String str) throws SecDispatcherException {
+ if (!isEncryptedString(str)) return str;
+ 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));
+ }
+ } catch (PlexusCipherException e) {
+ throw new SecDispatcherException(e.getMessage(), e);
+ }
+ }
+
+ 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);
+ if (conf != null) {
+ dispatcherConf.putAll(conf);
+ }
+ return dispatcherConf;
+ }
+
+ private String strip(String str) {
+ int start = str.indexOf(ATTR_START);
+ int stop = str.indexOf(ATTR_STOP);
+ if (start != -1 && stop != -1 && stop > start) {
+ return str.substring(stop + 1);
+ }
+ return str;
+ }
+
+ private Map stripAttributes(String str) {
+ int start = str.indexOf(ATTR_START);
+ int stop = str.indexOf(ATTR_STOP);
+ if (start != -1 && stop != -1 && stop > start) {
+ if (start != 0) throw new SecDispatcherException("Attributes can be prefix only");
+ 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);
+ }
+ return res;
+ }
+ return null;
+ }
+
+ private boolean isEncryptedString(String str) {
+ if (str == null) return false;
+ return cipher.isEncryptedString(str);
+ }
+
+ private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException {
+ String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile());
+ location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location;
+ SettingsSecurity sec = SecUtil.read(location, true);
+ if (mandatory && sec == null)
+ throw new SecDispatcherException("Please check that configuration file on path " + location + " exists");
+
+ return sec;
+ }
+
+ private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException {
+ if (sec == null && !mandatory) {
+ return null;
+ }
+ requireNonNull(sec, "configuration is null");
+ String masterSource = requireNonNull(sec.getMasterSource(), "masterSource is null");
+ for (MasterPasswordSource masterPasswordSource : masterPasswordSources.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
new file mode 100644
index 0000000..de030a8
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/Dispatcher.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Dispatcher.
+ *
+ * @author Oleg Gusakov
+ * @version $Id$
+ *
+ */
+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";
+
+ /**
+ * encrypt given plaintext string
+ *
+ * @param str string to encrypt
+ * @param attributes attributes, never {@code null}
+ * @param config configuration from settings-security.xml, never {@code null}
+ * @return encrypted string
+ */
+ String encrypt(String str, Map attributes, Map config)
+ throws SecDispatcherException;
+
+ /**
+ * decrypt given encrypted string
+ *
+ * @param str string to decrypt
+ * @param attributes attributes, never {@code null}
+ * @param config configuration from settings-security.xml, never {@code null}
+ * @return decrypted string
+ */
+ String decrypt(String str, Map attributes, Map config)
+ throws SecDispatcherException;
+}
diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java
new file mode 100644
index 0000000..e5704fd
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/MasterPasswordSource.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Source of master password.
+ */
+public interface MasterPasswordSource {
+ /**
+ * Handles the URI 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}
+ * - 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
+ */
+ String handle(String masterSource) throws SecDispatcherException;
+}
diff --git a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java
similarity index 59%
rename from src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecUtil.java
rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java
index ccd32da..ebf400e 100644
--- a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecUtil.java
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java
@@ -11,21 +11,27 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
-package org.sonatype.plexus.components.sec.dispatcher;
+package org.codehaus.plexus.components.secdispatcher.internal;
+
+import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.sonatype.plexus.components.sec.dispatcher.model.Config;
-import org.sonatype.plexus.components.sec.dispatcher.model.ConfigProperty;
-import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
-import org.sonatype.plexus.components.sec.dispatcher.model.io.xpp3.SecurityConfigurationXpp3Reader;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+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.SecurityConfigurationStaxReader;
+
+import static java.util.Objects.requireNonNull;
/**
*
@@ -43,68 +49,57 @@ public class SecUtil {
public static SettingsSecurity read(String location, boolean cycle) throws SecDispatcherException {
if (location == null) throw new SecDispatcherException("location to read from is null");
-
SettingsSecurity sec;
-
try {
try (InputStream in = toStream(location)) {
- sec = new SecurityConfigurationXpp3Reader().read(in);
+ sec = new SecurityConfigurationStaxReader().read(in);
}
-
if (cycle && sec.getRelocation() != null) return read(sec.getRelocation(), true);
-
return sec;
- } catch (Exception e) {
- throw new SecDispatcherException(e);
+ } catch (NoSuchFileException e) {
+ return null;
+ } catch (IOException e) {
+ throw new SecDispatcherException("IO Problem", e);
+ } catch (XMLStreamException e) {
+ throw new SecDispatcherException("Parsing error", e);
}
}
- // ---------------------------------------------------------------------------------------------------------------
- private static InputStream toStream(String resource) throws IOException {
- if (resource == null) return null;
+ private static InputStream toStream(String resource) throws IOException {
+ requireNonNull(resource, "resource is null");
int ind = resource.indexOf(PROTOCOL_DELIM);
-
if (ind > 1) {
String protocol = resource.substring(0, ind);
resource = resource.substring(ind + PROTOCOL_DELIM_LEN);
-
for (String p : URL_PROTOCOLS) {
if (protocol.regionMatches(true, 0, p, 0, p.length())) {
return new URL(p + PROTOCOL_DELIM + resource).openStream();
}
}
}
-
return Files.newInputStream(Paths.get(resource));
}
- // ---------------------------------------------------------------------------------------------------------------
- public static Map getConfig(SettingsSecurity sec, String name) {
- if (name == null) return null;
- List cl = sec.getConfigurations();
-
- if (cl == null || cl.isEmpty()) return null;
-
- for (Config cf : cl) {
- if (!name.equals(cf.getName())) {
- continue;
- }
-
- List pl = cf.getProperties();
-
- if (pl == null || pl.isEmpty()) {
- return null;
- }
-
- Map res = new HashMap<>(pl.size());
-
- for (ConfigProperty p : pl) {
- res.put(p.getName(), p.getValue());
+ public static Map getConfig(SettingsSecurity sec, String name) {
+ if (sec != null && name != null) {
+ List cl = sec.getConfigurations();
+ if (!cl.isEmpty()) {
+ for (Config cf : cl) {
+ if (!name.equals(cf.getName())) {
+ continue;
+ }
+ List pl = cf.getProperties();
+ if (pl.isEmpty()) {
+ break;
+ }
+ Map res = new HashMap<>(pl.size());
+ for (ConfigProperty p : pl) {
+ res.put(p.getName(), p.getValue());
+ }
+ return res;
+ }
}
-
- return res;
}
-
return null;
}
}
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/EnvMasterPasswordSource.java
new file mode 100644
index 0000000..ede5ce1
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterPasswordSource.java
@@ -0,0 +1,46 @@
+/*
+ * 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 org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Password source that uses env.
+ */
+@Singleton
+@Named(EnvMasterPasswordSource.NAME)
+public final class EnvMasterPasswordSource extends PrefixMasterPasswordSourceSupport {
+ public static final String NAME = "env";
+
+ public EnvMasterPasswordSource() {
+ super(NAME + ":");
+ }
+
+ @Override
+ protected String doHandle(String transformed) throws SecDispatcherException {
+ String value = System.getenv(transformed);
+ if (value == null) {
+ throw new SecDispatcherException("Environment variable '" + transformed + "' not found");
+ }
+ return value;
+ }
+}
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/GpgAgentMasterPasswordSource.java
new file mode 100644
index 0000000..afe2ffa
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterPasswordSource.java
@@ -0,0 +1,124 @@
+/*
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.StandardProtocolFamily;
+import java.net.UnixDomainSocketAddress;
+import java.nio.channels.Channels;
+import java.nio.channels.SocketChannel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HexFormat;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Password source that uses GnuPG Agent.
+ */
+@Singleton
+@Named(GpgAgentMasterPasswordSource.NAME)
+public final class GpgAgentMasterPasswordSource extends PrefixMasterPasswordSourceSupport {
+ public static final String NAME = "gpg-agent";
+
+ public GpgAgentMasterPasswordSource() {
+ super(NAME + ":");
+ }
+
+ @Override
+ protected String doHandle(String transformed) throws SecDispatcherException {
+ String extra = "";
+ if (transformed.contains("?")) {
+ extra = transformed.substring(transformed.indexOf("?"));
+ transformed = transformed.substring(0, transformed.indexOf("?"));
+ }
+ String socketLocation = transformed;
+ boolean interactive = !extra.contains("non-interactive");
+ try {
+ Path socketLocationPath = Paths.get(socketLocation);
+ if (!socketLocationPath.isAbsolute()) {
+ socketLocationPath = Paths.get(System.getProperty("user.home"))
+ .resolve(socketLocationPath)
+ .toAbsolutePath();
+ }
+ return load(socketLocationPath, interactive);
+ } catch (IOException e) {
+ throw new SecDispatcherException(e.getMessage(), e);
+ }
+ }
+
+ private String load(Path socketPath, boolean interactive) throws IOException {
+ try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) {
+ sock.connect(UnixDomainSocketAddress.of(socketPath));
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(Channels.newInputStream(sock)));
+ OutputStream os = Channels.newOutputStream(sock)) {
+
+ expectOK(in);
+ String display = System.getenv("DISPLAY");
+ if (display != null) {
+ os.write(("OPTION display=" + display + "\n").getBytes());
+ os.flush();
+ expectOK(in);
+ }
+ String term = System.getenv("TERM");
+ if (term != null) {
+ os.write(("OPTION ttytype=" + term + "\n").getBytes());
+ os.flush();
+ expectOK(in);
+ }
+ // https://unix.stackexchange.com/questions/71135/how-can-i-find-out-what-keys-gpg-agent-has-cached-like-how-ssh-add-l-shows-yo
+ String instruction = "GET_PASSPHRASE "
+ + (!interactive ? "--no-ask " : "")
+ + "plexus:secDispatcherMasterPassword"
+ + " "
+ + "X "
+ + "Maven+Master+Password "
+ + "Please+enter+your+Maven+master+password"
+ + "+to+use+it+for+decrypting+Maven+Settings\n";
+ os.write((instruction).getBytes());
+ os.flush();
+ return mayExpectOK(in);
+ }
+ }
+ }
+
+ private void expectOK(BufferedReader in) throws IOException {
+ String response = in.readLine();
+ if (!response.startsWith("OK")) {
+ throw new IOException("Expected OK but got this instead: " + response);
+ }
+ }
+
+ private String mayExpectOK(BufferedReader in) throws IOException {
+ String response = in.readLine();
+ if (response.startsWith("ERR")) {
+ return null;
+ } else if (!response.startsWith("OK")) {
+ throw new IOException("Expected OK/ERR but got this instead: " + response);
+ }
+ return new String(HexFormat.of()
+ .parseHex(response.substring(Math.min(response.length(), 3)).trim()));
+ }
+}
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/MasterPasswordSourceSupport.java
new file mode 100644
index 0000000..7b19876
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterPasswordSourceSupport.java
@@ -0,0 +1,50 @@
+/*
+ * 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 java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Master password source support class.
+ */
+public abstract class MasterPasswordSourceSupport implements MasterPasswordSource {
+ private final Predicate matcher;
+ private final Function transformer;
+
+ public MasterPasswordSourceSupport(Predicate matcher, Function transformer) {
+ this.matcher = requireNonNull(matcher);
+ this.transformer = requireNonNull(transformer);
+ }
+
+ @Override
+ public String handle(String masterSource) throws SecDispatcherException {
+ if (matcher.test(masterSource)) {
+ return doHandle(transformer.apply(masterSource));
+ }
+ return null;
+ }
+
+ protected abstract String doHandle(String transformed) throws SecDispatcherException;
+}
diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MatchingMasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MatchingMasterPasswordSource.java
new file mode 100644
index 0000000..438f49c
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MatchingMasterPasswordSource.java
@@ -0,0 +1,39 @@
+/*
+ * 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.function.Predicate;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource;
+
+import static java.util.Objects.requireNonNull;
+
+public class MatchingMasterPasswordSource implements MasterPasswordSource {
+ private final Predicate matcher;
+ private final MasterPasswordSource masterPasswordSource;
+
+ public MatchingMasterPasswordSource(Predicate matcher, MasterPasswordSource masterPasswordSource) {
+ this.matcher = requireNonNull(matcher);
+ this.masterPasswordSource = requireNonNull(masterPasswordSource);
+ }
+
+ @Override
+ public String handle(String masterSource) throws SecDispatcherException {
+ if (matcher.test(masterSource)) {
+ return masterPasswordSource.handle(masterSource);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MemoizingMasterPasswordSource.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MemoizingMasterPasswordSource.java
new file mode 100644
index 0000000..5460062
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MemoizingMasterPasswordSource.java
@@ -0,0 +1,36 @@
+/*
+ * 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.concurrent.ConcurrentHashMap;
+
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource;
+
+import static java.util.Objects.requireNonNull;
+
+public class MemoizingMasterPasswordSource implements MasterPasswordSource {
+ private final MasterPasswordSource masterPasswordSource;
+ private final ConcurrentHashMap memo;
+
+ public MemoizingMasterPasswordSource(MasterPasswordSource masterPasswordSource) {
+ this.masterPasswordSource = requireNonNull(masterPasswordSource);
+ this.memo = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public String handle(String masterSource) throws SecDispatcherException {
+ return memo.computeIfAbsent(masterSource, k -> masterPasswordSource.handle(masterSource));
+ }
+}
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/PrefixMasterPasswordSourceSupport.java
new file mode 100644
index 0000000..3d2d6b3
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/PrefixMasterPasswordSourceSupport.java
@@ -0,0 +1,43 @@
+/*
+ * 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 java.util.function.Function;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Master password source support class for simple "prefix" use case.
+ */
+public abstract class PrefixMasterPasswordSourceSupport extends MasterPasswordSourceSupport {
+ public PrefixMasterPasswordSourceSupport(String prefix) {
+ super(prefixMatcher(prefix), prefixRemover(prefix));
+ }
+
+ private static Predicate prefixMatcher(String prefix) {
+ requireNonNull(prefix, "prefix cannot be null");
+ return s -> s != null && s.startsWith(prefix);
+ }
+
+ private static Function prefixRemover(String prefix) {
+ requireNonNull(prefix, "prefix cannot be null");
+ return s -> s.substring(prefix.length());
+ }
+}
diff --git a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java
similarity index 51%
rename from src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcher.java
rename to src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java
index 1557a69..7ef6d89 100644
--- a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/SecDispatcher.java
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/StaticMasterPasswordSource.java
@@ -11,24 +11,22 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
-package org.sonatype.plexus.components.sec.dispatcher;
+package org.codehaus.plexus.components.secdispatcher.internal.sources;
-/**
- * This component decrypts a string, passed to it
- *
- * @author Oleg Gusakov
- */
-public interface SecDispatcher {
- String[] SYSTEM_PROPERTY_MASTER_PASSWORD = new String[] {"settings.master.password", "settings-master-password"};
+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;
- String[] SYSTEM_PROPERTY_SERVER_PASSWORD = new String[] {"settings.server.password", "settings-server-password"};
+ public StaticMasterPasswordSource(String masterPassword) {
+ this.masterPassword = requireNonNull(masterPassword);
+ }
- /**
- * decrypt given encrypted string
- *
- * @param str
- * @return decrypted string
- * @throws SecDispatcherException
- */
- String decrypt(String str) throws SecDispatcherException;
+ @Override
+ public String handle(String masterSource) throws SecDispatcherException {
+ return masterPassword;
+ }
}
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/SystemPropertyMasterPasswordSource.java
new file mode 100644
index 0000000..58b08b8
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/SystemPropertyMasterPasswordSource.java
@@ -0,0 +1,46 @@
+/*
+ * 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 org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Password source that uses env.
+ */
+@Singleton
+@Named(SystemPropertyMasterPasswordSource.NAME)
+public final class SystemPropertyMasterPasswordSource extends PrefixMasterPasswordSourceSupport {
+ public static final String NAME = "prop";
+
+ public SystemPropertyMasterPasswordSource() {
+ super(NAME + ":");
+ }
+
+ @Override
+ protected String doHandle(String transformed) throws SecDispatcherException {
+ String value = System.getProperty(transformed);
+ if (value == null) {
+ throw new SecDispatcherException("System property '" + transformed + "' not found");
+ }
+ return value;
+ }
+}
diff --git a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/DefaultSecDispatcher.java b/src/main/java/org/sonatype/plexus/components/sec/dispatcher/DefaultSecDispatcher.java
deleted file mode 100644
index 2421787..0000000
--- a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/DefaultSecDispatcher.java
+++ /dev/null
@@ -1,290 +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.sonatype.plexus.components.sec.dispatcher;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
-import org.sonatype.plexus.components.cipher.PlexusCipher;
-import org.sonatype.plexus.components.cipher.PlexusCipherException;
-import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
-
-/**
- * @author Oleg Gusakov
- */
-@Singleton
-@Named
-public class DefaultSecDispatcher implements SecDispatcher {
- private static final String DEFAULT_CONFIGURATION = "~/.settings-security.xml";
-
- public static final String SYSTEM_PROPERTY_SEC_LOCATION = "settings.security";
-
- public static final String TYPE_ATTR = "type";
-
- public static final char ATTR_START = '[';
-
- public static final char ATTR_STOP = ']';
-
- /**
- * DefaultHandler
- */
- protected final PlexusCipher _cipher;
-
- /**
- * All available dispatchers
- */
- protected final Map _decryptors;
-
- /**
- * Configuration file
- */
- protected String _configurationFile;
-
- @Inject
- public DefaultSecDispatcher(
- final PlexusCipher _cipher,
- final Map _decryptors,
- @Named("${_configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String _configurationFile) {
- this._cipher = _cipher;
- this._decryptors = _decryptors;
- this._configurationFile = _configurationFile;
- }
-
- /**
- * Ctor to be used in tests and other simplified cases (no decryptors and config).
- */
- public DefaultSecDispatcher(final PlexusCipher _cipher) {
- this(_cipher, new HashMap<>(), DEFAULT_CONFIGURATION);
- }
-
- // ---------------------------------------------------------------
-
- @Override
- public String decrypt(String str) throws SecDispatcherException {
- if (!isEncryptedString(str)) return str;
-
- String bare;
-
- try {
- bare = _cipher.unDecorate(str);
- } catch (PlexusCipherException e1) {
- throw new SecDispatcherException(e1);
- }
-
- try {
- Map attr = stripAttributes(bare);
-
- String res;
-
- SettingsSecurity sec = getSec();
-
- if (attr == null || attr.get("type") == null) {
- String master = getMaster(sec);
-
- res = _cipher.decrypt(bare, master);
- } else {
- String type = attr.get(TYPE_ATTR);
-
- if (_decryptors == null)
- throw new SecDispatcherException(
- "plexus container did not supply any required dispatchers - cannot lookup " + type);
-
- Map conf = SecUtil.getConfig(sec, type);
-
- PasswordDecryptor dispatcher = _decryptors.get(type);
-
- if (dispatcher == null) throw new SecDispatcherException("no dispatcher for hint " + type);
-
- String pass = attr == null ? bare : strip(bare);
-
- return dispatcher.decrypt(pass, attr, conf);
- }
-
- return res;
- } catch (Exception e) {
- throw new SecDispatcherException(e);
- }
- }
-
- private String strip(String str) {
- int pos = str.indexOf(ATTR_STOP);
-
- if (pos == str.length()) return null;
-
- if (pos != -1) return str.substring(pos + 1);
-
- return str;
- }
-
- private Map stripAttributes(String str) {
- int start = str.indexOf(ATTR_START);
- int stop = str.indexOf(ATTR_STOP);
- if (start != -1 && stop != -1 && stop > start) {
- if (stop == start + 1) return null;
-
- String attrs = str.substring(start + 1, stop).trim();
-
- if (attrs.length() < 1) 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) continue;
-
- String key = pair.substring(0, pos).trim();
-
- if (pos == pair.length()) {
- res.put(key, null);
- continue;
- }
-
- String val = pair.substring(pos + 1);
-
- res.put(key, val.trim());
- }
-
- return res;
- }
-
- return null;
- }
-
- // ----------------------------------------------------------------------------
-
- private boolean isEncryptedString(String str) {
- if (str == null) return false;
-
- return _cipher.isEncryptedString(str);
- }
-
- // ----------------------------------------------------------------------------
-
- private SettingsSecurity getSec() throws SecDispatcherException {
- String location = System.getProperty(SYSTEM_PROPERTY_SEC_LOCATION, getConfigurationFile());
- String realLocation =
- location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location;
-
- SettingsSecurity sec = SecUtil.read(realLocation, true);
-
- if (sec == null)
- throw new SecDispatcherException(
- "cannot retrieve master password. Please check that " + realLocation + " exists and has data");
-
- return sec;
- }
-
- // ----------------------------------------------------------------------------
-
- private String getMaster(SettingsSecurity sec) throws SecDispatcherException {
- String master = sec.getMaster();
-
- if (master == null) throw new SecDispatcherException("master password is not set");
-
- try {
- return _cipher.decryptDecorated(master, SYSTEM_PROPERTY_SEC_LOCATION);
- } catch (PlexusCipherException e) {
- throw new SecDispatcherException(e);
- }
- }
- // ---------------------------------------------------------------
- public String getConfigurationFile() {
- return _configurationFile;
- }
-
- public void setConfigurationFile(String file) {
- _configurationFile = file;
- }
-
- // ---------------------------------------------------------------
-
- private static boolean propertyExists(String[] values, String[] av) {
- if (values != null) {
- for (String item : values) {
- String p = System.getProperty(item);
-
- if (p != null) {
- return true;
- }
- }
-
- if (av != null)
- for (String value : values)
- for (String s : av) {
- if (("--" + value).equals(s)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static void usage() {
- System.out.println("usage: java -jar ...jar [-m|-p]\n-m: encrypt master password\n-p: encrypt password");
- }
-
- // ---------------------------------------------------------------
-
- public static void main(String[] args) throws Exception {
- if (args == null || args.length < 1) {
- usage();
- return;
- }
-
- if ("-m".equals(args[0]) || propertyExists(SYSTEM_PROPERTY_MASTER_PASSWORD, args)) show(true);
- else if ("-p".equals(args[0]) || propertyExists(SYSTEM_PROPERTY_SERVER_PASSWORD, args)) show(false);
- else usage();
- }
-
- // ---------------------------------------------------------------
-
- private static void show(boolean showMaster) throws Exception {
- if (showMaster) System.out.print("\nsettings master password\n");
- else System.out.print("\nsettings server password\n");
-
- System.out.print("enter password: ");
-
- BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
-
- String pass = r.readLine();
-
- System.out.println("\n");
-
- DefaultPlexusCipher dc = new DefaultPlexusCipher();
- DefaultSecDispatcher dd = new DefaultSecDispatcher(dc);
-
- if (showMaster)
- System.out.println(dc.encryptAndDecorate(pass, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION));
- else {
- SettingsSecurity sec = dd.getSec();
- System.out.println(dc.encryptAndDecorate(pass, dd.getMaster(sec)));
- }
- }
-}
diff --git a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/PasswordDecryptor.java b/src/main/java/org/sonatype/plexus/components/sec/dispatcher/PasswordDecryptor.java
deleted file mode 100644
index 2d6a434..0000000
--- a/src/main/java/org/sonatype/plexus/components/sec/dispatcher/PasswordDecryptor.java
+++ /dev/null
@@ -1,37 +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.sonatype.plexus.components.sec.dispatcher;
-
-import java.util.Map;
-
-/**
- *
- *
- * @author Oleg Gusakov
- * @version $Id$
- *
- */
-public interface PasswordDecryptor {
- /**
- * decrypt given encrypted string
- *
- * @param str - string to decrypt
- * @param attributes - string attributes
- * @param config - configuration from settings-security.xml, if any
- * @return decrypted string
- *
- * @throws SecDispatcherException
- */
- String decrypt(String str, Map attributes, Map config) throws SecDispatcherException;
-}
diff --git a/src/main/mdo/settings-security.mdo b/src/main/mdo/settings-security.mdo
index 436af7e..3702210 100644
--- a/src/main/mdo/settings-security.mdo
+++ b/src/main/mdo/settings-security.mdo
@@ -13,7 +13,8 @@
*/
-->
-
+
settings-security
SecurityConfiguration
@@ -22,7 +23,7 @@
package
- org.sonatype.plexus.components.sec.dispatcher.model
+ org.codehaus.plexus.components.secdispatcher.model
@@ -30,26 +31,47 @@
SettingsSecurity
- 1.0.0
+ 1.0.0+
master
- 1.0.0
+ 1.0.0/2.1.0
String
encrypted master password
+
+ modelVersion
+ 3.0.0+
+ String
+ The version of the model
+
+
+
+ masterSource
+ 3.0.0+
+ String
+ The URI describing the source of the master password
+
+
+
+ masterCipher
+ 3.0.0+
+ String
+ The Cipher to be used
+
+
relocation
- 1.0.0
+ 1.0.0+
String
reference to the location of the security file
configurations
- 1.0.0
+ 1.0.0+
named configurations
Config
@@ -62,7 +84,7 @@
Config
- 1.0.0
+ 1.0.0+
Named configuration
@@ -70,13 +92,13 @@
name
String
true
- 1.0.0
+ 1.0.0+
name of this configuration
properties
- 1.0.0
+ 1.0.0+
properties
ConfigProperty
@@ -89,7 +111,7 @@
ConfigProperty
- 1.0.0
+ 1.0.0+
generic property - name/value pair
@@ -98,7 +120,7 @@
name
String
true
- 1.0.0
+ 1.0.0+
name of this property
@@ -106,7 +128,7 @@
value
String
true
- 1.0.0
+ 1.0.0+
value of this property
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
new file mode 100644
index 0000000..5ecb58d
--- /dev/null
+++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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;
+
+import java.io.OutputStream;
+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.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.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);
+ sec.setMasterCipher(AESGCMNoPadding.CIPHER_ALG);
+
+ try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) {
+ new SecurityConfigurationStaxWriter().write(fos, sec);
+ }
+ System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml");
+ }
+
+ @BeforeEach
+ public void prepare() throws Exception {
+ saveSec("magic:might");
+ }
+
+ @Test
+ void testEncrypt() throws Exception {
+ DefaultSecDispatcher sd = new DefaultSecDispatcher(
+ new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())),
+ Map.of("static", new StaticMasterPasswordSource(masterPassword)),
+ 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 {
+ DefaultSecDispatcher sd = new DefaultSecDispatcher(
+ new DefaultPlexusCipher(Map.of(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding())),
+ Map.of("static", new StaticMasterPasswordSource(masterPassword)),
+ 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 SystemPropertyMasterPasswordSource(),
+ "env",
+ new EnvMasterPasswordSource(),
+ "gpg",
+ new GpgAgentMasterPasswordSource()),
+ 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 SystemPropertyMasterPasswordSource(),
+ "env",
+ new EnvMasterPasswordSource(),
+ "gpg",
+ new GpgAgentMasterPasswordSource()),
+ 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 SystemPropertyMasterPasswordSource(),
+ "env",
+ new EnvMasterPasswordSource(),
+ "gpg",
+ new GpgAgentMasterPasswordSource()),
+ Map.of(),
+ DefaultSecDispatcher.DEFAULT_CONFIGURATION);
+ String encrypted = sd.encrypt(password, null);
+ String pass = sd.decrypt(encrypted);
+ assertNotNull(pass);
+ assertEquals(password, pass);
+ }
+
+ @Test
+ void testEncryptWithDispatcher() 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 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);
+ }
+}
diff --git a/src/test/java/org/sonatype/plexus/components/sec/dispatcher/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java
similarity index 58%
rename from src/test/java/org/sonatype/plexus/components/sec/dispatcher/SecUtilTest.java
rename to src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java
index be9dc58..8afc4f1 100644
--- a/src/test/java/org/sonatype/plexus/components/sec/dispatcher/SecUtilTest.java
+++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java
@@ -11,18 +11,19 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
-package org.sonatype.plexus.components.sec.dispatcher;
+package org.codehaus.plexus.components.secdispatcher.internal;
-import java.io.FileWriter;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.Map;
+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.Test;
-import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
-import org.sonatype.plexus.components.sec.dispatcher.model.Config;
-import org.sonatype.plexus.components.sec.dispatcher.model.ConfigProperty;
-import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
-import org.sonatype.plexus.components.sec.dispatcher.model.io.xpp3.SecurityConfigurationXpp3Writer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -35,32 +36,15 @@
*
*/
public class SecUtilTest {
- String _pw = "{1wQaa6S/o8MH7FnaTNL53XmhT5O0SEGXQi3gC49o6OY=}";
-
- String _clear = "testtest";
-
- String _encrypted = "{BteqUEnqHecHM7MZfnj9FwLcYbdInWxou1C929Txa0A=}";
-
String _confName = "cname";
-
String _propName = "pname";
-
String _propVal = "pval";
- @BeforeEach
- public void prepare() throws Exception {
- System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, "./target/sec.xml");
-
- // DefaultPlexusCipher c = new DefaultPlexusCipher();
- // System.out.println(_clear+" -> "+c.encrypt( _clear, "testtest" ));
-
+ private void saveSec(String masterSource) throws Exception {
SettingsSecurity sec = new SettingsSecurity();
- sec.setRelocation("./target/sec1.xml");
- new SecurityConfigurationXpp3Writer().write(new FileWriter("./target/sec.xml"), sec);
-
sec.setRelocation(null);
- sec.setMaster(_pw);
+ sec.setMasterSource(masterSource);
ConfigProperty cp = new ConfigProperty();
cp.setName(_propName);
@@ -72,34 +56,30 @@ public void prepare() throws Exception {
sec.addConfiguration(conf);
- new SecurityConfigurationXpp3Writer().write(new FileWriter("./target/sec1.xml"), sec);
+ try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec1.xml"))) {
+ new SecurityConfigurationStaxWriter().write(fos, sec);
+ }
+ }
+
+ @BeforeEach
+ public void prepare() throws Exception {
+ System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml");
+ SettingsSecurity sec = new SettingsSecurity();
+ sec.setRelocation("./target/sec1.xml");
+ try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) {
+ new SecurityConfigurationStaxWriter().write(fos, sec);
+ }
+ saveSec("magic:mighty");
}
@Test
- void testRead() throws Exception {
+ void testReadWithRelocation() throws Exception {
SettingsSecurity sec = SecUtil.read("./target/sec.xml", true);
-
assertNotNull(sec);
-
- assertEquals(_pw, sec.getMaster());
-
+ assertEquals("magic:mighty", sec.getMasterSource());
Map conf = SecUtil.getConfig(sec, _confName);
-
assertNotNull(conf);
-
assertNotNull(conf.get(_propName));
-
assertEquals(_propVal, conf.get(_propName));
}
-
- @Test
- void testDecrypt() throws Exception {
- DefaultSecDispatcher sd = new DefaultSecDispatcher(new DefaultPlexusCipher());
-
- String pass = sd.decrypt(_encrypted);
-
- assertNotNull(pass);
-
- assertEquals(_clear, 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
new file mode 100644
index 0000000..4088212
--- /dev/null
+++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatcher/StaticDispatcher.java
@@ -0,0 +1,43 @@
+/*
+ * 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/resources/test-sec.xml b/src/test/resources/test-sec.xml
deleted file mode 100644
index b452cb4..0000000
--- a/src/test/resources/test-sec.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- {CFxZA99+BAZVLyBgcmvfLZciAWn31QdSjVpSXodH13MAkHoTl8JPLGpt1rFTh07dnKoNiOUh92sash3p0PXbKi2NhY3sxvmVXnlCf+Vdz38uaZBQ7L0ebNt+YhpsyUE33iKqMwZt4oWr1acD3mpIufk2godfNP2nKGO2ufIFfIbqO4mGMWQ5VIQ=}
-
\ No newline at end of file