Implement signing commits using BouncyCastle
This also includes a change to generating the jgit CLI jar. Shading is no longer possible because it breaks the signature of BouncyCastle. Instead, the Spring Boot Loader Maven plug-in is now used to generate an executable jar. Bug: 382212 Change-Id: I35ee3d4b06d9d479475ab2e51b29bed49661bbdc Also-by: Gunnar Wagenknecht <gunnar@wagenknecht.org> Signed-off-by: Gunnar Wagenknecht <gunnar@wagenknecht.org> Signed-off-by: Medha Bhargav Prabhala <mprabhala@salesforce.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
9150f5d836
commit
137e91a465
23
WORKSPACE
23
WORKSPACE
|
@ -211,3 +211,26 @@ maven_jar(
|
||||||
sha1 = "5bb3d7a38f7ea54138336591d89dd5867b806c02",
|
sha1 = "5bb3d7a38f7ea54138336591d89dd5867b806c02",
|
||||||
src_sha1 = "94e89a8c9f82e38555e95b9f7f58344a247e862c",
|
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",
|
||||||
|
)
|
||||||
|
|
27
lib/BUILD
27
lib/BUILD
|
@ -147,6 +147,33 @@ java_library(
|
||||||
exports = ["@jsch//jar"],
|
exports = ["@jsch//jar"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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(
|
java_library(
|
||||||
name = "jzlib",
|
name = "jzlib",
|
||||||
visibility = [
|
visibility = [
|
||||||
|
|
|
@ -86,4 +86,25 @@
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
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>
|
</feature>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<unit id="org.apache.httpcomponents.httpcore.source" version="4.4.9.v20180409-1525"/>
|
<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" version="1.2.15.v201012070815"/>
|
||||||
<unit id="org.apache.log4j.source" 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" version="2.33.0.v20160323-2218"/>
|
||||||
<unit id="org.kohsuke.args4j.source" 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"/>
|
<unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
|
||||||
|
|
|
@ -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.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 [1.2.15.v201012070815,1.2.15.v201012070815]
|
||||||
org.apache.log4j.source [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 [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.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
|
||||||
org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
|
org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
|
||||||
|
|
|
@ -110,9 +110,9 @@ then
|
||||||
LESS=${LESS:-FSRX}
|
LESS=${LESS:-FSRX}
|
||||||
export LESS
|
export LESS
|
||||||
|
|
||||||
"$java" $java_args org.eclipse.jgit.pgm.Main "$@" | $use_pager
|
"$java" $java_args org.springframework.boot.loader.JarLauncher "$@" | $use_pager
|
||||||
exit
|
exit
|
||||||
else
|
else
|
||||||
exec "$java" $java_args org.eclipse.jgit.pgm.Main "$@"
|
exec "$java" $java_args org.springframework.boot.loader.JarLauncher "$@"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -172,40 +172,19 @@
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
<goals>
|
||||||
<goal>shade</goal>
|
<goal>repackage</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<finalName>jgit-cli</finalName>
|
<finalName>jgit-cli</finalName>
|
||||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
<attach>false</attach>
|
||||||
<transformers>
|
<mainClass>org.eclipse.jgit.pgm.Main</mainClass>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
<executable>true</executable>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<embeddedLaunchScript>jgit.sh</embeddedLaunchScript>
|
||||||
<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 -->
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
@ -220,11 +199,13 @@
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<configuration>
|
<configuration>
|
||||||
<target>
|
<target>
|
||||||
<concat destfile="${basedir}/target/jgit" force="yes" binary="true">
|
<move
|
||||||
<fileset file="${basedir}/jgit.sh" />
|
file="${basedir}/target/jgit-cli.jar"
|
||||||
<fileset file="${basedir}/target/jgit-cli.jar" />
|
force="yes"
|
||||||
</concat>
|
tofile="${basedir}/target/jgit" />
|
||||||
<chmod file="${basedir}/target/jgit" perm="a+x"/>
|
<chmod
|
||||||
|
file="${basedir}/target/jgit"
|
||||||
|
perm="a+x" />
|
||||||
</target>
|
</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
<goals>
|
<goals>
|
||||||
|
|
|
@ -120,7 +120,7 @@ protected void run() throws NoHeadException, NoMessageException,
|
||||||
try {
|
try {
|
||||||
commit = commitCmd.call();
|
commit = commitCmd.call();
|
||||||
} catch (JGitInternalException e) {
|
} catch (JGitInternalException e) {
|
||||||
throw die(e.getMessage());
|
throw die(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
String branchName;
|
String branchName;
|
||||||
|
|
|
@ -11,6 +11,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||||
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
|
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
|
||||||
com.jcraft.jsch;version="[0.1.54,0.2.0)",
|
com.jcraft.jsch;version="[0.1.54,0.2.0)",
|
||||||
net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.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.annotations;version="[5.3.0,5.4.0)",
|
||||||
org.eclipse.jgit.api;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)",
|
org.eclipse.jgit.api.errors;version="[5.3.0,5.4.0)",
|
||||||
|
|
|
@ -76,7 +76,18 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<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>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.submodule.SubmoduleWalk;
|
import org.eclipse.jgit.submodule.SubmoduleWalk;
|
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
@ -639,21 +641,26 @@ public void callSignerWithProperSigningKey() throws Exception {
|
||||||
git.add().addFilepattern("file1").call();
|
git.add().addFilepattern("file1").call();
|
||||||
|
|
||||||
String[] signingKey = new String[1];
|
String[] signingKey = new String[1];
|
||||||
|
PersonIdent[] signingCommitters = new PersonIdent[1];
|
||||||
AtomicInteger callCount = new AtomicInteger();
|
AtomicInteger callCount = new AtomicInteger();
|
||||||
GpgSigner.setDefault(new GpgSigner() {
|
GpgSigner.setDefault(new GpgSigner() {
|
||||||
@Override
|
@Override
|
||||||
public void sign(CommitBuilder commit, String gpgSigningKey) {
|
public void sign(CommitBuilder commit, String gpgSigningKey,
|
||||||
|
PersonIdent signingCommitter, CredentialsProvider credentialsProvider) {
|
||||||
signingKey[0] = gpgSigningKey;
|
signingKey[0] = gpgSigningKey;
|
||||||
|
signingCommitters[0] = signingCommitter;
|
||||||
callCount.incrementAndGet();
|
callCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// first call should use config, which is expected to be null at
|
// first call should use config, which is expected to be null at
|
||||||
// this time
|
// this time
|
||||||
git.commit().setSign(Boolean.TRUE).setMessage("initial commit")
|
git.commit().setCommitter(committer).setSign(Boolean.TRUE)
|
||||||
|
.setMessage("initial commit")
|
||||||
.call();
|
.call();
|
||||||
assertNull(signingKey[0]);
|
assertNull(signingKey[0]);
|
||||||
assertEquals(1, callCount.get());
|
assertEquals(1, callCount.get());
|
||||||
|
assertSame(committer, signingCommitters[0]);
|
||||||
|
|
||||||
writeTrashFile("file2", "file2");
|
writeTrashFile("file2", "file2");
|
||||||
git.add().addFilepattern("file2").call();
|
git.add().addFilepattern("file2").call();
|
||||||
|
@ -665,20 +672,24 @@ public void sign(CommitBuilder commit, String gpgSigningKey) {
|
||||||
expectedConfigSigningKey);
|
expectedConfigSigningKey);
|
||||||
config.save();
|
config.save();
|
||||||
|
|
||||||
git.commit().setSign(Boolean.TRUE).setMessage("initial commit")
|
git.commit().setCommitter(committer).setSign(Boolean.TRUE)
|
||||||
|
.setMessage("initial commit")
|
||||||
.call();
|
.call();
|
||||||
assertEquals(expectedConfigSigningKey, signingKey[0]);
|
assertEquals(expectedConfigSigningKey, signingKey[0]);
|
||||||
assertEquals(2, callCount.get());
|
assertEquals(2, callCount.get());
|
||||||
|
assertSame(committer, signingCommitters[0]);
|
||||||
|
|
||||||
writeTrashFile("file3", "file3");
|
writeTrashFile("file3", "file3");
|
||||||
git.add().addFilepattern("file3").call();
|
git.add().addFilepattern("file3").call();
|
||||||
|
|
||||||
// now use specific on api
|
// now use specific on api
|
||||||
String expectedSigningKey = "my-" + System.nanoTime();
|
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();
|
.setMessage("initial commit").call();
|
||||||
assertEquals(expectedSigningKey, signingKey[0]);
|
assertEquals(expectedSigningKey, signingKey[0]);
|
||||||
assertEquals(3, callCount.get());
|
assertEquals(3, callCount.get());
|
||||||
|
assertSame(committer, signingCommitters[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,7 +702,8 @@ public void callSignerOnlyWhenSigning() throws Exception {
|
||||||
AtomicInteger callCount = new AtomicInteger();
|
AtomicInteger callCount = new AtomicInteger();
|
||||||
GpgSigner.setDefault(new GpgSigner() {
|
GpgSigner.setDefault(new GpgSigner() {
|
||||||
@Override
|
@Override
|
||||||
public void sign(CommitBuilder commit, String gpgSigningKey) {
|
public void sign(CommitBuilder commit, String gpgSigningKey,
|
||||||
|
PersonIdent signingCommitter, CredentialsProvider credentialsProvider) {
|
||||||
callCount.incrementAndGet();
|
callCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,6 +26,9 @@ java_library(
|
||||||
"//lib:jsch",
|
"//lib:jsch",
|
||||||
"//lib:jzlib",
|
"//lib:jzlib",
|
||||||
"//lib:slf4j-api",
|
"//lib:slf4j-api",
|
||||||
|
"//lib:bcpg",
|
||||||
|
"//lib:bcprov",
|
||||||
|
"//lib:bcpkix"
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)",
|
com.jcraft.jsch;version="[0.1.37,0.2.0)",
|
||||||
javax.crypto,
|
javax.crypto,
|
||||||
javax.net.ssl,
|
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.slf4j;version="[1.7.0,2.0.0)",
|
||||||
org.xml.sax,
|
org.xml.sax,
|
||||||
org.xml.sax.helpers
|
org.xml.sax.helpers
|
||||||
|
|
|
@ -84,12 +84,26 @@
|
||||||
<artifactId>JavaEWAH</artifactId>
|
<artifactId>JavaEWAH</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -223,6 +223,7 @@ createBranchUnexpectedResult=Create branch returned unexpected result {0}
|
||||||
createNewFileFailed=Could not create new file {0}
|
createNewFileFailed=Could not create new file {0}
|
||||||
createRequiresZeroOldId=Create requires old ID to be zero
|
createRequiresZeroOldId=Create requires old ID to be zero
|
||||||
credentialPassword=Password
|
credentialPassword=Password
|
||||||
|
credentialPassphrase=Passphrase
|
||||||
credentialUsername=Username
|
credentialUsername=Username
|
||||||
daemonAlreadyRunning=Daemon already running
|
daemonAlreadyRunning=Daemon already running
|
||||||
daysAgo={0} days ago
|
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.
|
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.
|
gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire.
|
||||||
gitmodulesNotFound=.gitmodules not found in tree.
|
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
|
headRequiredToStash=HEAD required to stash local changes
|
||||||
hoursAgo={0} hours ago
|
hoursAgo={0} hours ago
|
||||||
httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments
|
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}''
|
unableToRemovePath=Unable to remove path ''{0}''
|
||||||
unableToStore=Unable to store {0}.
|
unableToStore=Unable to store {0}.
|
||||||
unableToWrite=Unable to write {0}
|
unableToWrite=Unable to write {0}
|
||||||
|
unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available.
|
||||||
unauthorized=Unauthorized
|
unauthorized=Unauthorized
|
||||||
underflowedReftableBlock=Underflowed reftable block
|
underflowedReftableBlock=Underflowed reftable block
|
||||||
unencodeableFile=Unencodable file: {0}
|
unencodeableFile=Unencodable file: {0}
|
||||||
|
|
|
@ -88,10 +88,12 @@
|
||||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
|
import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevObject;
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
import org.eclipse.jgit.revwalk.RevTag;
|
import org.eclipse.jgit.revwalk.RevTag;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
@ -149,6 +151,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
|
||||||
|
|
||||||
private GpgSigner gpgSigner;
|
private GpgSigner gpgSigner;
|
||||||
|
|
||||||
|
private CredentialsProvider credentialsProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for CommitCommand
|
* Constructor for CommitCommand
|
||||||
*
|
*
|
||||||
|
@ -157,6 +161,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
|
||||||
*/
|
*/
|
||||||
protected CommitCommand(Repository repo) {
|
protected CommitCommand(Repository repo) {
|
||||||
super(repo);
|
super(repo);
|
||||||
|
this.credentialsProvider = CredentialsProvider.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -263,7 +268,8 @@ public RevCommit call() throws GitAPIException, NoHeadException,
|
||||||
commit.setTreeId(indexTreeId);
|
commit.setTreeId(indexTreeId);
|
||||||
|
|
||||||
if (signCommit.booleanValue()) {
|
if (signCommit.booleanValue()) {
|
||||||
gpgSigner.sign(commit, signingKey);
|
gpgSigner.sign(commit, signingKey, committer,
|
||||||
|
credentialsProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectId commitId = odi.insert(commit);
|
ObjectId commitId = odi.insert(commit);
|
||||||
|
@ -603,6 +609,9 @@ private void processOptions(RepositoryState state, RevWalk rw)
|
||||||
JGitText.get().onlyOpenPgpSupportedForSigning);
|
JGitText.get().onlyOpenPgpSupportedForSigning);
|
||||||
}
|
}
|
||||||
gpgSigner = GpgSigner.getDefault();
|
gpgSigner = GpgSigner.getDefault();
|
||||||
|
if (gpgSigner == null) {
|
||||||
|
gpgSigner = new BouncyCastleGpgSigner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -943,4 +952,17 @@ public CommitCommand setSign(Boolean sign) {
|
||||||
this.signCommit = sign;
|
this.signCommit = sign;
|
||||||
return this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,7 @@ public static JGitText get() {
|
||||||
/***/ public String createNewFileFailed;
|
/***/ public String createNewFileFailed;
|
||||||
/***/ public String createRequiresZeroOldId;
|
/***/ public String createRequiresZeroOldId;
|
||||||
/***/ public String credentialPassword;
|
/***/ public String credentialPassword;
|
||||||
|
/***/ public String credentialPassphrase;
|
||||||
/***/ public String credentialUsername;
|
/***/ public String credentialUsername;
|
||||||
/***/ public String daemonAlreadyRunning;
|
/***/ public String daemonAlreadyRunning;
|
||||||
/***/ public String daysAgo;
|
/***/ public String daysAgo;
|
||||||
|
@ -384,6 +385,14 @@ public static JGitText get() {
|
||||||
/***/ public String gcLogExists;
|
/***/ public String gcLogExists;
|
||||||
/***/ public String gcTooManyUnpruned;
|
/***/ public String gcTooManyUnpruned;
|
||||||
/***/ public String gitmodulesNotFound;
|
/***/ 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 headRequiredToStash;
|
||||||
/***/ public String hoursAgo;
|
/***/ public String hoursAgo;
|
||||||
/***/ public String httpConfigCannotNormalizeURL;
|
/***/ public String httpConfigCannotNormalizeURL;
|
||||||
|
@ -776,6 +785,7 @@ public static JGitText get() {
|
||||||
/***/ public String unableToRemovePath;
|
/***/ public String unableToRemovePath;
|
||||||
/***/ public String unableToStore;
|
/***/ public String unableToStore;
|
||||||
/***/ public String unableToWrite;
|
/***/ public String unableToWrite;
|
||||||
|
/***/ public String unableToSignCommitNoSecretKey;
|
||||||
/***/ public String unauthorized;
|
/***/ public String unauthorized;
|
||||||
/***/ public String underflowedReftableBlock;
|
/***/ public String underflowedReftableBlock;
|
||||||
/***/ public String unencodeableFile;
|
/***/ public String unencodeableFile;
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
package org.eclipse.jgit.lib;
|
package org.eclipse.jgit.lib;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
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.
|
* Creates GPG signatures for Git objects.
|
||||||
|
@ -51,7 +54,7 @@
|
||||||
*/
|
*/
|
||||||
public abstract class GpgSigner {
|
public abstract class GpgSigner {
|
||||||
|
|
||||||
private static GpgSigner defaultSigner;
|
private static GpgSigner defaultSigner = new BouncyCastleGpgSigner();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default signer, or <code>null</code>.
|
* Get the default signer, or <code>null</code>.
|
||||||
|
@ -93,8 +96,17 @@ public static void setDefault(GpgSigner signer) {
|
||||||
* complete to allow proper calculation of payload)
|
* complete to allow proper calculation of payload)
|
||||||
* @param gpgSigningKey
|
* @param gpgSigningKey
|
||||||
* the signing key (passed as is to the GPG signing tool)
|
* 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,
|
public abstract void sign(@NonNull CommitBuilder commit,
|
||||||
String gpgSigningKey);
|
String gpgSigningKey, @NonNull PersonIdent committer,
|
||||||
|
CredentialsProvider credentialsProvider) throws CanceledException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
pom.xml
26
pom.xml
|
@ -202,6 +202,7 @@
|
||||||
<maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version>
|
<maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version>
|
||||||
<tycho-extras-version>1.3.0</tycho-extras-version>
|
<tycho-extras-version>1.3.0</tycho-extras-version>
|
||||||
<gson-version>2.8.2</gson-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>
|
<spotbugs-maven-plugin-version>3.1.10</spotbugs-maven-plugin-version>
|
||||||
<maven-surefire-version>2.22.1</maven-surefire-version>
|
<maven-surefire-version>2.22.1</maven-surefire-version>
|
||||||
<maven-compiler-plugin-version>3.8.0</maven-compiler-plugin-version>
|
<maven-compiler-plugin-version>3.8.0</maven-compiler-plugin-version>
|
||||||
|
@ -394,6 +395,12 @@
|
||||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
<version>${maven-project-info-reports-plugin-version}</version>
|
<version>${maven-project-info-reports-plugin-version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>2.1.2.RELEASE</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
|
@ -760,6 +767,25 @@
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
<version>${gson-version}</version>
|
<version>${gson-version}</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue