Skip to content

Commit 146c93a

Browse files
committed
TLS: low-level hybrid ECDHE-MLKEM
- see draft-ietf-tls-ecdhe-mlkem-00
1 parent 1ac8c02 commit 146c93a

File tree

13 files changed

+940
-73
lines changed

13 files changed

+940
-73
lines changed

tls/src/main/java/org/bouncycastle/tls/NamedGroup.java

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ public class NamedGroup
116116
public static final int MLKEM768 = 0x0201;
117117
public static final int MLKEM1024 = 0x0202;
118118

119+
/*
120+
* draft-ietf-tls-ecdhe-mlkem-00
121+
*/
122+
public static final int SecP256r1MLKEM768 = 0x11EB;
123+
public static final int X25519MLKEM768 = 0x11EC;
124+
public static final int SecP384r1MLKEM1024 = 0x11ED;
125+
119126
/* Names of the actual underlying elliptic curves (not necessarily matching the NamedGroup names). */
120127
private static final String[] CURVE_NAMES = new String[]{ "sect163k1", "sect163r1", "sect163r2", "sect193r1",
121128
"sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1",
@@ -163,7 +170,7 @@ public static boolean canBeNegotiated(int namedGroup, ProtocolVersion version)
163170
}
164171
}
165172

166-
if (refersToASpecificKem(namedGroup))
173+
if (refersToASpecificHybrid(namedGroup) || refersToASpecificKem(namedGroup))
167174
{
168175
return isTLSv13;
169176
}
@@ -297,6 +304,66 @@ public static String getFiniteFieldName(int namedGroup)
297304
return null;
298305
}
299306

