Browse Source

Merge branch 'master' into next

* master: (143 commits)
  Prepare 5.11.0-SNAPSHOT builds
  JGit v5.11.0.202102240950-m3
  [releng] japicmp: update last release version
  IgnoreNode: include path to file for invalid .gitignore patterns
  FastIgnoreRule: include bad pattern in log message
  init: add config option to set default for the initial branch name
  init: allow specifying the initial branch name for the new repository
  Fail clone if initial branch doesn't exist in remote repository
  GPG: fix reading unprotected old-format secret keys
  Update Orbit to S20210216215844
  Add missing bazel dependency for o.e.j.gpg.bc.test
  GPG: handle extended private key format
  dfs: handle short copies
  [GPG] Provide a factory for the BouncyCastleGpgSigner
  Fix boxing warnings
  GPG: compute the keygrip to find a secret key
  GPG signature verification via BouncyCastle
  Post commit hook failure should not cause commit failure
  Allow to define additional Hook classes outside JGit
  GitHook: use default charset for output and error streams
  ...

Change-Id: I689f4070e79f4a0ac1c02b35698ccaab68ad2f34
changes/17/176917/2
Matthias Sohn 3 years ago
parent
commit
f659797199
100 changed files with 5389 additions and 444 deletions
  1. 1
    1
      .bazelversion
  2. 2
    0
      BUILD
  3. 58
    23
      WORKSPACE
  4. 6
    1
      lib/BUILD
  5. 2
    2
      org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
  6. 1
    0
      org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
  7. 11
    0
      org.eclipse.jgit.archive/.settings/.api_filters
  8. 1
    0
      org.eclipse.jgit.archive/META-INF/MANIFEST.MF
  9. 1
    0
      org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
  10. 1
    0
      org.eclipse.jgit.archive/resources/org/eclipse/jgit/archive/internal/ArchiveText.properties
  11. 36
    0
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/BaseFormat.java
  12. 7
    1
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java
  13. 10
    1
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java
  14. 7
    1
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java
  15. 6
    1
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java
  16. 1
    0
      org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java
  17. 145
    0
      org.eclipse.jgit.benchmarks/.settings/edu.umd.cs.findbugs.core.prefs
  18. 8
    0
      org.eclipse.jgit.benchmarks/findBugs/FindBugsExcludeFilter.xml
  19. 24
    4
      org.eclipse.jgit.benchmarks/pom.xml
  20. 7
    2
      org.eclipse.jgit.gpg.bc.test/.classpath
  21. 1
    0
      org.eclipse.jgit.gpg.bc.test/.gitignore
  22. 2
    2
      org.eclipse.jgit.gpg.bc.test/.settings/org.eclipse.jdt.core.prefs
  23. 21
    0
      org.eclipse.jgit.gpg.bc.test/BUILD
  24. 13
    3
      org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
  25. 1
    1
      org.eclipse.jgit.gpg.bc.test/build.properties
  26. 6
    0
      org.eclipse.jgit.gpg.bc.test/pom.xml
  27. 41
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc
  28. 42
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key
  29. 42
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc
  30. 45
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key
  31. 30
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc
  32. BIN
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key
  33. 30
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc
  34. 32
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key
  35. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc
  36. 17
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc
  37. 19
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc
  38. 44
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc
  39. 9
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc
  40. 23
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key
  41. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc
  42. 16
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc
  43. 19
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc
  44. 40
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc
  45. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc
  46. 13
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc
  47. 6
    5
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocatorTest.java
  48. 61
    0
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java
  49. 143
    0
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java
  50. 163
    0
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
  51. 16
    8
      org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
  52. 1
    0
      org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
  53. 38
    45
      org.eclipse.jgit.gpg.bc/about.html
  54. 1
    0
      org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory
  55. 26
    1
      org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
  56. 34
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java
  57. 34
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
  58. 125
    99
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java
  59. 3
    4
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java
  60. 388
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java
  61. 28
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java
  62. 53
    9
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
  63. 322
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java
  64. 121
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java
  65. 826
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java
  66. 110
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java
  67. 597
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java
  68. 12
    12
      org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
  69. 1
    0
      org.eclipse.jgit.http.apache/resources/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.properties
  70. 24
    10
      org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
  71. 82
    9
      org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java
  72. 1
    0
      org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java
  73. 2
    2
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
  74. 2
    2
      org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
  75. 2
    1
      org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
  76. 2
    0
      org.eclipse.jgit.http.test/build.properties
  77. 11
    0
      org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java
  78. 99
    0
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AllProtocolsHttpTestCase.java
  79. 4
    0
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
  80. 7
    4
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
  81. 16
    7
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
  82. 12
    11
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java
  83. 476
    80
      org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
  84. 2
    2
      org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
  85. 145
    0
      org.eclipse.jgit.junit.ssh/.settings/edu.umd.cs.findbugs.core.prefs
  86. 25
    22
      org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
  87. 9
    0
      org.eclipse.jgit.junit.ssh/findBugs/FindBugsExcludeFilter.xml
  88. 71
    0
      org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshBasicTestBase.java
  89. 1
    19
      org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java
  90. 49
    17
      org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
  91. 10
    4
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
  92. 3
    3
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
  93. 5
    5
      org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
  94. 0
    1
      org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
  95. 8
    0
      org.eclipse.jgit.lfs.server/findBugs/FindBugsExcludeFilter.xml
  96. 2
    2
      org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
  97. 267
    3
      org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
  98. 0
    2
      org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
  99. 60
    12
      org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
  100. 0
    0
      org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java

+ 1
- 1
.bazelversion View File

@@ -1 +1 @@
3.5.0
4.0.0

+ 2
- 0
BUILD View File

@@ -13,6 +13,8 @@ genrule(
"//org.eclipse.jgit.lfs:jgit-lfs",
"//org.eclipse.jgit.lfs.server:jgit-lfs-server",
"//org.eclipse.jgit.junit:junit",
"//org.eclipse.jgit.ssh.apache:ssh-apache",
"//org.eclipse.jgit.ssh.jsch:ssh-jsch",
],
outs = ["all.zip"],
cmd = " && ".join([

+ 58
- 23
WORKSPACE View File

@@ -27,6 +27,34 @@ load(
"maven_jar",
)

http_archive(
name = "openjdk15_linux_archive",
build_file_content = """
java_runtime(name = 'runtime', srcs = glob(['**']), visibility = ['//visibility:public'])
exports_files(["WORKSPACE"], visibility = ["//visibility:public"])
""",
sha256 = "0a38f1138c15a4f243b75eb82f8ef40855afcc402e3c2a6de97ce8235011b1ad",
strip_prefix = "zulu15.27.17-ca-jdk15.0.0-linux_x64",
urls = [
"https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz",
"https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz",
],
)

http_archive(
name = "openjdk15_darwin_archive",
build_file_content = """
java_runtime(name = 'runtime', srcs = glob(['**']), visibility = ['//visibility:public'])
exports_files(["WORKSPACE"], visibility = ["//visibility:public"])
""",
sha256 = "f80b2e0512d9d8a92be24497334c974bfecc8c898fc215ce0e76594f00437482",
strip_prefix = "zulu15.27.17-ca-jdk15.0.0-macosx_x64",
urls = [
"https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz",
"https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz",
],
)

JMH_VERS = "1.21"

maven_jar(
@@ -83,26 +111,26 @@ maven_jar(

maven_jar(
name = "httpclient",
artifact = "org.apache.httpcomponents:httpclient:4.5.10",
sha1 = "7ca2e4276f4ef95e4db725a8cd4a1d1e7585b9e5",
artifact = "org.apache.httpcomponents:httpclient:4.5.13",
sha1 = "e5f6cae5ca7ecaac1ec2827a9e2d65ae2869cada",
)

maven_jar(
name = "httpcore",
artifact = "org.apache.httpcomponents:httpcore:4.4.12",
sha1 = "21ebaf6d532bc350ba95bd81938fa5f0e511c132",
artifact = "org.apache.httpcomponents:httpcore:4.4.14",
sha1 = "9dd1a631c082d92ecd4bd8fd4cf55026c720a8c1",
)

maven_jar(
name = "sshd-osgi",
artifact = "org.apache.sshd:sshd-osgi:2.4.0",
sha1 = "fc4551c1eeda35e4671b263297d37d2bca81c4d4",
artifact = "org.apache.sshd:sshd-osgi:2.6.0",
sha1 = "40e365bb799e1bff3d31dc858b1e59a93c123f29",
)

maven_jar(
name = "sshd-sftp",
artifact = "org.apache.sshd:sshd-sftp:2.4.0",
sha1 = "92e1b7d1e19c715efb4a8871d34145da8f87cdb2",
artifact = "org.apache.sshd:sshd-sftp:2.6.0",
sha1 = "6eddfe8fdf59a3d9a49151e4177f8c1bebeb30c9",
)

maven_jar(
@@ -205,52 +233,59 @@ maven_jar(

maven_jar(
name = "gson",
artifact = "com.google.code.gson:gson:2.8.2",
sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf",
artifact = "com.google.code.gson:gson:2.8.6",
sha1 = "9180733b7df8542621dc12e21e87557e8c99b8cb",
)

JETTY_VER = "9.4.30.v20200611"
JETTY_VER = "9.4.36.v20210114"

maven_jar(
name = "jetty-servlet",
artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER,
sha1 = "ca3dea2cd34ee88cec017001603af0c9e74781d6",
src_sha1 = "6908f24428060bd542bddfa3e89e03d0dbbc2a6d",
sha1 = "b189e52a5ee55ae172e4e99e29c5c314f5daf4b9",
src_sha1 = "3a0fa449772ab0d76625f6afb81f60c32a490613",
)

maven_jar(
name = "jetty-security",
artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
sha1 = "1a5261f6ad4081ad9e9bb01416d639931d391273",
src_sha1 = "6ca41b34aa4f84c267603edd4b069122bd5f17d3",
sha1 = "42030d6ed7dfc0f75818cde0adcf738efc477574",
src_sha1 = "612220a97d45fad3983ccc56b0cb9a271f3fd003",
)

maven_jar(
name = "jetty-server",
artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
sha1 = "e5ede3724d062717d0c04e4c77f74fe8115c2a6f",
src_sha1 = "c8b02a47a35c1f083b310cbd202738cf08bc1d55",
sha1 = "88a7d342974aadca658e7386e8d0fcc5c0788f41",
src_sha1 = "4552c0c6db2948e8557db477b6b48d291006e481",
)

maven_jar(
name = "jetty-http",
artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
sha1 = "cd6223382e4f82b9ea807d8cdb04a23e5d629f1c",
src_sha1 = "00520c04b10609b981159b5ca284b5a158c077a9",
sha1 = "1eee89a55e04ff94df0f85d95200fc48acb43d86",
src_sha1 = "552a784ec789c7ba581c5341ae6d8b6353ed5ace",
)

maven_jar(
name = "jetty-io",
artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
sha1 = "9c360d08e903b2dbd5d1f8e889a32046948628ce",
src_sha1 = "dac8f8a3f84afdd3686d36f58b5ccb276961b8ce",
sha1 = "84a8faf9031eb45a5a2ddb7681e22c483d81ab3a",
src_sha1 = "72d5fc6d909e28f8720394b25babda80805a46b9",
)

maven_jar(
name = "jetty-util",
artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
sha1 = "39ec6aa4745952077f5407cb1394d8ba2db88b13",
src_sha1 = "f41f9391f91884a79350f3ad9b09b8e46c9be0ec",
sha1 = "925257fbcca6b501a25252c7447dbedb021f7404",
src_sha1 = "532e8b66044f4e58ca5da3aec19f02a2f3c16ddd",
)

maven_jar(
name = "jetty-util-ajax",
artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER,
sha1 = "2f478130c21787073facb64d7242e06f94980c60",
src_sha1 = "7153d7ca38878d971fd90992c303bb7719ba7a21",
)

BOUNCYCASTLE_VER = "1.65"

+ 6
- 1
lib/BUILD View File

@@ -132,7 +132,10 @@ java_library(
name = "jetty-servlet",
# TODO: This should be testonly but org.eclipse.jgit.pgm depends on it.
visibility = ["//visibility:public"],
exports = ["@jetty-servlet//jar"],
exports = [
"@jetty-servlet//jar",
"@jetty-util-ajax//jar",
],
)

java_library(
@@ -159,6 +162,7 @@ java_library(
"//org.eclipse.jgit:__pkg__",
"//org.eclipse.jgit.gpg.bc:__pkg__",
"//org.eclipse.jgit.test:__pkg__",
"//org.eclipse.jgit.gpg.bc.test:__pkg__",
],
exports = ["@bcpg//jar"],
)
@@ -169,6 +173,7 @@ java_library(
"//org.eclipse.jgit:__pkg__",
"//org.eclipse.jgit.gpg.bc:__pkg__",
"//org.eclipse.jgit.test:__pkg__",
"//org.eclipse.jgit.gpg.bc.test:__pkg__",
],
exports = ["@bcprov//jar"],
)

+ 2
- 2
org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs View File

@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private

+ 1
- 0
org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF View File

@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.ant.source
Bundle-Vendor: Eclipse.org - JGit
Bundle-Version: 6.0.0.qualifier
Eclipse-SourceBundle: org.eclipse.jgit.ant;version="6.0.0.qualifier";roots="."


+ 11
- 0
org.eclipse.jgit.archive/.settings/.api_filters View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit.archive" version="2">
<resource path="src/org/eclipse/jgit/archive/BaseFormat.java" type="org.eclipse.jgit.archive.BaseFormat">
<filter id="336658481">
<message_arguments>
<message_argument value="org.eclipse.jgit.archive.BaseFormat"/>
<message_argument value="COMPRESSION_LEVEL"/>
</message_arguments>
</filter>
</resource>
</component>

+ 1
- 0
org.eclipse.jgit.archive/META-INF/MANIFEST.MF View File

@@ -27,3 +27,4 @@ Export-Package: org.eclipse.jgit.archive;version="6.0.0";
org.apache.commons.compress.archivers,
org.osgi.framework",
org.eclipse.jgit.archive.internal;version="6.0.0";x-internal:=true


+ 1
- 0
org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF View File

@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.archive.source
Bundle-Vendor: Eclipse.org - JGit
Bundle-Version: 6.0.0.qualifier
Eclipse-SourceBundle: org.eclipse.jgit.archive;version="6.0.0.qualifier";roots="."


+ 1
- 0
org.eclipse.jgit.archive/resources/org/eclipse/jgit/archive/internal/ArchiveText.properties View File

@@ -1,3 +1,4 @@
cannotSetOption=Cannot set option: {0}
invalidCompressionLevel=Invalid compression level: {0}
pathDoesNotMatchMode=Path {0} does not match mode {1}
unsupportedMode=Unsupported mode {0}

+ 36
- 0
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/BaseFormat.java View File

@@ -25,6 +25,11 @@ import org.eclipse.jgit.util.StringUtils;
* @since 4.0
*/
public class BaseFormat {
/**
* Compression-level for the archive file. Only values in [0-9] are allowed.
* @since 5.11
*/
protected static final String COMPRESSION_LEVEL = "compression-level"; //$NON-NLS-1$

/**
* Apply options to archive output stream
@@ -40,6 +45,9 @@ public class BaseFormat {
Map<String, Object> o) throws IOException {
for (Map.Entry<String, Object> p : o.entrySet()) {
try {
if (p.getKey().equals(COMPRESSION_LEVEL)) {
continue;
}
new Statement(s, "set" + StringUtils.capitalize(p.getKey()), //$NON-NLS-1$
new Object[] { p.getValue() }).execute();
} catch (Exception e) {
@@ -49,4 +57,32 @@ public class BaseFormat {
}
return s;
}

/**
* Removes and returns the {@link #COMPRESSION_LEVEL} key from the input map
* parameter if it exists, or -1 if this key does not exist.
*
* @param o
* options map
* @return The compression level if it exists in the map, or -1 instead.
* @throws IllegalArgumentException
* if the {@link #COMPRESSION_LEVEL} option does not parse to an
* Integer.
* @since 5.11
*/
protected int getCompressionLevel(Map<String, Object> o) {
if (!o.containsKey(COMPRESSION_LEVEL)) {
return -1;
}
Object option = o.get(COMPRESSION_LEVEL);
try {
Integer compressionLevel = (Integer) option;
return compressionLevel.intValue();
} catch (ClassCastException e) {
throw new IllegalArgumentException(
MessageFormat.format(
ArchiveText.get().invalidCompressionLevel, option),
e);
}
}
}

+ 7
- 1
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java View File

@@ -45,7 +45,13 @@ public final class Tbz2Format extends BaseFormat implements
@Override
public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
Map<String, Object> o) throws IOException {
BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(s);
BZip2CompressorOutputStream out;
int compressionLevel = getCompressionLevel(o);
if (compressionLevel != -1) {
out = new BZip2CompressorOutputStream(s, compressionLevel);
} else {
out = new BZip2CompressorOutputStream(s);
}
return tarFormat.createArchiveOutputStream(out, o);
}


+ 10
- 1
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java View File

@@ -18,6 +18,7 @@ import java.util.Map;

import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipParameters;
import org.eclipse.jgit.api.ArchiveCommand;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
@@ -45,7 +46,15 @@ public final class TgzFormat extends BaseFormat implements
@Override
public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
Map<String, Object> o) throws IOException {
GzipCompressorOutputStream out = new GzipCompressorOutputStream(s);
GzipCompressorOutputStream out;
int compressionLevel = getCompressionLevel(o);
if (compressionLevel != -1) {
GzipParameters parameters = new GzipParameters();
parameters.setCompressionLevel(compressionLevel);
out = new GzipCompressorOutputStream(s, parameters);
} else {
out = new GzipCompressorOutputStream(s);
}
return tarFormat.createArchiveOutputStream(out, o);
}


+ 7
- 1
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java View File

@@ -45,7 +45,13 @@ public final class TxzFormat extends BaseFormat implements
@Override
public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
Map<String, Object> o) throws IOException {
XZCompressorOutputStream out = new XZCompressorOutputStream(s);
XZCompressorOutputStream out;
int compressionLevel = getCompressionLevel(o);
if (compressionLevel != -1) {
out = new XZCompressorOutputStream(s, compressionLevel);
} else {
out = new XZCompressorOutputStream(s);
}
return tarFormat.createArchiveOutputStream(out, o);
}


+ 6
- 1
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java View File

@@ -47,7 +47,12 @@ public final class ZipFormat extends BaseFormat implements
@Override
public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
Map<String, Object> o) throws IOException {
return applyFormatOptions(new ZipArchiveOutputStream(s), o);
ZipArchiveOutputStream out = new ZipArchiveOutputStream(s);
int compressionLevel = getCompressionLevel(o);
if (compressionLevel != -1) {
out.setLevel(compressionLevel);
}
return applyFormatOptions(out, o);
}

/** {@inheritDoc} */

+ 1
- 0
org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java View File

@@ -28,6 +28,7 @@ public class ArchiveText extends TranslationBundle {

// @formatter:off
/***/ public String cannotSetOption;
/***/ public String invalidCompressionLevel;
/***/ public String pathDoesNotMatchMode;
/***/ public String unsupportedMode;
}

+ 145
- 0
org.eclipse.jgit.benchmarks/.settings/edu.umd.cs.findbugs.core.prefs View File

@@ -0,0 +1,145 @@
#SpotBugs User Preferences
#Fri Dec 04 10:39:51 CET 2020
detectorExplicitSerialization=ExplicitSerialization|true
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
detectorWrongMapIterator=WrongMapIterator|true
detectorUnnecessaryMath=UnnecessaryMath|true
detectorUselessSubclassMethod=UselessSubclassMethod|false
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|15
detectorURLProblems=URLProblems|true
detectorIteratorIdioms=IteratorIdioms|true
detectorMutableEnum=MutableEnum|true
detectorFindNonShortCircuit=FindNonShortCircuit|true
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
detectorVolatileUsage=VolatileUsage|true
detectorFindNakedNotify=FindNakedNotify|true
detectorFindUninitializedGet=FindUninitializedGet|true
detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
detectorSwitchFallthrough=SwitchFallthrough|true
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
detectorConfusedInheritance=ConfusedInheritance|true
detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true
detectorMutableStaticFields=MutableStaticFields|true
detectorInvalidJUnitTest=InvalidJUnitTest|true
detectorInfiniteLoop=InfiniteLoop|true
detectorFindRunInvocations=FindRunInvocations|true
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
detectorXMLFactoryBypass=XMLFactoryBypass|true
detectorFindOpenStream=FindOpenStream|true
detectorCheckExpectedWarnings=CheckExpectedWarnings|false
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
detectorStringConcatenation=StringConcatenation|true
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
detectorFinalizerNullsFields=FinalizerNullsFields|true
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
detectorInefficientToArray=InefficientToArray|false
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
detectorInconsistentAnnotations=InconsistentAnnotations|true
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
detectorInstantiateStaticClass=InstantiateStaticClass|true
detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true
detectorMethodReturnCheck=MethodReturnCheck|true
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
detectorFindDoubleCheck=FindDoubleCheck|true
detectorFindBadForLoop=FindBadForLoop|true
detectorDefaultEncodingDetector=DefaultEncodingDetector|true
detectorFindInconsistentSync2=FindInconsistentSync2|true
detectorFindSpinLoop=FindSpinLoop|true
detectorFindMaskedFields=FindMaskedFields|true
detectorBooleanReturnNull=BooleanReturnNull|true
detectorFindUnsyncGet=FindUnsyncGet|true
detectorCrossSiteScripting=CrossSiteScripting|true
detectorDroppedException=DroppedException|true
detectorFindDeadLocalStores=FindDeadLocalStores|true
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
detectorFindRefComparison=FindRefComparison|true
detectorFindRoughConstants=FindRoughConstants|true
detectorMutableLock=MutableLock|true
detectorFindNullDeref=FindNullDeref|true
detectorFindReturnRef=FindReturnRef|true
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
detectorFindUselessControlFlow=FindUselessControlFlow|true
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
detectorFindSelfComparison=FindSelfComparison|true
detectorFindFloatEquality=FindFloatEquality|true
detectorFindComparatorProblems=FindComparatorProblems|true
detectorRepeatedConditionals=RepeatedConditionals|true
filter_settings_neg=NOISE|
detectorInefficientMemberAccess=InefficientMemberAccess|false
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
detectorNumberConstructor=NumberConstructor|true
detectorDontAssertInstanceofInTests=DontAssertInstanceofInTests|true
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
detectorFindUnconditionalWait=FindUnconditionalWait|true
detectorFindTwoLockWait=FindTwoLockWait|true
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
detectorFindUnreleasedLock=FindUnreleasedLock|true
detectorInefficientIndexOf=InefficientIndexOf|false
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
detectorOverridingMethodsMustInvokeSuperDetector=OverridingMethodsMustInvokeSuperDetector|true
detectorWaitInLoop=WaitInLoop|true
detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
detectorFindSqlInjection=FindSqlInjection|true
detectorUnreadFields=UnreadFields|true
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
detectorFindUselessObjects=FindUselessObjects|true
detectorBadAppletConstructor=BadAppletConstructor|false
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
detectorSerializableIdiom=SerializableIdiom|true
detectorNaming=Naming|true
detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true
detectorFormatStringChecker=FormatStringChecker|true
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
detectorEmptyZipFileEntry=EmptyZipFileEntry|false
detectorFindCircularDependencies=FindCircularDependencies|false
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
detectorAtomicityProblem=AtomicityProblem|true
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
detectorInitializationChain=InitializationChain|true
detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true
detectorOptionalReturnNull=OptionalReturnNull|true
detectorStartInConstructor=StartInConstructor|true
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
detectorRedundantConditions=RedundantConditions|true
effort=default
detectorRedundantInterfaces=RedundantInterfaces|true
detectorDuplicateBranches=DuplicateBranches|true
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
detectorComparatorIdiom=ComparatorIdiom|true
detectorFindBadCast2=FindBadCast2|true
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
excludefilter0=findBugs/FindBugsExcludeFilter.xml|true
detectorBadResultSetAccess=BadResultSetAccess|true
detectorIncompatMask=IncompatMask|true
detectorCovariantArrayAssignment=CovariantArrayAssignment|false
detectorDumbMethodInvocations=DumbMethodInvocations|true
run_at_full_build=false
detectorStaticCalendarDetector=StaticCalendarDetector|true
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
detectorVarArgsProblems=VarArgsProblems|true
detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|false
detectorCloneIdiom=CloneIdiom|true
detectorFindHEmismatch=FindHEmismatch|true
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
detectorFindSelfComparison2=FindSelfComparison2|true
detectorLazyInit=LazyInit|true
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
detectorDontUseEnum=DontUseEnum|true
detectorFindPuzzlers=FindPuzzlers|true
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
detector_threshold=2
detectorPublicSemaphores=PublicSemaphores|false
detectorDumbMethods=DumbMethods|true

+ 8
- 0
org.eclipse.jgit.benchmarks/findBugs/FindBugsExcludeFilter.xml View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<FindBugsFilter>
<!-- Silence warnings in classes generated by apt -->
<Match>
<Package name="org.eclipse.jgit.benchmarks.generated" />
<Bug pattern="DLS_DEAD_LOCAL_STORE" />
</Match>
</FindBugsFilter>

+ 24
- 4
org.eclipse.jgit.benchmarks/pom.xml View File

@@ -49,6 +49,26 @@
<build>
<sourceDirectory>src/</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>3.6.3</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -95,7 +115,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
@@ -157,19 +177,19 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.8.2</version>
<version>3.9.1</version>
<dependencies>
<dependency><!-- add support for ssh/scp -->
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>3.3.4</version>
<version>3.4.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.0.0-M3</version>
<version>3.0.0-M5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

+ 7
- 2
org.eclipse.jgit.gpg.bc.test/.classpath View File

@@ -2,10 +2,15 @@
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="tst">
<classpathentry kind="src" output="bin-tst" path="tst">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
<classpathentry kind="src" output="bin-tst" path="tst-rsrc">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin-tst"/>
</classpath>

+ 1
- 0
org.eclipse.jgit.gpg.bc.test/.gitignore View File

@@ -1,2 +1,3 @@
/bin
/bin-tst
/target

+ 2
- 2
org.eclipse.jgit.gpg.bc.test/.settings/org.eclipse.jdt.core.prefs View File

@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private

+ 21
- 0
org.eclipse.jgit.gpg.bc.test/BUILD View File

@@ -1,3 +1,8 @@
load(
"@com_googlesource_gerrit_bazlets//tools:genrule2.bzl",
"genrule2",
)
load("@rules_java//java:defs.bzl", "java_import")
load(
"@com_googlesource_gerrit_bazlets//tools:junit.bzl",
"junit_tests",
@@ -8,7 +13,23 @@ junit_tests(
srcs = glob(["tst/**/*.java"]),
tags = ["bc"],
deps = [
"//lib:bcpg",
"//lib:bcprov",
"//lib:junit",
"//org.eclipse.jgit:jgit",
"//org.eclipse.jgit.gpg.bc:gpg-bc",
"//org.eclipse.jgit.gpg.bc.test:tst_rsrc",
],
)

java_import(
name = "tst_rsrc",
jars = [":tst_rsrc_jar"],
)

genrule2(
name = "tst_rsrc_jar",
srcs = glob(["tst-rsrc/**"]),
outs = ["tst_rsrc.jar"],
cmd = "o=$$PWD/$@ && tar cf - $(SRCS) | tar -C $$TMP --strip-components=2 -xf - && cd $$TMP && zip -qr $$o .",
)

+ 13
- 3
org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF View File

@@ -7,8 +7,18 @@ Bundle-Version: 6.0.0.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.eclipse.jgit.gpg.bc.internal;version="[6.0.0,6.1.0)",
org.junit;version="[4.13,5.0.0)"
Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true
Import-Package: org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)",
org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)",
org.eclipse.jgit.gpg.bc.internal;version="[6.0.0,6.1.0)",
org.eclipse.jgit.gpg.bc.internal.keys;version="[6.0.0,6.1.0)",
org.eclipse.jgit.util.sha1;version="[6.0.0,6.1.0)",
org.junit;version="[4.13,5.0.0)",
org.junit.runner;version="[4.13,5.0.0)",
org.junit.runners;version="[4.13,5.0.0)"
Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true,
org.eclipse.jgit.gpg.bc.internal.keys;x-internal:=true
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"

+ 1
- 1
org.eclipse.jgit.gpg.bc.test/build.properties View File

@@ -1,5 +1,5 @@
source.. = tst/
output.. = bin/
output.. = bin-tst/
bin.includes = META-INF/,\
.,\
plugin.properties

+ 6
- 0
org.eclipse.jgit.gpg.bc.test/pom.xml View File

@@ -85,6 +85,12 @@
<sourceDirectory>src/</sourceDirectory>
<testSourceDirectory>tst/</testSourceDirectory>

<testResources>
<testResource>
<directory>tst-rsrc/</directory>
</testResource>
</testResources>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

