1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.generator.sigstore;
20
21 import java.io.IOException;
22 import java.io.UncheckedIOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.security.GeneralSecurityException;
26 import java.security.cert.X509Certificate;
27 import java.time.temporal.ChronoUnit;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.function.Predicate;
34
35 import dev.sigstore.KeylessSigner;
36 import dev.sigstore.KeylessSignerException;
37 import dev.sigstore.bundle.Bundle;
38 import dev.sigstore.encryption.certificates.Certificates;
39 import dev.sigstore.trustroot.SigstoreConfigurationException;
40 import org.eclipse.aether.artifact.Artifact;
41 import org.eclipse.aether.generator.sigstore.internal.FulcioOidHelper;
42 import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
43 import org.eclipse.aether.spi.io.PathProcessor;
44 import org.eclipse.aether.util.artifact.SubArtifact;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 final class SigstoreSignatureArtifactGenerator implements ArtifactGenerator {
49 private static final String ARTIFACT_EXTENSION = ".sigstore.json";
50 private final Logger logger = LoggerFactory.getLogger(getClass());
51 private final PathProcessor pathProcessor;
52 private final List<Artifact> artifacts;
53 private final Predicate<Artifact> signableArtifactPredicate;
54 private final boolean publicStaging;
55 private final List<Path> signatureTempFiles;
56 private final AtomicBoolean closed;
57
58 SigstoreSignatureArtifactGenerator(
59 PathProcessor pathProcessor,
60 Collection<Artifact> artifacts,
61 Predicate<Artifact> signableArtifactPredicate,
62 boolean publicStaging) {
63 this.pathProcessor = pathProcessor;
64 this.artifacts = new ArrayList<>(artifacts);
65 this.signableArtifactPredicate = signableArtifactPredicate;
66 this.publicStaging = publicStaging;
67 this.signatureTempFiles = new ArrayList<>();
68 this.closed = new AtomicBoolean(false);
69 logger.debug("Created sigstore generator (publicStaging={})", publicStaging);
70 }
71
72 @Override
73 public String generatorId() {
74 return SigstoreSignatureArtifactGeneratorFactory.NAME;
75 }
76
77 @Override
78 public synchronized Collection<? extends Artifact> generate(Collection<? extends Artifact> generatedArtifacts) {
79 try {
80 artifacts.addAll(generatedArtifacts);
81
82
83
84
85 ArrayList<Artifact> artifactsToSign = new ArrayList<>();
86 for (Artifact artifact : artifacts) {
87 if (isSignatureArtifact(artifact)) {
88 continue;
89 }
90 if (!signableArtifactPredicate.test(artifact)) {
91 continue;
92 }
93 if (hasSignature(artifact)) {
94 logger.debug("Sigstore signature already present for {}, not signing it again", artifact);
95 continue;
96 }
97 artifactsToSign.add(artifact);
98 }
99 if (artifactsToSign.isEmpty()) {
100 logger.debug("Sigstore signatures are present for all signable artifacts, nothing to sign");
101 return Collections.emptyList();
102 }
103 if (artifacts.stream().anyMatch(this::isSignatureArtifact)) {
104 logger.info(
105 "Sigstore signatures are present for some artifacts only; signing the remaining {} artifact(s)",
106 artifactsToSign.size());
107 }
108
109
110 ArrayList<Artifact> result = new ArrayList<>();
111 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
112 Thread.currentThread().setContextClassLoader(KeylessSigner.class.getClassLoader());
113 try (KeylessSigner signer = publicStaging
114 ? KeylessSigner.builder().sigstoreStagingDefaults().build()
115 : KeylessSigner.builder().sigstorePublicDefaults().build()) {
116 for (Artifact artifact : artifactsToSign) {
117 Path fileToSign = artifact.getPath();
118 Path signatureTempFile = Files.createTempFile("signer-sigstore", "tmp");
119 signatureTempFiles.add(signatureTempFile);
120
121 logger.debug("Signing " + artifact);
122 long start = System.currentTimeMillis();
123 Bundle bundle = signer.signFile(fileToSign);
124
125 X509Certificate cert = (X509Certificate)
126 bundle.getCertPath().getCertificates().get(0);
127 long durationMinutes = Certificates.validity(cert, ChronoUnit.MINUTES);
128
129 logger.debug(" Fulcio certificate (valid for "
130 + durationMinutes
131 + " m) obtained for "
132 + cert.getSubjectAlternativeNames()
133 .iterator()
134 .next()
135 .get(1)
136 + " (by "
137 + FulcioOidHelper.getIssuerV2(cert)
138 + " IdP)");
139
140 pathProcessor.write(signatureTempFile, bundle.toJson());
141
142 long duration = System.currentTimeMillis() - start;
143 logger.debug(" > Rekor entry "
144 + bundle.getEntries().get(0).getLogIndex()
145 + " obtained in "
146 + duration
147 + " ms, saved to "
148 + signatureTempFile);
149
150 result.add(new SubArtifact(
151 artifact,
152 artifact.getClassifier(),
153 artifact.getExtension() + ARTIFACT_EXTENSION,
154 signatureTempFile.toFile()));
155 }
156 } finally {
157 Thread.currentThread().setContextClassLoader(originalClassLoader);
158 }
159 logger.info("Signed {} artifacts with Sigstore", result.size());
160 return result;
161 } catch (SigstoreConfigurationException e) {
162 throw new IllegalArgumentException("Configuration problem", e);
163 } catch (GeneralSecurityException e) {
164 throw new IllegalArgumentException("Preparation problem", e);
165 } catch (KeylessSignerException e) {
166 throw new IllegalStateException("Processing problem", e);
167 } catch (IOException e) {
168 throw new UncheckedIOException("IO problem", e);
169 }
170 }
171
172 private boolean isSignatureArtifact(Artifact artifact) {
173 return artifact.getExtension().endsWith(ARTIFACT_EXTENSION);
174 }
175
176 private boolean hasSignature(Artifact artifact) {
177 String signatureExtension = artifact.getExtension() + ARTIFACT_EXTENSION;
178 return artifacts.stream()
179 .anyMatch(a -> a.getExtension().equals(signatureExtension)
180 && a.getClassifier().equals(artifact.getClassifier())
181 && a.getArtifactId().equals(artifact.getArtifactId())
182 && a.getGroupId().equals(artifact.getGroupId())
183 && a.getVersion().equals(artifact.getVersion()));
184 }
185
186 @Override
187 public void close() {
188 if (closed.compareAndSet(false, true)) {
189 signatureTempFiles.forEach(p -> {
190 try {
191 Files.deleteIfExists(p);
192 } catch (IOException e) {
193 p.toFile().deleteOnExit();
194 }
195 });
196 }
197 }
198 }