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.gnupg;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.UncheckedIOException;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.function.Predicate;
31  
32  import org.bouncycastle.bcpg.ArmoredOutputStream;
33  import org.bouncycastle.bcpg.BCPGOutputStream;
34  import org.bouncycastle.bcpg.HashAlgorithmTags;
35  import org.bouncycastle.openpgp.PGPException;
36  import org.bouncycastle.openpgp.PGPPrivateKey;
37  import org.bouncycastle.openpgp.PGPSecretKey;
38  import org.bouncycastle.openpgp.PGPSignature;
39  import org.bouncycastle.openpgp.PGPSignatureGenerator;
40  import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
41  import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
42  import org.eclipse.aether.artifact.Artifact;
43  import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
44  import org.eclipse.aether.util.artifact.SubArtifact;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  final class GnupgSignatureArtifactGenerator implements ArtifactGenerator {
49      private static final String ARTIFACT_EXTENSION = ".asc";
50      private final Logger logger = LoggerFactory.getLogger(getClass());
51      private final ArrayList<Artifact> artifacts;
52      private final Predicate<Artifact> signableArtifactPredicate;
53      private final PGPSecretKey secretKey;
54      private final PGPPrivateKey privateKey;
55      private final PGPSignatureSubpacketVector hashSubPackets;
56      private final String keyInfo;
57      private final ArrayList<Path> signatureTempFiles;
58  
59      GnupgSignatureArtifactGenerator(
60              Collection<Artifact> artifacts,
61              Predicate<Artifact> signableArtifactPredicate,
62              PGPSecretKey secretKey,
63              PGPPrivateKey privateKey,
64              PGPSignatureSubpacketVector hashSubPackets,
65              String keyInfo) {
66          this.artifacts = new ArrayList<>(artifacts);
67          this.signableArtifactPredicate = signableArtifactPredicate;
68          this.secretKey = secretKey;
69          this.privateKey = privateKey;
70          this.hashSubPackets = hashSubPackets;
71          this.keyInfo = keyInfo;
72          this.signatureTempFiles = new ArrayList<>();
73          logger.debug("Created generator using key {}", keyInfo);
74      }
75  
76      @Override
77      public String generatorId() {
78          return GnupgSignatureArtifactGeneratorFactory.NAME;
79      }
80  
81      @Override
82      public Collection<? extends Artifact> generate(Collection<? extends Artifact> generatedArtifacts) {
83          try {
84              artifacts.addAll(generatedArtifacts);
85  
86              // back out if PGP signatures found among artifacts
87              if (artifacts.stream().anyMatch(a -> a.getExtension().endsWith(ARTIFACT_EXTENSION))) {
88                  logger.debug("GPG signatures are present among artifacts, bailing out");
89                  return Collections.emptyList();
90              }
91  
92              // sign relevant artifacts
93              ArrayList<Artifact> result = new ArrayList<>();
94              for (Artifact artifact : artifacts) {
95                  if (signableArtifactPredicate.test(artifact)) {
96                      Path signatureTempFile = Files.createTempFile("signer-pgp", "tmp");
97                      signatureTempFiles.add(signatureTempFile);
98                      try (InputStream artifactContent = Files.newInputStream(artifact.getPath());
99                              OutputStream signatureContent = Files.newOutputStream(signatureTempFile)) {
100                         sign(artifactContent, signatureContent);
101                     }
102                     result.add(new SubArtifact(
103                             artifact,
104                             artifact.getClassifier(),
105                             artifact.getExtension() + ARTIFACT_EXTENSION,
106                             signatureTempFile.toFile()));
107                 }
108             }
109             logger.debug("Signed {} artifacts with key {}", result.size(), keyInfo);
110             return result;
111         } catch (IOException e) {
112             throw new UncheckedIOException(e);
113         }
114     }
115 
116     @Override
117     public void close() {
118         signatureTempFiles.forEach(p -> {
119             try {
120                 Files.deleteIfExists(p);
121             } catch (IOException e) {
122                 p.toFile().deleteOnExit();
123             }
124         });
125     }
126 
127     private void sign(InputStream content, OutputStream signature) throws IOException {
128         PGPSignatureGenerator sGen = new PGPSignatureGenerator(
129                 new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA512));
130         try {
131             sGen.init(PGPSignature.BINARY_DOCUMENT, privateKey);
132             sGen.setHashedSubpackets(hashSubPackets);
133             int len;
134             byte[] buffer = new byte[8 * 1024];
135             while ((len = content.read(buffer)) >= 0) {
136                 sGen.update(buffer, 0, len);
137             }
138             try (BCPGOutputStream bcpgOutputStream = new BCPGOutputStream(new ArmoredOutputStream(signature))) {
139                 sGen.generate().encode(bcpgOutputStream);
140             }
141         } catch (PGPException e) {
142             throw new IllegalStateException(e);
143         }
144     }
145 }