+ 41
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBGAHBLQBDACsS1vFqE3qgKD2R5X9n90Gz8bucwwvJWIqaHDsVoAtF6IcKIDo
1hQC9YksTQYl/L7BsMDdmjyEbWRfzW4ory5596d342Hl6g7ZB5jJR5kJJdhy2MCJ
BUiMy/724Fr/Dz8PNPcEoULz9ZH7HEaPRKqWWEQDUCq5ak0MfLKXtWVUBgsY5Mry
29d/GLJvnxZ5v16PK+P4oqZ7vh7FWJPlqPK2TCZ6s1rYfWlu9XbHOzwXwozVg7IX
tfFq4Rij4c0sg0S0GY8hGAlnOpRc/6J2S41Y8p3WqND6r1LPDQUFnNCKXVoHUGeK
X9U5iAP7pxZSuonsFCqr3CDGxr+kKUpbfZeLrqTA4lBUK7T6w6Wq0qHosCYUU7YC
GZjlEeCZBRWNfeq45LKlhdNUxHWWgaBsgWaaDmpFWaivblmQGOvmSv1nJMNmedRs
DSF51nsJnkQceprsvThSa6qJwEYi7pj6L9HO2UGgJLCb3dL5VTQih2gdhghckUSB
okUkvqBvvdiP2nEAEQEAAbQdVGVzdGVyMiA8dGVzdGVyMkBleGFtcGxlLm9yZz6J
Ac4EEwEKADgWIQRPCAvglun3I2Bs1iITM2XBzCpwbgUCYAcEtAIbAwULCQgHAwUV
CgkICwUWAgMBAAIeAQIXgAAKCRATM2XBzCpwboiBC/493+ruANV2eiro8MY8wZ3Y
gdjp3pHBSg9RK74SIh95J+MW5qzPwkU+vHd8l0+aj9e1sDQb5BFcFk/Z1ioI3TDW
B4vYWoMkdN932fJ/LcIlhOGjWwSNFZphbYmJzrAwUTA499yx3jt9Dg+vSU88S+8S
FzYe6CBNt+PqDCbk6Gm+ZcVpR+elq/QJeyhdDzCCrrfNXwPwsVGAM61Z8SvdvNKE
DA5gHXRsOKf8fu8lqW2Ay0MCvgsZLMIGOMDPCyBUd1bhlU0p18V6D6wdatfzu9gR
X/k36HJyqB2cHh89/F2KdBSonRVRJOvHc/88zEeRFkiV5pUyrXv40l099+5dvA+2
h4ODftY7ZbR22k4iX5rqj2BRow3H+N5lTIWgiADPUl+H8z4ZY5G+LWk9Xms3o1G9
DmEepM3ma1pg4sZbxf0iStikch7aPvL/HgGRPJnDxA/W4KJxqmSw9TTMH/6XHq3D
ah5Z1lbcylChgrFLFVJi+shnLTZSYttTeKOIqTPi0765AY0EYAcEtAEMANS23tqF
Dr69wz0AaT7tjoccT/WlSO/gxd80ShMr4vbr21PZp8qGklFmlcrSrMDRwfXY04x2
qxHR/Kf+hCD5gNvg8kh/yH2lQRcvekzQ4/rLmSXBfGOFg+LioQQ3CZJ1MZyIHzu5
YVZ2pqALfJwJSw9P5Z340y8sq8AOPaJ+cpIC0rYBp9BUAmz9IeLVT7fUc6CjaWBo
++E8H+9FyZC71RIPNcCvY+24Qky8ms7nw4hA47Dlht1pqL8dzOggCnohuSYMCXs2
YPLvDGdZMg7GgQ3AyZawDmjTxFWt51VU5hunGfGiC5Aock8rVHSYsQzUFjVBSR+Y
Zy+c4noxZD1eRfb8KdFnrewyVqGKFtc/JwA61qhhyYFe5AWMAFtudjGYG0WiTP82
CmFFc1Qsvyls9G2yMkLuay5wsdIJMnRW9XwBzwxm0mdZI6D3nSbWjPUUfRcGBY8C
Hqpc736G+UzMevZtorwy/5Q6D8v+Obrk02DIDKa6CJ7g7dTwK0I/fleJlwARAQAB
iQG2BBgBCgAgFiEETwgL4Jbp9yNgbNYiEzNlwcwqcG4FAmAHBLQCGwwACgkQEzNl
wcwqcG6mYQv8CFIVGj7/Qnr+wmviMzm8+B4WwQIUHGryqv9hnfp9hLOXMFmNuEDl
QYkHVChWO7ehrR3fpvpebhcieV19skf/WO8xm0pGSXyjV2/0/bVhXq01xesXHH9r
4aFxsCu0E8M9fZVAHP7NBr4A67knQ4EHRF6Rwml2ba6Zt2oP15IHvsAq/2B3f8ar
5sUau4zM1cItG3tg49rbYr6V71HdgkWA22+EkbXL/Qq3haY/er2dIGc73lu8t7oQ
msGK4LSAGc2661wMvJ6w6feCagkXAtrqyxodhSLoWgF3i0QVQnMbgmYWKEK2B6YA
g669CZCCXJF+9Ebq+PP/d3Cy/k9iUmWDh72C7iL136kYZt+71b+yOmlDRT9l6DvU
FP3bhRZWomOt3F3aP5mAdbwrP1NbvlxTYUAf++nUPdpr0Jrvgi67/VHVjaUtVh/K
gVQ2C+4Cp/fllxXXKQMPcC8dD1x/AL6ytDzPu099ETMULntgbt7A5Lsd/fFScnF3
ZNx6wjRReIvT
=8E/K
-----END PGP PUBLIC KEY BLOCK-----

+ 42
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key View File

