1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.gpg.bc.internal;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.security.Security;
16 import java.text.MessageFormat;
17 import java.time.Instant;
18 import java.util.Arrays;
19 import java.util.Date;
20 import java.util.Iterator;
21 import java.util.Locale;
22
23 import org.bouncycastle.bcpg.sig.IssuerFingerprint;
24 import org.bouncycastle.jce.provider.BouncyCastleProvider;
25 import org.bouncycastle.openpgp.PGPCompressedData;
26 import org.bouncycastle.openpgp.PGPException;
27 import org.bouncycastle.openpgp.PGPPublicKey;
28 import org.bouncycastle.openpgp.PGPSignature;
29 import org.bouncycastle.openpgp.PGPSignatureList;
30 import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
31 import org.bouncycastle.openpgp.PGPUtil;
32 import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
33 import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
34 import org.bouncycastle.util.encoders.Hex;
35 import org.eclipse.jgit.annotations.NonNull;
36 import org.eclipse.jgit.annotations.Nullable;
37 import org.eclipse.jgit.api.errors.JGitInternalException;
38 import org.eclipse.jgit.lib.GpgConfig;
39 import org.eclipse.jgit.lib.GpgSignatureVerifier;
40 import org.eclipse.jgit.revwalk.RevCommit;
41 import org.eclipse.jgit.revwalk.RevObject;
42 import org.eclipse.jgit.revwalk.RevTag;
43 import org.eclipse.jgit.util.LRUMap;
44 import org.eclipse.jgit.util.RawParseUtils;
45 import org.eclipse.jgit.util.StringUtils;
46
47
48
49
50 public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier {
51
52 private static void registerBouncyCastleProviderIfNecessary() {
53 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
54 Security.addProvider(new BouncyCastleProvider());
55 }
56 }
57
58
59
60
61
62 public BouncyCastleGpgSignatureVerifier() {
63 registerBouncyCastleProviderIfNecessary();
64 }
65
66
67
68
69 private static final Object NO_KEY = new Object();
70
71 private LRUMap<String, Object> byFingerprint = new LRUMap<>(16, 200);
72
73 private LRUMap<String, Object> bySigner = new LRUMap<>(16, 200);
74
75 @Override
76 public String getName() {
77 return "bc";
78 }
79
80 @Override
81 @Nullable
82 public SignatureVerification verifySignature(@NonNull RevObject object,
83 @NonNull GpgConfig config) throws IOException {
84 if (object instanceof RevCommit) {
85 RevCommit commit = (RevCommit) object;
86 byte[] signatureData = commit.getRawGpgSignature();
87 if (signatureData == null) {
88 return null;
89 }
90 byte[] raw = commit.getRawBuffer();
91
92 byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
93 int start = RawParseUtils.headerStart(header, raw, 0);
94 if (start < 0) {
95 return null;
96 }
97 int end = RawParseUtils.headerEnd(raw, start);
98
99 start -= header.length + 1;
100
101 if (end < raw.length) {
102 end++;
103 }
104 byte[] data = new byte[raw.length - (end - start)];
105 System.arraycopy(raw, 0, data, 0, start);
106 System.arraycopy(raw, end, data, start, raw.length - end);
107 return verify(data, signatureData);
108 } else if (object instanceof RevTag) {
109 RevTag tag = (RevTag) object;
110 byte[] signatureData = tag.getRawGpgSignature();
111 if (signatureData == null) {
112 return null;
113 }
114 byte[] raw = tag.getRawBuffer();
115
116
117 byte[] data = Arrays.copyOfRange(raw, 0,
118 raw.length - signatureData.length);
119 return verify(data, signatureData);
120 }
121 return null;
122 }
123
124 static PGPSignature parseSignature(InputStream in)
125 throws IOException, PGPException {
126 try (InputStream sigIn = PGPUtil.getDecoderStream(in)) {
127 JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn);
128 Object obj = pgpFactory.nextObject();
129 if (obj instanceof PGPCompressedData) {
130 obj = new JcaPGPObjectFactory(
131 ((PGPCompressedData) obj).getDataStream()).nextObject();
132 }
133 if (obj instanceof PGPSignatureList) {
134 return ((PGPSignatureList) obj).get(0);
135 }
136 return null;
137 }
138 }
139
140 @Override
141 public SignatureVerification verify(byte[] data, byte[] signatureData)
142 throws IOException {
143 PGPSignature signature = null;
144 String fingerprint = null;
145 String signer = null;
146 String keyId = null;
147 try (InputStream sigIn = new ByteArrayInputStream(signatureData)) {
148 signature = parseSignature(sigIn);
149 if (signature != null) {
150
151 if (signature.hasSubpackets()) {
152 PGPSignatureSubpacketVector packets = signature
153 .getHashedSubPackets();
154 IssuerFingerprint fingerprintPacket = packets
155 .getIssuerFingerprint();
156 if (fingerprintPacket != null) {
157 fingerprint = Hex
158 .toHexString(fingerprintPacket.getFingerprint())
159 .toLowerCase(Locale.ROOT);
160 }
161 signer = packets.getSignerUserID();
162 if (signer != null) {
163 signer = BouncyCastleGpgSigner.extractSignerId(signer);
164 }
165 }
166 keyId = Long.toUnsignedString(signature.getKeyID(), 16)
167 .toLowerCase(Locale.ROOT);
168 } else {
169 throw new JGitInternalException(BCText.get().nonSignatureError);
170 }
171 } catch (PGPException e) {
172 throw new JGitInternalException(BCText.get().signatureParseError,
173 e);
174 }
175 Date signatureCreatedAt = signature.getCreationTime();
176 if (fingerprint == null && signer == null && keyId == null) {
177 return new VerificationResult(signatureCreatedAt, null, null, null,
178 false, false, TrustLevel.UNKNOWN,
179 BCText.get().signatureNoKeyInfo);
180 }
181 if (fingerprint != null && keyId != null
182 && !fingerprint.endsWith(keyId)) {
183 return new VerificationResult(signatureCreatedAt, signer, fingerprint,
184 null, false, false, TrustLevel.UNKNOWN,
185 MessageFormat.format(BCText.get().signatureInconsistent,
186 keyId, fingerprint));
187 }
188 if (fingerprint == null && keyId != null) {
189 fingerprint = keyId;
190 }
191
192 String keySpec = '<' + signer + '>';
193 Object cached = null;
194 PGPPublicKey publicKey = null;
195 try {
196 cached = byFingerprint.get(fingerprint);
197 if (cached != null) {
198 if (cached instanceof PGPPublicKey) {
199 publicKey = (PGPPublicKey) cached;
200 }
201 } else if (!StringUtils.isEmptyOrNull(signer)) {
202 cached = bySigner.get(signer);
203 if (cached != null) {
204 if (cached instanceof PGPPublicKey) {
205 publicKey = (PGPPublicKey) cached;
206 }
207 }
208 }
209 if (cached == null) {
210 publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint,
211 keySpec);
212 }
213 } catch (IOException | PGPException e) {
214 throw new JGitInternalException(
215 BCText.get().signatureKeyLookupError, e);
216 }
217 if (publicKey == null) {
218 if (cached == null) {
219 byFingerprint.put(fingerprint, NO_KEY);
220 byFingerprint.put(keyId, NO_KEY);
221 if (signer != null) {
222 bySigner.put(signer, NO_KEY);
223 }
224 }
225 return new VerificationResult(signatureCreatedAt, signer,
226 fingerprint, null, false, false, TrustLevel.UNKNOWN,
227 BCText.get().signatureNoPublicKey);
228 }
229 if (cached == null) {
230 byFingerprint.put(fingerprint, publicKey);
231 byFingerprint.put(keyId, publicKey);
232 if (signer != null) {
233 bySigner.put(signer, publicKey);
234 }
235 }
236 String user = null;
237 Iterator<String> userIds = publicKey.getUserIDs();
238 if (!StringUtils.isEmptyOrNull(signer)) {
239 while (userIds.hasNext()) {
240 String userId = userIds.next();
241 if (BouncyCastleGpgKeyLocator.containsSigningKey(userId,
242 keySpec)) {
243 user = userId;
244 break;
245 }
246 }
247 }
248 if (user == null) {
249 userIds = publicKey.getUserIDs();
250 if (userIds.hasNext()) {
251 user = userIds.next();
252 }
253 }
254 boolean expired = false;
255 long validFor = publicKey.getValidSeconds();
256 if (validFor > 0 && signatureCreatedAt != null) {
257 Instant expiredAt = publicKey.getCreationTime().toInstant()
258 .plusSeconds(validFor);
259 expired = expiredAt.isBefore(signatureCreatedAt.toInstant());
260 }
261
262
263
264
265 byte[] trustData = publicKey.getTrustData();
266 TrustLevel trust = parseGpgTrustPacket(trustData);
267 boolean verified = false;
268 try {
269 signature.init(
270 new JcaPGPContentVerifierBuilderProvider()
271 .setProvider(BouncyCastleProvider.PROVIDER_NAME),
272 publicKey);
273 signature.update(data);
274 verified = signature.verify();
275 } catch (PGPException e) {
276 throw new JGitInternalException(
277 BCText.get().signatureVerificationError, e);
278 }
279 return new VerificationResult(signatureCreatedAt, signer, fingerprint, user,
280 verified, expired, trust, null);
281 }
282
283 private TrustLevel parseGpgTrustPacket(byte[] packet) {
284 if (packet == null || packet.length < 6) {
285
286 return TrustLevel.UNKNOWN;
287 }
288 if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') {
289
290 return TrustLevel.UNKNOWN;
291 }
292 int trust = packet[0] & 0x0F;
293 switch (trust) {
294 case 0:
295 case 1:
296 case 2:
297 return TrustLevel.UNKNOWN;
298 case 3:
299 return TrustLevel.NEVER;
300 case 4:
301 return TrustLevel.MARGINAL;
302 case 5:
303 return TrustLevel.FULL;
304 case 6:
305 return TrustLevel.ULTIMATE;
306 default:
307 return TrustLevel.UNKNOWN;
308 }
309 }
310
311 @Override
312 public void clear() {
313 byFingerprint.clear();
314 bySigner.clear();
315 }
316
317 private static class VerificationResult implements SignatureVerification {
318
319 private final Date creationDate;
320
321 private final String signer;
322
323 private final String keyUser;
324
325 private final String fingerprint;
326
327 private final boolean verified;
328
329 private final boolean expired;
330
331 private final @NonNull TrustLevel trustLevel;
332
333 private final String message;
334
335 public VerificationResult(Date creationDate, String signer,
336 String fingerprint, String user, boolean verified,
337 boolean expired, @NonNull TrustLevel trust, String message) {
338 this.creationDate = creationDate;
339 this.signer = signer;
340 this.fingerprint = fingerprint;
341 this.keyUser = user;
342 this.verified = verified;
343 this.expired = expired;
344 this.trustLevel = trust;
345 this.message = message;
346 }
347
348 @Override
349 public Date getCreationDate() {
350 return creationDate;
351 }
352
353 @Override
354 public String getSigner() {
355 return signer;
356 }
357
358 @Override
359 public String getKeyUser() {
360 return keyUser;
361 }
362
363 @Override
364 public String getKeyFingerprint() {
365 return fingerprint;
366 }
367
368 @Override
369 public boolean isExpired() {
370 return expired;
371 }
372
373 @Override
374 public TrustLevel getTrustLevel() {
375 return trustLevel;
376 }
377
378 @Override
379 public String getMessage() {
380 return message;
381 }
382
383 @Override
384 public boolean getVerified() {
385 return verified;
386 }
387 }
388 }