You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BouncyCastleGpgSignatureVerifier.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /*
  2. * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.gpg.bc.internal;
  11. import java.io.ByteArrayInputStream;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.security.Security;
  15. import java.text.MessageFormat;
  16. import java.time.Instant;
  17. import java.util.Arrays;
  18. import java.util.Date;
  19. import java.util.Iterator;
  20. import java.util.Locale;
  21. import org.bouncycastle.bcpg.sig.IssuerFingerprint;
  22. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  23. import org.bouncycastle.openpgp.PGPCompressedData;
  24. import org.bouncycastle.openpgp.PGPException;
  25. import org.bouncycastle.openpgp.PGPPublicKey;
  26. import org.bouncycastle.openpgp.PGPSignature;
  27. import org.bouncycastle.openpgp.PGPSignatureList;
  28. import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
  29. import org.bouncycastle.openpgp.PGPUtil;
  30. import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
  31. import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
  32. import org.bouncycastle.util.encoders.Hex;
  33. import org.eclipse.jgit.annotations.NonNull;
  34. import org.eclipse.jgit.annotations.Nullable;
  35. import org.eclipse.jgit.api.errors.JGitInternalException;
  36. import org.eclipse.jgit.lib.GpgConfig;
  37. import org.eclipse.jgit.lib.GpgSignatureVerifier;
  38. import org.eclipse.jgit.revwalk.RevCommit;
  39. import org.eclipse.jgit.revwalk.RevObject;
  40. import org.eclipse.jgit.revwalk.RevTag;
  41. import org.eclipse.jgit.util.LRUMap;
  42. import org.eclipse.jgit.util.RawParseUtils;
  43. import org.eclipse.jgit.util.StringUtils;
  44. /**
  45. * A {@link GpgSignatureVerifier} to verify GPG signatures using BouncyCastle.
  46. */
  47. public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier {
  48. private static void registerBouncyCastleProviderIfNecessary() {
  49. if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
  50. Security.addProvider(new BouncyCastleProvider());
  51. }
  52. }
  53. /**
  54. * Creates a new instance and registers the BouncyCastle security provider
  55. * if needed.
  56. */
  57. public BouncyCastleGpgSignatureVerifier() {
  58. registerBouncyCastleProviderIfNecessary();
  59. }
  60. // To support more efficient signature verification of multiple objects we
  61. // cache public keys once found in a LRU cache.
  62. private static final Object NO_KEY = new Object();
  63. private LRUMap<String, Object> byFingerprint = new LRUMap<>(16, 200);
  64. private LRUMap<String, Object> bySigner = new LRUMap<>(16, 200);
  65. @Override
  66. public String getName() {
  67. return "bc"; //$NON-NLS-1$
  68. }
  69. @Override
  70. @Nullable
  71. public SignatureVerification verifySignature(@NonNull RevObject object,
  72. @NonNull GpgConfig config) throws IOException {
  73. if (object instanceof RevCommit) {
  74. RevCommit commit = (RevCommit) object;
  75. byte[] signatureData = commit.getRawGpgSignature();
  76. if (signatureData == null) {
  77. return null;
  78. }
  79. byte[] raw = commit.getRawBuffer();
  80. // Now remove the GPG signature
  81. byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
  82. int start = RawParseUtils.headerStart(header, raw, 0);
  83. if (start < 0) {
  84. return null;
  85. }
  86. int end = RawParseUtils.headerEnd(raw, start);
  87. // start is at the beginning of the header's content
  88. start -= header.length + 1;
  89. // end is on the terminating LF; we need to skip that, too
  90. if (end < raw.length) {
  91. end++;
  92. }
  93. byte[] data = new byte[raw.length - (end - start)];
  94. System.arraycopy(raw, 0, data, 0, start);
  95. System.arraycopy(raw, end, data, start, raw.length - end);
  96. return verify(data, signatureData);
  97. } else if (object instanceof RevTag) {
  98. RevTag tag = (RevTag) object;
  99. byte[] signatureData = tag.getRawGpgSignature();
  100. if (signatureData == null) {
  101. return null;
  102. }
  103. byte[] raw = tag.getRawBuffer();
  104. // The signature is just tacked onto the end of the message, which
  105. // is last in the buffer.
  106. byte[] data = Arrays.copyOfRange(raw, 0,
  107. raw.length - signatureData.length);
  108. return verify(data, signatureData);
  109. }
  110. return null;
  111. }
  112. static PGPSignature parseSignature(InputStream in)
  113. throws IOException, PGPException {
  114. try (InputStream sigIn = PGPUtil.getDecoderStream(in)) {
  115. JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn);
  116. Object obj = pgpFactory.nextObject();
  117. if (obj instanceof PGPCompressedData) {
  118. obj = new JcaPGPObjectFactory(
  119. ((PGPCompressedData) obj).getDataStream()).nextObject();
  120. }
  121. if (obj instanceof PGPSignatureList) {
  122. return ((PGPSignatureList) obj).get(0);
  123. }
  124. return null;
  125. }
  126. }
  127. @Override
  128. public SignatureVerification verify(byte[] data, byte[] signatureData)
  129. throws IOException {
  130. PGPSignature signature = null;
  131. String fingerprint = null;
  132. String signer = null;
  133. String keyId = null;
  134. try (InputStream sigIn = new ByteArrayInputStream(signatureData)) {
  135. signature = parseSignature(sigIn);
  136. if (signature != null) {
  137. // Try to figure out something to find the public key with.
  138. if (signature.hasSubpackets()) {
  139. PGPSignatureSubpacketVector packets = signature
  140. .getHashedSubPackets();
  141. IssuerFingerprint fingerprintPacket = packets
  142. .getIssuerFingerprint();
  143. if (fingerprintPacket != null) {
  144. fingerprint = Hex
  145. .toHexString(fingerprintPacket.getFingerprint())
  146. .toLowerCase(Locale.ROOT);
  147. }
  148. signer = packets.getSignerUserID();
  149. if (signer != null) {
  150. signer = BouncyCastleGpgSigner.extractSignerId(signer);
  151. }
  152. }
  153. keyId = Long.toUnsignedString(signature.getKeyID(), 16)
  154. .toLowerCase(Locale.ROOT);
  155. } else {
  156. throw new JGitInternalException(BCText.get().nonSignatureError);
  157. }
  158. } catch (PGPException e) {
  159. throw new JGitInternalException(BCText.get().signatureParseError,
  160. e);
  161. }
  162. Date signatureCreatedAt = signature.getCreationTime();
  163. if (fingerprint == null && signer == null && keyId == null) {
  164. return new VerificationResult(signatureCreatedAt, null, null, null,
  165. false, false, TrustLevel.UNKNOWN,
  166. BCText.get().signatureNoKeyInfo);
  167. }
  168. if (fingerprint != null && keyId != null
  169. && !fingerprint.endsWith(keyId)) {
  170. return new VerificationResult(signatureCreatedAt, signer, fingerprint,
  171. null, false, false, TrustLevel.UNKNOWN,
  172. MessageFormat.format(BCText.get().signatureInconsistent,
  173. keyId, fingerprint));
  174. }
  175. if (fingerprint == null && keyId != null) {
  176. fingerprint = keyId;
  177. }
  178. // Try to find the public key
  179. String keySpec = '<' + signer + '>';
  180. Object cached = null;
  181. PGPPublicKey publicKey = null;
  182. try {
  183. cached = byFingerprint.get(fingerprint);
  184. if (cached != null) {
  185. if (cached instanceof PGPPublicKey) {
  186. publicKey = (PGPPublicKey) cached;
  187. }
  188. } else if (!StringUtils.isEmptyOrNull(signer)) {
  189. cached = bySigner.get(signer);
  190. if (cached != null) {
  191. if (cached instanceof PGPPublicKey) {
  192. publicKey = (PGPPublicKey) cached;
  193. }
  194. }
  195. }
  196. if (cached == null) {
  197. publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint,
  198. keySpec);
  199. }
  200. } catch (IOException | PGPException e) {
  201. throw new JGitInternalException(
  202. BCText.get().signatureKeyLookupError, e);
  203. }
  204. if (publicKey == null) {
  205. if (cached == null) {
  206. byFingerprint.put(fingerprint, NO_KEY);
  207. byFingerprint.put(keyId, NO_KEY);
  208. if (signer != null) {
  209. bySigner.put(signer, NO_KEY);
  210. }
  211. }
  212. return new VerificationResult(signatureCreatedAt, signer,
  213. fingerprint, null, false, false, TrustLevel.UNKNOWN,
  214. BCText.get().signatureNoPublicKey);
  215. }
  216. if (cached == null) {
  217. byFingerprint.put(fingerprint, publicKey);
  218. byFingerprint.put(keyId, publicKey);
  219. if (signer != null) {
  220. bySigner.put(signer, publicKey);
  221. }
  222. }
  223. String user = null;
  224. Iterator<String> userIds = publicKey.getUserIDs();
  225. if (!StringUtils.isEmptyOrNull(signer)) {
  226. while (userIds.hasNext()) {
  227. String userId = userIds.next();
  228. if (BouncyCastleGpgKeyLocator.containsSigningKey(userId,
  229. keySpec)) {
  230. user = userId;
  231. break;
  232. }
  233. }
  234. }
  235. if (user == null) {
  236. userIds = publicKey.getUserIDs();
  237. if (userIds.hasNext()) {
  238. user = userIds.next();
  239. }
  240. }
  241. boolean expired = false;
  242. long validFor = publicKey.getValidSeconds();
  243. if (validFor > 0 && signatureCreatedAt != null) {
  244. Instant expiredAt = publicKey.getCreationTime().toInstant()
  245. .plusSeconds(validFor);
  246. expired = expiredAt.isBefore(signatureCreatedAt.toInstant());
  247. }
  248. // Trust data is not defined in OpenPGP; the format is implementation
  249. // specific. We don't use the GPG trustdb but simply the trust packet
  250. // on the public key, if present. Even if present, it may or may not
  251. // be set.
  252. byte[] trustData = publicKey.getTrustData();
  253. TrustLevel trust = parseGpgTrustPacket(trustData);
  254. boolean verified = false;
  255. try {
  256. signature.init(
  257. new JcaPGPContentVerifierBuilderProvider()
  258. .setProvider(BouncyCastleProvider.PROVIDER_NAME),
  259. publicKey);
  260. signature.update(data);
  261. verified = signature.verify();
  262. } catch (PGPException e) {
  263. throw new JGitInternalException(
  264. BCText.get().signatureVerificationError, e);
  265. }
  266. return new VerificationResult(signatureCreatedAt, signer, fingerprint, user,
  267. verified, expired, trust, null);
  268. }
  269. private TrustLevel parseGpgTrustPacket(byte[] packet) {
  270. if (packet == null || packet.length < 6) {
  271. // A GPG trust packet has at least 6 bytes.
  272. return TrustLevel.UNKNOWN;
  273. }
  274. if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') {
  275. // Not a GPG trust packet
  276. return TrustLevel.UNKNOWN;
  277. }
  278. int trust = packet[0] & 0x0F;
  279. switch (trust) {
  280. case 0: // No determined/set
  281. case 1: // Trust expired; i.e., calculation outdated or key expired
  282. case 2: // Undefined: not enough information to set
  283. return TrustLevel.UNKNOWN;
  284. case 3:
  285. return TrustLevel.NEVER;
  286. case 4:
  287. return TrustLevel.MARGINAL;
  288. case 5:
  289. return TrustLevel.FULL;
  290. case 6:
  291. return TrustLevel.ULTIMATE;
  292. default:
  293. return TrustLevel.UNKNOWN;
  294. }
  295. }
  296. @Override
  297. public void clear() {
  298. byFingerprint.clear();
  299. bySigner.clear();
  300. }
  301. private static class VerificationResult implements SignatureVerification {
  302. private final Date creationDate;
  303. private final String signer;
  304. private final String keyUser;
  305. private final String fingerprint;
  306. private final boolean verified;
  307. private final boolean expired;
  308. private final @NonNull TrustLevel trustLevel;
  309. private final String message;
  310. public VerificationResult(Date creationDate, String signer,
  311. String fingerprint, String user, boolean verified,
  312. boolean expired, @NonNull TrustLevel trust, String message) {
  313. this.creationDate = creationDate;
  314. this.signer = signer;
  315. this.fingerprint = fingerprint;
  316. this.keyUser = user;
  317. this.verified = verified;
  318. this.expired = expired;
  319. this.trustLevel = trust;
  320. this.message = message;
  321. }
  322. @Override
  323. public Date getCreationDate() {
  324. return creationDate;
  325. }
  326. @Override
  327. public String getSigner() {
  328. return signer;
  329. }
  330. @Override
  331. public String getKeyUser() {
  332. return keyUser;
  333. }
  334. @Override
  335. public String getKeyFingerprint() {
  336. return fingerprint;
  337. }
  338. @Override
  339. public boolean isExpired() {
  340. return expired;
  341. }
  342. @Override
  343. public TrustLevel getTrustLevel() {
  344. return trustLevel;
  345. }
  346. @Override
  347. public String getMessage() {
  348. return message;
  349. }
  350. @Override
  351. public boolean getVerified() {
  352. return verified;
  353. }
  354. }
  355. }