@@ -0,0 +1,42 @@
Key: (private-key (rsa (n #00AC4B5BC5A84DEA80A0F64795FD9FDD06CFC6EE730C
2F25622A6870EC56802D17A21C2880E8D61402F5892C4D0625FCBEC1B0C0DD9A3C846D
645FCD6E28AF2E79F7A777E361E5EA0ED90798C947990925D872D8C08905488CCBFEF6
E05AFF0F3F0F34F704A142F3F591FB1C468F44AA96584403502AB96A4D0C7CB297B565
54060B18E4CAF2DBD77F18B26F9F1679BF5E8F2BE3F8A2A67BBE1EC55893E5A8F2B64C
267AB35AD87D696EF576C73B3C17C28CD583B217B5F16AE118A3E1CD2C8344B4198F21
1809673A945CFFA2764B8D58F29DD6A8D0FAAF52CF0D05059CD08A5D5A0750678A5FD5
398803FBA71652BA89EC142AABDC20C6C6BFA4294A5B7D978BAEA4C0E250542BB4FAC3
A5AAD2A1E8B0261453B6021998E511E09905158D7DEAB8E4B2A585D354C4759681A06C
81669A0E6A4559A8AF6E599018EBE64AFD6724C36679D46C0D2179D67B099E441C7A9A
ECBD38526BAA89C04622EE98FA2FD1CED941A024B09BDDD2F955342287681D86085C91
4481A24524BEA06FBDD88FDA71#)(e #010001#)(d
#208024A31FF0F6B3E5E91F2ED58572F1A67F1D9AD9290991BF732D1DFFE134E058E5
9BE459478CC5D42058997CF7EC79E55A9CBF10A9AAC761E04A85A5AA0A07DAE61DD0E8
36311534EE606D5392B42D8DEB7824B594280FDB2950D39886B58F0D24CE15F2FF88BA
819B8F4566202B57A9F5C6743862FA80E7429C83CEA57B189ABE4AE657B28DAF7D6EA7
6CA89635B9B6232EE14779452D636B919E707B92B13DA3229133A953DAF021E0928B83
75EDEE98163C2189E22CE9A236C3D0EABD2608DAEF09211B2C77FFE9158A95F8EF2750
221C5ADEDAED0446DC0E4CD8D38AD897D44FA3915934B6CF03F489DFAA6D939AB9F8EF
1C2A0CDCFC3F2207D63A9EB55B09A0A45323D5F59AE4A9D48E819E98E53D04F022905A
9C4D137F32CB33A974F202B0D3AD4AC64CFBA2A4C18650B671AB753D1D3BD7C4FCC8D2
0F85D1876D89A3D94C77423778C08BDF8FBA23A8501D766FC1B4D51F2D4BB4C27B8491
CC2595FF54034F4F192D668C1934D292752A4E44C95135D29449B75928BAF1A2389ED9
#)(p #00CCD74AC0DC1CC46052F784DB19545A09FF904846247BAD1AFA5E00CE84A4DA
BFCD3BCA175508C068553226DBA49EDAFBCC33CF2A914F9006326FCB62C0473B1E93F6
DCF89A24006B090834E5686464A8C216B70AD797732E671ED78CD3E922161069E46BA7
489F2A79CE46BDC4E6F5FCE97C3C9DC59399212235C53246609F8B7FDBF2AD191B3FB4
4CC59760BA6D2029541811D1E9B72DC2ADC98513589A9715C82EE88ADF9000A41512C9
6D491D2A30269FBFCD9CF5D2F275A1DBFFEEB72BE5#)(q
#00D7532ABA5F442A994ED8290AA71EAAB6F8137FE3F01488298637084157972D31EA
E9F495B4360A6E92ABA7C2418A5960DF29B8C8146CC7D1DF1201C17509D7315B6ECF86
F0A73C9F5B48D96F2108DD323FAE6BF897A8CB773EDCF5C82E203813945405F414E3F2
99EEDE43EE6259FDED1C01B47C20F67AC488209585FE6FB7D958AF5EF567737E1ACCB4
E293724BE8AB6159CD5A6603FFEFC2DBC30FB6EAF647DBE7D9664ED0BBA1C4A2268AE3
DE0850572E145BA811EB2087E1E26490F9439D#)(u
#00A8026DB6685170EC05DA3574451678718501489843227BCEB772FDB86B20AB2F2A
00B790A2373FD5DF7AD2CAA2E3896C4C9CBA08581F3644DF67743FA4BE1153421F8FB2
47F0EFB64C566CB7E361BAB21CCAF029B43B7172232D11D14912BC035E17FB8BC663CA
6766E6D2531F87AF378896A2AC7D98824AA8F51C5D6C11B7DC266209BCD3B23597F02B
A317CCAACC979402F84E47F3D143187F9739FE9A6C71829CC94E4003D70CFFA33AC0FA
A12776EFAFFB8261ABE664C6A6FAE623D3678F#)))
Created: 20210119T161132

+ 42
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc View File

@@ -0,0 +1,42 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBGAHBAUBDADAzIW1FhCcQmP/NDhzXoeRQ+DNACqTed7eEhqm3rowkW4wKi56
v1UxFR0ZoA3LoT1oQQjiL3IS2l4/qpBR3JQhMFH3pl7yBsCIrN7JvZfAvxq2Ud4e
YbdonY8mv/yCLq+nkTWlHkWGCppKMm6DupEUw5CFUCiptPXIxikU0uQYB7VRtXhh
Q1RGdsv6mcOwMIh7hj9flTrX025x0vRypjqgDR05RuM3hMMpJDGMAuf51w/lbRl9
dAsJzFzg2cf+8qv92Gx3RuP3a3yl6pEuKnkpddC47lj2pvuhWZBf2sXZMyPvMIvA
Dve4GIVj6k+wXE3DMp0xMy0Fvaxw5OORPxUAKNBR/BgjoRbkbjm+rJZzviu/XVPR
42+78isvsa+lnEAmTeoTqiz9nlTTPN+6JjwJXYn2LuiFM8XzNPJwNmd/86lW1qbP
DxZHhD9jjeXZNHUBUCKTIj2Rs2uFa0xrALdhhhGEao9JlVcUqz/Tw85qC+DlzSa3
3re5C6wGe2pnW3sAEQEAAbRGVGVzdGVyICjDhG5kZXJ1bmdlbiBpbiBHUEcga8O2
bm5lbiBGb2xnZW4gaGFiZW4hKSA8dGVzdGVyQGV4YW1wbGUub3JnPokBzgQTAQoA
OBYhBPidC43dWzUvOqsRbzx6+72u3pDoBQJgBwQFAhsDBQsJCAcDBRUKCQgLBRYC
AwEAAh4BAheAAAoJEDx6+72u3pDogeAL/iNn1aKxA7pKmucyuzcbzvUjtcbbqFL8
lOLdRxkrQNCDMb+wHgGY1UqJ6wsDruhV+TdPLzUXHpCl731MeLxZyIENr4wnjTBf
Cr4SU8eFUkusVf3aWK3rlk54W50EkfBjMvDVavRKNkVbCWAAwXZ7mTRf3UlWxg+F
9Sq4j2P/hEZIznKV3y7zXLDYg0OpMZLgbo3si0CD19/1T/8Z9C680qSwyAiPtjRo
vfJYqZFQc+ZH7j1Hmvg68d2Qwrkg/WMfOGoTLZq/6PQcM5leQBAodcS1t7C8o1JQ
6D+f2gLHpMfFdUKh9TkmvnKYI20TWUVm9XQLqyAHsOn2vRMUhydcZ8OP103TKmP4
mbpgiyp4i4S/7XofHSeFBrbdqt73ebubESuZVXNjTuuSUjH8Jq5nHq22ZrmotSd0
FNwc9qQmwPG/gmOGq3rUdT/KzUVntc66QN/+hMhFDYMRJsjJhhyszvGuuBp6vBzI
lMZqx5jqOaI9ON0m0o8CKC50sKdJ25G3wLkBjQRgBwQFAQwAqMXwgfSaIM+eSQWs
xb4Sf2aCr/RZi5wzZz89lSomMcblqtCpuHv9/C1PSd+N/D1yJKzPChbDjHh6B6gc
4OUvuDKHmxK+oAiURpvR+yJEdbSEYwiBhfAUD6u2q3IfY5PpyyZT3NjZ9EY8FpOX
wpgdSOdSiZVZfZt0xUPsGbW/xP1yVR1NHYLuZX5P5oTCvyNJyP8zQQmToamJsvzR
v9r+2sa5di9roe53kZwq24VjIvTDOOE4xoYEXk5UD83u1LA++9Nfdisxxts1bxgj
w1ThO/IRTyY5y5bKSQPskYFD3eVz+azjVurqhbj6ep69mR2X2gjLCetz6G1l4PU2
R6wcqVG6gR6XpGFPsN+M2JRtbgKrtMElA8egJIMMpH4hdXYqIqmpe/31zXhClHkO
99EbBpk6OawZC+MnreQFN4NIK5uO2aLi+3KL1FFnNlFCXkh/8afbt4+6rHcWC6En
q5W0ZkLZnpjdFOF5NPTinAdei+14gnf2QhIlFLeMHvqiVEXvABEBAAGJAbYEGAEK
ACAWIQT4nQuN3Vs1LzqrEW88evu9rt6Q6AUCYAcEBQIbDAAKCRA8evu9rt6Q6DcX
C/4orMX1YBZNJK5hLLdjfk45EsQDfCnhf8H991xd0Rq4VPJP6bvzikSdOn9bTUEz
AAhA4JnFu9AMTh8ioOA7ZtViIccplFBivsxi3rAVrQvmCfoP2AdHfG/jB6D9uWGs
MV5/o1p93Hr0ReO73HK6G4Q3FbJOG3fg6wTcMYyyEQrD5g4IQhKiIhudUlSkKUkA
9hWKlXSLw3Yx/S2Nq5Ye+Pqr4CU7UFOTCsBIH4Ky+6gLTmP6esPx0k8vXLcOjaCk
ENcLi0OaL/AgfATH/InN3wzrx2AFfU6eQdEG7HS+402eHl0fmWwMGV+SCsLl+2hx
AguLFwjetuVrroc/d+XeZdTcpr/2vojsr4UgbkH8Pa2KrGIpK7V85nSOeVbpDUru
tuimIRSxIQ6GDF2c7Ih5yBy+JPV47gppSV/GgHPgrOlebeqy4sytshRiEYw/nJzZ
LKBaG6gykN+6MeV6+A7c1BlCYpyi2vcyvouU+l3/Z9gR7vY+Oj1eAaxrqeTFf++3
qnA=
=03Wd
-----END PGP PUBLIC KEY BLOCK-----

+ 45
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key View File

@@ -0,0 +1,45 @@
Key: (protected-private-key (rsa (n #00C0CC85B516109C4263FF3438735E8791
43E0CD002A9379DEDE121AA6DEBA30916E302A2E7ABF5531151D19A00DCBA13D684108
E22F7212DA5E3FAA9051DC94213051F7A65EF206C088ACDEC9BD97C0BF1AB651DE1E61
B7689D8F26BFFC822EAFA79135A51E45860A9A4A326E83BA9114C390855028A9B4F5C8
C62914D2E41807B551B5786143544676CBFA99C3B030887B863F5F953AD7D36E71D2F4
72A63AA00D1D3946E33784C32924318C02E7F9D70FE56D197D740B09CC5CE0D9C7FEF2
ABFDD86C7746E3F76B7CA5EA912E2A792975D0B8EE58F6A6FBA159905FDAC5D93323EF
308BC00EF7B8188563EA4FB05C4DC3329D31332D05BDAC70E4E3913F150028D051FC18
23A116E46E39BEAC9673BE2BBF5D53D1E36FBBF22B2FB1AFA59C40264DEA13AA2CFD9E
54D33CDFBA263C095D89F62EE88533C5F334F27036677FF3A956D6A6CF0F1647843F63
8DE5D9347501502293223D91B36B856B4C6B00B7618611846A8F49955714AB3FD3C3CE
6A0BE0E5CD26B7DEB7B90BAC067B6A675B7B#)(e #010001#)(protected
openpgp-s2k3-ocb-aes ((sha1 #84A4A8051974D94E#
"26420224")#914E6847983627126D1CDF93#)#DEB66FA3201F91591F688F5D2B1B79
39FD75F58A962C227BC739C6F2F814ADE5115BD85B2E55427153CEC82612F0C5BBE8B9
71A0E5A6B796111B6B1A03C4C926825F03B871CBFE0F64BD0F0CC65EA34E718BA823BD
136D78C9E88CA1733DFC8D6A38830274322A589BC522A2A824FCEAF453523CDD9BC391
EEF1355470C110E9A92681DA0C61563465D5238CECCA2D6CFA78FFDFBDDA17A308D6E1
3B1858890EF25A7655F22FE6305AA0129DE5A353B657065E608A616A23C6AF561C4472
5AA705E55343E9C728641BB63C64F804F76A4C5008CE5FFBC09F03B632B42180425D28
9DC1201D91B1989627EE5930E6EF2F6606108B2F048934A9D79DB4834DD950C4A2013C
A40B50EE54FA9E3CCB20C210244BFACA795494A1FCFF35856ACF63214A0498ED894BAB
FE80CC24D8A478AD08D0BE8CDC8F357FB7F28A30B87540B9B4970D6EA0AEEF46A2549F
BA43A98FAD75B4228108DB50D1C3654422E24B4C7754673A66281BB283CD6A1EA8E64B
97DC9083C62034BF7FBCD193830F8FEC3589673B864E50EF7AF4DEE046BD26041E2925
170EB7B6DC6060E78309CC8A136AE9CE44D3B4EBDEE4479482464D0D23C13529184021
795557323D353A70CC710EC2A79C66E860095C082E40724867A9ABBFD3407B2F92CB2A
D0D95CC8DAC2FB2C0187B3BB09AEDF869AF1969BA641027D4D5DAA31B1DA5822D40A5A
7FAD1C054C02CF8F8F692B1C45C879299C0E9D5E5A165F6C22DEEBB8C16FFB91F381C4
8FCB209657A7BF9268BD34808D0A9D3D6F50F7026BC297FD3A08790B8EB5CC0291246B
16E4B50A7E9630B33F59B5EA24EEA396F07AEFD0C262BE9915CB32D5F03673CBD3D20A
831FDF55B5BA3D03440A8E1A331147A8AE0760EC593EDC881F5F0A04F4FCBC80C1531A
4DE71D014E3612C2C679BEF3AED59F358ABA5731FF80FA15EAD2CB95AC548F6AB0FD7B
BBBB2CB63DFFE9E672605B7F54EEA4B4C046C4CC8036F2F76260CF068D232A40F492FF
9648CC7459F0F46FEABA3D62B9F421B0F8A1BF914E41702540213848345498AA13F989
49EFC2888D3720DC34D20634472FC3A194F1403C1609C38A020F7E47F3205CA5C0CB50
26270083ED153BF97375407514BA15D92808A8C10F8160880F6981BE53294292E4EEE8
D215E7854FC79016B64984BBBAD2E99EED8D66B25575183C279E4DAFCB63F1067FA2B0
0888A9C226D4846376520720FC1E947A93A1D32444F78B2F4EB836A6F8C685C1D82958
E31560C3FC861D2B68B889E1B5EE0476B914DFE316411F750D252F24076E53557AD5F0
4050E5E839B33E5B8AF16FDD9FE033B39796A52DE8AF65375966D4DB137C85C800B5B9
0E29434E4B215DE35E60C85391DFDFA572C6F9747A0EA0964236FBB3B04394D9DF0694
6E4CC9CBCE352382908148D265293C6EADE7C2AED6F5AE#)(protected-at
"20210119T160858")))
Created: 20210119T160837

+ 30
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc View File

@@ -0,0 +1,30 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBF7EL8wBCADO46xh7nXn7vZ5ow2Zdrp7WTh9BlT2wtaHNKpnKvSoYHjJbbGz
yF8Jf/qVPuXNbjx2df1lT7zT7x3evcjQoNy80deftCw8ApZB9RMOo3uUIqS2VpO+
cS9rjTgBRFL6xDv3g4++CE9s+5dKE9gKkwleZ5/tVqUIoHPAIUEjpcPHngi5m2bi
tSmQUYWLGcliR1E79sJMSzPt1neksqHFMJ1KTEJLAABZ0t3PiBzmycIQWThX3uU/
lcgnZmmhWCJIqV0yRZqxl61ejUfq+zK0T7MzhAAugqe7D6BM1FRwZRNCHwDQXIvt
/t3fczTe+x9oTy4qX4MfaP8lHM0223MwGR13ABEBAAG0H0EgVSBUaG9yIDxhLnUu
dGhvckBleGFtcGxlLm9yZz6JAU4EEwEKADgWIQQILQAv4wNQfEJ6I/NEWemKCmiQ
+wUCXsQvzAIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRBEWemKCmiQ+xev
CACZSWh4xjTgafwGMP9RnReOhubVmfHS+XGlidDQzJDtshDQddPZ3oQwyTe3OgkW
ZgOrzjrHGsZp3WZmGUZejrKt2Brqp+h+VRujFVcKk4N9A52BkM6OeT9lzBabOpuA
UaDsNMSFsMcGTTYpB16+sDcyui8LW1jGi1y+8aQa+u1lIk/vVycq8o4htn2Af8xZ
rAT8peapsjoNjETEs8OQ0al3Q0UX9amW6Rq1zZZ0XtoXDCPTI01EfczDMN+AZoFk
UYHwSREDFLSh+c+q1HhYp4TqP+2a5Rayna//n7zci1PmSX7zD3iWzV1jEQ3Jm8U3
DY+P/WLezQdSJIBVCFpCualquQENBF7EL8wBCAC+ef+vNvfu1jl9BXpu6K9PG0I5
DQfrNtcdPq90O32ipvsYvqGOJX9MHoTyxBPLew+e5UsYb3ex62JyJqdAaqSwYXEN
MBESZx7yBqBMUvildfh8dowbJeblxCf5KsE4C9uNfg4ApWGD7PjVsUCh47V8VcfG
ymCxxq80r+4GfFtt/HC+l9fPUnDLuXpAWEM2GPUzcauUoEXxZK6nhstYCRlKlQcK
Tn+LtCC7SGpYlqvwWBzAnOYP9+eZfSJ897g0AiTEhK0JsBlDAb3UAWHYHkAkVa1+
oU/UedhPC4j2Q7RzPQFMun6aGkaDrntCxvT7IFiMplPG7iy0JDd6ubrWSzivABEB
AAGJATYEGAEKACAWIQQILQAv4wNQfEJ6I/NEWemKCmiQ+wUCXsQvzAIbDAAKCRBE
WemKCmiQ+xoBB/9BAmlHQUmVl/bkwszAcyXkR5HsyA4htMJt+6GKlqftuhLP0SGK
Il+7GeK6NqNdQXxXG5Wj6dn7ZqWalQRA0evEa6VLH+74zrn0llWfzTPIcP1bHW7l
uYaOzZ1z/q4FoEGNJxp/jdToZ4970OXLzqY/G/QlMJIlXWCC0EXNYbKCEpOE9uvW
h4kWe5xeGOmhZylYbzurTDzqEtKy+LZ9f2xNYn6ElcWtwxsxwSY7L9B3eNcCYE46
Np6uqzPffB9s7PHW46yEL1lQs6ME+9hBGyjeVop+Wg9qkh3YCrp+KY5Vkmdndwkn
Th4FnTpcCiS06fCVHHC5kelh+H6TgRA+XQ/V
=WGUq
-----END PGP PUBLIC KEY BLOCK-----

BIN
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key View File


+ 30
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc View File

@@ -0,0 +1,30 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBGAHViwBCADaE67a5U8oqNjqg//SmlNLd+MrFIccHsg+pkSDhF6ipjZEXdFu
oRQ116tH/qY1SzsHw/TMLujYeTBW0KwQ5E2+TWagckL8pJpDt7ZC08CTK6u0Xvjy
BSqy/t29NPTuQxxxqRx5gq91mtuo8v00fQmqkbFUgkVfEOKOv4qe2tlaR5pTvpmV
VboXOls87RPgP/X665kamHjsywrsDpZ5FbvPS8E2kKdYXqeHaiEU4i0Bizjx3diK
ilPEIxxl8zgDsROXyKagCy0KOOajBqhFhStQH1soIzvk8aG/9eItKmTa2v1BD2mV
UlZNQ9ZfsnXx3QIBLmA3jugH69rkcekHRCWPABEBAAG0HVRlc3RlcjQgPHRlc3Rl
cjRAZXhhbXBsZS5vcmc+iQFOBBMBCgA4FiEEJJx/+EvV3v35cJ+Jx5r/T8UxegwF
AmAHViwCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQx5r/T8Uxegx+hQgA
pIy+E58aWh9FIHW/POqLB/y3v/GbYdIbzk9ro0FAXQ2tQsoGQbsuLckJVIlyja7n
oQX23OmWTOc2tj/Kpy9lZ2ButW43FaMSiLh1G/VtM5pqJ9yHFdb1z7Q+LHMhhB7w
RKOoNSEgkwtxv4LAkKz5t/BrDU0hvDPxWPqCSRvSAEE6qIY0fa3mLf9ijy3gLlCd
hBayUC53W0tL7gLHgprTM/fX7b1pDab8beorroPHs5XzcYUBleaCGmEYbtV2eXPQ
5irOXOC/D/E8vOfOZwhOhFOZk9b4UnhFK4OCfpKIzIooc6tlboVPhdx7jb5DkXQo
rozfavEvAPx8INi9KNmdBrkBDQRgB1YsAQgA6q/O6mAPB0kj3pnpuIdJC8EumgKm
7W8rv+ZfRGePg+IEEm5DeFKtfWl70YH33nGmwnWB95wO5412JCNC174z5LKDBbz5
PWT/yTNnmjooxj6G4p/YVwXYJkvfaDP+kQnYgJAybpeqTa30tES0eqvI0J9aQo1h
GSMRCkE6QMV45IMj6gH9rptQv9e8U6gbnwBPxWPG2FH5rsGIGQGzIEmGxmKRyxXm
YDU4f7oWPHSg1ikQqCzAxxCBxeCOaid3acLK8//TOwF/Do8GPJbcupEDqsgbFNGM
BDWtmkmxjrLntlU+dvIPcsBxdBUrrADiJ/k7EfFv3kHfLfdAonSdKZL85wARAQAB
iQE2BBgBCgAgFiEEJJx/+EvV3v35cJ+Jx5r/T8UxegwFAmAHViwCGwwACgkQx5r/
T8Uxegy4+AgA1bzFKpsqkwrjZKDCCT759xeuUbxnYE9kBJgFSVuhn7fUbB4MoHx4
shBptx7iBOdxxT7yC0oaDPhbiIkttb/c5W0f6JuLr08JpjkFfkrWF+dMcVrtXwPx
i/30ccV98qWJDCBunyeCwBNie1Ck+qXMxm3FYy4qIbftMQ7mG6KKN6eFlbxu8B6M
p93DFUvycGH9CWz0yJcho7KT0NSSyoLZhJz2uxRe1BwGMV20O9AG9yicsU0/uJxY
a2Hble8NkH54XDuZkrsBaAb/o8UsWP7AJdYYsb904UZDIZNRfjWapOmODnlnK8Ta
Q8pyYRGS5of1SapatMfpQZF6hdsamnTH6A==
=guSE
-----END PGP PUBLIC KEY BLOCK-----

+ 32
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key View File

@@ -0,0 +1,32 @@
Key: (protected-private-key (rsa (n #00DA13AEDAE54F28A8D8EA83FFD29A534B
77E32B14871C1EC83EA64483845EA2A636445DD16EA11435D7AB47FEA6354B3B07C3F4
CC2EE8D8793056D0AC10E44DBE4D66A07242FCA49A43B7B642D3C0932BABB45EF8F205
2AB2FEDDBD34F4EE431C71A91C7982AF759ADBA8F2FD347D09AA91B15482455F10E28E
BF8A9EDAD95A479A53BE999555BA173A5B3CED13E03FF5FAEB991A9878ECCB0AEC0E96
7915BBCF4BC13690A7585EA7876A2114E22D018B38F1DDD88A8A53C4231C65F33803B1
1397C8A6A00B2D0A38E6A306A845852B501F5B28233BE4F1A1BFF5E22D2A64DADAFD41
0F699552564D43D65FB275F1DD02012E60378EE807EBDAE471E90744258F#)(e
#010001#)(protected openpgp-s2k3-ocb-aes ((sha1 #3E87FF0806B054D6#
"26420224")#7E8282B83522F91C53D76805#)#70B1997D514FF5800155A90FD785E7
7DC2D782C48FC9BE44D0192C0AD56804468C910A202191EAD077B5542C95FE72BCC450
0C2A8E8313C0CBD6C881236AC13E0BE893663946B5AABBDA57FFE4BA49973D547FA5DD
1236DEF9FA5A9CE52F7AF1947F42A6C3502A47E8EF7E8CEFDDD44D0BFE090EA3220C2B
52E11776DE36DD6C72D3B39A56F5D7295D26A69DB8CDAD1ABDBE1B21C1B754C9184E65
2CAE169E2F492FA0EE5908AC5CB3BE5F4C7F6CD9F41314D1BD9B1DE713A4E6C7DFB11D
2E64000ECFBBC89326B1322A8A227ECE7B919408C9187B5C9D53FC3985833E76D72164
40B7386569E4DE270C3616ABD2A91A657AC58AA872704CB3DD4DF08C77D03D8E3CEDB5
0D83BC3837FFE45D64B457AFE9A6ABF680637C51F80CB54691233BC4DE640026ACDAAC
3FC0749FA8353F6EAD5D362A63C1CF25ACA73A9CF3290B54C18DB3214AF078D918682E
513C434EFA06D9045571B1734CAE42990A1BE962D6E2A45846169EAAF2CDBD520813C2
D4DE97FFFABE582A02CA893F91EAF0EBCDCEB70B35850FDDF56EEA60C845A7E5C052C4
33344776E7A4C787690CC0E13F32373EE425CA10520C251D045C0AE73EE7A0CA83C858
E2E528CDBD117BC022ED3F5DDE40CED0128B761E29B11F422C8E7C4281BF94F6F75D07
0EE58426137548ABA38019A34DF1A66F700C29EF5545AC88BED75B5036801F0D8D4DB9
C6CCEA83D9DE3D626A04A80F218EFE9C74C173412A8A86786AB4A85403E8F8292CFED5
B8BA72FB5CE1BDD094AD9D633FD482F8FDBFA540DD2224149786ADA8DB6310A7C0C6E5
9167815B2CEF34E7C458C41B5C56A79414BA57073E9B06D28CA08C56ED5E685EEA2BA5
DD112F87B253A0D02AC7CF93EDE93F48A80B2DB57B254937EA80E9AC1CEBD36FD297EB
C8A3B42CBC3D2CA732891B49457F3F15AA3F9BF93553968A07CB1A834B392F27B2D152
47D93E46A6338694EA53CA0F5968109B4FAC9A#)(protected-at
"20210119T215925")))
Created: 20210119T215908

+ 14
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc View File

@@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mFMEWsODvRMJKyQDAwIIAQEHAgMEQLOxiiHZ/V6v3kvrhbnRtTp+oOPVpuvDKOiy
gJOCZ7EWMVAwTr4syaSh8W8hdRgZ85Evv/1PYNFovYb6vzgVr7QJZWNjLWJwMjU2
iJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEBjPF9yoZ
j1HmUOSr0Mij2vngY0oFAlxVsKIACgkQ0Mij2vngY0pARAD/RozGDidH/0aFlxeU
VWNJKjPiax6vdHqur5dqBS/RhhIA/1sPUnyAIvAXXID1uhK6oIBRKi7WJ5rI7vSy
rBR5MlNJuFcEWsODvRIJKyQDAwIIAQEHAgMEE9Vd8dIjHJkmRs/8MLz4Krfwz5BK
hunq1T0xnp65OEZJd00VxA+VUXdEUHfaDehtSv7izCpq4lbXGCkEGFN7QwMBCAeI
eAQYEwgAIAIbDBYhBAYzxfcqGY9R5lDkq9DIo9r54GNKBQJcVbCpAAoJENDIo9r5
4GNK0MYA/2p5cq5smjSvKD/EGkosQwfcqkeygsQuEpDDLeEdsv8vAP9j+RHKX2tl
W08zbayxGB0E+aCHuKCF8iLPeI4eroi/fw==
=vsa4
-----END PGP PUBLIC KEY BLOCK-----

+ 17
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc View File

@@ -0,0 +1,17 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mHMEWsOEUBMJKyQDAwIIAQELAwMEivcvlPJsPmivhJcrfHx+ORxyum57GtRhWM49
Yr8fJ48gyFqj9cLYOBdhEVvcfceyBPXmyt0TozWtjkGzgbF4LIvN1EB0DW0Rlsdn
p72/hf0gnXvWZdD8euArX4RaAYuQtAllY2MtYnAzODSItAQTEwkAPAIbAwULCQgH
AgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQRbiiVMgjztmN7NEO1s8tzoVZmtogUC
XFWwugAKCRBs8tzoVZmtoj1yAX9P1UV7FYpGUIP13aPP0d5Bx8HdQDAoexdXz3WW
WPL/7OhSjPde23Q8TfgWyO21M2wBf1oWjOsDSjO5mDLCr7ypAFF6IJAgx76tSUe9
Qmy7sL94OWDQ4+1Dccnc9GGiHLtRI7h3BFrDhFASCSskAwMCCAEBCwMDBETUkqGr
7p8kX2dm38jzzxXRh1+OL7nmY168Zeo9yfwDbnyx8BoihP9ZgPWjGXmefT78GSfw
ZDaYgC2NFQOcI/b8agh3PcjrXgZaFCZbUR9v2DnLUpCF8ZbxDJwEqweNTAMBCQmI
mAQYEwkAIAIbDBYhBFuKJUyCPO2Y3s0Q7Wzy3OhVma2iBQJcVbDCAAoJEGzy3OhV
ma2ig1IBfifduIiwdAlD45MOolSpHMX0AT7KoJHpt9ZFvWnjQkq9ZGEA/RA9vx7Z
sLb7IsG1GgF/Sn+gtf/JIteXaZMnOhEOZ2oFUufij6o8gII8/9s8mkIjkrIICy//
0n3q82ndFg0c
=fcpz
-----END PGP PUBLIC KEY BLOCK-----

+ 19
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc View File

@@ -0,0 +1,19 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mJMEWsOEhxMJKyQDAwIIAQENBAMEA28ylDnn3LR7t6EQow5DszCpmUA+yup03LsT
9o0Tw4/asi8nAz+1tlRY5HD49j53PziOlsGzKYa/jxGWjhVESgqLrJp/Eo65zK9v
yDhX+iCkSYQ15WGKr3QaRUmBOUbX9PqE6dY+DDGQ1kewI93QIGCB1gn+OSmyKPm3
YaVIbo60CWVjYy1icDUxMojUBBMTCgA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID
AQIeAwIXgBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDYAAoJEKpcWNFPe49I
F8UB/i8DwypbNusdbAqTbu1Twpn/QFMaVKHn8Ysgzqpimv+6hRq7uzekCvEOPOjl
Oke5yLp8bpTTMPRKyfjNatQhdn8B/2+54qtXJuQd9oTSz7f2eFYvA8ZsMQgApYNl
ksvKSw6dhSNX/DXK7JYIlgZXx7UGTkP4h3FQSiyCUJhVpClVVGa4lwRaw4SHEgkr
JAMDAggBAQ0EAwRCtEqQkEgzQDxNGCj0duj0aGvnH+DHKlP4V6p9LJVIL0TuF1uZ
BzP04efRvZT2vzCTcvvdE/G6G/txEZsR/989OchbkVWOPM/oCVktkaA02rBMcefh
k9wKD+O9E3oHEN+tBt3yhmsv0MIR9IfPwi1GCsu55p4WUI//+ysB2T0YaQMBCgmI
uAQYEwoAIAIbDBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDgAAoJEKpcWNFP
e49IZQUB/R4U4aWBMimZSL8kyaK+/Y8NcInIGqRzrm5zPnTSHrgQeH55SVKahzsq
j57D1Ec1HnUd4ctISVocOxpUfnJq5NAB/1fzbh+1RN2ZyNW6tAJlA/Irkwzzbil9
6fAIvRolwwaGsUZNMEiCF3rTcFaenJg9UhQvX6BoqXCdwawqTZCRN6E=
=h+On
-----END PGP PUBLIC KEY BLOCK-----

+ 44
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc View File

@@ -0,0 +1,44 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQMuBFrDf8YRCACHbPXT8jG3RNWdNms9xvdaiLrY+Iui1Gq2WGLSajPEZVASEWN+
JuuX8k9d05rb+F2VAqLnW3CQreDW6unVNeRf52tdM8J4eXmeu/Bkk8y1Qx/HbGca
sAGSIEKg34TuV5Ly5m4Z07bs3HPYUUQbmu0uclGfnX/ArZ+4Jp+uypC9bErdiXM0
cM7d52tb9IvOlXNu23rzHDbgVP6qF/AxLSRD8SQPvshu3/5b0bvdBkHVk+dHoLO0
fC5j476ibHGGZcnPMrTwqEIAxUCy5wQ3Lb/2/G31kuV55bAZ41tUNEvfzbiRN1L5
1uiO+XX96bRqLN13t0Coaba9fq1aN5Zr6piXAQCuNzvj8aaLXAOEXVRej6a2k+/C
Jny91MgjSM701twUDQf/RMWHwQuFPe6zSDQs4pWlxkHwXJw3AVidkoWg/DCwv2pJ
5VYQxBXRwND2OhcZvmeDT94UzPws0dFbprWyymtA49ZXitPGzFARAFWHWxk/IsOf
Idc6w5eHXDMHxLhiPFqfjKeNpibzO2P7LXP2bUKzwybkKZarz1N6pfanDXAtC4DU
SC3qWNqywYlfINAGCdwsPu5qFUNSnkjTYxe/MiHb4kL1p/z8qFNWrvg6GryXygp5
cLdqckjPaUHlR+B9wQZIVRzVdlFAbMDJ0EERLFG7FbIuY8dzy5x7n+oBOgRxee2I
ytUpGVMLIJuecARLXNKsMXviCMYVE7Tj5hiSoM0TIgf8CwLLFsSa0EDm/wlXYZMj
2gg3Z8iCz6ycxvFD9PXNt+8jyELO8CwS2pWu7ptBgaugkinqd40EQslQoP76CcHq
bGQEohm4SnmfGsV8dicuziMVVKkVrYgbGvZ5cQ/ONGTTnSJuiTPN19oztwh8JOEc
Jd4l+wFuVSm8OS1mj5eexeX1Tz3NfWQMT4deKh+jiTLe9Sw/57sSjxiw/8IczqhN
Fu3YIy40d3Bv+OF6i8I+94WLbJPiX1ban1wqcA0bMaps1aYVtTRZ+mP0b3M9W7qa
383/SLCBjUzQ7zm6PX/7uAXFyZOQfcyLaJ8Hc34yOE0git1DWmRS7U16Zv54v1Uh
HbQGZHNhLWVniJQEExEIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheA
FiEECRxEzpz7w/9+x6ZNyKEKfXgnPhAFAlxVr2sACgkQyKEKfXgnPhABkgD7BfEd
jhB+ApC9icNLs893i2jiHbAxZGSOQMRhCaJ7AzoBAIipcSIsBa3LJ4eTec1esiCY
a8xzxquCTA+oANNoX7p6uQMNBFrDf8YQDADMPZ6+/YAIjXMLfQKX80jz6FZ4Kdfx
Dc60m4O+ZElMv7eXtQJC2L/xOh4Th1fZUQIhSdtFiwaCSkCD8occfJwyt+lH3Dj0
Qrh3mIycAfPrjj0Rgxz8nRQbBLDbLF1QGPimt0zP69ByJ3opLujVVi5ixwgwza9S
eGffKwGdyb9uFcB9MVnC997zfLvx/uNV44BwLnCH6Tp68Lynf+FpuvSX+Rsj4li3
UiLoVxEIbBZ/5Bn3ygc7aW4fM4bR4hKjWwJR0Hh1y/kt6A7dEAypVKBfSqAtAu2A
zYAq3USsbtq1X1FaGEsmvcJY8IGa+aLTArq7dkhXzcv7K3EVdqOawS1zS/ARuG+B
k6kct3zzyj1EitiTvdMAkGOoyk16qKVzUcbFRVC1HsZtxYj6OxU4Eazvh0LjvZ0A
+eft/XO/ZmN6vyRaF1/10z+uHPfj1FLMpS8Zn4SN6x7Qtsx3iLL1n9cKBDFRXCqD
HDaxLVC3N4zAI2hMMmZid+fbTuhsqYbSX2cAAwUL/j/H4/9Ml7PUUCXoozX2V4K+
gi6WEYmY+pXN/we9NuFulW4aURo7jK4wRYBu0BS3K9e8f8WUMAV5V6ShPWHXcobt
iuSjLYJwdBJkgHbnKFWPZUozJ3Ftyp0Lh1M5bN7/ECofAxLHbRpCVrcOP2LC7vAU
AeMgdiFDqEiLCnr/aGvqUOxbGO6Isi4jvaM/ZUfGjGe/Z6yVoqm6wEsNM7+9cFGQ
QR1lRPeCPKcLeasCdbM5EIt1aLFNijZigWuDRLIgG5PuzA8Kpdk/u/UuCUeUFwJN
ym8MEv2JJDiWHmb8IcgFMp40VenUs0fte0LWwrMjWVPpLsHKmkraRjQ1UtarRhT0
ANYilGjZWCnCb11xGKhlM7r5IkLGY/L/Eh4vjLgg9T5rGwOF8p1jSgx9mA8SpHV0
O0BoKNX1ApWEHayTLcyayCnTYbY/e4axnSKodixAI/NghOnJHqGr4LeZeKk/Q0mm
GlljzFv3EAdoru4DVowWGFBmrwBy7o+GLgHs6K/+yIh4BBgRCAAgAhsMFiEECRxE
zpz7w/9+x6ZNyKEKfXgnPhAFAlxVr64ACgkQyKEKfXgnPhCETQEApruWUqCwfibQ
vyI/OZohPzljlvIoioj3rFjYNpufQD8A/RTaYtnPiEvsPynEZCj9zTV/SuHiKbHS
v5BhpoOOm+jM
=PnGk
-----END PGP PUBLIC KEY BLOCK-----

+ 9
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc View File

@@ -0,0 +1,9 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo
ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID
AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco
lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU
9RrUJSYZnMla/pQdgOxd7/PjRCpbCg==
=miZp
-----END PGP PUBLIC KEY BLOCK-----

+ 23
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key View File

@@ -0,0 +1,23 @@
Meta-Description: Example from GPG file 'keyformat.txt'.
Description: Key to sign all GnuPG released tarballs.
The key is actually stored on a smart card.
Use-for-ssh: yes
OpenSSH-cert: long base64 encoded string wrapped so that this
key file can be easily edited with a standard editor.
Token: D2760001240102000005000011730000 OPENPGP.1
Token: FF020001008A77C1 PIV.9C
Key: (shadowed-private-key
(rsa
(n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900
2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4
83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7
19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997
601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E
72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D
F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0
8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A
E186A02BA2497FDC5D1221#)
(e #00010001#)
(shadowed t1-v1
(#D2760001240102000005000011730000# OPENPGP.1)
)))

+ 14
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc View File

@@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU
BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6
XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB
WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7
+mqNLbhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG85ABQu1IwgQMpiIuUwj+N7ib
gGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez3+Sdg6UFAMFV2QMBCAeIeAQY
EwgAIAIbDBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcVa/vAAoJECNnTyGyRBUn
ZKoBAJ64gv3w27nFBERvIsRqufvR6xcimqS7Gif+WehBU+P5AQC5bqoISh0oSQid
adI84f60RuOaozpjvR3B1bPZiR6u7w==
=H2xn
-----END PGP PUBLIC KEY BLOCK-----

+ 16
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc View File

@@ -0,0 +1,16 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mG8EWsOCnBMFK4EEACIDAwTRTCLBHlq6SUTiXZfWR0vbUh/0VAlrePaqHVIE4LEK
0UBhPCIQOGuGL4JIufc8UzBWhiMLJ0z7KRjBWufsMZR2+rqj+unOK2lLu7sc9or8
X6B74hhP3Ili24PgGFAeAG+0CGVjYy1wMzg0iLQEExMJADwCGwMFCwkIBwIDIgIB
BhUKCQgLAgQWAgMBAh4DAheAFiEEqyXLoELdkkw6zD7TJCo6peqF9EoFAlxVsBEA
CgkQJCo6peqF9EooJQF7BPZelriXwZ/kJzaamImHBddkLFc7d2WbuSfDxEZQ+Mfw
BAP3+QYUaFtfeqApjY69AX4w6LhTUgI2kl4O0Vc7ZOlqZBlwAc8CMV08TTfOEio2
b51SItvhLdDrFRJ2K4jiO+a4cwRaw4KcEgUrgQQAIgMDBORWqhYflSrYzF04SK8q
8Om+DYTvwRtUlr3Aoq44+gm5yBcmJmgT3TKrp/bx5Jg/zwzIASFn0agbxkqKpQqH
sHeelWsSBROQzy98HXdCp3nVmghI2aDk8zdD6AV4m7c2ewMBCQmImAQYEwkAIAIb
DBYhBKsly6BC3ZJMOsw+0yQqOqXqhfRKBQJcVbAZAAoJECQqOqXqhfRKgAIBf3Wk
TsqUA1JXkPGetA9sjHglIICN+DZY5k+PwTJUxaW2zrkiPJ3BYEnKbmmBLzA7BgGA
4RYatyl2WOUYh/poRLgu7JpE4oRqdmNA+QOpCILMId1AeXfj4W01RKFWaKeH+3Yy
=2H/0
-----END PGP PUBLIC KEY BLOCK-----

+ 19
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc View File

@@ -0,0 +1,19 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mJMEWsODGxMFK4EEACMEIwQA8OZCJ8Iv4Qr2oRr0kqez0nPSW/vNxYBZRpCJ9ab8
kVaRhW7+5vEsecm5XugZSePbAmERmk3oEiSgu35r6aovowcAGrOBfBm9fyIVqaoX
veTS3yRHH6NEf044+pC+uBaaFukkrDmVTecdRvYr3Yrdc5ifyOve053znlpQ6a4n
9bh4GGy0CGVjYy1wNTIxiNYEExMKADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMB
Ah4DAheAFiEET7Of9vpIV6S9fvW0IJLKgyQmO2oFAlxVsCkACgkQIJLKgyQmO2oK
DwIGO72zo6otVkbHfeI9hWx/8FAOXh4MT4YtDicF/sj8QbHzdbEBHcLCByLYAnph
8VVoCxpPcBLmNSHbNyreoksjEE0CB10P5kPrd/bYkdNx/HTu+1i8f7a448+Nr2rF
PwdI9tOsghkT41qobZavjjnBlT/xv5DqXldJkEfaeiJxPHOKvMhWuJcEWsODGxIF
K4EEACMEIwQBAY7ZCAjks1MWWxibg/EVaz5t6iEKJTwu8mGGKWdPZAQRKKNtNpf0
pZAMV3I8ue/WQMsYKRYv5AGq1PnjV19DmLsA0aGw4MDM260coctkcn/2MAJQMC9+
3Z+BJS3hqzwDuZ+LS13r0RLpgnt3ks+3ucG4II38ZZ1lTwKoIc+w/OuhsOIDAQoJ
iLsEGBMKACACGwwWIQRPs5/2+khXpL1+9bQgksqDJCY7agUCXFWwLgAKCRAgksqD
JCY7ahqbAgkBiXYtiBlp5dmSYnbc4JoIYWcxTBQ+/dGHyU6ZEfC5VQz2mrdJetK1
bIID0rFSsd24/8IzAqM3L+nY9h9bULWroroCBjTohh0j2EbW+hFOrRqL01osnlY+
1/G8e44blB5JqsPI9FqOZOUj6IzsUuV1N9gJbm1RHu/hSpm52d6rX4nOTbqt
=3jnl
-----END PGP PUBLIC KEY BLOCK-----

+ 40
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc View File

@@ -0,0 +1,40 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBFrDgZ4BDACxg83nNkYvIAz6Nk4Np4BscWDrrL3tyiiQSz1yfCotxO8zUtVl
JorSlqRurNdAYU2XJLakMqpQHE7VVIMI/WdXCC8CrQbULOBxIf/LGiQf0VDo8ukg
iFFd5vUeMRRILWKnMc/GCmFkFOUHd1Y60h96oe5f/d286fRZQnCO8PGS8CgB1mDJ
GBY8U1DCCkt6g9O6bfFkfcwetr1+kB2cBIY7uDzN1Sm8dz2VrkqPqtqt/F02giRN
hD2GxX9HVYCkCKEE9DFsB+MDKR9z5lXrI3SAsL/3htXK/ukWgBe4DIFjTXbrzgP+
nuWb4s1NxwSD7yjlnqVD7mZTGAwMsQbCjhwIeY4t5onsCJ4/40zm1jIZ5TJsfQ/b
5gA7iu9kMKzxO0bPI2loJEB/K9aH7qsyQHgtt7G3+G8JrixLBUobtreY9V2QvhtC
Q4OAHRO5nIKYIazDuKv9bxautZ0WuLOk/qmWMMoqFAn5bzQeVTTBD6kvr6l8/+MZ
zaij9YPeUbeKJuMAEQEAAbQHcnNhLXJzYYkB0gQTAQIAPAIbAwULCQgHAgMiAgEG
FQoJCAsCBBYCAwECHgMCF4AWIQRrwEpaPds1dmuaQNgvuReRGImOiwUCXFWwhwAK
CRAvuReRGImOi1NiC/0RPjbQTWMw4/GxIMkRb9kmmsSUe7kCgqBCqZTxcI7rxZdn
JFDbP5c6DAS/11bRQJ9OsoUjbDx0d81UuBlXB/mNsb9nCcXOrqAUHRqgWoNSDk4g
9Oa1Kx77OM9BvRJbJGch2YW5Wcch5vcqQNu+6x3VGt7ipljYEJSQ6Dre+dgxYjXK
60x63/ulFk2XImPQYjQ8VHbW/HDg/+DLf++phjVy9l58U1sUKSSdO8uuYoW6dBv2
xRg18Sn3DWOU+mrkV8Ld95+NRRE1cSHTQv5hu4ELqrV+YdGNmv9DgQAMJOl3xy8i
vOz4cpKaOBasm423wr5Y56nOTzLFN+dxnYR8tbqswLkCldRY6fvL1NsS77rj0yZp
pecCyi0E6RAcmSiZJqpnOpcuI76AkZuWSDEP3Y3x5QBf5fu2uiQQsPXYN8ie6xcC
zeYtXsHyNxF0oBh2c26po8fo4E4T70RSO8Oqs1XzXnjIIle8pKU9M5U5ISbWS3Hj
vtOn5ZrLC5KYnVRna3G5AY0EWsOBngEMALmXpJoPC1m4THYrfHibtt2/OwAlDm20
3xn+Klw69bkeXdc71wsLOAHVL3+7gXpip+IYmN7CBIyqlOCtsluu+gwP3MczPJZX
vk1uXMMfLKiXl9Kodx/Fqq0Y26Tqse5PPMlagPStIvKyT0WTa3RCD28uVklapLuy
1w1k4G5hIDPt6uKyxXq/HzneRSGWafmqoCWOmXQZzfOMG249bMXNOcPMJhOejPRS
jREnnntbpvZ8DU/38+JFtqCUkPwuqYQkvGCKReSBifMiG3XAhHWOGzXPzdW1XdAi
aA+NQP/kMUs45jS3hDdp4EObllYRBsQwtFpKPMNmwaVuOmVlbrXTP0YsDYGndkE6
5nJ46/2xPhl8+nIgDLg3SBBzQdOiPOGtHYjs0bRKdwXTeAq4fDq0vCQXMJF2fwAQ
LEYWs7kabKhcPpWzTtoCG78WzR3TgldEPhPjE0offvVQO56x1XDqMBctoiDWkWkS
bdi03GhbFdK5A7uYBTJYEoo61Yp/2/MjyQARAQABiQG2BBgBAgAgAhsMFiEEa8BK
Wj3bNXZrmkDYL7kXkRiJjosFAlxVsI8ACgkQL7kXkRiJjoumjwv/c6a9G1bi3sh7
wRFhpsrFUoFfEBDI4eyI/haWhCIfI8n7p3lCSIy8lmf9yvUs75d5M3EQW08NQjIs
/o8FcFoUBnKQv2whWSHTpx/BkuhcNVY+NIwyBKomU0WkFSm3+80ix0uh97KSlRlW
Q5nMVExNxZ4mRFAhDQrJ2/pZ2DaddeO+4uZ7Twquaix+PMxpNKvkj2+757L23YjF
QmHdk6E8burofpSCfBTB84eUSDvzs6Eb/34/KlbBZhKMYdffMDCSAZIMfIav6YVJ
UDzT40kmS0vRW6bDIetSbpBM+GD1cSq0wKdlt+Giur9ZiaiyHIEqbPgr6WgdND25
Vx/i23Ik2o8wMb0Ub8cKD9wjdGAk+Rt2r7d2RzyO/R3ThKbUOGkQX6acAAZAjhPs
UGxt1dDojmQ3nF4l2hZ9PcsyD3pz226wUUPT4JA1eE6tdoVjzY2J7EhfNaVcQQlb
bQJQ+BQcO4oP1mPRCx1GiSmB+jRNQ4npxVJxLO/j7T27CrSZbhT7
=aibx
-----END PGP PUBLIC KEY BLOCK-----

+ 14
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc View File

@@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vo
kB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfdtAplY2MtcDI1NmsxiJQE
ExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cA
CmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d15NBuSJ6IB1brH0E9
nEWkqo892PaAY5akdCO/i9EBAMsjE5NPxBnCs03c+VHFU200k27ixdrWpUa+HZEI
A5wSuFMEWsOE8xIFK4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSK
hyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45HAwEIB4h4BBgTCAAg
AhsMFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsRUACgkQPqW7b5aSwaCETgD/
YXzCMYMbPGAU2oTitjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPi
E+jv7vadvEt1yMA8rmT041F5
=mDCI
-----END PGP PUBLIC KEY BLOCK-----

+ 13
- 0
org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc View File

@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEW8SVGhYJKwYBBAHaRw8BAQdA9SMZ2uw0YugMFcl5TpEZeBRAGniEk9a42XNs
7QA4Tky0DGVkZHNhLXgyNTUxOYiQBBMWCAA4FiEETJc4pvK+Thp5bJt7lBgioPwb
MKUFAlvElRoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQlBgioPwbMKUi
1wEAgMq3X7o17OJBPfY3He/exDR6LhWwAAXrVQR/WdRiHkEBALd1Mj0BlZZLoKTr
uJ4MD5CYZLicXTRwOv6e52F/DHwJuDgEW8SVGhIKKwYBBAGXVQEFAQEHQA0Lh2mG
lB1O4xDYgztm/aX7+8AdHEGaMsCF1RQ6wVUeAwEIB4h4BBgWCAAgFiEETJc4pvK+
Thp5bJt7lBgioPwbMKUFAlvElRoCGwwACgkQlBgioPwbMKXmlQD+KxVg2dGL8lRW
rQajwzmuwMrJX1lvJylg5Ozk6SGrBeABANZrdt8bmArEqeRVxFO2F4P7btyIpf1w
5aNpqqtvkRcB
=EYfV
-----END PGP PUBLIC KEY BLOCK-----

+ 6
- 5
org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocatorTest.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019, Thomas Wolf <thomas.wolf@paranor.ch> and others
* Copyright (C) 2019, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -53,7 +53,7 @@ public class BouncyCastleGpgKeyLocatorTest {
assertFalse(match(USER_ID, "<heinrichh>"));
assertFalse(match(USER_ID, "<uni-duesseldorf>"));
assertFalse(match(USER_ID, "<h@u>"));
assertFalse(match(USER_ID, "<HeinrichH@uni-duesseldorf.de>"));
assertTrue(match(USER_ID, "<HeinrichH@uni-duesseldorf.de>"));
assertFalse(match(USER_ID.substring(0, USER_ID.length() - 1),
"<heinrichh@uni-duesseldorf.de>"));
assertFalse(match("", "<>"));
@@ -72,8 +72,8 @@ public class BouncyCastleGpgKeyLocatorTest {
assertFalse(match(USER_ID, "@ "));
assertFalse(match(USER_ID, "@"));
assertFalse(match(USER_ID, "@Heine"));
assertFalse(match(USER_ID, "@HeinrichH"));
assertFalse(match(USER_ID, "@Heinrich"));
assertTrue(match(USER_ID, "@HeinrichH"));
assertTrue(match(USER_ID, "@Heinrich"));
assertFalse(match("", "@"));
assertFalse(match("", "@h"));
}
@@ -110,6 +110,7 @@ public class BouncyCastleGpgKeyLocatorTest {
public void testExplicitFingerprint() throws Exception {
assertFalse(match("John Fade <j.fade@example.com>", "0xfade"));
assertFalse(match("John Fade <0xfade@example.com>", "0xfade"));
assertFalse(match("John Fade <0xfade@example.com>", "0xFADE"));
assertFalse(match("", "0xfade"));
}

@@ -128,7 +129,7 @@ public class BouncyCastleGpgKeyLocatorTest {
assertTrue(match("John Fade <0xfade@example.com>", "*0xfade"));
assertTrue(match("John Fade <0xfade@example.com>", "*0xFADE"));
assertTrue(match("John Fade <0xfade@example.com>", "@0xfade"));
assertFalse(match("John Fade <0xfade@example.com>", "@0xFADE"));
assertTrue(match("John Fade <0xfade@example.com>", "@0xFADE"));
assertFalse(match("", "0x"));
}
}

+ 61
- 0
org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import static org.junit.Assert.assertEquals;

import java.math.BigInteger;
import java.util.Locale;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.util.encoders.Hex;
import org.eclipse.jgit.util.sha1.SHA1;
import org.junit.Test;

public class KeyGrip25519Test {

interface Hash {
byte[] hash(SHA1 sha, BigInteger q) throws PGPException;
}

private void assertKeyGrip(String key, String expectedKeyGrip, Hash hash)
throws Exception {
SHA1 grip = SHA1.newInstance();
grip.setDetectCollision(false);
BigInteger pk = new BigInteger(key, 16);
byte[] keyGrip = hash.hash(grip, pk);
assertEquals("Keygrip should match", expectedKeyGrip,
Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT));
}

@Test
public void testCompressed() throws Exception {
assertKeyGrip("40"
+ "773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB",
"9DB6C64A38830F4960701789475520BE8C821F47",
KeyGrip::hashEd25519);
}

@Test
public void testCompressedNoPrefix() throws Exception {
assertKeyGrip(
"773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB",
"9DB6C64A38830F4960701789475520BE8C821F47",
KeyGrip::hashEd25519);
}

@Test
public void testCurve25519() throws Exception {
assertKeyGrip("40"
+ "918C1733127F6BF2646FAE3D081A18AE77111C903B906310B077505EFFF12740",
"0F89A565D3EA187CE839332398F5D480677DF49C",
KeyGrip::hashCurve25519);
}
}

+ 143
- 0
org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.util.Iterator;
import java.util.Locale;
import java.util.function.Consumer;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.util.encoders.Hex;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class KeyGripTest {

@BeforeClass
public static void ensureBC() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}

protected static class TestData {

String filename;

String[] expectedKeyGrips;

TestData(String filename, String... keyGrips) {
this.filename = filename;
this.expectedKeyGrips = keyGrips;
}

@Override
public String toString() {
return filename;
}
}

@Parameters(name = "{0}")
public static TestData[] initTestData() {
return new TestData[] {
new TestData("rsa.asc",
"D148210FAF36468055B83D0F5A6DEB83FBC8E864",
"A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C"),
new TestData("dsa-elgamal.asc",
"552286BEB2999F0A9E26A50385B90D9724001187",
"CED7034A8EB5F4CE90DF99147EC33D86FCD3296C"),
new TestData("brainpool256.asc",
"A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7",
"C1678B7DE5F144C93B89468D5F9764ACE182ED36"),
new TestData("brainpool384.asc",
"2F25DB025DEBF3EA2715350209B985829B04F50A",
"B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8"),
new TestData("brainpool512.asc",
"5A484F56AB4B8B6583B6365034999F6543FAE1AE",
"9133E4A7E8FC8515518DF444C3F2F247EEBBADEC"),
new TestData("nistp256.asc",
"FC81AECE90BCE6E54D0D637D266109783AC8DAC0",
"A56DC8DB8355747A809037459B4258B8A743EAB5"),
new TestData("nistp384.asc",
"A1338230AED1C9C125663518470B49056C9D1733",
"797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF"),
new TestData("nistp521.asc",
"D91B789603EC9138AA20342A2B6DC86C81B70F5D",
"FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA"),
new TestData("secp256k1.asc",
"498B89C485489BA16B40755C0EBA580166393074",
"48FFED40D018747363BDEFFDD404D1F4870F8064"),
new TestData("ed25519.asc",
"940D97D75C306D737A59A98EAFF1272832CEDC0B"),
new TestData("x25519.asc",
"A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE",
"636C983EDB558527BA82780B52CB5DAE011BE46B")
};
}

// Injected by JUnit
@Parameter
public TestData data;

private void readAsc(InputStream in, Consumer<PGPPublicKey> process)
throws IOException, PGPException {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());

Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings();
while (keyRings.hasNext()) {
PGPPublicKeyRing keyRing = keyRings.next();

Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
while (keys.hasNext()) {
process.accept(keys.next());
}
}
}

@Test
public void testGrip() throws Exception {
try (InputStream in = this.getClass()
.getResourceAsStream(data.filename)) {
int index[] = { 0 };
readAsc(in, key -> {
byte[] keyGrip = null;
try {
keyGrip = KeyGrip.getKeyGrip(key);
} catch (PGPException e) {
throw new RuntimeException(e);
}
assertTrue("More keys than expected",
index[0] < data.expectedKeyGrips.length);
assertEquals("Wrong keygrip", data.expectedKeyGrips[index[0]++],
Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT));
});
assertEquals("Missing keys", data.expectedKeyGrips.length,
index[0]);
}
}
}

+ 163
- 0
org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.util.Iterator;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class SecretKeysTest {

@BeforeClass
public static void ensureBC() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}

private static volatile Boolean haveOCB;

private static boolean ocbAvailable() {
Boolean haveIt = haveOCB;
if (haveIt != null) {
return haveIt.booleanValue();
}
try {
Cipher c = Cipher.getInstance("AES/OCB/NoPadding"); //$NON-NLS-1$
if (c == null) {
haveOCB = Boolean.FALSE;
return false;
}
} catch (NoClassDefFoundError | Exception e) {
haveOCB = Boolean.FALSE;
return false;
}
haveOCB = Boolean.TRUE;
return true;
}

private static class TestData {

final String name;

final boolean encrypted;

final boolean keyValue;

TestData(String name, boolean encrypted, boolean keyValue) {
this.name = name;
this.encrypted = encrypted;
this.keyValue = keyValue;
}

@Override
public String toString() {
return name;
}
}

@Parameters(name = "{0}")
public static TestData[] initTestData() {
return new TestData[] {
new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false),
new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true),
new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true),
new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true),
new TestData("faked", false, true) };
}

