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 6f50819  [MGPG-106] Introduce new signer: BC (#72)
6f50819 is described below

commit 6f50819233cb368edba8335d7cb82edf2e7f2659
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Mon Mar 4 15:50:36 2024 +0100

    [MGPG-106] Introduce new signer: BC (#72)
    
    This introduces new pure Java signer BC backed one. Currently two signers 
supported: "gpg" (uses external executable, as before), and "bc" (uses pure 
Java Bouncy Castle backed signer).
    
    All the ITs (invoker and surefire) are _reused_ and now run twice, once 
with "gpg" and once with "bc". One IT needed adjustment, as BC does not emit 
error about "pinentry".
    
    ---
    
    https://issues.apache.org/jira/browse/MGPG-106
---
 pgp-keys-map.list                                  |   5 +
 pom.xml                                            | 105 ++++--
 .../sign-release-without-passphrase/verify.groovy  |   6 +-
 .../apache/maven/plugins/gpg/AbstractGpgMojo.java  | 149 +++++++--
 .../maven/plugins/gpg/AbstractGpgSigner.java       |   5 +
 .../org/apache/maven/plugins/gpg/BcSigner.java     | 371 +++++++++++++++++++++
 .../maven/plugins/gpg/GpgSignAttachedMojo.java     |   4 +-
 .../org/apache/maven/plugins/gpg/GpgSigner.java    |   6 +
 .../maven/plugins/gpg/SignAndDeployFileMojo.java   |   3 +
 .../org/apache/maven/plugins/gpg/BcSignerTest.java |  66 ++++
 ...pgSignArtifactIT.java => BcSignArtifactIT.java} |  21 +-
 .../maven/plugins/gpg/it/GpgSignArtifactIT.java    |  17 +-
 .../plugins/gpg/it/GpgSignAttachedMojoIT.java      |  18 +-
 .../apache/maven/plugins/gpg/it/ITSupport.java}    |  27 +-
 .../maven/plugins/gpg/it/InvokerTestUtils.java     |   6 +-
 src/test/resources/signing-key.asc                 |  32 ++
 16 files changed, 725 insertions(+), 116 deletions(-)

diff --git a/pgp-keys-map.list b/pgp-keys-map.list
index f0a0edb..c507a97 100644
--- a/pgp-keys-map.list
+++ b/pgp-keys-map.list
@@ -15,8 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
+com.kohlschutter.junixsocket:junixsocket-common = 
0xB5C082F1158B8C92AE3E5E1C29B8FEA02804261C
+com.kohlschutter.junixsocket:junixsocket-core = 
0xB5C082F1158B8C92AE3E5E1C29B8FEA02804261C
+com.kohlschutter.junixsocket:junixsocket-native-common = 
0xB5C082F1158B8C92AE3E5E1C29B8FEA02804261C
 commons-io:commons-io = 0x2DB4F1EF0FA761ECC4EA935C86FDC7E2A11262CB
 org.apiguardian:apiguardian-api = 0xFF6E2C001948C5F2F38B0CC385911F425EC61B51
+org.bouncycastle:bcpg-jdk18on = 0x7B121B76A7ED6CE6E60AD51784E913A8E3A748C0
+org.bouncycastle:bcprov-jdk18on = 0x7B121B76A7ED6CE6E60AD51784E913A8E3A748C0
 org.junit.jupiter:junit-jupiter-api = 
0xFF6E2C001948C5F2F38B0CC385911F425EC61B51
 org.junit.jupiter:junit-jupiter-params = 
0xFF6E2C001948C5F2F38B0CC385911F425EC61B51
 org.junit.platform:junit-platform-commons = 
0xFF6E2C001948C5F2F38B0CC385911F425EC61B51
diff --git a/pom.xml b/pom.xml
index a0e437d..c547723 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,9 +60,10 @@ under the License.
   </distributionManagement>
 
   <properties>
+    <javaVersion>8</javaVersion>
     <mavenVersion>3.9.6</mavenVersion>
     <resolverVersion>1.9.18</resolverVersion>
-    <javaVersion>8</javaVersion>
+    <bouncycastleVersion>1.77</bouncycastleVersion>
     
<project.build.outputTimestamp>2023-05-03T01:33:44Z</project.build.outputTimestamp>
     <resource.delimiter>@</resource.delimiter>
   </properties>
@@ -120,6 +121,22 @@ under the License.
       <artifactId>plexus-utils</artifactId>
       <version>3.5.1</version>
     </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpg-jdk18on</artifactId>
