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 javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.IOException;
27  import java.io.UncheckedIOException;
28  import java.time.LocalDateTime;
29  import java.time.ZoneId;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Iterator;
33  import java.util.Map;
34  import java.util.function.Predicate;
35  
36  import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
37  import org.bouncycastle.openpgp.PGPException;
38  import org.bouncycastle.openpgp.PGPPrivateKey;
39  import org.bouncycastle.openpgp.PGPSecretKey;
40  import org.bouncycastle.openpgp.PGPSecretKeyRing;
41  import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
42  import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
43  import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
44  import org.bouncycastle.openpgp.PGPUtil;
45  import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
46  import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
47  import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
48  import org.bouncycastle.util.encoders.Hex;
49  import org.eclipse.aether.RepositorySystemSession;
50  import org.eclipse.aether.artifact.Artifact;
51  import org.eclipse.aether.deployment.DeployRequest;
52  import org.eclipse.aether.installation.InstallRequest;
53  import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory;
54  import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
55  import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory;
56  import org.eclipse.aether.util.ConfigUtils;
57  
58  @Singleton
59  @Named(GnupgSignatureArtifactGeneratorFactory.NAME)
60  public final class GnupgSignatureArtifactGeneratorFactory implements ArtifactGeneratorFactory {
61  
62      public interface Loader {
63          /**
64           * Returns the key ring material, or {@code null}.
65           */
66          default byte[] loadKeyRingMaterial(RepositorySystemSession session) throws IOException {
67              return null;
68          }
69  
70          /**
71           * Returns the key fingerprint, or {@code null}.
72           */
73          default byte[] loadKeyFingerprint(RepositorySystemSession session) throws IOException {
74              return null;
75          }
76  
77          /**
78           * Returns the key password, or {@code null}.
79           */
80          default char[] loadPassword(RepositorySystemSession session, byte[] fingerprint) throws IOException {
81              return null;
82          }
83      }
84  
85      public static final String NAME = "gnupg";
86  
87      private final ArtifactPredicateFactory artifactPredicateFactory;
88      private final Map<String, Loader> loaders;
89  
90      @Inject
91      public GnupgSignatureArtifactGeneratorFactory(
92              ArtifactPredicateFactory artifactPredicateFactory, Map<String, Loader> loaders) {
93          this.artifactPredicateFactory = artifactPredicateFactory;
94          this.loaders = loaders;
95      }
96  
97      @Override
98      public ArtifactGenerator newInstance(RepositorySystemSession session, InstallRequest request) {
99          return null;
100     }
101 
102     @Override
103     public ArtifactGenerator newInstance(RepositorySystemSession session, DeployRequest request) {
104         final boolean enabled = ConfigUtils.getBoolean(
105                 session, GnupgConfigurationKeys.DEFAULT_ENABLED, GnupgConfigurationKeys.CONFIG_PROP_ENABLED);
106         if (!enabled) {
107             return null;
108         }
109 
110         try {
111             return doCreateArtifactGenerator(
112                     session, request.getArtifacts(), artifactPredicateFactory.newInstance(session)::hasChecksums);
113         } catch (IOException e) {
114             throw new UncheckedIOException(e);
115         }
116     }
117 
118     @Override
119     public float getPriority() {
120         return 100;
121     }
122 
123     private GnupgSignatureArtifactGenerator doCreateArtifactGenerator(
124             RepositorySystemSession session, Collection<Artifact> artifacts, Predicate<Artifact> artifactPredicate)
125             throws IOException {
126 
127         byte[] keyRingMaterial = null;
128         for (Loader loader : loaders.values()) {
129             keyRingMaterial = loader.loadKeyRingMaterial(session);
130             if (keyRingMaterial != null) {
131                 break;
132             }
133         }
134         if (keyRingMaterial == null) {
135             throw new IllegalArgumentException("Key ring material not found");
136         }
137 
138         byte[] fingerprint = null;
139         for (Loader loader : loaders.values()) {
140             fingerprint = loader.loadKeyFingerprint(session);
141             if (fingerprint != null) {
142                 break;
143             }
144         }
145 
146         try {
147             PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(
148                     PGPUtil.getDecoderStream(new ByteArrayInputStream(keyRingMaterial)),
149                     new BcKeyFingerprintCalculator());
150 
151             PGPSecretKey secretKey = null;
152             for (PGPSecretKeyRing ring : pgpSecretKeyRingCollection) {
153                 for (PGPSecretKey key : ring) {
154                     if (!key.isPrivateKeyEmpty()) {
155                         if (fingerprint == null || Arrays.equals(fingerprint, key.getFingerprint())) {
156                             secretKey = key;
157                             break;
158                         }
159                     }
160                 }
161             }
162             if (secretKey == null) {
163                 throw new IllegalArgumentException("Secret key not found");
164             }
165             if (secretKey.isPrivateKeyEmpty()) {
166                 throw new IllegalArgumentException("Private key not found in Secret key");
167             }
168 
169             long validSeconds = secretKey.getPublicKey().getValidSeconds();
170             if (validSeconds > 0) {
171                 LocalDateTime expireDateTime = secretKey
172                         .getPublicKey()
173                         .getCreationTime()
174                         .toInstant()
175                         .atZone(ZoneId.systemDefault())
176                         .toLocalDateTime()
177                         .plusSeconds(validSeconds);
178                 if (LocalDateTime.now().isAfter(expireDateTime)) {
179                     throw new IllegalArgumentException("Secret key expired at: " + expireDateTime);
180                 }
181             }
182 
183             char[] keyPassword = null;
184             final boolean keyPassNeeded = secretKey.getKeyEncryptionAlgorithm() != SymmetricKeyAlgorithmTags.NULL;
185             if (keyPassNeeded) {
186                 for (Loader loader : loaders.values()) {
187                     keyPassword = loader.loadPassword(session, secretKey.getFingerprint());
188                     if (keyPassword != null) {
189                         break;
190                     }
191                 }
192                 if (keyPassword == null) {
193                     throw new IllegalArgumentException("Secret key is encrypted but no key password provided");
194                 }
195             }
196 
197             PGPPrivateKey privateKey = secretKey.extractPrivateKey(
198                     new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(keyPassword));
199             if (keyPassword != null) {
200                 Arrays.fill(keyPassword, ' ');
201             }
202             PGPSignatureSubpacketGenerator subPacketGenerator = new PGPSignatureSubpacketGenerator();
203             subPacketGenerator.setIssuerFingerprint(false, secretKey);
204             PGPSignatureSubpacketVector hashSubPackets = subPacketGenerator.generate();
205 
206             return new GnupgSignatureArtifactGenerator(
207                     artifacts, artifactPredicate, secretKey, privateKey, hashSubPackets, getKeyInfo(secretKey));
208         } catch (PGPException | IOException e) {
209             throw new IllegalStateException(e);
210         }
211     }
212 
213     private static String getKeyInfo(PGPSecretKey secretKey) {
214         Iterator<String> userIds = secretKey.getPublicKey().getUserIDs();
215         if (userIds.hasNext()) {
216             return userIds.next();
217         }
218         return Hex.toHexString(secretKey.getPublicKey().getFingerprint());
219     }
220 }