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.nio.file.Files;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.LinkedHashMap;
28  
29  import org.bouncycastle.util.encoders.DecoderException;
30  import org.eclipse.aether.ConfigurationProperties;
31  import org.eclipse.aether.DefaultRepositorySystemSession;
32  import org.eclipse.aether.RepositorySystemSession;
33  import org.eclipse.aether.artifact.Artifact;
34  import org.eclipse.aether.artifact.DefaultArtifact;
35  import org.eclipse.aether.deployment.DeployRequest;
36  import org.eclipse.aether.generator.gnupg.loaders.*;
37  import org.eclipse.aether.internal.test.util.TestUtils;
38  import org.eclipse.aether.spi.artifact.ArtifactPredicate;
39  import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory;
40  import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
41  import org.junit.jupiter.api.Disabled;
42  import org.junit.jupiter.api.Test;
43  
44  import static org.junit.jupiter.api.Assertions.*;
45  import static org.mockito.ArgumentMatchers.any;
46  import static org.mockito.Mockito.mock;
47  import static org.mockito.Mockito.when;
48  
49  public class GpgSignerFactoryTest {
50      private GnupgSignatureArtifactGeneratorFactory createFactory() throws Exception {
51          Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire
52  
53          ArtifactPredicate artifactPredicate = mock(ArtifactPredicate.class);
54          when(artifactPredicate.hasChecksums(any(Artifact.class))).thenAnswer(a -> {
55              Artifact artifact = (Artifact) a.getArguments()[0];
56              return artifact != null
57                      && !artifact.getExtension().endsWith(".sha1")
58                      && !artifact.getExtension().endsWith(".md5");
59          });
60          ArtifactPredicateFactory artifactPredicateFactory = mock(ArtifactPredicateFactory.class);
61          when(artifactPredicateFactory.newInstance(any(RepositorySystemSession.class)))
62                  .thenReturn(artifactPredicate);
63  
64          // order matters
65          LinkedHashMap<String, GnupgSignatureArtifactGeneratorFactory.Loader> loaders = new LinkedHashMap<>();
66          loaders.put(GpgEnvLoader.NAME, new GpgEnvLoader());
67          loaders.put(GpgConfLoader.NAME, new GpgConfLoader());
68          loaders.put(GpgAgentPasswordLoader.NAME, new GpgAgentPasswordLoader());
69  
70          return new GnupgSignatureArtifactGeneratorFactory(artifactPredicateFactory, loaders);
71      }
72  
73      private RepositorySystemSession createSession(Path keyFilePath, String keyPass, boolean interactive) {
74          return createSession(keyFilePath, keyPass, null, interactive);
75      }
76  
77      private RepositorySystemSession createSession(
78              Path keyFilePath, String keyPass, String keyFingerprint, boolean interactive) {
79          DefaultRepositorySystemSession session = TestUtils.newSession();
80          session.setConfigProperty(GnupgConfigurationKeys.CONFIG_PROP_ENABLED, Boolean.TRUE);
81          session.setConfigProperty(GnupgConfigurationKeys.CONFIG_PROP_KEY_FILE_PATH, keyFilePath.toString());
82          if (keyPass != null) {
83              session.setConfigProperty("env." + GnupgConfigurationKeys.RESOLVER_GPG_KEY_PASS, keyPass);
84          }
85          if (keyFingerprint != null) {
86              session.setConfigProperty("env." + GnupgConfigurationKeys.RESOLVER_GPG_KEY_FINGERPRINT, keyFingerprint);
87          }
88          session.setConfigProperty(ConfigurationProperties.INTERACTIVE, interactive);
89          return session;
90      }
91  
92      @Test
93      void doNotSignNonRelevantArtifacts() throws Exception {
94          Path keyFile =
95                  Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
96          String keyPass = "TheBigSecret";
97          GnupgSignatureArtifactGeneratorFactory factory = createFactory();
98          try (ArtifactGenerator signer =
99                  factory.newInstance(createSession(keyFile, keyPass, false), new DeployRequest())) {
100             assertNotNull(signer);
101 
102             Collection<? extends Artifact> signatures = signer.generate(Arrays.asList(
103                     new DefaultArtifact("org.apache.maven.resolver:test:jar.sha1:1.0"),
104                     new DefaultArtifact("org.apache.maven.resolver:test:jar.md5:1.0")));
105 
106             assertEquals(0, signatures.size());
107         }
108     }
109 
110     @Test
111     void doSignAllRelevantArtifacts() throws Exception {
112         Path keyFile =
113                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
114         String keyPass = "TheBigSecret";
115         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
116         try (ArtifactGenerator signer =
117                 factory.newInstance(createSession(keyFile, keyPass, false), new DeployRequest())) {
118             assertNotNull(signer);
119             Path irrelevant = Paths.get("src/test/resources/gpg-signing/artifact.txt");
120 
121             Collection<? extends Artifact> signatures = signer.generate(Arrays.asList(
122                     new DefaultArtifact("org.apache.maven.resolver:test:jar:1.0").setPath(irrelevant),
123                     new DefaultArtifact("org.apache.maven.resolver:test:jar.sha1:1.0").setPath(irrelevant),
124                     new DefaultArtifact("org.apache.maven.resolver:test:jar:source:1.0").setPath(irrelevant),
125                     new DefaultArtifact("org.apache.maven.resolver:test:jar.md5:source:1.0").setPath(irrelevant),
126                     new DefaultArtifact("org.apache.maven.resolver:test:foo:1.0").setPath(irrelevant)));
127 
128             assertEquals(3, signatures.size());
129 
130             assertTrue(signatures.stream()
131                     .anyMatch(a -> "".equals(a.getClassifier()) && "jar.asc".equals(a.getExtension())));
132             assertTrue(signatures.stream()
133                     .anyMatch(a -> "source".equals(a.getClassifier()) && "jar.asc".equals(a.getExtension())));
134             assertTrue(signatures.stream()
135                     .anyMatch(a -> "".equals(a.getClassifier()) && "foo.asc".equals(a.getExtension())));
136         }
137     }
138 
139     @Test
140     void doNotSignIfGpgSignatureAlreadyPresent() throws Exception {
141         Path keyFile =
142                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
143         String keyPass = "TheBigSecret";
144         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
145         try (ArtifactGenerator signer =
146                 factory.newInstance(createSession(keyFile, keyPass, false), new DeployRequest())) {
147             assertNotNull(signer);
148 
149             Collection<? extends Artifact> signatures = signer.generate(Arrays.asList(
150                     new DefaultArtifact("org.apache.maven.resolver:test:jar:1.0"),
151                     new DefaultArtifact("org.apache.maven.resolver:test:jar.asc:1.0")));
152 
153             assertEquals(0, signatures.size());
154         }
155     }
156 
157     @Test
158     void signNonInteractive() throws Exception {
159         Path keyFile =
160                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
161         String keyPass = "TheBigSecret";
162         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
163         try (ArtifactGenerator signer =
164                 factory.newInstance(createSession(keyFile, keyPass, false), new DeployRequest())) {
165             assertNotNull(signer);
166             Path artifactPath = Paths.get("src/test/resources/gpg-signing/artifact.txt");
167             Collection<? extends Artifact> signatures = signer.generate(Collections.singleton(
168                     new DefaultArtifact("org.apache.maven.resolver:test:1.0").setPath(artifactPath)));
169 
170             // one signature expected for one relevant artifact
171             assertEquals(1, signatures.size());
172             Path signaturePath = signatures.iterator().next().getPath();
173 
174             // cannot assert file size due OS differences, so just count the lines instead: those should be same
175             assertEquals(8, Files.lines(signaturePath).count());
176 
177             // TODO: validate the signature
178         }
179     }
180 
181     @Test
182     void signNonInteractiveWithSelectedKey() throws Exception {
183         Path keyFile =
184                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
185         String keyPass = "TheBigSecret";
186         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
187         try (ArtifactGenerator signer = factory.newInstance(
188                 createSession(keyFile, keyPass, "6D27BDA430672EC700BA7DBD0A32C01AE8785B6E", false),
189                 new DeployRequest())) {
190             assertNotNull(signer);
191             Path artifactPath = Paths.get("src/test/resources/gpg-signing/artifact.txt");
192             Collection<? extends Artifact> signatures = signer.generate(Collections.singleton(
193                     new DefaultArtifact("org.apache.maven.resolver:test:1.0").setPath(artifactPath)));
194 
195             // one signature expected for one relevant artifact
196             assertEquals(1, signatures.size());
197             Path signaturePath = signatures.iterator().next().getPath();
198 
199             // cannot assert file size due OS differences, so just count the lines instead: those should be same
200             assertEquals(8, Files.lines(signaturePath).count());
201 
202             // TODO: validate the signature
203         }
204     }
205 
206     @Test
207     void signNonInteractiveWithSelectedKeyWrongFingerprint() throws Exception {
208         Path keyFile =
209                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
210         String keyPass = "TheBigSecret";
211         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
212 
213         assertThrows(
214                 IllegalArgumentException.class,
215                 () -> factory.newInstance(
216                         createSession(keyFile, keyPass, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", false),
217                         new DeployRequest()));
218     }
219 
220     @Test
221     void signNonInteractiveWithSelectedKeyMalformedFingerprint1() throws Exception {
222         Path keyFile =
223                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
224         String keyPass = "TheBigSecret";
225         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
226 
227         assertThrows(
228                 IllegalArgumentException.class,
229                 () -> factory.newInstance(createSession(keyFile, keyPass, "abcd", false), new DeployRequest()));
230     }
231 
232     @Test
233     void signNonInteractiveWithSelectedKeyMalformedFingerprint2() throws Exception {
234         Path keyFile =
235                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
236         String keyPass = "TheBigSecret";
237         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
238 
239         assertThrows(
240                 DecoderException.class,
241                 () -> factory.newInstance(
242                         createSession(keyFile, keyPass, "ZAZUFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", false),
243                         new DeployRequest()));
244     }
245 
246     /**
247      * This test is disabled by default as it is interactive and would use Gpg Agent.
248      * <p>
249      * To try it out: remove the {@code Disabled} annotation and run it from IDE. When agent asks for passkey,
250      * it will be "TheBigSecret". Subsequent runs will NOT ASK ANYTHING as agent will cache the passkey.
251      */
252     @Test
253     @Disabled
254     void signInteractive() throws Exception {
255         Path keyFile =
256                 Paths.get("src/test/resources/gpg-signing/gpg-secret.key").toAbsolutePath();
257         GnupgSignatureArtifactGeneratorFactory factory = createFactory();
258         try (ArtifactGenerator signer = factory.newInstance(createSession(keyFile, null, true), new DeployRequest())) {
259             assertNotNull(signer);
260             Path artifactPath = Paths.get("src/test/resources/gpg-signing/artifact.txt");
261             Collection<? extends Artifact> signatures = signer.generate(Collections.singleton(
262                     new DefaultArtifact("org.apache.maven.resolver:test:1.0").setPath(artifactPath)));
263 
264             // one signature expected for one relevant artifact
265             assertEquals(1, signatures.size());
266             Path signaturePath = signatures.iterator().next().getPath();
267 
268             // cannot assert file size due OS differences, so just count the lines instead: those should be same
269             assertEquals(8, Files.lines(signaturePath).count());
270 
271             // TODO: validate the signature
272         }
273     }
274 }