Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS: Initial work on draft-kwiatkowski-tls-ecdhe-mlkem-03 #1996

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ private enum All
OQS_mlkem1024(NamedGroup.OQS_mlkem1024, "ML-KEM"),
MLKEM512(NamedGroup.MLKEM512, "ML-KEM"),
MLKEM768(NamedGroup.MLKEM768, "ML-KEM"),
MLKEM1024(NamedGroup.MLKEM1024, "ML-KEM");
MLKEM1024(NamedGroup.MLKEM1024, "ML-KEM"),
SecP256r1MLKEM768(NamedGroup.SecP256r1MLKEM768, "ML-KEM"),
X25519MLKEM768(NamedGroup.X25519MLKEM768, "ML-KEM"),
SecP384r1MLKEM1024(NamedGroup.SecP384r1MLKEM1024, "ML-KEM");

private final int namedGroup;
private final String name;
Expand Down
22 changes: 22 additions & 0 deletions tls/src/main/java/org/bouncycastle/tls/NamedGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ public class NamedGroup
public static final int MLKEM768 = 0x0768;
public static final int MLKEM1024 = 0x1024;

/*
* draft-kwiatkowski-tls-ecdhe-mlkem-03
*/
public static final int SecP256r1MLKEM768 = 0x11EB;
public static final int X25519MLKEM768 = 0x11EC;
public static final int SecP384r1MLKEM1024 = 0x11ED;

/* Names of the actual underlying elliptic curves (not necessarily matching the NamedGroup names). */
private static final String[] CURVE_NAMES = new String[]{ "sect163k1", "sect163r1", "sect163r2", "sect193r1",
"sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1",
Expand Down Expand Up @@ -310,6 +317,12 @@ public static String getKemName(int namedGroup)
case OQS_mlkem1024:
case MLKEM1024:
return "ML-KEM-1024";
case SecP256r1MLKEM768:
return "SecP256r1MLKEM768";
case X25519MLKEM768:
return "X25519MLKEM768";
case SecP384r1MLKEM1024:
return "SecP384r1MLKEM1024";
default:
return null;
}
Expand Down Expand Up @@ -382,6 +395,12 @@ public static String getName(int namedGroup)
return "MLKEM768";
case MLKEM1024:
return "MLKEM1024";
case SecP256r1MLKEM768:
return "SecP256r1MLKEM768";
case X25519MLKEM768:
return "X25519MLKEM768";
case SecP384r1MLKEM1024:
return "SecP384r1MLKEM1024";
case arbitrary_explicit_prime_curves:
return "arbitrary_explicit_prime_curves";
case arbitrary_explicit_char2_curves:
Expand Down Expand Up @@ -502,6 +521,9 @@ public static boolean refersToASpecificKem(int namedGroup)
case MLKEM512:
case MLKEM768:
case MLKEM1024:
case SecP256r1MLKEM768:
case X25519MLKEM768:
case SecP384r1MLKEM1024:
return true;
default:
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,16 @@ public TlsECDomain createECDomain(TlsECConfig ecConfig)

public TlsKemDomain createKemDomain(TlsKemConfig kemConfig)
{
return new BcTlsMLKemDomain(this, kemConfig);
switch (kemConfig.getNamedGroup())
{
case NamedGroup.SecP256r1MLKEM768:
case NamedGroup.SecP384r1MLKEM1024:
return new BcTlsECDHMLKemDomain(this, kemConfig);
case NamedGroup.X25519MLKEM768:
return new BcTlsX25519MLKemDomain(this, kemConfig);
default:
return new BcTlsMLKemDomain(this, kemConfig);
}
}

public TlsNonceGenerator createNonceGenerator(byte[] additionalSeedMaterial)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.bouncycastle.tls.crypto.impl.bc;

import java.io.IOException;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
import org.bouncycastle.tls.crypto.TlsAgreement;
import org.bouncycastle.tls.crypto.TlsSecret;
import org.bouncycastle.util.Arrays;

