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.NoSuchAlgorithmException;
49 import java.security.NoSuchProviderException;
50 import java.security.Security;
51
52 import org.bouncycastle.bcpg.ArmoredOutputStream;
53 import org.bouncycastle.bcpg.BCPGOutputStream;
54 import org.bouncycastle.bcpg.HashAlgorithmTags;
55 import org.bouncycastle.jce.provider.BouncyCastleProvider;
56 import org.bouncycastle.openpgp.PGPException;
57 import org.bouncycastle.openpgp.PGPPrivateKey;
58 import org.bouncycastle.openpgp.PGPSecretKey;
59 import org.bouncycastle.openpgp.PGPSignature;
60 import org.bouncycastle.openpgp.PGPSignatureGenerator;
61 import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
62 import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
63 import org.eclipse.jgit.annotations.NonNull;
64 import org.eclipse.jgit.annotations.Nullable;
65 import org.eclipse.jgit.api.errors.CanceledException;
66 import org.eclipse.jgit.api.errors.JGitInternalException;
67 import org.eclipse.jgit.errors.UnsupportedCredentialItem;
68 import org.eclipse.jgit.internal.JGitText;
69 import org.eclipse.jgit.lib.CommitBuilder;
70 import org.eclipse.jgit.lib.GpgSignature;
71 import org.eclipse.jgit.lib.GpgSigner;
72 import org.eclipse.jgit.lib.PersonIdent;
73 import org.eclipse.jgit.transport.CredentialsProvider;
74
75
76
77
78 public class BouncyCastleGpgSigner extends GpgSigner {
79
80 private static void registerBouncyCastleProviderIfNecessary() {
81 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
82 Security.addProvider(new BouncyCastleProvider());
83 }
84 }
85
86
87
88
89
90
91
92 public BouncyCastleGpgSigner() {
93 registerBouncyCastleProviderIfNecessary();
94 }
95
96 @Override
97 public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
98 PersonIdent committer, CredentialsProvider credentialsProvider)
99 throws CanceledException {
100 try (BouncyCastleGpgKeyPassphrasePromptt.html#BouncyCastleGpgKeyPassphrasePrompt">BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
101 credentialsProvider)) {
102 BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
103 committer, passphrasePrompt);
104 return gpgKey != null;
105 } catch (PGPException | IOException | NoSuchAlgorithmException
106 | NoSuchProviderException | URISyntaxException e) {
107 return false;
108 }
109 }
110
111 private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey,
112 PersonIdent committer,
113 BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt)
114 throws CanceledException, UnsupportedCredentialItem, IOException,
115 NoSuchAlgorithmException, NoSuchProviderException, PGPException,
116 URISyntaxException {
117 if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
118 gpgSigningKey = committer.getEmailAddress();
119 }
120
121 BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
122 gpgSigningKey, passphrasePrompt);
123
124 return keyHelper.findSecretKey();
125 }
126
127 @Override
128 public void sign(@NonNull CommitBuilder commit,
129 @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
130 CredentialsProvider credentialsProvider) throws CanceledException {
131 try (BouncyCastleGpgKeyPassphrasePromptt.html#BouncyCastleGpgKeyPassphrasePrompt">BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
132 credentialsProvider)) {
133 BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
134 committer, passphrasePrompt);
135 PGPSecretKey secretKey = gpgKey.getSecretKey();
136 if (secretKey == null) {
137 throw new JGitInternalException(
138 JGitText.get().unableToSignCommitNoSecretKey);
139 }
140 char[] passphrase = passphrasePrompt.getPassphrase(
141 secretKey.getPublicKey().getFingerprint(),
142 gpgKey.getOrigin());
143 PGPPrivateKey privateKey = secretKey
144 .extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
145 .setProvider(BouncyCastleProvider.PROVIDER_NAME)
146 .build(passphrase));
147 PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
148 new JcaPGPContentSignerBuilder(
149 secretKey.getPublicKey().getAlgorithm(),
150 HashAlgorithmTags.SHA256).setProvider(
151 BouncyCastleProvider.PROVIDER_NAME));
152 signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
153 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
154 try (BCPGOutputStream out = new BCPGOutputStream(
155 new ArmoredOutputStream(buffer))) {
156 signatureGenerator.update(commit.build());
157 signatureGenerator.generate().encode(out);
158 }
159 commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
160 } catch (PGPException | IOException | NoSuchAlgorithmException
161 | NoSuchProviderException | URISyntaxException e) {
162 throw new JGitInternalException(e.getMessage(), e);
163 }
164 }
165 }