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