private static byte[] readTestKey(String filename) throws Exception {
try (InputStream in = new BufferedInputStream(
SecretKeysTest.class.getResourceAsStream(filename))) {
return SecretKeys.keyFromNameValueFormat(in);
}
}

private static PGPPublicKey readAsc(InputStream in)
throws IOException, PGPException {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());

Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings();
while (keyRings.hasNext()) {
PGPPublicKeyRing keyRing = keyRings.next();

Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
if (keys.hasNext()) {
return keys.next();
}
}
return null;
}

// Injected by JUnit
@Parameter
public TestData data;

@Test
public void testKeyRead() throws Exception {
if (data.keyValue) {
byte[] bytes = readTestKey(data.name + ".key");
assertEquals('(', bytes[0]);
assertEquals(')', bytes[bytes.length - 1]);
}
try (InputStream pubIn = this.getClass()
.getResourceAsStream(data.name + ".asc")) {
if (pubIn != null) {
PGPPublicKey publicKey = readAsc(pubIn);
// Do a full test trying to load the secret key.
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder()
.build();
try (InputStream in = new BufferedInputStream(this.getClass()
.getResourceAsStream(data.name + ".key"))) {
PGPSecretKey secretKey = SecretKeys.readSecretKey(in,
calculatorProvider,
data.encrypted ? () -> "nonsense".toCharArray()
: null,
publicKey);
assertNotNull(secretKey);
} catch (PGPException e) {
// Currently we may not be able to load OCB-encrypted keys.
assertTrue(e.getMessage().contains("OCB"));
assertTrue(data.encrypted);
assertFalse(ocbAvailable());
}
}
}
}

}

+ 16
- 8
org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF View File

@@ -8,22 +8,30 @@ Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-Version: 6.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)",
Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)",
org.bouncycastle.asn1.cryptlib;version="[1.65.0,2.0.0)",
org.bouncycastle.asn1.x9;version="[1.65.0,2.0.0)",
org.bouncycastle.bcpg;version="[1.65.0,2.0.0)",
org.bouncycastle.bcpg.sig;version="[1.65.0,2.0.0)",
org.bouncycastle.crypto.ec;version="[1.65.0,2.0.0)",
org.bouncycastle.gpg;version="[1.65.0,2.0.0)",
org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)",
org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)",
org.bouncycastle.jcajce.interfaces;version="[1.65.0,2.0.0)",
org.bouncycastle.jcajce.util;version="[1.65.0,2.0.0)",
org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)",
org.bouncycastle.math.ec;version="[1.65.0,2.0.0)",
org.bouncycastle.math.field;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp.jcajce;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)",
org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)",
org.bouncycastle.util;version="[1.65.0,2.0.0)",
org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)",
org.bouncycastle.util.io;version="[1.65.0,2.0.0)",
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)",
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)",
org.eclipse.jgit.errors;version="[6.0.0,6.1.0)",
org.eclipse.jgit.lib;version="[6.0.0,6.1.0)",
org.eclipse.jgit.nls;version="[6.0.0,6.1.0)",
org.eclipse.jgit.transport;version="[6.0.0,6.1.0)",
org.eclipse.jgit.util;version="[6.0.0,6.1.0)",
org.slf4j;version="[1.7.0,2.0.0)"
Export-Package: org.eclipse.jgit.gpg.bc.internal;version="6.0.0";
x-friends:="org.eclipse.jgit.gpg.bc.test"
Export-Package: org.eclipse.jgit.gpg.bc;version="6.0.0",
org.eclipse.jgit.gpg.bc.internal;version="6.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test",
org.eclipse.jgit.gpg.bc.internal.keys;version="6.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test"

+ 1
- 0
org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF View File

@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.source
Bundle-Vendor: Eclipse.org - JGit
Bundle-Version: 6.0.0.qualifier
Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="6.0.0.qualifier";roots="."


+ 38
- 45
org.eclipse.jgit.gpg.bc/about.html View File

@@ -11,7 +11,7 @@
margin: 0.25in 0.5in 0.25in 0.5in;
tab-interval: 0.5in;
}
p {
p {
margin-left: auto;
margin-top: 0.5em;
margin-bottom: 0.5em;
@@ -36,60 +36,53 @@
<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>

<p>All rights reserved.</p>
<p>Redistribution and use in source and binary forms, with or without modification,
<p>Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
<ul><li>Redistributions of source code must retain the above copyright notice,
<ul><li>Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. </li>
<li>Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
<li>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. </li>
<li>Neither the name of the Eclipse Foundation, Inc. nor the names of its
contributors may be used to endorse or promote products derived from
<li>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. </li></ul>
</p>
<p>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
<p>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.</p>

<hr>
<p><b>SHA-1 UbcCheck - MIT</b></p>
<p><b>org.eclipse.jgit.gpg.bc.internal.keys.SExprParser - MIT</b></p>

<p>Copyright (c) 2017:</p>
<div class="ubc-name">
Marc Stevens
Cryptology Group
Centrum Wiskunde & Informatica
P.O. Box 94079, 1090 GB Amsterdam, Netherlands
marc@marc-stevens.nl
</div>
<div class="ubc-name">
Dan Shumow
Microsoft Research
danshu@microsoft.com
</div>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
<p>Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc.
(<a href="https://www.bouncycastle.org">https://www.bouncycastle.org</a>)</p>

<p>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
</p>
<p>
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
</p>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</p>
<ul><li>The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.</li></ul>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.</p>

</body>


+ 1
- 0
org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory View File

@@ -0,0 +1 @@
org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSignatureVerifierFactory

+ 26
- 1
org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties View File

@@ -1,11 +1,36 @@
corrupt25519Key=Ed25519/Curve25519 public key has wrong length: {0}
credentialPassphrase=Passphrase
gpgFailedToParseSecretKey=Failed to parse secret key file in directory: {0}. Is the entered passphrase correct?
cryptCipherError=Cannot create cipher to decrypt: {0}
cryptWrongDecryptedLength=Decrypted key has wrong length; expected {0} bytes, got only {1} bytes
gpgFailedToParseSecretKey=Failed to parse secret key file {0}. Is the entered passphrase correct?
gpgNoCredentialsProvider=missing credentials provider
gpgNoKeygrip=Cannot find key {0}: cannot determine key grip
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}
gpgNoSuchAlgorithm=Cannot decrypt encrypted secret key: encryption algorithm {0} is not available
gpgNotASigningKey=Secret key ({0}) is not suitable for signing
gpgKeyInfo=GPG Key (fingerprint {0})
gpgSigningCancelled=Signing was cancelled
nonSignatureError=Signature does not decode into a signature object
secretKeyTooShort=Secret key file corrupt; only {0} bytes read
sexprHexNotClosed=Hex number in s-expression not closed
sexprHexOdd=Hex number in s-expression has an odd number of digits
sexprStringInvalidEscape=Invalid escape {0} in s-expression
sexprStringInvalidEscapeAtEnd=Invalid s-expression: quoted string ends with escape character
sexprStringInvalidHexEscape=Invalid hex escape in s-expression
sexprStringInvalidOctalEscape=Invalid octal escape in s-expression
sexprStringNotClosed=String in s-expression not closed
sexprUnhandled=Unhandled token {0} in s-expression
signatureInconsistent=Inconsistent signature; key ID {0} does not match issuer fingerprint {1}
signatureKeyLookupError=Error occurred while looking for public key
signatureNoKeyInfo=No way to determine a public key from the signature
signatureNoPublicKey=No public key found to verify the signature
signatureParseError=Signature cannot be parsed
signatureVerificationError=Signature verification failed
unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available.
uncompressed25519Key=Cannot handle ed25519 public key with uncompressed data: {0}
unknownCurve=Unknown curve {0}
unknownCurveParameters=Curve {0} does not have a prime field
unknownKeyType=Unknown key type {0}

+ 34
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc;

import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner;
import org.eclipse.jgit.lib.GpgSigner;

/**
* Factory for creating a {@link GpgSigner} based on Bouncy Castle.
*
* @since 5.11
*/
public final class BouncyCastleGpgSignerFactory {

private BouncyCastleGpgSignerFactory() {
// No instantiation
}

/**
* Creates a new {@link GpgSigner}.
*
* @return the {@link GpgSigner}
*/
public static GpgSigner create() {
return new BouncyCastleGpgSigner();
}
}

+ 34
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java View File

@@ -1,3 +1,12 @@
/*
* Copyright (C) 2018, 2021 Salesforce and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal;

import org.eclipse.jgit.nls.NLS;
@@ -18,16 +27,41 @@ public final class BCText extends TranslationBundle {
}

// @formatter:off
/***/ public String corrupt25519Key;
/***/ public String credentialPassphrase;
/***/ public String cryptCipherError;
/***/ public String cryptWrongDecryptedLength;
/***/ public String gpgFailedToParseSecretKey;
/***/ public String gpgNoCredentialsProvider;
/***/ public String gpgNoKeygrip;
/***/ public String gpgNoKeyring;
/***/ public String gpgNoKeyInLegacySecring;
/***/ public String gpgNoPublicKeyFound;
/***/ public String gpgNoSecretKeyForPublicKey;
/***/ public String gpgNoSuchAlgorithm;
/***/ public String gpgNotASigningKey;
/***/ public String gpgKeyInfo;
/***/ public String gpgSigningCancelled;
/***/ public String nonSignatureError;
/***/ public String secretKeyTooShort;
/***/ public String sexprHexNotClosed;
/***/ public String sexprHexOdd;
/***/ public String sexprStringInvalidEscape;
/***/ public String sexprStringInvalidEscapeAtEnd;
/***/ public String sexprStringInvalidHexEscape;
/***/ public String sexprStringInvalidOctalEscape;
/***/ public String sexprStringNotClosed;
/***/ public String sexprUnhandled;
/***/ public String signatureInconsistent;
/***/ public String signatureKeyLookupError;
/***/ public String signatureNoKeyInfo;
/***/ public String signatureNoPublicKey;
/***/ public String signatureParseError;
/***/ public String signatureVerificationError;
/***/ public String unableToSignCommitNoSecretKey;
/***/ public String uncompressed25519Key;
/***/ public String unknownCurve;
/***/ public String unknownCurveParameters;
/***/ public String unknownKeyType;

}

+ 125
- 99
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018, 2020 Salesforce and others
* Copyright (C) 2018, 2021 Salesforce and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -14,25 +14,22 @@ import static java.nio.file.Files.newInputStream;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
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;
@@ -50,15 +47,15 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
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.gpg.bc.internal.keys.KeyGrip;
import org.eclipse.jgit.gpg.bc.internal.keys.SecretKeys;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
@@ -78,17 +75,10 @@ public class BouncyCastleGpgKeyLocator {

}

/** Thrown if we try to read an encrypted private key without password. */
private static class EncryptedPgpKeyException extends RuntimeException {

private static final long serialVersionUID = 1L;

}

private static final Logger log = LoggerFactory
.getLogger(BouncyCastleGpgKeyLocator.class);

private static final Path GPG_DIRECTORY = findGpgDirectory();
static final Path GPG_DIRECTORY = findGpgDirectory();

private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY
.resolve("pubring.kbx"); //$NON-NLS-1$
@@ -155,16 +145,13 @@ public class BouncyCastleGpgKeyLocator {

private PGPSecretKey attemptParseSecretKey(Path keyFile,
PGPDigestCalculatorProvider calculatorProvider,
PBEProtectionRemoverFactory passphraseProvider,
PGPPublicKey publicKey) {
SecretKeys.PassphraseSupplier passphraseSupplier,
PGPPublicKey publicKey)
throws IOException, PGPException, CanceledException,
UnsupportedCredentialItem, URISyntaxException {
try (InputStream in = newInputStream(keyFile)) {
return new SExprParser(calculatorProvider).parseSecretKey(
new BufferedInputStream(in), passphraseProvider, publicKey);
} catch (IOException | PGPException | ClassCastException e) {
if (log.isDebugEnabled())
log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$
e.getMessage(), e);
return null;
return SecretKeys.readSecretKey(in, calculatorProvider,
passphraseSupplier, publicKey);
}
}

@@ -219,33 +206,60 @@ public class BouncyCastleGpgKeyLocator {
int stop = toMatch.indexOf('>');
return begin >= 0 && end > begin + 1 && stop > 0
&& userId.substring(begin + 1, end)
.equals(toMatch.substring(0, stop));
.equalsIgnoreCase(toMatch.substring(0, stop));
}
case '@': {
int begin = userId.indexOf('<');
int end = userId.indexOf('>', begin + 1);
return begin >= 0 && end > begin + 1
&& userId.substring(begin + 1, end).contains(toMatch);
&& containsIgnoreCase(userId.substring(begin + 1, end),
toMatch);
}
default:
if (toMatch.trim().isEmpty()) {
return false;
}
return userId.toLowerCase(Locale.ROOT)
.contains(toMatch.toLowerCase(Locale.ROOT));
return containsIgnoreCase(userId, toMatch);
}
}

private static boolean containsIgnoreCase(String a, String b) {
int alength = a.length();
int blength = b.length();
for (int i = 0; i + blength <= alength; i++) {
if (a.regionMatches(true, i, b, 0, blength)) {
return true;
}
}
return false;
}

private String toFingerprint(String keyId) {
private static String toFingerprint(String keyId) {
if (keyId.startsWith("0x")) { //$NON-NLS-1$
return keyId.substring(2);
}
return keyId;
}

private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob)
static PGPPublicKey findPublicKey(String fingerprint, String keySpec)
throws IOException, PGPException {
PGPPublicKey result = findPublicKeyInPubring(USER_PGP_PUBRING_FILE,
fingerprint, keySpec);
if (result == null && exists(USER_KEYBOX_PATH)) {
try {
result = findPublicKeyInKeyBox(USER_KEYBOX_PATH, fingerprint,
keySpec);
} catch (NoSuchAlgorithmException | NoSuchProviderException
| IOException | NoOpenPgpKeyException e) {
log.error(e.getMessage(), e);
}
}
return result;
}

private static PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob,
String keyId)
throws IOException {
String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT);
if (keyId.isEmpty()) {
return null;
}
@@ -259,10 +273,11 @@ public class BouncyCastleGpgKeyLocator {
return null;
}

private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob)
private static PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob,
String keySpec)
throws IOException {
for (UserID userID : keyBlob.getUserIds()) {
if (containsSigningKey(userID.getUserIDAsString(), signingKey)) {
if (containsSigningKey(userID.getUserIDAsString(), keySpec)) {
return getSigningPublicKey(keyBlob);
}
}
@@ -274,6 +289,10 @@ public class BouncyCastleGpgKeyLocator {
*
* @param keyboxFile
* the KeyBox file
* @param keyId
* to look for, may be null
* @param keySpec
* to look for
* @return publicKey the public key (maybe <code>null</code>)
* @throws IOException
* in case of problems reading the file
@@ -282,19 +301,22 @@ public class BouncyCastleGpgKeyLocator {
* @throws NoOpenPgpKeyException
* if the file does not contain any OpenPGP key
*/
private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile)
private static PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile,
String keyId, String keySpec)
throws IOException, NoSuchAlgorithmException,
NoSuchProviderException, NoOpenPgpKeyException {
KeyBox keyBox = readKeyBoxFile(keyboxFile);
String id = keyId != null ? keyId
: toFingerprint(keySpec).toLowerCase(Locale.ROOT);
boolean hasOpenPgpKey = false;
for (KeyBlob keyBlob : keyBox.getKeyBlobs()) {
if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) {
hasOpenPgpKey = true;
PGPPublicKey key = findPublicKeyByKeyId(keyBlob);
PGPPublicKey key = findPublicKeyByKeyId(keyBlob, id);
if (key != null) {
return key;
}
key = findPublicKeyByUserId(keyBlob);
key = findPublicKeyByUserId(keyBlob, keySpec);
if (key != null) {
return key;
}
@@ -338,7 +360,8 @@ public class BouncyCastleGpgKeyLocator {
// pubring.gpg also try secring.gpg to find the secret key.
if (exists(USER_KEYBOX_PATH)) {
try {
publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH);
publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH, null,
signingKey);
if (publicKey != null) {
key = findSecretKeyForKeyBoxPublicKey(publicKey,
USER_KEYBOX_PATH);
@@ -361,7 +384,8 @@ public class BouncyCastleGpgKeyLocator {
}
}
if (exists(USER_PGP_PUBRING_FILE)) {
publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE);
publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE, null,
signingKey);
if (publicKey != null) {
// GPG < 2.1 may have both; the agent using the directory
// and gpg using secring.gpg. GPG >= 2.1 delegates all
@@ -433,67 +457,59 @@ public class BouncyCastleGpgKeyLocator {
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();

try (Stream<Path> keyFiles = Files.walk(USER_SECRET_KEY_DIR)) {
List<Path> allPaths = keyFiles.filter(Files::isRegularFile)
.collect(Collectors.toCollection(ArrayList::new));
if (allPaths.isEmpty()) {
return null;
byte[] keyGrip = null;
try {
keyGrip = KeyGrip.getKeyGrip(publicKey);
} catch (PGPException e) {
throw new PGPException(
MessageFormat.format(BCText.get().gpgNoKeygrip,
Hex.toHexString(publicKey.getFingerprint())),
e);
}
String filename = Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT)
+ ".key"; //$NON-NLS-1$
Path keyFile = USER_SECRET_KEY_DIR.resolve(filename);
if (!Files.exists(keyFile)) {
return null;
}
boolean clearPrompt = false;
try {
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder()
.build();
clearPrompt = true;
PGPSecretKey secretKey = null;
try {
secretKey = attemptParseSecretKey(keyFile, calculatorProvider,
() -> passphrasePrompt.getPassphrase(
publicKey.getFingerprint(), userKeyboxPath),
publicKey);
} catch (PGPException e) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgFailedToParseSecretKey,
keyFile.toAbsolutePath()), e);
}
PBEProtectionRemoverFactory passphraseProvider = p -> {
throw new EncryptedPgpKeyException();
};
for (int attempts = 0; attempts < 2; attempts++) {
// Second pass will traverse only the encrypted keys with a real
// passphrase provider.
Iterator<Path> pathIterator = allPaths.iterator();
while (pathIterator.hasNext()) {
Path keyFile = pathIterator.next();
try {
PGPSecretKey secretKey = attemptParseSecretKey(keyFile,
calculatorProvider, passphraseProvider,
publicKey);
pathIterator.remove();
if (secretKey != null) {
if (!secretKey.isSigningKey()) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgNotASigningKey,
signingKey));
}
return new BouncyCastleGpgKey(secretKey,
userKeyboxPath);
}
} catch (EncryptedPgpKeyException e) {
// Ignore; we'll try again.
}
}
if (attempts > 0 || allPaths.isEmpty()) {
break;
if (secretKey != null) {
if (!secretKey.isSigningKey()) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgNotASigningKey, signingKey));
}
// allPaths contains only the encrypted keys now.
passphraseProvider = new JcePBEProtectionRemoverFactory(
passphrasePrompt.getPassphrase(
publicKey.getFingerprint(), userKeyboxPath));
clearPrompt = false;
return new BouncyCastleGpgKey(secretKey, userKeyboxPath);
}

passphrasePrompt.clear();
return null;
} catch (RuntimeException e) {
passphrasePrompt.clear();
throw e;
} catch (FileNotFoundException | NoSuchFileException e) {
clearPrompt = false;
return null;
} catch (IOException e) {
passphrasePrompt.clear();
throw new PGPException(MessageFormat.format(
BCText.get().gpgFailedToParseSecretKey,
USER_SECRET_KEY_DIR.toAbsolutePath()), e);
keyFile.toAbsolutePath()), e);
} finally {
if (clearPrompt) {
passphrasePrompt.clear();
}
}
}

