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.function.Predicate;
32
33 import dev.sigstore.KeylessSigner;
34 import dev.sigstore.KeylessSignerException;
35 import dev.sigstore.bundle.Bundle;
36 import dev.sigstore.encryption.certificates.Certificates;
37 import org.eclipse.aether.artifact.Artifact;
38 import org.eclipse.aether.generator.sigstore.internal.FulcioOidHelper;
39 import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
40 import org.eclipse.aether.util.FileUtils;
41 import org.eclipse.aether.util.artifact.SubArtifact;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 final class SigstoreSignatureArtifactGenerator implements ArtifactGenerator {
46 private static final String ARTIFACT_EXTENSION = ".sigstore.json";
47 private final Logger logger = LoggerFactory.getLogger(getClass());
48 private final ArrayList<Artifact> artifacts;
49 private final Predicate<Artifact> signableArtifactPredicate;
50 private final boolean publicStaging;
51 private final ArrayList<Path> signatureTempFiles;
52
53 SigstoreSignatureArtifactGenerator(
54 Collection<Artifact> artifacts, Predicate<Artifact> signableArtifactPredicate, boolean publicStaging) {
55 this.artifacts = new ArrayList<>(artifacts);
56 this.signableArtifactPredicate = signableArtifactPredicate;
57 this.publicStaging = publicStaging;
58 this.signatureTempFiles = new ArrayList<>();
59 logger.debug("Created sigstore generator (publicStaging={})", publicStaging);
60 }
61
62 @Override
63 public String generatorId() {
64 return SigstoreSignatureArtifactGeneratorFactory.NAME;
65 }
66
67 @Override
68 public Collection<? extends Artifact> generate(Collection<? extends Artifact> generatedArtifacts) {
69 try {
70 artifacts.addAll(generatedArtifacts);
71
72
73 if (artifacts.stream().anyMatch(a -> a.getExtension().endsWith(ARTIFACT_EXTENSION))) {
74 logger.debug("Sigstore signatures are present among artifacts, bailing out");
75 return Collections.emptyList();
76 }
77
78
79 ArrayList<Artifact> result = new ArrayList<>();
80 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
81 Thread.currentThread().setContextClassLoader(KeylessSigner.class.getClassLoader());
82 try (KeylessSigner signer = publicStaging
83 ? KeylessSigner.builder().sigstoreStagingDefaults().build()
84 : KeylessSigner.builder().sigstorePublicDefaults().build()) {
85 for (Artifact artifact : artifacts) {
86 if (signableArtifactPredicate.test(artifact)) {
87 Path fileToSign = artifact.getPath();
88 Path signatureTempFile = Files.createTempFile("signer-sigstore", "tmp");
89 signatureTempFiles.add(signatureTempFile);
90
91 logger.debug("Signing " + artifact);
92 long start = System.currentTimeMillis();
93 Bundle bundle = signer.signFile(fileToSign);
94
95 X509Certificate cert = (X509Certificate)
96 bundle.getCertPath().getCertificates().get(0);
97 long durationMinutes = Certificates.validity(cert, ChronoUnit.MINUTES);
98
99 logger.debug(" Fulcio certificate (valid for "
100 + durationMinutes
101 + " m) obtained for "
102 + cert.getSubjectAlternativeNames()
103 .iterator()
104 .next()
105 .get(1)
106 + " (by "
107 + FulcioOidHelper.getIssuerV2(cert)
108 + " IdP)");
109
110 FileUtils.writeFile(signatureTempFile, p -> Files.writeString(p, bundle.toJson()));
111
112 long duration = System.currentTimeMillis() - start;
113 logger.debug(" > Rekor entry "
114 + bundle.getEntries().get(0).getLogIndex()
115 + " obtained in "
116 + duration
117 + " ms, saved to "
118 + signatureTempFile);
119
120 result.add(new SubArtifact(
121 artifact,
122 artifact.getClassifier(),
123 artifact.getExtension() + ARTIFACT_EXTENSION,
124 signatureTempFile.toFile()));
125 }
126 }
127 } finally {
128 Thread.currentThread().setContextClassLoader(originalClassLoader);
129 }
130 logger.info("Signed {} artifacts with Sigstore", result.size());
131 return result;
132 } catch (GeneralSecurityException e) {
133 throw new IllegalArgumentException("Preparation problem", e);
134 } catch (KeylessSignerException e) {
135 throw new IllegalStateException("Processing problem", e);
136 } catch (IOException e) {
137 throw new UncheckedIOException("IO problem", e);
138 }
139 }
140
141 @Override
142 public void close() {
143 signatureTempFiles.forEach(p -> {
144 try {
145 Files.deleteIfExists(p);
146 } catch (IOException e) {
147 p.toFile().deleteOnExit();
148 }
149 });
150 }
151 }