diff options
28 files changed, 951 insertions, 46 deletions
@@ -211,3 +211,26 @@ maven_jar( sha1 = "5bb3d7a38f7ea54138336591d89dd5867b806c02", src_sha1 = "94e89a8c9f82e38555e95b9f7f58344a247e862c", ) + +BOUNCYCASTLE_VER = "1.60" + +maven_jar( + name = "bcpg-jdk15on", + artifact = "org.bouncycastle:bcpg-jdk15on:" + BOUNCYCASTLE_VER, + sha1 = "13c7a199c484127daad298996e95818478431a2c", + src_sha1 = "edcd9e86d95e39b4da39bb295efd93bc4f56266e", +) + +maven_jar( + name = "bcprov-jdk15on", + artifact = "org.bouncycastle:bcprov-jdk15on:" + BOUNCYCASTLE_VER, + sha1 = "bd47ad3bd14b8e82595c7adaa143501e60842a84", + src_sha1 = "7c57a4d13fe53d9abb967bba600dd0b293dafd6a", +) + +maven_jar( + name = "bcpkix-jdk15on", + artifact = "org.bouncycastle:bcpkix-jdk15on:" + BOUNCYCASTLE_VER, + sha1 = "d0c46320fbc07be3a24eb13a56cee4e3d38e0c75", + src_sha1 = "a25f041293f401af08efba63ff4bbdce98134a03", +) @@ -148,6 +148,33 @@ java_library( ) java_library( + name = "bcpg", + visibility = [ + "//org.eclipse.jgit:__pkg__", + "//org.eclipse.jgit.test:__pkg__", + ], + exports = ["@bcpg-jdk15on//jar"], +) + +java_library( + name = "bcprov", + visibility = [ + "//org.eclipse.jgit:__pkg__", + "//org.eclipse.jgit.test:__pkg__", + ], + exports = ["@bcprov-jdk15on//jar"], +) + +java_library( + name = "bcpkix", + visibility = [ + "//org.eclipse.jgit:__pkg__", + "//org.eclipse.jgit.test:__pkg__", + ], + exports = ["@bcpkix-jdk15on//jar"], +) + +java_library( name = "jzlib", visibility = [ "//org.eclipse.jgit:__pkg__", diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml index 8a6e2a7058..2874d70374 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml @@ -86,4 +86,25 @@ version="0.0.0" unpack="false"/> + <plugin + id="org.bouncycastle.bcpg" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + + <plugin + id="org.bouncycastle.bcpkix" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + + <plugin + id="org.bouncycastle.bcprov" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + </feature> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target index dff7b79b6f..83ca7c4917 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target index 3d6b5f3086..02955266fa 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index bcde775ab9..dfbb452335 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index c8b601c2b4..89a2014dc1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index 27e86e87cb..45321eea2d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index 4969136cda..baf845a6da 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -37,6 +37,12 @@ <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/> <unit id="org.apache.log4j" version="1.2.15.v201012070815"/> <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/> + <unit id="org.bouncycastle.bcpg" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181107-1520"/> + <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181107-1520"/> <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/> <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/> <unit id="org.hamcrest" version="1.1.0.v20090501071000"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd index d447d6ca45..fd69da2624 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd @@ -16,6 +16,12 @@ location "http://download.eclipse.org/tools/orbit/downloads/drops/R2018112817032 org.apache.httpcomponents.httpcore.source [4.4.9.v20180409-1525,4.4.9.v20180409-1525] org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] + org.bouncycastle.bcpg [1.60.0.v20181107-1520,1.60.0.v20181107-1520] + org.bouncycastle.bcpg.source [1.60.0.v20181107-1520,1.60.0.v20181107-1520] + org.bouncycastle.bcpkix [1.60.0.v20181107-1520,1.60.0.v20181107-1520] + org.bouncycastle.bcpkix.source [1.60.0.v20181107-1520,1.60.0.v20181107-1520] + org.bouncycastle.bcprov [1.60.0.v20181107-1520,1.60.0.v20181107-1520] + org.bouncycastle.bcprov.source [1.60.0.v20181107-1520,1.60.0.v20181107-1520] org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000] diff --git a/org.eclipse.jgit.pgm/jgit.sh b/org.eclipse.jgit.pgm/jgit.sh index 826714751c..e263782738 100644 --- a/org.eclipse.jgit.pgm/jgit.sh +++ b/org.eclipse.jgit.pgm/jgit.sh @@ -110,9 +110,9 @@ then LESS=${LESS:-FSRX} export LESS - "$java" $java_args org.eclipse.jgit.pgm.Main "$@" | $use_pager + "$java" $java_args org.springframework.boot.loader.JarLauncher "$@" | $use_pager exit else - exec "$java" $java_args org.eclipse.jgit.pgm.Main "$@" + exec "$java" $java_args org.springframework.boot.loader.JarLauncher "$@" exit 1 fi diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index 049d4de4af..6faaa00eb4 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -172,40 +172,19 @@ </plugin> <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> - <phase>package</phase> <goals> - <goal>shade</goal> + <goal>repackage</goal> </goals> <configuration> <finalName>jgit-cli</finalName> - <createDependencyReducedPom>false</createDependencyReducedPom> - <transformers> - <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> - <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> - <manifestEntries> - <Main-Class>org.eclipse.jgit.pgm.Main</Main-Class> - <Implementation-Title>JGit Command Line Interface</Implementation-Title> - </manifestEntries> - </transformer> - </transformers> - <filters> - <!-- exclude the signing data for individual jars, ueberjar will be signed again --> - <filter> - <artifact>*:*</artifact> - <excludes> - <exclude>META-INF/*.SF</exclude> - <exclude>META-INF/*.DSA</exclude> - <exclude>META-INF/*.RSA</exclude> - <exclude>OSGI-OPT/**</exclude> - </excludes> - </filter> - </filters> - <shadedArtifactAttached>true</shadedArtifactAttached> - <shadedClassifierName>shaded</shadedClassifierName> <!-- Any name that makes sense --> + <attach>false</attach> + <mainClass>org.eclipse.jgit.pgm.Main</mainClass> + <executable>true</executable> + <embeddedLaunchScript>jgit.sh</embeddedLaunchScript> </configuration> </execution> </executions> @@ -220,11 +199,13 @@ <phase>package</phase> <configuration> <target> - <concat destfile="${basedir}/target/jgit" force="yes" binary="true"> - <fileset file="${basedir}/jgit.sh" /> - <fileset file="${basedir}/target/jgit-cli.jar" /> - </concat> - <chmod file="${basedir}/target/jgit" perm="a+x"/> + <move + file="${basedir}/target/jgit-cli.jar" + force="yes" + tofile="${basedir}/target/jgit" /> + <chmod + file="${basedir}/target/jgit" + perm="a+x" /> </target> </configuration> <goals> diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java index 81c4731914..00d2d100d2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java @@ -120,7 +120,7 @@ class Commit extends TextBuiltin { try { commit = commitCmd.call(); } catch (JGitInternalException e) { - throw die(e.getMessage()); + throw die(e.getMessage(), e); } String branchName; diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 7bdb722346..45592e44c3 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", com.jcraft.jsch;version="[0.1.54,0.2.0)", net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", + org.bouncycastle.util.encoders;version="[1.60.0,2.0.0)", org.eclipse.jgit.annotations;version="[5.3.0,5.4.0)", org.eclipse.jgit.api;version="[5.3.0,5.4.0)", org.eclipse.jgit.api.errors;version="[5.3.0,5.4.0)", diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml index 10117d89d6..718fa399ff 100644 --- a/org.eclipse.jgit.test/pom.xml +++ b/org.eclipse.jgit.test/pom.xml @@ -76,11 +76,22 @@ <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> - <version>1.59</version> <scope>test</scope> </dependency> <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <scope>test</scope> diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 9128bb66c0..5f1992cb51 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -76,6 +77,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FS; @@ -639,21 +641,26 @@ public class CommitCommandTest extends RepositoryTestCase { git.add().addFilepattern("file1").call(); String[] signingKey = new String[1]; + PersonIdent[] signingCommitters = new PersonIdent[1]; AtomicInteger callCount = new AtomicInteger(); GpgSigner.setDefault(new GpgSigner() { @Override - public void sign(CommitBuilder commit, String gpgSigningKey) { + public void sign(CommitBuilder commit, String gpgSigningKey, + PersonIdent signingCommitter, CredentialsProvider credentialsProvider) { signingKey[0] = gpgSigningKey; + signingCommitters[0] = signingCommitter; callCount.incrementAndGet(); } }); // first call should use config, which is expected to be null at // this time - git.commit().setSign(Boolean.TRUE).setMessage("initial commit") + git.commit().setCommitter(committer).setSign(Boolean.TRUE) + .setMessage("initial commit") .call(); assertNull(signingKey[0]); assertEquals(1, callCount.get()); + assertSame(committer, signingCommitters[0]); writeTrashFile("file2", "file2"); git.add().addFilepattern("file2").call(); @@ -665,20 +672,24 @@ public class CommitCommandTest extends RepositoryTestCase { expectedConfigSigningKey); config.save(); - git.commit().setSign(Boolean.TRUE).setMessage("initial commit") + git.commit().setCommitter(committer).setSign(Boolean.TRUE) + .setMessage("initial commit") .call(); assertEquals(expectedConfigSigningKey, signingKey[0]); assertEquals(2, callCount.get()); + assertSame(committer, signingCommitters[0]); writeTrashFile("file3", "file3"); git.add().addFilepattern("file3").call(); // now use specific on api String expectedSigningKey = "my-" + System.nanoTime(); - git.commit().setSign(Boolean.TRUE).setSigningKey(expectedSigningKey) + git.commit().setCommitter(committer).setSign(Boolean.TRUE) + .setSigningKey(expectedSigningKey) .setMessage("initial commit").call(); assertEquals(expectedSigningKey, signingKey[0]); assertEquals(3, callCount.get()); + assertSame(committer, signingCommitters[0]); } } @@ -691,7 +702,8 @@ public class CommitCommandTest extends RepositoryTestCase { AtomicInteger callCount = new AtomicInteger(); GpgSigner.setDefault(new GpgSigner() { @Override - public void sign(CommitBuilder commit, String gpgSigningKey) { + public void sign(CommitBuilder commit, String gpgSigningKey, + PersonIdent signingCommitter, CredentialsProvider credentialsProvider) { callCount.incrementAndGet(); } }); diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD index 6ba7796b7e..9ddcb9d9d6 100644 --- a/org.eclipse.jgit/BUILD +++ b/org.eclipse.jgit/BUILD @@ -26,6 +26,9 @@ java_library( "//lib:jsch", "//lib:jzlib", "//lib:slf4j-api", + "//lib:bcpg", + "//lib:bcprov", + "//lib:bcpkix" ], ) diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 38b734b540..d71910f8a4 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -160,6 +160,16 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", com.jcraft.jsch;version="[0.1.37,0.2.0)", javax.crypto, javax.net.ssl, + org.bouncycastle;version="[1.60.0,2.0.0)", + org.bouncycastle.bcpg;version="[1.60.0,2.0.0)", + org.bouncycastle.gpg;version="[1.60.0,2.0.0)", + org.bouncycastle.gpg.keybox;version="[1.60.0,2.0.0)", + org.bouncycastle.jce.provider;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.jcajce;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.operator;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.operator.jcajce;version="[1.60.0,2.0.0)", + org.bouncycastle.util.encoders;version="[1.60.0,2.0.0)", org.slf4j;version="[1.7.0,2.0.0)", org.xml.sax, org.xml.sax.helpers diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index 0659df2529..ff819237b2 100644 --- a/org.eclipse.jgit/pom.xml +++ b/org.eclipse.jgit/pom.xml @@ -84,12 +84,26 @@ <artifactId>JavaEWAH</artifactId> </dependency> - <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + </dependency> + </dependencies> <build> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index dc26e58689..6ee144ff64 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -223,6 +223,7 @@ createBranchUnexpectedResult=Create branch returned unexpected result {0} createNewFileFailed=Could not create new file {0} createRequiresZeroOldId=Create requires old ID to be zero credentialPassword=Password +credentialPassphrase=Passphrase credentialUsername=Username daemonAlreadyRunning=Daemon already running daysAgo={0} days ago @@ -323,6 +324,14 @@ gcFailed=Garbage collection failed. gcLogExists=A previous GC run reported an error: ''{0}''. Automatic gc will fail until ''{1}'' is removed. gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire. gitmodulesNotFound=.gitmodules not found in tree. +gpgFailedToParseSecretKey=Failed to parse secret key file in directory: {0}. Is the entered passphrase correct? +gpgNoCredentialsProvider=missing credentials provider +gpgNoKeyring=neither pubring.kbx nor secring.gpg files found +gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0} +gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0} +gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0} +gpgKeyInfo=GPG Key (fingerprint {0}) +gpgSigningCancelled=Signing was cancelled headRequiredToStash=HEAD required to stash local changes hoursAgo={0} hours ago httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments @@ -716,6 +725,7 @@ unableToCreateNewObject=Unable to create new object: {0} unableToRemovePath=Unable to remove path ''{0}'' unableToStore=Unable to store {0}. unableToWrite=Unable to write {0} +unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available. unauthorized=Unauthorized underflowedReftableBlock=Underflowed reftable block unencodeableFile=Unencodable file: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 00d3842dae..9f071c4c4e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -88,10 +88,12 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; @@ -149,6 +151,8 @@ public class CommitCommand extends GitCommand<RevCommit> { private GpgSigner gpgSigner; + private CredentialsProvider credentialsProvider; + /** * Constructor for CommitCommand * @@ -157,6 +161,7 @@ public class CommitCommand extends GitCommand<RevCommit> { */ protected CommitCommand(Repository repo) { super(repo); + this.credentialsProvider = CredentialsProvider.getDefault(); } /** @@ -263,7 +268,8 @@ public class CommitCommand extends GitCommand<RevCommit> { commit.setTreeId(indexTreeId); if (signCommit.booleanValue()) { - gpgSigner.sign(commit, signingKey); + gpgSigner.sign(commit, signingKey, committer, + credentialsProvider); } ObjectId commitId = odi.insert(commit); @@ -603,6 +609,9 @@ public class CommitCommand extends GitCommand<RevCommit> { JGitText.get().onlyOpenPgpSupportedForSigning); } gpgSigner = GpgSigner.getDefault(); + if (gpgSigner == null) { + gpgSigner = new BouncyCastleGpgSigner(); + } } } @@ -943,4 +952,17 @@ public class CommitCommand extends GitCommand<RevCommit> { this.signCommit = sign; return this; } + + /** + * Sets a {@link CredentialsProvider} + * + * @param credentialsProvider + * the provider to use when querying for credentials (eg., during + * signing) + * @since 5.3 + */ + public void setCredentialsProvider( + CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 9da7f15746..bd91fe0f82 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -284,6 +284,7 @@ public class JGitText extends TranslationBundle { /***/ public String createNewFileFailed; /***/ public String createRequiresZeroOldId; /***/ public String credentialPassword; + /***/ public String credentialPassphrase; /***/ public String credentialUsername; /***/ public String daemonAlreadyRunning; /***/ public String daysAgo; @@ -384,6 +385,14 @@ public class JGitText extends TranslationBundle { /***/ public String gcLogExists; /***/ public String gcTooManyUnpruned; /***/ public String gitmodulesNotFound; + /***/ public String gpgFailedToParseSecretKey; + /***/ public String gpgNoCredentialsProvider; + /***/ public String gpgNoKeyring; + /***/ public String gpgNoKeyInLegacySecring; + /***/ public String gpgNoPublicKeyFound; + /***/ public String gpgNoSecretKeyForPublicKey; + /***/ public String gpgKeyInfo; + /***/ public String gpgSigningCancelled; /***/ public String headRequiredToStash; /***/ public String hoursAgo; /***/ public String httpConfigCannotNormalizeURL; @@ -776,6 +785,7 @@ public class JGitText extends TranslationBundle { /***/ public String unableToRemovePath; /***/ public String unableToStore; /***/ public String unableToWrite; + /***/ public String unableToSignCommitNoSecretKey; /***/ public String unauthorized; /***/ public String underflowedReftableBlock; /***/ public String unencodeableFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java index e509c9783f..7796c2058f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java @@ -43,6 +43,9 @@ package org.eclipse.jgit.lib; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner; +import org.eclipse.jgit.transport.CredentialsProvider; /** * Creates GPG signatures for Git objects. @@ -51,7 +54,7 @@ import org.eclipse.jgit.annotations.NonNull; */ public abstract class GpgSigner { - private static GpgSigner defaultSigner; + private static GpgSigner defaultSigner = new BouncyCastleGpgSigner(); /** * Get the default signer, or <code>null</code>. @@ -93,8 +96,17 @@ public abstract class GpgSigner { * complete to allow proper calculation of payload) * @param gpgSigningKey * the signing key (passed as is to the GPG signing tool) + * @param committer + * the signing identity (to help with key lookup) + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) */ public abstract void sign(@NonNull CommitBuilder commit, - String gpgSigningKey); + String gpgSigningKey, @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider) throws CanceledException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java new file mode 100644 index 0000000000..ef9d7ee393 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018, Salesforce. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib.internal; + +import java.nio.file.Path; + +import org.bouncycastle.openpgp.PGPSecretKey; + +/** + * Container which holds a {@link #getSecretKey()} together with the + * {@link #getOrigin() path it was loaded from}. + */ +class BouncyCastleGpgKey { + + private PGPSecretKey secretKey; + + private Path origin; + + public BouncyCastleGpgKey(PGPSecretKey secretKey, Path origin) { + this.secretKey = secretKey; + this.origin = origin; + } + + public PGPSecretKey getSecretKey() { + return secretKey; + } + + public Path getOrigin() { + return origin; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java new file mode 100644 index 0000000000..c7cbe360c2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2018, Salesforce. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib.internal; + +import static java.nio.file.Files.exists; +import static java.nio.file.Files.newInputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.bouncycastle.gpg.SExprParser; +import org.bouncycastle.gpg.keybox.BlobType; +import org.bouncycastle.gpg.keybox.KeyBlob; +import org.bouncycastle.gpg.keybox.KeyBox; +import org.bouncycastle.gpg.keybox.KeyInformation; +import org.bouncycastle.gpg.keybox.PublicKeyRingBlob; +import org.bouncycastle.gpg.keybox.UserID; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; + +/** + * Locates GPG keys from either <code>~/.gnupg/private-keys-v1.d</code> or + * <code>~/.gnupg/secring.gpg</code> + */ +class BouncyCastleGpgKeyLocator { + + private static final Path USER_KEYBOX_PATH = Paths + .get(System.getProperty("user.home"), ".gnupg", "pubring.kbx"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private static final Path USER_SECRET_KEY_DIR = Paths.get( + System.getProperty("user.home"), ".gnupg", "private-keys-v1.d"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private static final Path USER_PGP_LEGACY_SECRING_FILE = Paths + .get(System.getProperty("user.home"), ".gnupg", "secring.gpg"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private final String signingKey; + + private BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt; + + /** + * Create a new key locator for the specified signing key. + * <p> + * The signing key must either be a hex representation of a specific key or + * a user identity substring (eg., email address). All keys in the KeyBox + * will be looked up in the order as returned by the KeyBox. A key id will + * be searched before attempting to find a key by user id. + * </p> + * + * @param signingKey + * the signing key to search for + * @param passphrasePrompt + * the provider to use when asking for key passphrase + */ + public BouncyCastleGpgKeyLocator(String signingKey, + @NonNull BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt) { + this.signingKey = signingKey; + this.passphrasePrompt = passphrasePrompt; + } + + private PGPSecretKey attemptParseSecretKey(Path keyFile, + PGPDigestCalculatorProvider calculatorProvider, + PBEProtectionRemoverFactory passphraseProvider, + PGPPublicKey publicKey) throws IOException { + try (InputStream in = newInputStream(keyFile)) { + return new SExprParser(calculatorProvider).parseSecretKey( + new BufferedInputStream(in), passphraseProvider, publicKey); + } catch (PGPException | ClassCastException e) { + return null; + } + } + + private boolean containsSigningKey(String userId) { + return userId.toLowerCase(Locale.ROOT) + .contains(signingKey.toLowerCase(Locale.ROOT)); + } + + private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) + throws IOException { + for (KeyInformation keyInfo : keyBlob.getKeyInformation()) { + if (signingKey.toLowerCase(Locale.ROOT) + .equals(Hex.toHexString(keyInfo.getKeyID()) + .toLowerCase(Locale.ROOT))) { + return getFirstPublicKey(keyBlob); + } + } + return null; + } + + private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) + throws IOException { + for (UserID userID : keyBlob.getUserIds()) { + if (containsSigningKey(userID.getUserIDAsString())) { + return getFirstPublicKey(keyBlob); + } + } + return null; + } + + /** + * Finds a public key associated with the signing key. + * + * @param keyboxFile + * the KeyBox file + * @return publicKey the public key (maybe <code>null</code>) + * @throws IOException + * in case of problems reading the file + */ + private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile) + throws IOException { + KeyBox keyBox = readKeyBoxFile(keyboxFile); + for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { + if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { + PGPPublicKey key = findPublicKeyByKeyId(keyBlob); + if (key != null) { + return key; + } + key = findPublicKeyByUserId(keyBlob); + if (key != null) { + return key; + } + } + } + return null; + } + + /** + * Use pubring.kbx when available, if not fallback to secring.gpg or secret + * key path provided to parse and return secret key + * + * @return the secret key + * @throws IOException + * in case of issues reading key files + * @throws PGPException + * in case of issues finding a key + * @throws CanceledException + * @throws URISyntaxException + * @throws UnsupportedCredentialItem + */ + public BouncyCastleGpgKey findSecretKey() + throws IOException, PGPException, CanceledException, + UnsupportedCredentialItem, URISyntaxException { + if (exists(USER_KEYBOX_PATH)) { + PGPPublicKey publicKey = // + findPublicKeyInKeyBox(USER_KEYBOX_PATH); + + if (publicKey != null) { + return findSecretKeyForKeyBoxPublicKey(publicKey, + USER_KEYBOX_PATH); + } + + throw new PGPException(MessageFormat + .format(JGitText.get().gpgNoPublicKeyFound, signingKey)); + } else if (exists(USER_PGP_LEGACY_SECRING_FILE)) { + PGPSecretKey secretKey = findSecretKeyInLegacySecring(signingKey, + USER_PGP_LEGACY_SECRING_FILE); + + if (secretKey != null) { + return new BouncyCastleGpgKey(secretKey, USER_PGP_LEGACY_SECRING_FILE); + } + + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoKeyInLegacySecring, signingKey)); + } + + throw new PGPException(JGitText.get().gpgNoKeyring); + } + + private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( + PGPPublicKey publicKey, Path userKeyboxPath) + throws PGPException, CanceledException, UnsupportedCredentialItem, + URISyntaxException { + /* + * this is somewhat brute-force but there doesn't seem to be another + * way; we have to walk all private key files we find and try to open + * them + */ + + PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() + .build(); + + PBEProtectionRemoverFactory passphraseProvider = new JcePBEProtectionRemoverFactory( + passphrasePrompt.getPassphrase(publicKey.getFingerprint(), + userKeyboxPath)); + + try (Stream<Path> keyFiles = Files.walk(USER_SECRET_KEY_DIR)) { + for (Path keyFile : keyFiles.filter(Files::isRegularFile) + .collect(Collectors.toList())) { + PGPSecretKey secretKey = attemptParseSecretKey(keyFile, + calculatorProvider, passphraseProvider, publicKey); + if (secretKey != null) { + return new BouncyCastleGpgKey(secretKey, userKeyboxPath); + } + } + + passphrasePrompt.clear(); + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoSecretKeyForPublicKey, + Long.toHexString(publicKey.getKeyID()))); + } catch (RuntimeException e) { + passphrasePrompt.clear(); + throw e; + } catch (IOException e) { + passphrasePrompt.clear(); + throw new PGPException(MessageFormat.format( + JGitText.get().gpgFailedToParseSecretKey, + USER_SECRET_KEY_DIR.toAbsolutePath()), e); + } + } + + /** + * Return the first suitable key for signing in the key ring collection. For + * this case we only expect there to be one key available for signing. + * </p> + * + * @param signingkey + * @param secringFile + * + * @return the first suitable PGP secret key found for signing + * @throws IOException + * on I/O related errors + * @throws PGPException + * on BouncyCastle errors + */ + private PGPSecretKey findSecretKeyInLegacySecring(String signingkey, + Path secringFile) throws IOException, PGPException { + + try (InputStream in = newInputStream(secringFile)) { + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( + PGPUtil.getDecoderStream(new BufferedInputStream(in)), + new JcaKeyFingerprintCalculator()); + + Iterator<PGPSecretKeyRing> keyrings = pgpSec.getKeyRings(); + while (keyrings.hasNext()) { + PGPSecretKeyRing keyRing = keyrings.next(); + Iterator<PGPSecretKey> keys = keyRing.getSecretKeys(); + while (keys.hasNext()) { + PGPSecretKey key = keys.next(); + // try key id + String fingerprint = Hex + .toHexString(key.getPublicKey().getFingerprint()) + .toLowerCase(Locale.ROOT); + if (fingerprint + .endsWith(signingkey.toLowerCase(Locale.ROOT))) { + return key; + } + // try user id + Iterator<String> userIDs = key.getUserIDs(); + while (userIDs.hasNext()) { + String userId = userIDs.next(); + if (containsSigningKey(userId)) { + return key; + } + } + } + } + } + return null; + } + + private PGPPublicKey getFirstPublicKey(KeyBlob keyBlob) throws IOException { + return ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing() + .getPublicKey(); + } + + private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException { + KeyBox keyBox; + try (InputStream in = new BufferedInputStream( + newInputStream(keyboxFile))) { + // note: KeyBox constructor reads in the whole InputStream at once + // this code will change in 1.61 to + // either 'new BcKeyBox(in)' or 'new JcaKeyBoxBuilder().build(in)' + keyBox = new KeyBox(in, new JcaKeyFingerprintCalculator()); + } + return keyBox; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java new file mode 100644 index 0000000000..2efe962918 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java @@ -0,0 +1,134 @@ +/*- + * Copyright (C) 2019, Salesforce. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib.internal; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.text.MessageFormat; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.transport.CredentialItem.CharArrayType; +import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** + * Prompts for a passphrase and caches it until {@link #clear() cleared}. + * <p> + * Implements {@link AutoCloseable} so it can be used within a + * try-with-resources block. + * </p> + */ +class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { + + private CharArrayType passphrase; + + private CredentialsProvider credentialsProvider; + + public BouncyCastleGpgKeyPassphrasePrompt( + CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + /** + * Clears any cached passphrase + */ + public void clear() { + if (passphrase != null) { + passphrase.clear(); + passphrase = null; + } + } + + @Override + public void close() { + clear(); + } + + private URIish createURI(Path keyLocation) throws URISyntaxException { + return new URIish(keyLocation.toUri().toString()); + } + + /** + * Prompts use for a passphrase unless one was cached from a previous + * prompt. + * + * @param keyFingerprint + * the fingerprint to show to the user during prompting + * @param keyLocation + * the location the key was loaded from + * @return the passphrase (maybe <code>null</code>) + * @throws PGPException + * @throws CanceledException + * in case passphrase was not entered by user + * @throws URISyntaxException + * @throws UnsupportedCredentialItem + */ + public char[] getPassphrase(byte[] keyFingerprint, Path keyLocation) + throws PGPException, CanceledException, UnsupportedCredentialItem, + URISyntaxException { + if (passphrase == null) { + passphrase = new CharArrayType(JGitText.get().credentialPassphrase, + true); + } + + if (credentialsProvider == null) { + throw new PGPException(JGitText.get().gpgNoCredentialsProvider); + } + + if (passphrase.getValue() == null + && !credentialsProvider.get(createURI(keyLocation), + new InformationalMessage( + MessageFormat.format(JGitText.get().gpgKeyInfo, + Hex.toHexString(keyFingerprint))), + passphrase)) { + throw new CanceledException(JGitText.get().gpgSigningCancelled); + } + return passphrase.getValue(); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java new file mode 100644 index 0000000000..f447912f0d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018, Salesforce. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib.internal; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.Security; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.GpgSignature; +import org.eclipse.jgit.lib.GpgSigner; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.transport.CredentialsProvider; + +/** + * GPG Signer using BouncyCastle library + */ +public class BouncyCastleGpgSigner extends GpgSigner { + + private static void registerBouncyCastleProviderIfNecessary() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * Create a new instance. + * <p> + * The BounceCastleProvider will be registered if necessary. + * </p> + */ + public BouncyCastleGpgSigner() { + registerBouncyCastleProviderIfNecessary(); + } + + @Override + public void sign(@NonNull CommitBuilder commit, String gpgSigningKey, + @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider) throws CanceledException { + if (gpgSigningKey == null || gpgSigningKey.isEmpty()) { + gpgSigningKey = committer.getEmailAddress(); + } + + try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( + credentialsProvider)) { + BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator( + gpgSigningKey, passphrasePrompt); + + BouncyCastleGpgKey gpgKey = keyHelper.findSecretKey(); + PGPSecretKey secretKey = gpgKey.getSecretKey(); + if (secretKey == null) { + throw new JGitInternalException( + JGitText.get().unableToSignCommitNoSecretKey); + } + char[] passphrase = passphrasePrompt + .getPassphrase(secretKey.getPublicKey().getFingerprint(), + gpgKey.getOrigin()); + PGPPrivateKey privateKey = secretKey + .extractPrivateKey(new JcePBESecretKeyDecryptorBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + .build(passphrase)); + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( + new JcaPGPContentSignerBuilder( + secretKey.getPublicKey().getAlgorithm(), + HashAlgorithmTags.SHA256).setProvider( + BouncyCastleProvider.PROVIDER_NAME)); + signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (BCPGOutputStream out = new BCPGOutputStream( + new ArmoredOutputStream(buffer))) { + signatureGenerator.update(commit.build()); + signatureGenerator.generate().encode(out); + } + commit.setGpgSignature(new GpgSignature(buffer.toByteArray())); + } catch (PGPException | IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } +} @@ -202,6 +202,7 @@ <maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version> <tycho-extras-version>1.3.0</tycho-extras-version> <gson-version>2.8.2</gson-version> + <bouncycastle-version>1.60</bouncycastle-version> <spotbugs-maven-plugin-version>3.1.10</spotbugs-maven-plugin-version> <maven-surefire-version>2.22.1</maven-surefire-version> <maven-compiler-plugin-version>3.8.0</maven-compiler-plugin-version> @@ -394,6 +395,12 @@ <artifactId>maven-project-info-reports-plugin</artifactId> <version>${maven-project-info-reports-plugin-version}</version> </plugin> + + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>2.1.2.RELEASE</version> + </plugin> </plugins> </pluginManagement> @@ -760,6 +767,25 @@ <artifactId>gson</artifactId> <version>${gson-version}</version> </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + <version>${bouncycastle-version}</version> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>${bouncycastle-version}</version> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <version>${bouncycastle-version}</version> + </dependency> + </dependencies> </dependencyManagement> |