This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-gpg-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new 036dfe0  [MGPG-105] [MGPG-108] Make plugin backward compat and update 
site and doco (#77)
036dfe0 is described below

commit 036dfe005c64a3f881f493806e854ead033c563d
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Fri Mar 8 09:02:38 2024 +0100

    [MGPG-105] [MGPG-108] Make plugin backward compat and update site and doco 
(#77)
    
    Document the latest changes. But also implement Java changes related to 
agent usage and back compat.
    
    Now we distinguish really (and option is un-deprecated):
    * `useAgent`
    * `interactive`
    
    Means to provide secret needed for signing:
    
    |flag|agent pinentry|agent cached|env variable|
    |---|---|---|---|
    |`useAgent && interactive` | :heavy_check_mark: | :heavy_check_mark: | 
:heavy_check_mark: |
    |`useAgent && !interactive` | :x:  | :heavy_check_mark: | 
:heavy_check_mark: |
    |`!useAgent && interactive` | :x:  | :x: | :heavy_check_mark: |
    |`!useAgent && !interactive` | :x:  | :x: | :heavy_check_mark: |
    
    Finally, `!bestPractices` provides existing "pass in passphrase as 
property" mode as well.
    
    As first really means "can we talk to the agent" and second means "can 
agent pop up pinentry dialogue" for both
    signers. In fact, this was the case already in `GpgSigner`, but `BcSigner` 
conflated the two. As it turns out, `gpg-agent`
    also supports "non interactive" password caching that now both signers make 
use of.
    
    ---
    
    https://issues.apache.org/jira/browse/MGPG-108
    https://issues.apache.org/jira/browse/MGPG-105
---
 .../apache/maven/plugins/gpg/AbstractGpgMojo.java  | 102 ++++++++++-----------
 .../org/apache/maven/plugins/gpg/BcSigner.java     |  79 +++++++++-------
 .../maven/plugins/gpg/SignAndDeployFileMojo.java   |   2 +-
 src/site/apt/examples/deploy-signed-artifacts.apt  |  55 ++++++++++-
 src/site/apt/index.apt.vm                          |  19 ++++
 src/site/apt/usage.apt.vm                          |  54 +++++++++--
 .../org/apache/maven/plugins/gpg/BcSignerTest.java |  15 +++
 7 files changed, 226 insertions(+), 100 deletions(-)

diff --git a/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java 
b/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java
index 91009f8..52c61b2 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java
@@ -51,11 +51,13 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String agentSocketLocations;
 
     /**
-     * BC Signer only: The path of the exported key in TSK format, and 
probably passphrase protected. If relative,
-     * the file is resolved against Maven local repository root.
+     * BC Signer only: The path of the exported key in
+     * <a 
href="https://openpgp.dev/book/private_keys.html#transferable-secret-key-format";>TSK
 format</a>,
+     * and may be passphrase protected. If relative, the file is resolved 
against user home directory.
      * <p>
-     * <em>Note: it is not recommended to have sensitive files on disk or SCM 
repository, this mode is more to be used
-     * in local environment (workstations) or for testing purposes.</em>
+     * <em>Note: it is not recommended to have sensitive files checked into 
SCM repository. Key file should reside on
+     * developer workstation, outside of SCM tracked repository. For CI-like 
use cases you should set the
+     * key material as env variable instead.</em>
      *
      * @since 3.2.0
      */
@@ -71,9 +73,11 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String keyFingerprint;
 
     /**
-     * BC Signer only: The env variable name where the GnuPG key is set. The 
default value is {@code MAVEN_GPG_KEY}.
+     * BC Signer only: The env variable name where the GnuPG key is set.
      * To use BC Signer you must provide GnuPG key, as it does not use GnuPG 
home directory to extract/find the
-     * key (while it does use GnuPG Agent to ask for password in interactive 
mode).
+     * key (while it does use GnuPG Agent to ask for password in interactive 
mode). The key should be in
+     * <a 
href="https://openpgp.dev/book/private_keys.html#transferable-secret-key-format";>TSK
 format</a> and may
+     * be passphrase protected.
      *
      * @since 3.2.0
      */
@@ -82,7 +86,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
 
     /**
      * BC Signer only: The env variable name where the GnuPG key fingerprint 
is set, if the provided keyring contains
-     * multiple keys. The default value is {@code MAVEN_GPG_KEY_FINGERPRINT}.
+     * multiple keys.
      *
      * @since 3.2.0
      */
@@ -90,8 +94,8 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String keyFingerprintEnvName;
 
     /**
-     * The env variable name where the GnuPG passphrase is set. The default 
value is {@code MAVEN_GPG_PASSPHRASE}.
-     * This is the recommended way to pass passphrase for signing in batch 
mode execution of Maven.
+     * The env variable name where the GnuPG passphrase is set. This is the 
recommended way to pass passphrase
+     * for signing in batch mode execution of Maven.
      *
      * @since 3.2.0
      */
@@ -109,23 +113,25 @@ public abstract class AbstractGpgMojo extends 
AbstractMojo {
 
     /**
      * The passphrase to use when signing. If not given, look up the value 
under Maven
-     * settings using server id at 'passphraseServerKey' configuration. <em>Do 
not use this parameter, if set, the
-     * plugin will fail. Passphrase should be provided only via gpg-agent 
(interactive) or via env variable
-     * (non-interactive).</em>
+     * settings using server id at 'passphraseServerKey' configuration. <em>Do 
not use this parameter, it leaks
+     * sensitive data. Passphrase should be provided only via gpg-agent or via 
env variable.
+     * If parameter {@link #bestPractices} set to {@code true}, plugin fails 
when this parameter is configured.</em>
      *
-     * @deprecated Do not use this configuration, plugin will fail if set.
+     * @deprecated Do not use this configuration, it may leak sensitive 
information. Rely on gpg-agent or env
+     * variables instead.
      **/
     @Deprecated
     @Parameter(property = "gpg.passphrase")
     private String passphrase;
 
     /**
-     * Server id to lookup the passphrase under Maven settings. <em>Do not use 
this parameter, if set, the
-     * plugin will fail. Passphrase should be provided only via gpg-agent 
(interactive) or via env variable
-     * (non-interactive).</em>
+     * Server id to lookup the passphrase under Maven settings. <em>Do not use 
this parameter, it leaks
+     * sensitive data. Passphrase should be provided only via gpg-agent or via 
env variable.
+     * If parameter {@link #bestPractices} set to {@code true}, plugin fails 
when this parameter is configured.</em>
      *
      * @since 1.6
-     * @deprecated Do not use this configuration, plugin will fail if set.
+     * @deprecated Do not use this configuration, it may leak sensitive 
information. Rely on gpg-agent or env
+     * variables instead.
      **/
     @Deprecated
     @Parameter(property = "gpg.passphraseServerId")
@@ -138,23 +144,22 @@ public abstract class AbstractGpgMojo extends 
AbstractMojo {
     private String keyname;
 
     /**
-     * GPG Signer only: Passes <code>--use-agent</code> or 
<code>--no-use-agent</code> to gpg. If using an agent, the
-     * passphrase is optional as the agent will provide it. For gpg2, specify 
true as --no-use-agent was removed in
-     * gpg2 and doesn't ask for a passphrase anymore. Deprecated, and better 
to rely on session "interactive" setting
-     * (if interactive, agent will be used, otherwise not).
-     *
-     * @deprecated
+     * All signers: whether gpg-agent is allowed to be used or not. If 
enabled, passphrase is optional, as agent may
+     * provide it. Have to be noted, that in "batch" mode, gpg-agent will be 
prevented to pop up pinentry
+     * dialogue, hence best is to "prime" the agent caches beforehand.
+     * <p>
+     * GPG Signer: Passes <code>--use-agent</code> or 
<code>--no-use-agent</code> option to gpg if it is version 2.1
+     * or older. Otherwise, will use an agent. In non-interactive mode gpg 
options are appended with
+     * <code>--pinentry-mode error</code>, preventing gpg agent to pop up 
pinentry dialogue. Agent will be able to
+     * hand over only cached passwords.
+     * <p>
+     * BC Signer: Allows signer to communicate with gpg agent. In 
non-interactive mode it uses
+     * <code>--no-ask</code> option with the <code>GET_PASSPHRASE</code> 
function. Agent will be able to hand over
+     * only cached passwords.
      */
-    @Deprecated
     @Parameter(property = "gpg.useagent", defaultValue = "true")
     private boolean useAgent;
 
-    /**
-     * Detect is session interactive or not.
-     */
-    @Parameter(defaultValue = "${settings.interactiveMode}", readonly = true)
-    private boolean interactive;
-
     /**
      * GPG Signer only: The path to the GnuPG executable to use for artifact 
signing. Defaults to either "gpg" or
      * "gpg.exe" depending on the operating system.
@@ -182,7 +187,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
      * ‘private-keys-v1.d’ directory below the GnuPG home directory.
      *
      * @since 1.2
-     * @deprecated
+     * @deprecated Obsolete option since GnuPG 2.1 version.
      */
     @Deprecated
     @Parameter(property = "gpg.secretKeyring")
@@ -198,7 +203,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
      * ‘pubring.kbx’ file below the GnuPG home directory.
      *
      * @since 1.2
-     * @deprecated
+     * @deprecated Obsolete option since GnuPG 2.1 version.
      */
     @Deprecated
     @Parameter(property = "gpg.publicKeyring")
@@ -224,7 +229,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private boolean skip;
 
     /**
-     * Sets the arguments to be passed to gpg. Example:
+     * GPG Signer only: Sets the arguments to be passed to gpg. Example:
      *
      * <pre>
      * &lt;gpgArguments&gt;
@@ -256,24 +261,24 @@ public abstract class AbstractGpgMojo extends 
AbstractMojo {
     // === Deprecated stuff
 
     /**
-     * Switch to lax plugin enforcement of "best practices". If set to {@code 
false}, plugin will retain all the
-     * backward compatibility regarding getting secrets (but will warn). By 
default, plugin enforces "best practices"
-     * and in such cases plugin fails.
+     * Switch to improve plugin enforcement of "best practices". If set to 
{@code false}, plugin retains all the
+     * backward compatibility regarding getting secrets (but will warn). If 
set to {@code true}, plugin will fail
+     * if any "bad practices" regarding sensitive data handling are detected. 
By default, plugin remains backward
+     * compatible (this flag is {@code false}). Somewhere in the future, when 
this parameter enabling transitioning
+     * from older plugin versions is removed, the logic using this flag will 
be modified like it is set to {@code true}.
+     * It is warmly advised to configure this parameter to {@code true} and 
migrate project and user environment
+     * regarding how sensitive information is stored.
      *
      * @since 3.2.0
-     * @deprecated
      */
-    @Deprecated
-    @Parameter(property = "gpg.bestPractices", defaultValue = "true")
+    @Parameter(property = "gpg.bestPractices", defaultValue = "false")
     private boolean bestPractices;
 
     /**
      * Current user system settings for use in Maven.
      *
      * @since 1.6
-     * @deprecated
      */
-    @Deprecated
     @Parameter(defaultValue = "${settings}", readonly = true, required = true)
     private Settings settings;
 
@@ -281,7 +286,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
      * Maven Security Dispatcher.
      *
      * @since 1.6
-     * @deprecated
+     * @deprecated Provides quasi-encryption, should be avoided.
      */
     @Deprecated
     @Component
@@ -310,7 +315,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
         getLog().warn("W A R N I N G");
         getLog().warn("");
         getLog().warn("Do not store passphrase in any file (disk or SCM 
repository),");
-        getLog().warn("instead rely on GnuPG agent in interactive sessions, or 
provide passphrase in ");
+        getLog().warn("instead rely on GnuPG agent or provide passphrase in ");
         getLog().warn(passphraseEnvName + " environment variable for batch 
mode.");
         getLog().warn("");
         getLog().warn("Sensitive content loaded from " + source);
@@ -334,7 +339,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
         }
 
         signer.setLog(getLog());
-        signer.setInteractive(interactive);
+        signer.setInteractive(settings.isInteractiveMode());
         signer.setKeyName(keyname);
         signer.setUseAgent(useAgent);
         signer.setHomeDirectory(homedir);
@@ -371,13 +376,6 @@ public abstract class AbstractGpgMojo extends AbstractMojo 
{
                 }
             }
         }
-
-        // gpg signer: always failed if no passphrase and no agent and not 
interactive: retain this behavior
-        // bc signer: it is optimistic, will fail during prepare() only IF key 
is passphrase protected
-        if (GpgSigner.NAME.equals(this.signer) && null == passphrase && 
!useAgent && !interactive) {
-            throw new MojoFailureException("Cannot obtain passphrase in batch 
mode");
-        }
-
         signer.prepare();
 
         return signer;
@@ -419,7 +417,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
                 pass = prj2.getProperties().getProperty(GPG_PASSPHRASE);
             }
         }
-        if (project != null) {
+        if (project != null && pass != null) {
             
findReactorProject(project).getProperties().setProperty(GPG_PASSPHRASE, pass);
         }
         return pass;
diff --git a/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java 
b/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java
index 3ab8c22..6a0fc2a 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java
@@ -34,6 +34,7 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -71,11 +72,6 @@ public class BcSigner extends AbstractGpgSigner {
     public static final String NAME = "bc";
 
     public interface Loader {
-        /**
-         * Returns {@code true} if this loader requires user interactivity.
-         */
-        boolean isInteractive();
-
         /**
          * Returns the key ring material, or {@code null}.
          */
@@ -93,17 +89,12 @@ public class BcSigner extends AbstractGpgSigner {
         /**
          * Returns the key password, or {@code null}.
          */
-        default char[] loadPassword(RepositorySystemSession session, long 
keyId) throws IOException {
+        default char[] loadPassword(RepositorySystemSession session, byte[] 
fingerprint) throws IOException {
             return null;
         }
     }
 
     public final class GpgEnvLoader implements Loader {
-        @Override
-        public boolean isInteractive() {
-            return false;
-        }
-
         @Override
         public byte[] loadKeyRingMaterial(RepositorySystemSession session) {
             String keyMaterial = (String) 
session.getConfigProperties().get("env." + keyEnvName);
@@ -134,16 +125,13 @@ public class BcSigner extends AbstractGpgSigner {
          */
         private static final long MAX_SIZE = 5 * 1024 + 1L;
 
-        @Override
-        public boolean isInteractive() {
-            return false;
-        }
-
         @Override
         public byte[] loadKeyRingMaterial(RepositorySystemSession session) 
throws IOException {
             Path keyPath = Paths.get(keyFilePath);
             if (!keyPath.isAbsolute()) {
-                keyPath = 
session.getLocalRepository().getBasedir().toPath().resolve(keyPath);
+                keyPath = Paths.get(System.getProperty("user.home"))
+                        .resolve(keyPath)
+                        .toAbsolutePath();
             }
             if (Files.isRegularFile(keyPath)) {
                 if (Files.size(keyPath) < MAX_SIZE) {
@@ -171,19 +159,25 @@ public class BcSigner extends AbstractGpgSigner {
 
     public final class GpgAgentPasswordLoader implements Loader {
         @Override
-        public boolean isInteractive() {
-            return true;
-        }
-
-        @Override
-        public char[] loadPassword(RepositorySystemSession session, long 
keyId) throws IOException {
+        public char[] loadPassword(RepositorySystemSession session, byte[] 
fingerprint) throws IOException {
+            if (!useAgent) {
+                return null;
+            }
             List<String> socketLocations = 
Arrays.stream(agentSocketLocations.split(","))
                     .filter(s -> s != null && !s.isEmpty())
                     .collect(Collectors.toList());
             for (String socketLocation : socketLocations) {
                 try {
-                    return load(keyId, 
Paths.get(System.getProperty("user.home"), socketLocation))
-                            .toCharArray();
+                    Path socketLocationPath = Paths.get(socketLocation);
+                    if (!socketLocationPath.isAbsolute()) {
+                        socketLocationPath = 
Paths.get(System.getProperty("user.home"))
+                                .resolve(socketLocationPath)
+                                .toAbsolutePath();
+                    }
+                    String pw = load(fingerprint, socketLocationPath);
+                    if (pw != null) {
+                        return pw.toCharArray();
+                    }
                 } catch (SocketException e) {
                     // try next location
                 }
@@ -191,7 +185,7 @@ public class BcSigner extends AbstractGpgSigner {
             return null;
         }
 
-        private String load(long keyId, Path socketPath) throws IOException {
+        private String load(byte[] fingerprint, Path socketPath) throws 
IOException {
             try (AFUNIXSocket sock = AFUNIXSocket.newInstance()) {
                 sock.connect(AFUNIXSocketAddress.of(socketPath));
                 try (BufferedReader in = new BufferedReader(new 
InputStreamReader(sock.getInputStream()));
@@ -210,23 +204,43 @@ public class BcSigner extends AbstractGpgSigner {
                         os.flush();
                         expectOK(in);
                     }
-                    String hexKeyId = Long.toHexString(keyId & 0xFFFFFFFFL);
+                    String hexKeyFingerprint = Hex.toHexString(fingerprint);
+                    String displayFingerprint = 
hexKeyFingerprint.toUpperCase(Locale.ROOT);
                     // 
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 " + hexKeyId + " " + 
"Passphrase+incorrect"
-                            + " GnuPG+Key+Passphrase 
Enter+passphrase+for+encrypted+GnuPG+key+" + hexKeyId
+                    String instruction = "GET_PASSPHRASE "
+                            + (!isInteractive ? "--no-ask " : "")
+                            + hexKeyFingerprint
+                            + " "
+                            + "X "
+                            + "GnuPG+Passphrase "
+                            + 
"Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key+with+fingerprint:+"
+                            + displayFingerprint
                             + "+to+use+it+for+signing+Maven+Artifacts\n";
                     os.write((instruction).getBytes());
                     os.flush();
-                    return new String(Hex.decode(expectOK(in).trim()));
+                    String pw = mayExpectOK(in);
+                    if (pw != null) {
+                        return new String(Hex.decode(pw.trim()));
+                    }
+                    return null;
                 }
             }
         }
 
-        private String expectOK(BufferedReader in) throws IOException {
+        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 response.substring(Math.min(response.length(), 3));
         }
     }
@@ -265,7 +279,6 @@ public class BcSigner extends AbstractGpgSigner {
     public void prepare() throws MojoFailureException {
         try {
             List<Loader> loaders = Stream.of(new GpgEnvLoader(), new 
GpgConfLoader(), new GpgAgentPasswordLoader())
-                    .filter(l -> this.isInteractive || !l.isInteractive())
                     .collect(Collectors.toList());
 
             byte[] keyRingMaterial = null;
@@ -327,7 +340,7 @@ public class BcSigner extends AbstractGpgSigner {
             final boolean keyPassNeeded = 
secretKey.getKeyEncryptionAlgorithm() != SymmetricKeyAlgorithmTags.NULL;
             if (keyPassNeeded && keyPassword == null) {
                 for (Loader loader : loaders) {
-                    keyPassword = loader.loadPassword(session, 
secretKey.getKeyID());
+                    keyPassword = loader.loadPassword(session, 
secretKey.getFingerprint());
                     if (keyPassword != null) {
                         break;
                     }
diff --git 
a/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java 
b/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java
index 52f9828..2d2c699 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java
@@ -135,7 +135,7 @@ public class SignAndDeployFileMojo extends AbstractGpgMojo {
 
     /**
      * URL where the artifact will be deployed. <br/>
-     * ie ( file:///C:/m2-repo or scp://host.com/path/to/repo )
+     * ie ( file:///C:/m2-repo or https://host.com/path/to/repo )
      */
     @Parameter(property = "url", required = true)
     private String url;
diff --git a/src/site/apt/examples/deploy-signed-artifacts.apt 
b/src/site/apt/examples/deploy-signed-artifacts.apt
index 79c49ae..cfbc789 100644
--- a/src/site/apt/examples/deploy-signed-artifacts.apt
+++ b/src/site/apt/examples/deploy-signed-artifacts.apt
@@ -32,16 +32,61 @@ Deploy Signed Artifacts
 mvn deploy
 +----------+
 
- If you have configured this plugin according to the instructions in the
- {{{../usage.html}usage page}}, you just need to specify the passphrase for
- your private key on the command line like this:
+  If you have configured this plugin according to the instructions in the
+  {{{../usage.html}usage page}}, nothing changes for interactive sessions:
 
 +----------+
-mvn deploy -Dgpg.passphrase=thephrase
+mvn deploy
++----------+
+
+  And the gpg-agent will prompt you for passphrase.
+
+  General remark regarding environment variables: Examples below are NOT
+  instructions how to invoke Maven, as if you'd follow these examples
+  literally, it would defy the goal of not leaking cleartext passphrases,
+  as these would end up in terminal history! You should set these environment
+  variables on your own discretion in some secure manner.
+
+  If you use "batch" build (or build is invoked by Maven Release Plugin),
+  then gpg-agent will be unable to ask interactively for password. In such
+  cases you want to "prime" the agent with passwords first. See 
{{{../usage.html}usage page}}
+  for details how to "prime" gpg-agent.
+
+  In "agent-less" (CI like usage) mode one can supply passphrase via 
environment
+  variable only.
+
++----------+
+MAVEN_GPG_PASSPHRASE=thephrase mvn --batch-mode deploy
++----------+
+
+* Sign using BC Signer
+
+  By default the plugin uses the "gpg" Signer (that relies on GnuPG tool 
installed
+  on host OS). The "bc" Signer on the other hand implements signing in pure
+  Java using Bouncy Castle libraries.
+
+  The "bc" signer, unlike "gpg", does not and cannot make use of <<<~/.gnupg>>>
+  directory in user home, and have to have configured both, the key used
+  to sign and the passphrase (if key is passphrase protected). The key is 
expected to be in
+  TSK format (see 
{{{https://openpgp.dev/book/private_keys.html#transferable-secret-key-format}"Transferable
 Secret Keys"}} format).
+
++----------+
+mvn deploy -Dgpg.signer=bc -Dgpg.keyFilePath=path/to/key
++----------+
+
+  In interactive sessions, similarly as with "gpg" Signer, gpg-agent will be 
used to
+  ask for password. In batch sessions, you can use environment variables to 
achieve
+  similar thing:
+
++----------+
+MAVEN_GPG_PASSPHRASE=thephrase mvn deploy -Dgpg.signer=bc 
-Dgpg.keyFilePath=path/to/key
 +----------+
 
-  If you don't specify a passphrase, it will prompt for one.
+  Ultimately, you can place both, they key and passphrase into environment 
variables:
 
++----------+
+MAVEN_GPG_KEY=thekeymaterial MAVEN_GPG_PASSPHRASE=thephrase mvn deploy 
-Dgpg.signer=bc
++----------+
 
 * Install/Deploy without configuring the plugin in the POM
 
diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm
index a0e4840..4a6f049 100644
--- a/src/site/apt/index.apt.vm
+++ b/src/site/apt/index.apt.vm
@@ -40,6 +40,25 @@ ${project.name}
   General instructions on how to use the GPG Plugin can be found on the 
{{{./usage.html}usage page}}. Some more
   specific use cases are described in the examples given below.
 
+  Since 3.2.0, plugin can enforce "best practices", and will fail the build if 
any violation are detected.
+  In short, intent is to stop users putting secrets (plaintext or 
quasi-encrypted) in their Maven configuration
+  files (settings.xml, POMs) or use secrets in a way they leave trace (like in 
terminal history). In this mode,
+  plugin leaves two options to obtain passphrase: use of gpg-agent (with 
pinentry in interactive sessions, or pre-seeded
+  "cached" passwords in non-interactive mode), and use of environment 
variables in batch/non-interactive/no-agent
+  sessions. To enable "best practices" configure the plugin accordingly (see 
goals, look for <<<bestPractices>>>
+  configuration). By default, the plugin does not enforce these, but does emit 
warnings.
+
+  To "prime" the GnuPG agent, you have several options: either just "sign" 
something beforehand (usable on
+  workstations) like <<<echo "test" | gpg --clearsign>>>, or use
+  
{{{https://www.gnupg.org/documentation/manuals/gnupg/Invoking-gpg_002dpreset_002dpassphrase.html}gpg-preset-passphrase}}
+  GnuPG command, that will "cache" the password in gpg-agent for given login 
session, cache content is lost between
+  reboots. Note: this tool, while is part of GnuPG suite, may not be on path. 
Check your OS documentation for it.
+  For example, on modern versions of Fedora this tool is not on path, but is 
located in <<</usr/libexec/gpg-preset-passphrase>>>.
+
+  <<Note:>> The GpgSigner, that uses GnuPG tool installed and configured on 
the host OS, while it does contain support
+  for older GnuPGP versions, is tested (locally by developers and on CI 
systems) only by using
+  {{{https://www.gnupg.org/download/index.html}latest "stable" GnuPG version}} 
(scroll to bottom of page for EOL information).
+
   In case you still have questions regarding the plugin's usage, please have a 
look at the {{{./faq.html}FAQ}} and feel
   free to contact the {{{./mailing-lists.html}user mailing list}}. The posts 
to the mailing list are archived and could
   already contain the answer to your question as part of an older thread. 
Hence, it is also worth browsing/searching
diff --git a/src/site/apt/usage.apt.vm b/src/site/apt/usage.apt.vm
index 4e27740..c69776a 100644
--- a/src/site/apt/usage.apt.vm
+++ b/src/site/apt/usage.apt.vm
@@ -29,7 +29,7 @@ Usage
 
   Signs all of a project's attached artifacts with GnuPG.
 
-  You need to have previously configured the default key.
+  You need to have previously configured the default key using GnuPG.
 
   <<<gpg>>> also needs to be on the search path.
 
@@ -60,27 +60,63 @@ Usage
 </project>
 +----------+
 
-  Then you specify the passphrase on the command line. Like this:
+  Ideally, if invoked on workstation, you should rely on gpg-agent to
+  collect passphrase from, as in that way no secrets will enter terminal 
history nor
+  any file on disk. In agent-less (batch) sessions, typically on CI, you 
should provide
+  passphrases via environment variable (see goals).
+
+  <<Note:>> When using the GPG Plugin in combination with the Maven Release 
Plugin,
+  on a developer Workstation, you should rely on gpg-agent, but have it 
"primed",
+  as Release plugin invokes build in batch mode, that will prevent agent to 
present
+  the "pinentry pop up". If fully unattended release is being done, for example
+  on a CI system, then with <<<useAgent>>> set to <<<false>>> one can pass
+  the passphrase via environment variable.
+
+  <<To prime gpg-agent caches>>, one can perform simple "sign" operation on
+  workstation like this <<<echo "test" | gpg --clearsign>>> or can use
+  gpg command 
{{{https://www.gnupg.org/documentation/manuals/gnupg/Invoking-gpg_002dpreset_002dpassphrase.html}gpg-preset-passphrase}}.
+
+  General remark regarding environment variables: Examples below are NOT
+  instructions how to invoke Maven, as if you'd follow these examples
+  literally, it would defy the goal of not leaking cleartext passphrases,
+  as these would end up in terminal history! You should set these environment
+  variables on your own discretion in some secure manner.
 
 +----------+
-mvn verify -Dgpg.passphrase=thephrase
+MAVEN_GPG_PASSPHRASE=thephrase mvn release:perform
 +----------+
 
-  If you don't specify a passphrase, it will prompt for one.
+  One "real life" example, on Un*x systems could be this:
+
++----------+
+read -s -p "Enter your GnuPG key passphrase: " MAVEN_GPG_PASSPHRASE; mvn 
release:perform
++----------+
 
-  <<Note:>> When using the GPG Plugin in combination with the Maven Release 
Plugin, you might need to specify the passphrase
-  like this:
+  Finally, the passphrase can be given on the command line as well, but this 
is not recommended,
+  and plugin will emit warnings. This mode of invocation is highly discouraged,
+  as passphrase in cleartext is recorded into Terminal history.
 
 +----------+
-mvn release:perform -Darguments=-Dgpg.passphrase=thephrase
+mvn verify -Dgpg.passphrase=thephrase
 +----------+
 
-  This accounts for the fact, that the Release Plugin forks Maven and system 
properties of the current Maven session are
-  unfortunately not automatically propagated to the forked Maven session (see 
also {{{https://issues.apache.org/jira/browse/MGPG-9}MGPG-9}}).
+* Security considerations
 
+  In the future, plugin will operate in <<<bestPractices>>> mode enabled, and 
will fail
+  the build if any violation of those is detected. The goal of this change was 
to protect
+  plugin users from possible "leaks" of sensitive information (like passphrase 
is).
+  Sensitive information like passphrases should never be stored on disks 
(plaintext
+  or quasi-encrypted), nor should be used in way they may "leak" into other 
files
+  (for example bash terminal history).
+
+  Hence, examples below will work by emit warnings. In the future, once "best 
practices"
+  become enforced, these examples will not work anymore.
 
 * Configure passphrase in settings.xml
 
+  <<NOTE:>> These techniques below are highly discouraged. Ideally sensitive 
information
+  should enter via gpg-agent or via environment variables.
+
   Instead of specifying the passphrase on the command line, you can place it 
in your local <<<settings.xml>>>
   either in clear or {{{/guides/mini/guide-encryption.html}encrypted}} text.
 
diff --git a/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java 
b/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java
index 67d360d..49bfb97 100644
--- a/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java
+++ b/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java
@@ -54,6 +54,7 @@ class BcSignerTest {
         DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession();
         session.setLocalRepositoryManager(new 
SimpleLocalRepositoryManagerFactory(new DefaultLocalPathComposer())
                 .newInstance(session, new 
LocalRepository("target/local-repo")));
+        // first: interactive session: it will pop up a pinentry dialogue, 
enter "TEST"
         BcSigner signer = new BcSigner(
                 session,
                 "unimportant",
@@ -61,6 +62,20 @@ class BcSignerTest {
                 ".gnupg/S.gpg-agent",
                 new 
File("src/test/resources/signing-key.asc").getAbsolutePath(),
                 null);
+        signer.setUseAgent(true);
+        signer.setInteractive(true);
+        signer.prepare();
+
+        // second: non-interactive: will use agent but no 2nd popup will appear
+        signer = new BcSigner(
+                session,
+                "unimportant",
+                "unimportant",
+                ".gnupg/S.gpg-agent",
+                new 
File("src/test/resources/signing-key.asc").getAbsolutePath(),
+                null);
+        signer.setUseAgent(true);
+        signer.setInteractive(false);
         signer.prepare();
     }
 }

Reply via email to