@@ -551,6 +567,11 @@ public class BouncyCastleGpgKeyLocator {
* Return the first public key matching the key id ({@link #signingKey}.
*
* @param pubringFile
* to search
* @param keyId
* to look for, may be null
* @param keySpec
* to look for
*
* @return the PGP public key, or {@code null} if none found
* @throws IOException
@@ -558,14 +579,16 @@ public class BouncyCastleGpgKeyLocator {
* @throws PGPException
* on BouncyCastle errors
*/
private PGPPublicKey findPublicKeyInPubring(Path pubringFile)
private static PGPPublicKey findPublicKeyInPubring(Path pubringFile,
String keyId, String keySpec)
throws IOException, PGPException {
try (InputStream in = newInputStream(pubringFile)) {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
new BufferedInputStream(in),
new JcaKeyFingerprintCalculator());

String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT);
String id = keyId != null ? keyId
: toFingerprint(keySpec).toLowerCase(Locale.ROOT);
Iterator<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings();
while (keyrings.hasNext()) {
PGPPublicKeyRing keyRing = keyrings.next();
@@ -575,30 +598,33 @@ public class BouncyCastleGpgKeyLocator {
// try key id
String fingerprint = Hex.toHexString(key.getFingerprint())
.toLowerCase(Locale.ROOT);
if (fingerprint.endsWith(keyId)) {
if (fingerprint.endsWith(id)) {
return key;
}
// try user id
Iterator<String> userIDs = key.getUserIDs();
while (userIDs.hasNext()) {
String userId = userIDs.next();
if (containsSigningKey(userId, signingKey)) {
if (containsSigningKey(userId, keySpec)) {
return key;
}
}
}
}
} catch (FileNotFoundException | NoSuchFileException e) {
// Ignore and return null
}
return null;
}

private PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint)
private static PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint)
throws IOException {
return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing()
.getPublicKey(fingerprint);
}

private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException {
private static PGPPublicKey getSigningPublicKey(KeyBlob blob)
throws IOException {
PGPPublicKey masterKey = null;
Iterator<PGPPublicKey> keys = ((PublicKeyRingBlob) blob)
.getPGPPublicKeyRing().getPublicKeys();
@@ -618,7 +644,7 @@ public class BouncyCastleGpgKeyLocator {
return masterKey;
}

private boolean isSigningKey(PGPPublicKey key) {
private static boolean isSigningKey(PGPPublicKey key) {
Iterator signatures = key.getSignatures();
while (signatures.hasNext()) {
PGPSignature sig = (PGPSignature) signatures.next();
@@ -630,7 +656,7 @@ public class BouncyCastleGpgKeyLocator {
return false;
}

private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException,
private static KeyBox readKeyBoxFile(Path keyboxFile) throws IOException,
NoSuchAlgorithmException, NoSuchProviderException,
NoOpenPgpKeyException {
if (keyboxFile.toFile().length() == 0) {

+ 3
- 4
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java View File

@@ -17,8 +17,8 @@ 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.transport.CredentialItem.CharArrayType;
import org.eclipse.jgit.transport.CredentialItem.InformationalMessage;
import org.eclipse.jgit.transport.CredentialItem.Password;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.URIish;

@@ -31,7 +31,7 @@ import org.eclipse.jgit.transport.URIish;
*/
class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable {

private CharArrayType passphrase;
private Password passphrase;

private CredentialsProvider credentialsProvider;

@@ -78,8 +78,7 @@ class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable {
throws PGPException, CanceledException, UnsupportedCredentialItem,
URISyntaxException {
if (passphrase == null) {
passphrase = new CharArrayType(BCText.get().credentialPassphrase,
true);
passphrase = new Password(BCText.get().credentialPassphrase);
}

if (credentialsProvider == null) {

+ 388
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java View File

@@ -0,0 +1,388 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;

import org.bouncycastle.bcpg.sig.IssuerFingerprint;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.util.encoders.Hex;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.GpgConfig;
import org.eclipse.jgit.lib.GpgSignatureVerifier;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.util.LRUMap;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.StringUtils;

/**
* A {@link GpgSignatureVerifier} to verify GPG signatures using BouncyCastle.
*/
public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier {

private static void registerBouncyCastleProviderIfNecessary() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}

/**
* Creates a new instance and registers the BouncyCastle security provider
* if needed.
*/
public BouncyCastleGpgSignatureVerifier() {
registerBouncyCastleProviderIfNecessary();
}

// To support more efficient signature verification of multiple objects we
// cache public keys once found in a LRU cache.

private static final Object NO_KEY = new Object();

private LRUMap<String, Object> byFingerprint = new LRUMap<>(16, 200);

private LRUMap<String, Object> bySigner = new LRUMap<>(16, 200);

@Override
public String getName() {
return "bc"; //$NON-NLS-1$
}

@Override
@Nullable
public SignatureVerification verifySignature(@NonNull RevObject object,
@NonNull GpgConfig config) throws IOException {
if (object instanceof RevCommit) {
RevCommit commit = (RevCommit) object;
byte[] signatureData = commit.getRawGpgSignature();
if (signatureData == null) {
return null;
}
byte[] raw = commit.getRawBuffer();
// Now remove the GPG signature
byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
int start = RawParseUtils.headerStart(header, raw, 0);
if (start < 0) {
return null;
}
int end = RawParseUtils.headerEnd(raw, start);
// start is at the beginning of the header's content
start -= header.length + 1;
// end is on the terminating LF; we need to skip that, too
if (end < raw.length) {
end++;
}
byte[] data = new byte[raw.length - (end - start)];
System.arraycopy(raw, 0, data, 0, start);
System.arraycopy(raw, end, data, start, raw.length - end);
return verify(data, signatureData);
} else if (object instanceof RevTag) {
RevTag tag = (RevTag) object;
byte[] signatureData = tag.getRawGpgSignature();
if (signatureData == null) {
return null;
}
byte[] raw = tag.getRawBuffer();
// The signature is just tacked onto the end of the message, which
// is last in the buffer.
byte[] data = Arrays.copyOfRange(raw, 0,
raw.length - signatureData.length);
return verify(data, signatureData);
}
return null;
}

static PGPSignature parseSignature(InputStream in)
throws IOException, PGPException {
try (InputStream sigIn = PGPUtil.getDecoderStream(in)) {
JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn);
Object obj = pgpFactory.nextObject();
if (obj instanceof PGPCompressedData) {
obj = new JcaPGPObjectFactory(
((PGPCompressedData) obj).getDataStream()).nextObject();
}
if (obj instanceof PGPSignatureList) {
return ((PGPSignatureList) obj).get(0);
}
return null;
}
}

@Override
public SignatureVerification verify(byte[] data, byte[] signatureData)
throws IOException {
PGPSignature signature = null;
String fingerprint = null;
String signer = null;
String keyId = null;
try (InputStream sigIn = new ByteArrayInputStream(signatureData)) {
signature = parseSignature(sigIn);
if (signature != null) {
// Try to figure out something to find the public key with.
if (signature.hasSubpackets()) {
PGPSignatureSubpacketVector packets = signature
.getHashedSubPackets();
IssuerFingerprint fingerprintPacket = packets
.getIssuerFingerprint();
if (fingerprintPacket != null) {
fingerprint = Hex
.toHexString(fingerprintPacket.getFingerprint())
.toLowerCase(Locale.ROOT);
}
signer = packets.getSignerUserID();
if (signer != null) {
signer = BouncyCastleGpgSigner.extractSignerId(signer);
}
}
keyId = Long.toUnsignedString(signature.getKeyID(), 16)
.toLowerCase(Locale.ROOT);
} else {
throw new JGitInternalException(BCText.get().nonSignatureError);
}
} catch (PGPException e) {
throw new JGitInternalException(BCText.get().signatureParseError,
e);
}
Date signatureCreatedAt = signature.getCreationTime();
if (fingerprint == null && signer == null && keyId == null) {
return new VerificationResult(signatureCreatedAt, null, null, null,
false, false, TrustLevel.UNKNOWN,
BCText.get().signatureNoKeyInfo);
}
if (fingerprint != null && keyId != null
&& !fingerprint.endsWith(keyId)) {
return new VerificationResult(signatureCreatedAt, signer, fingerprint,
null, false, false, TrustLevel.UNKNOWN,
MessageFormat.format(BCText.get().signatureInconsistent,
keyId, fingerprint));
}
if (fingerprint == null && keyId != null) {
fingerprint = keyId;
}
// Try to find the public key
String keySpec = '<' + signer + '>';
Object cached = null;
PGPPublicKey publicKey = null;
try {
cached = byFingerprint.get(fingerprint);
if (cached != null) {
if (cached instanceof PGPPublicKey) {
publicKey = (PGPPublicKey) cached;
}
} else if (!StringUtils.isEmptyOrNull(signer)) {
cached = bySigner.get(signer);
if (cached != null) {
if (cached instanceof PGPPublicKey) {
publicKey = (PGPPublicKey) cached;
}
}
}
if (cached == null) {
publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint,
keySpec);
}
} catch (IOException | PGPException e) {
throw new JGitInternalException(
BCText.get().signatureKeyLookupError, e);
}
if (publicKey == null) {
if (cached == null) {
byFingerprint.put(fingerprint, NO_KEY);
byFingerprint.put(keyId, NO_KEY);
if (signer != null) {
bySigner.put(signer, NO_KEY);
}
}
return new VerificationResult(signatureCreatedAt, signer,
fingerprint, null, false, false, TrustLevel.UNKNOWN,
BCText.get().signatureNoPublicKey);
}
if (cached == null) {
byFingerprint.put(fingerprint, publicKey);
byFingerprint.put(keyId, publicKey);
if (signer != null) {
bySigner.put(signer, publicKey);
}
}
String user = null;
Iterator<String> userIds = publicKey.getUserIDs();
if (!StringUtils.isEmptyOrNull(signer)) {
while (userIds.hasNext()) {
String userId = userIds.next();
if (BouncyCastleGpgKeyLocator.containsSigningKey(userId,
keySpec)) {
user = userId;
break;
}
}
}
if (user == null) {
userIds = publicKey.getUserIDs();
if (userIds.hasNext()) {
user = userIds.next();
}
}
boolean expired = false;
long validFor = publicKey.getValidSeconds();
if (validFor > 0 && signatureCreatedAt != null) {
Instant expiredAt = publicKey.getCreationTime().toInstant()
.plusSeconds(validFor);
expired = expiredAt.isBefore(signatureCreatedAt.toInstant());
}
// Trust data is not defined in OpenPGP; the format is implementation
// specific. We don't use the GPG trustdb but simply the trust packet
// on the public key, if present. Even if present, it may or may not
// be set.
byte[] trustData = publicKey.getTrustData();
TrustLevel trust = parseGpgTrustPacket(trustData);
boolean verified = false;
try {
signature.init(
new JcaPGPContentVerifierBuilderProvider()
.setProvider(BouncyCastleProvider.PROVIDER_NAME),
publicKey);
signature.update(data);
verified = signature.verify();
} catch (PGPException e) {
throw new JGitInternalException(
BCText.get().signatureVerificationError, e);
}
return new VerificationResult(signatureCreatedAt, signer, fingerprint, user,
verified, expired, trust, null);
}

private TrustLevel parseGpgTrustPacket(byte[] packet) {
if (packet == null || packet.length < 6) {
// A GPG trust packet has at least 6 bytes.
return TrustLevel.UNKNOWN;
}
if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') {
// Not a GPG trust packet
return TrustLevel.UNKNOWN;
}
int trust = packet[0] & 0x0F;
switch (trust) {
case 0: // No determined/set
case 1: // Trust expired; i.e., calculation outdated or key expired
case 2: // Undefined: not enough information to set
return TrustLevel.UNKNOWN;
case 3:
return TrustLevel.NEVER;
case 4:
return TrustLevel.MARGINAL;
case 5:
return TrustLevel.FULL;
case 6:
return TrustLevel.ULTIMATE;
default:
return TrustLevel.UNKNOWN;
}
}

@Override
public void clear() {
byFingerprint.clear();
bySigner.clear();
}

private static class VerificationResult implements SignatureVerification {

private final Date creationDate;

private final String signer;

private final String keyUser;

private final String fingerprint;

private final boolean verified;

private final boolean expired;

private final @NonNull TrustLevel trustLevel;

private final String message;

public VerificationResult(Date creationDate, String signer,
String fingerprint, String user, boolean verified,
boolean expired, @NonNull TrustLevel trust, String message) {
this.creationDate = creationDate;
this.signer = signer;
this.fingerprint = fingerprint;
this.keyUser = user;
this.verified = verified;
this.expired = expired;
this.trustLevel = trust;
this.message = message;
}

@Override
public Date getCreationDate() {
return creationDate;
}

@Override
public String getSigner() {
return signer;
}

@Override
public String getKeyUser() {
return keyUser;
}

@Override
public String getKeyFingerprint() {
return fingerprint;
}

@Override
public boolean isExpired() {
return expired;
}

@Override
public TrustLevel getTrustLevel() {
return trustLevel;
}

@Override
public String getMessage() {
return message;
}

@Override
public boolean getVerified() {
return verified;
}
}
}

+ 28
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal;

import org.eclipse.jgit.lib.GpgSignatureVerifier;
import org.eclipse.jgit.lib.GpgSignatureVerifierFactory;

/**
* A {@link GpgSignatureVerifierFactory} that creates
* {@link GpgSignatureVerifier} instances that verify GPG signatures using
* BouncyCastle and that do cache public keys.
*/
public final class BouncyCastleGpgSignatureVerifierFactory
extends GpgSignatureVerifierFactory {

@Override
public GpgSignatureVerifier getVerifier() {
return new BouncyCastleGpgSignatureVerifier();
}

}

+ 53
- 9
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018, 2020, Salesforce and others
* Copyright (C) 2018, 2021, Salesforce and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -34,18 +34,25 @@ import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.GpgConfig;
import org.eclipse.jgit.lib.GpgObjectSigner;
import org.eclipse.jgit.lib.GpgSignature;
import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.ObjectBuilder;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.util.StringUtils;

/**
* GPG Signer using BouncyCastle library
* GPG Signer using the BouncyCastle library.
*/
public class BouncyCastleGpgSigner extends GpgSigner {
public class BouncyCastleGpgSigner extends GpgSigner
implements GpgObjectSigner {

private static void registerBouncyCastleProviderIfNecessary() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
@@ -67,13 +74,32 @@ public class BouncyCastleGpgSigner extends GpgSigner {
public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
PersonIdent committer, CredentialsProvider credentialsProvider)
throws CanceledException {
try {
return canLocateSigningKey(gpgSigningKey, committer,
credentialsProvider, null);
} catch (UnsupportedSigningFormatException e) {
// Cannot occur with a null config
return false;
}
}

@Override
public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
PersonIdent committer, CredentialsProvider credentialsProvider,
GpgConfig config)
throws CanceledException, UnsupportedSigningFormatException {
if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) {
throw new UnsupportedSigningFormatException(
JGitText.get().onlyOpenPgpSupportedForSigning);
}
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
credentialsProvider)) {
BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
committer, passphrasePrompt);
return gpgKey != null;
} catch (PGPException | IOException | NoSuchAlgorithmException
| NoSuchProviderException | URISyntaxException e) {
} catch (CanceledException e) {
throw e;
} catch (Exception e) {
return false;
}
}
@@ -98,10 +124,28 @@ public class BouncyCastleGpgSigner extends GpgSigner {
public void sign(@NonNull CommitBuilder commit,
@Nullable String gpgSigningKey, @NonNull PersonIdent committer,
CredentialsProvider credentialsProvider) throws CanceledException {
try {
signObject(commit, gpgSigningKey, committer, credentialsProvider,
null);
} catch (UnsupportedSigningFormatException e) {
// Cannot occur with a null config
}
}

@Override
public void signObject(@NonNull ObjectBuilder object,
@Nullable String gpgSigningKey, @NonNull PersonIdent committer,
CredentialsProvider credentialsProvider, GpgConfig config)
throws CanceledException, UnsupportedSigningFormatException {
if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) {
throw new UnsupportedSigningFormatException(
JGitText.get().onlyOpenPgpSupportedForSigning);
}
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
credentialsProvider)) {
BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
committer, passphrasePrompt);
committer,
passphrasePrompt);
PGPSecretKey secretKey = gpgKey.getSecretKey();
if (secretKey == null) {
throw new JGitInternalException(
@@ -158,17 +202,17 @@ public class BouncyCastleGpgSigner extends GpgSigner {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (BCPGOutputStream out = new BCPGOutputStream(
new ArmoredOutputStream(buffer))) {
signatureGenerator.update(commit.build());
signatureGenerator.update(object.build());
signatureGenerator.generate().encode(out);
}
commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
object.setGpgSignature(new GpgSignature(buffer.toByteArray()));
} catch (PGPException | IOException | NoSuchAlgorithmException
| NoSuchProviderException | URISyntaxException e) {
throw new JGitInternalException(e.getMessage(), e);
}
}

private String extractSignerId(String pgpUserId) {
static String extractSignerId(String pgpUserId) {
int from = pgpUserId.indexOf('<');
if (from >= 0) {
int to = pgpUserId.indexOf('>', from + 1);

+ 322
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java View File

@@ -0,0 +1,322 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Arrays;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.bcpg.DSAPublicBCPGKey;
import org.bouncycastle.bcpg.ECPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.field.FiniteField;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.util.encoders.Hex;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.gpg.bc.internal.BCText;
import org.eclipse.jgit.util.sha1.SHA1;

/**
* Utilities to compute the <em>keygrip</em> of a key. A keygrip is a SHA1 hash
* over the public key parameters and is used internally by the gpg-agent to
* find the secret key belonging to a public key: the secret key is stored in a
* file under ~/.gnupg/private-keys-v1.d/ with a name "&lt;keygrip>.key". While
* this storage organization is an implementation detail of GPG, the way
* keygrips are computed is not; they are computed by libgcrypt and their
* definition is stable.
*/
public final class KeyGrip {

// Some OIDs apparently unknown to BouncyCastle.

private static String OID_OPENPGP_ED25519 = "1.3.6.1.4.1.11591.15.1"; //$NON-NLS-1$

private static String OID_RFC8410_CURVE25519 = "1.3.101.110"; //$NON-NLS-1$

private static String OID_RFC8410_ED25519 = "1.3.101.112"; //$NON-NLS-1$

private KeyGrip() {
// No instantiation
}

/**
* Computes the keygrip for a {@link PGPPublicKey}.
*
* @param publicKey
* to get the keygrip of
* @return the keygrip
* @throws PGPException
* if an unknown key type is encountered.
*/
@NonNull
public static byte[] getKeyGrip(PGPPublicKey publicKey)
throws PGPException {
SHA1 grip = SHA1.newInstance();
grip.setDetectCollision(false);

switch (publicKey.getAlgorithm()) {
case PublicKeyAlgorithmTags.RSA_GENERAL:
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_SIGN:
BigInteger modulus = ((RSAPublicBCPGKey) publicKey
.getPublicKeyPacket().getKey()).getModulus();
hash(grip, modulus.toByteArray());
break;
case PublicKeyAlgorithmTags.DSA:
DSAPublicBCPGKey dsa = (DSAPublicBCPGKey) publicKey
.getPublicKeyPacket().getKey();
hash(grip, dsa.getP().toByteArray(), 'p', true);
hash(grip, dsa.getQ().toByteArray(), 'q', true);
hash(grip, dsa.getG().toByteArray(), 'g', true);
hash(grip, dsa.getY().toByteArray(), 'y', true);
break;
case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
ElGamalPublicBCPGKey eg = (ElGamalPublicBCPGKey) publicKey
.getPublicKeyPacket().getKey();
hash(grip, eg.getP().toByteArray(), 'p', true);
hash(grip, eg.getG().toByteArray(), 'g', true);
hash(grip, eg.getY().toByteArray(), 'y', true);
break;
case PublicKeyAlgorithmTags.ECDH:
case PublicKeyAlgorithmTags.ECDSA:
case PublicKeyAlgorithmTags.EDDSA:
ECPublicBCPGKey ec = (ECPublicBCPGKey) publicKey
.getPublicKeyPacket().getKey();
ASN1ObjectIdentifier curveOID = ec.getCurveOID();
// BC doesn't know these OIDs.
if (OID_OPENPGP_ED25519.equals(curveOID.getId())
|| OID_RFC8410_ED25519.equals(curveOID.getId())) {
return hashEd25519(grip, ec.getEncodedPoint());
} else if (CryptlibObjectIdentifiers.curvey25519.equals(curveOID)
|| OID_RFC8410_CURVE25519.equals(curveOID.getId())) {
// curvey25519 actually is the OpenPGP OID for Curve25519 and is
// known to BC, but the parameters are for the short Weierstrass
// form. See https://github.com/bcgit/bc-java/issues/399 .
// libgcrypt uses Montgomery form.
return hashCurve25519(grip, ec.getEncodedPoint());
}
X9ECParameters params = getX9Parameters(curveOID);
if (params == null) {
throw new PGPException(MessageFormat
.format(BCText.get().unknownCurve, curveOID.getId()));
}
// Need to write p, a, b, g, n, q
BigInteger q = ec.getEncodedPoint();
byte[] g = params.getG().getEncoded(false);
BigInteger a = params.getCurve().getA().toBigInteger();
BigInteger b = params.getCurve().getB().toBigInteger();
BigInteger n = params.getN();
BigInteger p = null;
FiniteField field = params.getCurve().getField();
if (ECAlgorithms.isFpField(field)) {
p = field.getCharacteristic();
}
if (p == null) {
// Don't know...
throw new PGPException(MessageFormat.format(
BCText.get().unknownCurveParameters, curveOID.getId()));
}
hash(grip, p.toByteArray(), 'p', false);
hash(grip, a.toByteArray(), 'a', false);
hash(grip, b.toByteArray(), 'b', false);
hash(grip, g, 'g', false);
hash(grip, n.toByteArray(), 'n', false);
if (publicKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) {
hashQ25519(grip, q);
} else {
hash(grip, q.toByteArray(), 'q', false);
}
break;
default:
throw new PGPException(
MessageFormat.format(BCText.get().unknownKeyType,
Integer.toString(publicKey.getAlgorithm())));
}
return grip.digest();
}

private static void hash(SHA1 grip, byte[] data) {
// Need to skip leading zero bytes
int i = 0;
while (i < data.length && data[i] == 0) {
i++;
}
int length = data.length - i;
if (i < data.length) {
if ((data[i] & 0x80) != 0) {
grip.update((byte) 0);
}
grip.update(data, i, length);
}
}

private static void hash(SHA1 grip, byte[] data, char id, boolean zeroPad) {
// Need to skip leading zero bytes
int i = 0;
while (i < data.length && data[i] == 0) {
i++;
}
int length = data.length - i;
boolean addZero = false;
if (i < data.length && zeroPad && (data[i] & 0x80) != 0) {
addZero = true;
}
// libgcrypt includes an SExp in the hash
String prefix = "(1:" + id + (addZero ? length + 1 : length) + ':'; //$NON-NLS-1$
grip.update(prefix.getBytes(StandardCharsets.US_ASCII));
// For some items, gcrypt prepends a zero byte if the high bit is set
if (addZero) {
grip.update((byte) 0);
}
if (i < data.length) {
grip.update(data, i, length);
}
grip.update((byte) ')');
}

private static void hashQ25519(SHA1 grip, BigInteger q)
throws PGPException {
byte[] data = q.toByteArray();
switch (data[0]) {
case 0x04:
if (data.length != 65) {
throw new PGPException(MessageFormat.format(
BCText.get().corrupt25519Key, Hex.toHexString(data)));
}
// Uncompressed: should not occur with ed25519 or curve25519
throw new PGPException(MessageFormat.format(
BCText.get().uncompressed25519Key, Hex.toHexString(data)));
case 0x40:
if (data.length != 33) {
throw new PGPException(MessageFormat.format(
BCText.get().corrupt25519Key, Hex.toHexString(data)));
}
// Compressed; normal case. Skip prefix.
hash(grip, Arrays.copyOfRange(data, 1, data.length), 'q', false);
break;
default:
if (data.length != 32) {
throw new PGPException(MessageFormat.format(
BCText.get().corrupt25519Key, Hex.toHexString(data)));
}
// Compressed format without prefix. Should not occur?
hash(grip, data, 'q', false);
break;
}
}

/**
* Computes the keygrip for an ed25519 public key.
* <p>
* Package-visible for tests only.
* </p>
*
* @param grip
* initialized {@link SHA1}
* @param q
* the public key's EC point
* @return the keygrip
* @throws PGPException
* if q indicates uncompressed format
*/
@SuppressWarnings("nls")
static byte[] hashEd25519(SHA1 grip, BigInteger q) throws PGPException {
// For the values, see RFC 7748: https://tools.ietf.org/html/rfc7748
// p = 2^255 - 19
hash(grip, Hex.decodeStrict(
"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"),
'p', false);
// Field: a = 1
hash(grip, new byte[] { 0x01 }, 'a', false);
// Field: b = 121665/121666 (mod p)
// See Berstein et.al., "Twisted Edwards Curves",
// https://doi.org/10.1007/978-3-540-68164-9_26
hash(grip, Hex.decodeStrict(
"2DFC9311D490018C7338BF8688861767FF8FF5B2BEBE27548A14B235ECA6874A"),
'b', false);
// Generator point with affine X,Y
// @formatter:off
// X(P) = 15112221349535400772501151409588531511454012693041857206046113283949847762202
// Y(P) = 46316835694926478169428394003475163141307993866256225615783033603165251855960
// the "04" signifies uncompressed format.
// @formatter:on
hash(grip, Hex.decodeStrict("04"
+ "216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A"
+ "6666666666666666666666666666666666666666666666666666666666666658"),
'g', false);
// order = 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
hash(grip, Hex.decodeStrict(
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"),
'n', false);
hashQ25519(grip, q);
return grip.digest();
}

/**
* Computes the keygrip for a curve25519 public key.
* <p>
* Package-visible for tests only.
* </p>
*
* @param grip
* initialized {@link SHA1}
* @param q
* the public key's EC point
* @return the keygrip
* @throws PGPException
* if q indicates uncompressed format
*/
@SuppressWarnings("nls")
static byte[] hashCurve25519(SHA1 grip, BigInteger q) throws PGPException {
hash(grip, Hex.decodeStrict(
"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"),
'p', false);
// Unclear: RFC 7748 says A = 486662. This value here is (A-2)/4 =
// 121665. Compare ecc-curves.c in libgcrypt:
// https://github.com/gpg/libgcrypt/blob/361a058/cipher/ecc-curves.c#L146
hash(grip, new byte[] { 0x01, (byte) 0xDB, 0x41 }, 'a', false);
hash(grip, new byte[] { 0x01 }, 'b', false);
// libgcrypt uses the old g.y value before the erratum to RFC 7748 for
// the keygrip. The new value would be
// 5F51E65E475F794B1FE122D388B72EB36DC2B28192839E4DD6163A5D81312C14. See
// https://www.rfc-editor.org/errata/eid4730 and
// https://github.com/gpg/libgcrypt/commit/f67b6492e0b0
hash(grip, Hex.decodeStrict("04"
+ "0000000000000000000000000000000000000000000000000000000000000009"
+ "20AE19A1B8A086B4E01EDD2C7748D14C923D4D7E6D7C61B229E9C5A27ECED3D9"),
'g', false);
hash(grip, Hex.decodeStrict(
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"),
'n', false);
hashQ25519(grip, q);
return grip.digest();
}

private static X9ECParameters getX9Parameters(
ASN1ObjectIdentifier curveOID) {
X9ECParameters params = CustomNamedCurves.getByOID(curveOID);
if (params == null) {
params = ECNamedCurveTable.getByOID(curveOID);
}
return params;
}

}

+ 121
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.util.Arrays;
import org.eclipse.jgit.gpg.bc.internal.BCText;

/**
* A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption.
* It accepts an AAD in the factory's constructor, so the factory can be used to
* create a {@link PBESecretKeyDecryptor} only for a particular input.
* <p>
* For JGit's needs, this is sufficient, but for a general upstream
* implementation that limitation might not be acceptable.
* </p>
*/
class OCBPBEProtectionRemoverFactory
implements PBEProtectionRemoverFactory {

private final PGPDigestCalculatorProvider calculatorProvider;

private final char[] passphrase;

private final byte[] aad;

/**
* Creates a new factory instance with the given parameters.
* <p>
* Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s
* created by the factory can be used to decrypt only a particular input
* matching this AAD.
* </p>
*
* @param passphrase to use for secret key derivation
* @param calculatorProvider for computing digests
* @param aad for the OCB decryption
*/
OCBPBEProtectionRemoverFactory(char[] passphrase,
PGPDigestCalculatorProvider calculatorProvider, byte[] aad) {
this.calculatorProvider = calculatorProvider;
this.passphrase = passphrase;
this.aad = aad;
}

@Override
public PBESecretKeyDecryptor createDecryptor(String protection)
throws PGPException {
return new PBESecretKeyDecryptor(passphrase, calculatorProvider) {

@Override
public byte[] recoverKeyData(int encAlgorithm, byte[] key,
byte[] iv, byte[] encrypted, int encryptedOffset,
int encryptedLength) throws PGPException {
String algorithmName = PGPUtil
.getSymmetricCipherName(encAlgorithm);
byte[] decrypted = null;
try {
Cipher c = Cipher
.getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$
SecretKey secretKey = new SecretKeySpec(key, algorithmName);
c.init(Cipher.DECRYPT_MODE, secretKey,
new IvParameterSpec(iv));
c.updateAAD(aad);
decrypted = new byte[c.getOutputSize(encryptedLength)];
int decryptedLength = c.update(encrypted, encryptedOffset,
encryptedLength, decrypted);
// doFinal() for OCB will check the MAC and throw an
// exception if it doesn't match
decryptedLength += c.doFinal(decrypted, decryptedLength);
if (decryptedLength != decrypted.length) {
throw new PGPException(MessageFormat.format(
BCText.get().cryptWrongDecryptedLength,
Integer.valueOf(decryptedLength),
Integer.valueOf(decrypted.length)));
}
byte[] result = decrypted;
decrypted = null; // Don't clear in finally
return result;
} catch (NoClassDefFoundError e) {
String msg = MessageFormat.format(
BCText.get().gpgNoSuchAlgorithm,
algorithmName + "/OCB"); //$NON-NLS-1$
throw new PGPException(msg,
new NoSuchAlgorithmException(msg, e));
} catch (PGPException e) {
throw e;
} catch (Exception e) {
throw new PGPException(
MessageFormat.format(BCText.get().cryptCipherError,
e.getLocalizedMessage()),
e);
} finally {
if (decrypted != null) {
// Prevent halfway decrypted data leaking.
Arrays.fill(decrypted, (byte) 0);
}
}
}
};
}
}

+ 826
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java View File

@@ -0,0 +1,826 @@
/*
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
*including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* </p>
* <p>
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
* </p>
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </p>
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Date;

import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.bcpg.DSAPublicBCPGKey;
import org.bouncycastle.bcpg.DSASecretBCPGKey;
import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
import org.bouncycastle.bcpg.ECPublicBCPGKey;
import org.bouncycastle.bcpg.ECSecretBCPGKey;
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
import org.bouncycastle.bcpg.RSASecretBCPGKey;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SecretKeyPacket;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;

/**
* A parser for secret keys stored in s-expressions. Original BouncyCastle code
* modified by the JGit team to:
* <ul>
* <li>handle unencrypted DSA, EC, and ElGamal keys (upstream only handles
* unencrypted RSA), and</li>
* <li>handle secret keys using AES/OCB as encryption (those don't have a
* hash).</li>
* </ul>
*/
@SuppressWarnings("nls")
public class SExprParser {
private final PGPDigestCalculatorProvider digestProvider;

/**
* Base constructor.
*
* @param digestProvider
* a provider for digest calculations. Used to confirm key
* protection hashes.
*/
public SExprParser(PGPDigestCalculatorProvider digestProvider) {
this.digestProvider = digestProvider;
}

/**
* Parse a secret key from one of the GPG S expression keys associating it
* with the passed in public key.
*
* @param inputStream
* to read from
* @param keyProtectionRemoverFactory
* for decrypting encrypted keys
* @param pubKey
* the private key should belong to
*
* @return a secret key object.
* @throws IOException
* @throws PGPException
*/
public PGPSecretKey parseSecretKey(InputStream inputStream,
PBEProtectionRemoverFactory keyProtectionRemoverFactory,
PGPPublicKey pubKey) throws IOException, PGPException {
SXprUtils.skipOpenParenthesis(inputStream);

String type;

type = SXprUtils.readString(inputStream, inputStream.read());
if (type.equals("protected-private-key")
|| type.equals("private-key")) {
SXprUtils.skipOpenParenthesis(inputStream);

String keyType = SXprUtils.readString(inputStream,
inputStream.read());
if (keyType.equals("ecc")) {
SXprUtils.skipOpenParenthesis(inputStream);

String curveID = SXprUtils.readString(inputStream,
inputStream.read());
String curveName = SXprUtils.readString(inputStream,
inputStream.read());

SXprUtils.skipCloseParenthesis(inputStream);

byte[] qVal;

SXprUtils.skipOpenParenthesis(inputStream);

type = SXprUtils.readString(inputStream, inputStream.read());
if (type.equals("q")) {
qVal = SXprUtils.readBytes(inputStream, inputStream.read());
} else {
throw new PGPException("no q value found");
}

SXprUtils.skipCloseParenthesis(inputStream);

BigInteger d = processECSecretKey(inputStream, curveID,
curveName, qVal, keyProtectionRemoverFactory);

if (curveName.startsWith("NIST ")) {
curveName = curveName.substring("NIST ".length());
}

ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey(
ECNamedCurveTable.getOID(curveName),
new BigInteger(1, qVal));
ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey
.getPublicKeyPacket().getKey();
if (!basePubKey.getCurveOID().equals(assocPubKey.getCurveOID())
|| !basePubKey.getEncodedPoint()
.equals(assocPubKey.getEncodedPoint())) {
throw new PGPException(
"passed in public key does not match secret key");
}

return new PGPSecretKey(
new SecretKeyPacket(pubKey.getPublicKeyPacket(),
SymmetricKeyAlgorithmTags.NULL, null, null,
new ECSecretBCPGKey(d).getEncoded()),
pubKey);
} else if (keyType.equals("dsa")) {
BigInteger p = readBigInteger("p", inputStream);
BigInteger q = readBigInteger("q", inputStream);
BigInteger g = readBigInteger("g", inputStream);

BigInteger y = readBigInteger("y", inputStream);

BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
keyProtectionRemoverFactory);

DSAPublicBCPGKey basePubKey = new DSAPublicBCPGKey(p, q, g, y);
DSAPublicBCPGKey assocPubKey = (DSAPublicBCPGKey) pubKey
.getPublicKeyPacket().getKey();
if (!basePubKey.getP().equals(assocPubKey.getP())
|| !basePubKey.getQ().equals(assocPubKey.getQ())
|| !basePubKey.getG().equals(assocPubKey.getG())
|| !basePubKey.getY().equals(assocPubKey.getY())) {
throw new PGPException(
"passed in public key does not match secret key");
}
return new PGPSecretKey(
new SecretKeyPacket(pubKey.getPublicKeyPacket(),
SymmetricKeyAlgorithmTags.NULL, null, null,
new DSASecretBCPGKey(x).getEncoded()),
pubKey);
} else if (keyType.equals("elg")) {
BigInteger p = readBigInteger("p", inputStream);
BigInteger g = readBigInteger("g", inputStream);

BigInteger y = readBigInteger("y", inputStream);

BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
keyProtectionRemoverFactory);

ElGamalPublicBCPGKey basePubKey = new ElGamalPublicBCPGKey(p, g,
y);
ElGamalPublicBCPGKey assocPubKey = (ElGamalPublicBCPGKey) pubKey
.getPublicKeyPacket().getKey();
if (!basePubKey.getP().equals(assocPubKey.getP())
|| !basePubKey.getG().equals(assocPubKey.getG())
|| !basePubKey.getY().equals(assocPubKey.getY())) {
throw new PGPException(
"passed in public key does not match secret key");
}

return new PGPSecretKey(
new SecretKeyPacket(pubKey.getPublicKeyPacket(),
SymmetricKeyAlgorithmTags.NULL, null, null,
new ElGamalSecretBCPGKey(x).getEncoded()),
pubKey);
} else if (keyType.equals("rsa")) {
BigInteger n = readBigInteger("n", inputStream);
BigInteger e = readBigInteger("e", inputStream);

BigInteger[] values = processRSASecretKey(inputStream, n, e,
keyProtectionRemoverFactory);

// TODO: type of RSA key?
RSAPublicBCPGKey basePubKey = new RSAPublicBCPGKey(n, e);
RSAPublicBCPGKey assocPubKey = (RSAPublicBCPGKey) pubKey
.getPublicKeyPacket().getKey();
if (!basePubKey.getModulus().equals(assocPubKey.getModulus())
|| !basePubKey.getPublicExponent()
.equals(assocPubKey.getPublicExponent())) {
throw new PGPException(
"passed in public key does not match secret key");
}

return new PGPSecretKey(new SecretKeyPacket(
pubKey.getPublicKeyPacket(),
SymmetricKeyAlgorithmTags.NULL, null, null,
new RSASecretBCPGKey(values[0], values[1], values[2])
.getEncoded()),
pubKey);
} else {
throw new PGPException("unknown key type: " + keyType);
}
}

throw new PGPException("unknown key type found");
}

/**
* Parse a secret key from one of the GPG S expression keys.
*
* @param inputStream
* to read from
* @param keyProtectionRemoverFactory
* for decrypting encrypted keys
* @param fingerPrintCalculator
* for calculating key fingerprints
*
* @return a secret key object.
* @throws IOException
* @throws PGPException
*/
public PGPSecretKey parseSecretKey(InputStream inputStream,
PBEProtectionRemoverFactory keyProtectionRemoverFactory,
KeyFingerPrintCalculator fingerPrintCalculator)
throws IOException, PGPException {
SXprUtils.skipOpenParenthesis(inputStream);

String type;

type = SXprUtils.readString(inputStream, inputStream.read());
if (type.equals("protected-private-key")
|| type.equals("private-key")) {
SXprUtils.skipOpenParenthesis(inputStream);

String keyType = SXprUtils.readString(inputStream,
inputStream.read());
if (keyType.equals("ecc")) {
SXprUtils.skipOpenParenthesis(inputStream);

String curveID = SXprUtils.readString(inputStream,
inputStream.read());
String curveName = SXprUtils.readString(inputStream,
inputStream.read());

if (curveName.startsWith("NIST ")) {
curveName = curveName.substring("NIST ".length());
}

SXprUtils.skipCloseParenthesis(inputStream);

byte[] qVal;

SXprUtils.skipOpenParenthesis(inputStream);

type = SXprUtils.readString(inputStream, inputStream.read());
if (type.equals("q")) {
qVal = SXprUtils.readBytes(inputStream, inputStream.read());
} else {
throw new PGPException("no q value found");
}

PublicKeyPacket pubPacket = new PublicKeyPacket(
PublicKeyAlgorithmTags.ECDSA, new Date(),
new ECDSAPublicBCPGKey(
ECNamedCurveTable.getOID(curveName),
new BigInteger(1, qVal)));

SXprUtils.skipCloseParenthesis(inputStream);

BigInteger d = processECSecretKey(inputStream, curveID,
curveName, qVal, keyProtectionRemoverFactory);

return new PGPSecretKey(
new SecretKeyPacket(pubPacket,
SymmetricKeyAlgorithmTags.NULL, null, null,
new ECSecretBCPGKey(d).getEncoded()),
new PGPPublicKey(pubPacket, fingerPrintCalculator));
} else if (keyType.equals("dsa")) {
BigInteger p = readBigInteger("p", inputStream);
BigInteger q = readBigInteger("q", inputStream);
BigInteger g = readBigInteger("g", inputStream);

BigInteger y = readBigInteger("y", inputStream);

BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
keyProtectionRemoverFactory);

PublicKeyPacket pubPacket = new PublicKeyPacket(
PublicKeyAlgorithmTags.DSA, new Date(),
new DSAPublicBCPGKey(p, q, g, y));

return new PGPSecretKey(
new SecretKeyPacket(pubPacket,
SymmetricKeyAlgorithmTags.NULL, null, null,
new DSASecretBCPGKey(x).getEncoded()),
new PGPPublicKey(pubPacket, fingerPrintCalculator));
} else if (keyType.equals("elg")) {
BigInteger p = readBigInteger("p", inputStream);
BigInteger g = readBigInteger("g", inputStream);

BigInteger y = readBigInteger("y", inputStream);

BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
keyProtectionRemoverFactory);

PublicKeyPacket pubPacket = new PublicKeyPacket(
PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, new Date(),
new ElGamalPublicBCPGKey(p, g, y));

return new PGPSecretKey(
new SecretKeyPacket(pubPacket,
SymmetricKeyAlgorithmTags.NULL, null, null,
new ElGamalSecretBCPGKey(x).getEncoded()),
new PGPPublicKey(pubPacket, fingerPrintCalculator));
} else if (keyType.equals("rsa")) {
BigInteger n = readBigInteger("n", inputStream);
BigInteger e = readBigInteger("e", inputStream);

BigInteger[] values = processRSASecretKey(inputStream, n, e,
keyProtectionRemoverFactory);

// TODO: type of RSA key?
PublicKeyPacket pubPacket = new PublicKeyPacket(
PublicKeyAlgorithmTags.RSA_GENERAL, new Date(),
new RSAPublicBCPGKey(n, e));

return new PGPSecretKey(
new SecretKeyPacket(pubPacket,
SymmetricKeyAlgorithmTags.NULL, null, null,
new RSASecretBCPGKey(values[0], values[1],
values[2]).getEncoded()),
new PGPPublicKey(pubPacket, fingerPrintCalculator));
} else {
throw new PGPException("unknown key type: " + keyType);
}
}

throw new PGPException("unknown key type found");
}

private BigInteger readBigInteger(String expectedType,
InputStream inputStream) throws IOException, PGPException {
SXprUtils.skipOpenParenthesis(inputStream);

String type = SXprUtils.readString(inputStream, inputStream.read());
if (!type.equals(expectedType)) {
throw new PGPException(expectedType + " value expected");
}

byte[] nBytes = SXprUtils.readBytes(inputStream, inputStream.read());
BigInteger v = new BigInteger(1, nBytes);

SXprUtils.skipCloseParenthesis(inputStream);

return v;
}

private static byte[][] extractData(InputStream inputStream,
PBEProtectionRemoverFactory keyProtectionRemoverFactory)
throws PGPException, IOException {
byte[] data;
byte[] protectedAt = null;

SXprUtils.skipOpenParenthesis(inputStream);

String type = SXprUtils.readString(inputStream, inputStream.read());
if (type.equals("protected")) {
String protection = SXprUtils.readString(inputStream,
inputStream.read());

SXprUtils.skipOpenParenthesis(inputStream);

S2K s2k = SXprUtils.parseS2K(inputStream);

byte[] iv = SXprUtils.readBytes(inputStream, inputStream.read());

SXprUtils.skipCloseParenthesis(inputStream);

byte[] secKeyData = SXprUtils.readBytes(inputStream,
inputStream.read());

SXprUtils.skipCloseParenthesis(inputStream);

PBESecretKeyDecryptor keyDecryptor = keyProtectionRemoverFactory
.createDecryptor(protection);

// TODO: recognise other algorithms
byte[] key = keyDecryptor.makeKeyFromPassPhrase(
SymmetricKeyAlgorithmTags.AES_128, s2k);

data = keyDecryptor.recoverKeyData(
SymmetricKeyAlgorithmTags.AES_128, key, iv, secKeyData, 0,
secKeyData.length);

// check if protected at is present
if (inputStream.read() == '(') {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();

bOut.write('(');
int ch;
while ((ch = inputStream.read()) >= 0 && ch != ')') {
bOut.write(ch);
}

if (ch != ')') {
throw new IOException("unexpected end to SExpr");
}

bOut.write(')');

protectedAt = bOut.toByteArray();
}

SXprUtils.skipCloseParenthesis(inputStream);
SXprUtils.skipCloseParenthesis(inputStream);
} else if (type.equals("d") || type.equals("x")) {
// JGit modification: unencrypted DSA or ECC keys can have an "x"
// here
return null;
} else {
throw new PGPException("protected block not found");
}

return new byte[][] { data, protectedAt };
}

private BigInteger processDSASecretKey(InputStream inputStream,
BigInteger p, BigInteger q, BigInteger g, BigInteger y,
PBEProtectionRemoverFactory keyProtectionRemoverFactory)
throws IOException, PGPException {
String type;
byte[][] basicData = extractData(inputStream,
keyProtectionRemoverFactory);

// JGit modification: handle unencrypted DSA keys
if (basicData == null) {
byte[] nBytes = SXprUtils.readBytes(inputStream,
inputStream.read());
BigInteger x = new BigInteger(1, nBytes);
SXprUtils.skipCloseParenthesis(inputStream);
return x;
}

byte[] keyData = basicData[0];
byte[] protectedAt = basicData[1];

//
// parse the secret key S-expr
//
InputStream keyIn = new ByteArrayInputStream(keyData);

SXprUtils.skipOpenParenthesis(keyIn);
SXprUtils.skipOpenParenthesis(keyIn);

BigInteger x = readBigInteger("x", keyIn);

SXprUtils.skipCloseParenthesis(keyIn);

// JGit modification: OCB-encrypted keys don't have and don't need a
// hash
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
return x;
}

SXprUtils.skipOpenParenthesis(keyIn);
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("hash")) {
throw new PGPException("hash keyword expected");
}
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("sha1")) {
throw new PGPException("hash keyword expected");
}

byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

SXprUtils.skipCloseParenthesis(keyIn);

if (digestProvider != null) {
PGPDigestCalculator digestCalculator = digestProvider
.get(HashAlgorithmTags.SHA1);

OutputStream dOut = digestCalculator.getOutputStream();

dOut.write(Strings.toByteArray("(3:dsa"));
writeCanonical(dOut, "p", p);
writeCanonical(dOut, "q", q);
writeCanonical(dOut, "g", g);
writeCanonical(dOut, "y", y);
writeCanonical(dOut, "x", x);

// check protected-at
if (protectedAt != null) {
dOut.write(protectedAt);
}

dOut.write(Strings.toByteArray(")"));

byte[] check = digestCalculator.getDigest();
if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
throw new PGPException(
"checksum on protected data failed in SExpr");
}
}

return x;
}

private BigInteger processElGamalSecretKey(InputStream inputStream,
BigInteger p, BigInteger g, BigInteger y,
PBEProtectionRemoverFactory keyProtectionRemoverFactory)
throws IOException, PGPException {
String type;
byte[][] basicData = extractData(inputStream,
keyProtectionRemoverFactory);

// JGit modification: handle unencrypted EC keys
if (basicData == null) {
byte[] nBytes = SXprUtils.readBytes(inputStream,
inputStream.read());
BigInteger x = new BigInteger(1, nBytes);
SXprUtils.skipCloseParenthesis(inputStream);
return x;
}

byte[] keyData = basicData[0];
byte[] protectedAt = basicData[1];

//
// parse the secret key S-expr
//
InputStream keyIn = new ByteArrayInputStream(keyData);

SXprUtils.skipOpenParenthesis(keyIn);
SXprUtils.skipOpenParenthesis(keyIn);

BigInteger x = readBigInteger("x", keyIn);

SXprUtils.skipCloseParenthesis(keyIn);

// JGit modification: OCB-encrypted keys don't have and don't need a
// hash
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
return x;
}

SXprUtils.skipOpenParenthesis(keyIn);
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("hash")) {
throw new PGPException("hash keyword expected");
}
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("sha1")) {
throw new PGPException("hash keyword expected");
}

byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

SXprUtils.skipCloseParenthesis(keyIn);

if (digestProvider != null) {
PGPDigestCalculator digestCalculator = digestProvider
.get(HashAlgorithmTags.SHA1);

OutputStream dOut = digestCalculator.getOutputStream();

dOut.write(Strings.toByteArray("(3:elg"));
writeCanonical(dOut, "p", p);
writeCanonical(dOut, "g", g);
writeCanonical(dOut, "y", y);
writeCanonical(dOut, "x", x);

// check protected-at
if (protectedAt != null) {
dOut.write(protectedAt);
}

dOut.write(Strings.toByteArray(")"));

byte[] check = digestCalculator.getDigest();
if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
throw new PGPException(
"checksum on protected data failed in SExpr");
}
}

