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

Allow maven encryption for keyPass #34

Merged
merged 1 commit into from
Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
Creates OpenPGP signatures for all of the project's artifacts
without any external software.

This plugin can replace **maven-gpg-plugin** in easy way and give you new feature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English Gramar, so it should be: "in an easy way and provide new features."


# Feature

- all the signing operations are done using `Bouncy Castle`
- support Maven `3.6` and is ready for next version `3.7/4.0` of Maven with `Consumer POM`
- support `subkey` for signing
- easy to use on CI system, configuration can be provided by environment variables
- easy to use on CI system, configuration can be provided by environment variables
- key passphrase can be encrypted by standard Maven [Password Encryption](https://maven.apache.org/guides/mini/guide-encryption.html)

# Key prepare

Expand Down
20 changes: 20 additions & 0 deletions src/it/invalid-encrypted-password/invoker.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# Copyright 2021 Slawomir Jaranowski
#
# Licensed 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.
#
invoker.goals = install --no-transfer-progress -Drevision=1.1.1 -Dsettings.security=${project.basedir}/src/it/settings-security.xml
invoker.mavenOpts = ${argLine}
invoker.buildResult = failure

invoker.ordinal = 100
52 changes: 52 additions & 0 deletions src/it/invalid-encrypted-password/pom-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version='1.0' encoding='UTF-8'?>

<!--
~ Copyright 2021 Slawomir Jaranowski
~
~ Licensed 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.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>test-group</groupId>
<artifactId>invalid-encrypted-password</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>

<build>
<plugins>

<plugin>
<groupId>org.simplify4u.plugins</groupId>
<artifactId>sign-maven-plugin</artifactId>
<version>@project.version@</version>
<executions>

<execution>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<keyId>AC71B3E31C0C0D38</keyId>
<keyPass>{Kii9qpeY09YHqthhLmmtWA7q2KWCABsqXMrPrWVsY6w=}</keyPass>
<keyFile>@project.basedir@/src/test/resources/priv-key.asc</keyFile>
</configuration>
</execution>

</executions>
</plugin>
</plugins>
</build>
</project>
22 changes: 22 additions & 0 deletions src/it/invalid-encrypted-password/postbuild.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2021 Slawomir Jaranowski
*
* Licensed 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.
*/

def buildLog = new File( basedir, 'build.log' ).text

buildLog.contains('SignMojoException: Invalid encrypted password')

def target = new File( basedir, "target" )
assert !target.exists()
2 changes: 1 addition & 1 deletion src/it/pom-packaging/invoker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
invoker.goals = install --no-transfer-progress -Drevision=1.1.1
invoker.goals = install --no-transfer-progress -Drevision=1.1.1 -Dsettings.security=${project.basedir}/src/it/settings-security.xml
invoker.mavenOpts = ${argLine}

invoker.ordinal = 100
2 changes: 1 addition & 1 deletion src/it/pom-packaging/pom-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</goals>
<configuration>
<keyId>AC71B3E31C0C0D38</keyId>
<keyPass>testPass</keyPass>
<keyPass>{p01Vm25gEoIH/wDlR3hWNhOH5mn4PBEnSFEtH96k1Yw=}</keyPass>
<keyFile>@project.basedir@/src/test/resources/priv-key.asc</keyFile>
</configuration>
</execution>
Expand Down
18 changes: 18 additions & 0 deletions src/it/settings-security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!--
~ Copyright 2021 Slawomir Jaranowski
~
~ Licensed 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.
-->
<settingsSecurity>
<master>{C2S/FkUJ51EHeGSVvXXp+thgf/SzyfoN5naopubT/ZU=}</master>
</settingsSecurity>
3 changes: 3 additions & 0 deletions src/main/java/org/simplify4u/plugins/sign/ArtifactSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public abstract class ArtifactSigner {

/**
* Check if artifact has correct data.
*
* @param artifact an artifact to sign
*/
protected static void verifyArtifact(Artifact artifact) {
Expand All @@ -67,6 +68,7 @@ protected static void verifyArtifact(Artifact artifact) {
* @param inputStream data to sign
* @param artifactId used for build filename
* @param classifier used for build filename
* @param version used for build filename
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English Grammar: Either "used for built filename" or "used to build filename".

* @param extension used for build filename
*
* @return result of signing
Expand All @@ -93,6 +95,7 @@ protected SignResult makeSignature(InputStream inputStream,

/**
* Setup signarer with key configuration
*
* @param pgpKeyInfo a private key configuration
*/
public void setKeyInfo(PGPKeyInfo pgpKeyInfo) {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/simplify4u/plugins/sign/SignMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Set;
import javax.inject.Inject;

import io.vavr.control.Try;
import lombok.AccessLevel;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -34,6 +35,7 @@
import org.apache.maven.project.artifact.ProjectArtifact;
import org.simplify4u.plugins.sign.openpgp.PGPKeyInfo;
import org.simplify4u.plugins.sign.openpgp.PGPSignerKeyNotFoundException;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;

/**
* Creates OpenPGP signatures for all of the project's artifacts.
Expand All @@ -54,6 +56,9 @@ public class SignMojo extends AbstractMojo {
@Inject
private ArtifactSignerFactory artifactSignerFactory;

@Inject
private SecDispatcher secDispatcher;

/**
* <p><code>keyId</code> used for signing. If not provided first key from <code>keyFile</code> will be taken.</p>
*
Expand All @@ -67,12 +72,16 @@ public class SignMojo extends AbstractMojo {
/**
* <p><code>passphrase</code> to decrypt private signing key.</p>
*
* <p>Can be encrypted by standard Maven
* <a href="https://maven.apache.org/guides/mini/guide-encryption.html">Password Encryption</a></p>
*
* <p>Provided key can be stored in plain text, in this case <code>keyPass</code> can be empty.</p>
*
* <p>This value can be delivered by environment variable <code>SIGN_KEY_PASS</code>.</p>
*
* @since 0.1.0
*/
@Setter(AccessLevel.PACKAGE)
@Parameter(property = "sign.keyPass")
private String keyPass;

Expand Down Expand Up @@ -125,6 +134,7 @@ public void execute() {
PGPKeyInfo keyInfo;
try {
keyInfo = PGPKeyInfo.builder()
.passDecryptor(this::decryptPass)
.keyId(keyId)
.keyPass(keyPass)
.keyFile(keyFile)
Expand Down Expand Up @@ -154,6 +164,11 @@ public void execute() {
.forEach(this::attachSignResult);
}

private String decryptPass(String pass) {
return Try.of(() -> secDispatcher.decrypt(pass))
.getOrElseThrow(e -> new SignMojoException("Invalid encrypted password", e));
}

/**
* Attache sign result to project.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public class SignMojoException extends RuntimeException {
SignMojoException(String message) {
super(message);
}

SignMojoException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Optional;
import java.util.function.UnaryOperator;

import io.vavr.control.Try;
import lombok.AccessLevel;
Expand All @@ -48,13 +49,14 @@ public class PGPKeyInfo {
InputStream key;

@Builder
private PGPKeyInfo(String keyId, String keyPass, File keyFile) {
private PGPKeyInfo(String keyId, String keyPass, File keyFile, UnaryOperator<String> passDecryptor) {

id = Optional.ofNullable(stringFromEnv(SIGN_KEY_ID_ENV).orElse(keyId))
.map(PGPKeyInfo::parseKeyId)
.orElse(null);

pass = Optional.ofNullable(stringFromEnv(SIGN_KEY_PASS_ENV).orElse(keyPass))
.map(Optional.ofNullable(passDecryptor).orElseGet(UnaryOperator::identity))
.map(String::toCharArray)
.orElse(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;

/**
* Signig data by PGP.
* Signing data by PGP.
*
* @author Slawomir Jaranowski
*/
Expand Down
6 changes: 5 additions & 1 deletion src/site/markdown/index.md.vm
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ Introduction
`Sign Maven Plugin` allows you to creates OpenPGP signatures for all of the project's artifacts
without any external software.

# Feature
This plugin can replace **maven-gpg-plugin** in easy way and give you new feature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English Grammar, see above.


Feature
-------

- all the signing operations are done using `Bouncy Castle`
- support Maven `3.6` and is ready for next version `3.7/4.0` of Maven with `Consumer POM`
- support `subkey` for signing
- easy to use on CI system, configuration can be provided by environment variables
- key passphrase can be encrypted by standard Maven [Password Encryption](https://maven.apache.org/guides/mini/guide-encryption.html)

22 changes: 19 additions & 3 deletions src/test/java/org/simplify4u/plugins/sign/SignMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.simplify4u.plugins.sign.openpgp.PGPSignerKeyNotFoundException;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;

@ExtendWith(MockitoExtension.class)
class SignMojoTest {
Expand All @@ -47,6 +48,9 @@ class SignMojoTest {
@Mock
private ArtifactSignerFactory artifactSignerFactory;

@Mock
private SecDispatcher secDispatcher;

@InjectMocks
private SignMojo mojo;

Expand All @@ -60,7 +64,7 @@ void skipExecution() {
mojo.execute();

// then
verifyNoInteractions(artifactSignerFactory, artifactSigner, project);
verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project);
}

@Test
Expand All @@ -73,7 +77,7 @@ void noExistingKeyShouldSkipExecution() {
mojo.execute();

//then
verifyNoInteractions(artifactSignerFactory, artifactSigner, project);
verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project);
}

@Test
Expand All @@ -86,7 +90,8 @@ void noExistingKeyShouldBreakExecution() {
assertThatThrownBy(() -> mojo.execute())
.isExactlyInstanceOf(PGPSignerKeyNotFoundException.class)
.hasMessage("key file: no-existing-key.asc not found");
verifyNoInteractions(artifactSignerFactory, artifactSigner, project);

verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project);
}

@Nested
Expand All @@ -113,6 +118,17 @@ void executeWithOutParams() {
mojo.execute();

verify(artifactSigner).signArtifact(any());
verifyNoInteractions(secDispatcher);
}

@Test
void executeWithPassword() throws Exception {

mojo.setKeyPass("keyPass");
mojo.execute();

verify(artifactSigner).signArtifact(any());
verify(secDispatcher).decrypt("keyPass");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,18 @@ void nullStringInEnvironmentValueShouldBeFiltered() {
assertThat(keyInfo.getPass()).isNull();
assertThat(keyInfo.getKey()).hasContent("signKey from environment");
}

@Test
void passDecryptorShouldBeCalled() {

// when
PGPKeyInfo keyInfo = PGPKeyInfo.builder()
.passDecryptor(String::toUpperCase)
.keyPass(KEY_PASS_STR)
.keyFile(KEY_FILE)
.build();

// then
assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS_STR.toUpperCase().toCharArray());
}
}