+      <version>${bouncycastleVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcprov-jdk18on</artifactId>
+      <version>${bouncycastleVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.kohlschutter.junixsocket</groupId>
+      <artifactId>junixsocket-core</artifactId>
+      <version>2.9.0</version>
+      <type>pom</type>
+    </dependency>
 
     <dependency>
       <groupId>org.junit.jupiter</groupId>
@@ -179,8 +196,8 @@ under the License.
           <artifactId>apache-rat-plugin</artifactId>
           <configuration>
             <excludes combine.children="append">
-              <!-- rat check errors seen on ASF Jenkins instance, but not on 
local machine... -->
-              <exclude>src/test/resources/gnupg/**</exclude>
+              <!-- rat is too cheeky, ignore these resources -->
+              <exclude>src/test/resources/**</exclude>
             </excludes>
           </configuration>
         </plugin>
@@ -220,6 +237,67 @@ under the License.
       <build>
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-invoker-plugin</artifactId>
+            <configuration>
+              <settingsFile>src/it/settings.xml</settingsFile>
+              <pomIncludes>
+                <pomInclude>*</pomInclude>
+              </pomIncludes>
+              <pomExcludes>
+                <!-- not supported with gpg 2.1+ -->
+                <pomExclude>alternative-secret-keyring</pomExclude>
+              </pomExcludes>
+              <goals>
+                <goal>clean</goal>
+                <goal>install</goal>
+              </goals>
+              <properties>
+                
<gpg.homedir>${project.build.testOutputDirectory}/gnupg</gpg.homedir>
+              </properties>
+            </configuration>
+            <executions>
+              <execution>
+                <id>integration-test</id>
+                <phase>none</phase>
+              </execution>
+              <execution>
+                <id>integration-test-install</id>
+                <goals>
+                  <goal>install</goal>
+                </goals>
+                <phase>integration-test</phase>
+              </execution>
+              <execution>
+                <id>gpg-integration-tests</id>
+                <goals>
+                  <goal>run</goal>
+                </goals>
+                <phase>integration-test</phase>
+                <configuration>
+                  <properties>
+                    <gpg.signer>gpg</gpg.signer>
+                  </properties>
+                </configuration>
+              </execution>
+              <execution>
+                <id>bc-integration-tests</id>
+                <goals>
+                  <goal>run</goal>
+                </goals>
+                <phase>integration-test</phase>
+                <configuration>
+                  <properties>
+                    <gpg.signer>bc</gpg.signer>
+                    <!-- must be absolute -->
+                    
<gpg.keyFilePath>${project.basedir}/src/test/resources/signing-key.asc</gpg.keyFilePath>
+                  </properties>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <!-- this failsafe invocation depends on invoker:install above -->
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-failsafe-plugin</artifactId>
             <configuration>
@@ -243,27 +321,6 @@ under the License.
               </execution>
             </executions>
           </plugin>
-          <plugin>
-            <groupId>org.apache.maven.plugins</groupId>
-            <artifactId>maven-invoker-plugin</artifactId>
-            <configuration>
-              <settingsFile>src/it/settings.xml</settingsFile>
-              <pomIncludes>
-                <pomInclude>*</pomInclude>
-              </pomIncludes>
-              <pomExcludes>
-                <!-- not supported with gpg 2.1+ -->
-                <pomExclude>alternative-secret-keyring</pomExclude>
-              </pomExcludes>
-              <goals>
-                <goal>clean</goal>
-                <goal>install</goal>
-              </goals>
-              <properties>
-                
<gpg.homedir>${project.build.testOutputDirectory}/gnupg</gpg.homedir>
-              </properties>
-            </configuration>
-          </plugin>
         </plugins>
       </build>
     </profile>
diff --git a/src/it/sign-release-without-passphrase/verify.groovy 
b/src/it/sign-release-without-passphrase/verify.groovy
index eaeee90..5815a83 100644
--- a/src/it/sign-release-without-passphrase/verify.groovy
+++ b/src/it/sign-release-without-passphrase/verify.groovy
@@ -28,8 +28,8 @@ if (!logContent.contains("Total time: ") || 
!logContent.contains("Finished at: "
     throw new Exception("Maven build did not fail, but timed out")
 }
 
-// assert that the Maven build failed, because pinentry is not allowed in 
non-interactive mode
-if (!logContent.contains("[GNUPG:] FAILURE sign 67108949")) {
+// gpg: assert that the Maven build failed, because pinentry is not allowed in 
non-interactive mode
+// bc: assert that the Maven build failed, because key to sign is encrypted by 
no passphrase provided
+if (!logContent.contains("[GNUPG:] FAILURE sign 67108949") && 
!logContent.contains("Secret key is encrypted but no passphrase provided")) {
     throw new Exception("Maven build did not fail in consequence of pinentry 
not being available to GPG")
 }
-
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 e0bb093..691834d 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgMojo.java
@@ -32,10 +32,61 @@ import org.apache.maven.plugins.annotations.Parameter;
  * @author Benjamin Bentmann
  */
 public abstract class AbstractGpgMojo extends AbstractMojo {
+    public static final String DEFAULT_ENV_MAVEN_GPG_KEY = "MAVEN_GPG_KEY";
+    public static final String DEFAULT_ENV_MAVEN_GPG_FINGERPRINT = 
"MAVEN_GPG_KEY_FINGERPRINT";
     public static final String DEFAULT_ENV_MAVEN_GPG_PASSPHRASE = 
"MAVEN_GPG_PASSPHRASE";
 
+    /**
+     * BC Signer only: The comma separate list of Unix Domain Socket paths, to 
use to communicate with GnuPG agent.
+     * If relative, they are resolved against user home directory.
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.agentSocketLocations", defaultValue = 
".gnupg/S.gpg-agent")
+    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.
+     * <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>
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.keyFilePath", defaultValue = 
"maven-signing-key.key")
+    private String keyFilePath;
+
+    /**
+     * BC Signer only: The fingerprint of the key to use for signing. If not 
given, first key in keyring will be used.
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.keyFingerprint")
+    private String keyFingerprint;
+
+    /**
+     * BC Signer only: The env variable name where the GnuPG key is set. The 
default value is {@code MAVEN_GPG_KEY}.
+     * 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).
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.keyEnvName", defaultValue = 
DEFAULT_ENV_MAVEN_GPG_KEY)
+    private String keyEnvName;
+
+    /**
+     * 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}.
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.keyFingerprintEnvName", defaultValue = 
DEFAULT_ENV_MAVEN_GPG_FINGERPRINT)
+    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.
      *
      * @since 3.2.0
      */
@@ -43,7 +94,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String passphraseEnvName;
 
     /**
-     * The directory from which gpg will load keyrings. If not specified, gpg 
will use the value configured for its
+     * GPG Signer only: The directory from which gpg will load keyrings. If 
not specified, gpg will use the value configured for its
      * installation, e.g. <code>~/.gnupg</code> or 
<code>%APPDATA%/gnupg</code>.
      *
      * @since 1.0
@@ -53,7 +104,9 @@ 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.
+     * 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>
      *
      * @deprecated Do not use this configuration, plugin will fail if set.
      **/
@@ -62,9 +115,11 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String passphrase;
 
     /**
-     * Server id to lookup the passphrase under Maven settings.
-     * @since 1.6
+     * 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>
      *
+     * @since 1.6
      * @deprecated Do not use this configuration, plugin will fail if set.
      **/
     @Deprecated
@@ -72,27 +127,32 @@ public abstract class AbstractGpgMojo extends AbstractMojo 
{
     private String passphraseServerId;
 
     /**
-     * The "name" of the key to sign with. Passed to gpg as 
<code>--local-user</code>.
+     * GPG Signer only: The "name" of the key to sign with. Passed to gpg as 
<code>--local-user</code>.
      */
     @Parameter(property = "gpg.keyname")
     private String keyname;
 
     /**
-     * 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.
+     * 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
      */
+    @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;
 
     /**
-     * The path to the GnuPG executable to use for artifact signing. Defaults 
to either "gpg" or "gpg.exe" depending on
-     * the operating system.
+     * 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.
      *
      * @since 1.1
      */
@@ -100,7 +160,7 @@ public abstract class AbstractGpgMojo extends AbstractMojo {
     private String executable;
 
     /**
-     * Whether to add the default keyrings from gpg's home directory to the 
list of used keyrings.
+     * GPG Signer only: Whether to add the default keyrings from gpg's home 
directory to the list of used keyrings.
      *
      * @since 1.2
      */
@@ -108,32 +168,42 @@ public abstract class AbstractGpgMojo extends 
AbstractMojo {
     private boolean defaultKeyring;
 
     /**
-     * <p>The path to a secret keyring to add to the list of keyrings. By 
default, only the {@code secring.gpg} from
-     * gpg's home directory is considered. Use this option (in combination 
with {@link #publicKeyring} and
-     * {@link #defaultKeyring} if required) to use a different secret key. 
<em>Note:</em> Relative paths are resolved
-     * against gpg's home directory, not the project base directory.</p>
+     * GPG Signer only: The path to a secret keyring to add to the list of 
keyrings. By default, only the
+     * {@code secring.gpg} from gpg's home directory is considered. Use this 
option (in combination with
+     * {@link #publicKeyring} and {@link #defaultKeyring} if required) to use 
a different secret key.
+     * <em>Note:</em> Relative paths are resolved against gpg's home 
directory, not the project base directory.
+     * <p>
      * <strong>NOTE: </strong>As of gpg 2.1 this is an obsolete option and 
ignored. All secret keys are stored in the
      * ‘private-keys-v1.d’ directory below the GnuPG home directory.
      *
      * @since 1.2
+     * @deprecated
      */
+    @Deprecated
     @Parameter(property = "gpg.secretKeyring")
     private String secretKeyring;
 
     /**
-     * The path to a public keyring to add to the list of keyrings. By 
default, only the {@code pubring.gpg} from gpg's
-     * home directory is considered. Use this option (and {@link 
#defaultKeyring} if required) to use a different public
-     * key. <em>Note:</em> Relative paths are resolved against gpg's home 
directory, not the project base directory.
+     * GPG Signer only: The path to a public keyring to add to the list of 
keyrings. By default, only the
+     * {@code pubring.gpg} from gpg's home directory is considered. Use this 
option (and {@link #defaultKeyring}
+     * if required) to use a different public key. <em>Note:</em> Relative 
paths are resolved against gpg's home
+     * directory, not the project base directory.
+     * <p>
+     * <strong>NOTE: </strong>As of gpg 2.1 this is an obsolete option and 
ignored. All public keys are stored in the
+     * ‘pubring.kbx’ file below the GnuPG home directory.
      *
      * @since 1.2
+     * @deprecated
      */
+    @Deprecated
     @Parameter(property = "gpg.publicKeyring")
     private String publicKeyring;
 
     /**
-     * The lock mode to use when invoking gpg. By default no lock mode will be 
specified. Valid values are {@code once},
-     * {@code multiple} and {@code never}. The lock mode gets translated into 
the corresponding {@code --lock-___}
-     * command line argument. Improper usage of this option may lead to data 
and key corruption.
+     * GPG Signer only: The lock mode to use when invoking gpg. By default no 
lock mode will be specified. Valid
+     * values are {@code once}, {@code multiple} and {@code never}. The lock 
mode gets translated into the
+     * corresponding {@code --lock-___} command line argument. Improper usage 
of this option may lead to data and
+     * key corruption.
      *
      * @see <a 
href="http://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html";>the
      *      --lock-options</a>
@@ -163,6 +233,15 @@ public abstract class AbstractGpgMojo extends AbstractMojo 
{
     @Parameter
     private List<String> gpgArguments;
 
+    /**
+     * The name of the Signer implementation to use. Accepted values are 
{@code "gpg"} (the default, uses GnuPG
+     * executable) and {@code "bc"} (uses Bouncy Castle pure Java signer).
+     *
+     * @since 3.2.0
+     */
+    @Parameter(property = "gpg.signer", defaultValue = GpgSigner.NAME)
+    private String signer;
+
     /**
      * @since 3.0.0
      */
@@ -188,8 +267,21 @@ public abstract class AbstractGpgMojo extends AbstractMojo 
{
 
     protected abstract void doExecute() throws MojoExecutionException, 
MojoFailureException;
 
-    protected AbstractGpgSigner newSigner() throws MojoExecutionException, 
MojoFailureException {
-        AbstractGpgSigner signer = new GpgSigner(executable);
+    protected AbstractGpgSigner newSigner() throws MojoFailureException {
+        AbstractGpgSigner signer;
+        if (GpgSigner.NAME.equals(this.signer)) {
+            signer = new GpgSigner(executable);
+        } else if (BcSigner.NAME.equals(this.signer)) {
+            signer = new BcSigner(
+                    session.getRepositorySession(),
+                    keyEnvName,
+                    keyFingerprintEnvName,
+                    agentSocketLocations,
+                    keyFilePath,
+                    keyFingerprint);
+        } else {
+            throw new MojoFailureException("Unknown signer: " + this.signer);
+        }
 
         signer.setLog(getLog());
         signer.setInteractive(interactive);
@@ -208,13 +300,14 @@ public abstract class AbstractGpgMojo extends 
AbstractMojo {
             signer.setPassPhrase(passphrase);
         }
 
-        signer.setPassPhrase(passphrase);
-        if (null == passphrase && !useAgent) {
-            if (!interactive) {
-                throw new MojoFailureException("Cannot obtain passphrase in 
batch mode");
-            }
+        // 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;
     }
 }
diff --git a/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgSigner.java 
b/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgSigner.java
index d94c3cc..daef26d 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgSigner.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/AbstractGpgSigner.java
@@ -22,6 +22,7 @@ import java.io.File;
 import java.util.List;
 
 import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugin.logging.Log;
 
 /**
@@ -121,6 +122,10 @@ public abstract class AbstractGpgSigner {
         publicKeyring = path;
     }
 
+    public abstract String signerName();
+
+    public void prepare() throws MojoFailureException {}
+
     /**
      * Create a detached signature file for the provided file.
      *
diff --git a/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java 
b/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java
new file mode 100644
index 0000000..3ab8c22
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/gpg/BcSigner.java
@@ -0,0 +1,371 @@
+/*
+ * 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.apache.maven.plugins.gpg;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.codehaus.plexus.util.io.CachingOutputStream;
+import org.eclipse.aether.RepositorySystemSession;
+import org.newsclub.net.unix.AFUNIXSocket;
+import org.newsclub.net.unix.AFUNIXSocketAddress;
+
+/**
+ * A signer implementation that uses pure Java Bouncy Castle implementation to 
sign.
+ */
+@SuppressWarnings("checkstyle:magicnumber")
+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}.
+         */
+        default byte[] loadKeyRingMaterial(RepositorySystemSession session) 
throws IOException {
+            return null;
+        }
+
+        /**
+         * Returns the key fingerprint, or {@code null}.
+         */
+        default byte[] loadKeyFingerprint(RepositorySystemSession session) 
throws IOException {
+            return null;
+        }
+
+        /**
+         * Returns the key password, or {@code null}.
+         */
+        default char[] loadPassword(RepositorySystemSession session, long 
keyId) 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);
+            if (keyMaterial != null) {
+                return keyMaterial.getBytes(StandardCharsets.UTF_8);
+            }
+            return null;
+        }
+
+        @Override
+        public byte[] loadKeyFingerprint(RepositorySystemSession session) {
+            String keyFingerprint = (String) 
session.getConfigProperties().get("env." + keyFingerprintEnvName);
+            if (keyFingerprint != null) {
+                if (keyFingerprint.trim().length() == 40) {
+                    return Hex.decode(keyFingerprint);
+                } else {
+                    throw new IllegalArgumentException(
+                            "Key fingerprint configuration is wrong (hex 
encoded, 40 characters)");
+                }
+            }
+            return null;
+        }
+    }
+
+    public final class GpgConfLoader implements Loader {
+        /**
+         * Maximum key size, see <a 
href="https://wiki.gnupg.org/LargeKeys";>Large Keys</a>.
+         */
+        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);
+            }
+            if (Files.isRegularFile(keyPath)) {
+                if (Files.size(keyPath) < MAX_SIZE) {
+                    return Files.readAllBytes(keyPath);
+                } else {
+                    throw new IOException("Refusing to load key " + keyPath + 
"; is larger than 5KB");
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public byte[] loadKeyFingerprint(RepositorySystemSession session) {
+            if (keyFingerprint != null) {
+                if (keyFingerprint.trim().length() == 40) {
+                    return Hex.decode(keyFingerprint);
+                } else {
+                    throw new IllegalArgumentException(
+                            "Key fingerprint configuration is wrong (hex 
encoded, 40 characters)");
+                }
+            }
+            return null;
+        }
+    }
+
+    public final class GpgAgentPasswordLoader implements Loader {
+        @Override
+        public boolean isInteractive() {
+            return true;
+        }
+
+        @Override
+        public char[] loadPassword(RepositorySystemSession session, long 
keyId) throws IOException {
+            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();
+                } catch (SocketException e) {
+                    // try next location
+                }
+            }
+            return null;
+        }
+
+        private String load(long keyId, Path socketPath) throws IOException {
+            try (AFUNIXSocket sock = AFUNIXSocket.newInstance()) {
+                sock.connect(AFUNIXSocketAddress.of(socketPath));
+                try (BufferedReader in = new BufferedReader(new 
InputStreamReader(sock.getInputStream()));
+                        OutputStream os = sock.getOutputStream()) {
+
+                    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);
+                    }
+                    String hexKeyId = Long.toHexString(keyId & 0xFFFFFFFFL);
+                    // 
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
+                            + "+to+use+it+for+signing+Maven+Artifacts\n";
+                    os.write((instruction).getBytes());
+                    os.flush();
+                    return new String(Hex.decode(expectOK(in).trim()));
+                }
+            }
+        }
+
+        private String expectOK(BufferedReader in) throws IOException {
+            String response = in.readLine();
+            if (!response.startsWith("OK")) {
+                throw new IOException("Expected OK but got this instead: " + 
response);
+            }
+            return response.substring(Math.min(response.length(), 3));
+        }
+    }
+
+    private final RepositorySystemSession session;
+    private final String keyEnvName;
+    private final String keyFingerprintEnvName;
+    private final String agentSocketLocations;
+    private final String keyFilePath;
+    private final String keyFingerprint;
+    private PGPSecretKey secretKey;
+    private PGPPrivateKey privateKey;
+    private PGPSignatureSubpacketVector hashSubPackets;
+
+    public BcSigner(
+            RepositorySystemSession session,
+            String keyEnvName,
+            String keyFingerprintEnvName,
+            String agentSocketLocations,
+            String keyFilePath,
+            String keyFingerprint) {
+        this.session = session;
+        this.keyEnvName = keyEnvName;
+        this.keyFingerprintEnvName = keyFingerprintEnvName;
+        this.agentSocketLocations = agentSocketLocations;
+        this.keyFilePath = keyFilePath;
+        this.keyFingerprint = keyFingerprint;
+    }
+
+    @Override
+    public String signerName() {
+        return NAME;
+    }
+
+    @Override
+    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;
+            for (Loader loader : loaders) {
+                keyRingMaterial = loader.loadKeyRingMaterial(session);
+                if (keyRingMaterial != null) {
+                    break;
+                }
+            }
+            if (keyRingMaterial == null) {
+                throw new MojoFailureException("Key ring material not found");
+            }
+
+            byte[] fingerprint = null;
+            for (Loader loader : loaders) {
+                fingerprint = loader.loadKeyFingerprint(session);
+                if (fingerprint != null) {
+                    break;
+                }
+            }
+
+            PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new 
PGPSecretKeyRingCollection(
+                    PGPUtil.getDecoderStream(new 
ByteArrayInputStream(keyRingMaterial)),
+                    new BcKeyFingerprintCalculator());
+
+            PGPSecretKey secretKey = null;
+            for (PGPSecretKeyRing ring : pgpSecretKeyRingCollection) {
+                for (PGPSecretKey key : ring) {
+                    if (!key.isPrivateKeyEmpty()) {
+                        if (fingerprint == null || Arrays.equals(fingerprint, 
key.getFingerprint())) {
+                            secretKey = key;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (secretKey == null) {
+                throw new MojoFailureException("Secret key not found");
+            }
+            if (secretKey.isPrivateKeyEmpty()) {
+                throw new MojoFailureException("Private key not found in 
Secret key");
+            }
+
+            long validSeconds = secretKey.getPublicKey().getValidSeconds();
+            if (validSeconds > 0) {
+                LocalDateTime expireDateTime = secretKey
+                        .getPublicKey()
+                        .getCreationTime()
+                        .toInstant()
+                        .atZone(ZoneId.systemDefault())
+                        .toLocalDateTime()
+                        .plusSeconds(validSeconds);
+                if (LocalDateTime.now().isAfter(expireDateTime)) {
+                    throw new MojoFailureException("Secret key expired at: " + 
expireDateTime);
+                }
+            }
+
+            char[] keyPassword = passphrase != null ? passphrase.toCharArray() 
: null;
+            final boolean keyPassNeeded = 
secretKey.getKeyEncryptionAlgorithm() != SymmetricKeyAlgorithmTags.NULL;
+            if (keyPassNeeded && keyPassword == null) {
+                for (Loader loader : loaders) {
+                    keyPassword = loader.loadPassword(session, 
secretKey.getKeyID());
+                    if (keyPassword != null) {
+                        break;
+                    }
+                }
+                if (keyPassword == null) {
+                    throw new MojoFailureException("Secret key is encrypted 
but no passphrase provided");
+                }
+            }
+
+            this.secretKey = secretKey;
+            this.privateKey = secretKey.extractPrivateKey(
+                    new BcPBESecretKeyDecryptorBuilder(new 
BcPGPDigestCalculatorProvider()).build(keyPassword));
+            PGPSignatureSubpacketGenerator subPacketGenerator = new 
PGPSignatureSubpacketGenerator();
+            subPacketGenerator.setIssuerFingerprint(false, secretKey);
+            this.hashSubPackets = subPacketGenerator.generate();
+        } catch (PGPException | IOException e) {
+            throw new MojoFailureException(e);
+        }
+    }
+
+    @Override
+    protected void generateSignatureForFile(File file, File signature) throws 
MojoExecutionException {
+        try (InputStream in = Files.newInputStream(file.toPath());
+                OutputStream out = new 
CachingOutputStream(signature.toPath())) {
+            PGPSignatureGenerator sGen = new PGPSignatureGenerator(
+                    new 
BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), 
HashAlgorithmTags.SHA512));
+            sGen.init(PGPSignature.BINARY_DOCUMENT, privateKey);
+            sGen.setHashedSubpackets(hashSubPackets);
+            int len;
+            byte[] buffer = new byte[8 * 1024];
+            while ((len = in.read(buffer)) >= 0) {
+                sGen.update(buffer, 0, len);
+            }
+            try (BCPGOutputStream bcpgOutputStream = new BCPGOutputStream(new 
ArmoredOutputStream(out))) {
+                sGen.generate().encode(bcpgOutputStream);
+            }
+        } catch (PGPException | IOException e) {
+            throw new MojoExecutionException(e);
+        }
+    }
+}
diff --git 
a/src/main/java/org/apache/maven/plugins/gpg/GpgSignAttachedMojo.java 
b/src/main/java/org/apache/maven/plugins/gpg/GpgSignAttachedMojo.java
index 2032b0b..55ae88a 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/GpgSignAttachedMojo.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/GpgSignAttachedMojo.java
@@ -87,8 +87,8 @@ public class GpgSignAttachedMojo extends AbstractGpgMojo {
         signer.setBuildDirectory(new File(project.getBuild().getDirectory()));
         signer.setBaseDirectory(project.getBasedir());
 
-        getLog().info("Signing " + items.size() + " file" + ((items.size() > 
1) ? "s" : "") + " with "
-                + ((signer.keyname == null) ? "default" : signer.keyname) + " 
secret key.");
+        getLog().info("Signer '" + signer.signerName() + "' is signing " + 
items.size() + " file"
+                + ((items.size() > 1) ? "s" : ""));
 
         for (FilesCollector.Item item : items) {
             getLog().debug("Generating signature for " + item.getFile());
diff --git a/src/main/java/org/apache/maven/plugins/gpg/GpgSigner.java 
b/src/main/java/org/apache/maven/plugins/gpg/GpgSigner.java
index c0f7a96..deaff7b 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/GpgSigner.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/GpgSigner.java
@@ -33,12 +33,18 @@ import org.codehaus.plexus.util.cli.DefaultConsumer;
  * A signer implementation that uses the GnuPG command line executable.
  */
 public class GpgSigner extends AbstractGpgSigner {
+    public static final String NAME = "gpg";
     private String executable;
 
     public GpgSigner(String executable) {
         this.executable = executable;
     }
 
+    @Override
+    public String signerName() {
+        return NAME;
+    }
+
     /**
      * {@inheritDoc}
      */
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 c754950..623e3e3 100644
--- a/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java
+++ b/src/main/java/org/apache/maven/plugins/gpg/SignAndDeployFileMojo.java
@@ -353,6 +353,9 @@ public class SignAndDeployFileMojo extends AbstractGpgMojo {
         signer.setOutputDirectory(ascDirectory);
         signer.setBaseDirectory(new File("").getAbsoluteFile());
 
+        getLog().info("Signer '" + signer.signerName() + "' is signing " + 
artifacts.size() + " file"
+                + ((artifacts.size() > 1) ? "s" : ""));
+
         ArrayList<Artifact> signatures = new ArrayList<>();
         for (Artifact a : artifacts) {
             signatures.add(new DefaultArtifact(
diff --git a/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java 
b/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java
new file mode 100644
index 0000000..67d360d
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/gpg/BcSignerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.apache.maven.plugins.gpg;
+
+import java.io.File;
+
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.internal.impl.DefaultLocalPathComposer;
+import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
+import org.eclipse.aether.repository.LocalRepository;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link BcSigner}.
+ */
+class BcSignerTest {
+
+    /**
+     * Test for BC agent use. Disabled, as this test cannot run on CI, only on 
computer that have gpg-agent.
+     * The goal of this test is to prepare BC signer, but to be able to 
prepare, it needs passphrase for the
+     * passphrase protected signing key (provided in 
src/test/resources/signing-key.asc). Passphrase is "TEST"
+     * (without quotes, all caps). If you want to execute this test, remove 
disabled annotation and run it from
+     * IDE (or whatever is your preferred way). On first run, Agent will pop a 
dialogue asking for password,
+     * and it will cache your response, so subsequent invocation will NOT ask 
for password.
+     * <p>
+     * IF you enter correct password ("TEST"), the test will pass (prepare 
will execute without any issue).
+     * IF you enter incorrect password, the test will fail with some message 
like:
+     * {@code org.apache.maven.plugin.MojoFailureException: 
org.bouncycastle.openpgp.PGPException: checksum mismatch at in checksum of 20 
bytes}
+     * and this would cause plugin failure as well.
+     * <p>
+     * On Un*x, to make agent "forget" what you entered, use {@code 
gpg-connect-agent RELOADAGENT} command. To exit use
+     * Ctrl+D (EOF).
+     */
+    @Disabled
+    @Test
+    void testAgent() throws Exception {
+        DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession();
+        session.setLocalRepositoryManager(new 
SimpleLocalRepositoryManagerFactory(new DefaultLocalPathComposer())
+                .newInstance(session, new 
LocalRepository("target/local-repo")));
+        BcSigner signer = new BcSigner(
+                session,
+                "unimportant",
+                "unimportant",
+                ".gnupg/S.gpg-agent",
+                new 
File("src/test/resources/signing-key.asc").getAbsolutePath(),
+                null);
+        signer.prepare();
+    }
+}
diff --git 
a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java 
b/src/test/java/org/apache/maven/plugins/gpg/it/BcSignArtifactIT.java
similarity index 77%
copy from src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java
copy to src/test/java/org/apache/maven/plugins/gpg/it/BcSignArtifactIT.java
index c9b4ab6..91aef65 100644
--- a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java
+++ b/src/test/java/org/apache/maven/plugins/gpg/it/BcSignArtifactIT.java
@@ -26,23 +26,9 @@ import org.apache.maven.shared.invoker.InvocationRequest;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
-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 GpgSignArtifactIT {
-    private final File mavenHome;
-    private final File localRepository;
-    private final File mavenUserSettings;
-    private final File gpgHome;
-
-    public GpgSignArtifactIT() throws Exception {
-        this.mavenHome = new File(System.getProperty("maven.home"));
-        this.localRepository = new 
File(System.getProperty("localRepositoryPath"));
-        this.mavenUserSettings = 
InvokerTestUtils.getTestResource("/it/settings.xml");
-        this.gpgHome = new File(System.getProperty("gpg.homedir"));
-    }
+import static org.junit.jupiter.api.Assertions.*;
 
+public class BcSignArtifactIT extends ITSupport {
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] {
             {
@@ -74,7 +60,8 @@ public class GpgSignArtifactIT {
             throws Exception {
         // given
         final File pomFile = InvokerTestUtils.getTestResource(pomPath);
-        final InvocationRequest request = 
InvokerTestUtils.createRequest(pomFile, mavenUserSettings, gpgHome, true);
+        final InvocationRequest request =
+                InvokerTestUtils.createRequest(pomFile, mavenUserSettings, 
gpgHome, "bc", true);
         final File integrationTestRootDirectory = new 
File(pomFile.getParent());
         final File expectedOutputDirectory = new 
File(integrationTestRootDirectory + expectedFileLocation);
 
diff --git 
a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java 
b/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java
index c9b4ab6..69160ed 100644
--- a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java
+++ b/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignArtifactIT.java
@@ -30,19 +30,7 @@ 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 GpgSignArtifactIT {
-    private final File mavenHome;
-    private final File localRepository;
-    private final File mavenUserSettings;
-    private final File gpgHome;
-
-    public GpgSignArtifactIT() throws Exception {
-        this.mavenHome = new File(System.getProperty("maven.home"));
-        this.localRepository = new 
File(System.getProperty("localRepositoryPath"));
-        this.mavenUserSettings = 
InvokerTestUtils.getTestResource("/it/settings.xml");
-        this.gpgHome = new File(System.getProperty("gpg.homedir"));
-    }
-
+public class GpgSignArtifactIT extends ITSupport {
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] {
             {
@@ -74,7 +62,8 @@ public class GpgSignArtifactIT {
             throws Exception {
         // given
         final File pomFile = InvokerTestUtils.getTestResource(pomPath);
-        final InvocationRequest request = 
InvokerTestUtils.createRequest(pomFile, mavenUserSettings, gpgHome, true);
+        final InvocationRequest request =
+                InvokerTestUtils.createRequest(pomFile, mavenUserSettings, 
gpgHome, "gpg", true);
         final File integrationTestRootDirectory = new 
File(pomFile.getParent());
         final File expectedOutputDirectory = new 
File(integrationTestRootDirectory + expectedFileLocation);
 
diff --git 
a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignAttachedMojoIT.java 
b/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignAttachedMojoIT.java
index d75880d..c13e1c1 100644
--- a/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignAttachedMojoIT.java
+++ b/src/test/java/org/apache/maven/plugins/gpg/it/GpgSignAttachedMojoIT.java
@@ -28,26 +28,14 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class GpgSignAttachedMojoIT {
-
-    private final File mavenHome;
-    private final File localRepository;
-    private final File mavenUserSettings;
-    private final File gpgHome;
-
-    public GpgSignAttachedMojoIT() throws Exception {
-        this.mavenHome = new File(System.getProperty("maven.home"));
-        this.localRepository = new 
File(System.getProperty("localRepositoryPath"));
-        this.mavenUserSettings = 
InvokerTestUtils.getTestResource(System.getProperty("settingsFile"));
-        this.gpgHome = new File(System.getProperty("gpg.homedir"));
-    }
-
+public class GpgSignAttachedMojoIT extends ITSupport {
     @Test
     void testInteractiveWithoutPassphrase() throws Exception {
         // given
         final File pomFile =
                 
InvokerTestUtils.getTestResource("/it/sign-release-without-passphrase-interactive/pom.xml");
-        final InvocationRequest request = 
InvokerTestUtils.createRequest(pomFile, mavenUserSettings, gpgHome, false);
+        final InvocationRequest request =
+                InvokerTestUtils.createRequest(pomFile, mavenUserSettings, 
gpgHome, "gpg", false);
 
         // require Maven interactive mode
         request.setBatchMode(false);
diff --git a/src/it/sign-release-without-passphrase/verify.groovy 
b/src/test/java/org/apache/maven/plugins/gpg/it/ITSupport.java
similarity index 55%
copy from src/it/sign-release-without-passphrase/verify.groovy
copy to src/test/java/org/apache/maven/plugins/gpg/it/ITSupport.java
index eaeee90..4741266 100644
--- a/src/it/sign-release-without-passphrase/verify.groovy
+++ b/src/test/java/org/apache/maven/plugins/gpg/it/ITSupport.java
@@ -16,20 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.gpg.it;
 
+import java.io.File;
 
-import org.codehaus.plexus.util.FileUtils
+import org.junit.jupiter.api.BeforeEach;
 
-var buildLog = new File(basedir, "build.log")
-var logContent = FileUtils.fileRead(buildLog)
+public abstract class ITSupport {
+    protected File mavenHome;
+    protected File localRepository;
+    protected File mavenUserSettings;
+    protected File gpgHome;
 
-// assert that the Maven build properly failed and did not time out
-if (!logContent.contains("Total time: ") || !logContent.contains("Finished at: 
")) {
-    throw new Exception("Maven build did not fail, but timed out")
+    @BeforeEach
+    public void prepare() throws Exception {
+        this.mavenHome = new File(System.getProperty("maven.home"));
+        this.localRepository = new 
File(System.getProperty("localRepositoryPath"));
+        this.mavenUserSettings = 
InvokerTestUtils.getTestResource(System.getProperty("settingsFile"));
+        this.gpgHome = new File(System.getProperty("gpg.homedir"));
+    }
 }
-
-// assert that the Maven build failed, because pinentry is not allowed in 
non-interactive mode
-if (!logContent.contains("[GNUPG:] FAILURE sign 67108949")) {
-    throw new Exception("Maven build did not fail in consequence of pinentry 
not being available to GPG")
-}
-
diff --git 
a/src/test/java/org/apache/maven/plugins/gpg/it/InvokerTestUtils.java 
b/src/test/java/org/apache/maven/plugins/gpg/it/InvokerTestUtils.java
index 2c642e9..89bbeea 100644
--- a/src/test/java/org/apache/maven/plugins/gpg/it/InvokerTestUtils.java
+++ b/src/test/java/org/apache/maven/plugins/gpg/it/InvokerTestUtils.java
@@ -41,7 +41,7 @@ import org.apache.maven.shared.invoker.PrintStreamLogger;
 public class InvokerTestUtils {
 
     public static InvocationRequest createRequest(
-            File pomFile, File mavenUserSettings, File gpgHome, boolean 
providePassphraseEnv) {
+            File pomFile, File mavenUserSettings, File gpgHome, String signer, 
boolean providePassphraseEnv) {
         final InvocationRequest request = new DefaultInvocationRequest();
         request.setUserSettingsFile(mavenUserSettings);
         request.setShowVersion(true);
@@ -64,6 +64,10 @@ public class InvokerTestUtils {
             properties.setProperty("https.protocols", httpsProtocols);
         }
 
+        if (signer != null) {
+            properties.setProperty("gpg.signer", signer);
+            properties.setProperty("gpg.keyFilePath", new 
File("src/test/resources/signing-key.asc").getAbsolutePath());
+        }
         properties.setProperty("gpg.homedir", gpgHome.getAbsolutePath());
 
         return request;
diff --git a/src/test/resources/signing-key.asc 
b/src/test/resources/signing-key.asc
new file mode 100644
index 0000000..9f9bcbd
--- /dev/null
+++ b/src/test/resources/signing-key.asc
@@ -0,0 +1,32 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQHpBEqeoMkRBADgRe1HMAJ1LdxnVgfXijYTtlSBe7gYo5SqYvzvi26xUYsgOF96
+N4xXnf+aNKL4DZ+onPqpR1SdNu4Dc78vZGrSzyAjrpLuxODIr5r/xCmOzRrzM8nU
+GkP8KVSvcK9V5dHrwZZH+N340BptiMwTJIrvuUOArzHEOc00Bh0pXowSmwCgwN7w
+u/uvC2EGpTghHV+ht1VM53cEALmXyodsYQsIdSFPh1slK14Gwo8sipag7rhypek3
+OPXyW1UfysiVXVh0deWbbiFSx/i2yWq1AumTPPfTRyRGWAIFAeRV6Vniil4ufq1a
+Un2yeYpGo/cVT+DyQYP1OW6/+/mAjbbEtZauQeUEjVa3j4SqyR10sS4yebJ+ctPQ
+Bbe/A/9v5bTjl/u9TbiImbM34K19tBIe3jTQt/V6Tj1mZ8P/ssVa7BEgapp3/BY6
+4V48SkhxumEjHcy1wc3njLMb8Xbv9js2O6bpG84CBP3IN9iZOz7ANhxBuwG5LJN2
+0bEcQsE3gCNLx8Sz2A4Z/8sZfJzq5NdGmiX2X0L3EfF+pTMlKv4HAwKMaiQPAe58
+Xf97gsMrVrd6ruUzLYyUFNXwino8MD9uQS/URJ+hVGAM6WvD3WKMKaHGYt8DgeTM
+bJZASGOYMP2j4b3JtDVNYXZlbiBHUEcgUGx1Z2luIChURVNUSU5HIEtFWSkgPGRl
+dkBtYXZlbi5hcGFjaGUub3JnPohgBBMRAgAgBQJKnqDJAhsDBgsJCAcDAgQVAggD
+BBYCAwECHgECF4AACgkQg8qoh2UlSiYJpACeOfzjOBNIzmbfdDHqA9iY33BwgxQA
+n1U07P548XAkochaezFB2Cr/ZeGTnQJrBEqeoMkQCACKRwro2fnPATtzEcpsvt0j
+XMF7WgQcYFUYed5DZ6ra7bVp5yOyZmDswHlhSBXjYWgLBps5FEzawly5mbIc7Wh5
++izUtD8LiyUWcRiCAvoBOF9J0HKXHhnZxkM0XK6BrSRlARr7HsEpTOosFUPoHsMX
+xixwd2WzmV6U3A+4cOnjrnZD5YUJhKU/5JwuP+ZRZpyiyR1Q2qdQdaODy5zvM84q
+i2GuORGdYWs5hlkV4ur8ihu7zErQD9sBXPhlzFqRUM/2L57z2wUgDLoNgFzCQSZp
+1Rtb0VZvIjhXZ0vquJ8UKNWVpuhzlj7qOdNFHwEA6pRUy/fquk3f1vgo1x5SZv0X
+AAMFB/9u0u+BCOJH00762Tr1hClzuzPkQQpaWAmSqbDfj7Lb/0KKlBfOorby1Gdl
+Lz1XqsbkwzboDob7hbuAI5YV6iBYEVW8wYI49h+jA1cPFDj2kb1tT4RQrpvfP7GA
+n1/GdKHDGupTr2ZoKgU6iEb86ru5gcGbPPItx7rlPDMPGEbCvFtuPImNoscQMh1F
+ONHt+8YPJBTYVJFrsFag+p5JXsZWReBsnER5znhHoH8PTgZAGhdMWnKshqESFrNL
+rgz9JpGguJ1TL+suTG9CPubCnvXpsMGXTMURYSomwfQD4lXhiwx3DTBL1LXpFbTq
+iNBb/jaiJ5sTaGuwTp2+tppPiJ4//gcDAhi2Ufij/zA1/0UivR37hD8OQD4kIJDF
+TnFd6hYdJcRJdJAV8hI7ai4f6My7ihC29WoXBrCN2Uaizno2KCBt7m4hUSzLWhSm
+audxW4oPzGJwMu9Ux7+m4GCfVYhJBBgRAgAJBQJKnqDJAhsMAAoJEIPKqIdlJUom
+IxcAnjPrJT6I0XPlfKuz23x6KFinX4XtAJ40KkIfgDWI2Smg85Fc24E7QRqReg==
+=1WyT
+-----END PGP PRIVATE KEY BLOCK-----
\ No newline at end of file

Reply via email to