Skip to content

Commit 1c8275b

Browse files
committed
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.
1 parent 1932e0f commit 1c8275b

1 file changed

Lines changed: 27 additions & 23 deletions

File tree

src/main/java/com/trilead/ssh2/crypto/dh/PlatformX25519Provider.java

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.trilead.ssh2.crypto.dh;
22

3-
import java.math.BigInteger;
43
import java.security.InvalidKeyException;
54
import java.security.KeyFactory;
65
import java.security.KeyPair;
@@ -9,19 +8,18 @@
98
import java.security.PrivateKey;
109
import java.security.PublicKey;
1110
import java.security.spec.InvalidKeySpecException;
12-
import java.security.spec.NamedParameterSpec;
1311
import java.security.spec.PKCS8EncodedKeySpec;
12+
import java.security.spec.X509EncodedKeySpec;
1413
import java.security.spec.XECPrivateKeySpec;
15-
import java.security.spec.XECPublicKeySpec;
1614

1715
import javax.crypto.KeyAgreement;
1816

1917
/**
20-
* X25519 provider implementation using platform-native APIs (Java 11+/Android API 33+).
18+
* X25519 provider implementation using platform-native APIs (Java 11+/Android
19+
* API 33+).
2120
*/
2221
public class PlatformX25519Provider implements X25519Provider {
2322
private static final String ALGORITHM = "X25519";
24-
private static final NamedParameterSpec X25519_SPEC = new NamedParameterSpec(ALGORITHM);
2523

2624
private final KeyPairGenerator keyPairGenerator;
2725
private final KeyFactory keyFactory;
@@ -48,18 +46,22 @@ public byte[] publicFromPrivate(byte[] privateKey) throws InvalidKeyException {
4846
return pubKeyBytes;
4947
}
5048

49+
private static final byte[] BASE_POINT = new byte[KEY_SIZE];
50+
static {
51+
BASE_POINT[0] = 9;
52+
}
53+
5154
private void computePublicFromPrivate(byte[] privateKey, byte[] publicKey) throws InvalidKeyException {
5255
try {
5356
PrivateKey privKey = createPrivateKey(privateKey);
5457
KeyAgreement ka = KeyAgreement.getInstance(ALGORITHM);
5558
ka.init(privKey);
5659

57-
XECPublicKeySpec basePointSpec = new XECPublicKeySpec(X25519_SPEC, BigInteger.valueOf(9));
58-
PublicKey basePoint = keyFactory.generatePublic(basePointSpec);
60+
PublicKey basePoint = createPublicKey(BASE_POINT);
5961
ka.doPhase(basePoint, true);
6062
byte[] result = ka.generateSecret();
6163
System.arraycopy(result, 0, publicKey, 0, KEY_SIZE);
62-
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
64+
} catch (NoSuchAlgorithmException e) {
6365
throw new InvalidKeyException("X25519 not available", e);
6466
}
6567
}
@@ -81,12 +83,19 @@ public byte[] computeSharedSecret(byte[] privateKey, byte[] publicKey) throws In
8183
}
8284

8385
private static final byte[] PKCS8_PREFIX = {
84-
0x30, 0x2e, // SEQUENCE (46 bytes)
85-
0x02, 0x01, 0x00, // INTEGER 0 (version)
86-
0x30, 0x05, // SEQUENCE (5 bytes)
87-
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
88-
0x04, 0x22, // OCTET STRING (34 bytes)
89-
0x04, 0x20 // OCTET STRING (32 bytes) - key follows
86+
0x30, 0x2e, // SEQUENCE (46 bytes)
87+
0x02, 0x01, 0x00, // INTEGER 0 (version)
88+
0x30, 0x05, // SEQUENCE (5 bytes)
89+
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
90+
0x04, 0x22, // OCTET STRING (34 bytes)
91+
0x04, 0x20 // OCTET STRING (32 bytes) - key follows
92+
};
93+
94+
private static final byte[] X509_PREFIX = {
95+
0x30, 0x2a, // SEQUENCE (42 bytes)
96+
0x30, 0x05, // SEQUENCE (5 bytes)
97+
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
98+
0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits)
9099
};
91100

92101
private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException {
@@ -103,8 +112,10 @@ private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException
103112

104113
private PublicKey createPublicKey(byte[] keyBytes) throws InvalidKeyException {
105114
try {
106-
BigInteger u = decodeLittleEndian(keyBytes);
107-
XECPublicKeySpec spec = new XECPublicKeySpec(X25519_SPEC, u);
115+
byte[] x509 = new byte[X509_PREFIX.length + KEY_SIZE];
116+
System.arraycopy(X509_PREFIX, 0, x509, 0, X509_PREFIX.length);
117+
System.arraycopy(keyBytes, 0, x509, X509_PREFIX.length, KEY_SIZE);
118+
X509EncodedKeySpec spec = new X509EncodedKeySpec(x509);
108119
return keyFactory.generatePublic(spec);
109120
} catch (InvalidKeySpecException e) {
110121
throw new InvalidKeyException("Invalid public key", e);
@@ -129,11 +140,4 @@ private byte[] extractPrivateKeyBytes(PrivateKey privateKey) {
129140
}
130141
}
131142

132-
private static BigInteger decodeLittleEndian(byte[] bytes) {
133-
byte[] reversed = new byte[bytes.length];
134-
for (int i = 0; i < bytes.length; i++) {
135-
reversed[i] = bytes[bytes.length - 1 - i];
136-
}
137-
return new BigInteger(1, reversed);
138-
}
139143
}

0 commit comments

Comments
 (0)