return x;
}

private BigInteger processECSecretKey(InputStream inputStream,
String curveID, String curveName, byte[] qVal,
PBEProtectionRemoverFactory keyProtectionRemoverFactory)
throws IOException, PGPException {
String type;

byte[][] basicData = extractData(inputStream,
keyProtectionRemoverFactory);

// JGit modification: handle unencrypted EC keys
if (basicData == null) {
byte[] nBytes = SXprUtils.readBytes(inputStream,
inputStream.read());
BigInteger d = new BigInteger(1, nBytes);
SXprUtils.skipCloseParenthesis(inputStream);
return d;
}

byte[] keyData = basicData[0];
byte[] protectedAt = basicData[1];

//
// parse the secret key S-expr
//
InputStream keyIn = new ByteArrayInputStream(keyData);

SXprUtils.skipOpenParenthesis(keyIn);
SXprUtils.skipOpenParenthesis(keyIn);
BigInteger d = readBigInteger("d", keyIn);
SXprUtils.skipCloseParenthesis(keyIn);

// JGit modification: OCB-encrypted keys don't have and don't need a
// hash
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
return d;
}

SXprUtils.skipOpenParenthesis(keyIn);

type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("hash")) {
throw new PGPException("hash keyword expected");
}
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("sha1")) {
throw new PGPException("hash keyword expected");
}

byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

SXprUtils.skipCloseParenthesis(keyIn);

if (digestProvider != null) {
PGPDigestCalculator digestCalculator = digestProvider
.get(HashAlgorithmTags.SHA1);

OutputStream dOut = digestCalculator.getOutputStream();

dOut.write(Strings.toByteArray("(3:ecc"));

dOut.write(Strings.toByteArray("(" + curveID.length() + ":"
+ curveID + curveName.length() + ":" + curveName + ")"));

writeCanonical(dOut, "q", qVal);
writeCanonical(dOut, "d", d);

// check protected-at
if (protectedAt != null) {
dOut.write(protectedAt);
}

dOut.write(Strings.toByteArray(")"));

byte[] check = digestCalculator.getDigest();

if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
throw new PGPException(
"checksum on protected data failed in SExpr");
}
}

return d;
}

private BigInteger[] processRSASecretKey(InputStream inputStream,
BigInteger n, BigInteger e,
PBEProtectionRemoverFactory keyProtectionRemoverFactory)
throws IOException, PGPException {
String type;
byte[][] basicData = extractData(inputStream,
keyProtectionRemoverFactory);

byte[] keyData;
byte[] protectedAt = null;

InputStream keyIn;
BigInteger d;

if (basicData == null) {
keyIn = inputStream;
byte[] nBytes = SXprUtils.readBytes(inputStream,
inputStream.read());
d = new BigInteger(1, nBytes);

SXprUtils.skipCloseParenthesis(inputStream);

} else {
keyData = basicData[0];
protectedAt = basicData[1];

keyIn = new ByteArrayInputStream(keyData);

SXprUtils.skipOpenParenthesis(keyIn);
SXprUtils.skipOpenParenthesis(keyIn);
d = readBigInteger("d", keyIn);
}

//
// parse the secret key S-expr
//

BigInteger p = readBigInteger("p", keyIn);
BigInteger q = readBigInteger("q", keyIn);
BigInteger u = readBigInteger("u", keyIn);

// JGit modification: OCB-encrypted keys don't have and don't need a
// hash
if (basicData == null
|| keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
return new BigInteger[] { d, p, q, u };
}

SXprUtils.skipCloseParenthesis(keyIn);

SXprUtils.skipOpenParenthesis(keyIn);
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("hash")) {
throw new PGPException("hash keyword expected");
}
type = SXprUtils.readString(keyIn, keyIn.read());

if (!type.equals("sha1")) {
throw new PGPException("hash keyword expected");
}

byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

SXprUtils.skipCloseParenthesis(keyIn);

if (digestProvider != null) {
PGPDigestCalculator digestCalculator = digestProvider
.get(HashAlgorithmTags.SHA1);

OutputStream dOut = digestCalculator.getOutputStream();

dOut.write(Strings.toByteArray("(3:rsa"));

writeCanonical(dOut, "n", n);
writeCanonical(dOut, "e", e);
writeCanonical(dOut, "d", d);
writeCanonical(dOut, "p", p);
writeCanonical(dOut, "q", q);
writeCanonical(dOut, "u", u);

// check protected-at
if (protectedAt != null) {
dOut.write(protectedAt);
}

dOut.write(Strings.toByteArray(")"));

byte[] check = digestCalculator.getDigest();

if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
throw new PGPException(
"checksum on protected data failed in SExpr");
}
}

return new BigInteger[] { d, p, q, u };
}

private void writeCanonical(OutputStream dOut, String label, BigInteger i)
throws IOException {
writeCanonical(dOut, label, i.toByteArray());
}

private void writeCanonical(OutputStream dOut, String label, byte[] data)
throws IOException {
dOut.write(Strings.toByteArray(
"(" + label.length() + ":" + label + data.length + ":"));
dOut.write(data);
dOut.write(Strings.toByteArray(")"));
}
}

+ 110
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
*including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* </p>
* <p>
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
* </p>
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </p>
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

// This class is an unmodified copy from Bouncy Castle; needed because it's package-visible only and used by SExprParser.

import java.io.IOException;
import java.io.InputStream;

import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.util.io.Streams;

/**
* Utility functions for looking a S-expression keys. This class will move when
* it finds a better home!
* <p>
* Format documented here:
* http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
* </p>
*/
class SXprUtils {
private static int readLength(InputStream in, int ch) throws IOException {
int len = ch - '0';

while ((ch = in.read()) >= 0 && ch != ':') {
len = len * 10 + ch - '0';
}

return len;
}

static String readString(InputStream in, int ch) throws IOException {
int len = readLength(in, ch);

char[] chars = new char[len];

for (int i = 0; i != chars.length; i++) {
chars[i] = (char) in.read();
}

return new String(chars);
}

static byte[] readBytes(InputStream in, int ch) throws IOException {
int len = readLength(in, ch);

byte[] data = new byte[len];

Streams.readFully(in, data);

return data;
}

static S2K parseS2K(InputStream in) throws IOException {
skipOpenParenthesis(in);

// Algorithm is hard-coded to SHA1 below anyway.
readString(in, in.read());
byte[] iv = readBytes(in, in.read());
final long iterationCount = Long.parseLong(readString(in, in.read()));

skipCloseParenthesis(in);

// we have to return the actual iteration count provided.
S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, (int) iterationCount) {
@Override
public long getIterationCount() {
return iterationCount;
}
};

return s2k;
}

static void skipOpenParenthesis(InputStream in) throws IOException {
int ch = in.read();
if (ch != '(') {
throw new IOException(
"unknown character encountered: " + (char) ch); //$NON-NLS-1$
}
}

static void skipCloseParenthesis(InputStream in) throws IOException {
int ch = in.read();
if (ch != ')') {
throw new IOException("unknown character encountered"); //$NON-NLS-1$
}
}
}

+ 597
- 0
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java View File

@@ -0,0 +1,597 @@
/*
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Arrays;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory;
import org.bouncycastle.util.io.Streams;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.gpg.bc.internal.BCText;
import org.eclipse.jgit.util.RawParseUtils;

/**
* Utilities for reading GPG secret keys from a gpg-agent key file.
*/
public final class SecretKeys {

private SecretKeys() {
// No instantiation.
}

/**
* Something that can supply a passphrase to decrypt an encrypted secret
* key.
*/
public interface PassphraseSupplier {

/**
* Supplies a passphrase.
*
* @return the passphrase
* @throws PGPException
* if no passphrase can be obtained
* @throws CanceledException
* if the user canceled passphrase entry
* @throws UnsupportedCredentialItem
* if an internal error occurred
* @throws URISyntaxException
* if an internal error occurred
*/
char[] getPassphrase() throws PGPException, CanceledException,
UnsupportedCredentialItem, URISyntaxException;
}

private static final byte[] PROTECTED_KEY = "protected-private-key" //$NON-NLS-1$
.getBytes(StandardCharsets.US_ASCII);

private static final byte[] OCB_PROTECTED = "openpgp-s2k3-ocb-aes" //$NON-NLS-1$
.getBytes(StandardCharsets.US_ASCII);

/**
* Reads a GPG secret key from the given stream.
*
* @param in
* {@link InputStream} to read from, doesn't need to be buffered
* @param calculatorProvider
* for checking digests
* @param passphraseSupplier
* for decrypting encrypted keys
* @param publicKey
* the secret key should be for
* @return the secret key
* @throws IOException
* if the stream cannot be parsed
* @throws PGPException
* if thrown by the underlying S-Expression parser, for instance
* when the passphrase is wrong
* @throws CanceledException
* if thrown by the {@code passphraseSupplier}
* @throws UnsupportedCredentialItem
* if thrown by the {@code passphraseSupplier}
* @throws URISyntaxException
* if thrown by the {@code passphraseSupplier}
*/
public static PGPSecretKey readSecretKey(InputStream in,
PGPDigestCalculatorProvider calculatorProvider,
PassphraseSupplier passphraseSupplier, PGPPublicKey publicKey)
throws IOException, PGPException, CanceledException,
UnsupportedCredentialItem, URISyntaxException {
byte[] data = Streams.readAll(in);
if (data.length == 0) {
throw new EOFException();
} else if (data.length < 4 + PROTECTED_KEY.length) {
// +4 for "(21:" for a binary protected key
throw new IOException(
MessageFormat.format(BCText.get().secretKeyTooShort,
Integer.toUnsignedString(data.length)));
}
SExprParser parser = new SExprParser(calculatorProvider);
byte firstChar = data[0];
try {
if (firstChar == '(') {
// Binary format.
PBEProtectionRemoverFactory decryptor = null;
if (matches(data, 4, PROTECTED_KEY)) {
// AES/CBC encrypted.
decryptor = new JcePBEProtectionRemoverFactory(
passphraseSupplier.getPassphrase(),
calculatorProvider);
}
try (InputStream sIn = new ByteArrayInputStream(data)) {
return parser.parseSecretKey(sIn, decryptor, publicKey);
}
}
// Assume it's the new key-value format.
try (ByteArrayInputStream keyIn = new ByteArrayInputStream(data)) {
byte[] rawData = keyFromNameValueFormat(keyIn);
if (!matches(rawData, 1, PROTECTED_KEY)) {
// Not encrypted human-readable format.
try (InputStream sIn = new ByteArrayInputStream(
convertSexpression(rawData))) {
return parser.parseSecretKey(sIn, null, publicKey);
}
}
// An encrypted key from a key-value file. Most likely AES/OCB
// encrypted.
boolean isOCB[] = { false };
byte[] sExp = convertSexpression(rawData, isOCB);
PBEProtectionRemoverFactory decryptor;
if (isOCB[0]) {
decryptor = new OCBPBEProtectionRemoverFactory(
passphraseSupplier.getPassphrase(),
calculatorProvider, getAad(sExp));
} else {
decryptor = new JcePBEProtectionRemoverFactory(
passphraseSupplier.getPassphrase(),
calculatorProvider);
}
try (InputStream sIn = new ByteArrayInputStream(sExp)) {
return parser.parseSecretKey(sIn, decryptor, publicKey);
}
}
} catch (IOException e) {
throw new PGPException(e.getLocalizedMessage(), e);
}
}

/**
* Extract the AAD for the OCB decryption from an s-expression.
*
* @param sExp
* buffer containing a valid binary s-expression
* @return the AAD
*/
private static byte[] getAad(byte[] sExp) {
// Given a key
// @formatter:off
// (protected-private-key (rsa ... (protected openpgp-s2k3-ocb-aes ... )(protected-at ...)))
// A B C D
// The AAD is [A..B)[C..D). (From the binary serialized form.)
// @formatter:on
int i = 1; // Skip initial '('
while (sExp[i] != '(') {
i++;
}
int aadStart = i++;
int aadEnd = skip(sExp, aadStart);
byte[] protectedPrefix = "(9:protected" //$NON-NLS-1$
.getBytes(StandardCharsets.US_ASCII);
while (!matches(sExp, i, protectedPrefix)) {
i++;
}
int protectedStart = i;
int protectedEnd = skip(sExp, protectedStart);
byte[] aadData = new byte[aadEnd - aadStart
- (protectedEnd - protectedStart)];
System.arraycopy(sExp, aadStart, aadData, 0, protectedStart - aadStart);
System.arraycopy(sExp, protectedEnd, aadData, protectedStart - aadStart,
aadEnd - protectedEnd);
return aadData;
}

/**
* Skips a list including nested lists.
*
* @param sExp
* buffer containing valid binary s-expression data
* @param start
* index of the opening '(' of the list to skip
* @return the index after the closing ')' of the skipped list
*/
private static int skip(byte[] sExp, int start) {
int i = start + 1;
int depth = 1;
while (depth > 0) {
switch (sExp[i]) {
case '(':
depth++;
break;
case ')':
depth--;
break;
default:
// We must be on a length
int j = i;
while (sExp[j] >= '0' && sExp[j] <= '9') {
j++;
}
// j is on the colon
int length = Integer.parseInt(
new String(sExp, i, j - i, StandardCharsets.US_ASCII));
i = j + length;
}
i++;
}
return i;
}

/**
* Checks whether the {@code needle} matches {@code src} at offset
* {@code from}.
*
* @param src
* to match against {@code needle}
* @param from
* position in {@code src} to start matching
* @param needle
* to match against
* @return {@code true} if {@code src} contains {@code needle} at position
* {@code from}, {@code false} otherwise
*/
private static boolean matches(byte[] src, int from, byte[] needle) {
if (from < 0 || from + needle.length > src.length) {
return false;
}
return org.bouncycastle.util.Arrays.constantTimeAreEqual(needle.length,
src, from, needle, 0);
}

/**
* Converts a human-readable serialized s-expression into a binary
* serialized s-expression.
*
* @param humanForm
* to convert
* @return the converted s-expression
* @throws IOException
* if the conversion fails
*/
private static byte[] convertSexpression(byte[] humanForm)
throws IOException {
boolean[] isOCB = { false };
return convertSexpression(humanForm, isOCB);
}

/**
* Converts a human-readable serialized s-expression into a binary
* serialized s-expression.
*
* @param humanForm
* to convert
* @param isOCB
* returns whether the s-expression specified AES/OCB encryption
* @return the converted s-expression
* @throws IOException
* if the conversion fails
*/
private static byte[] convertSexpression(byte[] humanForm, boolean[] isOCB)
throws IOException {
int pos = 0;
try (ByteArrayOutputStream out = new ByteArrayOutputStream(
humanForm.length)) {
while (pos < humanForm.length) {
byte b = humanForm[pos];
if (b == '(' || b == ')') {
out.write(b);
pos++;
} else if (isGpgSpace(b)) {
pos++;
} else if (b == '#') {
// Hex value follows up to the next #
int i = ++pos;
while (i < humanForm.length && isHex(humanForm[i])) {
i++;
}
if (i == pos || humanForm[i] != '#') {
throw new StreamCorruptedException(
BCText.get().sexprHexNotClosed);
}
if ((i - pos) % 2 != 0) {
throw new StreamCorruptedException(
BCText.get().sexprHexOdd);
}
int l = (i - pos) / 2;
out.write(Integer.toString(l)
.getBytes(StandardCharsets.US_ASCII));
out.write(':');
while (pos < i) {
int x = (nibble(humanForm[pos]) << 4)
| nibble(humanForm[pos + 1]);
pos += 2;
out.write(x);
}
pos = i + 1;
} else if (isTokenChar(b)) {
// Scan the token
int start = pos++;
while (pos < humanForm.length
&& isTokenChar(humanForm[pos])) {
pos++;
}
int l = pos - start;
if (pos - start == OCB_PROTECTED.length
&& matches(humanForm, start, OCB_PROTECTED)) {
isOCB[0] = true;
}
out.write(Integer.toString(l)
.getBytes(StandardCharsets.US_ASCII));
out.write(':');
out.write(humanForm, start, pos - start);
} else if (b == '"') {
// Potentially quoted string.
int start = ++pos;
boolean escaped = false;
while (pos < humanForm.length
&& (escaped || humanForm[pos] != '"')) {
int ch = humanForm[pos++];
escaped = !escaped && ch == '\\';
}
if (pos >= humanForm.length) {
throw new StreamCorruptedException(
BCText.get().sexprStringNotClosed);
}
// start is on the first character of the string, pos on the
// closing quote.
byte[] dq = dequote(humanForm, start, pos);
out.write(Integer.toString(dq.length)
.getBytes(StandardCharsets.US_ASCII));
out.write(':');
out.write(dq);
pos++;
} else {
throw new StreamCorruptedException(
MessageFormat.format(BCText.get().sexprUnhandled,
Integer.toHexString(b & 0xFF)));
}
}
return out.toByteArray();
}
}

/**
* GPG-style string de-quoting, which is basically C-style, with some
* literal CR/LF escaping.
*
* @param in
* buffer containing the quoted string
* @param from
* index after the opening quote in {@code in}
* @param to
* index of the closing quote in {@code in}
* @return the dequoted raw string value
* @throws StreamCorruptedException
*/
private static byte[] dequote(byte[] in, int from, int to)
throws StreamCorruptedException {
// Result must be shorter or have the same length
byte[] out = new byte[to - from];
int j = 0;
int i = from;
while (i < to) {
byte b = in[i++];
if (b != '\\') {
out[j++] = b;
continue;
}
if (i == to) {
throw new StreamCorruptedException(
BCText.get().sexprStringInvalidEscapeAtEnd);
}
b = in[i++];
switch (b) {
case 'b':
out[j++] = '\b';
break;
case 'f':
out[j++] = '\f';
break;
case 'n':
out[j++] = '\n';
break;
case 'r':
out[j++] = '\r';
break;
case 't':
out[j++] = '\t';
break;
case 'v':
out[j++] = 0x0B;
break;
case '"':
case '\'':
case '\\':
out[j++] = b;
break;
case '\r':
// Escaped literal line end. If an LF is following, skip that,
// too.
if (i < to && in[i] == '\n') {
i++;
}
break;
case '\n':
// Same for LF possibly followed by CR.
if (i < to && in[i] == '\r') {
i++;
}
break;
case 'x':
if (i + 1 >= to || !isHex(in[i]) || !isHex(in[i + 1])) {
throw new StreamCorruptedException(
BCText.get().sexprStringInvalidHexEscape);
}
out[j++] = (byte) ((nibble(in[i]) << 4) | nibble(in[i + 1]));
i += 2;
break;
case '0':
case '1':
case '2':
case '3':
if (i + 2 >= to || !isOctal(in[i]) || !isOctal(in[i + 1])
|| !isOctal(in[i + 2])) {
throw new StreamCorruptedException(
BCText.get().sexprStringInvalidOctalEscape);
}
out[j++] = (byte) (((((in[i] - '0') << 3)
| (in[i + 1] - '0')) << 3) | (in[i + 2] - '0'));
i += 3;
break;
default:
throw new StreamCorruptedException(MessageFormat.format(
BCText.get().sexprStringInvalidEscape,
Integer.toHexString(b & 0xFF)));
}
}
return Arrays.copyOf(out, j);
}

/**
* Extracts the key from a GPG name-value-pair key file.
* <p>
* Package-visible for tests only.
* </p>
*
* @param in
* {@link InputStream} to read from; should be buffered
* @return the raw key data as extracted from the file
* @throws IOException
* if the {@code in} stream cannot be read or does not contain a
* key
*/
static byte[] keyFromNameValueFormat(InputStream in) throws IOException {
// It would be nice if we could use RawParseUtils here, but GPG compares
// names case-insensitively. We're only interested in the "Key:"
// name-value pair.
int[] nameLow = { 'k', 'e', 'y', ':' };
int[] nameCap = { 'K', 'E', 'Y', ':' };
int nameIdx = 0;
for (;;) {
int next = in.read();
if (next < 0) {
throw new EOFException();
}
if (next == '\n') {
nameIdx = 0;
} else if (nameIdx >= 0) {
if (nameLow[nameIdx] == next || nameCap[nameIdx] == next) {
nameIdx++;
if (nameIdx == nameLow.length) {
break;
}
} else {
nameIdx = -1;
}
}
}
// We're after "Key:". Read the value as continuation lines.
int last = ':';
byte[] rawData;
try (ByteArrayOutputStream out = new ByteArrayOutputStream(8192)) {
for (;;) {
int next = in.read();
if (next < 0) {
break;
}
if (last == '\n') {
if (next == ' ' || next == '\t') {
// Continuation line; skip this whitespace
last = next;
continue;
}
break; // Not a continuation line
}
out.write(next);
last = next;
}
rawData = out.toByteArray();
}
// GPG trims off trailing whitespace, and a line having only whitespace
// is a single LF.
try (ByteArrayOutputStream out = new ByteArrayOutputStream(
rawData.length)) {
int lineStart = 0;
boolean trimLeading = true;
while (lineStart < rawData.length) {
int nextLineStart = RawParseUtils.nextLF(rawData, lineStart);
if (trimLeading) {
while (lineStart < nextLineStart
&& isGpgSpace(rawData[lineStart])) {
lineStart++;
}
}
// Trim trailing
int i = nextLineStart - 1;
while (lineStart < i && isGpgSpace(rawData[i])) {
i--;
}
if (i <= lineStart) {
// Empty line signifies LF
out.write('\n');
trimLeading = true;
} else {
out.write(rawData, lineStart, i - lineStart + 1);
trimLeading = false;
}
lineStart = nextLineStart;
}
return out.toByteArray();
}
}

private static boolean isGpgSpace(int ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
}

private static boolean isTokenChar(int ch) {
switch (ch) {
case '-':
case '.':
case '/':
case '_':
case ':':
case '*':
case '+':
case '=':
return true;
default:
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')) {
return true;
}
return false;
}
}

