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.

BouncyCastleGpgSigner.java 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /*
  2. * Copyright (C) 2018, Salesforce. 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.lib.internal;
  11. import java.io.ByteArrayOutputStream;
  12. import java.io.IOException;
  13. import java.net.URISyntaxException;
  14. import java.security.NoSuchAlgorithmException;
  15. import java.security.NoSuchProviderException;
  16. import java.security.Security;
  17. import org.bouncycastle.bcpg.ArmoredOutputStream;
  18. import org.bouncycastle.bcpg.BCPGOutputStream;
  19. import org.bouncycastle.bcpg.HashAlgorithmTags;
  20. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  21. import org.bouncycastle.openpgp.PGPException;
  22. import org.bouncycastle.openpgp.PGPPrivateKey;
  23. import org.bouncycastle.openpgp.PGPSecretKey;
  24. import org.bouncycastle.openpgp.PGPSignature;
  25. import org.bouncycastle.openpgp.PGPSignatureGenerator;
  26. import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
  27. import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
  28. import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
  29. import org.eclipse.jgit.annotations.NonNull;
  30. import org.eclipse.jgit.annotations.Nullable;
  31. import org.eclipse.jgit.api.errors.CanceledException;
  32. import org.eclipse.jgit.api.errors.JGitInternalException;
  33. import org.eclipse.jgit.errors.UnsupportedCredentialItem;
  34. import org.eclipse.jgit.internal.JGitText;
  35. import org.eclipse.jgit.lib.CommitBuilder;
  36. import org.eclipse.jgit.lib.GpgSignature;
  37. import org.eclipse.jgit.lib.GpgSigner;
  38. import org.eclipse.jgit.lib.PersonIdent;
  39. import org.eclipse.jgit.transport.CredentialsProvider;
  40. /**
  41. * GPG Signer using BouncyCastle library
  42. */
  43. public class BouncyCastleGpgSigner extends GpgSigner {
  44. private static void registerBouncyCastleProviderIfNecessary() {
  45. if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
  46. Security.addProvider(new BouncyCastleProvider());
  47. }
  48. }
  49. /**
  50. * Create a new instance.
  51. * <p>
  52. * The BounceCastleProvider will be registered if necessary.
  53. * </p>
  54. */
  55. public BouncyCastleGpgSigner() {
  56. registerBouncyCastleProviderIfNecessary();
  57. }
  58. @Override
  59. public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
  60. PersonIdent committer, CredentialsProvider credentialsProvider)
  61. throws CanceledException {
  62. try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
  63. credentialsProvider)) {
  64. BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
  65. committer, passphrasePrompt);
  66. return gpgKey != null;
  67. } catch (PGPException | IOException | NoSuchAlgorithmException
  68. | NoSuchProviderException | URISyntaxException e) {
  69. return false;
  70. }
  71. }
  72. private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey,
  73. PersonIdent committer,
  74. BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt)
  75. throws CanceledException, UnsupportedCredentialItem, IOException,
  76. NoSuchAlgorithmException, NoSuchProviderException, PGPException,
  77. URISyntaxException {
  78. if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
  79. gpgSigningKey = '<' + committer.getEmailAddress() + '>';
  80. }
  81. BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
  82. gpgSigningKey, passphrasePrompt);
  83. return keyHelper.findSecretKey();
  84. }
  85. @Override
  86. public void sign(@NonNull CommitBuilder commit,
  87. @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
  88. CredentialsProvider credentialsProvider) throws CanceledException {
  89. try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
  90. credentialsProvider)) {
  91. BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
  92. committer, passphrasePrompt);
  93. PGPSecretKey secretKey = gpgKey.getSecretKey();
  94. if (secretKey == null) {
  95. throw new JGitInternalException(
  96. JGitText.get().unableToSignCommitNoSecretKey);
  97. }
  98. char[] passphrase = passphrasePrompt.getPassphrase(
  99. secretKey.getPublicKey().getFingerprint(),
  100. gpgKey.getOrigin());
  101. PGPPrivateKey privateKey = secretKey
  102. .extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
  103. .setProvider(BouncyCastleProvider.PROVIDER_NAME)
  104. .build(passphrase));
  105. PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
  106. new JcaPGPContentSignerBuilder(
  107. secretKey.getPublicKey().getAlgorithm(),
  108. HashAlgorithmTags.SHA256).setProvider(
  109. BouncyCastleProvider.PROVIDER_NAME));
  110. signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
  111. PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
  112. subpacketGenerator.setIssuerFingerprint(false,
  113. secretKey.getPublicKey());
  114. signatureGenerator
  115. .setHashedSubpackets(subpacketGenerator.generate());
  116. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  117. try (BCPGOutputStream out = new BCPGOutputStream(
  118. new ArmoredOutputStream(buffer))) {
  119. signatureGenerator.update(commit.build());
  120. signatureGenerator.generate().encode(out);
  121. }
  122. commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
  123. } catch (PGPException | IOException | NoSuchAlgorithmException
  124. | NoSuchProviderException | URISyntaxException e) {
  125. throw new JGitInternalException(e.getMessage(), e);
  126. }
  127. }
  128. }