Skip to content

Commit df6b4f1

Browse files
committed
initial test at signing with sigstore
1 parent ffc82c2 commit df6b4f1

File tree

3 files changed

+267
-3
lines changed

3 files changed

+267
-3
lines changed

pom.xml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,18 @@ under the License.
6363

6464
<properties>
6565
<mavenVersion>3.2.5</mavenVersion>
66-
<javaVersion>8</javaVersion>
66+
<javaVersion>11</javaVersion>
67+
<maven.compiler.release>${javaVersion}</maven.compiler.release>
6768
<project.build.outputTimestamp>2021-05-05T16:39:01Z</project.build.outputTimestamp>
6869
<resource.delimiter>@</resource.delimiter>
6970
</properties>
7071

7172
<dependencies>
73+
<dependency>
74+
<groupId>dev.sigstore</groupId>
75+
<artifactId>sigstore-java</artifactId>
76+
<version>0.4.0</version>
77+
</dependency>
7278
<dependency>
7379
<groupId>org.apache.maven</groupId>
7480
<artifactId>maven-plugin-api</artifactId>
@@ -282,5 +288,34 @@ under the License.
282288
</plugins>
283289
</build>
284290
</profile>
291+
292+
<profile>
293+
<id>apache-release</id>
294+
<build>
295+
<pluginManagement>
296+
<plugins>
297+
<plugin>
298+
<groupId>org.apache.maven.plugins</groupId>
299+
<artifactId>maven-gpg-plugin</artifactId>
300+
<version>${project.version}</version>
301+
</plugin>
302+
</plugins>
303+
</pluginManagement>
304+
<plugins>
305+
<plugin>
306+
<groupId>org.apache.maven.plugins</groupId>
307+
<artifactId>maven-gpg-plugin</artifactId>
308+
<executions>
309+
<execution>
310+
<id>sigstore-sign-release-artifacts</id>
311+
<goals>
312+
<goal>sigstore</goal>
313+
</goals>
314+
</execution>
315+
</executions>
316+
</plugin>
317+
</plugins>
318+
</build>
319+
</profile>
285320
</profiles>
286321
</project>

