1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
91
92
93 ArrayList<Artifact> artifactsToSign = new ArrayList<>();
94 for (Artifact artifact : artifacts) {
95 if (isSignatureArtifact(artifact)) {
96 continue;
97 }
98 if (!signableArtifactPredicate.test(artifact)) {
99 continue;
100 }
101 if (hasSignature(artifact)) {
102 logger.debug("GPG signature already present for {}, not signing it again", artifact);
103 continue;
104 }
105 artifactsToSign.add(artifact);
106 }
107 if (artifactsToSign.isEmpty()) {
108 logger.debug("GPG signatures are present for all signable artifacts, nothing to sign");
109 return Collections.emptyList();
110 }
111 if (artifacts.stream().anyMatch(this::isSignatureArtifact)) {
112 logger.info(
113 "GPG signatures are present for some artifacts only; signing the remaining {} artifact(s) with key {}",
114 artifactsToSign.size(),
115 keyInfo);
116 }
117
118
119 ArrayList<Artifact> result = new ArrayList<>();
120 for (Artifact artifact : artifactsToSign) {
121 Path signatureTempFile = Files.createTempFile("signer-pgp", "tmp");
122 signatureTempFiles.add(signatureTempFile);
123 try (InputStream artifactContent = Files.newInputStream(artifact.getPath());
124 OutputStream signatureContent = Files.newOutputStream(signatureTempFile)) {
125 sign(artifactContent, signatureContent);
126 }
127 result.add(new SubArtifact(
128 artifact,
129 artifact.getClassifier(),
130 artifact.getExtension() + ARTIFACT_EXTENSION,
131 signatureTempFile.toFile()));
132 }
133 logger.debug("Signed {} artifacts with key {}", result.size(), keyInfo);
134 return result;
135 } catch (IOException e) {
136 throw new UncheckedIOException(e);
137 }
138 }
139
140 private boolean isSignatureArtifact(Artifact artifact) {
141 return artifact.getExtension().endsWith(ARTIFACT_EXTENSION);
142 }
143
144 private boolean hasSignature(Artifact artifact) {
145 String signatureExtension = artifact.getExtension() + ARTIFACT_EXTENSION;
146 return artifacts.stream()
147 .anyMatch(a -> a.getExtension().equals(signatureExtension)
148 && a.getClassifier().equals(artifact.getClassifier())
149 && a.getArtifactId().equals(artifact.getArtifactId())
150 && a.getGroupId().equals(artifact.getGroupId())
151 && a.getVersion().equals(artifact.getVersion()));
152 }
153
154 @Override
155 public void close() {
156 if (closed.compareAndSet(false, true)) {
157 signatureTempFiles.forEach(p -> {
158 try {
159 Files.deleteIfExists(p);
160 } catch (IOException e) {
161 p.toFile().deleteOnExit();
162 }
163 });
164 }
165 }
166
167 private void sign(InputStream content, OutputStream signature) throws IOException {
168 PGPSignatureGenerator sGen = new PGPSignatureGenerator(
169 new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA512));
170 try {
171 sGen.init(PGPSignature.BINARY_DOCUMENT, privateKey);
172 sGen.setHashedSubpackets(hashSubPackets);
173 int len;
174 byte[] buffer = new byte[8 * 1024];
175 while ((len = content.read(buffer)) >= 0) {
176 sGen.update(buffer, 0, len);
177 }
178 try (BCPGOutputStream bcpgOutputStream = new BCPGOutputStream(new ArmoredOutputStream(signature))) {
179 sGen.generate().encode(bcpgOutputStream);
180 }
181 } catch (PGPException e) {
182 throw new IllegalStateException(e);
183 }
184 }
185 }