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