Skip to content

Commit 0e1bae5

Browse files
Add XWING keys and support them in HPKE. (#1383)
* Add XWING keys and support them in HPKE. * Fix formating. * Fix typo. * Add X-Wing to HPKE suite. * Add missing import. * Add missing entry. * fix format. * Fix typo. * Add serialVersionUID to keys. * Make two helper functions private. * Move dispatch of key serialization into subclasses of HPKEImpl. * Fix formatting. * Use constant-time compare in Xwing private key. * Fix order in compare with constant string. --------- Co-authored-by: miguelaranda0 <90468342+miguelaranda0@users.noreply.github.com>
1 parent e4d3231 commit 0e1bae5

11 files changed

Lines changed: 691 additions & 20 deletions

File tree

common/src/main/java/org/conscrypt/HpkeImpl.java

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305;
2121
import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256;
2222
import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256;
23+
import static org.conscrypt.HpkeSuite.KEM_XWING;
2324

2425
import java.security.GeneralSecurityException;
2526
import java.security.InvalidKeyException;
@@ -35,7 +36,7 @@
3536
* of the subclasses of {@link HpkeContext}.
3637
*/
3738
@Internal
38-
public class HpkeImpl implements HpkeSpi {
39+
public abstract class HpkeImpl implements HpkeSpi {
3940
private final HpkeSuite hpkeSuite;
4041

4142
private NativeRef.EVP_HPKE_CTX ctx;
@@ -45,19 +46,17 @@ public HpkeImpl(HpkeSuite hpkeSuite) {
4546
this.hpkeSuite = hpkeSuite;
4647
}
4748

49+
abstract byte[] getRecipientPublicKeyBytes(PublicKey recipientKey) throws InvalidKeyException;
50+
4851
@Override
4952
public void engineInitSender(PublicKey recipientKey, byte[] info, PrivateKey senderKey,
5053
byte[] psk, byte[] psk_id) throws InvalidKeyException {
5154
checkNotInitialised();
5255
checkArgumentsForBaseModeOnly(senderKey, psk, psk_id);
5356
if (recipientKey == null) {
5457
throw new InvalidKeyException("null recipient key");
55-
} else if (!(recipientKey instanceof OpenSSLX25519PublicKey)) {
56-
throw new InvalidKeyException(
57-
"Unsupported recipient key class: " + recipientKey.getClass());
5858
}
59-
final byte[] recipientKeyBytes = ((OpenSSLX25519PublicKey) recipientKey).getU();
60-
59+
final byte[] recipientKeyBytes = getRecipientPublicKeyBytes(recipientKey);
6160
final Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender(
6261
hpkeSuite, recipientKeyBytes, info);
6362
ctx = (NativeRef.EVP_HPKE_CTX) result[0];
@@ -73,19 +72,17 @@ public void engineInitSenderForTesting(PublicKey recipientKey, byte[] info,
7372
checkArgumentsForBaseModeOnly(senderKey, psk, psk_id);
7473
if (recipientKey == null) {
7574
throw new InvalidKeyException("null recipient key");
76-
} else if (!(recipientKey instanceof OpenSSLX25519PublicKey)) {
77-
throw new InvalidKeyException(
78-
"Unsupported recipient key class: " + recipientKey.getClass());
7975
}
80-
final byte[] recipientKeyBytes = ((OpenSSLX25519PublicKey) recipientKey).getU();
81-
76+
final byte[] recipientKeyBytes = getRecipientPublicKeyBytes(recipientKey);
8277
final Object[] result =
8378
NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing(
8479
hpkeSuite, recipientKeyBytes, info, sKe);
8580
ctx = (NativeRef.EVP_HPKE_CTX) result[0];
8681
encapsulated = (byte[]) result[1];
8782
}
8883

84+
abstract byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException;
85+
8986
@Override
9087
public void engineInitRecipient(byte[] encapsulated, PrivateKey recipientKey, byte[] info,
9188
PublicKey senderKey, byte[] psk, byte[] psk_id) throws InvalidKeyException {
@@ -98,12 +95,8 @@ public void engineInitRecipient(byte[] encapsulated, PrivateKey recipientKey, by
9895

9996
if (recipientKey == null) {
10097
throw new InvalidKeyException("null recipient key");
101-
} else if (!(recipientKey instanceof OpenSSLX25519PrivateKey)) {
102-
throw new InvalidKeyException(
103-
"Unsupported recipient key class: " + recipientKey.getClass());
10498
}
105-
final byte[] recipientKeyBytes = ((OpenSSLX25519PrivateKey) recipientKey).getU();
106-
99+
final byte[] recipientKeyBytes = getPrivateRecipientKeyBytes(recipientKey);
107100
ctx = (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient(
108101
hpkeSuite, recipientKeyBytes, encapsulated, info);
109102
}
@@ -181,22 +174,94 @@ public byte[] getEncapsulated() {
181174
return encapsulated;
182175
}
183176

184-
public static class X25519_AES_128 extends HpkeImpl {
177+
private static class HpkeX25519Impl extends HpkeImpl {
178+
private HpkeX25519Impl(HpkeSuite hpkeSuite) {
179+
super(hpkeSuite);
180+
}
181+
182+
@Override
183+
byte[] getRecipientPublicKeyBytes(PublicKey recipientKey) throws InvalidKeyException {
184+
if (!(recipientKey instanceof OpenSSLX25519PublicKey)) {
185+
throw new InvalidKeyException(
186+
"Unsupported recipient key class: " + recipientKey.getClass());
187+
}
188+
return ((OpenSSLX25519PublicKey) recipientKey).getU();
189+
}
190+
191+
@Override
192+
byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException {
193+
if (!(recipientKey instanceof OpenSSLX25519PrivateKey)) {
194+
throw new InvalidKeyException(
195+
"Unsupported recipient private key class: " + recipientKey.getClass());
196+
}
197+
return ((OpenSSLX25519PrivateKey) recipientKey).getU();
198+
}
199+
}
200+
201+
/** Implementation of X25519/HKDF_SHA256/AES_128_GCM. */
202+
public static class X25519_AES_128 extends HpkeX25519Impl {
185203
public X25519_AES_128() {
186204
super(new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM));
187205
}
188206
}
189207

190-
public static class X25519_AES_256 extends HpkeImpl {
208+
/** Implementation of X25519/HKDF_SHA256/AES_256_GCM. */
209+
public static class X25519_AES_256 extends HpkeX25519Impl {
191210
public X25519_AES_256() {
192211
super(new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_256_GCM));
193212
}
194213
}
195214

196-
public static class X25519_CHACHA20 extends HpkeImpl {
215+
/** Implementation of X25519/HKDF_SHA256/CHACHA20_POLY1305. */
216+
public static class X25519_CHACHA20 extends HpkeX25519Impl {
197217
public X25519_CHACHA20() {
198218
super(new HpkeSuite(
199219
KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305));
200220
}
201221
}
222+
223+
private static class HpkeXwingImpl extends HpkeImpl {
224+
HpkeXwingImpl(HpkeSuite hpkeSuite) {
225+
super(hpkeSuite);
226+
}
227+
228+
@Override
229+
byte[] getRecipientPublicKeyBytes(PublicKey publicKey) throws InvalidKeyException {
230+
if (!(publicKey instanceof OpenSslXwingPublicKey)) {
231+
throw new InvalidKeyException(
232+
"Unsupported recipient key class: " + publicKey.getClass());
233+
}
234+
return ((OpenSslXwingPublicKey) publicKey).getRaw();
235+
}
236+
237+
@Override
238+
byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException {
239+
if (!(recipientKey instanceof OpenSslXwingPrivateKey)) {
240+
throw new InvalidKeyException(
241+
"Unsupported recipient private key class: " + recipientKey.getClass());
242+
}
243+
return ((OpenSslXwingPrivateKey) recipientKey).getRaw();
244+
}
245+
}
246+
247+
/** Implementation of XWING/HKDF_SHA256/AES_128_GCM. */
248+
public static class XwingHkdfSha256Aes128Gcm extends HpkeXwingImpl {
249+
public XwingHkdfSha256Aes128Gcm() {
250+
super(new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_AES_128_GCM));
251+
}
252+
}
253+
254+
/** Implementation of XWING/HKDF_SHA256/AES_256_GCM. */
255+
public static class XwingHkdfSha256Aes256Gcm extends HpkeXwingImpl {
256+
public XwingHkdfSha256Aes256Gcm() {
257+
super(new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_AES_256_GCM));
258+
}
259+
}
260+
261+
/** Implementation of XWING/HKDF_SHA256/CHACHA20_POLY1305. */
262+
public static class XwingHkdfSha256ChaCha20Poly1305 extends HpkeXwingImpl {
263+
public XwingHkdfSha256ChaCha20Poly1305() {
264+
super(new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305));
265+
}
266+
}
202267
}

common/src/main/java/org/conscrypt/HpkeSuite.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public final class HpkeSuite {
3535
*/
3636
public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x0020;
3737

38+
/**
39+
* KEM: 0x647a X-Wing
40+
*/
41+
public static final int KEM_XWING = 0x647a;
42+
3843
/**
3944
* KDF: 0x0001 HKDF-SHA256
4045
*/
@@ -135,10 +140,13 @@ public AEAD convertAead(int aead) {
135140
*
136141
* @see <a href="https://www.rfc-editor.org/rfc/rfc9180.html#name-key-encapsulation-mechanism">
137142
* rfc9180 </a>
143+
* @see <a href="https://www.iana.org/assignments/hpke/hpke.xhtml">IANA HPKE</a>
138144
*/
139145
public enum KEM {
140146
DHKEM_X25519_HKDF_SHA256(
141-
/* id= */ 0x20, /* nSecret= */ 32, /* nEnc= */ 32, /* nPk= */ 32, /* nSk= */ 32);
147+
/* id= */ 0x20, /* nSecret= */ 32, /* nEnc= */ 32, /* nPk= */ 32, /* nSk= */ 32),
148+
XWING(/* id= */ 0x647a, /* nSecret= */ 32, /* nEnc= */ 1120, /* nPk= */ 1216,
149+
/* nSk= */ 32);
142150

143151
private final int id;
144152
private final int nSecret;

common/src/main/java/org/conscrypt/OpenSSLProvider.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ public OpenSSLProvider(String providerName) {
223223
// We don't support SLH-DSA, because it's not clear which algorithm to use.
224224
put("KeyPairGenerator.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyPairGenerator");
225225

226+
put("KeyPairGenerator.XWING", PREFIX + "OpenSslXwingKeyPairGenerator");
227+
226228
/* == KeyFactory == */
227229
put("KeyFactory.RSA", PREFIX + "OpenSSLRSAKeyFactory");
228230
put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
@@ -248,6 +250,8 @@ public OpenSSLProvider(String providerName) {
248250
// We don't support SLH-DSA, because it's not clear which algorithm to use.
249251
put("KeyFactory.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyFactory");
250252

253+
put("KeyFactory.XWING", PREFIX + "OpenSslXwingKeyFactory");
254+
251255
/* == SecretKeyFactory == */
252256
put("SecretKeyFactory.DESEDE", PREFIX + "DESEDESecretKeyFactory");
253257
put("Alg.Alias.SecretKeyFactory.TDEA", "DESEDE");
@@ -580,6 +584,10 @@ public OpenSSLProvider(String providerName) {
580584
baseClass + "$X25519_CHACHA20");
581585
put("Alg.Alias.ConscryptHpke.DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_GhpkeCHACHA20POLY1305",
582586
"DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305");
587+
put("ConscryptHpke.XWING/HKDF_SHA256/AES_128_GCM", baseClass + "$XwingHkdfSha256Aes128Gcm");
588+
put("ConscryptHpke.XWING/HKDF_SHA256/AES_256_GCM", baseClass + "$XwingHkdfSha256Aes256Gcm");
589+
put("ConscryptHpke.XWING/HKDF_SHA256/CHACHA20POLY1305",
590+
baseClass + "$XwingHkdfSha256ChaCha20Poly1305");
583591

584592
/* === PAKE === */
585593
if (Platform.isPakeSupported()) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.conscrypt;
18+
19+
import java.security.InvalidKeyException;
20+
import java.security.Key;
21+
import java.security.KeyFactorySpi;
22+
import java.security.PrivateKey;
23+
import java.security.PublicKey;
24+
import java.security.spec.EncodedKeySpec;
25+
import java.security.spec.InvalidKeySpecException;
26+
import java.security.spec.KeySpec;
27+
import java.security.spec.PKCS8EncodedKeySpec;
28+
import java.security.spec.X509EncodedKeySpec;
29+
30+
/** An implementation of a {@link KeyFactorySpi} for XWING keys based on BoringSSL. */
31+
@Internal
32+
public final class OpenSslXwingKeyFactory extends KeyFactorySpi {
33+
public OpenSslXwingKeyFactory() {}
34+
35+
@Override
36+
protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
37+
if (keySpec == null) {
38+
throw new InvalidKeySpecException("keySpec == null");
39+
}
40+
if (keySpec instanceof EncodedKeySpec) {
41+
return new OpenSslXwingPublicKey((EncodedKeySpec) keySpec);
42+
}
43+
throw new InvalidKeySpecException(
44+
"Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName());
45+
}
46+
47+
@Override
48+
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
49+
if (keySpec == null) {
50+
throw new InvalidKeySpecException("keySpec == null");
51+
}
52+
if (keySpec instanceof EncodedKeySpec) {
53+
return new OpenSslXwingPrivateKey((EncodedKeySpec) keySpec);
54+
}
55+
throw new InvalidKeySpecException(
56+
"Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName());
57+
}
58+
59+
@Override
60+
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
61+
throws InvalidKeySpecException {
62+
if (key == null) {
63+
throw new InvalidKeySpecException("key == null");
64+
}
65+
if (keySpec == null) {
66+
throw new InvalidKeySpecException("keySpec == null");
67+
}
68+
if (key instanceof OpenSslXwingPublicKey) {
69+
OpenSslXwingPublicKey conscryptKey = (OpenSslXwingPublicKey) key;
70+
if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
71+
throw new UnsupportedOperationException(
72+
"X509EncodedKeySpec is currently not supported");
73+
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) {
74+
return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec);
75+
}
76+
} else if (key instanceof OpenSslXwingPrivateKey) {
77+
OpenSslXwingPrivateKey conscryptKey = (OpenSslXwingPrivateKey) key;
78+
if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
79+
throw new UnsupportedOperationException(
80+
"PKCS8EncodedKeySpec is currently not supported");
81+
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) {
82+
return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec);
83+
}
84+
}
85+
throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
86+
+ key.getClass().getName() + ", keySpec=" + keySpec.getName());
87+
}
88+
89+
@Override
90+
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
91+
if (key == null) {
92+
throw new InvalidKeyException("key == null");
93+
}
94+
if ((key instanceof OpenSslXwingPublicKey) || (key instanceof OpenSslXwingPrivateKey)) {
95+
return key;
96+
}
97+
throw new InvalidKeyException(
98+
"Key must be OpenSslXwingPublicKey or OpenSslXwingPrivateKey");
99+
}
100+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.conscrypt;
18+
19+
import java.security.InvalidParameterException;
20+
import java.security.KeyPair;
21+
import java.security.KeyPairGenerator;
22+
23+
/**
24+
* An implementation of {@link KeyPairGenerator} for XWING keys which uses BoringSSL to perform all
25+
* the operations.
26+
*/
27+
@Internal
28+
public final class OpenSslXwingKeyPairGenerator extends KeyPairGenerator {
29+
public OpenSslXwingKeyPairGenerator() {
30+
super("XWING");
31+
}
32+
33+
@Override
34+
public void initialize(int bits) throws InvalidParameterException {
35+
if (bits != -1) {
36+
throw new InvalidParameterException("XWING only supports -1 for bits");
37+
}
38+
}
39+
40+
@Override
41+
public KeyPair generateKeyPair() {
42+
byte[] privateKeyBytes = new byte[OpenSslXwingPrivateKey.PRIVATE_KEY_SIZE_BYTES];
43+
NativeCrypto.RAND_bytes(privateKeyBytes);
44+
byte[] publicKeyBytes = NativeCrypto.XWING_public_key_from_seed(privateKeyBytes);
45+
return new KeyPair(new OpenSslXwingPublicKey(publicKeyBytes),
46+
new OpenSslXwingPrivateKey(privateKeyBytes));
47+
}
48+
}

0 commit comments

Comments
 (0)