private static boolean isHex(int ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
|| (ch >= 'a' && ch <= 'f');
}

private static boolean isOctal(int ch) {
return (ch >= '0' && ch <= '7');
}

private static int nibble(int ch) {
if (ch >= '0' && ch <= '9') {
return ch - '0';
} else if (ch >= 'A' && ch <= 'F') {
return ch - 'A' + 10;
} else if (ch >= 'a' && ch <= 'f') {
return ch - 'a' + 10;
}
return -1;
}
}

+ 12
- 12
org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF View File

@@ -9,20 +9,20 @@ Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
Import-Package: org.apache.http;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.3.0,5.0.0)",
org.apache.http.client.config;version="[4.3.0,5.0.0)",
org.apache.http.client.methods;version="[4.3.0,5.0.0)",
org.apache.http.client.params;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.4.0,5.0.0)",
org.apache.http.client.config;version="[4.4.0,5.0.0)",
org.apache.http.client.methods;version="[4.4.0,5.0.0)",
org.apache.http.client.params;version="[4.4.0,5.0.0)",
org.apache.http.config;version="[4.3.0,5.0.0)",
org.apache.http.conn;version="[4.3.0,5.0.0)",
org.apache.http.conn.params;version="[4.3.0,5.0.0)",
org.apache.http.conn.scheme;version="[4.3.0,5.0.0)",
org.apache.http.conn.socket;version="[4.3.0,5.0.0)",
org.apache.http.conn.ssl;version="[4.3.0,5.0.0)",
org.apache.http.conn.util;version="[4.3.0,5.0.0)",
org.apache.http.conn;version="[4.4.0,5.0.0)",
org.apache.http.conn.params;version="[4.4.0,5.0.0)",
org.apache.http.conn.scheme;version="[4.4.0,5.0.0)",
org.apache.http.conn.socket;version="[4.4.0,5.0.0)",
org.apache.http.conn.ssl;version="[4.4.0,5.0.0)",
org.apache.http.conn.util;version="[4.4.0,5.0.0)",
org.apache.http.entity;version="[4.3.0,5.0.0)",
org.apache.http.impl.client;version="[4.3.0,5.0.0)",
org.apache.http.impl.conn;version="[4.3.0,5.0.0)",
org.apache.http.impl.client;version="[4.4.0,5.0.0)",
org.apache.http.impl.conn;version="[4.4.0,5.0.0)",
org.apache.http.params;version="[4.3.0,5.0.0)",
org.apache.http.ssl;version="[4.3.0,5.0.0)",
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)",

+ 1
- 0
org.eclipse.jgit.http.apache/resources/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.properties View File

@@ -1 +1,2 @@
httpWrongConnectionType=Wrong connection type: expected {0}, got {1}.
unexpectedSSLContextException=unexpected exception when searching for the TLS protocol

+ 24
- 10
org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java View File

@@ -57,9 +57,7 @@ import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
@@ -103,7 +101,11 @@ public class HttpClientConnection implements HttpConnection {

private HostnameVerifier hostnameverifier;

SSLContext ctx;
private SSLContext ctx;

private SSLConnectionSocketFactory socketFactory;

private boolean usePooling = true;

private HttpClient getClient() {
if (client == null) {
@@ -125,11 +127,18 @@ public class HttpClientConnection implements HttpConnection {
configBuilder
.setRedirectsEnabled(followRedirects.booleanValue());
}
SSLConnectionSocketFactory sslConnectionFactory = getSSLSocketFactory();
boolean pooled = true;
SSLConnectionSocketFactory sslConnectionFactory;
if (socketFactory != null) {
pooled = usePooling;
sslConnectionFactory = socketFactory;
} else {
// Legacy implementation.
pooled = (hostnameverifier == null);
sslConnectionFactory = getSSLSocketFactory();
}
clientBuilder.setSSLSocketFactory(sslConnectionFactory);
if (hostnameverifier != null) {
// Using a custom verifier: we don't want pooled connections
// with this.
if (!pooled) {
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory> create()
.register("https", sslConnectionFactory)
@@ -147,14 +156,19 @@ public class HttpClientConnection implements HttpConnection {
return client;
}

void setSSLSocketFactory(@NonNull SSLConnectionSocketFactory factory,
boolean isDefault) {
socketFactory = factory;
usePooling = isDefault;
}

private SSLConnectionSocketFactory getSSLSocketFactory() {
HostnameVerifier verifier = hostnameverifier;
SSLContext context;
if (verifier == null) {
// Use defaults
context = SSLContexts.createDefault();
verifier = new DefaultHostnameVerifier(
PublicSuffixMatcherLoader.getDefault());
context = SSLContexts.createSystemDefault();
verifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
} else {
// Using a custom verifier. Attention: configure() must have been
// called already, otherwise one gets a "context not initialized"

+ 82
- 9
org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com> and others
* Copyright (C) 2013, 2020 Christian Halstrick <christian.halstrick@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -12,27 +12,100 @@ package org.eclipse.jgit.transport.http.apache;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.text.MessageFormat;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.HttpConnectionFactory2;
import org.eclipse.jgit.transport.http.NoCheckX509TrustManager;
import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
import org.eclipse.jgit.util.HttpSupport;

/**
* A factory returning instances of
* {@link org.eclipse.jgit.transport.http.apache.HttpClientConnection}
* A factory returning instances of {@link HttpClientConnection}.
*
* @since 3.3
*/
public class HttpClientConnectionFactory implements HttpConnectionFactory {
/** {@inheritDoc} */
public class HttpClientConnectionFactory implements HttpConnectionFactory2 {
@Override
public HttpConnection create(URL url) throws IOException {
return new HttpClientConnection(url.toString());
}

/** {@inheritDoc} */
@Override
public HttpConnection create(URL url, Proxy proxy)
throws IOException {
public HttpConnection create(URL url, Proxy proxy) throws IOException {
return new HttpClientConnection(url.toString(), proxy);
}

@Override
public GitSession newSession() {
return new HttpClientSession();
}

private static class HttpClientSession implements GitSession {

private SSLContext securityContext;

private SSLConnectionSocketFactory socketFactory;

private boolean isDefault;

@Override
public HttpClientConnection configure(HttpConnection connection,
boolean sslVerify)
throws IOException, GeneralSecurityException {
if (!(connection instanceof HttpClientConnection)) {
throw new IllegalArgumentException(MessageFormat.format(
HttpApacheText.get().httpWrongConnectionType,
HttpClientConnection.class.getName(),
connection.getClass().getName()));
}
HttpClientConnection conn = (HttpClientConnection) connection;
String scheme = conn.getURL().getProtocol();
if (!"https".equals(scheme)) { //$NON-NLS-1$
return conn;
}
if (securityContext == null || isDefault != sslVerify) {
isDefault = sslVerify;
HostnameVerifier verifier;
if (sslVerify) {
securityContext = SSLContext.getDefault();
verifier = SSLConnectionSocketFactory
.getDefaultHostnameVerifier();
} else {
securityContext = SSLContext.getInstance("TLS");
TrustManager[] trustAllCerts = {
new NoCheckX509TrustManager() };
securityContext.init(null, trustAllCerts, null);
verifier = (name, session) -> true;
}
socketFactory = new SSLConnectionSocketFactory(securityContext,
verifier) {

@Override
protected void prepareSocket(SSLSocket socket)
throws IOException {
super.prepareSocket(socket);
HttpSupport.configureTLS(socket);
}
};
}
conn.setSSLSocketFactory(socketFactory, isDefault);
return conn;
}

@Override
public void close() {
securityContext = null;
socketFactory = null;
}

}
}

+ 1
- 0
org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java View File

@@ -27,5 +27,6 @@ public class HttpApacheText extends TranslationBundle {
}

// @formatter:off
/***/ public String httpWrongConnectionType;
/***/ public String unexpectedSSLContextException;
}

+ 2
- 2
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java View File

@@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
import org.eclipse.jgit.internal.storage.file.PackFile;
import org.eclipse.jgit.internal.storage.file.Pack;
import org.eclipse.jgit.lib.ObjectDatabase;

/** Sends the current list of pack files, sorted most recent first. */
@@ -38,7 +38,7 @@ class InfoPacksServlet extends HttpServlet {
final StringBuilder out = new StringBuilder();
final ObjectDatabase db = getRepository(req).getObjectDatabase();
if (db instanceof ObjectDirectory) {
for (PackFile pack : ((ObjectDirectory) db).getPacks()) {
for (Pack pack : ((ObjectDirectory) db).getPacks()) {
out.append("P ");
out.append(pack.getPackFile().getName());
out.append('\n');

+ 2
- 2
org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs View File

@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private

+ 2
- 1
org.eclipse.jgit.http.test/META-INF/MANIFEST.MF View File

@@ -12,7 +12,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
org.apache.commons.codec;version="[1.6.0,2.0.0)",
org.apache.commons.codec.binary;version="[1.6.0,2.0.0)",
org.apache.http;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.4.0,5.0.0)",
org.apache.http.message;version="[4.3.0,5.0.0)",
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)",
org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
@@ -28,6 +28,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
org.eclipse.jgit.api;version="[6.0.0,6.1.0)",
org.eclipse.jgit.errors;version="[6.0.0,6.1.0)",
org.eclipse.jgit.http.server;version="[6.0.0,6.1.0)",
org.eclipse.jgit.http.server.glue;version="[6.0.0,6.1.0)",

+ 2
- 0
org.eclipse.jgit.http.test/build.properties View File

@@ -4,3 +4,5 @@ output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.properties
additional.bundles = org.apache.log4j,\
org.slf4j.binding.log4j12

+ 11
- 0
org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java View File

@@ -83,6 +83,17 @@ class RefsUnreadableInMemoryRepository extends InMemoryRepository {
return super.getRefsByPrefix(prefix);
}

/** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
throws IOException {
if (failing) {
throw new IOException("disk failed, no refs found");
}

return super.getRefsByPrefixWithExclusions(include, excludes);
}

/** {@inheritDoc} */
@Override
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {

+ 99
- 0
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AllProtocolsHttpTestCase.java View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.http.test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.jgit.junit.http.HttpTestCase;
import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
* Abstract test base class for running HTTP-related tests with all connection
* factories provided in JGit and with both protocol V0 and V2.
*/
@Ignore
@RunWith(Parameterized.class)
public abstract class AllProtocolsHttpTestCase extends HttpTestCase {

protected static class TestParameters {

public final HttpConnectionFactory factory;

public final boolean enableProtocolV2;

public TestParameters(HttpConnectionFactory factory,
boolean enableProtocolV2) {
this.factory = factory;
this.enableProtocolV2 = enableProtocolV2;
}

@Override
public String toString() {
return factory.toString() + " protocol "
+ (enableProtocolV2 ? "V2" : "V0");
}
}

@Parameters(name = "{0}")
public static Collection<TestParameters> data() {
// run all tests with both connection factories we have
HttpConnectionFactory[] factories = new HttpConnectionFactory[] {
new JDKHttpConnectionFactory() {

@Override
public String toString() {
return this.getClass().getSuperclass().getName();
}
}, new HttpClientConnectionFactory() {

@Override
public String toString() {
return this.getClass().getSuperclass().getName();
}
} };
List<TestParameters> result = new ArrayList<>();
for (HttpConnectionFactory factory : factories) {
result.add(new TestParameters(factory, false));
result.add(new TestParameters(factory, true));
}
return result;
}

protected final boolean enableProtocolV2;

protected AllProtocolsHttpTestCase(TestParameters params) {
HttpTransport.setConnectionFactory(params.factory);
enableProtocolV2 = params.enableProtocolV2;
}

private static HttpConnectionFactory originalFactory;

@BeforeClass
public static void saveConnectionFactory() {
originalFactory = HttpTransport.getConnectionFactory();
}

@AfterClass
public static void restoreConnectionFactory() {
HttpTransport.setConnectionFactory(originalFactory);
}

}

+ 4
- 0
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java View File

@@ -35,6 +35,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.FetchConnection;
@@ -77,6 +78,9 @@ public class DumbClientDumbServerTest extends AllFactoriesHttpTestCase {

remoteRepository = src.getRepository();
remoteURI = toURIish(app, srcGit.getName());
StoredConfig cfg = remoteRepository.getConfig();
cfg.setInt("protocol", null, "version", 0);
cfg.save();

A_txt = src.blob("A");
A = src.commit().add("A_txt", A_txt).create();

+ 7
- 4
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java View File

@@ -35,6 +35,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.FetchConnection;
@@ -42,11 +43,10 @@ import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportHttp;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.junit.Before;
import org.junit.Test;

public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase {
public class DumbClientSmartServerTest extends AllProtocolsHttpTestCase {
private Repository remoteRepository;

private URIish remoteURI;
@@ -55,8 +55,8 @@ public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase {

private RevCommit A, B;

public DumbClientSmartServerTest(HttpConnectionFactory cf) {
super(cf);
public DumbClientSmartServerTest(TestParameters params) {
super(params);
}

@Override
@@ -76,6 +76,9 @@ public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase {

remoteRepository = src.getRepository();
remoteURI = toURIish(app, srcName);
StoredConfig cfg = remoteRepository.getConfig();
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0);
cfg.save();

A_txt = src.blob("A");
A = src.commit().add("A_txt", A_txt).create();

+ 16
- 7
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java View File

@@ -324,7 +324,22 @@ public class HttpClientTests extends AllFactoriesHttpTestCase {
}

@Test
public void testHttpClientWantsV2ButServerNotConfigured() throws Exception {
public void testHttpClientWantsV2AndServerNotConfigured() throws Exception {
String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
HttpConnection c = HttpTransport.getConnectionFactory()
.create(new URL(url));
c.setRequestMethod("GET");
c.setRequestProperty("Git-Protocol", "version=2");
assertEquals(200, c.getResponseCode());

PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
assertThat(pckIn.readString(), is("version 2"));
}

@Test
public void testHttpServerConfiguredToV0() throws Exception {
remoteRepository.getRepository().getConfig().setInt(
"protocol", null, "version", 0);
String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
HttpConnection c = HttpTransport.getConnectionFactory()
.create(new URL(url));
@@ -342,9 +357,6 @@ public class HttpClientTests extends AllFactoriesHttpTestCase {

@Test
public void testV2HttpFirstResponse() throws Exception {
remoteRepository.getRepository().getConfig().setInt(
"protocol", null, "version", 2);

String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
HttpConnection c = HttpTransport.getConnectionFactory()
.create(new URL(url));
@@ -364,9 +376,6 @@ public class HttpClientTests extends AllFactoriesHttpTestCase {

@Test
public void testV2HttpSubsequentResponse() throws Exception {
remoteRepository.getRepository().getConfig().setInt(
"protocol", null, "version", 2);

String url = smartAuthNoneURI.toString() + "/git-upload-pack";
HttpConnection c = HttpTransport.getConnectionFactory()
.create(new URL(url));

+ 12
- 11
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch> and others
* Copyright (C) 2017, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -41,6 +41,7 @@ import org.eclipse.jgit.junit.http.AppServer;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialItem;
@@ -48,7 +49,6 @@ import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.util.HttpSupport;
import org.junit.Before;
import org.junit.Test;
@@ -56,7 +56,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase {
public class SmartClientSmartServerSslTest extends AllProtocolsHttpTestCase {

// We run these tests with a server on localhost with a self-signed
// certificate. We don't do authentication tests here, so there's no need
@@ -112,8 +112,8 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase {

private RevCommit A, B;

public SmartClientSmartServerSslTest(HttpConnectionFactory cf) {
super(cf);
public SmartClientSmartServerSslTest(TestParameters params) {
super(params);
}

@Override
@@ -128,10 +128,11 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase {

final TestRepository<Repository> src = createTestRepository();
final String srcName = src.getRepository().getDirectory().getName();
src.getRepository()
.getConfig()
.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
StoredConfig cfg = src.getRepository().getConfig();
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0);
cfg.save();

GitServlet gs = new GitServlet();

@@ -238,7 +239,7 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase {
fsck(dst, B);

List<AccessEvent> requests = getRequests();
assertEquals(2, requests.size());
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
}

@Test
@@ -256,7 +257,7 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase {
fsck(dst, B);

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
}

@Test

+ 476
- 80
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010, 2017 Google Inc. and others
* Copyright (C) 2010, 2020 Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -22,10 +22,17 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.EnumSet;
@@ -48,6 +55,8 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.errors.RemoteRepositoryException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
@@ -70,6 +79,7 @@ import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -86,13 +96,14 @@ import org.eclipse.jgit.transport.TransportHttp;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.util.HttpSupport;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.Test;

public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase {
private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";

private AdvertiseRefsHook advertiseRefsHook;
@@ -120,8 +131,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {

private RevCommit A, B, unreachableCommit;

public SmartClientSmartServerTest(HttpConnectionFactory cf) {
super(cf);
public SmartClientSmartServerTest(TestParameters params) {
super(params);
}

@Override
@@ -131,10 +142,11 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {

final TestRepository<Repository> src = createTestRepository();
final String srcName = src.getRepository().getDirectory().getName();
src.getRepository()
.getConfig()
.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
StoredConfig cfg = src.getRepository().getConfig();
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0);
cfg.save();

GitServlet gs = new GitServlet();
gs.setUploadPackFactory((HttpServletRequest req, Repository db) -> {
@@ -448,7 +460,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(B, map.get(Constants.HEAD).getObjectId());

List<AccessEvent> requests = getRequests();
assertEquals(1, requests.size());
assertEquals(enableProtocolV2 ? 2 : 1, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
@@ -458,7 +470,22 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement", info
.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
} else {
AccessEvent lsRefs = requests.get(1);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}
}

@Test
@@ -576,9 +603,10 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(2, requests.size());
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());

AccessEvent info = requests.get(0);
int requestNumber = 0;
AccessEvent info = requests.get(requestNumber++);
assertEquals("GET", info.getMethod());
assertEquals(join(remoteURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
@@ -586,9 +614,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement", info
.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
} else {
AccessEvent lsRefs = requests.get(requestNumber++);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}

AccessEvent service = requests.get(1);
AccessEvent service = requests.get(requestNumber);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
@@ -602,6 +645,63 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
.getResponseHeader(HDR_CONTENT_TYPE));
}

@Test
public void test_CloneWithCustomFactory() throws Exception {
HttpConnectionFactory globalFactory = HttpTransport
.getConnectionFactory();
HttpConnectionFactory failingConnectionFactory = new HttpConnectionFactory() {

@Override
public HttpConnection create(URL url) throws IOException {
throw new IOException("Should not be reached");
}

@Override
public HttpConnection create(URL url, Proxy proxy)
throws IOException {
throw new IOException("Should not be reached");
}
};
HttpTransport.setConnectionFactory(failingConnectionFactory);
try {
File tmp = createTempDirectory("cloneViaApi");
boolean[] localFactoryUsed = { false };
TransportConfigCallback callback = new TransportConfigCallback() {

@Override
public void configure(Transport transport) {
if (transport instanceof TransportHttp) {
((TransportHttp) transport).setHttpConnectionFactory(
new HttpConnectionFactory() {

@Override
public HttpConnection create(URL url)
throws IOException {
localFactoryUsed[0] = true;
return globalFactory.create(url);
}

@Override
public HttpConnection create(URL url,
Proxy proxy) throws IOException {
localFactoryUsed[0] = true;
return globalFactory.create(url, proxy);
}
});
}
}
};
try (Git git = Git.cloneRepository().setDirectory(tmp)
.setTransportConfigCallback(callback)
.setURI(remoteURI.toPrivateString()).call()) {
assertTrue("Should have used the local HttpConnectionFactory",
localFactoryUsed[0]);
}
} finally {
HttpTransport.setConnectionFactory(globalFactory);
}
}

private void initialClone_Redirect(int nofRedirects, int code)
throws Exception {
initialClone_Redirect(nofRedirects, code, false);
@@ -628,7 +728,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(2 + nofRedirects, requests.size());
assertEquals((enableProtocolV2 ? 3 : 2) + nofRedirects,
requests.size());

int n = 0;
while (n < nofRedirects) {
@@ -644,7 +745,22 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
} else {
AccessEvent lsRefs = requests.get(n++);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}

AccessEvent service = requests.get(n++);
assertEquals("POST", service.getMethod());
@@ -756,7 +872,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
@@ -766,24 +882,27 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
}

AccessEvent redirect = requests.get(1);
assertEquals("POST", redirect.getMethod());
assertEquals(301, redirect.getStatus());

AccessEvent service = requests.get(2);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
for (int i = 2; i < requests.size(); i++) {
AccessEvent service = requests.get(i);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}
}

@Test
@@ -817,6 +936,35 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}
}

private void assertFetchRequests(List<AccessEvent> requests, int index) {
AccessEvent info = requests.get(index++);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
}

for (int i = index; i < requests.size(); i++) {
AccessEvent service = requests.get(i);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}
}

@Test
public void testInitialClone_WithAuthentication() throws Exception {
try (Repository dst = createBareRepository();
@@ -830,34 +978,167 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(401, info.getStatus());

info = requests.get(1);
assertFetchRequests(requests, 1);
}

@Test
public void testInitialClone_WithPreAuthentication() throws Exception {
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, authURI)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
}

List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());

assertFetchRequests(requests, 0);
}

@Test
public void testInitialClone_WithPreAuthenticationCleared()
throws Exception {
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, authURI)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password);
((TransportHttp) t).setPreemptiveBasicAuthentication(null, null);
t.setCredentialsProvider(testCredentials);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
}

List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
assertEquals(401, info.getStatus());

AccessEvent service = requests.get(2);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));
assertFetchRequests(requests, 1);
}

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
@Test
public void testInitialClone_PreAuthenticationTooLate() throws Exception {
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, authURI)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
assertFetchRequests(requests, 0);
assertThrows(IllegalStateException.class,
() -> ((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password));
assertThrows(IllegalStateException.class, () -> ((TransportHttp) t)
.setPreemptiveBasicAuthentication(null, null));
}
}

@Test
public void testInitialClone_WithWrongPreAuthenticationAndCredentialProvider()
throws Exception {
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, authURI)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password + 'x');
t.setCredentialsProvider(testCredentials);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
}

List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(401, info.getStatus());

assertFetchRequests(requests, 1);
}

@Test
public void testInitialClone_WithWrongPreAuthentication() throws Exception {
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, authURI)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password + 'x');
TransportException e = assertThrows(TransportException.class,
() -> t.fetch(NullProgressMonitor.INSTANCE,
mirror(master)));
String msg = e.getMessage();
assertTrue("Unexpected exception message: " + msg,
msg.contains("no CredentialsProvider"));
}
List<AccessEvent> requests = getRequests();
assertEquals(1, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(401, info.getStatus());
}

@Test
public void testInitialClone_WithUserInfo() throws Exception {
URIish withUserInfo = authURI.setUser(AppServer.username)
.setPass(AppServer.password);
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, withUserInfo)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
}

List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());

assertFetchRequests(requests, 0);
}

@Test
public void testInitialClone_PreAuthOverridesUserInfo() throws Exception {
URIish withUserInfo = authURI.setUser(AppServer.username)
.setPass(AppServer.password + 'x');
try (Repository dst = createBareRepository();
Transport t = Transport.open(dst, withUserInfo)) {
assertFalse(dst.getObjectDatabase().has(A_txt));
((TransportHttp) t).setPreemptiveBasicAuthentication(
AppServer.username, AppServer.password);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
assertTrue(dst.getObjectDatabase().has(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);
}

List<AccessEvent> requests = getRequests();
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());

assertFetchRequests(requests, 0);
}

@Test
@@ -937,19 +1218,20 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(4, requests.size());
assertEquals(enableProtocolV2 ? 5 : 4, requests.size());

AccessEvent redirect = requests.get(0);
int requestNumber = 0;
AccessEvent redirect = requests.get(requestNumber++);
assertEquals("GET", redirect.getMethod());
assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
assertEquals(301, redirect.getStatus());

AccessEvent info = requests.get(1);
AccessEvent info = requests.get(requestNumber++);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(401, info.getStatus());

info = requests.get(2);
info = requests.get(requestNumber++);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
@@ -957,9 +1239,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
} else {
AccessEvent lsRefs = requests.get(requestNumber++);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(authURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}

AccessEvent service = requests.get(3);
AccessEvent service = requests.get(requestNumber);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
@@ -987,7 +1284,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
}

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
@@ -997,25 +1294,30 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
if (!enableProtocolV2) {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
}

AccessEvent service = requests.get(1);
assertEquals("POST", service.getMethod());
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
assertEquals(401, service.getStatus());

service = requests.get(2);
assertEquals("POST", service.getMethod());
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
for (int i = 2; i < requests.size(); i++) {
service = requests.get(i);
assertEquals("POST", service.getMethod());
assertEquals(join(authOnPostURI, "git-upload-pack"),
service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}
}

@Test
@@ -1052,9 +1354,11 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {

List<AccessEvent> requests = getRequests();
requests.removeAll(cloneRequests);
assertEquals(2, requests.size());

AccessEvent info = requests.get(0);
assertEquals(enableProtocolV2 ? 3 : 2, requests.size());

int requestNumber = 0;
AccessEvent info = requests.get(requestNumber++);
assertEquals("GET", info.getMethod());
assertEquals(join(remoteURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
@@ -1063,9 +1367,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));

if (enableProtocolV2) {
AccessEvent lsRefs = requests.get(requestNumber++);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}

// We should have needed one request to perform the fetch.
//
AccessEvent service = requests.get(1);
AccessEvent service = requests.get(requestNumber);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
@@ -1116,9 +1435,10 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {

List<AccessEvent> requests = getRequests();
requests.removeAll(cloneRequests);
assertEquals(3, requests.size());
assertEquals(enableProtocolV2 ? 4 : 3, requests.size());

AccessEvent info = requests.get(0);
int requestNumber = 0;
AccessEvent info = requests.get(requestNumber++);
assertEquals("GET", info.getMethod());
assertEquals(join(remoteURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
@@ -1127,10 +1447,25 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals("application/x-git-upload-pack-advertisement", info
.getResponseHeader(HDR_CONTENT_TYPE));

if (enableProtocolV2) {
AccessEvent lsRefs = requests.get(requestNumber++);
assertEquals("POST", lsRefs.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
assertEquals(0, lsRefs.getParameters().size());
assertNotNull("has content-length",
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
assertEquals(200, lsRefs.getStatus());
assertEquals("application/x-git-upload-pack-result",
lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
}

// We should have needed two requests to perform the fetch
// due to the high number of local unknown commits.
//
AccessEvent service = requests.get(1);
AccessEvent service = requests.get(requestNumber++);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
@@ -1143,7 +1478,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
assertEquals("application/x-git-upload-pack-result", service
.getResponseHeader(HDR_CONTENT_TYPE));

service = requests.get(2);
service = requests.get(requestNumber);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
@@ -1157,6 +1492,64 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
.getResponseHeader(HDR_CONTENT_TYPE));
}

@Test
public void testFetch_MaxHavesCutoffAfterAckOnly() throws Exception {
// Bootstrap by doing the clone.
//
TestRepository dst = createTestRepository();
try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
}
assertEquals(B, dst.getRepository().exactRef(master).getObjectId());

// Force enough into the local client that enumeration will
// need more than MAX_HAVES (256) haves to be sent. The server
// doesn't know any of these, so it will never ACK. The client
// should keep going.
//
// If it does, client and server will find a common commit, and
// the pack file will contain exactly the one commit object Z
// we create on the remote, which we can test for via the progress
// monitor, which should have something like
// "Receiving objects: 100% (1/1)". If the client sends a "done"
// too early, the server will send more objects, and we'll have
// a line like "Receiving objects: 100% (8/8)".
TestRepository.BranchBuilder b = dst.branch(master);
// The client will send 32 + 64 + 128 + 256 + 512 haves. Only the
// last one will be a common commit. If the cutoff kicks in too
// early (after 480), we'll get too many objects in the fetch.
for (int i = 0; i < 992; i++)
b.commit().tick(3600 /* 1 hour */).message("c" + i).create();

// Create a new commit on the remote.
//
RevCommit Z;
try (TestRepository<Repository> tr = new TestRepository<>(
remoteRepository)) {
b = tr.branch(master);
Z = b.commit().message("Z").create();
}

// Now incrementally update.
//
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
TextProgressMonitor monitor = new TextProgressMonitor(writer);
try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(monitor, mirror(master));
}
assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());

String progressMessages = new String(buffer.toByteArray(),
StandardCharsets.UTF_8);
Pattern expected = Pattern
.compile("Receiving objects:\\s+100% \\(1/1\\)\n");
if (!expected.matcher(progressMessages).find()) {
System.out.println(progressMessages);
fail("Expected only one object to be sent");
}
}

@Test
public void testInitialClone_BrokenServer() throws Exception {
try (Repository dst = createBareRepository();
@@ -1211,7 +1604,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
Collections.<ObjectId> emptySet());
fail("Server accepted want " + id.name());
} catch (TransportException err) {
assertEquals("want " + id.name() + " not valid", err.getMessage());
assertTrue(err.getMessage()
.contains("want " + id.name() + " not valid"));
}
}

@@ -1224,6 +1618,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
new DfsRepositoryDescription(repoName));
final TestRepository<Repository> repo = new TestRepository<>(
badRefsRepo);
badRefsRepo.getConfig().setInt("protocol", null, "version",
enableProtocolV2 ? 2 : 0);

ServletContextHandler app = noRefServer.addContext("/git");
GitServlet gs = new GitServlet();
@@ -1253,7 +1649,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
Collections.<ObjectId> emptySet());
fail("Successfully served ref with value " + c.getRef(master));
} catch (TransportException err) {
assertEquals("Internal server error", err.getMessage());
assertTrue("Unexpected exception message " + err.getMessage(),
err.getMessage().contains("Internal server error"));
}
} finally {
noRefServer.tearDown();
@@ -1429,5 +1826,4 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase {
cfg.setBoolean("http", null, "receivepack", true);
cfg.save();
}

}

+ 2
- 2
org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java View File

@@ -23,8 +23,8 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.security.AbstractLoginService;
@@ -259,7 +259,7 @@ public class AppServer {
static class TestMappedLoginService extends AbstractLoginService {
private String role;

protected final ConcurrentMap<String, UserPrincipal> users = new ConcurrentHashMap<>();
protected final Map<String, UserPrincipal> users = new ConcurrentHashMap<>();

TestMappedLoginService(String role) {
this.role = role;

+ 145
- 0
org.eclipse.jgit.junit.ssh/.settings/edu.umd.cs.findbugs.core.prefs View File

@@ -0,0 +1,145 @@
#SpotBugs User Preferences
#Fri Dec 04 11:26:04 CET 2020
detectorExplicitSerialization=ExplicitSerialization|true
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
detectorWrongMapIterator=WrongMapIterator|true
detectorUnnecessaryMath=UnnecessaryMath|true
detectorUselessSubclassMethod=UselessSubclassMethod|false
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|15
detectorURLProblems=URLProblems|true
detectorIteratorIdioms=IteratorIdioms|true
detectorMutableEnum=MutableEnum|true
detectorFindNonShortCircuit=FindNonShortCircuit|true
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
detectorVolatileUsage=VolatileUsage|true
detectorFindNakedNotify=FindNakedNotify|true
detectorFindUninitializedGet=FindUninitializedGet|true
detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
detectorSwitchFallthrough=SwitchFallthrough|true
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
detectorConfusedInheritance=ConfusedInheritance|true
detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true
detectorMutableStaticFields=MutableStaticFields|true
detectorInvalidJUnitTest=InvalidJUnitTest|true
detectorInfiniteLoop=InfiniteLoop|true
detectorFindRunInvocations=FindRunInvocations|true
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
detectorXMLFactoryBypass=XMLFactoryBypass|true
detectorFindOpenStream=FindOpenStream|true
detectorCheckExpectedWarnings=CheckExpectedWarnings|false
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
detectorStringConcatenation=StringConcatenation|true
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
detectorFinalizerNullsFields=FinalizerNullsFields|true
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
detectorInefficientToArray=InefficientToArray|false
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
detectorInconsistentAnnotations=InconsistentAnnotations|true
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
detectorInstantiateStaticClass=InstantiateStaticClass|true
detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true
detectorMethodReturnCheck=MethodReturnCheck|true
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
detectorFindDoubleCheck=FindDoubleCheck|true
detectorFindBadForLoop=FindBadForLoop|true
detectorDefaultEncodingDetector=DefaultEncodingDetector|true
detectorFindInconsistentSync2=FindInconsistentSync2|true
detectorFindSpinLoop=FindSpinLoop|true
detectorFindMaskedFields=FindMaskedFields|true
detectorBooleanReturnNull=BooleanReturnNull|true
detectorFindUnsyncGet=FindUnsyncGet|true
detectorCrossSiteScripting=CrossSiteScripting|true
detectorDroppedException=DroppedException|true
detectorFindDeadLocalStores=FindDeadLocalStores|true
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
detectorFindRefComparison=FindRefComparison|true
detectorFindRoughConstants=FindRoughConstants|true
detectorMutableLock=MutableLock|true
detectorFindNullDeref=FindNullDeref|true
detectorFindReturnRef=FindReturnRef|true
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
detectorFindUselessControlFlow=FindUselessControlFlow|true
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
detectorFindSelfComparison=FindSelfComparison|true
detectorFindFloatEquality=FindFloatEquality|true
detectorFindComparatorProblems=FindComparatorProblems|true
detectorRepeatedConditionals=RepeatedConditionals|true
filter_settings_neg=NOISE|
detectorInefficientMemberAccess=InefficientMemberAccess|false
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
detectorNumberConstructor=NumberConstructor|true
detectorDontAssertInstanceofInTests=DontAssertInstanceofInTests|true
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
detectorFindUnconditionalWait=FindUnconditionalWait|true
detectorFindTwoLockWait=FindTwoLockWait|true
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
detectorFindUnreleasedLock=FindUnreleasedLock|true
detectorInefficientIndexOf=InefficientIndexOf|false
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
detectorOverridingMethodsMustInvokeSuperDetector=OverridingMethodsMustInvokeSuperDetector|true
detectorWaitInLoop=WaitInLoop|true
detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
detectorFindSqlInjection=FindSqlInjection|true
detectorUnreadFields=UnreadFields|true
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
detectorFindUselessObjects=FindUselessObjects|true
detectorBadAppletConstructor=BadAppletConstructor|false
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
detectorSerializableIdiom=SerializableIdiom|true
detectorNaming=Naming|true
detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true
detectorFormatStringChecker=FormatStringChecker|true
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
detectorEmptyZipFileEntry=EmptyZipFileEntry|false
detectorFindCircularDependencies=FindCircularDependencies|false
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
detectorAtomicityProblem=AtomicityProblem|true
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
detectorInitializationChain=InitializationChain|true
detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true
detectorOptionalReturnNull=OptionalReturnNull|true
detectorStartInConstructor=StartInConstructor|true
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
detectorRedundantConditions=RedundantConditions|true
effort=default
detectorRedundantInterfaces=RedundantInterfaces|true
detectorDuplicateBranches=DuplicateBranches|true
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
detectorComparatorIdiom=ComparatorIdiom|true
detectorFindBadCast2=FindBadCast2|true
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
excludefilter0=findBugs/FindBugsExcludeFilter.xml|true
detectorBadResultSetAccess=BadResultSetAccess|true
detectorIncompatMask=IncompatMask|true
detectorCovariantArrayAssignment=CovariantArrayAssignment|false
detectorDumbMethodInvocations=DumbMethodInvocations|true
run_at_full_build=false
detectorStaticCalendarDetector=StaticCalendarDetector|true
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
detectorVarArgsProblems=VarArgsProblems|true
detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|false
detectorCloneIdiom=CloneIdiom|true
detectorFindHEmismatch=FindHEmismatch|true
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
detectorFindSelfComparison2=FindSelfComparison2|true
detectorLazyInit=LazyInit|true
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
detectorDontUseEnum=DontUseEnum|true
detectorFindPuzzlers=FindPuzzlers|true
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
detector_threshold=2
detectorPublicSemaphores=PublicSemaphores|false
detectorDumbMethods=DumbMethods|true

+ 25
- 22
org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF View File

@@ -8,28 +8,31 @@ Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.apache.sshd.common;version="[2.4.0,2.5.0)",
org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)",
org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)",
org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)",
org.apache.sshd.common.io;version="[2.4.0,2.5.0)",
org.apache.sshd.common.kex;version="[2.4.0,2.5.0)",
org.apache.sshd.common.keyprovider;version="[2.4.0,2.5.0)",
org.apache.sshd.common.session;version="[2.4.0,2.5.0)",
org.apache.sshd.common.util.buffer;version="[2.4.0,2.5.0)",
org.apache.sshd.common.util.logging;version="[2.4.0,2.5.0)",
org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)",
org.apache.sshd.common.util.threads;version="[2.4.0,2.5.0)",
org.apache.sshd.server;version="[2.4.0,2.5.0)",
org.apache.sshd.server.auth;version="[2.4.0,2.5.0)",
org.apache.sshd.server.auth.gss;version="[2.4.0,2.5.0)",
org.apache.sshd.server.auth.keyboard;version="[2.4.0,2.5.0)",
org.apache.sshd.server.auth.password;version="[2.4.0,2.5.0)",
org.apache.sshd.server.command;version="[2.4.0,2.5.0)",
org.apache.sshd.server.session;version="[2.4.0,2.5.0)",
org.apache.sshd.server.shell;version="[2.4.0,2.5.0)",
org.apache.sshd.server.subsystem;version="[2.4.0,2.5.0)",
org.apache.sshd.server.subsystem.sftp;version="[2.4.0,2.5.0)",
Import-Package: org.apache.sshd.common;version="[2.6.0,2.7.0)",
org.apache.sshd.common.config.keys;version="[2.6.0,2.7.0)",
org.apache.sshd.common.file.virtualfs;version="[2.6.0,2.7.0)",
org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)",
org.apache.sshd.common.io;version="[2.6.0,2.7.0)",
org.apache.sshd.common.kex;version="[2.6.0,2.7.0)",
org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)",
org.apache.sshd.common.session;version="[2.6.0,2.7.0)",
org.apache.sshd.common.signature;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.buffer;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.logging;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.threads;version="[2.6.0,2.7.0)",
org.apache.sshd.core;version="[2.6.0,2.7.0)",
org.apache.sshd.server;version="[2.6.0,2.7.0)",
org.apache.sshd.server.auth;version="[2.6.0,2.7.0)",
org.apache.sshd.server.auth.gss;version="[2.6.0,2.7.0)",
org.apache.sshd.server.auth.keyboard;version="[2.6.0,2.7.0)",
org.apache.sshd.server.auth.password;version="[2.6.0,2.7.0)",
org.apache.sshd.server.command;version="[2.6.0,2.7.0)",
org.apache.sshd.server.session;version="[2.6.0,2.7.0)",
org.apache.sshd.server.shell;version="[2.6.0,2.7.0)",
org.apache.sshd.server.subsystem;version="[2.6.0,2.7.0)",
org.apache.sshd.sftp;version="[2.6.0,2.7.0)",
org.apache.sshd.sftp.server;version="[2.6.0,2.7.0)",
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)",
org.eclipse.jgit.api;version="[6.0.0,6.1.0)",
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)",

