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 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
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 }