View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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              // back out if Sigstore signatures found among artifacts
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              // sign relevant artifacts
79              ArrayList<Artifact> result = new ArrayList<>();
80              try (KeylessSigner signer = publicStaging
81                      ? KeylessSigner.builder().sigstoreStagingDefaults().build()
82                      : KeylessSigner.builder().sigstorePublicDefaults().build()) {
83                  for (Artifact artifact : artifacts) {
84                      if (signableArtifactPredicate.test(artifact)) {
85                          Path fileToSign = artifact.getPath();
86                          Path signatureTempFile = Files.createTempFile("signer-sigstore", "tmp");
87                          signatureTempFiles.add(signatureTempFile);
88  
89                          logger.debug("Signing " + artifact);
90                          long start = System.currentTimeMillis();
91                          Bundle bundle = signer.signFile(fileToSign);
92  
93                          X509Certificate cert = (X509Certificate)
94                                  bundle.getCertPath().getCertificates().get(0);
95                          long durationMinutes = Certificates.validity(cert, ChronoUnit.MINUTES);
96  
97                          logger.debug("  Fulcio certificate (valid for "
98                                  + durationMinutes
99                                  + " m) obtained for "
100                                 + cert.getSubjectAlternativeNames()
101                                         .iterator()
102                                         .next()
103                                         .get(1)
104                                 + " (by "
105                                 + FulcioOidHelper.getIssuerV2(cert)
106                                 + " IdP)");
107 
108                         FileUtils.writeFile(signatureTempFile, p -> Files.writeString(p, bundle.toJson()));
109 
110                         long duration = System.currentTimeMillis() - start;
111                         logger.debug("  > Rekor entry "
112                                 + bundle.getEntries().get(0).getLogIndex()
113                                 + " obtained in "
114                                 + duration
115                                 + " ms, saved to "
116                                 + signatureTempFile);
117 
118                         result.add(new SubArtifact(
119                                 artifact,
120                                 artifact.getClassifier(),
121                                 artifact.getExtension() + ARTIFACT_EXTENSION,
122                                 signatureTempFile.toFile()));
123                     }
124                 }
125             }
126             logger.info("Signed {} artifacts with Sigstore", result.size());
127             return result;
128         } catch (GeneralSecurityException e) {
129             throw new IllegalArgumentException("Preparation problem", e);
130         } catch (KeylessSignerException e) {
131             throw new IllegalStateException("Processing problem", e);
132         } catch (IOException e) {
133             throw new UncheckedIOException("IO problem", e);
134         }
135     }
136 
137     @Override
138     public void close() {
139         signatureTempFiles.forEach(p -> {
140             try {
141                 Files.deleteIfExists(p);
142             } catch (IOException e) {
143                 p.toFile().deleteOnExit();
144             }
145         });
146     }
147 }