diff options
6 files changed, 167 insertions, 26 deletions
diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.asc new file mode 100644 index 0000000000..ee8207ec41 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.asc @@ -0,0 +1,9 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZet5vRYJKwYBBAHaRw8BAQdA1lUwXTD4ia1i4+ckhPr0O0a9aQAarg6U8prB +6H85XJG0GFRlc3RlciA8dGVzdGVyQHRlc3QuY29tPoiQBBMWCgA4FiEEwLQ/UWQ8 +ydO7u8ea7hn0dWq7fbwFAmXreb0CGwEFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA +CgkQ7hn0dWq7fbxhPwEA3a0COi4sV7Uxd91H9P5DXJA2XzYtyvYsxZJEICFZPo8B +AO6fF9Ii5ATO5USSMf6bNCevcBlDFBNXIO+pwjemrBYJ +=LYEV +-----END PGP PUBLIC KEY BLOCK----- diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.key new file mode 100644 index 0000000000..03ed01cb0b --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/62D43D7F117F7A5E4998ECB6617EE9942D069C14.key @@ -0,0 +1,7 @@ +Key: (protected-private-key (ecc (curve Ed25519)(flags eddsa)(q + #40D655305D30F889AD62E3E72484FAF43B46BD69001AAE0E94F29AC1E87F395C91#) + (protected openpgp-s2k3-ocb-aes ((sha1 #EBA45EE5104E7ED6# + "24672256")#36CB86BDBEA4947789F555B6#)#2D3CBB52F66DED8E0E7B0A1FEE9732 + 4FC1624B32069CD1ED507877E26B3099E62C2AC615DA7E8DAAD335EC613AD2CD9B89C4 + D1BCDEADEA3C67785428#)(protected-at "20240308T204911"))) +Created: 20240308T204901 diff --git a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java index 5e5e303319..fed06103b6 100644 --- a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java +++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java @@ -97,6 +97,7 @@ public class SecretKeysTest { new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true), new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true), new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true), + new TestData("62D43D7F117F7A5E4998ECB6617EE9942D069C14", true, true), new TestData("faked", false, true) }; } @@ -152,7 +153,7 @@ public class SecretKeysTest { assertNotNull(secretKey); } catch (PGPException e) { // Currently we may not be able to load OCB-encrypted keys. - assertTrue(e.getMessage().contains("OCB")); + assertTrue(e.toString(), e.getMessage().contains("OCB")); assertTrue(data.encrypted); assertFalse(ocbAvailable()); } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java index 3eee18aef5..9b5d592fa8 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java @@ -43,17 +43,6 @@ import org.eclipse.jgit.util.sha1.SHA1; */ public final class KeyGrip { - // Some OIDs apparently unknown to BouncyCastle. - - private static String OID_OPENPGP_ED25519 = "1.3.6.1.4.1.11591.15.1"; //$NON-NLS-1$ - - private static String OID_RFC8410_CURVE25519 = "1.3.101.110"; //$NON-NLS-1$ - - private static String OID_RFC8410_ED25519 = "1.3.101.112"; //$NON-NLS-1$ - - private static ASN1ObjectIdentifier CURVE25519 = ECNamedCurveTable - .getOID("curve25519"); //$NON-NLS-1$ - private KeyGrip() { // No instantiation } @@ -99,20 +88,15 @@ public final class KeyGrip { break; case PublicKeyAlgorithmTags.ECDH: case PublicKeyAlgorithmTags.ECDSA: - case PublicKeyAlgorithmTags.EDDSA: + case PublicKeyAlgorithmTags.EDDSA_LEGACY: + case PublicKeyAlgorithmTags.Ed25519: ECPublicBCPGKey ec = (ECPublicBCPGKey) publicKey .getPublicKeyPacket().getKey(); ASN1ObjectIdentifier curveOID = ec.getCurveOID(); // BC doesn't know these OIDs. - if (OID_OPENPGP_ED25519.equals(curveOID.getId()) - || OID_RFC8410_ED25519.equals(curveOID.getId())) { + if (ObjectIds.isEd25519(curveOID)) { return hashEd25519(grip, ec.getEncodedPoint()); - } else if (CURVE25519.equals(curveOID) - || OID_RFC8410_CURVE25519.equals(curveOID.getId())) { - // curvey25519 actually is the OpenPGP OID for Curve25519 and is - // known to BC, but the parameters are for the short Weierstrass - // form. See https://github.com/bcgit/bc-java/issues/399 . - // libgcrypt uses Montgomery form. + } else if (ObjectIds.isCurve25519(curveOID)) { return hashCurve25519(grip, ec.getEncodedPoint()); } X9ECParameters params = getX9Parameters(curveOID); @@ -141,7 +125,9 @@ public final class KeyGrip { hash(grip, b.toByteArray(), 'b', false); hash(grip, g, 'g', false); hash(grip, n.toByteArray(), 'n', false); - if (publicKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) { + int algorithm = publicKey.getAlgorithm(); + if (algorithm == PublicKeyAlgorithmTags.EDDSA_LEGACY + || algorithm == PublicKeyAlgorithmTags.Ed25519) { hashQ25519(grip, q); } else { hash(grip, q.toByteArray(), 'q', false); diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/ObjectIds.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/ObjectIds.java new file mode 100644 index 0000000000..3d3098158a --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/ObjectIds.java @@ -0,0 +1,109 @@ +/* + * 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.gpg.bc.internal.keys; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; + +/** + * Some OIDs apparently unknown to Bouncy Castle. + */ +public final class ObjectIds { + + /** + * Legacy OID for ed25519 used in OpenPGP. + */ + public static final ASN1ObjectIdentifier OID_OPENPGP_ED25519 = + new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.15.1"); //$NON-NLS-1$ + + /** + * OID for ed25519 according to RFC 8410. + */ + public static final ASN1ObjectIdentifier OID_RFC8410_ED25519 = + new ASN1ObjectIdentifier("1.3.101.112"); //$NON-NLS-1$ + + /** + * Legacy OID for curve25519 used in OpenPGP. + */ + public static final ASN1ObjectIdentifier OID_OPENPGP_CURVE25519 = + ECNamedCurveTable.getOID("curve25519"); //$NON-NLS-1$ + // This is 1.3.6.1.4.1.3029.1.5.1 + + /** + * OID for curve25519 according to RFC 8410. + */ + public static final ASN1ObjectIdentifier OID_RFC8410_CURVE25519 = new ASN1ObjectIdentifier( + "1.3.101.110"); //$NON-NLS-1$ + + /** + * Determines whether the given {@code oid} denoted ed25519. + * + * @param oid + * to test + * @return {@code true} if it is ed25519, {@code false} otherwise + */ + public static boolean isEd25519(ASN1ObjectIdentifier oid) { + return OID_OPENPGP_ED25519.equals(oid) + || OID_RFC8410_ED25519.equals(oid); + } + + /** + * Determines whether the given {@code oid} denoted curve25519. + * + * @param oid + * to test + * @return {@code true} if it is curve25519, {@code false} otherwise + */ + public static boolean isCurve25519(ASN1ObjectIdentifier oid) { + return OID_RFC8410_CURVE25519.equals(oid) + || OID_OPENPGP_CURVE25519.equals(oid); + } + + /** + * Retrieves an OID by name. + * + * @param name + * to get the OID for + * @return the OID, or {@code null} if unknown. + */ + public static ASN1ObjectIdentifier getByName(String name) { + if (name != null) { + switch (name) { + case "Ed25519": //$NON-NLS-1$ + return OID_OPENPGP_ED25519; + case "Curve25519": //$NON-NLS-1$ + return OID_OPENPGP_CURVE25519; + default: + break; + } + } + return null; + } + + /** + * Checks whether two OIDs denote the same algorithm. + * + * @param oid1 + * First OID to test + * @param oid2 + * Second OID to test + * @return {@code true} if the two OIDs match, {@code false} otherwise + */ + public static boolean match(ASN1ObjectIdentifier oid1, + ASN1ObjectIdentifier oid2) { + if (isEd25519(oid1)) { + return isEd25519(oid2); + } + if (isCurve25519(oid1)) { + return isCurve25519(oid2); + } + return oid1 != null && oid1.equals(oid2); + } +} diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java index c93c2164c9..fd030ee032 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java @@ -30,6 +30,7 @@ import java.io.OutputStream; import java.math.BigInteger; import java.util.Date; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.bcpg.DSAPublicBCPGKey; import org.bouncycastle.bcpg.DSASecretBCPGKey; @@ -62,9 +63,12 @@ import org.bouncycastle.util.Strings; * modified by the JGit team to: * <ul> * <li>handle unencrypted DSA, EC, and ElGamal keys (upstream only handles - * unencrypted RSA), and</li> + * unencrypted RSA)</li> * <li>handle secret keys using AES/OCB as encryption (those don't have a - * hash).</li> + * hash)</li> + * <li>fix EC parsing to account for "flags" sub-list present for ed25519 and + * curve25519</li> + * <li>add support for ed25519 OIDs unknown to BouncyCastle</li> * </ul> */ @SuppressWarnings("nls") @@ -128,6 +132,15 @@ public class SExprParser { SXprUtils.skipOpenParenthesis(inputStream); type = SXprUtils.readString(inputStream, inputStream.read()); + // JGit: c.f. https://github.com/bcgit/bc-java/issues/1590. + // There may be a flags sub-list here for ed25519 or curve25519. + if (type.equals("flags")) { + SXprUtils.readString(inputStream, inputStream.read()); + SXprUtils.skipCloseParenthesis(inputStream); + SXprUtils.skipOpenParenthesis(inputStream); + type = SXprUtils.readString(inputStream, + inputStream.read()); + } if (type.equals("q")) { qVal = SXprUtils.readBytes(inputStream, inputStream.read()); } else { @@ -143,12 +156,19 @@ public class SExprParser { curveName = curveName.substring("NIST ".length()); } + // JGit: BC doesn't know Ed25519 curve name. + ASN1ObjectIdentifier curveOid = ECNamedCurveTable + .getOID(curveName); + if (curveOid == null) { + curveOid = ObjectIds.getByName(curveName); + } ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey( - ECNamedCurveTable.getOID(curveName), + curveOid, new BigInteger(1, qVal)); ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey .getPublicKeyPacket().getKey(); - if (!basePubKey.getCurveOID().equals(assocPubKey.getCurveOID()) + if (!ObjectIds.match(basePubKey.getCurveOID(), + assocPubKey.getCurveOID()) || !basePubKey.getEncodedPoint() .equals(assocPubKey.getEncodedPoint())) { throw new PGPException( @@ -292,6 +312,15 @@ public class SExprParser { SXprUtils.skipOpenParenthesis(inputStream); type = SXprUtils.readString(inputStream, inputStream.read()); + // JGit: c.f. https://github.com/bcgit/bc-java/issues/1590. + // There may be a flags sub-list here for ed25519 or curve25519. + if (type.equals("flags")) { + SXprUtils.readString(inputStream, inputStream.read()); + SXprUtils.skipCloseParenthesis(inputStream); + SXprUtils.skipOpenParenthesis(inputStream); + type = SXprUtils.readString(inputStream, + inputStream.read()); + } if (type.equals("q")) { qVal = SXprUtils.readBytes(inputStream, inputStream.read()); } else { |