307+
public static int getHybridFirst(int namedGroup)
308+
{
309+
switch (namedGroup)
310+
{
311+
case SecP256r1MLKEM768:
312+
return secp256r1;
313+
case X25519MLKEM768:
314+
return MLKEM768;
315+
case SecP384r1MLKEM1024:
316+
return secp384r1;
317+
default:
318+
return -1;
319+
}
320+
}
321+
322+
public static int getHybridSecond(int namedGroup)
323+
{
324+
switch (namedGroup)
325+
{
326+
case SecP256r1MLKEM768:
327+
return MLKEM768;
328+
case X25519MLKEM768:
329+
return x25519;
330+
case SecP384r1MLKEM1024:
331+
return MLKEM1024;
332+
default:
333+
return -1;
334+
}
335+
}
336+
337+
// TODO Temporary until crypto implementations become more self-documenting around lengths
338+
static int getHybridSplitClientShare(int namedGroup)
339+
{
340+
switch (namedGroup)
341+
{
342+
case secp256r1:
343+
return 65;
344+
case secp384r1:
345+
return 97;
346+
case MLKEM768:
347+
return 1184;
348+
}
349+
return -1;
350+
}
351+
352+
// TODO Temporary until crypto implementations become more self-documenting around lengths
353+
static int getHybridSplitServerShare(int namedGroup)
354+
{
355+
switch (namedGroup)
356+
{
357+
case secp256r1:
358+
return 65;
359+
case secp384r1:
360+
return 97;
361+
case MLKEM768:
362+
return 1088;
363+
}
364+
return -1;
365+
}
366+
300367
public static String getKemName(int namedGroup)
301368
{
302369
switch (namedGroup)
@@ -382,6 +449,12 @@ public static String getName(int namedGroup)
382449
return "MLKEM768";
383450
case MLKEM1024:
384451
return "MLKEM1024";
452+
case SecP256r1MLKEM768:
453+
return "SecP256r1MLKEM768";
454+
case X25519MLKEM768:
455+
return "X25519MLKEM768";
456+
case SecP384r1MLKEM1024:
457+
return "SecP384r1MLKEM1024";
385458
case arbitrary_explicit_prime_curves:
386459
return "arbitrary_explicit_prime_curves";
387460
case arbitrary_explicit_char2_curves:
@@ -489,9 +562,23 @@ public static boolean refersToASpecificGroup(int namedGroup)
489562
{
490563
return refersToASpecificCurve(namedGroup)
491564
|| refersToASpecificFiniteField(namedGroup)
565+
|| refersToASpecificHybrid(namedGroup)
492566
|| refersToASpecificKem(namedGroup);
493567
}
494568

569+
public static boolean refersToASpecificHybrid(int namedGroup)
570+
{
571+
switch (namedGroup)
572+
{
573+
case SecP256r1MLKEM768:
574+
case X25519MLKEM768:
575+
case SecP384r1MLKEM1024:
576+
return true;
577+
default:
578+
return false;
579+
}
580+
}
581+
495582
public static boolean refersToASpecificKem(int namedGroup)
496583
{
497584
switch (namedGroup)

tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -396,21 +396,9 @@ protected ServerHello generate13ServerHello(ClientHello clientHello, HandshakeMe
396396
TlsSecret sharedSecret;
397397
{
398398
int namedGroup = clientShare.getNamedGroup();
399-
400-
TlsAgreement agreement;
401-
if (NamedGroup.refersToAnECDHCurve(namedGroup))
402-
{
403-
agreement = crypto.createECDomain(new TlsECConfig(namedGroup)).createECDH();
404-
}
405-
else if (NamedGroup.refersToASpecificFiniteField(namedGroup))
406-
{
407-
agreement = crypto.createDHDomain(new TlsDHConfig(namedGroup, true)).createDH();
408-
}
409-
else if (NamedGroup.refersToASpecificKem(namedGroup))
410-
{
411-
agreement = crypto.createKemDomain(new TlsKemConfig(namedGroup, true)).createKem();
412-
}
413-
else
399+
400+
TlsAgreement agreement = TlsUtils.createKeyShare(crypto, namedGroup, true);
401+
if (agreement == null)
414402
{
415403
throw new TlsFatalAlert(AlertDescription.internal_error);
416404
}

tls/src/main/java/org/bouncycastle/tls/TlsUtils.java

Lines changed: 103 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.bouncycastle.tls.crypto.TlsEncryptor;
4141
import org.bouncycastle.tls.crypto.TlsHash;
4242
import org.bouncycastle.tls.crypto.TlsHashOutputStream;
43+
import org.bouncycastle.tls.crypto.TlsHybridAgreement;
4344
import org.bouncycastle.tls.crypto.TlsKemConfig;
4445
import org.bouncycastle.tls.crypto.TlsSecret;
4546
import org.bouncycastle.tls.crypto.TlsStreamSigner;
@@ -1149,7 +1150,7 @@ public static void addIfSupported(Vector supportedAlgs, TlsCrypto crypto, Signat
11491150

11501151
public static void addIfSupported(Vector supportedGroups, TlsCrypto crypto, int namedGroup)
11511152
{
1152-
if (crypto.hasNamedGroup(namedGroup))
1153+
if (isSupportedNamedGroup(crypto, namedGroup))
11531154
{
11541155
supportedGroups.addElement(Integers.valueOf(namedGroup));
11551156
}
@@ -4449,6 +4450,17 @@ public static boolean isSupportedKeyExchange(TlsCrypto crypto, int keyExchangeAl
44494450
}
44504451
}
44514452

4453+
public static boolean isSupportedNamedGroup(TlsCrypto crypto, int namedGroup)
4454+
{
4455+
if (!NamedGroup.refersToASpecificHybrid(namedGroup))
4456+
{
4457+
return crypto.hasNamedGroup(namedGroup);
4458+
}
4459+
4460+
return crypto.hasNamedGroup(NamedGroup.getHybridFirst(namedGroup))
4461+
&& crypto.hasNamedGroup(NamedGroup.getHybridSecond(namedGroup));
4462+
}
4463+
44524464
static boolean hasAnyRSASigAlgs(TlsCrypto crypto)
44534465
{
44544466
return crypto.hasSignatureAlgorithm(SignatureAlgorithm.rsa)
@@ -5362,45 +5374,79 @@ private static void collectKeyShares(TlsCrypto crypto, int[] supportedGroups, Ve
53625374
int supportedGroup = supportedGroups[i];
53635375
Integer supportedGroupElement = Integers.valueOf(supportedGroup);
53645376

5365-
if (!keyShareGroups.contains(supportedGroupElement)
5366-
|| clientAgreements.containsKey(supportedGroupElement)
5367-
|| !crypto.hasNamedGroup(supportedGroup))
5377+
if (!keyShareGroups.contains(supportedGroupElement) ||
5378+
clientAgreements.containsKey(supportedGroupElement))
53685379
{
53695380
continue;
53705381
}
53715382

5372-
TlsAgreement agreement = null;
5373-
if (NamedGroup.refersToAnECDHCurve(supportedGroup))
5383+
TlsAgreement agreement = createKeyShare(crypto, supportedGroup, false);
5384+
if (agreement != null)
5385+
{
5386+
byte[] key_exchange = agreement.generateEphemeral();
5387+
KeyShareEntry clientShare = new KeyShareEntry(supportedGroup, key_exchange);
5388+
5389+
clientShares.addElement(clientShare);
5390+
clientAgreements.put(supportedGroupElement, agreement);
5391+
}
5392+
}
5393+
}
5394+
5395+
static TlsAgreement createKeyShare(TlsCrypto crypto, int keyShareGroup, boolean isServer)
5396+
{
5397+
if (!NamedGroup.refersToASpecificHybrid(keyShareGroup))
5398+
{
5399+
return createKeyShareSimple(crypto, keyShareGroup, isServer);
5400+
}
5401+
5402+
int hybridFirst = NamedGroup.getHybridFirst(keyShareGroup);
5403+
TlsAgreement firstAgreement = createKeyShareSimple(crypto, hybridFirst, isServer);
5404+
if (firstAgreement == null)
5405+
{
5406+
return null;
5407+
}
5408+
5409+
int hybridSecond = NamedGroup.getHybridSecond(keyShareGroup);
5410+
TlsAgreement secondAgreement = createKeyShareSimple(crypto, hybridSecond, isServer);
5411+
if (secondAgreement == null)
5412+
{
5413+
return null;
5414+
}
5415+
5416+
int peerValueSplit = isServer
5417+
? NamedGroup.getHybridSplitClientShare(hybridFirst)
5418+
: NamedGroup.getHybridSplitServerShare(hybridFirst);
5419+
5420+
return new TlsHybridAgreement(crypto, firstAgreement, secondAgreement, peerValueSplit);
5421+
}
5422+
5423+
private static TlsAgreement createKeyShareSimple(TlsCrypto crypto, int keyShareGroup, boolean isServer)
5424+
{
5425+
if (crypto.hasNamedGroup(keyShareGroup))
5426+
{
5427+
if (NamedGroup.refersToAnECDHCurve(keyShareGroup))
53745428
{
53755429
if (crypto.hasECDHAgreement())
53765430
{
5377-
agreement = crypto.createECDomain(new TlsECConfig(supportedGroup)).createECDH();
5431+
return crypto.createECDomain(new TlsECConfig(keyShareGroup)).createECDH();
53785432
}
53795433
}
5380-
else if (NamedGroup.refersToASpecificFiniteField(supportedGroup))
5434+
else if (NamedGroup.refersToASpecificFiniteField(keyShareGroup))
53815435
{
53825436
if (crypto.hasDHAgreement())
53835437
{
5384-
agreement = crypto.createDHDomain(new TlsDHConfig(supportedGroup, true)).createDH();
5438+
return crypto.createDHDomain(new TlsDHConfig(keyShareGroup, true)).createDH();
53855439
}
53865440
}
5387-
else if (NamedGroup.refersToASpecificKem(supportedGroup))
5441+
else if (NamedGroup.refersToASpecificKem(keyShareGroup))
53885442
{
53895443
if (crypto.hasKemAgreement())
53905444
{
5391-
agreement = crypto.createKemDomain(new TlsKemConfig(supportedGroup, false)).createKem();
5445+
return crypto.createKemDomain(new TlsKemConfig(keyShareGroup, isServer)).createKem();
53925446
}
53935447
}
5394-
5395-
if (null != agreement)
5396-
{
5397-
byte[] key_exchange = agreement.generateEphemeral();
5398-
KeyShareEntry clientShare = new KeyShareEntry(supportedGroup, key_exchange);
5399-
5400-
clientShares.addElement(clientShare);
5401-
clientAgreements.put(supportedGroupElement, agreement);
5402-
}
54035448
}
5449+
return null;
54045450
}
54055451

54065452
static KeyShareEntry selectKeyShare(Vector clientShares, int keyShareGroup)
@@ -5427,25 +5473,10 @@ static KeyShareEntry selectKeyShare(TlsCrypto crypto, ProtocolVersion negotiated
54275473

54285474
int group = clientShare.getNamedGroup();
54295475

5430-
if (!NamedGroup.canBeNegotiated(group, negotiatedVersion))
5431-
{
5432-
continue;
5433-
}
5434-
5435-
if (!Arrays.contains(serverSupportedGroups, group) ||
5436-
!Arrays.contains(clientSupportedGroups, group))
5437-
{
5438-
continue;
5439-
}
5440-
5441-
if (!crypto.hasNamedGroup(group))
5442-
{
5443-
continue;
5444-
}
5445-
5446-
if ((NamedGroup.refersToAnECDHCurve(group) && crypto.hasECDHAgreement()) ||
5447-
(NamedGroup.refersToASpecificFiniteField(group) && crypto.hasDHAgreement()) ||
5448-
(NamedGroup.refersToASpecificKem(group) && crypto.hasKemAgreement()))
5476+
if (NamedGroup.canBeNegotiated(group, negotiatedVersion) &&
5477+
Arrays.contains(serverSupportedGroups, group) &&
5478+
Arrays.contains(clientSupportedGroups, group) &&
5479+
supportsKeyShareGroup(crypto, group))
54495480
{
54505481
return clientShare;
54515482
}
@@ -5463,30 +5494,46 @@ static int selectKeyShareGroup(TlsCrypto crypto, ProtocolVersion negotiatedVersi
54635494
{
54645495
int group = clientSupportedGroups[i];
54655496

5466-
if (!NamedGroup.canBeNegotiated(group, negotiatedVersion))
5497+
if (NamedGroup.canBeNegotiated(group, negotiatedVersion) &&
5498+
Arrays.contains(serverSupportedGroups, group) &&
5499+
supportsKeyShareGroup(crypto, group))
54675500
{
5468-
continue;
5501+
return group;
54695502
}
5503+
}
5504+
}
5505+
return -1;
5506+
}
54705507

5471-
if (!Arrays.contains(serverSupportedGroups, group))
5472-
{
5473-
continue;
5474-
}
5508+
private static boolean supportsKeyShareGroup(TlsCrypto crypto, int keyShareGroup)
5509+
{
5510+
if (!NamedGroup.refersToASpecificHybrid(keyShareGroup))
5511+
{
5512+
return supportsKeyShareGroupSimple(crypto, keyShareGroup);
5513+
}
54755514

5476-
if (!crypto.hasNamedGroup(group))
5477-
{
5478-
continue;
5479-
}
5515+
return supportsKeyShareGroupSimple(crypto, NamedGroup.getHybridFirst(keyShareGroup))
5516+
&& supportsKeyShareGroupSimple(crypto, NamedGroup.getHybridSecond(keyShareGroup));
5517+
}
54805518

5481-
if ((NamedGroup.refersToAnECDHCurve(group) && crypto.hasECDHAgreement()) ||
5482-
(NamedGroup.refersToASpecificFiniteField(group) && crypto.hasDHAgreement()) ||
5483-
(NamedGroup.refersToASpecificKem(group) && crypto.hasKemAgreement()))
5484-
{
5485-
return group;
5486-
}
5519+
private static boolean supportsKeyShareGroupSimple(TlsCrypto crypto, int keyShareGroup)
5520+
{
5521+
if (crypto.hasNamedGroup(keyShareGroup))
5522+
{
5523+
if (NamedGroup.refersToAnECDHCurve(keyShareGroup))
5524+
{
5525+
return crypto.hasECDHAgreement();
5526+
}
5527+
else if (NamedGroup.refersToASpecificFiniteField(keyShareGroup))
5528+
{
5529+
return crypto.hasDHAgreement();
5530+
}
5531+
else if (NamedGroup.refersToASpecificKem(keyShareGroup))
5532+
{
5533+
return crypto.hasKemAgreement();
54875534
}
54885535
}
5489-
return -1;
5536+
return false;
54905537
}
54915538

54925539
static byte[] readEncryptedPMS(TlsContext context, InputStream input) throws IOException

tls/src/main/java/org/bouncycastle/tls/crypto/TlsCrypto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ public interface TlsCrypto
146146
*/
147147
boolean hasSRPAuthentication();
148148

149+
TlsSecret createHybridSecret(TlsSecret s1, TlsSecret s2);
150+
149151
/**
150152
* Create a TlsSecret object based on provided data.
151153
*

0 commit comments

Comments
 (0)