From 11274011f9d89d4cb1bcd46125e901484f677716 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 23 Jan 2026 23:16:06 -0800 Subject: [PATCH] fix(x25519): workaround bug in Conscrypt, pt 2 Conscrypt checks that the KeySpec passed in is extending EncodedKeySpec, but XECPublicKeySpec extends KeySpec directly. To work around that, we can create a X509EncodedKeySpec to allow the X25519 key to be decoded. --- .../crypto/dh/PlatformX25519Provider.java | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/trilead/ssh2/crypto/dh/PlatformX25519Provider.java b/src/main/java/com/trilead/ssh2/crypto/dh/PlatformX25519Provider.java index 9a353d6..638a963 100644 --- a/src/main/java/com/trilead/ssh2/crypto/dh/PlatformX25519Provider.java +++ b/src/main/java/com/trilead/ssh2/crypto/dh/PlatformX25519Provider.java @@ -1,6 +1,5 @@ package com.trilead.ssh2.crypto.dh; -import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; @@ -9,19 +8,18 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; -import java.security.spec.NamedParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.security.spec.XECPrivateKeySpec; -import java.security.spec.XECPublicKeySpec; import javax.crypto.KeyAgreement; /** - * X25519 provider implementation using platform-native APIs (Java 11+/Android API 33+). + * X25519 provider implementation using platform-native APIs (Java 11+/Android + * API 33+). */ public class PlatformX25519Provider implements X25519Provider { private static final String ALGORITHM = "X25519"; - private static final NamedParameterSpec X25519_SPEC = new NamedParameterSpec(ALGORITHM); private final KeyPairGenerator keyPairGenerator; private final KeyFactory keyFactory; @@ -48,18 +46,22 @@ public byte[] publicFromPrivate(byte[] privateKey) throws InvalidKeyException { return pubKeyBytes; } + private static final byte[] BASE_POINT = new byte[KEY_SIZE]; + static { + BASE_POINT[0] = 9; + } + private void computePublicFromPrivate(byte[] privateKey, byte[] publicKey) throws InvalidKeyException { try { PrivateKey privKey = createPrivateKey(privateKey); KeyAgreement ka = KeyAgreement.getInstance(ALGORITHM); ka.init(privKey); - XECPublicKeySpec basePointSpec = new XECPublicKeySpec(X25519_SPEC, BigInteger.valueOf(9)); - PublicKey basePoint = keyFactory.generatePublic(basePointSpec); + PublicKey basePoint = createPublicKey(BASE_POINT); ka.doPhase(basePoint, true); byte[] result = ka.generateSecret(); System.arraycopy(result, 0, publicKey, 0, KEY_SIZE); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + } catch (NoSuchAlgorithmException e) { throw new InvalidKeyException("X25519 not available", e); } } @@ -81,12 +83,19 @@ public byte[] computeSharedSecret(byte[] privateKey, byte[] publicKey) throws In } private static final byte[] PKCS8_PREFIX = { - 0x30, 0x2e, // SEQUENCE (46 bytes) - 0x02, 0x01, 0x00, // INTEGER 0 (version) - 0x30, 0x05, // SEQUENCE (5 bytes) - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) - 0x04, 0x22, // OCTET STRING (34 bytes) - 0x04, 0x20 // OCTET STRING (32 bytes) - key follows + 0x30, 0x2e, // SEQUENCE (46 bytes) + 0x02, 0x01, 0x00, // INTEGER 0 (version) + 0x30, 0x05, // SEQUENCE (5 bytes) + 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) + 0x04, 0x22, // OCTET STRING (34 bytes) + 0x04, 0x20 // OCTET STRING (32 bytes) - key follows + }; + + private static final byte[] X509_PREFIX = { + 0x30, 0x2a, // SEQUENCE (42 bytes) + 0x30, 0x05, // SEQUENCE (5 bytes) + 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) + 0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits) }; private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException { @@ -103,8 +112,10 @@ private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException private PublicKey createPublicKey(byte[] keyBytes) throws InvalidKeyException { try { - BigInteger u = decodeLittleEndian(keyBytes); - XECPublicKeySpec spec = new XECPublicKeySpec(X25519_SPEC, u); + byte[] x509 = new byte[X509_PREFIX.length + KEY_SIZE]; + System.arraycopy(X509_PREFIX, 0, x509, 0, X509_PREFIX.length); + System.arraycopy(keyBytes, 0, x509, X509_PREFIX.length, KEY_SIZE); + X509EncodedKeySpec spec = new X509EncodedKeySpec(x509); return keyFactory.generatePublic(spec); } catch (InvalidKeySpecException e) { throw new InvalidKeyException("Invalid public key", e); @@ -129,11 +140,4 @@ private byte[] extractPrivateKeyBytes(PrivateKey privateKey) { } } - private static BigInteger decodeLittleEndian(byte[] bytes) { - byte[] reversed = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - reversed[i] = bytes[bytes.length - 1 - i]; - } - return new BigInteger(1, reversed); - } }