public class BcTlsECDHMLKem implements TlsAgreement
{
protected final BcTlsECDHMLKemDomain domain;

protected AsymmetricCipherKeyPair ecLocalKeyPair;
protected ECPublicKeyParameters ecPeerPublicKey;
protected MLKEMPrivateKeyParameters mlkemPrivateKey;
protected MLKEMPublicKeyParameters mlkemPublicKey;
protected byte[] mlkemSecret;

public BcTlsECDHMLKem(BcTlsECDHMLKemDomain domain)
{
this.domain = domain;
}

public byte[] generateEphemeral() throws IOException
{
this.ecLocalKeyPair = domain.getECDomain().generateKeyPair();
byte[] ecPublickey = domain.getECDomain().encodePublicKey((ECPublicKeyParameters)ecLocalKeyPair.getPublic());

if (domain.isServer())
{
SecretWithEncapsulation encap = domain.getMLKemDomain().encapsulate(mlkemPublicKey);
this.mlkemPublicKey = null;
this.mlkemSecret = encap.getSecret();
byte[] mlkemValue = encap.getEncapsulation();
return Arrays.concatenate(ecPublickey, mlkemValue);
}
else
{
AsymmetricCipherKeyPair kp = domain.getMLKemDomain().generateKeyPair();
this.mlkemPrivateKey = (MLKEMPrivateKeyParameters)kp.getPrivate();
byte[] mlkemValue = domain.getMLKemDomain().encodePublicKey((MLKEMPublicKeyParameters)kp.getPublic());
return Arrays.concatenate(ecPublickey, mlkemValue);
}
}

public void receivePeerValue(byte[] peerValue) throws IOException
{
this.ecPeerPublicKey = domain.getECDomain().decodePublicKey(Arrays.copyOf(peerValue, domain.getECDomain().getPublicKeyByteLength()));
byte[] mlkemValue = Arrays.copyOfRange(peerValue, domain.getECDomain().getPublicKeyByteLength(), peerValue.length);

if (domain.isServer())
{
this.mlkemPublicKey = domain.getMLKemDomain().decodePublicKey(mlkemValue);
}
else
{
this.mlkemSecret = domain.getMLKemDomain().decapsulate(mlkemPrivateKey, mlkemValue);
this.mlkemPrivateKey = null;
}
}

public TlsSecret calculateSecret() throws IOException
{
byte[] ecSecret = domain.getECDomain().calculateECDHAgreementBytes((ECPrivateKeyParameters)ecLocalKeyPair.getPrivate(), ecPeerPublicKey);
TlsSecret secret = domain.adoptLocalSecret(Arrays.concatenate(ecSecret, mlkemSecret));
this.mlkemSecret = null;
return secret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.bouncycastle.tls.crypto.impl.bc;

import org.bouncycastle.tls.NamedGroup;
import org.bouncycastle.tls.crypto.TlsAgreement;
import org.bouncycastle.tls.crypto.TlsECConfig;
import org.bouncycastle.tls.crypto.TlsKemConfig;
import org.bouncycastle.tls.crypto.TlsKemDomain;

public class BcTlsECDHMLKemDomain implements TlsKemDomain
{
protected final BcTlsCrypto crypto;
protected final boolean isServer;
private final BcTlsECDomain ecDomain;
private final BcTlsMLKemDomain mlkemDomain;

public BcTlsECDHMLKemDomain(BcTlsCrypto crypto, TlsKemConfig kemConfig)
{
this.crypto = crypto;
this.ecDomain = getBcTlsECDomain(crypto, kemConfig);
this.mlkemDomain = new BcTlsMLKemDomain(crypto, kemConfig);
this.isServer = kemConfig.isServer();
}

public BcTlsSecret adoptLocalSecret(byte[] secret)
{
return crypto.adoptLocalSecret(secret);
}

public TlsAgreement createKem()
{
return new BcTlsECDHMLKem(this);
}

public boolean isServer()
{
return isServer;
}

public BcTlsECDomain getECDomain()
{
return ecDomain;
}

public BcTlsMLKemDomain getMLKemDomain()
{
return mlkemDomain;
}

private BcTlsECDomain getBcTlsECDomain(BcTlsCrypto crypto, TlsKemConfig kemConfig)
{
switch (kemConfig.getNamedGroup())
{
case NamedGroup.SecP256r1MLKEM768:
return new BcTlsECDomain(crypto, new TlsECConfig(NamedGroup.secp256r1));
case NamedGroup.SecP384r1MLKEM1024:
return new BcTlsECDomain(crypto, new TlsECConfig(NamedGroup.secp384r1));
default:
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
*/
public class BcTlsECDomain implements TlsECDomain
{
public int getPublicKeyByteLength()
{
return (((domainParameters.getCurve().getFieldSize() + 7) / 8) * 2) + 1;
}

public byte[] calculateECDHAgreementBytes(ECPrivateKeyParameters privateKey, ECPublicKeyParameters publicKey)
{
ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement();
basicAgreement.init(privateKey);
BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey);
return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue);
}

public static BcTlsSecret calculateECDHAgreement(BcTlsCrypto crypto, ECPrivateKeyParameters privateKey,
ECPublicKeyParameters publicKey)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void receivePeerValue(byte[] peerValue) throws IOException
}
else
{
this.secret = domain.decapsulate(privateKey, peerValue);
this.secret = domain.adoptLocalSecret(domain.decapsulate(privateKey, peerValue));
this.privateKey = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ public static MLKEMParameters getDomainParameters(TlsKemConfig kemConfig)
return MLKEMParameters.ml_kem_512;
case NamedGroup.OQS_mlkem768:
case NamedGroup.MLKEM768:
case NamedGroup.SecP256r1MLKEM768:
case NamedGroup.X25519MLKEM768:
return MLKEMParameters.ml_kem_768;
case NamedGroup.OQS_mlkem1024:
case NamedGroup.MLKEM1024:
case NamedGroup.SecP384r1MLKEM1024:
return MLKEMParameters.ml_kem_1024;
default:
throw new IllegalArgumentException("No ML-KEM configuration provided");
Expand Down Expand Up @@ -57,11 +60,10 @@ public TlsAgreement createKem()
return new BcTlsMLKem(this);
}

public BcTlsSecret decapsulate(MLKEMPrivateKeyParameters privateKey, byte[] ciphertext)
public byte[] decapsulate(MLKEMPrivateKeyParameters privateKey, byte[] ciphertext)
{
MLKEMExtractor kemExtract = new MLKEMExtractor(privateKey);
byte[] secret = kemExtract.extractSecret(ciphertext);
return adoptLocalSecret(secret);
return kemExtract.extractSecret(ciphertext);
}

public MLKEMPublicKeyParameters decodePublicKey(byte[] encoding)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.bouncycastle.tls.crypto.impl.bc;

import java.io.IOException;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.math.ec.rfc7748.X25519;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
import org.bouncycastle.tls.crypto.TlsAgreement;
import org.bouncycastle.tls.crypto.TlsSecret;
import org.bouncycastle.util.Arrays;

public class BcTlsX25519MLKem implements TlsAgreement
{
protected final BcTlsX25519MLKemDomain domain;

protected byte[] x25519PrivateKey;
protected byte[] x25519PeerPublicKey;
protected MLKEMPrivateKeyParameters mlkemPrivateKey;
protected MLKEMPublicKeyParameters mlkemPublicKey;
protected byte[] mlkemSecret;

public BcTlsX25519MLKem(BcTlsX25519MLKemDomain domain)
{
this.domain = domain;
}

public byte[] generateEphemeral() throws IOException
{
this.x25519PrivateKey = domain.generateX25519PrivateKey();
byte[] x25519PublicKey = domain.getX25519PublicKey(x25519PrivateKey);

if (domain.isServer())
{
SecretWithEncapsulation encap = domain.getMLKemDomain().encapsulate(mlkemPublicKey);
this.mlkemPublicKey = null;
this.mlkemSecret = encap.getSecret();
byte[] mlkemValue = encap.getEncapsulation();
return Arrays.concatenate(mlkemValue, x25519PublicKey);
}
else
{
AsymmetricCipherKeyPair kp = domain.getMLKemDomain().generateKeyPair();
this.mlkemPrivateKey = (MLKEMPrivateKeyParameters)kp.getPrivate();
byte[] mlkemValue = domain.getMLKemDomain().encodePublicKey((MLKEMPublicKeyParameters)kp.getPublic());
return Arrays.concatenate(mlkemValue, x25519PublicKey);
}
}

public void receivePeerValue(byte[] peerValue) throws IOException
{
this.x25519PeerPublicKey = Arrays.copyOfRange(peerValue, peerValue.length - X25519.POINT_SIZE, peerValue.length);
byte[] mlkemValue = Arrays.copyOf(peerValue, peerValue.length - X25519.POINT_SIZE);

if (domain.isServer())
{
this.mlkemPublicKey = domain.getMLKemDomain().decodePublicKey(mlkemValue);
}
else
{
this.mlkemSecret = domain.getMLKemDomain().decapsulate(mlkemPrivateKey, mlkemValue);
this.mlkemPrivateKey = null;
}
}

public TlsSecret calculateSecret() throws IOException
{
byte[] x25519Secret = domain.calculateX25519Secret(x25519PrivateKey, x25519PeerPublicKey);
TlsSecret secret = domain.adoptLocalSecret(Arrays.concatenate(mlkemSecret, x25519Secret));
this.x25519PrivateKey = null;
this.mlkemSecret = null;
return secret;
}
}
Loading