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