Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception while signing artifacts using in-memory ascii-armored keys #15718

Closed
xaviarias opened this issue Jan 7, 2021 · 11 comments
Closed

Exception while signing artifacts using in-memory ascii-armored keys #15718

xaviarias opened this issue Jan 7, 2021 · 11 comments

Comments

@xaviarias
Copy link
Contributor

xaviarias commented Jan 7, 2021

I have a project using the signing plugin and I'm tryign to sign the artifact in a CI workflow using environment variables as described here.

When reading GPG exported armored ASCII secret key, the signing plugin cannot read from the environment variable ORG_GRADLE_PROJECT_signingKey as described in the docs.

Expected Behavior

The signing plugin should read the environment variable ORG_GRADLE_PROJECT_signingKey and sign the artifacts.

Current Behavior

This is the error stack trace:

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':test-project:signMavenPublication'.
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:409)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:399)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:242)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:150)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:94)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:356)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.api.InvalidUserDataException: Could not read PGP secret key
        at org.gradle.security.internal.pgp.BaseInMemoryPgpSignatoryProvider.createSignatory(BaseInMemoryPgpSignatoryProvider.java:94)
        at org.gradle.security.internal.pgp.BaseInMemoryPgpSignatoryProvider.getDefaultSignatory(BaseInMemoryPgpSignatoryProvider.java:63)
        at org.gradle.security.internal.pgp.BaseInMemoryPgpSignatoryProvider.getDefaultSignatory(BaseInMemoryPgpSignatoryProvider.java:42)
        at org.gradle.plugins.signing.SigningExtension.getSignatory(SigningExtension.java:199)
        at org.gradle.plugins.signing.SigningExtension_Decorated.getSignatory(Unknown Source)
        at org.gradle.plugins.signing.SigningExtension$2.call(SigningExtension.java:294)
        at org.gradle.plugins.signing.SigningExtension$2.call(SigningExtension.java:291)
        at org.gradle.util.GUtil.uncheckedCall(GUtil.java:442)
        at org.gradle.internal.extensibility.ConventionAwareHelper$2.doGetValue(ConventionAwareHelper.java:82)
        at org.gradle.internal.extensibility.ConventionAwareHelper$MappedPropertyImpl.getValue(ConventionAwareHelper.java:128)
        at org.gradle.internal.extensibility.ConventionAwareHelper.getConventionValue(ConventionAwareHelper.java:110)
        at org.gradle.plugins.signing.Sign_Decorated.getSignatory(Unknown Source)
        at jdk.internal.reflect.GeneratedMethodAccessor162.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue$1$1.create(AbstractNestedRuntimeBeanNode.java:77)
        at org.gradle.internal.deprecation.DeprecationLogger.whileDisabled(DeprecationLogger.java:228)
        at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue$1.get(AbstractNestedRuntimeBeanNode.java:73)
        at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers.java:167)
        at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue.call(AbstractNestedRuntimeBeanNode.java:133)
        at org.gradle.api.internal.tasks.properties.annotations.NestedBeanAnnotationHandler.visitPropertyValue(NestedBeanAnnotationHandler.java:62)
        at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode.visitProperties(AbstractNestedRuntimeBeanNode.java:56)
        at org.gradle.api.internal.tasks.properties.bean.RootRuntimeBeanNode.visitNode(RootRuntimeBeanNode.java:32)
        at org.gradle.api.internal.tasks.properties.DefaultPropertyWalker.visitProperties(DefaultPropertyWalker.java:41)
        at org.gradle.api.internal.tasks.TaskPropertyUtils.visitProperties(TaskPropertyUtils.java:44)
        at org.gradle.api.internal.tasks.properties.DefaultTaskProperties.resolve(DefaultTaskProperties.java:64)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:409)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:399)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:242)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:150)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:94)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:356)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: java.io.IOException: secret key ring doesn't start with secret key tag: tag 0xffffffff
        at org.bouncycastle.openpgp.PGPSecretKeyRing.<init>(Unknown Source)
        at org.bouncycastle.openpgp.jcajce.JcaPGPSecretKeyRing.<init>(Unknown Source)
        at org.gradle.security.internal.pgp.BaseInMemoryPgpSignatoryProvider.createSignatory(BaseInMemoryPgpSignatoryProvider.java:80)
        ... 51 more

Context

While trying to sign artifacts before being published using the signing plugin, the read packet tag is incorrect.

The BouncyCastle library (class PGPSecretKeyRing) tries to match the first byte of the secret key:

BCPGInputStream pIn = wrap(in);

int initialTag = pIn.nextPacketTag();
if (initialTag != PacketTags.SECRET_KEY && initialTag != PacketTags.SECRET_SUBKEY)
{
    throw new IOException(
        "secret key ring doesn't start with secret key tag: " +
        "tag 0x" + Integer.toHexString(initialTag));
}

But the first character should be 0x14 in order to match the SECRET_KEY condition, which is a non-printable char.

How can the armored secret key can contain this character? Or I am missing something? My secret key looks like:

-----BEGIN PGP PRIVATE KEY BLOCK-----

lQWGBF/15bkBDACtaka1BDVLU6XbSYh6GAyrdDS0LECNQyeHeblkqDlMbQgiS9/V
...

Steps to Reproduce

Define a project with the Gradle plugins:

apply plugin: 'maven-publish'
apply plugin: 'signing'
apply plugin: 'java'

