]> source.dussan.org Git - jgit.git/commitdiff
SSH signing: implement a SignatureVerifier 27/1202327/3
authorThomas Wolf <twolf@apache.org>
Sat, 28 Sep 2024 14:11:45 +0000 (16:11 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Wed, 23 Oct 2024 17:16:41 +0000 (19:16 +0200)
Signature verification needs quite a bit of infrastructure. There are
two files to read: a list of allowed signers, and a list of revoked keys
or certificates. Introduce a SigningKeyDatabase abstraction for these,
and give client code the possibility to plug in its own implementation.

Loading these files afresh for every signature to be checked would be
prohibitively expensive. Introduce a cache of SigningKeyDatabases, and
have them reload the files only when they have changed.

Include a default implementation that works with the OpenSSH allowed
signers file and with OpenSSH revocation lists. Binary KRLs are parsed
according to [1]; the test data was generated using the OpenSSH test
script[2].

[1] https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.krl
[2] https://github.com/openssh/openssh-portable/blob/67a115e/regress/krl.sh

Bug: jgit-44
Change-Id: I6a2fa24f38a2f2fe63ffb353da5b6665ca7277e1
Signed-off-by: Thomas Wolf <twolf@apache.org>
115 files changed:
org.eclipse.jgit.ssh.apache.test/.gitattributes
org.eclipse.jgit.ssh.apache.test/BUILD
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011 [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AbstractSshSignatureTest.java
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java [new file with mode: 0644]
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java [new file with mode: 0644]

index c1f1b7371a30d87f8348eef66939044a4692e381..b5b937561d3e4b5ac32c96a11a839683a9c14b2a 100644 (file)
@@ -1 +1,2 @@
-/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle binary
\ No newline at end of file
+/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle binary
+/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl* binary
index bb31f8b3c7e4e3f25384bae3afcf94415ea500f6..dfc059f0a34eb4b4edcb84ecc76ae645089eae3f 100644 (file)
@@ -10,6 +10,7 @@ load(
 DEPS = [
     "//lib:eddsa",
     "//lib:junit",
+    "//lib:slf4j-api",
     "//lib:sshd-osgi",
     "//lib:sshd-sftp",
     "//org.eclipse.jgit:jgit",
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers
new file mode 100644 (file)
index 0000000..ec74409
--- /dev/null
@@ -0,0 +1,2 @@
+tester@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO
+*@example.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBV
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl
new file mode 100644 (file)
index 0000000..9469340
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all
new file mode 100644 (file)
index 0000000..6f744c3
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca
new file mode 100644 (file)
index 0000000..84a8bc6
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert
new file mode 100644 (file)
index 0000000..26f29b2
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty
new file mode 100644 (file)
index 0000000..78e5187
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash
new file mode 100644 (file)
index 0000000..cdd1351
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid
new file mode 100644 (file)
index 0000000..1a65243
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild
new file mode 100644 (file)
index 0000000..9ba549f
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys
new file mode 100644 (file)
index 0000000..8dd496d
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial
new file mode 100644 (file)
index 0000000..9965e2e
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild
new file mode 100644 (file)
index 0000000..aefd2b1
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1
new file mode 100644 (file)
index 0000000..3928543
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1 differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256
new file mode 100644 (file)
index 0000000..cdd1351
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256 differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text
new file mode 100644 (file)
index 0000000..77ddd5e
--- /dev/null
@@ -0,0 +1,11 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA 
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001
new file mode 100644 (file)
index 0000000..893fd5e
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAtvUGe9h7cgnWjaOqdhh93yaVMp8JL7PAHMJ+uhu2E4QAAAIhUa4KiVGuC
+ogAAAAtzc2gtZWQyNTUxOQAAACAtvUGe9h7cgnWjaOqdhh93yaVMp8JL7PAHMJ+uhu2E4Q
+AAAECKgy+3FBgpdfxjOtNy9TamhadMWSyPlPiwu06mYVReyS29QZ72HtyCdaNo6p2GH3fJ
+pUynwkvs8Acwn66G7YThAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub
new file mode 100644 (file)
index 0000000..e2bcd25
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE1UUsQ+sncsuST6eGe3B5Se7purqhGcWrkyIwUnQM/jAAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YThAAAAAAAAAAEAAAABAAAACXJldm9rZWQgMQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQCjDATJVQs3odl9fsqaxyx/18qrodZEDyYZAsdqg0GMx8CvLYt4xHENyVm7kyBRxOeh3EKfII0WFoYCV4mGZ/wU= ./tst-keys/revoked-0001.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub
new file mode 100644 (file)
index 0000000..f561982
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004
new file mode 100644 (file)
index 0000000..e50a4fe
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACC9GQuH6oGwYETnl9bDhD6+LjFTXz3Setm1aP870jEUMQAAAIiQzZzikM2c
+4gAAAAtzc2gtZWQyNTUxOQAAACC9GQuH6oGwYETnl9bDhD6+LjFTXz3Setm1aP870jEUMQ
+AAAEBpn5dxbvHhqAsSVN3IqRwzbFFgOhdmpkOP+nvoKq+rSr0ZC4fqgbBgROeX1sOEPr4u
+MVNfPdJ62bVo/zvSMRQxAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub
new file mode 100644 (file)
index 0000000..8e92fa7
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIC5jMLPDlEVbyPU/Icb04BF5jxN+OT8kpuO5c0CV6/AYAAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQxAAAAAAAAAAQAAAABAAAACXJldm9rZWQgNAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQOH4yNn7+zyvsCV8BCoop5xYv4uFk27VZRjmscuy3J66KNwLay9XkvkRNArDaWBwH47dmkcU7F6fLLpY4vN2jgM= ./tst-keys/revoked-0004.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub
new file mode 100644 (file)
index 0000000..1d7fe7f
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010
new file mode 100644 (file)
index 0000000..fb457df
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBuSB/U+xWOO1SQ1KMjpQf4qgjeKTvHYDq8XJZijeUecAAAAIgvtSiML7Uo
+jAAAAAtzc2gtZWQyNTUxOQAAACBuSB/U+xWOO1SQ1KMjpQf4qgjeKTvHYDq8XJZijeUecA
+AAAECI2si7/SGjMM1UyhrFPXx4laQIfFUsb1+yfXKwQyeOXW5IH9T7FY47VJDUoyOlB/iq
+CN4pO8dgOrxclmKN5R5wAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub
new file mode 100644 (file)
index 0000000..9492f88
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIN2arXaBzVIdxAFfU+XU1Uc788HKlDH3tOLdDtcoORLmAAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5wAAAAAAAAAAoAAAABAAAACnJldm9rZWQgMTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDwhgQsYOG/eKf8EfH+fAmEW+88/ZJCmxAExEFPxkGL59waZcGiOJqquTKiqN5Kod8hpUrvZywrA0tjrRkYw8wH ./tst-keys/revoked-0010.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub
new file mode 100644 (file)
index 0000000..37a0d84
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050
new file mode 100644 (file)
index 0000000..b02e9df
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDY4RuPhJ0MvGBy7HxPPiEMDVmVZ9RgWF++acMQQmRpsgAAAIgCZLe5AmS3
+uQAAAAtzc2gtZWQyNTUxOQAAACDY4RuPhJ0MvGBy7HxPPiEMDVmVZ9RgWF++acMQQmRpsg
+AAAEB9Q6rpWK04mQDoeKSB2I7p/rb8pu00ClhR+vRATl4TYdjhG4+EnQy8YHLsfE8+IQwN
+WZVn1GBYX75pwxBCZGmyAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub
new file mode 100644 (file)
index 0000000..90bb86f
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIecNj2Es6VfyCrhol4swP9lutvphd3seh+/b2LpD0EsAAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmyAAAAAAAAADIAAAABAAAACnJldm9rZWQgNTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA2q8tCXV8FXkB0QWnFNWfCL7zz5jCXL9ZQADM1DaGi8oUU/dxmlQtWgMxuu5vNuvOYQGPDcBLj+by8VqAdvZMP ./tst-keys/revoked-0050.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub
new file mode 100644 (file)
index 0000000..f3ad249
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090
new file mode 100644 (file)
index 0000000..efa3d5e
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCV5vqoE7PrDvvc76GQcZEA9/Udq0KkAlXs8UKBoyrouQAAAIg3mgznN5oM
+5wAAAAtzc2gtZWQyNTUxOQAAACCV5vqoE7PrDvvc76GQcZEA9/Udq0KkAlXs8UKBoyrouQ
+AAAEAkRynGUH9n5hcp/S1WALvuIEDtbkMi2A7yNWze0o4gWpXm+qgTs+sO+9zvoZBxkQD3
+9R2rQqQCVezxQoGjKui5AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub
new file mode 100644 (file)
index 0000000..26e61e0
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIOjIztpPiaKY0hztHWtWpX+4LEoyy8qYPPT277K3bykSAAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5AAAAAAAAAFoAAAABAAAACnJldm9rZWQgOTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBUaAWyv/jZrbrCO5zw2HuZcWYBig8R2jdvkKr5yzWMWEVRtn97gnAUsIGxkgUnUAs3B2En2FH2NaicC1F1n3sF ./tst-keys/revoked-0090.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub
new file mode 100644 (file)
index 0000000..e51b88c
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500
new file mode 100644 (file)
index 0000000..900d444
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACC/PmkCo2vkm6sX1ketRQnLGwcNo2hfyh73KnT9hW6ekAAAAIhDam0PQ2pt
+DwAAAAtzc2gtZWQyNTUxOQAAACC/PmkCo2vkm6sX1ketRQnLGwcNo2hfyh73KnT9hW6ekA
+AAAED606GrYWlY7TOXcr8vAr3fjMtCtetdpwFHi2pzgf2Bbb8+aQKja+SbqxfWR61FCcsb
+Bw2jaF/KHvcqdP2Fbp6QAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub
new file mode 100644 (file)
index 0000000..0709618
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIEblAg4b1eJ5KnT7KvYoOfe24La+nAKKLIYdsR6CdreAAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6QAAAAAAAAAfQAAAABAAAAC3Jldm9rZWQgNTAwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAc0WEuRfi9LG9uTfKY4Dh5MJCHUG7Dqp1J4S4Gs1iOzFX2YKgYXc0O+9j3jJ5/fB4z960Y1AxYR4TWEo1pNjzBQ== ./tst-keys/revoked-0500.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub
new file mode 100644 (file)
index 0000000..13d1aa4
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510
new file mode 100644 (file)
index 0000000..a58675e
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACADi+yFC2pU9iM6PU4EX9bdhzaWeVgmSR+aEQnfSytwTQAAAIgigF2AIoBd
+gAAAAAtzc2gtZWQyNTUxOQAAACADi+yFC2pU9iM6PU4EX9bdhzaWeVgmSR+aEQnfSytwTQ
+AAAEBWpyFpK0a+cdNPFMsvHTHtjBJpX4aMHxBAcEPN8hnpWAOL7IULalT2Izo9TgRf1t2H
+NpZ5WCZJH5oRCd9LK3BNAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub
new file mode 100644 (file)
index 0000000..1431af3
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAII8u8ho0YtDyXWYKv4WeOXSaRUxU8sUV0dQujB2J9VLaAAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BNAAAAAAAAAf4AAAABAAAAC3Jldm9rZWQgNTEwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABA3aijJnt8mJ8vLtr7H2PBVJHtNJpL6MQZNXHC6svzygIqZwEq3tDHGR00TPHaCYAqDEXQZysONciOQtQHzKXuBw== ./tst-keys/revoked-0510.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub
new file mode 100644 (file)
index 0000000..33ad644
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520
new file mode 100644 (file)
index 0000000..630316c
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBlWknnYT5Jdh68dZzjik9hOdgA7AXm2sMLPOzTdfj+ngAAAIghEm1OIRJt
+TgAAAAtzc2gtZWQyNTUxOQAAACBlWknnYT5Jdh68dZzjik9hOdgA7AXm2sMLPOzTdfj+ng
+AAAEDfVYURudvfzK3ZFx6T2O1CWi0emOZ0MYPcDzUVlu1WmGVaSedhPkl2Hrx1nOOKT2E5
+2ADsBebawws87NN1+P6eAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub
new file mode 100644 (file)
index 0000000..b290943
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAID/r9T2Sv0NGmlcHl6Fw8rVPIupmsqwq3WAG1NvW7WRcAAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6eAAAAAAAAAggAAAABAAAAC3Jldm9rZWQgNTIwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAF8zkeAqwtlxF4iy4mDEHkzVaRqcS0sZ57gcZBWGn/peGFy3MpSxlFQM/IC2pNZ7GuCVSIPV6rRLJC65YMMOEDQ== ./tst-keys/revoked-0520.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub
new file mode 100644 (file)
index 0000000..fc13d37
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550
new file mode 100644 (file)
index 0000000..5e671b4
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAscmsY+6UVQ3of4MSXpvQS4aFEChpylx3w6wfxrQtSgQAAAIj/9GKZ//Ri
+mQAAAAtzc2gtZWQyNTUxOQAAACAscmsY+6UVQ3of4MSXpvQS4aFEChpylx3w6wfxrQtSgQ
+AAAEDKC3eEgvCMy86rktq7VU1YQjjKY1iDFPVxWgKKcGJKkyxyaxj7pRVDeh/gxJem9BLh
+oUQKGnKXHfDrB/GtC1KBAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub
new file mode 100644 (file)
index 0000000..f529a91
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIF9q+Cg+9DSKt09eW1NXqVC4dZ3v80sZIYtc0/yqHRb+AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KBAAAAAAAAAiYAAAABAAAAC3Jldm9rZWQgNTUwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAovTuFOXLNCc4hQcI2hatXe2hbBQYbcnUo2BNdJ9EvIOsH/T0DzzEfRQajMQ+QD6oujIx7fb1Z2sRVPOAb3AcBg== ./tst-keys/revoked-0550.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub
new file mode 100644 (file)
index 0000000..e09316a
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799
new file mode 100644 (file)
index 0000000..8edd736
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA/ehirY+MDfjsL60LID9+30FVF04QjC+/eqqhTS1QwgAAAAIjdntzR3Z7c
+0QAAAAtzc2gtZWQyNTUxOQAAACA/ehirY+MDfjsL60LID9+30FVF04QjC+/eqqhTS1QwgA
+AAAEDQEb+IFCIz+yvkhmrOQ85GafOm9ra0oNRontpox62UTj96GKtj4wN+OwvrQsgP37fQ
+VUXThCML796qqFNLVDCAAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub
new file mode 100644 (file)
index 0000000..80312fb
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIC1z1LkrZhMz1mBWPU8sJIuH59v+ig4OK/B4/x8jLAtUAAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCAAAAAAAAAAx8AAAABAAAAC3Jldm9rZWQgNzk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABASNkJSbdRDARfgbqPOnuES0o6m6VZ7RC2XLPm3uwTqCvMqtHbFvq9etMddSUIR4XXah6ef+O7CJDk/Yjpkn+2CA== ./tst-keys/revoked-0799.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub
new file mode 100644 (file)
index 0000000..1f0556c
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999
new file mode 100644 (file)
index 0000000..f05a1e4
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBIdwRXE815oNRYuS71olA1jkbB81YVApvHpKxCRuvugAAAAIgzBpObMwaT
+mwAAAAtzc2gtZWQyNTUxOQAAACBIdwRXE815oNRYuS71olA1jkbB81YVApvHpKxCRuvugA
+AAAECxY5wx3XKIhMT+ajMZXPl51x8rkCPBq6gUgZV3Uqpu7Eh3BFcTzXmg1Fi5LvWiUDWO
+RsHzVhUCm8ekrEJG6+6AAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub
new file mode 100644 (file)
index 0000000..4aedb77
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGt3nV/XJmtz9sQGP2fiZiKOH7mkPhezN3S+8TnsVcQjAAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6AAAAAAAAAA+cAAAABAAAAC3Jldm9rZWQgOTk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAvLVCRCs7CV0JSXYL8ge4iRxL4y48bYuvu3YimKZDg7NdCXqw/jkaCsxJykRzb/xVnQDoNVCQQuzydt/I13FdBA== ./tst-keys/revoked-0999.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub
new file mode 100644 (file)
index 0000000..c837fe0
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca
new file mode 100644 (file)
index 0000000..47e01fb
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAIgok4I2KJOC
+NgAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzg
+AAAEAEN+knz2qOyj+jbY+SJSHYQhlJoB1u9jLqoQoiAerI3hbReZevLKczhayKUADRdAvZ
+5DXVzAJpQkcB4MPdQu/OAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub
new file mode 100644 (file)
index 0000000..2b92f89
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/O 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2
new file mode 100644 (file)
index 0000000..770ceee
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDsEBCX5jBwggkpt4XZXct1fOhDBuvgLL0KMpGoHRtj9wAAAIjMEwOtzBMD
+rQAAAAtzc2gtZWQyNTUxOQAAACDsEBCX5jBwggkpt4XZXct1fOhDBuvgLL0KMpGoHRtj9w
+AAAEAurE2/d7VhoEJeNFdDnVS7lpBRoMe/zAjA8dJRP1Z/I+wQEJfmMHCCCSm3hdldy3V8
+6EMG6+AsvQoykagdG2P3AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub
new file mode 100644 (file)
index 0000000..a177fd0
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOwQEJfmMHCCCSm3hdldy3V86EMG6+AsvQoykagdG2P3 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash
new file mode 100644 (file)
index 0000000..c6f2361
--- /dev/null
@@ -0,0 +1,11 @@
+hash: SHA256:RvNFBEc/N9jsm3toDkitgr/wnWu/6qWBHo4Xmh5ZUpM
+hash: SHA256:qu2IwCnItWWX+orXv0rjCeT4i++2O6ViTzLye6kyWzU
+hash: SHA256:qQTACAkAJxYk1zvSQ+Rx9wa2IuOFJKtaEy/XwxM89J0
+hash: SHA256:Fe4GdmipzulS9oMB/h3U69tSm5wil6bTUKSJCT+Jf3E
+hash: SHA256:esUK/whZ5oJeRFNeOrHK1bbx9dKC+nRITZ7up7HJaGA
+hash: SHA256:xkii+r6t9rEBFYkx1b3dGNXzEs69M5NUMfHP05ypSdI
+hash: SHA256:lZrSycKcBNvUafU9y4R0EEbDaQWqMFvIGM9M+VKt2zk
+hash: SHA256:/2bgZOiYEH2UVahUllNaQ5P0advEB7liCPkp+aNVKDk
+hash: SHA256:He3c0W5o/P1I0pK5/VusqD5V6duAMeZl6f+6Yy5P1z0
+hash: SHA256:5V5Xw2lgcAGR8dO9cbgRmCNlhcCsBBv/hmEstKsqKr4
+hash: SHA256:T7s26JPzzRP2WHOcw3OjLwWo8ZZTkfo2jBCrRfJ6BR4
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid
new file mode 100644 (file)
index 0000000..592ddb4
--- /dev/null
@@ -0,0 +1,512 @@
+id: revoked 1
+id: revoked 2
+id: revoked 3
+id: revoked 4
+id: revoked 10
+id: revoked 15
+id: revoked 30
+id: revoked 50
+id: revoked 90
+id: revoked 300
+id: revoked 301
+id: revoked 302
+id: revoked 303
+id: revoked 304
+id: revoked 305
+id: revoked 306
+id: revoked 307
+id: revoked 308
+id: revoked 309
+id: revoked 310
+id: revoked 311
+id: revoked 312
+id: revoked 313
+id: revoked 314
+id: revoked 315
+id: revoked 316
+id: revoked 317
+id: revoked 318
+id: revoked 319
+id: revoked 320
+id: revoked 321
+id: revoked 322
+id: revoked 323
+id: revoked 324
+id: revoked 325
+id: revoked 326
+id: revoked 327
+id: revoked 328
+id: revoked 329
+id: revoked 330
+id: revoked 331
+id: revoked 332
+id: revoked 333
+id: revoked 334
+id: revoked 335
+id: revoked 336
+id: revoked 337
+id: revoked 338
+id: revoked 339
+id: revoked 340
+id: revoked 341
+id: revoked 342
+id: revoked 343
+id: revoked 344
+id: revoked 345
+id: revoked 346
+id: revoked 347
+id: revoked 348
+id: revoked 349
+id: revoked 350
+id: revoked 351
+id: revoked 352
+id: revoked 353
+id: revoked 354
+id: revoked 355
+id: revoked 356
+id: revoked 357
+id: revoked 358
+id: revoked 359
+id: revoked 360
+id: revoked 361
+id: revoked 362
+id: revoked 363
+id: revoked 364
+id: revoked 365
+id: revoked 366
+id: revoked 367
+id: revoked 368
+id: revoked 369
+id: revoked 370
+id: revoked 371
+id: revoked 372
+id: revoked 373
+id: revoked 374
+id: revoked 375
+id: revoked 376
+id: revoked 377
+id: revoked 378
+id: revoked 379
+id: revoked 380
+id: revoked 381
+id: revoked 382
+id: revoked 383
+id: revoked 384
+id: revoked 385
+id: revoked 386
+id: revoked 387
+id: revoked 388
+id: revoked 389
+id: revoked 390
+id: revoked 391
+id: revoked 392
+id: revoked 393
+id: revoked 394
+id: revoked 395
+id: revoked 396
+id: revoked 397
+id: revoked 398
+id: revoked 399
+id: revoked 400
+id: revoked 401
+id: revoked 402
+id: revoked 403
+id: revoked 404
+id: revoked 405
+id: revoked 406
+id: revoked 407
+id: revoked 408
+id: revoked 409
+id: revoked 410
+id: revoked 411
+id: revoked 412
+id: revoked 413
+id: revoked 414
+id: revoked 415
+id: revoked 416
+id: revoked 417
+id: revoked 418
+id: revoked 419
+id: revoked 420
+id: revoked 421
+id: revoked 422
+id: revoked 423
+id: revoked 424
+id: revoked 425
+id: revoked 426
+id: revoked 427
+id: revoked 428
+id: revoked 429
+id: revoked 430
+id: revoked 431
+id: revoked 432
+id: revoked 433
+id: revoked 434
+id: revoked 435
+id: revoked 436
+id: revoked 437
+id: revoked 438
+id: revoked 439
+id: revoked 440
+id: revoked 441
+id: revoked 442
+id: revoked 443
+id: revoked 444
+id: revoked 445
+id: revoked 446
+id: revoked 447
+id: revoked 448
+id: revoked 449
+id: revoked 450
+id: revoked 451
+id: revoked 452
+id: revoked 453
+id: revoked 454
+id: revoked 455
+id: revoked 456
+id: revoked 457
+id: revoked 458
+id: revoked 459
+id: revoked 460
+id: revoked 461
+id: revoked 462
+id: revoked 463
+id: revoked 464
+id: revoked 465
+id: revoked 466
+id: revoked 467
+id: revoked 468
+id: revoked 469
+id: revoked 470
+id: revoked 471
+id: revoked 472
+id: revoked 473
+id: revoked 474
+id: revoked 475
+id: revoked 476
+id: revoked 477
+id: revoked 478
+id: revoked 479
+id: revoked 480
+id: revoked 481
+id: revoked 482
+id: revoked 483
+id: revoked 484
+id: revoked 485
+id: revoked 486
+id: revoked 487
+id: revoked 488
+id: revoked 489
+id: revoked 490
+id: revoked 491
+id: revoked 492
+id: revoked 493
+id: revoked 494
+id: revoked 495
+id: revoked 496
+id: revoked 497
+id: revoked 498
+id: revoked 500
+id: revoked 501
+id: revoked 502
+id: revoked 503
+id: revoked 504
+id: revoked 505
+id: revoked 506
+id: revoked 507
+id: revoked 508
+id: revoked 509
+id: revoked 510
+id: revoked 511
+id: revoked 512
+id: revoked 513
+id: revoked 514
+id: revoked 515
+id: revoked 516
+id: revoked 517
+id: revoked 518
+id: revoked 519
+id: revoked 520
+id: revoked 521
+id: revoked 522
+id: revoked 523
+id: revoked 524
+id: revoked 525
+id: revoked 526
+id: revoked 527
+id: revoked 528
+id: revoked 529
+id: revoked 530
+id: revoked 531
+id: revoked 532
+id: revoked 533
+id: revoked 534
+id: revoked 535
+id: revoked 536
+id: revoked 537
+id: revoked 538
+id: revoked 539
+id: revoked 540
+id: revoked 541
+id: revoked 542
+id: revoked 543
+id: revoked 544
+id: revoked 545
+id: revoked 546
+id: revoked 547
+id: revoked 548
+id: revoked 549
+id: revoked 550
+id: revoked 551
+id: revoked 552
+id: revoked 553
+id: revoked 554
+id: revoked 555
+id: revoked 556
+id: revoked 557
+id: revoked 558
+id: revoked 559
+id: revoked 560
+id: revoked 561
+id: revoked 562
+id: revoked 563
+id: revoked 564
+id: revoked 565
+id: revoked 566
+id: revoked 567
+id: revoked 568
+id: revoked 569
+id: revoked 570
+id: revoked 571
+id: revoked 572
+id: revoked 573
+id: revoked 574
+id: revoked 575
+id: revoked 576
+id: revoked 577
+id: revoked 578
+id: revoked 579
+id: revoked 580
+id: revoked 581
+id: revoked 582
+id: revoked 583
+id: revoked 584
+id: revoked 585
+id: revoked 586
+id: revoked 587
+id: revoked 588
+id: revoked 589
+id: revoked 590
+id: revoked 591
+id: revoked 592
+id: revoked 593
+id: revoked 594
+id: revoked 595
+id: revoked 596
+id: revoked 597
+id: revoked 598
+id: revoked 599
+id: revoked 600
+id: revoked 601
+id: revoked 602
+id: revoked 603
+id: revoked 604
+id: revoked 605
+id: revoked 606
+id: revoked 607
+id: revoked 608
+id: revoked 609
+id: revoked 610
+id: revoked 611
+id: revoked 612
+id: revoked 613
+id: revoked 614
+id: revoked 615
+id: revoked 616
+id: revoked 617
+id: revoked 618
+id: revoked 619
+id: revoked 620
+id: revoked 621
+id: revoked 622
+id: revoked 623
+id: revoked 624
+id: revoked 625
+id: revoked 626
+id: revoked 627
+id: revoked 628
+id: revoked 629
+id: revoked 630
+id: revoked 631
+id: revoked 632
+id: revoked 633
+id: revoked 634
+id: revoked 635
+id: revoked 636
+id: revoked 637
+id: revoked 638
+id: revoked 639
+id: revoked 640
+id: revoked 641
+id: revoked 642
+id: revoked 643
+id: revoked 644
+id: revoked 645
+id: revoked 646
+id: revoked 647
+id: revoked 648
+id: revoked 649
+id: revoked 650
+id: revoked 651
+id: revoked 652
+id: revoked 653
+id: revoked 654
+id: revoked 655
+id: revoked 656
+id: revoked 657
+id: revoked 658
+id: revoked 659
+id: revoked 660
+id: revoked 661
+id: revoked 662
+id: revoked 663
+id: revoked 664
+id: revoked 665
+id: revoked 666
+id: revoked 667
+id: revoked 668
+id: revoked 669
+id: revoked 670
+id: revoked 671
+id: revoked 672
+id: revoked 673
+id: revoked 674
+id: revoked 675
+id: revoked 676
+id: revoked 677
+id: revoked 678
+id: revoked 679
+id: revoked 680
+id: revoked 681
+id: revoked 682
+id: revoked 683
+id: revoked 684
+id: revoked 685
+id: revoked 686
+id: revoked 687
+id: revoked 688
+id: revoked 689
+id: revoked 690
+id: revoked 691
+id: revoked 692
+id: revoked 693
+id: revoked 694
+id: revoked 695
+id: revoked 696
+id: revoked 697
+id: revoked 698
+id: revoked 699
+id: revoked 700
+id: revoked 701
+id: revoked 702
+id: revoked 703
+id: revoked 704
+id: revoked 705
+id: revoked 706
+id: revoked 707
+id: revoked 708
+id: revoked 709
+id: revoked 710
+id: revoked 711
+id: revoked 712
+id: revoked 713
+id: revoked 714
+id: revoked 715
+id: revoked 716
+id: revoked 717
+id: revoked 718
+id: revoked 719
+id: revoked 720
+id: revoked 721
+id: revoked 722
+id: revoked 723
+id: revoked 724
+id: revoked 725
+id: revoked 726
+id: revoked 727
+id: revoked 728
+id: revoked 729
+id: revoked 730
+id: revoked 731
+id: revoked 732
+id: revoked 733
+id: revoked 734
+id: revoked 735
+id: revoked 736
+id: revoked 737
+id: revoked 738
+id: revoked 739
+id: revoked 740
+id: revoked 741
+id: revoked 742
+id: revoked 743
+id: revoked 744
+id: revoked 745
+id: revoked 746
+id: revoked 747
+id: revoked 748
+id: revoked 749
+id: revoked 750
+id: revoked 751
+id: revoked 752
+id: revoked 753
+id: revoked 754
+id: revoked 755
+id: revoked 756
+id: revoked 757
+id: revoked 758
+id: revoked 759
+id: revoked 760
+id: revoked 761
+id: revoked 762
+id: revoked 763
+id: revoked 764
+id: revoked 765
+id: revoked 766
+id: revoked 767
+id: revoked 768
+id: revoked 769
+id: revoked 770
+id: revoked 771
+id: revoked 772
+id: revoked 773
+id: revoked 774
+id: revoked 775
+id: revoked 776
+id: revoked 777
+id: revoked 778
+id: revoked 779
+id: revoked 780
+id: revoked 781
+id: revoked 782
+id: revoked 783
+id: revoked 784
+id: revoked 785
+id: revoked 786
+id: revoked 787
+id: revoked 788
+id: revoked 789
+id: revoked 790
+id: revoked 791
+id: revoked 792
+id: revoked 793
+id: revoked 794
+id: revoked 795
+id: revoked 796
+id: revoked 797
+id: revoked 798
+id: revoked 799
+id: revoked 999
+id: revoked 1000
+id: revoked 1001
+id: revoked 1002
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials
new file mode 100644 (file)
index 0000000..b20fec2
--- /dev/null
@@ -0,0 +1,19 @@
+serial: 1-4
+serial: 10
+serial: 15
+serial: 30
+serial: 50
+serial: 90
+serial: 999
+# The following sum to 500-799
+serial: 500
+serial: 501
+serial: 502
+serial: 503-600
+serial: 700-797
+serial: 798
+serial: 799
+serial: 599-701
+# Some multiple consecutive serial number ranges
+serial: 10000-20000
+serial: 30000-40000
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1
new file mode 100644 (file)
index 0000000..475e90c
--- /dev/null
@@ -0,0 +1,11 @@
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA 
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256
new file mode 100644 (file)
index 0000000..13109e9
--- /dev/null
@@ -0,0 +1,11 @@
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA 
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005
new file mode 100644 (file)
index 0000000..d82a0b5
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDqONQediveIXoseoT+MWp9yEdMO7hP7F4fAno6gunyoAAAAIig1MZroNTG
+awAAAAtzc2gtZWQyNTUxOQAAACDqONQediveIXoseoT+MWp9yEdMO7hP7F4fAno6gunyoA
+AAAEBSEPLoX4NVkAchYZEGi7hjd5NoVBWuoxqluCGt/fWrYeo41B52K94heix6hP4xan3I
+R0w7uE/sXh8CejqC6fKgAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub
new file mode 100644 (file)
index 0000000..59ea422
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGnzDhP/hp83ipkW8T7f0CIXJuPK7ldbJFKDUrkvn6J1AAAAIOo41B52K94heix6hP4xan3IR0w7uE/sXh8CejqC6fKgAAAAAAAAAAUAAAABAAAACXJldm9rZWQgNQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQO9W58IrK+I0o2us9Hs/QBkrEe1YIgl6PzCMsu/Zu/tdZxGDK5Pxoz7tKzXezS9LPGQfZ3fVdl58PZC1DtxQ5gU= ./tst-keys/unrevoked-0005.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub
new file mode 100644 (file)
index 0000000..081ac6c
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOo41B52K94heix6hP4xan3IR0w7uE/sXh8CejqC6fKg 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009
new file mode 100644 (file)
index 0000000..9479498
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDXQqTeALQCMo64B4EX5abjRvrjVu69Mnxgg2q0SB5/oQAAAIgIqeXLCKnl
+ywAAAAtzc2gtZWQyNTUxOQAAACDXQqTeALQCMo64B4EX5abjRvrjVu69Mnxgg2q0SB5/oQ
+AAAECubGChJGu90ZNiP/zF+tTtr0+l7y8BrTDMQ0m0+cU0qtdCpN4AtAIyjrgHgRflpuNG
++uNW7r0yfGCDarRIHn+hAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub
new file mode 100644 (file)
index 0000000..9ee8890
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIERRY0M1bHm2Qjyo105OCHWp0UCRHLP0xkMuHnkMDP5eAAAAINdCpN4AtAIyjrgHgRflpuNG+uNW7r0yfGCDarRIHn+hAAAAAAAAAAkAAAABAAAACXJldm9rZWQgOQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQFsA4xJHRCXSyq6GHkKdemfbg+jvUZxHlu/UBoZf4esEHAtx0mXiajbUwkWzkh1vCtxZNZhiLIhxqDcNMu+O+wo= ./tst-keys/unrevoked-0009.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub
new file mode 100644 (file)
index 0000000..74a797b
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINdCpN4AtAIyjrgHgRflpuNG+uNW7r0yfGCDarRIHn+h 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014
new file mode 100644 (file)
index 0000000..6fa4fd9
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDvTTMHyjozzZabuUzy61XOKBm4klUjUGSWYtX6T4XtEwAAAIhyFdxYchXc
+WAAAAAtzc2gtZWQyNTUxOQAAACDvTTMHyjozzZabuUzy61XOKBm4klUjUGSWYtX6T4XtEw
+AAAEBtC+f4bz1/qtq5K2Rf+0bPeY3P0OWdD3rvrlGPh8wN5u9NMwfKOjPNlpu5TPLrVc4o
+GbiSVSNQZJZi1fpPhe0TAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub
new file mode 100644 (file)
index 0000000..bb954f9
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPes2n/Xk4mm4OpuvHDqx9+76vm+SmFgc9d7ATGT1+C8AAAAIO9NMwfKOjPNlpu5TPLrVc4oGbiSVSNQZJZi1fpPhe0TAAAAAAAAAA4AAAABAAAACnJldm9rZWQgMTQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDGVORypw3DoMuWBu0V4cH/OgRBstD5cY37CfLrVZpmGv9jDRXVNQee7vYowk0r3XvQPoUecQBIMZGAQtEiw18E ./tst-keys/unrevoked-0014.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub
new file mode 100644 (file)
index 0000000..4a866e4
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO9NMwfKOjPNlpu5TPLrVc4oGbiSVSNQZJZi1fpPhe0T 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016
new file mode 100644 (file)
index 0000000..62d5027
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBWKMDlSwSGo4dcBAZmL+Xxk64Wp/ZfFSu2vkp82JXQCQAAAIjUcNt51HDb
+eQAAAAtzc2gtZWQyNTUxOQAAACBWKMDlSwSGo4dcBAZmL+Xxk64Wp/ZfFSu2vkp82JXQCQ
+AAAEC1V7PD5tJSOUZtpfqVfWyiSIMJkCDFZzTmFs7GBpJE71YowOVLBIajh1wEBmYv5fGT
+rhan9l8VK7a+SnzYldAJAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub
new file mode 100644 (file)
index 0000000..367e4ab
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAICGqa0xwr0etbKquuBy5/hYQ/rbMrKfEE6XShgb4YWpUAAAAIFYowOVLBIajh1wEBmYv5fGTrhan9l8VK7a+SnzYldAJAAAAAAAAABAAAAABAAAACnJldm9rZWQgMTYAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBKVetE3dsch2wjMIHGoiH8zp6gFMn1KgGKn01EPc1A08a/JKNvaSDYhlARLjiBzjIUGlykhHTTr4EcHTPWl58P ./tst-keys/unrevoked-0016.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub
new file mode 100644 (file)
index 0000000..47cac1e
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFYowOVLBIajh1wEBmYv5fGTrhan9l8VK7a+SnzYldAJ 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029
new file mode 100644 (file)
index 0000000..589daa6
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA3B1NQ9RFEkJUGcIUcCL22yMVEeob8/PUsk9lYH43vPwAAAIjxPrzV8T68
+1QAAAAtzc2gtZWQyNTUxOQAAACA3B1NQ9RFEkJUGcIUcCL22yMVEeob8/PUsk9lYH43vPw
+AAAED89ht9KdlYRfsKwh+pzh6BOvPf/U58QBkw1d3LfKnn+jcHU1D1EUSQlQZwhRwIvbbI
+xUR6hvz89SyT2Vgfje8/AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub
new file mode 100644 (file)
index 0000000..1bf3883
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIEVLRuchC4z7/EqITmyqCxOyhC7/enmFWsalP8FFFYiXAAAAIDcHU1D1EUSQlQZwhRwIvbbIxUR6hvz89SyT2Vgfje8/AAAAAAAAAB0AAAABAAAACnJldm9rZWQgMjkAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEChRFz/Zb6b3znoIWJjd8OTmCIEH7YE/fKWtyWHoGjz02G4VnCfwuHp23yD+k1XsoOGC7xcSnQeqZ19160HDNgC ./tst-keys/unrevoked-0029.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub
new file mode 100644 (file)
index 0000000..4072d92
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDcHU1D1EUSQlQZwhRwIvbbIxUR6hvz89SyT2Vgfje8/ 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049
new file mode 100644 (file)
index 0000000..b5788a0
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACD2mB5GBuavtb/bX7W54OmUCCJzUWBwG7cQ4q/jon1MBQAAAIjRkEU40ZBF
+OAAAAAtzc2gtZWQyNTUxOQAAACD2mB5GBuavtb/bX7W54OmUCCJzUWBwG7cQ4q/jon1MBQ
+AAAECuUtJb+T0um2mGvjD/ZZpbtjIhWc3jGVbzuDnEovOjnPaYHkYG5q+1v9tftbng6ZQI
+InNRYHAbtxDir+OifUwFAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub
new file mode 100644 (file)
index 0000000..587cf62
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILZPLEL5xQ8HDLa8pJhchJ3EEhZcjMqACCAEeL+U6c/QAAAAIPaYHkYG5q+1v9tftbng6ZQIInNRYHAbtxDir+OifUwFAAAAAAAAADEAAAABAAAACnJldm9rZWQgNDkAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB2GglzoC1VgsYNAVd5BDsLbeR5M5hHcVVvNsGnK1QCXMj56cgfkbXLj6W6tjJEEFY4G+KPJh1F/SGJi02P5lkJ ./tst-keys/unrevoked-0049.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub
new file mode 100644 (file)
index 0000000..07d5369
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPaYHkYG5q+1v9tftbng6ZQIInNRYHAbtxDir+OifUwF 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051
new file mode 100644 (file)
index 0000000..52d3283
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACD39ygfAlHPhZWU8inWu1hypIlQTChQxSKKB6iaV6Q0lQAAAIgMawsqDGsL
+KgAAAAtzc2gtZWQyNTUxOQAAACD39ygfAlHPhZWU8inWu1hypIlQTChQxSKKB6iaV6Q0lQ
+AAAEB4Ng9MekhsMKYDaBcOUWdxmi1rjgCsPOOfpABTxiCef/f3KB8CUc+FlZTyKda7WHKk
+iVBMKFDFIooHqJpXpDSVAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub
new file mode 100644 (file)
index 0000000..5b4bd11
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGTNYRrlJ1vExK7dume319Krn4YW6wyZc4PzZLjZoB8zAAAAIPf3KB8CUc+FlZTyKda7WHKkiVBMKFDFIooHqJpXpDSVAAAAAAAAADMAAAABAAAACnJldm9rZWQgNTEAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAgUiwWKerMo8nuejTER/EmM6ZUpmXjgFwPCpb1LAxBJH71iOnyF9S0gp+CSmjqiTS2yuQajSMen64wOdJCX7wF ./tst-keys/unrevoked-0051.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub
new file mode 100644 (file)
index 0000000..88867e5
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPf3KB8CUc+FlZTyKda7WHKkiVBMKFDFIooHqJpXpDSV 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499
new file mode 100644 (file)
index 0000000..8f59be9
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCpwI1aCbAOVvA7NJhLtBNpR4tiGGtTQ019wjKL6zJ/uQAAAIhllrzrZZa8
+6wAAAAtzc2gtZWQyNTUxOQAAACCpwI1aCbAOVvA7NJhLtBNpR4tiGGtTQ019wjKL6zJ/uQ
+AAAECQ6o+3J9W3wXFWEcrPJl5qJZudUPmPdKF7SYxcMTrVP6nAjVoJsA5W8Ds0mEu0E2lH
+i2IYa1NDTX3CMovrMn+5AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub
new file mode 100644 (file)
index 0000000..a6e76f1
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJvt1IxZsGIIS9DDCCKiD13Dbs5Af5ouews+YwZ9FoydAAAAIKnAjVoJsA5W8Ds0mEu0E2lHi2IYa1NDTX3CMovrMn+5AAAAAAAAAfMAAAABAAAAC3Jldm9rZWQgNDk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAMaA4UjND4LX9kdHjhgWJjGzzs/xUBwxQQcAmNgwmmQzmkwj8ctWBBA1+TkBMcZbSNUWBdclT4UcnDPEYqG1NBg== ./tst-keys/unrevoked-0499.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub
new file mode 100644 (file)
index 0000000..5a3acbb
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKnAjVoJsA5W8Ds0mEu0E2lHi2IYa1NDTX3CMovrMn+5 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800
new file mode 100644 (file)
index 0000000..9684d72
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAn5h8A2vYJ1+IWVtdLMulUQKCqlVLHpcHEFqYC5gtGlwAAAIh2lf7UdpX+
+1AAAAAtzc2gtZWQyNTUxOQAAACAn5h8A2vYJ1+IWVtdLMulUQKCqlVLHpcHEFqYC5gtGlw
+AAAEAEXGgMPKs3HwkQmNdVkbO3PcaBVCBEv1l8yy/ly30jPSfmHwDa9gnX4hZW10sy6VRA
+oKqVUselwcQWpgLmC0aXAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub
new file mode 100644 (file)
index 0000000..ab47a2b
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPAKFTJ25v9CsCppsQ/FwXAZgntAIdQHUXo0KQ3FrlTzAAAAICfmHwDa9gnX4hZW10sy6VRAoKqVUselwcQWpgLmC0aXAAAAAAAAAyAAAAABAAAAC3Jldm9rZWQgODAwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABA16aKfsgD0iZ+qc2b1AxBHZ/nyczN2Xjbhg4eJm/6cPSkBHs8uan5e8yPBIQJq2LztC3If6Z6PARoWUnIKb43CQ== ./tst-keys/unrevoked-0800.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub
new file mode 100644 (file)
index 0000000..3a41f29
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICfmHwDa9gnX4hZW10sy6VRAoKqVUselwcQWpgLmC0aX 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010
new file mode 100644 (file)
index 0000000..89df717
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAg0jawQzRMO/ESfFm6yDc66J5kjasOqTb7rmQSU6Nk3QAAAIhczXMoXM1z
+KAAAAAtzc2gtZWQyNTUxOQAAACAg0jawQzRMO/ESfFm6yDc66J5kjasOqTb7rmQSU6Nk3Q
+AAAEAdeQiqpyZqBaffmgy+UrvFVpygD0n8isn3zjumVNtKxiDSNrBDNEw78RJ8WbrINzro
+nmSNqw6pNvuuZBJTo2TdAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub
new file mode 100644 (file)
index 0000000..2d0fe53
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIITg9nSjjofIKXTKf2byvYL3Ce43PP9Dtrbj/+AlfgEtAAAAICDSNrBDNEw78RJ8WbrINzronmSNqw6pNvuuZBJTo2TdAAAAAAAAA/IAAAABAAAADHJldm9rZWQgMTAxMAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQIndHhKILtU0+FkKKw1KmhaHQS3p1KiQdld/2P5jpcEgb292iY+ICU+aHXKvS8qGM2aMImv8835NEyWy/MB74QM= ./tst-keys/unrevoked-1010.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub
new file mode 100644 (file)
index 0000000..05c5eac
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICDSNrBDNEw78RJ8WbrINzronmSNqw6pNvuuZBJTo2Td 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011
new file mode 100644 (file)
index 0000000..38b8232
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCd4IBQx9BhO9FzYMOKu3cKgBcwUwb7XzS3uI26RgmEYgAAAIjHvhtux74b
+bgAAAAtzc2gtZWQyNTUxOQAAACCd4IBQx9BhO9FzYMOKu3cKgBcwUwb7XzS3uI26RgmEYg
+AAAEBsteyDUYUNwgY3SMkMs0guy8MJfek2kuvH35zEpVf6Hp3ggFDH0GE70XNgw4q7dwqA
+FzBTBvtfNLe4jbpGCYRiAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub
new file mode 100644 (file)
index 0000000..4671638
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMjD2+xjmUC1VviOH+peT9C81Y4xjyTue/F69nFKmQBMAAAAIJ3ggFDH0GE70XNgw4q7dwqAFzBTBvtfNLe4jbpGCYRiAAAAAAAAA/MAAAABAAAADHJldm9rZWQgMTAxMQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQNENdVFCE02X6z+wFJtm2DQcgdc4oov9DyFKLPqLrogo+pVao5QwOkeJ2J/tmp40H2+uP/jrDlQuCvOcoQGHqwY= ./tst-keys/unrevoked-1011.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub
new file mode 100644 (file)
index 0000000..0809077
--- /dev/null
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3ggFDH0GE70XNgw4q7dwqAFzBTBvtfNLe4jbpGCYRi 
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle
new file mode 100644 (file)
index 0000000..c402f54
Binary files /dev/null and b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle differ
index e13379be72bf13805af4c6485ffa9ccc9d807371..fdfffce8106ce6f61f1c2fedcbb37f93e5de75f8 100644 (file)
@@ -50,6 +50,7 @@ public abstract class AbstractSshSignatureTest extends RepositoryTestCase {
        @Before
        public void setUp() throws Exception {
                super.setUp();
+               copyResource("allowed_signers", keys.getRoot());
                copyResource("other_key", keys.getRoot());
                copyResource("other_key.pub", keys.getRoot());
                copyResource("other_key-cert.pub", keys.getRoot());
@@ -65,6 +66,9 @@ public abstract class AbstractSshSignatureTest extends RepositoryTestCase {
                Repository repo = db;
                StoredConfig config = repo.getConfig();
                config.setString("gpg", null, "format", "ssh");
+               config.setString("gpg", "ssh", "allowedSignersFile",
+                               keys.getRoot().toPath().resolve("allowed_signers").toString()
+                                               .replace('\\', '/'));
                config.save();
                // Run all tests with commit times on 2024-10-02T12:00:00Z. The test
                // certificates are valid from 2024-09-01 to 2024-10-31, except the
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java
new file mode 100644 (file)
index 0000000..90fde3f
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import java.io.StreamCorruptedException;
+import java.time.Instant;
+
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the line parsing in {@link AllowedSigners}.
+ */
+public class AllowedSignersParseTest {
+
+       @Before
+       public void setup() {
+               // Uses GMT-03:30 as time zone.
+               SystemReader.setInstance(new MockSystemReader());
+       }
+
+       @After
+       public void tearDown() {
+               SystemReader.setInstance(null);
+       }
+
+       @Test
+       public void testValidDate() {
+               assertEquals(Instant.parse("2024-09-01T00:00:00.00Z"),
+                               AllowedSigners.parseDate("20240901Z"));
+               assertEquals(Instant.parse("2024-09-01T01:02:00.00Z"),
+                               AllowedSigners.parseDate("202409010102Z"));
+               assertEquals(Instant.parse("2024-09-01T01:02:03.00Z"),
+                               AllowedSigners.parseDate("20240901010203Z"));
+               assertEquals(Instant.parse("2024-09-01T03:30:00.00Z"),
+                               AllowedSigners.parseDate("20240901"));
+               assertEquals(Instant.parse("2024-09-01T04:32:00.00Z"),
+                               AllowedSigners.parseDate("202409010102"));
+               assertEquals(Instant.parse("2024-09-01T04:32:03.00Z"),
+                               AllowedSigners.parseDate("20240901010203"));
+       }
+
+       @Test
+       public void testInvalidDate() {
+               assertThrows(Exception.class, () -> AllowedSigners.parseDate("1234"));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parseDate("09/01/2024"));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parseDate("2024-09-01"));
+       }
+
+       private void checkValidKey(String expected, String input, int from)
+                       throws StreamCorruptedException {
+               assertEquals(expected, AllowedSigners.parsePublicKey(input, from));
+       }
+       @Test
+       public void testValidPublicKey() throws StreamCorruptedException {
+               checkValidKey(
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               0);
+               checkValidKey(
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               "xyzssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               3);
+               checkValidKey(
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               "xyz ssh-ed25519   AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO abc",
+                               3);
+               checkValidKey(
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+                               "xyz\tssh-ed25519 \tAAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO abc",
+                               3);
+       }
+
+       @Test
+       public void testInvalidPublicKey() {
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey(null, 0));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("", 0));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("foo", 0));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", -1));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 12));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 13));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 16));
+       }
+
+       @Test
+       public void testValidDequote() {
+               assertEquals(new AllowedSigners.Dequoted("a\\bc", 4),
+                               AllowedSigners.dequote("a\\bc", 0));
+               assertEquals(new AllowedSigners.Dequoted("a\\bc\"", 5),
+                               AllowedSigners.dequote("a\\bc\"", 0));
+               assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 5),
+                               AllowedSigners.dequote("a\\b\"c", 0));
+               assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 8),
+                               AllowedSigners.dequote("\"a\\b\\\"c\"", 0));
+               assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 11),
+                               AllowedSigners.dequote("xyz\"a\\b\\\"c\"", 3));
+               assertEquals(new AllowedSigners.Dequoted("abc", 6),
+                               AllowedSigners.dequote("   abc def", 3));
+       }
+
+       @Test
+       public void testInvalidDequote() {
+               assertThrows(Exception.class, () -> AllowedSigners.dequote("\"abc", 0));
+               assertThrows(Exception.class,
+                               () -> AllowedSigners.dequote("\"abc\\\"", 0));
+       }
+
+       @Test
+       public void testValidLine() throws Exception {
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "*@a.com" },
+                               true, null, null, null,
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "*@a.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "*@a.com", "*@b.a.com" },
+                               true, null, null, null,
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "*@a.com,*@b.a.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "foo@a.com" },
+                               false, null, null, null,
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "foo@a.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "foo@a.com" },
+                               false, new String[] { "foo", "bar" }, null, null,
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "foo@a.com namespaces=\"foo,bar\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "foo@a.com" },
+                               false, null, Instant.parse("2024-09-01T03:30:00.00Z"), null,
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "foo@a.com valid-After=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertEquals(new AllowedSigners.AllowedEntry(
+                               new String[] { "*@a.com", "*@b.a.com" },
+                               true, new String[] { "git" },
+                               Instant.parse("2024-09-01T03:30:00.00Z"),
+                               Instant.parse("2024-09-01T12:00:00.00Z"),
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+                               AllowedSigners.parseLine(
+                                               "*@a.com,*@b.a.com cert-authority namespaces=\"git\" valid-after=\"20240901\" valid-before=\"202409011200Z\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+       }
+
+       @Test
+       public void testInvalidLine() {
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "namespaces=\"git\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "valid-after=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "valid-before=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "a@a.com namespaces=\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "a@a.com namespaces=\",,,\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+               assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+                               "a@a.com,,b@a.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+       }
+
+       @Test
+       public void testSkippedLine() throws Exception {
+               assertNull(AllowedSigners.parseLine(null));
+               assertNull(AllowedSigners.parseLine(""));
+               assertNull(AllowedSigners.parseLine("# Comment"));
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java
new file mode 100644 (file)
index 0000000..9f9c3ca
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+
+import org.junit.Test;
+
+/**
+ * Tests loading an {@link OpenSshBinaryKrl}.
+ */
+public class OpenSshBinaryKrlLoadTest {
+
+       @Test
+       public void testLoad() throws Exception {
+               try (InputStream in = new BufferedInputStream(
+                               this.getClass().getResourceAsStream("krl/krl"))) {
+                       OpenSshBinaryKrl krl = OpenSshBinaryKrl.load(in, false);
+                       assertNotNull(krl);
+               }
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java
new file mode 100644 (file)
index 0000000..2fd7756
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.AfterClass;
+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;
+
+/**
+ * Tests for {@link OpenSshKrl} using binary KRLs.
+ */
+@RunWith(Parameterized.class)
+public class OpenSshKrlTest {
+
+       // The test data was generated using the public domain OpenSSH test script
+       // with some minor modifications (une ed25519 always, generate a "includes
+       // everything KRl, name the unrekoked keys "unrevoked*", generate a plain
+       // text KRL, and don't run the ssh-keygen tests). The original script is
+       // available at
+       // https://github.com/openssh/openssh-portable/blob/67a115e/regress/krl.sh
+
+       private static final String[] KRLS = {
+                       "krl-empty", "krl-keys", "krl-all",
+                       "krl-sha1", "krl-sha256", "krl-hash",
+                       "krl-serial", "krl-keyid", "krl-cert", "krl-ca",
+                       "krl-serial-wild", "krl-keyid-wild", "krl-text" };
+
+       private static final int[] REVOKED = { 1, 4, 10, 50, 90, 500, 510, 520, 550,
+                       799, 999 };
+
+       private static final int[] UNREVOKED = { 5, 9, 14, 16, 29, 49, 51, 499, 800,
+                       1010, 1011 };
+
+       private static class TestData {
+
+               String key;
+
+               String krl;
+
+               Boolean expected;
+
+               TestData(String key, String krl, boolean expected) {
+                       this.key = key;
+                       this.krl = krl;
+                       this.expected = Boolean.valueOf(expected);
+               }
+
+               @Override
+               public String toString() {
+                       return key + '-' + krl;
+               }
+       }
+
+       @Parameters(name = "{0}")
+       public static List<TestData> initTestData() {
+               List<TestData> tests = new ArrayList<>();
+               for (int i = 0; i < REVOKED.length; i++) {
+                       String key = String.format("revoked-%04d",
+                                       Integer.valueOf(REVOKED[i]));
+                       for (String krl : KRLS) {
+                               boolean expected = !krl.endsWith("-empty");
+                               tests.add(new TestData(key + "-cert.pub", krl, expected));
+                               expected = krl.endsWith("-keys") || krl.endsWith("-all")
+                                               || krl.endsWith("-hash") || krl.endsWith("-sha1")
+                                               || krl.endsWith("-sha256") || krl.endsWith("-text");
+                               tests.add(new TestData(key + ".pub", krl, expected));
+                       }
+               }
+               for (int i = 0; i < UNREVOKED.length; i++) {
+                       String key = String.format("unrevoked-%04d",
+                                       Integer.valueOf(UNREVOKED[i]));
+                       for (String krl : KRLS) {
+                               boolean expected = false;
+                               tests.add(new TestData(key + ".pub", krl, expected));
+                               expected = krl.endsWith("-ca");
+                               tests.add(new TestData(key + "-cert.pub", krl, expected));
+                       }
+               }
+               return tests;
+       }
+
+       private static Path tmp;
+
+       @BeforeClass
+       public static void setUp() throws IOException {
+               tmp = Files.createTempDirectory("krls");
+               for (String krl : KRLS) {
+                       copyResource("krl/" + krl, tmp);
+               }
+       }
+
+       private static void copyResource(String name, Path directory)
+                       throws IOException {
+               try (InputStream in = OpenSshKrlTest.class
+                               .getResourceAsStream(name)) {
+                       int i = name.lastIndexOf('/');
+                       String fileName = i < 0 ? name : name.substring(i + 1);
+                       Files.copy(in, directory.resolve(fileName));
+               }
+       }
+
+       @AfterClass
+       public static void cleanUp() throws Exception {
+               FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE);
+       }
+
+       // Injected by JUnit
+       @Parameter
+       public TestData data;
+
+       @Test
+       public void testIsRevoked() throws Exception {
+               OpenSshKrl krl = new OpenSshKrl(tmp.resolve(data.krl));
+               try (InputStream in = this.getClass()
+                               .getResourceAsStream("krl/" + data.key)) {
+                       PublicKey key = AuthorizedKeyEntry.readAuthorizedKeys(in, true)
+                                       .get(0)
+                                       .resolvePublicKey(null, PublicKeyEntryResolver.FAILING);
+                       assertEquals(data.expected, Boolean.valueOf(krl.isRevoked(key)));
+               }
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java
new file mode 100644 (file)
index 0000000..e6709ad
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Tests for the set of serial number ranges.
+ */
+public class SerialRangeSetTest {
+
+       private SerialRangeSet ranges = new SerialRangeSet();
+
+       @Test
+       public void testInsertSimple() {
+               ranges.add(1);
+               ranges.add(3);
+               ranges.add(5);
+               assertEquals(3, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertFalse(ranges.contains(2));
+               assertTrue(ranges.contains(3));
+               assertFalse(ranges.contains(4));
+               assertTrue(ranges.contains(5));
+               assertFalse(ranges.contains(6));
+       }
+
+       @Test
+       public void testInsertSimpleRanges() {
+               ranges.add(1, 2);
+               ranges.add(4, 5);
+               ranges.add(7, 8);
+               assertEquals(3, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertTrue(ranges.contains(2));
+               assertFalse(ranges.contains(3));
+               assertTrue(ranges.contains(4));
+               assertTrue(ranges.contains(5));
+               assertFalse(ranges.contains(6));
+               assertTrue(ranges.contains(7));
+               assertTrue(ranges.contains(8));
+               assertFalse(ranges.contains(9));
+       }
+
+       @Test
+       public void testInsertCoalesce() {
+               ranges.add(5);
+               ranges.add(1);
+               ranges.add(2);
+               ranges.add(4);
+               ranges.add(7);
+               ranges.add(3);
+               assertEquals(2, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertTrue(ranges.contains(2));
+               assertTrue(ranges.contains(3));
+               assertTrue(ranges.contains(4));
+               assertTrue(ranges.contains(5));
+               assertFalse(ranges.contains(6));
+               assertTrue(ranges.contains(7));
+               assertFalse(ranges.contains(8));
+       }
+
+       @Test
+       public void testInsertOverlap() {
+               ranges.add(1, 3);
+               ranges.add(6);
+               ranges.add(2, 5);
+               assertEquals(1, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertTrue(ranges.contains(2));
+               assertTrue(ranges.contains(3));
+               assertTrue(ranges.contains(4));
+               assertTrue(ranges.contains(5));
+               assertTrue(ranges.contains(6));
+               assertFalse(ranges.contains(7));
+       }
+
+       @Test
+       public void testInsertOverlapMultiple() {
+               ranges.add(1, 3);
+               ranges.add(5, 6);
+               ranges.add(8);
+               ranges.add(2, 5);
+               assertEquals(2, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertTrue(ranges.contains(2));
+               assertTrue(ranges.contains(3));
+               assertTrue(ranges.contains(4));
+               assertTrue(ranges.contains(5));
+               assertTrue(ranges.contains(6));
+               assertFalse(ranges.contains(7));
+               assertTrue(ranges.contains(8));
+               assertFalse(ranges.contains(9));
+       }
+
+       @Test
+       public void testInsertOverlapTotal() {
+               ranges.add(1, 3);
+               ranges.add(2, 3);
+               assertEquals(1, ranges.size());
+               assertFalse(ranges.contains(0));
+               assertTrue(ranges.contains(1));
+               assertTrue(ranges.contains(2));
+               assertTrue(ranges.contains(3));
+               assertFalse(ranges.contains(4));
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java
new file mode 100644 (file)
index 0000000..e5dfe49
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.VerificationResult;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.StringUtils;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link SshSignatureVerifier}.
+ */
+public class SshSignatureVerifierTest extends AbstractSshSignatureTest {
+
+       @Test
+       public void testPlainSignature() throws Exception {
+               RevCommit c = checkSshSignature(
+                               createSignedCommit(null, "signing_key.pub"));
+               try (Git git = new Git(db)) {
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addName(c.getName()).call();
+                       assertEquals(1, results.size());
+                       VerificationResult verified = results.get(c.getName());
+                       assertNotNull(verified);
+                       assertNull(verified.getException());
+                       SignatureVerifier.SignatureVerification v = verified
+                                       .getVerification();
+                       assertTrue(v.verified());
+                       assertFalse(v.expired());
+                       assertTrue(StringUtils.isEmptyOrNull(v.message()));
+                       assertEquals("tester@example.com", v.keyUser());
+                       assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+                                       v.keyFingerprint());
+                       assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+                       assertEquals(commitTime, v.creationDate().toInstant());
+               }
+       }
+
+       @Test
+       public void testCertificateSignature() throws Exception {
+               RevCommit c = checkSshSignature(
+                               createSignedCommit("tester.cert", "signing_key-cert.pub"));
+               try (Git git = new Git(db)) {
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addName(c.getName()).call();
+                       assertEquals(1, results.size());
+                       VerificationResult verified = results.get(c.getName());
+                       assertNotNull(verified);
+                       assertNull(verified.getException());
+                       SignatureVerifier.SignatureVerification v = verified
+                                       .getVerification();
+                       assertTrue(v.verified());
+                       assertFalse(v.expired());
+                       assertTrue(StringUtils.isEmptyOrNull(v.message()));
+                       assertEquals("tester@example.com", v.keyUser());
+                       assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+                                       v.keyFingerprint());
+                       assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+                       assertEquals(commitTime, v.creationDate().toInstant());
+               }
+       }
+
+       @Test
+       public void testNoPrincipalsSignature() throws Exception {
+               RevCommit c = checkSshSignature(createSignedCommit("no_principals.cert",
+                               "signing_key-cert.pub"));
+               try (Git git = new Git(db)) {
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addName(c.getName()).call();
+                       assertEquals(1, results.size());
+                       VerificationResult verified = results.get(c.getName());
+                       assertNotNull(verified);
+                       assertNull(verified.getException());
+                       SignatureVerifier.SignatureVerification v = verified
+                                       .getVerification();
+                       assertFalse(v.verified());
+                       assertFalse(v.expired());
+                       assertNull(v.keyUser());
+                       assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+                                       v.keyFingerprint());
+                       assertEquals(SignatureVerifier.TrustLevel.NEVER, v.trustLevel());
+                       assertTrue(v.message().contains("*@example.com"));
+                       assertEquals(commitTime, v.creationDate().toInstant());
+               }
+       }
+
+       @Test
+       public void testOtherCertificateSignature() throws Exception {
+               RevCommit c = checkSshSignature(
+                               createSignedCommit("other.cert", "signing_key-cert.pub"));
+               try (Git git = new Git(db)) {
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addName(c.getName()).call();
+                       assertEquals(1, results.size());
+                       VerificationResult verified = results.get(c.getName());
+                       assertNotNull(verified);
+                       assertNull(verified.getException());
+                       SignatureVerifier.SignatureVerification v = verified
+                                       .getVerification();
+                       assertTrue(v.verified());
+                       assertFalse(v.expired());
+                       assertTrue(StringUtils.isEmptyOrNull(v.message()));
+                       assertEquals("other@example.com", v.keyUser());
+                       assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+                                       v.keyFingerprint());
+                       assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+                       assertEquals(commitTime, v.creationDate().toInstant());
+               }
+       }
+
+       @Test
+       public void testTwoPrincipalsCertificateSignature() throws Exception {
+               RevCommit c = checkSshSignature(createSignedCommit(
+                               "two_principals.cert", "signing_key-cert.pub"));
+               try (Git git = new Git(db)) {
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addName(c.getName()).call();
+                       assertEquals(1, results.size());
+                       VerificationResult verified = results.get(c.getName());
+                       assertNotNull(verified);
+                       assertNull(verified.getException());
+                       SignatureVerifier.SignatureVerification v = verified
+                                       .getVerification();
+                       assertTrue(v.verified());
+                       assertFalse(v.expired());
+                       assertTrue(StringUtils.isEmptyOrNull(v.message()));
+                       assertEquals("foo@example.com,tester@example.com", v.keyUser());
+                       assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+                                       v.keyFingerprint());
+                       assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+                       assertEquals(commitTime, v.creationDate().toInstant());
+               }
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java
new file mode 100644 (file)
index 0000000..30ddee5
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.VerificationResult;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.GitDateFormatter;
+import org.eclipse.jgit.util.SignatureUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Verifies signatures made with C git and OpenSSH 9.0 to ensure we arrive at
+ * the same good/bad decisions, and that we can verify signatures not created by
+ * ourselves.
+ * <p>
+ * Clones a JGit repo from a git bundle file created with C git, then checks all
+ * the commits and their signatures. (All commits in that bundle have SSH
+ * signatures.)
+ * </p>
+ */
+public class VerifyGitSignaturesTest extends LocalDiskRepositoryTestCase {
+
+       private static final Logger LOG = LoggerFactory
+                       .getLogger(VerifyGitSignaturesTest.class);
+
+       @Rule
+       public TemporaryFolder bundleDir = new TemporaryFolder();
+
+       @Before
+       @Override
+       public void setUp() throws Exception {
+               super.setUp();
+               try (InputStream in = this.getClass()
+                               .getResourceAsStream("repo.bundle")) {
+                       Files.copy(in, bundleDir.getRoot().toPath().resolve("repo.bundle"));
+               }
+               try (InputStream in = this.getClass()
+                               .getResourceAsStream("allowed_signers")) {
+                       Files.copy(in,
+                                       bundleDir.getRoot().toPath().resolve("allowed_signers"));
+               }
+       }
+
+       /**
+        * Tests signatures created by C git using OpenSSH 9.0.
+        */
+       @Test
+       public void testGitSignatures() throws Exception {
+               File gitDir = new File(getTemporaryDirectory(), "repo.git");
+               try (Git git = Git.cloneRepository().setBare(true)
+                               .setGitDir(gitDir)
+                               .setURI(new File(bundleDir.getRoot(), "repo.bundle").toURI()
+                                               .toString())
+                               .setBranch("master")
+                               .call()) {
+                       StoredConfig config = git.getRepository().getConfig();
+                       config.setString("gpg", "ssh", "allowedSignersFile",
+                                       bundleDir.getRoot().toPath().resolve("allowed_signers")
+                                                       .toAbsolutePath().toString().replace('\\', '/'));
+                       config.save();
+                       List<String> commits = new ArrayList<>();
+                       Map<String, PersonIdent> committers = new HashMap<>();
+                       git.log().all().call().forEach(c -> {
+                               commits.add(c.getName());
+                               committers.put(c.getName(), c.getCommitterIdent());
+                       });
+                       Map<String, Boolean> expected = new HashMap<>();
+                       // These two commits do have multiple principals. GIT just reports
+                       // the first one; we report both.
+                       expected.put("9f79a7b661a22ab1ddf8af880d23678ae7696b71",
+                                       Boolean.TRUE);
+                       expected.put("435108d157440e77d61a914b6a5736bc831c874d",
+                                       Boolean.TRUE);
+                       // This commit has a wrong commit message; the certificate used
+                       // did _not_ have two principals, but only a single principal
+                       // foo@example.org.
+                       expected.put("779dac7de40ebc3886af87d5e6680a09f8b13a3e",
+                                       Boolean.TRUE);
+                       // Signed with other_key-cert.pub: we still don't know the key,
+                       // but we do know the certificate's CA key, and trust it, so it's
+                       // accepted as a signature from the principal(s) listed in the
+                       // certificate.
+                       expected.put("951f06d5b5598b721b98d98b04e491f234c1926a",
+                                       Boolean.TRUE);
+                       // Signature with other_key.pub not listed in allowed_signers
+                       expected.put("984e629c6d543a7f77eb49a8c9316f2ae4416375",
+                                       Boolean.FALSE);
+                       // Signed with other-ca.cert (CA key not in allowed_signers), but
+                       // the certified key _is_ listed in allowed_signers.
+                       expected.put("1d7ac6d91747a9c9a777df238fbdaeffa7731a6c",
+                                       Boolean.FALSE);
+                       expected.put("a297bcfbf5c4a850f9770655fef7315328a4b3fb",
+                                       Boolean.TRUE);
+                       expected.put("852729d54676cb83826ed821dc7734013e97950d",
+                                       Boolean.TRUE);
+                       // Signature with a certificate without principals.
+                       expected.put("e39a049f75fe127eb74b30aba4b64e171d4281dd",
+                                       Boolean.FALSE);
+                       // Signature made with expired.cert (expired at the commit time).
+                       // git/OpenSSH 9.0 allows to create such signatures, but reports
+                       // them as FALSE. Our SshSigner doesn't allow creating such
+                       // signatures.
+                       expected.put("303ea5e61feacdad4cb012b4cb6b0cea3fbcef9f",
+                                       Boolean.FALSE);
+                       expected.put("1ae4b120a869b72a7a2d4ad4d7a8c9d454384333",
+                                       Boolean.TRUE);
+                       Map<String, VerificationResult> results = git.verifySignature()
+                                       .addNames(commits).call();
+                       GitDateFormatter dateFormat = new GitDateFormatter(
+                                       GitDateFormatter.Format.ISO);
+                       for (String oid : commits) {
+                               VerificationResult v = results.get(oid);
+                               assertNotNull(v);
+                               assertNull(v.getException());
+                               SignatureVerifier.SignatureVerification sv = v
+                                               .getVerification();
+                               assertNotNull(sv);
+                               LOG.info("Commit {}\n{}", oid, SignatureUtils.toString(sv,
+                                               committers.get(oid), dateFormat));
+                               Boolean wanted = expected.get(oid);
+                               assertNotNull(wanted);
+                               assertEquals(wanted, Boolean.valueOf(sv.verified()));
+                       }
+               }
+       }
+}
index 512c19f4c29fa5b3f864162a8975ba53f50535cd..826d33009b128e47f06401f0a5bcf3c7a9e71f71 100644 (file)
@@ -28,6 +28,7 @@ Export-Package: org.eclipse.jgit.internal.signing.ssh;version="7.1.0";x-friends:
  org.eclipse.jgit.internal.transport.sshd.auth;version="7.1.0";x-internal:=true,
  org.eclipse.jgit.internal.transport.sshd.pkcs11;version="7.1.0";x-internal:=true,
  org.eclipse.jgit.internal.transport.sshd.proxy;version="7.1.0";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.signing.ssh;version="7.1.0";uses:="org.eclipse.jgit.lib",
  org.eclipse.jgit.transport.sshd;version="7.1.0";
   uses:="org.eclipse.jgit.transport,
    org.apache.sshd.client.config.hosts,
diff --git a/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory b/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory
new file mode 100644 (file)
index 0000000..4a0f553
--- /dev/null
@@ -0,0 +1 @@
+org.eclipse.jgit.signing.ssh.SshSignatureVerifierFactory
\ No newline at end of file
index e51c80a5acfebed9c003c07f801409633ca7a863..604823939133bd419e21327d8ba6fd97ed964164 100644 (file)
@@ -125,17 +125,59 @@ sshClosingDown=Apache MINA sshd session factory is closing down; cannot create n
 sshCommandTimeout={0} timed out after {1} seconds while opening the channel
 sshProcessStillRunning={0} is not yet completed, cannot get exit code
 sshProxySessionCloseFailed=Error while closing proxy session {0}
+signAllowedSignersCertAuthorityError=Garbage after cert-authority
+signAllowedSignersEmptyIdentity=Identities contains an empty identity; check for spurious extra commas: {0}
+signAllowedSignersEmptyNamespaces=Empty namespaces= is not allowed; to allow a key for any namespace, omit the namespaces option
+signAllowedSignersFormatError=Cannot parse allowed signers file {0}, problem at line {1}: {2}
+signAllowedSignersInvalidDate=Cannot parse valid-before or valid-after date {0}
+signAllowedSignersLineFormat=Invalid line format
+signAllowedSignersMultiple={0} is allowed only once
+signAllowedSignersNoIdentities=Line has no identity patterns
+signAllowedSignersPublicKeyParsing=Cannot parse public key {0}
+signAllowedSignersUnterminatedQuote=Unterminated double quote
 signCertAlgorithmMismatch=Certificate of type {0} with CA key {1} uses an incompatible signature algorithm {2}
 signCertAlgorithmUnknown=Certificate with CA key {0} is signed with an unknown algorithm {1}
 signCertificateExpired=Expired certificate with CA key {0}
 signCertificateInvalid=Certificate signature does not match on certificate with CA key {0}
+signCertificateNotForName=Certificate with CA key {0} does not apply for name ''{1}''
+signCertificateRevoked=Certificate with CA key {0} was revoked
 signCertificateTooEarly=Certificate with CA key {0} was not valid yet
+signCertificateWithoutPrincipals=Certificate with CA key {0} has no principals; identities from gpg.ssh.allowedSignersFile: {1}
 signDefaultKeyEmpty=git.ssh.defaultKeyCommand {0} returned no key
 signDefaultKeyFailed=git.ssh.defaultKeyCommand {0} failed with exit code {1}\n{2}
 signDefaultKeyInterrupted=git.ssh.defaultKeyCommand {0} was interrupted
+signGarbageAtEnd=SSH signature has extra bytes at the end
+signInvalidAlgorithm=SSH signature has invalid signature algorithm {0}
 signInvalidKeyDSA=SSH signatures with DSA keys or certificates are not supported; use a different signing key.
+signInvalidMagic=SSH signature does not start with "SSHSIG"
+signInvalidNamespace=Namespace of SSH signature should be ''git'' but is ''{0}''
+signInvalidSignature=SSH signature is invalid: {0}
+signInvalidVersion=Cannot verify signature with version {0}
+signKeyExpired=Expired key used for SSH signature
+signKeyRevoked=Key used for the SSH signature was revoked
+signKeyTooEarly=Key used for the SSH signature was not valid yet
+signKrlBlobLeftover=gpg.ssh.revocationFile has invalid blob section {0} with {1} leftover bytes
+signKrlBlobLengthInvalid=gpg.ssh.revocationFile has invalid blob length {1} in section {0}
+signKrlBlobLengthInvalidExpected=gpg.ssh.revocationFile has invalid blob length {1} (expected {2}) in section {0}
+signKrlCaKeyLengthInvalid=gpg.ssh.revocationFile has invalid CA key length {0} in certificates section
+signKrlCertificateLeftover=gpg.ssh.revocationFile has invalid certificates section with {0} leftover bytes
+signKrlCertificateSubsectionLeftover=gpg.ssh.revocationFile has invalid certificates subsection with {0} leftover bytes
+signKrlCertificateSubsectionLength=gpg.ssh.revocationFile has invalid certificates subsection length {0}
+signKrlEmptyRange=gpg.ssh.revocationFile has an empty range of certificate serial numbers
+signKrlInvalidBitSetLength=gpg.ssh.revocationFile has invalid certificate serial number bit set length {0}
+signKrlInvalidKeyIdLength=gpg.ssh.revocationFile has invalid certificate key ID length {0}
+signKrlInvalidMagic=gpg.ssh.revocationFile is not a binary OpenSSH key revocation list
+signKrlInvalidReservedLength=gpg.ssh.revocationFile has an invalid reserved string length {0}
+signKrlInvalidVersion=gpg.ssh.revocationFile: cannot read KRLs with FORMAT_VERSION {0}
+signKrlNoCertificateSubsection=gpg.ssh.revocationFile has certificate section without subsections
+signKrlSerialZero=gpg.ssh.revocationFile: certificate serial number zero cannot be revoked
+signKrlShortRange=gpg.ssh.revocationFile: short certificate serial number range, need at least 8 more bytes, got only {0}
+signKrlUnknownSection=gpg.ssh.revocationFile has an unknown section type {0}
+signKrlUnknownSubsection=gpg.ssh.revocationFile has an unknown certificates subsection type {0}
 signLogFailure=SSH signature verification failed
+signMismatchedSignatureAlgorithm=SSH signature made with an ''{0}'' key has incompatible signature algorithm ''{1}''
 signNoAgent=No connector for ssh-agent found; maybe include org.eclipse.jgit.ssh.apache.agent in the application.
+signNoPrincipalMatched=No principal matched in gpg.ssh.allowedSignersFile
 signNoPublicKey=No public key found with signing key {0}
 signNoSigningKey=Git config user.signingKey or gpg.ssh.defaultKeyCommand must be set for SSH signing.
 signNotUserCertificate=Certificate with CA key {0} used for the SSH signature is not a user certificate.
@@ -147,4 +189,5 @@ signTooManyPrivateKeys=Private key file {0} must contain exactly one private key
 signTooManyPublicKeys=Public key file {0} must contain exactly one public key
 signUnknownHashAlgorithm=SSH Signature has an unknown hash algorithm {0}
 signUnknownSignatureAlgorithm=SSH Signature has an unknown signature algorithm {0}
+signWrongNamespace=Key may not be used in namespace "{0}".
 unknownProxyProtocol=Ignoring unknown proxy protocol {0}
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java
new file mode 100644 (file)
index 0000000..92cf1fa
--- /dev/null
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Encapsulates the allowed signers handling.
+ */
+final class AllowedSigners extends ModifiableFileWatcher {
+
+       private static final String CERT_AUTHORITY = "cert-authority"; //$NON-NLS-1$
+
+       private static final String NAMESPACES = "namespaces="; //$NON-NLS-1$
+
+       private static final String VALID_AFTER = "valid-after="; //$NON-NLS-1$
+
+       private static final String VALID_BEFORE = "valid-before="; //$NON-NLS-1$
+
+       private static final String SSH_KEY_PREFIX = "ssh-"; //$NON-NLS-1$
+
+       private static final DateTimeFormatter SSH_DATE_FORMAT = new DateTimeFormatterBuilder()
+                       .appendValue(ChronoField.YEAR, 4)
+                       .appendValue(ChronoField.MONTH_OF_YEAR, 2)
+                       .appendValue(ChronoField.DAY_OF_MONTH, 2)
+                       .optionalStart()
+                       .appendValue(ChronoField.HOUR_OF_DAY, 2)
+                       .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+                       .optionalStart()
+                       .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+                       .toFormatter(Locale.ROOT);
+
+       private static final Predicate<AllowedEntry> CERTIFICATES = AllowedEntry::isCA;
+
+       private static final Predicate<AllowedEntry> PLAIN_KEYS = Predicate
+                       .not(CERTIFICATES);
+
+       static record AllowedEntry(String[] identities, boolean isCA,
+                       String[] namespaces, Instant validAfter, Instant validBefore,
+                       String key) {
+               // Empty
+
+               @Override
+               public final boolean equals(Object any) {
+                       if (this == any) {
+                               return true;
+                       }
+                       if (any == null || !(any instanceof AllowedEntry)) {
+                               return false;
+                       }
+                       AllowedEntry other = (AllowedEntry) any;
+                       return isCA == other.isCA
+                                       && Arrays.equals(identities, other.identities)
+                                       && Arrays.equals(namespaces, other.namespaces)
+                                       && Objects.equals(validAfter, other.validAfter)
+                                       && Objects.equals(validBefore, other.validBefore)
+                                       && Objects.equals(key, other.key);
+               }
+
+               @Override
+               public final int hashCode() {
+                       int hash = Boolean.hashCode(isCA);
+                       hash = hash * 31 + Arrays.hashCode(identities);
+                       hash = hash * 31 + Arrays.hashCode(namespaces);
+                       return hash * 31 + Objects.hash(validAfter, validBefore, key);
+               }
+       }
+
+       private static record State(Map<String, List<AllowedEntry>> entries) {
+               // Empty
+       }
+
+       private State state;
+
+       public AllowedSigners(Path path) {
+               super(path);
+               state = new State(new HashMap<>());
+       }
+
+       public String isAllowed(PublicKey key, String namespace, String name,
+                       Instant time) throws IOException, VerificationException {
+               State currentState = refresh();
+               PublicKey keyToCheck = key;
+               if (key instanceof OpenSshCertificate certificate) {
+                       AllowedEntry entry = find(currentState, certificate.getCaPubKey(),
+                                       namespace, name, time, CERTIFICATES);
+                       if (entry != null) {
+                               Collection<String> principals = certificate.getPrincipals();
+                               if (principals.isEmpty()) {
+                                       // According to the OpenSSH documentation, a certificate
+                                       // without principals is valid for anyone.
+                                       //
+                                       // See https://man.openbsd.org/ssh-keygen.1#CERTIFICATES .
+                                       //
+                                       // However, the same documentation also says that a name
+                                       // must match both the entry's patterns and be listed in the
+                                       // certificate's principals.
+                                       //
+                                       // See https://man.openbsd.org/ssh-keygen.1#ALLOWED_SIGNERS
+                                       //
+                                       // git/OpenSSH considers signatures made by such
+                                       // certificates untrustworthy.
+                                       String identities;
+                                       if (!StringUtils.isEmptyOrNull(name)) {
+                                               // The name must have matched entry.identities.
+                                               identities = name;
+                                       } else {
+                                               identities = Arrays.stream(entry.identities())
+                                                               .collect(Collectors.joining(",")); //$NON-NLS-1$
+                                       }
+                                       throw new VerificationException(false, MessageFormat.format(
+                                                       SshdText.get().signCertificateWithoutPrincipals,
+                                                       KeyUtils.getFingerPrint(certificate.getCaPubKey()),
+                                                       identities));
+                               }
+                               if (!StringUtils.isEmptyOrNull(name)) {
+                                       if (!principals.contains(name)) {
+                                               throw new VerificationException(false,
+                                                               MessageFormat.format(SshdText
+                                                                               .get().signCertificateNotForName,
+                                                                               KeyUtils.getFingerPrint(
+                                                                                               certificate.getCaPubKey()),
+                                                                               name));
+                                       }
+                                       return name;
+                               }
+                               // Filter the principals listed in the certificate by
+                               // the patterns defined in the file.
+                               Set<String> filtered = new LinkedHashSet<>();
+                               List<String> patterns = Arrays.asList(entry.identities());
+                               for (String principal : principals) {
+                                       if (OpenSshConfigFile.patternMatch(patterns, principal)) {
+                                               filtered.add(principal);
+                                       }
+                               }
+                               return filtered.stream().collect(Collectors.joining(",")); //$NON-NLS-1$
+                       }
+                       // Certificate not found. git/OpenSSH considers this untrustworthy,
+                       // even if the certified key itself might be listed.
+                       return null;
+                       // Alternative: go check for the certified key itself:
+                       // keyToCheck = certificate.getCertPubKey();
+               }
+               AllowedEntry entry = find(currentState, keyToCheck, namespace, name,
+                               time, PLAIN_KEYS);
+               if (entry != null) {
+                       if (!StringUtils.isEmptyOrNull(name)) {
+                               // The name must have matched entry.identities.
+                               return name;
+                       }
+                       // No name given, but we consider the key valid: report the
+                       // identities.
+                       return Arrays.stream(entry.identities())
+                                       .collect(Collectors.joining(",")); //$NON-NLS-1$
+               }
+               return null;
+       }
+
+       private AllowedEntry find(State current, PublicKey key,
+                       String namespace, String name, Instant time,
+                       Predicate<AllowedEntry> filter)
+                       throws VerificationException {
+               String k = PublicKeyEntry.toString(key);
+               VerificationException v = null;
+               List<AllowedEntry> candidates = current.entries().get(k);
+               if (candidates == null) {
+                       return null;
+               }
+               for (AllowedEntry entry : candidates) {
+                       if (!filter.test(entry)) {
+                               continue;
+                       }
+                       if (name != null && !OpenSshConfigFile
+                                       .patternMatch(Arrays.asList(entry.identities()), name)) {
+                               continue;
+                       }
+                       if (entry.namespaces() != null) {
+                               if (!OpenSshConfigFile.patternMatch(
+                                               Arrays.asList(entry.namespaces()),
+                                               namespace)) {
+                                       if (v == null) {
+                                               v = new VerificationException(false,
+                                                               MessageFormat.format(
+                                                                               SshdText.get().signWrongNamespace,
+                                                                               KeyUtils.getFingerPrint(key),
+                                                                               namespace));
+                                       }
+                                       continue;
+                               }
+                       }
+                       if (time != null) {
+                               if (entry.validAfter() != null
+                                               && time.isBefore(entry.validAfter())) {
+                                       if (v == null) {
+                                               v = new VerificationException(true,
+                                                               MessageFormat.format(
+                                                                               SshdText.get().signKeyTooEarly,
+                                                                               KeyUtils.getFingerPrint(key)));
+                                       }
+                                       continue;
+                               } else if (entry.validBefore() != null
+                                               && time.isAfter(entry.validBefore())) {
+                                       if (v == null) {
+                                               v = new VerificationException(true,
+                                                               MessageFormat.format(
+                                                                               SshdText.get().signKeyTooEarly,
+                                                                               KeyUtils.getFingerPrint(key)));
+                                       }
+                                       continue;
+                               }
+                       }
+                       return entry;
+               }
+               if (v != null) {
+                       throw v;
+               }
+               return null;
+       }
+
+       private synchronized State refresh() throws IOException {
+               if (checkReloadRequired()) {
+                       updateReloadAttributes();
+                       try {
+                               state = reload(getPath());
+                       } catch (NoSuchFileException e) {
+                               // File disappeared
+                               resetReloadAttributes();
+                               state = new State(new HashMap<>());
+                       }
+               }
+               return state;
+       }
+
+       private static State reload(Path path) throws IOException {
+               Map<String, List<AllowedEntry>> entries = new HashMap<>();
+               try (BufferedReader r = Files.newBufferedReader(path,
+                               StandardCharsets.UTF_8)) {
+                       String line;
+                       for (int lineNumber = 1;; lineNumber++) {
+                               line = r.readLine();
+                               if (line == null) {
+                                       break;
+                               }
+                               line = line.strip();
+                               try {
+                                       AllowedEntry entry = parseLine(line);
+                                       if (entry != null) {
+                                               entries.computeIfAbsent(entry.key(),
+                                                               k -> new ArrayList<>()).add(entry);
+                                       }
+                               } catch (IOException | RuntimeException e) {
+                                       throw new IOException(MessageFormat.format(
+                                                       SshdText.get().signAllowedSignersFormatError, path,
+                                                       Integer.toString(lineNumber), line), e);
+                               }
+                       }
+               }
+               return new State(entries);
+       }
+
+       private static boolean matches(String src, String other, int offset) {
+               return src.regionMatches(true, offset, other, 0, other.length());
+       }
+
+       // Things below have package visibility for testing.
+
+       static AllowedEntry parseLine(String line)
+                       throws IOException {
+               if (StringUtils.isEmptyOrNull(line) || line.charAt(0) == '#') {
+                       return null;
+               }
+               int length = line.length();
+               if ((matches(line, CERT_AUTHORITY, 0)
+                               && CERT_AUTHORITY.length() < length
+                               && Character.isWhitespace(line.charAt(CERT_AUTHORITY.length())))
+                               || matches(line, NAMESPACES, 0)
+                               || matches(line, VALID_AFTER, 0)
+                               || matches(line, VALID_BEFORE, 0)
+                               || matches(line, SSH_KEY_PREFIX, 0)) {
+                       throw new StreamCorruptedException(
+                                       SshdText.get().signAllowedSignersNoIdentities);
+               }
+               int i = 0;
+               while (i < length && !Character.isWhitespace(line.charAt(i))) {
+                       i++;
+               }
+               if (i >= length) {
+                       throw new StreamCorruptedException(SshdText.get().signAllowedSignersLineFormat);
+               }
+               String[] identities = line.substring(0, i).split(","); //$NON-NLS-1$
+               if (Arrays.stream(identities).anyMatch(String::isEmpty)) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersEmptyIdentity,
+                                       line.substring(0, i)));
+               }
+               // Parse the options
+               i++;
+               boolean isCA = false;
+               List<String> namespaces = null;
+               Instant validAfter = null;
+               Instant validBefore = null;
+               while (i < length) {
+                       // Skip whitespace
+                       if (Character.isSpaceChar(line.charAt(i))) {
+                               i++;
+                               continue;
+                       }
+                       if (matches(line, CERT_AUTHORITY, i)) {
+                               i += CERT_AUTHORITY.length();
+                               isCA = true;
+                               if (!Character.isWhitespace(line.charAt(i))) {
+                                       throw new StreamCorruptedException(SshdText.get().signAllowedSignersCertAuthorityError);
+                               }
+                               i++;
+                       } else if (matches(line, NAMESPACES, i)) {
+                               if (namespaces != null) {
+                                       throw new StreamCorruptedException(MessageFormat.format(
+                                                       SshdText.get().signAllowedSignersMultiple,
+                                                       NAMESPACES));
+                               }
+                               i += NAMESPACES.length();
+                               Dequoted parsed = dequote(line, i);
+                               i = parsed.after();
+                               String ns = parsed.value();
+                               String[] items = ns.split(","); //$NON-NLS-1$
+                               namespaces = new ArrayList<>(items.length);
+                               for (int j = 0; j < items.length; j++) {
+                                       String n = items[j].strip();
+                                       if (!n.isEmpty()) {
+                                               namespaces.add(n);
+                                       }
+                               }
+                               if (namespaces.isEmpty()) {
+                                       throw new StreamCorruptedException(
+                                                       SshdText.get().signAllowedSignersEmptyNamespaces);
+                               }
+                       } else if (matches(line, VALID_AFTER, i)) {
+                               if (validAfter != null) {
+                                       throw new StreamCorruptedException(MessageFormat.format(
+                                                       SshdText.get().signAllowedSignersMultiple,
+                                                       VALID_AFTER));
+                               }
+                               i += VALID_AFTER.length();
+                               Dequoted parsed = dequote(line, i);
+                               i = parsed.after();
+                               validAfter = parseDate(parsed.value());
+                       } else if (matches(line, VALID_BEFORE, i)) {
+                               if (validBefore != null) {
+                                       throw new StreamCorruptedException(MessageFormat.format(
+                                                       SshdText.get().signAllowedSignersMultiple,
+                                                       VALID_BEFORE));
+                               }
+                               i += VALID_BEFORE.length();
+                               Dequoted parsed = dequote(line, i);
+                               i = parsed.after();
+                               validBefore = parseDate(parsed.value());
+                       } else {
+                               break;
+                       }
+               }
+               // Now we should be at the key
+               String key = parsePublicKey(line, i);
+               return new AllowedEntry(identities, isCA,
+                               namespaces == null ? null : namespaces.toArray(new String[0]),
+                               validAfter, validBefore, key);
+       }
+
+       static String parsePublicKey(String s, int from)
+                       throws StreamCorruptedException {
+               int i = from;
+               int length = s.length();
+               while (i < length && Character.isWhitespace(s.charAt(i))) {
+                       i++;
+               }
+               if (i >= length) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersPublicKeyParsing,
+                                       s.substring(from)));
+               }
+               int start = i;
+               while (i < length && !Character.isWhitespace(s.charAt(i))) {
+                       i++;
+               }
+               if (i >= length) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersPublicKeyParsing,
+                                       s.substring(start)));
+               }
+               int endOfKeyType = i;
+               i = endOfKeyType + 1;
+               while (i < length && Character.isWhitespace(s.charAt(i))) {
+                       i++;
+               }
+               int startOfKey = i;
+               while (i < length && !Character.isWhitespace(s.charAt(i))) {
+                       i++;
+               }
+               if (i == startOfKey) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersPublicKeyParsing,
+                                       s.substring(start)));
+               }
+               String keyType = s.substring(start, endOfKeyType);
+               if (!keyType.startsWith(SSH_KEY_PREFIX)) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersPublicKeyParsing,
+                                       s.substring(start)));
+               }
+               return keyType + ' ' + s.substring(startOfKey, i);
+       }
+
+       static Instant parseDate(String input) {
+               // Allowed formats are YYYYMMDD[Z] or YYYYMMDDHHMM[SS][Z]. If 'Z', it's
+               // UTC, otherwise local time.
+               String timeSpec = input;
+               int length = input.length();
+               if (length < 8) {
+                       throw new IllegalArgumentException(MessageFormat.format(
+                                       SshdText.get().signAllowedSignersInvalidDate, input));
+               }
+               boolean isUTC = false;
+               if (timeSpec.charAt(length - 1) == 'Z') {
+                       isUTC = true;
+                       timeSpec = timeSpec.substring(0, length - 1);
+               }
+               LocalDateTime time;
+               TemporalAccessor temporalAccessor = SSH_DATE_FORMAT.parseBest(timeSpec,
+                               LocalDateTime::from, LocalDate::from);
+               if (temporalAccessor instanceof LocalDateTime) {
+                       time = (LocalDateTime) temporalAccessor;
+               } else {
+                       time = ((LocalDate) temporalAccessor).atStartOfDay();
+               }
+               if (isUTC) {
+                       return time.atOffset(ZoneOffset.UTC).toInstant();
+               }
+               TimeZone tz = SystemReader.getInstance().getTimeZone();
+               // Since there are a few TimeZone IDs that are not recognized by ZoneId,
+               // use offsets.
+               return time.atOffset(ZoneOffset.ofTotalSeconds(
+                               (int) TimeUnit.MILLISECONDS.toSeconds(tz.getRawOffset())))
+                               .toInstant();
+       }
+
+       // OpenSSH uses the backslash *only* to quote the double-quote.
+       static Dequoted dequote(String line, int from) {
+               int length = line.length();
+               int i = from;
+               if (line.charAt(i) == '"') {
+                       boolean quoted = false;
+                       i++;
+                       StringBuilder b = new StringBuilder();
+                       while (i < length) {
+                               char ch = line.charAt(i);
+                               if (ch == '"') {
+                                       if (quoted) {
+                                               b.append(ch);
+                                               quoted = false;
+                                       } else {
+                                               break;
+                                       }
+                               } else if (ch == '\\') {
+                                       quoted = true;
+                               } else {
+                                       if (quoted) {
+                                               b.append('\\');
+                                       }
+                                       b.append(ch);
+                                       quoted = false;
+                               }
+                               i++;
+                       }
+                       if (i >= length) {
+                               throw new IllegalArgumentException(
+                                               SshdText.get().signAllowedSignersUnterminatedQuote);
+                       }
+                       return new Dequoted(b.toString(), i + 1);
+               }
+               while (i < length && !Character.isWhitespace(line.charAt(i))) {
+                       i++;
+               }
+               return new Dequoted(line.substring(from, i), i);
+       }
+
+       static record Dequoted(String value, int after) {
+               // Empty
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java
new file mode 100644 (file)
index 0000000..46518d8
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * An implementation of OpenSSH binary format key revocation lists (KRLs).
+ *
+ * @see <a href=
+ *      "https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.krl">PROTOCOL.krl</a>
+ */
+class OpenSshBinaryKrl {
+
+       /**
+        * The "magic" bytes at the start of an OpenSSH binary KRL.
+        */
+       static final byte[] MAGIC = { 'S', 'S', 'H', 'K', 'R', 'L', '\n', 0 };
+
+       private static final int FORMAT_VERSION = 1;
+
+       private static final int SECTION_CERTIFICATES = 1;
+
+       private static final int SECTION_KEY = 2;
+
+       private static final int SECTION_SHA1 = 3;
+
+       private static final int SECTION_SIGNATURE = 4; // Skipped
+
+       private static final int SECTION_SHA256 = 5;
+
+       private static final int SECTION_EXTENSION = 255; // Skipped
+
+       // Certificates
+
+       private static final int CERT_SERIAL_LIST = 0x20;
+
+       private static final int CERT_SERIAL_RANGES = 0x21;
+
+       private static final int CERT_SERIAL_BITS = 0x22;
+
+       private static final int CERT_KEY_IDS = 0x23;
+
+       private static final int CERT_EXTENSIONS = 0x39; // Skipped
+
+       private final Map<Blob, CertificateRevocation> certificates = new HashMap<>();
+
+       private static class CertificateRevocation {
+
+               final SerialRangeSet ranges = new SerialRangeSet();
+
+               final Set<String> keyIds = new HashSet<>();
+       }
+
+       // Plain keys
+
+       /**
+        * A byte array that can be used as a key in a {@link Map} or {@link Set}.
+        * {@link #equals(Object)} and {@link #hashCode()} are based on the content.
+        *
+        * @param blob
+        *            the array to wrap
+        */
+       private static record Blob(byte[] blob) {
+
+               @Override
+               public final boolean equals(Object any) {
+                       if (this == any) {
+                               return true;
+                       }
+                       if (any == null || !(any instanceof Blob)) {
+                               return false;
+                       }
+                       Blob other = (Blob) any;
+                       return Arrays.equals(blob, other.blob);
+               }
+
+               @Override
+               public final int hashCode() {
+                       return Arrays.hashCode(blob);
+               }
+       }
+
+       private final Set<Blob> blobs = new HashSet<>();
+
+       private final Set<Blob> sha1 = new HashSet<>();
+
+       private final Set<Blob> sha256 = new HashSet<>();
+
+       private OpenSshBinaryKrl() {
+               // No public instantiation, use load(InputStream, boolean) instead.
+       }
+
+       /**
+        * Tells whether the given key has been revoked.
+        *
+        * @param key
+        *            {@link PublicKey} to check
+        * @return {@code true} if the key was revoked, {@code false} otherwise
+        */
+       boolean isRevoked(PublicKey key) {
+               if (key instanceof OpenSshCertificate certificate) {
+                       if (certificates.isEmpty()) {
+                               return false;
+                       }
+                       // These apply to all certificates
+                       if (isRevoked(certificate, certificates.get(null))) {
+                               return true;
+                       }
+                       if (isRevoked(certificate,
+                                       certificates.get(blob(certificate.getCaPubKey())))) {
+                               return true;
+                       }
+                       // Keys themselves are checked in OpenSshKrl.
+                       return false;
+               }
+               if (!blobs.isEmpty() && blobs.contains(blob(key))) {
+                       return true;
+               }
+               if (!sha256.isEmpty() && sha256.contains(hash("SHA256", key))) { //$NON-NLS-1$
+                       return true;
+               }
+               if (!sha1.isEmpty() && sha1.contains(hash("SHA1", key))) { //$NON-NLS-1$
+                       return true;
+               }
+               return false;
+       }
+
+       private boolean isRevoked(OpenSshCertificate certificate,
+                       CertificateRevocation revocations) {
+               if (revocations == null) {
+                       return false;
+               }
+               String id = certificate.getId();
+               if (!StringUtils.isEmptyOrNull(id) && revocations.keyIds.contains(id)) {
+                       return true;
+               }
+               long serial = certificate.getSerial();
+               if (serial != 0 && revocations.ranges.contains(serial)) {
+                       return true;
+               }
+               return false;
+       }
+
+       private Blob blob(PublicKey key) {
+               ByteArrayBuffer buf = new ByteArrayBuffer();
+               buf.putRawPublicKey(key);
+               return new Blob(buf.getCompactData());
+       }
+
+       private Blob hash(String algorithm, PublicKey key) {
+               ByteArrayBuffer buf = new ByteArrayBuffer();
+               buf.putRawPublicKey(key);
+               try {
+                       return new Blob(MessageDigest.getInstance(algorithm)
+                                       .digest(buf.getCompactData()));
+               } catch (NoSuchAlgorithmException e) {
+                       throw new JGitInternalException(e.getMessage(), e);
+               }
+       }
+
+       /**
+        * Loads a binary KRL from the given stream.
+        *
+        * @param in
+        *            {@link InputStream} to read from
+        * @param magicSkipped
+        *            whether the {@link #MAGIC} bytes at the beginning have already
+        *            been skipped
+        * @return a new {@link OpenSshBinaryKrl}.
+        * @throws IOException
+        *             if the stream cannot be read as an OpenSSH binary KRL
+        */
+       @NonNull
+       static OpenSshBinaryKrl load(InputStream in, boolean magicSkipped)
+                       throws IOException {
+               if (!magicSkipped) {
+                       byte[] magic = new byte[MAGIC.length];
+                       IO.readFully(in, magic);
+                       if (!Arrays.equals(magic, MAGIC)) {
+                               throw new StreamCorruptedException(
+                                               SshdText.get().signKrlInvalidMagic);
+                       }
+               }
+               skipHeader(in);
+               return load(in);
+       }
+
+       private static long getUInt(InputStream in) throws IOException {
+               byte[] buf = new byte[Integer.BYTES];
+               IO.readFully(in, buf);
+               return BufferUtils.getUInt(buf);
+       }
+
+       private static long getLong(InputStream in) throws IOException {
+               byte[] buf = new byte[Long.BYTES];
+               IO.readFully(in, buf);
+               return BufferUtils.getLong(buf, 0, Long.BYTES);
+       }
+
+       private static void skipHeader(InputStream in) throws IOException {
+               long version = getUInt(in);
+               if (version != FORMAT_VERSION) {
+                       throw new StreamCorruptedException(
+                                       MessageFormat.format(SshdText.get().signKrlInvalidVersion,
+                                                       Long.valueOf(version)));
+               }
+               // krl_version, generated_date, flags (none defined in version 1)
+               in.skip(24);
+               in.skip(getUInt(in)); // reserved
+               in.skip(getUInt(in)); // comment
+       }
+
+       private static OpenSshBinaryKrl load(InputStream in) throws IOException {
+               OpenSshBinaryKrl krl = new OpenSshBinaryKrl();
+               for (;;) {
+                       int sectionType = in.read();
+                       if (sectionType < 0) {
+                               break; // EOF
+                       }
+                       switch (sectionType) {
+                       case SECTION_CERTIFICATES:
+                               readCertificates(krl.certificates, in, getUInt(in));
+                               break;
+                       case SECTION_KEY:
+                               readBlobs("explicit_keys", krl.blobs, in, getUInt(in), 0); //$NON-NLS-1$
+                               break;
+                       case SECTION_SHA1:
+                               readBlobs("fingerprint_sha1", krl.sha1, in, getUInt(in), 20); //$NON-NLS-1$
+                               break;
+                       case SECTION_SIGNATURE:
+                               // Unsupported as of OpenSSH 9.4. It even refuses to load such
+                               // KRLs. Just skip it.
+                               in.skip(getUInt(in));
+                               break;
+                       case SECTION_SHA256:
+                               readBlobs("fingerprint_sha256", krl.sha256, in, getUInt(in), //$NON-NLS-1$
+                                               32);
+                               break;
+                       case SECTION_EXTENSION:
+                               // No extensions are defined for version 1 KRLs.
+                               in.skip(getUInt(in));
+                               break;
+                       default:
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlUnknownSection,
+                                               Integer.valueOf(sectionType)));
+                       }
+               }
+               return krl;
+       }
+
+       private static void readBlobs(String sectionName, Set<Blob> blobs,
+                       InputStream in, long sectionLength, long expectedBlobLength)
+                       throws IOException {
+               while (sectionLength >= Integer.BYTES) {
+                       // Read blobs.
+                       long blobLength = getUInt(in);
+                       sectionLength -= Integer.BYTES;
+                       if (blobLength > sectionLength) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlBlobLengthInvalid, sectionName,
+                                               Long.valueOf(blobLength)));
+                       }
+                       if (expectedBlobLength != 0 && blobLength != expectedBlobLength) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlBlobLengthInvalidExpected,
+                                               sectionName, Long.valueOf(blobLength),
+                                               Long.valueOf(expectedBlobLength)));
+                       }
+                       byte[] blob = new byte[(int) blobLength];
+                       IO.readFully(in, blob);
+                       sectionLength -= blobLength;
+                       blobs.add(new Blob(blob));
+               }
+               if (sectionLength != 0) {
+                       throw new StreamCorruptedException(
+                                       MessageFormat.format(SshdText.get().signKrlBlobLeftover,
+                                                       sectionName, Long.valueOf(sectionLength)));
+               }
+       }
+
+       private static void readCertificates(Map<Blob, CertificateRevocation> certs,
+                       InputStream in, long sectionLength) throws IOException {
+               long keyLength = getUInt(in);
+               sectionLength -= Integer.BYTES;
+               if (keyLength > sectionLength) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signKrlCaKeyLengthInvalid,
+                                       Long.valueOf(keyLength)));
+               }
+               Blob key = null;
+               if (keyLength > 0) {
+                       byte[] blob = new byte[(int) keyLength];
+                       IO.readFully(in, blob);
+                       key = new Blob(blob);
+                       sectionLength -= keyLength;
+               }
+               CertificateRevocation rev = certs.computeIfAbsent(key,
+                               k -> new CertificateRevocation());
+               long reservedLength = getUInt(in);
+               sectionLength -= Integer.BYTES;
+               if (reservedLength > sectionLength) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signKrlCaKeyLengthInvalid,
+                                       Long.valueOf(reservedLength)));
+               }
+               in.skip(reservedLength);
+               sectionLength -= reservedLength;
+               if (sectionLength == 0) {
+                       throw new StreamCorruptedException(
+                                       SshdText.get().signKrlNoCertificateSubsection);
+               }
+               while (sectionLength > 0) {
+                       int subSection = in.read();
+                       if (subSection < 0) {
+                               throw new EOFException();
+                       }
+                       sectionLength--;
+                       if (sectionLength < Integer.BYTES) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlCertificateLeftover,
+                                               Long.valueOf(sectionLength)));
+                       }
+                       long subLength = getUInt(in);
+                       sectionLength -= Integer.BYTES;
+                       if (subLength > sectionLength) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlCertificateSubsectionLength,
+                                               Long.valueOf(subLength)));
+                       }
+                       if (subLength > 0) {
+                               switch (subSection) {
+                               case CERT_SERIAL_LIST:
+                                       readSerials(rev.ranges, in, subLength, false);
+                                       break;
+                               case CERT_SERIAL_RANGES:
+                                       readSerials(rev.ranges, in, subLength, true);
+                                       break;
+                               case CERT_SERIAL_BITS:
+                                       readSerialBitSet(rev.ranges, in, subLength);
+                                       break;
+                               case CERT_KEY_IDS:
+                                       readIds(rev.keyIds, in, subLength);
+                                       break;
+                               case CERT_EXTENSIONS:
+                                       in.skip(subLength);
+                                       break;
+                               default:
+                                       throw new StreamCorruptedException(MessageFormat.format(
+                                                       SshdText.get().signKrlUnknownSubsection,
+                                                       Long.valueOf(subSection)));
+                               }
+                       }
+                       sectionLength -= subLength;
+               }
+       }
+
+       private static void readSerials(SerialRangeSet set, InputStream in,
+                       long length, boolean ranges) throws IOException {
+               while (length >= Long.BYTES) {
+                       long a = getLong(in);
+                       length -= Long.BYTES;
+                       if (a == 0) {
+                               throw new StreamCorruptedException(
+                                               SshdText.get().signKrlSerialZero);
+                       }
+                       if (!ranges) {
+                               set.add(a);
+                               continue;
+                       }
+                       if (length < Long.BYTES) {
+                               throw new StreamCorruptedException(
+                                               MessageFormat.format(SshdText.get().signKrlShortRange,
+                                                               Long.valueOf(length)));
+                       }
+                       long b = getLong(in);
+                       length -= Long.BYTES;
+                       if (Long.compareUnsigned(a, b) > 0) {
+                               throw new StreamCorruptedException(
+                                               SshdText.get().signKrlEmptyRange);
+                       }
+                       set.add(a, b);
+               }
+               if (length != 0) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signKrlCertificateSubsectionLeftover,
+                                       Long.valueOf(length)));
+               }
+       }
+
+       private static void readSerialBitSet(SerialRangeSet set, InputStream in,
+                       long subLength) throws IOException {
+               while (subLength > 0) {
+                       if (subLength < Long.BYTES) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlCertificateSubsectionLeftover,
+                                               Long.valueOf(subLength)));
+                       }
+                       long base = getLong(in);
+                       subLength -= Long.BYTES;
+                       if (subLength < Integer.BYTES) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlCertificateSubsectionLeftover,
+                                               Long.valueOf(subLength)));
+                       }
+                       long setLength = getUInt(in);
+                       subLength -= Integer.BYTES;
+                       if (setLength == 0 || setLength > subLength) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlInvalidBitSetLength,
+                                               Long.valueOf(setLength)));
+                       }
+                       // Now process the bits. Note that the mpint is stored MSB first.
+                       //
+                       // We set individual serial numbers (one for each set bit) and let
+                       // the SerialRangeSet take care of coalescing for successive runs
+                       // of set bits.
+                       int n = (int) setLength;
+                       for (int i = n - 1; i >= 0; i--) {
+                               int b = in.read();
+                               if (b < 0) {
+                                       throw new EOFException();
+                               } else if (b == 0) {
+                                       // Stored as an mpint: may have leading zero bytes (actually
+                                       // at most one; if the high bit of the first byte is set).
+                                       continue;
+                               }
+                               for (int bit = 0,
+                                               mask = 1; bit < Byte.SIZE; bit++, mask <<= 1) {
+                                       if ((b & mask) != 0) {
+                                               set.add(base + (i * Byte.SIZE) + bit);
+                                       }
+                               }
+                       }
+                       subLength -= setLength;
+               }
+       }
+
+       private static void readIds(Set<String> ids, InputStream in, long subLength)
+                       throws IOException {
+               while (subLength >= Integer.BYTES) {
+                       long length = getUInt(in);
+                       subLength -= Integer.BYTES;
+                       if (length > subLength) {
+                               throw new StreamCorruptedException(MessageFormat.format(
+                                               SshdText.get().signKrlInvalidKeyIdLength,
+                                               Long.valueOf(length)));
+                       }
+                       byte[] bytes = new byte[(int) length];
+                       IO.readFully(in, bytes);
+                       ids.add(new String(bytes, StandardCharsets.UTF_8));
+                       subLength -= length;
+               }
+               if (subLength != 0) {
+                       throw new StreamCorruptedException(MessageFormat.format(
+                                       SshdText.get().signKrlCertificateSubsectionLeftover,
+                                       Long.valueOf(subLength)));
+               }
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java
new file mode 100644 (file)
index 0000000..7993def
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.eclipse.jgit.util.IO;
+
+/**
+ * An implementation of an OpenSSH key revocation list (KRL), either a binary
+ * KRL or a simple list of public keys.
+ */
+class OpenSshKrl extends ModifiableFileWatcher {
+
+       private static record State(Set<String> keys, OpenSshBinaryKrl krl) {
+               // Empty
+       }
+
+       private State state;
+
+       public OpenSshKrl(Path path) {
+               super(path);
+               state = new State(Set.of(), null);
+       }
+
+       public boolean isRevoked(PublicKey key) throws IOException {
+               State current = refresh();
+               return isRevoked(current, key);
+       }
+
+       private boolean isRevoked(State current, PublicKey key) {
+               if (key instanceof OpenSshCertificate cert) {
+                       OpenSshBinaryKrl krl = current.krl();
+                       if (krl != null && krl.isRevoked(cert)) {
+                               return true;
+                       }
+                       if (isRevoked(current, cert.getCaPubKey())
+                                       || isRevoked(current, cert.getCertPubKey())) {
+                               return true;
+                       }
+                       return false;
+               }
+               OpenSshBinaryKrl krl = current.krl();
+               if (krl != null) {
+                       return krl.isRevoked(key);
+               }
+               return current.keys().contains(PublicKeyEntry.toString(key));
+       }
+
+       private synchronized State refresh() throws IOException {
+               if (checkReloadRequired()) {
+                       updateReloadAttributes();
+                       try {
+                               state = reload(getPath());
+                       } catch (NoSuchFileException e) {
+                               // File disappeared
+                               resetReloadAttributes();
+                               state = new State(Set.of(), null);
+                       }
+               }
+               return state;
+       }
+
+       private static State reload(Path path) throws IOException {
+               try (BufferedInputStream in = new BufferedInputStream(
+                               Files.newInputStream(path))) {
+                       byte[] magic = new byte[OpenSshBinaryKrl.MAGIC.length];
+                       in.mark(magic.length);
+                       IO.readFully(in, magic);
+                       if (Arrays.equals(magic, OpenSshBinaryKrl.MAGIC)) {
+                               return new State(null, OpenSshBinaryKrl.load(in, true));
+                       }
+                       // Otherwise try reading it textually
+                       in.reset();
+                       return loadTextKrl(in);
+               }
+       }
+
+       private static State loadTextKrl(InputStream in) throws IOException {
+               Set<String> keys = new HashSet<>();
+               try (BufferedReader r = new BufferedReader(
+                               new InputStreamReader(in, StandardCharsets.UTF_8))) {
+                       String line;
+                       for (;;) {
+                               line = r.readLine();
+                               if (line == null) {
+                                       break;
+                               }
+                               line = line.strip();
+                               if (line.isEmpty() || line.charAt(0) == '#') {
+                                       continue;
+                               }
+                               keys.add(AllowedSigners.parsePublicKey(line, 0));
+                       }
+               }
+               return new State(keys, null);
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java
new file mode 100644 (file)
index 0000000..aa26886
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * A {@link CachingSigningKeyDatabase} using the OpenSSH allowed signers file
+ * and the OpenSSH key revocation list.
+ */
+public class OpenSshSigningKeyDatabase implements CachingSigningKeyDatabase {
+
+       // Keep caches of allowed signers and KRLs. Cache by canonical path.
+
+       private static final int DEFAULT_CACHE_SIZE = 5;
+
+       private AtomicInteger cacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
+
+       private class LRU<K, V> extends LinkedHashMap<K, V> {
+
+               private static final long serialVersionUID = 1L;
+
+               LRU() {
+                       super(DEFAULT_CACHE_SIZE, 0.75f, true);
+               }
+
+               @Override
+               protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+                       return size() > cacheSize.get();
+               }
+       }
+
+       private final HashMap<Path, AllowedSigners> allowedSigners = new LRU<>();
+
+       private final HashMap<Path, OpenSshKrl> revocations = new LRU<>();
+
+       @Override
+       public boolean isRevoked(Repository repository, GpgConfig config,
+                       PublicKey key) throws IOException {
+               String fileName = config.getSshRevocationFile();
+               if (StringUtils.isEmptyOrNull(fileName)) {
+                       return false;
+               }
+               File file = getFile(repository, fileName);
+               OpenSshKrl revocationList;
+               synchronized (revocations) {
+                       revocationList = revocations.computeIfAbsent(file.toPath(),
+                                       OpenSshKrl::new);
+               }
+               return revocationList.isRevoked(key);
+       }
+
+       @Override
+       public String isAllowed(Repository repository, GpgConfig config,
+                       PublicKey key, String namespace, PersonIdent ident)
+                       throws IOException, VerificationException {
+               String fileName = config.getSshAllowedSignersFile();
+               if (StringUtils.isEmptyOrNull(fileName)) {
+                       // No file configured. Git would error out.
+                       return null;
+               }
+               File file = getFile(repository, fileName);
+               AllowedSigners allowed;
+               synchronized (allowedSigners) {
+                       allowed = allowedSigners.computeIfAbsent(file.toPath(),
+                                       AllowedSigners::new);
+               }
+               Instant gitTime = null;
+               if (ident != null) {
+                       gitTime = ident.getWhenAsInstant();
+               }
+               return allowed.isAllowed(key, namespace, null, gitTime);
+       }
+
+       private File getFile(@NonNull Repository repository, String fileName)
+                       throws IOException {
+               File file;
+               if (fileName.startsWith("~/") //$NON-NLS-1$
+                               || fileName.startsWith('~' + File.separator)) {
+                       file = FS.DETECTED.resolve(FS.DETECTED.userHome(),
+                                       fileName.substring(2));
+               } else {
+                       file = new File(fileName);
+                       if (!file.isAbsolute()) {
+                               file = new File(repository.getWorkTree(), fileName);
+                       }
+               }
+               return file.getCanonicalFile();
+       }
+
+       @Override
+       public int getCacheSize() {
+               return cacheSize.get();
+       }
+
+       @Override
+       public void setCacheSize(int size) {
+               if (size > 0) {
+                       cacheSize.set(size);
+                       pruneCache(size);
+               }
+       }
+
+       private void pruneCache(int size) {
+               prune(allowedSigners, size);
+               prune(revocations, size);
+       }
+
+       private void prune(HashMap<?, ?> map, int size) {
+               synchronized (map) {
+                       if (map.size() <= size) {
+                               return;
+                       }
+                       Iterator<?> iter = map.entrySet().iterator();
+                       int i = 0;
+                       while (iter.hasNext() && i < size) {
+                               iter.next();
+                               i++;
+                       }
+                       while (iter.hasNext()) {
+                               iter.next();
+                               iter.remove();
+                       }
+               }
+       }
+
+       @Override
+       public void clearCache() {
+               synchronized (allowedSigners) {
+                       allowedSigners.clear();
+               }
+               synchronized (revocations) {
+                       revocations.clear();
+               }
+       }
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java
new file mode 100644 (file)
index 0000000..6a0cec8
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+
+/**
+ * Encapsulates the storage for revoked certificate serial numbers.
+ */
+class SerialRangeSet {
+
+       /**
+        * A range of certificate serial numbers [from..to], i.e., with both range
+        * limits included.
+        */
+       private interface SerialRange {
+
+               long from();
+
+               long to();
+       }
+
+       private static record Singleton(long from) implements SerialRange {
+
+               @Override
+               public long to() {
+                       return from;
+               }
+       }
+
+       private static record Range(long from, long to) implements SerialRange {
+
+               public Range(long from, long to) {
+                       if (Long.compareUnsigned(from, to) > 0) {
+                               throw new IllegalArgumentException(
+                                               SshdText.get().signKrlEmptyRange);
+                       }
+                       this.from = from;
+                       this.to = to;
+               }
+       }
+
+       // We use the same data structure as OpenSSH,; basically a
+       // TreeSet<SerialRange> of mutable elements. To get "mutability", the set is
+       // implemented as a TreeMap with the same elements as keys and values.
+       //
+       // get(x) will return null if none of the serial numbers in the range x is
+       // in the set, and some range (partially) overlapping with x otherwise.
+       //
+       // containsKey will return true if there is any (partially) overlapping
+       // range in the TreeMap.
+       private final TreeMap<SerialRange, SerialRange> ranges = new TreeMap<>(
+                       SerialRangeSet::compare);
+
+       private static int compare(SerialRange a, SerialRange b) {
+               // Return == if they overlap
+               if (Long.compareUnsigned(a.to(), b.from()) >= 0
+                               && Long.compareUnsigned(a.from(), b.to()) <= 0) {
+                       return 0;
+               }
+               return Long.compareUnsigned(a.from(), b.from());
+       }
+
+       void add(long serial) {
+               add(ranges, new Singleton(serial));
+       }
+
+       void add(long from, long to) {
+               add(ranges, new Range(from, to));
+       }
+
+       boolean contains(long serial) {
+               return ranges.containsKey(new Singleton(serial));
+       }
+
+       int size() {
+               return ranges.size();
+       }
+
+       boolean isEmpty() {
+               return ranges.isEmpty();
+       }
+
+       private static void add(TreeMap<SerialRange, SerialRange> ranges,
+                       SerialRange newRange) {
+               for (;;) {
+                       SerialRange existing = ranges.get(newRange);
+                       if (existing == null) {
+                               break;
+                       }
+                       if (Long.compareUnsigned(existing.from(), newRange.from()) <= 0
+                                       && Long.compareUnsigned(existing.to(),
+                                                       newRange.to()) >= 0) {
+                               // newRange completely contained in existing
+                               return;
+                       }
+                       ranges.remove(existing);
+                       long newFrom = newRange.from();
+                       if (Long.compareUnsigned(existing.from(), newFrom) < 0) {
+                               newFrom = existing.from();
+                       }
+                       long newTo = newRange.to();
+                       if (Long.compareUnsigned(existing.to(), newTo) > 0) {
+                               newTo = existing.to();
+                       }
+                       newRange = new Range(newFrom, newTo);
+               }
+               // No overlapping range exists: check for coalescing with the
+               // previous/next range
+               SortedMap<SerialRange, SerialRange> head = ranges.headMap(newRange);
+               if (!head.isEmpty()) {
+                       SerialRange prev = head.lastKey();
+                       if (newRange.from() - prev.to() == 1) {
+                               ranges.remove(prev);
+                               newRange = new Range(prev.from(), newRange.to());
+                       }
+               }
+               SortedMap<SerialRange, SerialRange> tail = ranges.tailMap(newRange);
+               if (!tail.isEmpty()) {
+                       SerialRange next = tail.firstKey();
+                       if (next.from() - newRange.to() == 1) {
+                               ranges.remove(next);
+                               newRange = new Range(newRange.from(), next.to());
+                       }
+               }
+               ranges.put(newRange, newRange);
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java
new file mode 100644 (file)
index 0000000..e2e1a36
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.SigningKeyDatabase;
+
+/**
+ * A global {@link SigningKeyDatabase} instance.
+ */
+public final class SigningDatabase {
+
+       private static SigningKeyDatabase INSTANCE = new OpenSshSigningKeyDatabase();
+
+       private SigningDatabase() {
+               // No instantiation
+       }
+
+       /**
+        * Obtains the current instance.
+        *
+        * @return the global {@link SigningKeyDatabase}
+        */
+       public static synchronized SigningKeyDatabase getInstance() {
+               return INSTANCE;
+       }
+
+       /**
+        * Sets the global {@link SigningKeyDatabase}.
+        *
+        * @param database
+        *            to set; if {@code null} a default database using the OpenSSH
+        *            allowed signers file and the OpenSSH revocation list mechanism
+        *            is used.
+        * @return the previously set {@link SigningKeyDatabase}
+        */
+       public static synchronized SigningKeyDatabase setInstance(
+                       SigningKeyDatabase database) {
+               SigningKeyDatabase previous = INSTANCE;
+               if (database != INSTANCE) {
+                       if (INSTANCE instanceof CachingSigningKeyDatabase caching) {
+                               caching.clearCache();
+                       }
+                       if (database == null) {
+                               INSTANCE = new OpenSshSigningKeyDatabase();
+                       } else {
+                               INSTANCE = database;
+                       }
+               }
+               return previous;
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java
new file mode 100644 (file)
index 0000000..76be340
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.internal.signing.ssh;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+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.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.SigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link SignatureVerifier} for SSH signatures.
+ */
+public class SshSignatureVerifier implements SignatureVerifier {
+
+       private static final Logger LOG = LoggerFactory
+                       .getLogger(SshSignatureVerifier.class);
+
+       private static final byte[] OBJECT = { 'o', 'b', 'j', 'e', 'c', 't', ' ' };
+
+       private static final byte[] TREE = { 't', 'r', 'e', 'e', ' ' };
+
+       private static final byte[] TYPE = { 't', 'y', 'p', 'e', ' ' };
+
+       @Override
+       public String getName() {
+               return "ssh"; //$NON-NLS-1$
+       }
+
+       @Override
+       public SignatureVerification verify(Repository repository, GpgConfig config,
+                       byte[] data, byte[] signatureData) throws IOException {
+               // This is a bit stupid. SSH signatures do not store a signer, nor a
+               // time the signature was created. So we must use the committer's or
+               // tagger's PersonIdent, but here we have neither. But... if we see
+               // that the data is a commit or tag, then we can parse the PersonIdent
+               // from the data.
+               //
+               // Note: we cannot assume that absent a principal recorded in the
+               // allowedSignersFile or on a certificate that the key used to sign the
+               // commit belonged to the committer.
+               PersonIdent gitIdentity = getGitIdentity(data);
+               Date signatureDate = null;
+               Instant signatureInstant = null;
+               if (gitIdentity != null) {
+                       signatureDate = gitIdentity.getWhen();
+                       signatureInstant = gitIdentity.getWhenAsInstant();
+               }
+
+               TrustLevel trust = TrustLevel.NEVER;
+               byte[] decodedSignature;
+               try {
+                       decodedSignature = dearmor(signatureData);
+               } catch (IllegalArgumentException e) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       null, null, false, false, trust,
+                                       MessageFormat.format(SshdText.get().signInvalidSignature,
+                                                       e.getLocalizedMessage()));
+               }
+               int start = RawParseUtils.match(decodedSignature, 0,
+                               SshSignatureConstants.MAGIC);
+               if (start < 0) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       null, null, false, false, trust,
+                                       SshdText.get().signInvalidMagic);
+               }
+               ByteArrayBuffer signature = new ByteArrayBuffer(decodedSignature, start,
+                               decodedSignature.length - start);
+
+               long version = signature.getUInt();
+               if (version != SshSignatureConstants.VERSION) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       null, null, false, false, trust,
+                                       MessageFormat.format(SshdText.get().signInvalidVersion,
+                                                       Long.toString(version)));
+               }
+
+               PublicKey key = signature.getPublicKey();
+               String fingerprint;
+               if (key instanceof OpenSshCertificate cert) {
+                       fingerprint = KeyUtils.getFingerPrint(cert.getCertPubKey());
+                       String message = SshCertificateUtils.verify(cert, signatureInstant);
+                       if (message != null) {
+                               return new SignatureVerification(getName(), signatureDate, null,
+                                               fingerprint, null, false, false, trust, message);
+                       }
+               } else {
+                       fingerprint = KeyUtils.getFingerPrint(key);
+               }
+
+               String namespace = signature.getString();
+               if (!SshSignatureConstants.NAMESPACE.equals(namespace)) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       MessageFormat.format(SshdText.get().signInvalidNamespace,
+                                                       namespace));
+               }
+
+               signature.getString(); // Skip the reserved field
+               String hashAlgorithm = signature.getString();
+               byte[] hash;
+               try {
+                       hash = MessageDigest
+                                       .getInstance(hashAlgorithm.toUpperCase(Locale.ROOT))
+                                       .digest(data);
+               } catch (NoSuchAlgorithmException e) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       MessageFormat.format(
+                                                       SshdText.get().signUnknownHashAlgorithm,
+                                                       hashAlgorithm));
+               }
+               ByteArrayBuffer rawSignature = new ByteArrayBuffer(
+                               signature.getBytes());
+               if (signature.available() > 0) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       SshdText.get().signGarbageAtEnd);
+               }
+
+               String signatureAlgorithm = rawSignature.getString();
+               switch (signatureAlgorithm) {
+               case KeyPairProvider.SSH_DSS:
+               case KeyPairProvider.SSH_DSS_CERT:
+               case KeyPairProvider.SSH_RSA:
+               case KeyPairProvider.SSH_RSA_CERT:
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       MessageFormat.format(SshdText.get().signInvalidAlgorithm,
+                                                       signatureAlgorithm));
+               }
+
+               String keyType = KeyUtils
+                               .getSignatureAlgorithm(KeyUtils.getKeyType(key), key);
+               if (!KeyUtils.getCanonicalKeyType(keyType)
+                               .equals(KeyUtils.getCanonicalKeyType(signatureAlgorithm))) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       MessageFormat.format(
+                                                       SshdText.get().signMismatchedSignatureAlgorithm,
+                                                       keyType, signatureAlgorithm));
+               }
+
+               BuiltinSignatures factory = BuiltinSignatures
+                               .fromFactoryName(signatureAlgorithm);
+               if (factory == null || !factory.isSupported()) {
+                       return new SignatureVerification(getName(), signatureDate, null,
+                                       fingerprint, null, false, false, trust,
+                                       MessageFormat.format(
+                                                       SshdText.get().signUnknownSignatureAlgorithm,
+                                                       signatureAlgorithm));
+               }
+
+               boolean valid;
+               String message = null;
+               try {
+                       Signature verifier = factory.create();
+                       verifier.initVerifier(null,
+                                       key instanceof OpenSshCertificate cert
+                                                       ? cert.getCertPubKey()
+                                                       : key);
+                       // Feed it the data
+                       Buffer toSign = new ByteArrayBuffer();
+                       toSign.putRawBytes(SshSignatureConstants.MAGIC);
+                       toSign.putString(SshSignatureConstants.NAMESPACE);
+                       toSign.putUInt(0); // reserved: zero-length string
+                       toSign.putString(hashAlgorithm);
+                       toSign.putBytes(hash);
+                       verifier.update(null, toSign.getCompactData());
+                       valid = verifier.verify(null, rawSignature.getBytes());
+               } catch (Exception e) {
+                       LOG.warn("{}", SshdText.get().signLogFailure, e); //$NON-NLS-1$
+                       valid = false;
+                       message = SshdText.get().signSeeLog;
+               }
+               boolean expired = false;
+               String principal = null;
+               if (valid) {
+                       if (rawSignature.available() > 0) {
+                               valid = false;
+                               message = SshdText.get().signGarbageAtEnd;
+                       } else {
+                               SigningKeyDatabase database = SigningKeyDatabase.getInstance();
+                               if (database.isRevoked(repository, config, key)) {
+                                       valid = false;
+                                       if (key instanceof OpenSshCertificate certificate) {
+                                               message = MessageFormat.format(
+                                                               SshdText.get().signCertificateRevoked,
+                                                               KeyUtils.getFingerPrint(
+                                                                               certificate.getCaPubKey()));
+                                       } else {
+                                               message = SshdText.get().signKeyRevoked;
+                                       }
+                               } else {
+                                       // This may turn a positive verification into a failed one.
+                                       try {
+                                               principal = database.isAllowed(repository, config, key,
+                                                               SshSignatureConstants.NAMESPACE, gitIdentity);
+                                               if (!StringUtils.isEmptyOrNull(principal)) {
+                                                       trust = TrustLevel.FULL;
+                                               } else {
+                                                       valid = false;
+                                                       message = SshdText.get().signNoPrincipalMatched;
+                                                       trust = TrustLevel.UNKNOWN;
+                                               }
+                                       } catch (VerificationException e) {
+                                               valid = false;
+                                               message = e.getMessage();
+                                               expired = e.isExpired();
+                                       } catch (IOException e) {
+                                               LOG.warn("{}", SshdText.get().signLogFailure, e); //$NON-NLS-1$
+                                               valid = false;
+                                               message = SshdText.get().signSeeLog;
+                                       }
+                               }
+                       }
+               }
+               return new SignatureVerification(getName(), signatureDate, null,
+                               fingerprint, principal, valid, expired, trust, message);
+       }
+
+       private static PersonIdent getGitIdentity(byte[] rawObject) {
+               // Data from a commit will start with "tree ID\n".
+               int i = RawParseUtils.match(rawObject, 0, TREE);
+               if (i > 0) {
+                       i = RawParseUtils.committer(rawObject, 0);
+                       if (i < 0) {
+                               return null;
+                       }
+                       return RawParseUtils.parsePersonIdent(rawObject, i);
+               }
+               // Data from a tag will start with "object ID\ntype ".
+               i = RawParseUtils.match(rawObject, 0, OBJECT);
+               if (i > 0) {
+                       i = RawParseUtils.nextLF(rawObject, i);
+                       i = RawParseUtils.match(rawObject, i, TYPE);
+                       if (i > 0) {
+                               i = RawParseUtils.tagger(rawObject, 0);
+                               if (i < 0) {
+                                       return null;
+                               }
+                               return RawParseUtils.parsePersonIdent(rawObject, i);
+                       }
+               }
+               return null;
+       }
+
+       private static byte[] dearmor(byte[] data) {
+               int start = RawParseUtils.match(data, 0,
+                               SshSignatureConstants.ARMOR_HEAD);
+               if (start > 0) {
+                       if (data[start] == '\r') {
+                               start++;
+                       }
+                       if (data[start] == '\n') {
+                               start++;
+                       }
+               }
+               int end = data.length;
+               if (end > start + 1 && data[end - 1] == '\n') {
+                       end--;
+                       if (end > start + 1 && data[end - 1] == '\r') {
+                               end--;
+                       }
+               }
+               end = end - SshSignatureConstants.ARMOR_END.length;
+               if (end >= 0 && end >= start
+                               && RawParseUtils.match(data, end,
+                                               SshSignatureConstants.ARMOR_END) >= 0) {
+                       // end is fine: on the first the character of the end marker
+               } else {
+                       // No end marker.
+                       end = data.length;
+               }
+               if (start < 0) {
+                       start = 0;
+               }
+               return Base64.decode(data, start, end - start);
+       }
+
+       @Override
+       public void clear() {
+               SigningKeyDatabase database = SigningKeyDatabase.getInstance();
+               if (database instanceof CachingSigningKeyDatabase caching) {
+                       caching.clearCache();
+               }
+       }
+}
index 6f7a4e9ef4830a46638365012ee59462e54710ac..0533b651e0de205c3584dd754eef050347d43ff8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2024 Thomas Wolf <twolf@apache.org> 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
@@ -147,17 +147,59 @@ public final class SshdText extends TranslationBundle {
        /***/ public String sshCommandTimeout;
        /***/ public String sshProcessStillRunning;
        /***/ public String sshProxySessionCloseFailed;
+       /***/ public String signAllowedSignersCertAuthorityError;
+       /***/ public String signAllowedSignersEmptyIdentity;
+       /***/ public String signAllowedSignersEmptyNamespaces;
+       /***/ public String signAllowedSignersFormatError;
+       /***/ public String signAllowedSignersInvalidDate;
+       /***/ public String signAllowedSignersLineFormat;
+       /***/ public String signAllowedSignersMultiple;
+       /***/ public String signAllowedSignersNoIdentities;
+       /***/ public String signAllowedSignersPublicKeyParsing;
+       /***/ public String signAllowedSignersUnterminatedQuote;
        /***/ public String signCertAlgorithmMismatch;
        /***/ public String signCertAlgorithmUnknown;
        /***/ public String signCertificateExpired;
        /***/ public String signCertificateInvalid;
+       /***/ public String signCertificateNotForName;
+       /***/ public String signCertificateRevoked;
        /***/ public String signCertificateTooEarly;
+       /***/ public String signCertificateWithoutPrincipals;
        /***/ public String signDefaultKeyEmpty;
        /***/ public String signDefaultKeyFailed;
        /***/ public String signDefaultKeyInterrupted;
+       /***/ public String signGarbageAtEnd;
+       /***/ public String signInvalidAlgorithm;
        /***/ public String signInvalidKeyDSA;
+       /***/ public String signInvalidMagic;
+       /***/ public String signInvalidNamespace;
+       /***/ public String signInvalidSignature;
+       /***/ public String signInvalidVersion;
+       /***/ public String signKeyExpired;
+       /***/ public String signKeyRevoked;
+       /***/ public String signKeyTooEarly;
+       /***/ public String signKrlBlobLeftover;
+       /***/ public String signKrlBlobLengthInvalid;
+       /***/ public String signKrlBlobLengthInvalidExpected;
+       /***/ public String signKrlCaKeyLengthInvalid;
+       /***/ public String signKrlCertificateLeftover;
+       /***/ public String signKrlCertificateSubsectionLeftover;
+       /***/ public String signKrlCertificateSubsectionLength;
+       /***/ public String signKrlEmptyRange;
+       /***/ public String signKrlInvalidBitSetLength;
+       /***/ public String signKrlInvalidKeyIdLength;
+       /***/ public String signKrlInvalidMagic;
+       /***/ public String signKrlInvalidReservedLength;
+       /***/ public String signKrlInvalidVersion;
+       /***/ public String signKrlNoCertificateSubsection;
+       /***/ public String signKrlSerialZero;
+       /***/ public String signKrlShortRange;
+       /***/ public String signKrlUnknownSection;
+       /***/ public String signKrlUnknownSubsection;
        /***/ public String signLogFailure;
+       /***/ public String signMismatchedSignatureAlgorithm;
        /***/ public String signNoAgent;
+       /***/ public String signNoPrincipalMatched;
        /***/ public String signNoPublicKey;
        /***/ public String signNoSigningKey;
        /***/ public String signNotUserCertificate;
@@ -169,6 +211,7 @@ public final class SshdText extends TranslationBundle {
        /***/ public String signTooManyPublicKeys;
        /***/ public String signUnknownHashAlgorithm;
        /***/ public String signUnknownSignatureAlgorithm;
+       /***/ public String signWrongNamespace;
        /***/ public String unknownProxyProtocol;
 
 }
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java
new file mode 100644 (file)
index 0000000..4d2d8b6
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.signing.ssh;
+
+/**
+ * A {@link SigningKeyDatabase} that caches data.
+ * <p>
+ * A signing key database may be used to check keys frequently; it may thus need
+ * to cache some data and it may need to cache data per repository. If an
+ * implementation does cache data, it is responsible itself for refreshing that
+ * cache at appropriate times. Clients can control the cache size somewhat via
+ * {@link #setCacheSize(int)}, although the meaning of the cache size (i.e., its
+ * unit) is left undefined here.
+ * </p>
+ *
+ * @since 7.1
+ */
+public interface CachingSigningKeyDatabase extends SigningKeyDatabase {
+
+       /**
+        * Retrieves the current cache size.
+        *
+        * @return the cache size, or -1 if this database has no cache.
+        */
+       int getCacheSize();
+
+       /**
+        * Sets the cache size to use.
+        *
+        * @param size
+        *            the cache size, ignored if this database does not have a
+        *            cache.
+        * @throws IllegalArgumentException
+        *             if {@code size < 0}
+        */
+       void setCacheSize(int size);
+
+       /**
+        * Discards any cached data. A no-op if the database has no cache.
+        */
+       void clearCache();
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java
new file mode 100644 (file)
index 0000000..eec64c3
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.signing.ssh;
+
+import java.io.IOException;
+import java.security.PublicKey;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.signing.ssh.SigningDatabase;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * A database storing meta-information about signing keys and certificates.
+ *
+ * @since 7.1
+ */
+public interface SigningKeyDatabase {
+
+       /**
+        * Obtains the current global instance.
+        *
+        * @return the global {@link SigningKeyDatabase}
+        */
+       static SigningKeyDatabase getInstance() {
+               return SigningDatabase.getInstance();
+       }
+
+       /**
+        * Sets the global {@link SigningKeyDatabase}.
+        *
+        * @param database
+        *            to set; if {@code null} a default database using the OpenSSH
+        *            allowed signers file and the OpenSSH revocation list mechanism
+        *            is used.
+        * @return the previously set {@link SigningKeyDatabase}
+        */
+       static SigningKeyDatabase setInstance(SigningKeyDatabase database) {
+               return SigningDatabase.setInstance(database);
+       }
+
+       /**
+        * Determines whether the gives key has been revoked.
+        *
+        * @param repository
+        *            {@link Repository} the key is being used in
+        * @param config
+        *            {@link GpgConfig} to use
+        * @param key
+        *            {@link PublicKey} to check
+        * @return {@code true} if the key has been revoked, {@code false} otherwise
+        * @throws IOException
+        *             if an I/O problem occurred
+        */
+       boolean isRevoked(@NonNull Repository repository, @NonNull GpgConfig config,
+                       @NonNull PublicKey key) throws IOException;
+
+       /**
+        * Checks whether the given key is allowed to be used for signing, and if
+        * allowed returns the principal.
+        *
+        * @param repository
+        *            {@link Repository} the key is being used in
+        * @param config
+        *            {@link GpgConfig} to use
+        * @param key
+        *            {@link PublicKey} to check
+        * @param namespace
+        *            of the signature
+        * @param ident
+        *            optional {@link PersonIdent} giving a signer's e-mail address
+        *            and a signature time
+        * @return {@code null} if the database does not contain any information
+        *         about the given key; the principal if it does and all checks
+        *         passed
+        * @throws IOException
+        *             if an I/O problem occurred
+        * @throws VerificationException
+        *             if the database contains information about the key and the
+        *             checks determined that the key is not allowed to be used for
+        *             signing
+        */
+       String isAllowed(@NonNull Repository repository, @NonNull GpgConfig config,
+                       @NonNull PublicKey key, @NonNull String namespace,
+                       PersonIdent ident) throws IOException, VerificationException;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java
new file mode 100644 (file)
index 0000000..c315428
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.signing.ssh;
+
+import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.internal.signing.ssh.SshSignatureVerifier;
+import org.eclipse.jgit.lib.SignatureVerifierFactory;
+
+/**
+ * Factory creating {@link SshSignatureVerifier}s.
+ *
+ * @since 7.1
+ */
+public final class SshSignatureVerifierFactory
+               implements SignatureVerifierFactory {
+
+       @Override
+       public GpgFormat getType() {
+               return GpgFormat.SSH;
+       }
+
+       @Override
+       public SignatureVerifier create() {
+               return new SshSignatureVerifier();
+       }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java
new file mode 100644 (file)
index 0000000..cd77111
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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.signing.ssh;
+
+/**
+ * An exception giving details about a failed
+ * {@link SigningKeyDatabase#isAllowed(org.eclipse.jgit.lib.Repository, org.eclipse.jgit.lib.GpgConfig, java.security.PublicKey, String, org.eclipse.jgit.lib.PersonIdent)}
+ * validation.
+ *
+ * @since 7.1
+ */
+public class VerificationException extends Exception {
+
+       private static final long serialVersionUID = 313760495170326160L;
+
+       private final boolean expired;
+
+       private final String reason;
+
+       /**
+        * Creates a new instance.
+        *
+        * @param expired
+        *            whether the checked public key or certificate was expired
+        * @param reason
+        *            describing the check failure
+        */
+       public VerificationException(boolean expired, String reason) {
+               this.expired = expired;
+               this.reason = reason;
+       }
+
+       @Override
+       public String getMessage() {
+               return reason;
+       }
+
+       /**
+        * Tells whether the check failed because the public key was expired.
+        *
+        * @return {@code true} if the check failed because the public key was
+        *         expired, {@code false} otherwise
+        */
+       public boolean isExpired() {
+               return expired;
+       }
+
+       /**
+        * Retrieves the check failure reason.
+        *
+        * @return the reason description
+        */
+       public String getReason() {
+               return reason;
+       }
+}