+ 9
- 0
org.eclipse.jgit.junit.ssh/findBugs/FindBugsExcludeFilter.xml View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<FindBugsFilter>
<!-- Silence returning null for Boolean return type -->
<Match>
<Class name="org.eclipse.jgit.junit.ssh.SshTestGitServer$FakeUserAuthGSS" />
<Method name="doAuth" />
<Bug pattern="NP_BOOLEAN_RETURN_NULL" />
</Match>
</FindBugsFilter>

+ 71
- 0
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshBasicTestBase.java View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.junit.ssh;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;

import org.eclipse.jgit.api.Git;
import org.junit.Test;

/**
* Some minimal cloning and fetching tests. Concrete subclasses can implement
* the abstract operations from {@link SshTestHarness} to run with different SSH
* implementations.
*/
public abstract class SshBasicTestBase extends SshTestHarness {

protected File defaultCloneDir;

@Override
public void setUp() throws Exception {
super.setUp();
defaultCloneDir = new File(getTemporaryDirectory(), "cloned");
}

@Test
public void testSshCloneWithConfig() throws Exception {
cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
"Host localhost", //
"HostName localhost", //
"Port " + testPort, //
"User " + TEST_USER, //
"IdentityFile " + privateKey1.getAbsolutePath());
}

@Test
public void testSshFetchWithConfig() throws Exception {
File localClone = cloneWith("ssh://localhost/doesntmatter",
defaultCloneDir, null, //
"Host localhost", //
"HostName localhost", //
"Port " + testPort, //
"User " + TEST_USER, //
"IdentityFile " + privateKey1.getAbsolutePath());
// Do a commit in the upstream repo
try (Git git = new Git(db)) {
writeTrashFile("SomeOtherFile.txt", "Other commit");
git.add().addFilepattern("SomeOtherFile.txt").call();
git.commit().setMessage("New commit").call();
}
// Pull in the clone
try (Git git = Git.open(localClone)) {
File f = new File(git.getRepository().getWorkTree(),
"SomeOtherFile.txt");
assertFalse(f.exists());
git.pull().setRemote("origin").call();
assertTrue(f.exists());
assertEquals("Other commit", read(f));
}
}
}

+ 1
- 19
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java View File

@@ -40,7 +40,7 @@ import org.junit.experimental.theories.Theory;
* abstract operations from {@link SshTestHarness}. This gives a way to test
* different ssh clients against a unified test suite.
*/
public abstract class SshTestBase extends SshTestHarness {
public abstract class SshTestBase extends SshBasicTestBase {

@DataPoints
public static String[] KEY_RESOURCES = { //
@@ -65,14 +65,6 @@ public abstract class SshTestBase extends SshTestHarness {
"id_ed25519_testpass", //
"id_ed25519_expensive_testpass" };

protected File defaultCloneDir;

@Override
public void setUp() throws Exception {
super.setUp();
defaultCloneDir = new File(getTemporaryDirectory(), "cloned");
}

@Test
public void testSshWithoutConfig() throws Exception {
assertThrows(TransportException.class,
@@ -132,16 +124,6 @@ public abstract class SshTestBase extends SshTestHarness {
+ "/doesntmatter", defaultCloneDir, null);
}

@Test
public void testSshWithConfig() throws Exception {
cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
"Host localhost", //
"HostName localhost", //
"Port " + testPort, //
"User " + TEST_USER, //
"IdentityFile " + privateKey1.getAbsolutePath());
}

@Test
public void testSshWithConfigEncryptedUnusedKey() throws Exception {
// Copy the encrypted test key from the bundle.

+ 49
- 17
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.jgit.junit.ssh;

import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES;
import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -21,26 +24,28 @@ import java.security.KeyPair;
import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.signature.BuiltinSignatures;
import org.apache.sshd.common.signature.Signature;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.threads.CloseableExecutorService;
import org.apache.sshd.common.util.threads.ThreadUtils;
import org.apache.sshd.server.ServerAuthenticationManager;
import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.ServerBuilder;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.UserAuth;
import org.apache.sshd.server.auth.UserAuthFactory;
@@ -52,8 +57,9 @@ import org.apache.sshd.server.command.AbstractCommandSupport;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.shell.UnknownCommand;
import org.apache.sshd.server.subsystem.SubsystemFactory;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.RemoteConfig;
@@ -161,7 +167,9 @@ public class SshTestGitServer {
this.testUser = testUser;
setTestUserPublicKey(testKey);
this.repository = repository;
server = SshServer.setUpDefaultServer();
ServerBuilder builder = ServerBuilder.builder()
.signatureFactories(getSignatureFactories());
server = builder.build();
hostKeys.add(hostKey);
server.setKeyPairProvider((session) -> hostKeys);

@@ -186,6 +194,37 @@ public class SshTestGitServer {
});
}

/**
* Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to
* set it up explicitly to still allow users to connect with DSA keys.
*
* @return a list of supported signature factories
*/
@SuppressWarnings("deprecation")
private static List<NamedFactory<Signature>> getSignatureFactories() {
// @formatter:off
return Arrays.asList(
BuiltinSignatures.nistp256_cert,
BuiltinSignatures.nistp384_cert,
BuiltinSignatures.nistp521_cert,
BuiltinSignatures.ed25519_cert,
BuiltinSignatures.rsaSHA512_cert,
BuiltinSignatures.rsaSHA256_cert,
BuiltinSignatures.rsa_cert,
BuiltinSignatures.nistp256,
BuiltinSignatures.nistp384,
BuiltinSignatures.nistp521,
BuiltinSignatures.ed25519,
BuiltinSignatures.sk_ecdsa_sha2_nistp256,
BuiltinSignatures.sk_ssh_ed25519,
BuiltinSignatures.rsaSHA512,
BuiltinSignatures.rsaSHA256,
BuiltinSignatures.rsa,
BuiltinSignatures.dsa_cert,
BuiltinSignatures.dsa);
// @formatter:on
}

private static PublicKey readPublicKey(Path key)
throws IOException, GeneralSecurityException {
return AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
@@ -202,7 +241,7 @@ public class SshTestGitServer {

private static class FakeUserAuthGSS extends UserAuthGSS {
@Override
protected Boolean doAuth(Buffer buffer, boolean initial)
protected @Nullable Boolean doAuth(Buffer buffer, boolean initial)
throws Exception {
// We always reply that we did do this, but then we fail at the
// first token message. That way we can test that the client-side
@@ -277,14 +316,8 @@ public class SshTestGitServer {
@NonNull
protected List<SubsystemFactory> configureSubsystems() {
// SFTP.
server.setFileSystemFactory(new VirtualFileSystemFactory() {

@Override
protected Path computeRootDir(Session session) throws IOException {
return SshTestGitServer.this.repository.getDirectory()
.getParentFile().getAbsoluteFile().toPath();
}
});
server.setFileSystemFactory(new VirtualFileSystemFactory(repository
.getDirectory().getParentFile().getAbsoluteFile().toPath()));
return Collections
.singletonList((new SftpSubsystemFactory.Builder()).build());
}
@@ -433,9 +466,8 @@ public class SshTestGitServer {
*/
public void setPreamble(String... lines) {
if (lines != null && lines.length > 0) {
PropertyResolverUtils.updateProperty(this.server,
ServerFactoryManager.SERVER_EXTRA_IDENTIFICATION_LINES,
String.join("|", lines));
SERVER_EXTRA_IDENTIFICATION_LINES.set(server, String.join(
String.valueOf(SERVER_EXTRA_IDENT_LINES_SEPARATOR), lines));
}
}


+ 10
- 4
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java View File

@@ -9,10 +9,10 @@
*/
package org.eclipse.jgit.junit;

import static java.lang.ClassLoader.getSystemClassLoader;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
@@ -40,7 +40,13 @@ public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {
private static Class<?> loadNewClass(Class<?> klass)
throws InitializationError {
try {
URL[] urls = ((URLClassLoader) getSystemClassLoader()).getURLs();
String pathSeparator = System.getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path")
.split(pathSeparator);
URL[] urls = new URL[classPathEntries.length];
for (int i = 0; i < classPathEntries.length; i++) {
urls[i] = Paths.get(classPathEntries[i]).toUri().toURL();
}
ClassLoader testClassLoader = new URLClassLoader(urls) {

@Override
@@ -54,7 +60,7 @@ public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {
}
};
return Class.forName(klass.getName(), true, testClassLoader);
} catch (ClassNotFoundException e) {
} catch (ClassNotFoundException | MalformedURLException e) {
throw new InitializationError(e);
}
}

+ 3
- 3
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java View File

@@ -43,7 +43,7 @@ import org.eclipse.jgit.errors.ObjectWritingException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
import org.eclipse.jgit.internal.storage.file.PackFile;
import org.eclipse.jgit.internal.storage.file.Pack;
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -773,7 +773,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
rw.writeInfoRefs();

final StringBuilder w = new StringBuilder();
for (PackFile p : fr.getObjectDatabase().getPacks()) {
for (Pack p : fr.getObjectDatabase().getPacks()) {
w.append("P ");
w.append(p.getPackFile().getName());
w.append('\n');
@@ -954,7 +954,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
}

private static void prunePacked(ObjectDirectory odb) throws IOException {
for (PackFile p : odb.getPacks()) {
for (Pack p : odb.getPacks()) {
for (MutableEntry e : p)
FileUtils.delete(odb.fileFor(e.toObjectId()));
}

+ 5
- 5
org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF View File

@@ -9,11 +9,11 @@ Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="[3.1.0,4.0.0)",
javax.servlet.http;version="[3.1.0,4.0.0)",
org.apache.http;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.3.0,5.0.0)",
org.apache.http.client.methods;version="[4.3.0,5.0.0)",
org.apache.http.entity;version="[4.3.0,5.0.0)",
org.apache.http.impl.client;version="[4.3.0,5.0.0)",
org.apache.http;version="[4.4.0,5.0.0)",
org.apache.http.client;version="[4.4.0,5.0.0)",
org.apache.http.client.methods;version="[4.4.0,5.0.0)",
org.apache.http.entity;version="[4.4.0,5.0.0)",
org.apache.http.impl.client;version="[4.4.0,5.0.0)",
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)",
org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
org.eclipse.jetty.io;version="[9.4.5,10.0.0)",

+ 0
- 1
org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF View File

@@ -24,7 +24,6 @@ Import-Package: com.google.gson;version="[2.8.0,3.0.0)",
javax.servlet.annotation;version="[3.1.0,4.0.0)",
javax.servlet.http;version="[3.1.0,4.0.0)",
org.apache.http;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.3.0,5.0.0)",
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal.storage.file;version="[6.0.0,6.1.0)",

+ 8
- 0
org.eclipse.jgit.lfs.server/findBugs/FindBugsExcludeFilter.xml View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<FindBugsFilter>
<!-- Field is written by gson, seems like spotbugs doesn't recognize this -->
<Match>
<Class name="org.eclipse.jgit.lfs.server.LfsObject" />
<Bug pattern="UWF_UNWRITTEN_FIELD" />
</Match>
</FindBugsFilter>

+ 2
- 2
org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs View File

@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private

+ 267
- 3
org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java View File

@@ -12,7 +12,13 @@ package org.eclipse.jgit.lfs.lib;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

@@ -23,17 +29,275 @@ import org.junit.Test;
* Test LfsPointer file abstraction
*/
public class LFSPointerTest {

private static final String TEST_SHA256 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10";

@Test
public void testEncoding() throws IOException {
final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10";
AnyLongObjectId id = LongObjectId.fromString(s);
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer ptr = new LfsPointer(id, 4);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ptr.encode(baos);
assertEquals(
"version https://git-lfs.github.com/spec/v1\noid sha256:"
+ s + "\nsize 4\n",
+ TEST_SHA256 + "\nsize 4\n",
baos.toString(UTF_8.name()));
}
}

@Test
public void testReadValidLfsPointer() throws Exception {
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "size 4\n";
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertEquals(lfs, LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadValidLfsPointerUnordered() throws Exception {
// This is actually not allowed per the spec, but JGit accepts it
// anyway.
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "size 4\n"
+ "oid sha256:" + TEST_SHA256 + '\n';
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertEquals(lfs, LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadValidLfsPointerVersionNotFirst() throws Exception {
// This is actually not allowed per the spec, but JGit accepts it
// anyway.
String ptr = "oid sha256:" + TEST_SHA256 + '\n'
+ "size 4\n"
+ "version https://git-lfs.github.com/spec/v1\n";
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertEquals(lfs, LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInvalidLfsPointer() throws Exception {
String cSource = "size_t someFunction(void *ptr); // Fake C source\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
cSource.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInvalidLfsPointer2() throws Exception {
String cSource = "size_t\nsomeFunction(void *ptr);\n// Fake C source\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
cSource.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInValidLfsPointerVersionWrong() throws Exception {
String ptr = "version https://git-lfs.example.org/spec/v1\n"
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "size 4\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInValidLfsPointerVersionTwice() throws Exception {
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "version https://git-lfs.github.com/spec/v1\n"
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "size 4\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInValidLfsPointerVersionTwice2() throws Exception {
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "version https://git-lfs.github.com/spec/v1\n"
+ "size 4\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInValidLfsPointerOidTwice() throws Exception {
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "oid sha256:" + TEST_SHA256 + '\n'
+ "size 4\n";
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testReadInValidLfsPointerSizeTwice() throws Exception {
String ptr = "version https://git-lfs.github.com/spec/v1\n"
+ "size 4\n"
+ "size 4\n"
+ "oid sha256:" + TEST_SHA256 + '\n';
try (ByteArrayInputStream in = new ByteArrayInputStream(
ptr.getBytes(UTF_8))) {
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in));
}
}

@Test
public void testRoundtrip() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer ptr = new LfsPointer(id, 4);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ptr.encode(baos);
try (ByteArrayInputStream in = new ByteArrayInputStream(
baos.toByteArray())) {
assertEquals(ptr, LfsPointer.parseLfsPointer(in));
}
}
}

@Test
public void testEquals() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 4);
assertTrue(lfs.equals(lfs2));
assertTrue(lfs2.equals(lfs));
}

@Test
public void testEqualsNull() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertFalse(lfs.equals(null));
}

@Test
public void testEqualsSame() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertTrue(lfs.equals(lfs));
}

@Test
public void testEqualsOther() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertFalse(lfs.equals(new Object()));
}

@Test
public void testNotEqualsOid() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId
.fromString(TEST_SHA256.replace('7', '5'));
LfsPointer lfs2 = new LfsPointer(id2, 4);
assertFalse(lfs.equals(lfs2));
assertFalse(lfs2.equals(lfs));
}

@Test
public void testNotEqualsSize() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 5);
assertFalse(lfs.equals(lfs2));
assertFalse(lfs2.equals(lfs));
}

@Test
public void testCompareToEquals() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 4);
assertEquals(0, lfs.compareTo(lfs2));
assertEquals(0, lfs2.compareTo(lfs));
}

@Test
@SuppressWarnings("SelfComparison")
public void testCompareToSame() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertEquals(0, lfs.compareTo(lfs));
}

@Test
public void testCompareToNotEqualsOid() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId
.fromString(TEST_SHA256.replace('7', '5'));
LfsPointer lfs2 = new LfsPointer(id2, 4);
assertNotEquals(0, lfs.compareTo(lfs2));
assertNotEquals(0, lfs2.compareTo(lfs));
}

@Test
public void testCompareToNotEqualsSize() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 5);
assertNotEquals(0, lfs.compareTo(lfs2));
assertNotEquals(0, lfs2.compareTo(lfs));
}

@Test
public void testCompareToNull() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertThrows(NullPointerException.class, () -> lfs.compareTo(null));
}

@Test
public void testHashcodeEquals() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 4);
assertEquals(lfs.hashCode(), lfs2.hashCode());
}

@Test
public void testHashcodeSame() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
assertEquals(lfs.hashCode(), lfs.hashCode());
}

@Test
public void testHashcodeNotEquals() throws Exception {
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs = new LfsPointer(id, 4);
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256);
LfsPointer lfs2 = new LfsPointer(id2, 5);
assertNotEquals(lfs.hashCode(), lfs2.hashCode());
}
}

+ 0
- 2
org.eclipse.jgit.lfs/META-INF/MANIFEST.MF View File

@@ -13,8 +13,6 @@ Export-Package: org.eclipse.jgit.lfs;version="6.0.0",
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
com.google.gson.stream;version="[2.8.2,3.0.0)",
org.apache.http.impl.client;version="[4.2.6,5.0.0)",
org.apache.http.impl.conn;version="[4.2.6,5.0.0)",
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)";resolution:=optional,
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)",
org.eclipse.jgit.attributes;version="[6.0.0,6.1.0)",

+ 60
- 12
org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016, Christian Halstrick <christian.halstrick@sap.com> and others
* Copyright (C) 2016, 2021 Christian Halstrick <christian.halstrick@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -19,6 +19,7 @@ import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Objects;

import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
@@ -56,9 +57,9 @@ public class LfsPointer implements Comparable<LfsPointer> {
public static final String HASH_FUNCTION_NAME = Constants.LONG_HASH_FUNCTION
.toLowerCase(Locale.ROOT).replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$

private AnyLongObjectId oid;
private final AnyLongObjectId oid;

private long size;
private final long size;

/**
* <p>Constructor for LfsPointer.</p>
@@ -129,19 +130,49 @@ public class LfsPointer implements Comparable<LfsPointer> {
LongObjectId id = null;
long sz = -1;

// This parsing is a bit too general if we go by the spec at
// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
// Comment lines are not mentioned in the spec, and the "version" line
// MUST be the first.
try (BufferedReader br = new BufferedReader(
new InputStreamReader(in, UTF_8))) {
for (String s = br.readLine(); s != null; s = br.readLine()) {
if (s.startsWith("#") || s.length() == 0) { //$NON-NLS-1$
continue;
} else if (s.startsWith("version") && s.length() > 8 //$NON-NLS-1$
&& (s.substring(8).trim().equals(VERSION) ||
s.substring(8).trim().equals(VERSION_LEGACY))) {
versionLine = true;
} else if (s.startsWith("oid sha256:")) { //$NON-NLS-1$
id = LongObjectId.fromString(s.substring(11).trim());
} else if (s.startsWith("size") && s.length() > 5) { //$NON-NLS-1$
sz = Long.parseLong(s.substring(5).trim());
} else if (s.startsWith("version")) { //$NON-NLS-1$
if (versionLine || s.length() < 8 || s.charAt(7) != ' ') {
return null; // Not a LFS pointer
}
String rest = s.substring(8).trim();
versionLine = VERSION.equals(rest)
|| VERSION_LEGACY.equals(rest);
if (!versionLine) {
return null; // Not a LFS pointer
}
} else {
try {
if (s.startsWith("oid sha256:")) { //$NON-NLS-1$
if (id != null) {
return null; // Not a LFS pointer
}
id = LongObjectId
.fromString(s.substring(11).trim());
} else if (s.startsWith("size")) { //$NON-NLS-1$
if (sz > 0 || s.length() < 5
|| s.charAt(4) != ' ') {
return null; // Not a LFS pointer
}
sz = Long.parseLong(s.substring(5).trim());
}
} catch (RuntimeException e) {
// We could not parse the line. If we have a version
// already, this is a corrupt LFS pointer. Otherwise it
// is just not an LFS pointer.
if (versionLine) {
throw e;
}
return null;
}
}
}
if (versionLine && id != null && sz > -1) {
@@ -170,5 +201,22 @@ public class LfsPointer implements Comparable<LfsPointer> {

return Long.compare(getSize(), o.getSize());
}
}

@Override
public int hashCode() {
return Objects.hash(getOid()) * 31 + Long.hashCode(getSize());
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LfsPointer other = (LfsPointer) obj;
return Objects.equals(getOid(), other.getOid())
&& getSize() == other.getSize();
}
}

+ 0
- 0
org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save