signing {
    sign publishing.publications.maven
    def signingKey = findProperty("signingKey")
    def signingPassword = findProperty("signingPassword")
    useInMemoryPgpKeys(signingKey, signingPassword)
}

In a terminal, export the environment variables ORG_GRADLE_PROJECT_signingKey and ORG_GRADLE_PROJECT_signingPassword, and run the Gradle task: ./gradlew signMavenPublication.

Your Environment

I'm using Gradle 6.7 in a MacOSX Big Sur, Intellij 2020.

@xaviarias xaviarias changed the title Exception while Exception while signing artifacts using in-memory ascii-armored keys Jan 7, 2021
@xaviarias
Copy link
Contributor Author

I have fixed it using GPG instead of OpenSSL.

@taleodor
Copy link

taleodor commented Feb 5, 2021

@xaviarias Would you mind providing your solution in more details how you fixed this?

@gnarea
Copy link

gnarea commented Apr 8, 2021

@xaviarias, do you mean that you added useGpgCmd() inside signing { ... }?

I tried that but it didn't fix the issue. I'm using Gradle 6.8.3.

@iurysza
Copy link

iurysza commented Jul 25, 2021

@gnarea I was able to work around this by using useGpgCmd().
You need to point your keyring file to the appropriate envvar. You can get one with:

$ gpg --export-secret-key ${your_keyid} > /path/to/keyring.gpg

Then, you should create add a SIGNING_KEY_RING_FILE env var pointing to path/to/keyring.gpg
You should also provide the SIGNING_KEY_ID and SIGNING_PASSWORD env vars.

Still, the problem remains when using the useInMemoryPgpKeys, which I believe is similar to what @xaviarias experienced.
I've tried using this strategy with no luck.

Any hints?

@iurysza
Copy link

iurysza commented Jul 25, 2021

I was able to successfully sign with the useInMemoryPgpKeys method.
The issue is related to the keyId format.

Here's what I did:

gpg --export-secret-keys --armor your_key_id > local.asc

Then you should escape the output and add it to your gradle.properties file, like so:

signing.key=-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nexported_secret_key\n-----END PGP PRIVATE KEY BLOCK-----

Then, you need to set your signing.keyId on the same file, but only the last 8 characters!

signing.keyId=abcd1234
# you also need your password
signing.password=yourpassword123

This did the trick! Previously, I was using the whole keyId and it kept failing, now it works.
I'm not sure if this was the intended behavior, but this is how I got it working.

@alicederyn
Copy link
Contributor

A note in case anyone else hits a related issue I encountered, namely a Could not read PGP secret key error that (on rerunning with --stacktrace) turned out to be caused by org.bouncycastle.openpgp.PGPException: unknown public key algorithm encountered. I fixed this one by (sadly) generating a new key, this time using gpg --full-generate-key so I could tell it to use the "RSA (sign only)" option.

@samuel-rufi
Copy link

samuel-rufi commented Oct 18, 2022

The ASCII armored signing key contains important new line characters that are lost when passed directly to gradle as an environment variable.

A solution that works without having to create a gradle.properties file:
Encode the ascii armored signing key in base64 and use store it in a ORG_GRADLE_PROJECT_base64EncodedAsciiArmoredSigningKey environment variable. Then do:

signing {
    def signingKey = base64Decode(findProperty("base64EncodedAsciiArmoredSigningKey"))
    def signingPassword = findProperty("signingPassword")
    useInMemoryPgpKeys(signingKey, signingPassword)
    sign publishing.publications.mavenJava
}

def base64Decode(encodedString){
    if(encodedString != null) {
        byte[] decoded = encodedString.decodeBase64()
        String decode = new String(decoded)
        return decode
    }
    return null;
}

@alicederyn
Copy link
Contributor

@samuel-rufi You can put newlines in environment variables. I have successfully passed newlines in a github secret to Gradle via environment variables. Perhaps you are losing them in the process of setting them?

@skuzzle
Copy link
Member

skuzzle commented Feb 8, 2023

I had the exact same issue on a jenkins CI server. Turns out that jenkins doesn't support putting newlines in secret text credentials because of its security model (jenkins autmotically masks credentials in log output and this only works for strings without new lines)

Using the base64 encoded key as suggested by @samuel-rufi did the trick

@axzae
Copy link

axzae commented Apr 28, 2023

ascii-armor is already a base64 encoded string. it feels inefficient to base64 it again.

Here's a better alternative by splitting your one-liner input back to ascii-armor:

useInMemoryPgpKeys(signingKey.chunked(64).joinToString("\n"), signingPassword)

@thedroiddiv
Copy link

I was able to successfully sign with the useInMemoryPgpKeys method. The issue is related to the keyId format.

Here's what I did:

gpg --export-secret-keys --armor your_key_id > local.asc

Then you should escape the output and add it to your gradle.properties file, like so:

signing.key=-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nexported_secret_key\n-----END PGP PRIVATE KEY BLOCK-----

Then, you need to set your signing.keyId on the same file, but only the last 8 characters!

signing.keyId=abcd1234
# you also need your password
signing.password=yourpassword123

This did the trick! Previously, I was using the whole keyId and it kept failing, now it works. I'm not sure if this was the intended behavior, but this is how I got it working.

I also got it working by using only the last 8 characters of the keyId. Using it fully was causing failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests