1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.gpg.bc.internal;
11
12 import java.io.ByteArrayOutputStream;
13 import java.io.IOException;
14 import java.net.URISyntaxException;
15 import java.security.NoSuchAlgorithmException;
16 import java.security.NoSuchProviderException;
17 import java.security.Security;
18
19 import org.bouncycastle.bcpg.ArmoredOutputStream;
20 import org.bouncycastle.bcpg.BCPGOutputStream;
21 import org.bouncycastle.bcpg.HashAlgorithmTags;
22 import org.bouncycastle.jce.provider.BouncyCastleProvider;
23 import org.bouncycastle.openpgp.PGPException;
24 import org.bouncycastle.openpgp.PGPPrivateKey;
25 import org.bouncycastle.openpgp.PGPSecretKey;
26 import org.bouncycastle.openpgp.PGPSignature;
27 import org.bouncycastle.openpgp.PGPSignatureGenerator;
28 import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
29 import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
30 import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
31 import org.eclipse.jgit.annotations.NonNull;
32 import org.eclipse.jgit.annotations.Nullable;
33 import org.eclipse.jgit.api.errors.CanceledException;
34 import org.eclipse.jgit.api.errors.JGitInternalException;
35 import org.eclipse.jgit.errors.UnsupportedCredentialItem;
36 import org.eclipse.jgit.lib.CommitBuilder;
37 import org.eclipse.jgit.lib.GpgSignature;
38 import org.eclipse.jgit.lib.GpgSigner;
39 import org.eclipse.jgit.lib.PersonIdent;
40 import org.eclipse.jgit.transport.CredentialsProvider;
41
42
43
44
45 public class BouncyCastleGpgSigner extends GpgSigner {
46
47 private static void registerBouncyCastleProviderIfNecessary() {
48 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
49 Security.addProvider(new BouncyCastleProvider());
50 }
51 }
52
53
54
55
56
57
58
59 public BouncyCastleGpgSigner() {
60 registerBouncyCastleProviderIfNecessary();
61 }
62
63 @Override
64 public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
65 PersonIdent committer, CredentialsProvider credentialsProvider)
66 throws CanceledException {
67 try (BouncyCastleGpgKeyPassphrasePromptePrompt.html#BouncyCastleGpgKeyPassphrasePrompt">BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
68 credentialsProvider)) {
69 BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
70 committer, passphrasePrompt);
71 return gpgKey != null;
72 } catch (PGPException | IOException | NoSuchAlgorithmException
73 | NoSuchProviderException | URISyntaxException e) {
74 return false;
75 }
76 }
77
78 private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey,
79 PersonIdent committer,
80 BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt)
81 throws CanceledException, UnsupportedCredentialItem, IOException,
82 NoSuchAlgorithmException, NoSuchProviderException, PGPException,
83 URISyntaxException {
84 if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
85 gpgSigningKey = '<' + committer.getEmailAddress() + '>';
86 }
87
88 BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
89 gpgSigningKey, passphrasePrompt);
90
91 return keyHelper.findSecretKey();
92 }
93
94 @Override
95 public void sign(@NonNull CommitBuilder commit,
96 @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
97 CredentialsProvider credentialsProvider) throws CanceledException {
98 try (BouncyCastleGpgKeyPassphrasePromptePrompt.html#BouncyCastleGpgKeyPassphrasePrompt">BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
99 credentialsProvider)) {
100 BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
101 committer, passphrasePrompt);
102 PGPSecretKey secretKey = gpgKey.getSecretKey();
103 if (secretKey == null) {
104 throw new JGitInternalException(
105 BCText.get().unableToSignCommitNoSecretKey);
106 }
107 JcePBESecretKeyDecryptorBuilder decryptorBuilder = new JcePBESecretKeyDecryptorBuilder()
108 .setProvider(BouncyCastleProvider.PROVIDER_NAME);
109 PGPPrivateKey privateKey = null;
110 if (!passphrasePrompt.hasPassphrase()) {
111
112
113
114 try {
115 privateKey = secretKey.extractPrivateKey(
116 decryptorBuilder.build(new char[0]));
117 } catch (PGPException e) {
118
119 }
120 }
121 if (privateKey == null) {
122
123 char[] passphrase = passphrasePrompt.getPassphrase(
124 secretKey.getPublicKey().getFingerprint(),
125 gpgKey.getOrigin());
126 privateKey = secretKey
127 .extractPrivateKey(decryptorBuilder.build(passphrase));
128 }
129 PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
130 new JcaPGPContentSignerBuilder(
131 secretKey.getPublicKey().getAlgorithm(),
132 HashAlgorithmTags.SHA256).setProvider(
133 BouncyCastleProvider.PROVIDER_NAME));
134 signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
135 PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
136 subpacketGenerator.setIssuerFingerprint(false,
137 secretKey.getPublicKey());
138 signatureGenerator
139 .setHashedSubpackets(subpacketGenerator.generate());
140 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
141 try (BCPGOutputStream out = new BCPGOutputStream(
142 new ArmoredOutputStream(buffer))) {
143 signatureGenerator.update(commit.build());
144 signatureGenerator.generate().encode(out);
145 }
146 commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
147 } catch (PGPException | IOException | NoSuchAlgorithmException
148 | NoSuchProviderException | URISyntaxException e) {
149 throw new JGitInternalException(e.getMessage(), e);
150 }
151 }
152 }