src/main/java/org/apache/maven/plugins/gpg/GpgSignAttachedMojo.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class GpgSignAttachedMojo
5050
{
5151

5252
private static final String DEFAULT_EXCLUDES[] =
53-
new String[] { "**/*.md5", "**/*.sha1", "**/*.sha256", "**/*.sha512", "**/*.asc" };
53+
new String[] { "**/*.md5", "**/*.sha1", "**/*.sha256", "**/*.sha512", "**/*.asc", "**/*.sigstore" };
5454

5555
/**
5656
* Skip doing the gpg signing.
@@ -60,7 +60,8 @@ public class GpgSignAttachedMojo
6060

6161
/**
6262
* A list of files to exclude from being signed. Can contain Ant-style wildcards and double wildcards. The default
63-
* excludes are <code>**&#47;*.md5 **&#47;*.sha1 **&#47;*.sha256 **&#47;*.sha512 **&#47;*.asc</code>.
63+
* excludes are <code>**&#47;*.md5 **&#47;*.sha1 **&#47;*.sha256 **&#47;*.sha512
64+
* **&#47;*.asc **&#47;*.sigstore</code>.
6465
*
6566
* @since 1.0-alpha-4
6667
*/
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package org.apache.maven.plugins.gpg;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.File;
23+
import java.io.IOException;
24+
import java.nio.file.Path;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
28+
import org.apache.maven.artifact.Artifact;
29+
import org.apache.maven.plugin.AbstractMojo;
30+
import org.apache.maven.plugin.MojoExecutionException;
31+
import org.apache.maven.plugin.MojoFailureException;
32+
import org.apache.maven.plugins.annotations.Component;
33+
import org.apache.maven.plugins.annotations.LifecyclePhase;
34+
import org.apache.maven.plugins.annotations.Mojo;
35+
import org.apache.maven.plugins.annotations.Parameter;
36+
import org.apache.maven.project.MavenProject;
37+
import org.apache.maven.project.MavenProjectHelper;
38+
import org.codehaus.plexus.util.FileUtils;
39+
import org.codehaus.plexus.util.SelectorUtils;
40+
41+
import dev.sigstore.KeylessSignature;
42+
import dev.sigstore.KeylessSigner;
43+
import dev.sigstore.bundle.BundleFactory;
44+
45+
/**
46+
* Sign project artifact, the POM, and attached artifacts with sigstore for deployment.
47+
*
48+
* @since 3.1.0
49+
*/
50+
@Mojo( name = "sigstore", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true )
51+
public class SigstoreSignAttachedMojo
52+
extends AbstractMojo
53+
{
54+
55+
private static final String DEFAULT_EXCLUDES[] =
56+
new String[] { "**/*.md5", "**/*.sha1", "**/*.sha256", "**/*.sha512", "**/*.asc", "**/*.sigstore" };
57+
58+
/**
59+
* Skip doing the gpg signing.
60+
*/
61+
@Parameter( property = "sigstore.skip", defaultValue = "false" )
62+
private boolean skip;
63+
64+
/**
65+
* A list of files to exclude from being signed. Can contain Ant-style wildcards and double wildcards. The default
66+
* excludes are <code>**&#47;*.md5 **&#47;*.sha1 **&#47;*.sha256 **&#47;*.sha512
67+
* **&#47;*.asc **&#47;*.sigstore</code>.
68+
*/
69+
@Parameter
70+
private String[] excludes;
71+
72+
/**
73+
* The Maven project.
74+
*/
75+
@Parameter( defaultValue = "${project}", readonly = true, required = true )
76+
protected MavenProject project;
77+
78+
/**
79+
* Maven ProjectHelper
80+
*/
81+
@Component
82+
private MavenProjectHelper projectHelper;
83+
84+
@Override
85+
public void execute()
86+
throws MojoExecutionException, MojoFailureException
87+
{
88+
if ( skip )
89+
{
90+
// We're skipping the signing stuff
91+
return;
92+
}
93+
94+
if ( excludes == null || excludes.length == 0 )
95+
{
96+
excludes = DEFAULT_EXCLUDES;
97+
}
98+
String newExcludes[] = new String[excludes.length];
99+
for ( int i = 0; i < excludes.length; i++ )
100+
{
101+
String pattern;
102+
pattern = excludes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
103+
if ( pattern.endsWith( File.separator ) )
104+
{
105+
pattern += "**";
106+
}
107+
newExcludes[i] = pattern;
108+
}
109+
excludes = newExcludes;
110+
111+
List<SigningBundle> filesToSign = new ArrayList<>();
112+
113+
if ( !"pom".equals( project.getPackaging() ) )
114+
{
115+
// ----------------------------------------------------------------------------
116+
// Project artifact
117+
// ----------------------------------------------------------------------------
118+
119+
Artifact artifact = project.getArtifact();
120+
121+
File file = artifact.getFile();
122+
123+
if ( file != null && file.isFile() )
124+
{
125+
filesToSign.add( new SigningBundle( artifact.getArtifactHandler().getExtension(), file ) );
126+
}
127+
else if ( project.getAttachedArtifacts().isEmpty() )
128+
{
129+
throw new MojoFailureException( "The project artifact has not been assembled yet. "
130+
+ "Please do not invoke this goal before the lifecycle phase \"package\"." );
131+
}
132+
else
133+
{
134+
getLog().debug( "Main artifact not assembled, skipping signature generation" );
135+
}
136+
}
137+
138+
// ----------------------------------------------------------------------------
139+
// POM
140+
// ----------------------------------------------------------------------------
141+
142+
File pomToSign = new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".pom" );
143+
144+
try
145+
{
146+
FileUtils.copyFile( project.getFile(), pomToSign );
147+
}
148+
catch ( IOException e )
149+
{
150+
throw new MojoExecutionException( "Error copying POM for signing.", e );
151+
}
152+
153+
filesToSign.add( new SigningBundle( "pom", pomToSign ) );
154+
155+
// ----------------------------------------------------------------------------
156+
// Attached artifacts
157+
// ----------------------------------------------------------------------------
158+
159+
for ( Object o : project.getAttachedArtifacts() )
160+
{
161+
Artifact artifact = (Artifact) o;
162+
163+
File file = artifact.getFile();
164+
165+
if ( isExcluded( artifact ) )
166+
{
167+
getLog().debug( "Skipping generation of signature for excluded " + file );
168+
continue;
169+
}
170+
171+
filesToSign.add( new SigningBundle( artifact.getArtifactHandler().getExtension(),
172+
artifact.getClassifier(), file ) );
173+
}
174+
175+
// ----------------------------------------------------------------------------
176+
// Sign the filesToSign and attach all the signatures
177+
// ----------------------------------------------------------------------------
178+
179+
try
180+
{
181+
KeylessSigner signer = KeylessSigner.builder().sigstoreStagingDefaults().build();
182+
for ( SigningBundle bundleToSign : filesToSign )
183+
{
184+
File fileToSign = bundleToSign.getSignature(); // reusing original GPG implementation where it's the signature: TODO change
185+
186+
KeylessSignature signature = signer.signFile( fileToSign.toPath() );
187+
188+
// sigstore signature in bundle format (json string)
189+
String sigstoreBundle = BundleFactory.createBundle( signature );
190+
191+
File signatureFile = new File( fileToSign + ".sigstore" );
192+
FileUtils.fileWrite( signatureFile, "UTF-8", sigstoreBundle );
193+
194+
projectHelper.attachArtifact( project, bundleToSign.getExtension() + ".sigstore",
195+
bundleToSign.getClassifier(), signatureFile );
196+
}
197+
}
198+
catch ( Exception e )
199+
{
200+
throw new MojoExecutionException( "Error while signing with sigstore", e );
201+
}
202+
}
203+
204+
/**
205+
* Tests whether or not a name matches against at least one exclude pattern.
206+
*
207+
* @param artifact The artifact to match. Must not be <code>null</code>.
208+
* @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code>
209+
* otherwise.
210+
*/
211+
protected boolean isExcluded( Artifact artifact )
212+
{
213+
final Path projectBasePath = project.getBasedir().toPath();
214+
final Path artifactPath = artifact.getFile().toPath();
215+
final String relativeArtifactPath = projectBasePath.relativize( artifactPath ).toString();
216+
217+
for ( String exclude : excludes )
218+
{
219+
if ( SelectorUtils.matchPath( exclude, relativeArtifactPath ) )
220+
{
221+
return true;
222+
}
223+
}
224+
225+
return false;
226+
}
227+
228+
}

0 commit comments

Comments
 (0)