Browse Source

GPG: compute the keygrip to find a secret key

The gpg-agent stores secret keys in individual files in the secret
key directory private-keys-v1.d. The files have the key's keygrip
(in upper case) as name and extension ".key".

A keygrip is a SHA1 hash over the parameters of the public key. By
computing this keygrip, we can pre-compute the expected file name and
then check only that one file instead of having to iterate over all
keys stored in that directory.

This file naming scheme is actually an implementation detail of
gpg-agent. It is unlikely to change, though. The keygrip itself is
computed via libgcrypt and will remain stable according to the GPG
main author.[1]

Add an implementation for calculating the keygrip and include tests.
Do not iterate over files in BouncyCastleGpgKeyLocator but only check
the single file identified by the keygrip.

Ideally upstream BouncyCastle would provide such a getKeyGrip() method.
But as it re-builds GPG and libgcrypt internals, it's doubtful it would
be included there, and since BouncyCastle even lacks a number of curve
OIDs for ed25519/curve25519 and uses the short-Weierstrass parameters
instead of the more common Montgomery parameters, including it there
might be quite a bit of work.

[1] http://gnupg.10057.n7.nabble.com/GnuPG-2-1-x-and-2-2-x-keyring-formats-tp54146p54154.html

Bug: 547536
Change-Id: I30022a0e7b33b1bf35aec1222f84591f0c30ddfd
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
tags/v5.11.0.202102240950-m3
Thomas Wolf 3 years ago
parent
commit
64cbea8a97
25 changed files with 869 additions and 67 deletions
  1. 2
    0
      lib/BUILD
  2. 7
    2
      org.eclipse.jgit.gpg.bc.test/.classpath
  3. 1
    0
      org.eclipse.jgit.gpg.bc.test/.gitignore
  4. 20
    0
      org.eclipse.jgit.gpg.bc.test/BUILD
  5. 10
    2
      org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
  6. 1
    1
      org.eclipse.jgit.gpg.bc.test/build.properties
  7. 6
    0
      org.eclipse.jgit.gpg.bc.test/pom.xml
  8. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc
  9. 17
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc
  10. 19
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc
  11. 44
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc
  12. 9
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc
  13. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc
  14. 16
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc
  15. 19
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc
  16. 40
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc
  17. 14
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc
  18. 13
    0
      org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc
  19. 61
    0
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java
  20. 143
    0
      org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java
  21. 10
    3
      org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
  22. 7
    1
      org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
  23. 6
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
  24. 54
    58
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java
  25. 322
    0
      org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java

+ 2
- 0
lib/BUILD View File

@@ -162,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"],
)
@@ -172,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"],
)

+ 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

+ 20
- 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,22 @@ junit_tests(
srcs = glob(["tst/**/*.java"]),
tags = ["bc"],
deps = [
"//lib:bcpg",
"//lib:bcprov",
"//lib:junit",
"//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 .",
)

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

@@ -7,8 +7,16 @@ Bundle-Version: 5.11.0.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.eclipse.jgit.gpg.bc.internal;version="[5.11.0,5.12.0)",
org.junit;version="[4.13,5.0.0)"
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.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="[5.11.0,5.12.0)",
org.eclipse.jgit.gpg.bc.internal.keys;version="[5.11.0,5.12.0)",
org.eclipse.jgit.util.sha1;version="[5.11.0,5.12.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
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>

+ 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-----

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
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-----

+ 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-----

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
=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-----

+ 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]);
}
}
}

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

@@ -8,12 +8,19 @@ Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-Version: 5.11.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.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)",
@@ -27,5 +34,5 @@ Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)",
org.eclipse.jgit.transport;version="[5.11.0,5.12.0)",
org.eclipse.jgit.util;version="[5.11.0,5.12.0)",
org.slf4j;version="[1.7.0,2.0.0)"
Export-Package: org.eclipse.jgit.gpg.bc.internal;version="5.11.0";
x-friends:="org.eclipse.jgit.gpg.bc.test"
Export-Package: org.eclipse.jgit.gpg.bc.internal;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test",
org.eclipse.jgit.gpg.bc.internal.keys;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test"

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

@@ -1,6 +1,8 @@
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?
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}
@@ -16,3 +18,7 @@ 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}

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

@@ -27,9 +27,11 @@ public final class BCText extends TranslationBundle {
}

// @formatter:off
/***/ public String corrupt25519Key;
/***/ public String credentialPassphrase;
/***/ public String gpgFailedToParseSecretKey;
/***/ public String gpgNoCredentialsProvider;
/***/ public String gpgNoKeygrip;
/***/ public String gpgNoKeyring;
/***/ public String gpgNoKeyInLegacySecring;
/***/ public String gpgNoPublicKeyFound;
@@ -45,5 +47,9 @@ public final class BCText extends TranslationBundle {
/***/ public String signatureParseError;
/***/ public String signatureVerificationError;
/***/ public String unableToSignCommitNoSecretKey;
/***/ public String uncompressed25519Key;
/***/ public String unknownCurve;
/***/ public String unknownCurveParameters;
/***/ public String unknownKeyType;

}

+ 54
- 58
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java View File

@@ -27,12 +27,8 @@ 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;
@@ -61,6 +57,7 @@ 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.util.FS;
import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
@@ -158,15 +155,10 @@ public class BouncyCastleGpgKeyLocator {
private PGPSecretKey attemptParseSecretKey(Path keyFile,
PGPDigestCalculatorProvider calculatorProvider,
PBEProtectionRemoverFactory passphraseProvider,
PGPPublicKey publicKey) {
PGPPublicKey publicKey) throws IOException, PGPException {
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;
}
}

@@ -472,67 +464,71 @@ 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();
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;
}
// allPaths contains only the encrypted keys now.
PGPSecretKey secretKey = null;
try {
// Try without passphrase
secretKey = attemptParseSecretKey(keyFile, calculatorProvider,
passphraseProvider, publicKey);
} catch (EncryptedPgpKeyException e) {
// Let's try again with a passphrase
passphraseProvider = new JcePBEProtectionRemoverFactory(
passphrasePrompt.getPassphrase(
publicKey.getFingerprint(), userKeyboxPath));
}
clearPrompt = true;
try {
secretKey = attemptParseSecretKey(keyFile, calculatorProvider,
passphraseProvider, publicKey);
} catch (PGPException e1) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgFailedToParseSecretKey,
keyFile.toAbsolutePath()), e);

passphrasePrompt.clear();
}
}
if (secretKey != null) {
if (!secretKey.isSigningKey()) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgNotASigningKey, signingKey));
}
clearPrompt = false;
return new BouncyCastleGpgKey(secretKey, userKeyboxPath);
}
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();
}
}
}


+ 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;
}

}

Loading…
Cancel
Save