Skip to content

Commit c74c88c

Browse files
yu-2hpeterdettman
authored andcommitted
TLS: ML-DSA support in JcaTlsCrypto (still disabled)
1 parent f32259f commit c74c88c

File tree

8 files changed

+357
-0
lines changed

8 files changed

+357
-0
lines changed

tls/src/main/java/org/bouncycastle/jsse/provider/ProvX509KeyManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ private static Map<String, PublicKeyFilter> createFiltersClient()
158158
addFilter(filters, "Ed25519");
159159
addFilter(filters, "Ed448");
160160

161+
addFilter(filters, "ML-DSA-44");
162+
addFilter(filters, "ML-DSA-65");
163+
addFilter(filters, "ML-DSA-87");
164+
161165
addECFilter13(filters, NamedGroup.brainpoolP256r1tls13);
162166
addECFilter13(filters, NamedGroup.brainpoolP384r1tls13);
163167
addECFilter13(filters, NamedGroup.brainpoolP512r1tls13);
@@ -183,6 +187,10 @@ private static Map<String, PublicKeyFilter> createFiltersServer()
183187
addFilter(filters, "Ed25519");
184188
addFilter(filters, "Ed448");
185189

190+
addFilter(filters, "ML-DSA-44");
191+
addFilter(filters, "ML-DSA-65");
192+
addFilter(filters, "ML-DSA-87");
193+
186194
addECFilter13(filters, NamedGroup.brainpoolP256r1tls13);
187195
addECFilter13(filters, NamedGroup.brainpoolP384r1tls13);
188196
addECFilter13(filters, NamedGroup.brainpoolP512r1tls13);

tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsMLDSASigner.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ private BcTlsMLDSASigner(BcTlsCrypto crypto, MLDSAPrivateKeyParameters privateKe
2727
{
2828
super(crypto, privateKey);
2929

30+
if (!SignatureScheme.isMLDSA(signatureScheme))
31+
{
32+
throw new IllegalArgumentException("signatureScheme");
33+
}
34+
3035
this.signatureScheme = signatureScheme;
3136
}
3237

tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/JcaDefaultTlsCredentialedSigner.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ else if ("Ed448".equalsIgnoreCase(algorithm))
6666
{
6767
signer = new JcaTlsEd448Signer(crypto, privateKey);
6868
}
69+
else if ("ML-DSA-44".equalsIgnoreCase(algorithm))
70+
{
71+
signer = new JcaTlsMLDSASigner(crypto, privateKey, SignatureScheme.DRAFT_mldsa44);
72+
}
73+
else if ("ML-DSA-65".equalsIgnoreCase(algorithm))
74+
{
75+
signer = new JcaTlsMLDSASigner(crypto, privateKey, SignatureScheme.DRAFT_mldsa65);
76+
}
77+
else if ("ML-DSA-87".equalsIgnoreCase(algorithm))
78+
{
79+
signer = new JcaTlsMLDSASigner(crypto, privateKey, SignatureScheme.DRAFT_mldsa87);
80+
}
6981
else
7082
{
7183
throw new IllegalArgumentException("'privateKey' type not supported: " + privateKey.getClass().getName());

tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/JcaTlsCertificate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ public Tls13Verifier createVerifier(int signatureScheme) throws IOException
277277
case SignatureScheme.DRAFT_mldsa44:
278278
case SignatureScheme.DRAFT_mldsa65:
279279
case SignatureScheme.DRAFT_mldsa87:
280+
return crypto.createTls13Verifier("ML-DSA", null, getPubKeyMLDSA());
280281

281282
default:
282283
throw new TlsFatalAlert(AlertDescription.internal_error);
@@ -396,6 +397,11 @@ PublicKey getPubKeyRSA() throws IOException
396397
return getPublicKey();
397398
}
398399

400+
PublicKey getPubKeyMLDSA() throws IOException
401+
{
402+
return getPublicKey();
403+
}
404+
399405
public short getLegacySignatureAlgorithm() throws IOException
400406
{
401407
PublicKey publicKey = getPublicKey();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.bouncycastle.tls.crypto.impl.jcajce;
2+
3+
import java.io.IOException;
4+
import java.security.PrivateKey;
5+
6+
import org.bouncycastle.tls.SignatureAndHashAlgorithm;
7+
import org.bouncycastle.tls.SignatureScheme;
8+
import org.bouncycastle.tls.crypto.TlsSigner;
9+
import org.bouncycastle.tls.crypto.TlsStreamSigner;
10+
11+
public class JcaTlsMLDSASigner
12+
implements TlsSigner
13+
{
14+
private final JcaTlsCrypto crypto;
15+
private final PrivateKey privateKey;
16+
private final int signatureScheme;
17+
18+
public JcaTlsMLDSASigner(JcaTlsCrypto crypto, PrivateKey privateKey, int signatureScheme)
19+
{
20+
if (null == crypto)
21+
{
22+
throw new NullPointerException("crypto");
23+
}
24+
if (null == privateKey)
25+
{
26+
throw new NullPointerException("privateKey");
27+
}
28+
if (!SignatureScheme.isMLDSA(signatureScheme))
29+
{
30+
throw new IllegalArgumentException("signatureScheme");
31+
}
32+
33+
this.crypto = crypto;
34+
this.privateKey = privateKey;
35+
this.signatureScheme = signatureScheme;
36+
}
37+
38+
public byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash) throws IOException
39+
{
40+
throw new UnsupportedOperationException();
41+
}
42+
43+
public TlsStreamSigner getStreamSigner(SignatureAndHashAlgorithm algorithm) throws IOException
44+
{
45+
if (algorithm == null || SignatureScheme.from(algorithm) != signatureScheme)
46+
{
47+
throw new IllegalStateException("Invalid algorithm: " + algorithm);
48+
}
49+
50+
return crypto.createStreamSigner("ML-DSA", null, privateKey, false);
51+
}
52+
}

tls/src/test/java/org/bouncycastle/jsse/provider/test/AllTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import junit.framework.Test;
55
import junit.framework.TestCase;
66
import junit.framework.TestSuite;
7+
78
import org.bouncycastle.test.PrintTestResult;
89

910
public class AllTests
@@ -25,6 +26,7 @@ public static Test suite()
2526
suite.addTestSuite(ConfigTest.class);
2627
suite.addTestSuite(ECDSACredentialsTest.class);
2728
suite.addTestSuite(EdDSACredentialsTest.class);
29+
suite.addTestSuite(MLDSACredentialsTest.class);
2830
suite.addTestSuite(InstanceTest.class);
2931
suite.addTestSuite(KeyManagerFactoryTest.class);
3032
suite.addTestSuite(PSSCredentialsTest.class);
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package org.bouncycastle.jsse.provider.test;
2+
3+
import java.io.IOException;
4+
import java.security.GeneralSecurityException;
5+
import java.security.KeyPair;
6+
import java.security.KeyStore;
7+
import java.security.SecureRandom;
8+
import java.security.cert.X509Certificate;
9+
import java.util.concurrent.CountDownLatch;
10+
11+
import javax.net.ssl.KeyManagerFactory;
12+
import javax.net.ssl.SSLContext;
13+
import javax.net.ssl.SSLServerSocket;
14+
import javax.net.ssl.SSLServerSocketFactory;
15+
import javax.net.ssl.SSLSession;
16+
import javax.net.ssl.SSLSocket;
17+
import javax.net.ssl.SSLSocketFactory;
18+
import javax.net.ssl.TrustManagerFactory;
19+
20+
import junit.framework.TestCase;
21+
22+
public class MLDSACredentialsTest
23+
extends TestCase
24+
{
25+
protected void setUp()
26+
{
27+
ProviderUtils.setupLowPriority(false);
28+
}
29+
30+
private static final String HOST = "localhost";
31+
private static final int PORT_NO_13_MLDSA44 = 9060;
32+
private static final int PORT_NO_13_MLDSA65 = 9061;
33+
private static final int PORT_NO_13_MLDSA87 = 9062;
34+
35+
static class MLDSAClient
36+
implements TestProtocolUtil.BlockingCallable
37+
{
38+
private final int port;
39+
private final String protocol;
40+
private final KeyStore trustStore;
41+
private final KeyStore clientStore;
42+
private final char[] clientKeyPass;
43+
private final CountDownLatch latch;
44+
45+
MLDSAClient(int port, String protocol, KeyStore clientStore, char[] clientKeyPass,
46+
X509Certificate trustAnchor) throws GeneralSecurityException, IOException
47+
{
48+
KeyStore trustStore = createKeyStore();
49+
trustStore.setCertificateEntry("server", trustAnchor);
50+
51+
this.port = port;
52+
this.protocol = protocol;
53+
this.trustStore = trustStore;
54+
this.clientStore = clientStore;
55+
this.clientKeyPass = clientKeyPass;
56+
this.latch = new CountDownLatch(1);
57+
}
58+
59+
public Exception call() throws Exception
60+
{
61+
try
62+
{
63+
TrustManagerFactory trustMgrFact = TrustManagerFactory.getInstance("PKIX",
64+
ProviderUtils.PROVIDER_NAME_BCJSSE);
65+
trustMgrFact.init(trustStore);
66+
67+
KeyManagerFactory keyMgrFact = KeyManagerFactory.getInstance("PKIX",
68+
ProviderUtils.PROVIDER_NAME_BCJSSE);
69+
keyMgrFact.init(clientStore, clientKeyPass);
70+
71+
SSLContext clientContext = SSLContext.getInstance("TLS", ProviderUtils.PROVIDER_NAME_BCJSSE);
72+
clientContext.init(keyMgrFact.getKeyManagers(), trustMgrFact.getTrustManagers(),
73+
SecureRandom.getInstance("DEFAULT", ProviderUtils.PROVIDER_NAME_BC));
74+
75+
SSLSocketFactory fact = clientContext.getSocketFactory();
76+
SSLSocket cSock = (SSLSocket)fact.createSocket(HOST, port);
77+
cSock.setEnabledProtocols(new String[]{ protocol });
78+
79+
SSLSession session = cSock.getSession();
80+
assertNotNull(session);
81+
assertFalse("SSL_NULL_WITH_NULL_NULL".equals(session.getCipherSuite()));
82+
assertEquals("CN=Test CA Certificate", session.getLocalPrincipal().getName());
83+
assertEquals("CN=Test CA Certificate", session.getPeerPrincipal().getName());
84+
85+
TestProtocolUtil.doClientProtocol(cSock, "Hello");
86+
}
87+
finally
88+
{
89+
latch.countDown();
90+
}
91+
92+
return null;
93+
}
94+
95+
public void await()
96+
throws InterruptedException
97+
{
98+
latch.await();
99+
}
100+
}
101+
102+
static class MLDSAServer
103+
implements TestProtocolUtil.BlockingCallable
104+
{
105+
private final int port;
106+
private final String protocol;
107+
private final KeyStore serverStore;
108+
private final char[] keyPass;
109+
private final KeyStore trustStore;
110+
private final CountDownLatch latch;
111+
112+
MLDSAServer(int port, String protocol, KeyStore serverStore, char[] keyPass, X509Certificate trustAnchor)
113+
throws GeneralSecurityException, IOException
114+
{
115+
KeyStore trustStore = createKeyStore();
116+
trustStore.setCertificateEntry("client", trustAnchor);
117+
118+
this.port = port;
119+
this.protocol = protocol;
120+
this.serverStore = serverStore;
121+
this.keyPass = keyPass;
122+
this.trustStore = trustStore;
123+
this.latch = new CountDownLatch(1);
124+
}
125+
126+
public Exception call() throws Exception
127+
{
128+
try
129+
{
130+
KeyManagerFactory keyMgrFact = KeyManagerFactory.getInstance("PKIX",
131+
ProviderUtils.PROVIDER_NAME_BCJSSE);
132+
keyMgrFact.init(serverStore, keyPass);
133+
134+
TrustManagerFactory trustMgrFact = TrustManagerFactory.getInstance("PKIX",
135+
ProviderUtils.PROVIDER_NAME_BCJSSE);
136+
trustMgrFact.init(trustStore);
137+
138+
SSLContext serverContext = SSLContext.getInstance("TLS", ProviderUtils.PROVIDER_NAME_BCJSSE);
139+
serverContext.init(keyMgrFact.getKeyManagers(), trustMgrFact.getTrustManagers(),
140+
SecureRandom.getInstance("DEFAULT", ProviderUtils.PROVIDER_NAME_BC));
141+
142+
SSLServerSocketFactory fact = serverContext.getServerSocketFactory();
143+
SSLServerSocket sSock = (SSLServerSocket)fact.createServerSocket(port);
144+
145+
SSLUtils.enableAll(sSock);
146+
sSock.setNeedClientAuth(true);
147+
148+
latch.countDown();
149+
150+
SSLSocket sslSock = (SSLSocket)sSock.accept();
151+
sslSock.setEnabledProtocols(new String[]{ protocol });
152+
153+
SSLSession session = sslSock.getSession();
154+
assertNotNull(session);
155+
assertFalse("SSL_NULL_WITH_NULL_NULL".equals(session.getCipherSuite()));
156+
assertEquals("CN=Test CA Certificate", session.getLocalPrincipal().getName());
157+
assertEquals("CN=Test CA Certificate", session.getPeerPrincipal().getName());
158+
159+
TestProtocolUtil.doServerProtocol(sslSock, "World");
160+
161+
sslSock.close();
162+
sSock.close();
163+
}
164+
finally
165+
{
166+
latch.countDown();
167+
}
168+
169+
return null;
170+
}
171+
172+
public void await() throws InterruptedException
173+
{
174+
latch.await();
175+
}
176+
}
177+
178+
public void testDISABLED()
179+
{
180+
// TODO[tls-mldsa] Enable below tests once support enabled in implementation(s).
181+
}
182+
183+
// public void test13_MLDSA44() throws Exception
184+
// {
185+
// implTestMLDSACredentials(PORT_NO_13_MLDSA44, "TLSv1.3", TestUtils.generateMLDSA44KeyPair());
186+
// }
187+
//
188+
// public void test13_MLDSA65() throws Exception
189+
// {
190+
// implTestMLDSACredentials(PORT_NO_13_MLDSA65, "TLSv1.3", TestUtils.generateMLDSA65KeyPair());
191+
// }
192+
//
193+
// public void test13_MLDSA87() throws Exception
194+
// {
195+
// implTestMLDSACredentials(PORT_NO_13_MLDSA87, "TLSv1.3", TestUtils.generateMLDSA87KeyPair());
196+
// }
197+
198+
private void implTestMLDSACredentials(int port, String protocol, KeyPair caKeyPair) throws Exception
199+
{
200+
char[] keyPass = "keyPassword".toCharArray();
201+
202+
X509Certificate caCert = TestUtils.generateRootCert(caKeyPair);
203+
204+
KeyStore serverKs = createKeyStore();
205+
serverKs.setKeyEntry("server", caKeyPair.getPrivate(), keyPass, new X509Certificate[]{ caCert });
206+
207+
KeyStore clientKs = createKeyStore();
208+
clientKs.setKeyEntry("client", caKeyPair.getPrivate(), keyPass, new X509Certificate[]{ caCert });
209+
210+
TestProtocolUtil.runClientAndServer(new MLDSAServer(port, protocol, serverKs, keyPass, caCert),
211+
new MLDSAClient(port, protocol, clientKs, keyPass, caCert));
212+
}
213+
214+
private static KeyStore createKeyStore() throws GeneralSecurityException, IOException
215+
{
216+
/*
217+
* NOTE: At the time of writing, default JKS implementation can't recover PKCS8 private keys
218+
* with version != 0, which e.g. is the case when a public key is included, which the BC
219+
* provider currently does for MLDSA.
220+
*/
221+
// KeyStore keyStore = KeyStore.getInstance("JKS");
222+
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
223+
keyStore.load(null, null);
224+
return keyStore;
225+
}
226+
}

0 commit comments

Comments
 (0)