From 38e5575c3f91fd40f12dc721dfae8c7a3be8c0cc Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 17 Feb 2025 14:50:40 +0000 Subject: [PATCH 1/4] Add support for ML-DSA-65. PKCS8 and X509 are not yet supported, because that is not yet standardized, see https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates-07 It is better to not support this until it is standardized, so that we don't have to make a breaking change if the format still changes. Therefore, keys also can't yet be serialized. Raw serialization and deserialization is supported. For private keys, the seed is used as raw format. The expanded private key is not supported. BoringSSL doesn't provided a public API to serialise the expanded private key. --- .../java/org/conscrypt/OpenSSLProvider.java | 9 + .../org/conscrypt/OpenSslMlDsaKeyFactory.java | 121 +++++++++++ .../OpenSslMlDsaKeyPairGenerator.java | 51 +++++ .../org/conscrypt/OpenSslMlDsaPrivateKey.java | 93 +++++++++ .../org/conscrypt/OpenSslMlDsaPublicKey.java | 88 ++++++++ .../org/conscrypt/OpenSslSignatureMlDsa.java | 103 +++++++++ .../test/java/org/conscrypt/MlDsaTest.java | 197 ++++++++++++++++++ .../java/security/KeyPairGeneratorTest.java | 5 + .../java/security/SignatureTest.java | 4 + common/src/test/resources/crypto/mldsa.txt | 17 ++ .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + 11 files changed, 689 insertions(+) create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java create mode 100644 common/src/test/java/org/conscrypt/MlDsaTest.java create mode 100644 common/src/test/resources/crypto/mldsa.txt diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index fc3bcbb0f..a02b0ee04 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -216,6 +216,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyPairGenerator.1.3.101.112", "EdDSA"); put("Alg.Alias.KeyPairGenerator.Ed25519", "EdDSA"); + put("KeyPairGenerator.ML-DSA", PREFIX + "OpenSslMlDsaKeyPairGenerator"); + put("Alg.Alias.KeyPairGenerator.ML-DSA-65", "ML-DSA"); + /* == KeyFactory == */ put("KeyFactory.RSA", PREFIX + "OpenSSLRSAKeyFactory"); put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA"); @@ -234,6 +237,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyFactory.1.3.101.112", "EdDSA"); put("Alg.Alias.KeyFactory.Ed25519", "EdDSA"); + put("KeyFactory.ML-DSA", PREFIX + "OpenSslMlDsaKeyFactory"); + put("Alg.Alias.KeyFactory.ML-DSA-65", "ML-DSA"); + /* == SecretKeyFactory == */ put("SecretKeyFactory.DESEDE", PREFIX + "DESEDESecretKeyFactory"); put("Alg.Alias.SecretKeyFactory.TDEA", "DESEDE"); @@ -360,6 +366,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.Signature.1.3.101.112", "EdDSA"); put("Alg.Alias.Signature.Ed25519", "EdDSA"); + putSignatureImplClass("ML-DSA", "OpenSslSignatureMlDsa"); + put("Alg.Alias.Signature.ML-DSA-65", "ML-DSA"); + /* === SecureRandom === */ /* * We have to specify SHA1PRNG because various documentation mentions diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java new file mode 100644 index 000000000..80a33324a --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** An implementation of a {@link KeyFactorySpi} for MLDSA keys based on BoringSSL. */ +@Internal +public final class OpenSslMlDsaKeyFactory extends KeyFactorySpi { + + public OpenSslMlDsaKeyFactory() {} + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (keySpec instanceof EncodedKeySpec) { + return new OpenSslMlDsaPublicKey((EncodedKeySpec) keySpec); + } + throw new InvalidKeySpecException( + "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (keySpec instanceof EncodedKeySpec) { + return new OpenSslMlDsaPrivateKey((EncodedKeySpec) keySpec); + } + throw new InvalidKeySpecException( + "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); + } + + @Override + protected T engineGetKeySpec(Key key, Class keySpec) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("key == null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!key.getAlgorithm().equals("ML-DSA")) { + throw new InvalidKeySpecException("Key must be an ML-DSA key"); + } + if (key instanceof OpenSslMlDsaPublicKey) { + OpenSslMlDsaPublicKey conscryptKey = (OpenSslMlDsaPublicKey) key; + if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { + throw new UnsupportedOperationException("X509EncodedKeySpec is currently not supported"); + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return makeRawKeySpec(conscryptKey.getRaw(), keySpec); + } + } else if (key instanceof OpenSslMlDsaPrivateKey) { + OpenSslMlDsaPrivateKey conscryptKey = (OpenSslMlDsaPrivateKey) key; + if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { + throw new UnsupportedOperationException("PKCS8EncodedKeySpec is currently not supported"); + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return makeRawKeySpec(conscryptKey.getSeed(), keySpec); + } + } + throw new InvalidKeySpecException("Unsupported key type and key spec combination; key=" + + key.getClass().getName() + ", keySpec=" + keySpec.getName()); + } + + private T makeRawKeySpec(byte[] bytes, Class keySpecClass) + throws InvalidKeySpecException { + try { + Constructor constructor = keySpecClass.getConstructor(byte[].class); + T instance = constructor.newInstance((Object) bytes); + EncodedKeySpec spec = (EncodedKeySpec) instance; + if (!spec.getFormat().equalsIgnoreCase("raw")) { + throw new InvalidKeySpecException("EncodedKeySpec class must be raw format"); + } + return instance; + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | + IllegalAccessException e) { + throw new InvalidKeySpecException( + "Can't process KeySpec class " + keySpecClass.getName(), e); + } + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } + if ((key instanceof OpenSslMlDsaPublicKey) || (key instanceof OpenSslMlDsaPrivateKey)) { + return key; + } + throw new InvalidKeyException( + "Key must be OpenSslMlDsaPublicKey or OpenSslMlDsaPrivateKey"); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java new file mode 100644 index 000000000..9197d2d86 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.InvalidParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +/** + * An implementation of {@link KeyPairGenerator} for XDH keys which uses BoringSSL to perform all + * the operations. This only supports ML-DSA keys. + */ +@Internal +public final class OpenSslMlDsaKeyPairGenerator extends KeyPairGenerator { + private static final String ALGORITHM = "ML-DSA"; + + public OpenSslMlDsaKeyPairGenerator() { + super(ALGORITHM); + } + + @Override + public void initialize(int bits) throws InvalidParameterException { + if (bits != -1) { + throw new InvalidParameterException("ML-DSA only supports -1 for bits"); + } + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[32]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLDSA65_public_key_from_seed(privateKeyBytes); + + return new KeyPair( + new OpenSslMlDsaPublicKey(publicKeyBytes), new OpenSslMlDsaPrivateKey(privateKeyBytes)); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java new file mode 100644 index 000000000..f6dedda90 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -0,0 +1,93 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.PrivateKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +/** An OpenSSL ML-DSA private key. */ +public class OpenSslMlDsaPrivateKey implements PrivateKey { + + private byte[] seed; + + public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { + byte[] encoded = keySpec.getEncoded(); + if ("raw".equalsIgnoreCase(keySpec.getFormat())) { + seed = encoded; + } else { + throw new InvalidKeySpecException("Encoding must be in raw format"); + } + } + + public OpenSslMlDsaPrivateKey(byte[] seed) { + this.seed = seed.clone(); + } + + @Override + public String getAlgorithm() { + return "ML-DSA"; + } + + @Override + public String getFormat() { + throw new UnsupportedOperationException("getFormat() not yet supported"); + } + + @Override + public byte[] getEncoded() { + throw new UnsupportedOperationException("getEncoded() not yet supported"); + } + + byte[] getSeed() { + if (seed == null) { + throw new IllegalStateException("key is destroyed"); + } + return seed.clone(); + } + + @Override + public void destroy() { + if (seed != null) { + Arrays.fill(seed, (byte) 0); + seed = null; + } + } + + @Override + public boolean isDestroyed() { + return seed == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlDsaPrivateKey)) { + return false; + } + OpenSslMlDsaPrivateKey that = (OpenSslMlDsaPrivateKey) o; + return Arrays.equals(seed, that.seed); + } + + @Override + public int hashCode() { + return Arrays.hashCode(seed); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java new file mode 100644 index 000000000..9dc59de55 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +/** An OpenSSL ML-DSA public key. */ +public class OpenSslMlDsaPublicKey implements PublicKey { + private static final long serialVersionUID = 453861992373478445L; + + private final byte[] raw; + + public OpenSslMlDsaPublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { + byte[] encoded = keySpec.getEncoded(); + if ("raw".equalsIgnoreCase(keySpec.getFormat())) { + raw = encoded; + } else { + throw new InvalidKeySpecException("Encoding must be in raw format"); + } + } + + public OpenSslMlDsaPublicKey(byte[] raw) { + this.raw = raw.clone(); + } + + @Override + public String getAlgorithm() { + return "ML-DSA"; + } + + @Override + public String getFormat() { + throw new UnsupportedOperationException("getFormat() not yet supported"); + } + + @Override + public byte[] getEncoded() { + throw new UnsupportedOperationException("getEncoded() not yet supported"); + } + + byte[] getRaw() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return raw.clone(); + } + + @Override + public boolean equals(Object o) { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlDsaPublicKey)) { + return false; + } + OpenSslMlDsaPublicKey that = (OpenSslMlDsaPublicKey) o; + return Arrays.equals(raw, that.raw); + } + + @Override + public int hashCode() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return Arrays.hashCode(raw); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java new file mode 100644 index 000000000..93b81ae52 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.conscrypt; + +import java.io.ByteArrayOutputStream; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.SignatureSpi; + +/** + * Implements the JDK Signature interface needed for Ed25519 signature generation and verification + * using BoringSSL. + */ +@Internal +public class OpenSslSignatureMlDsa extends SignatureSpi { + + /** + * The current OpenSSL key we're operating on. + */ + private OpenSslMlDsaPrivateKey privateKey; + private OpenSslMlDsaPublicKey publicKey; + + /** + * Buffer to hold value to be signed or verified. + */ + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + public OpenSslSignatureMlDsa() {} + + @Override + protected void engineUpdate(byte input) { + buffer.write(input); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + buffer.write(input, offset, len); + } + + @Override + @SuppressWarnings("deprecation") + protected Object engineGetParameter(String param) throws InvalidParameterException { + return null; + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + this.privateKey = (OpenSslMlDsaPrivateKey) privateKey; + this.publicKey = null; + buffer.reset(); + } + + @Override + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + this.publicKey = (OpenSslMlDsaPublicKey) publicKey; + this.privateKey = null; + buffer.reset(); + } + + @Override + @SuppressWarnings("deprecation") + protected void engineSetParameter(String param, Object value) throws InvalidParameterException { + } + + @Override + protected byte[] engineSign() throws SignatureException { + if (privateKey == null) { + // This can't actually happen, but you never know... + throw new SignatureException("No privateKey provided"); + } + byte[] data = buffer.toByteArray(); + buffer.reset(); + return NativeCrypto.MLDSA65_sign(data, privateKey.getSeed()); + } + + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + if (publicKey == null) { + // This can't actually happen, but you never know... + throw new SignatureException("No publicKey provided"); + } + byte[] data = buffer.toByteArray(); + buffer.reset(); + int result = NativeCrypto.MLDSA65_verify(data, sigBytes, publicKey.getRaw()); + return result == 1; + } +} diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java new file mode 100644 index 000000000..28dec0d90 --- /dev/null +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.EncodedKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MlDsaTest { + + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } + + public static final class RawKeySpec extends EncodedKeySpec { + + public RawKeySpec(byte[] encoded) { + super(encoded); + } + + @Override + public String getFormat() { + return "raw"; + } + } + + // Example from https://openjdk.org/jeps/497. + @Test + public void example_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + byte[] msg = new byte[123]; + Signature ss = Signature.getInstance("ML-DSA", conscryptProvider); + ss.initSign(privateKey); + ss.update(msg); + byte[] sig = ss.sign(); + + Signature sv = Signature.getInstance("ML-DSA", conscryptProvider); + sv.initVerify(publicKey); + sv.update(msg); + boolean verified = sv.verify(sig); + assertTrue(verified); + } + + @Test + public void emptyMessage_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + byte[] emptyMessage = new byte[0]; + + Signature signature = Signature.getInstance("ML-DSA-65", conscryptProvider); + + signature.initSign(keyPair.getPrivate()); + signature.update(emptyMessage); + byte[] sig = signature.sign(); + + signature.initVerify(keyPair.getPublic()); + signature.update(emptyMessage); + assertTrue(signature.verify(sig)); + + // Create a signature without calling update. + signature.initSign(keyPair.getPrivate()); + byte[] sig2 = signature.sign(); + + signature.initVerify(keyPair.getPublic()); + assertTrue(signature.verify(sig2)); + + signature.initVerify(keyPair.getPublic()); + signature.update(emptyMessage); + assertTrue(signature.verify(sig2)); + } + + @Test + public void mldsa65_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + assertEquals("ML-DSA", privateKey.getAlgorithm()); + assertEquals("ML-DSA", publicKey.getAlgorithm()); + + byte[] msg = new byte[123]; + Signature ss = Signature.getInstance("ML-DSA-65", conscryptProvider); + ss.initSign(privateKey); + ss.update(msg); + byte[] sig = ss.sign(); + + Signature sv = Signature.getInstance("ML-DSA-65", conscryptProvider); + sv.initVerify(publicKey); + sv.update(msg); + boolean verified = sv.verify(sig); + assertTrue(verified); + } + + @Test + public void getRawKey_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + EncodedKeySpec privateKeySpec = keyFactory.getKeySpec(keyPair.getPrivate(), RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertEquals(32, privateKeySpec.getEncoded().length); + + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertEquals(1952, publicKeySpec.getEncoded().length); + } + + @Test + public void x509AndPkcs8_areNotSupported() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + assertThrows( + UnsupportedOperationException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows( + UnsupportedOperationException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + } + + + @Test + public void testVectors() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/mldsa.txt"); + + for (TestVector vector : vectors) { + String errMsg = vector.getString("name"); + String algorithm = vector.getString("algorithm"); + byte[] seed = vector.getBytes("seed"); + byte[] publicKey = vector.getBytes("public_key"); + byte[] message = vector.getBytes("message"); + byte[] signature = vector.getBytes("signature"); + + assertEquals(errMsg + ", algorithm:", "ML-DSA-65", algorithm); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + Signature signer = Signature.getInstance("ML-DSA", conscryptProvider); + signer.initSign(keyFactory.generatePrivate(new RawKeySpec(seed))); + signer.update(message); + byte[] sig = signer.sign(); + + Signature verifier = Signature.getInstance("ML-DSA", conscryptProvider); + verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); + verifier.update(message); + assertTrue(verifier.verify(sig)); + + verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); + verifier.update(message); + assertTrue(verifier.verify(signature)); + } + } +} diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index 54550870e..0c50de166 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -150,6 +150,7 @@ private static List getKeySizes(String algorithm) throws Exception { putKeySize("EC", 521); putKeySize("XDH", 255); putKeySize("EdDSA", 255); + putKeySize("ML-DSA", -1); } /** Elliptic Curve Crypto named curves that should be supported. */ @@ -250,6 +251,10 @@ private void test_Key(KeyPairGenerator kpg, Key k) throws Exception { // serializable, so just skip them. return; } + if (expectedAlgorithm.equals("ML-DSA")) { + // ML-DSA keys are not yet serializable, so just skip them. + return; + } assertNotNull(k.getEncoded()); assertNotNull(k.getFormat()); diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java index 5d489376c..e8a90fa8e 100644 --- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java +++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java @@ -102,6 +102,8 @@ public void test_getInstance() throws Exception { // We don't have code to generate key pairs for these yet. .skipAlgorithm("Ed448") .skipAlgorithm("EdDSA") + // ML-DSA is skipped because it doesn't yet support getFormat() and getEncoded(). + .skipAlgorithm("ML-DSA") .skipAlgorithm("HSS/LMS") .run((provider, algorithm) -> { KeyPair kp = keyPair(algorithm); @@ -148,6 +150,8 @@ private KeyPair keyPair(String sigAlgorithm) throws Exception { kpAlgorithm = "RSA"; } else if (sigAlgorithmUpperCase.equals("ED25519")) { kpAlgorithm = "ED25519"; + } else if (sigAlgorithmUpperCase.equals("ML-DSA")) { + kpAlgorithm = "ML-DSA"; } else { throw new Exception("Unknown KeyPair algorithm for Signature algorithm " + sigAlgorithm); diff --git a/common/src/test/resources/crypto/mldsa.txt b/common/src/test/resources/crypto/mldsa.txt new file mode 100644 index 000000000..8e4ae7e64 --- /dev/null +++ b/common/src/test/resources/crypto/mldsa.txt @@ -0,0 +1,17 @@ +# Test vector based on the ML-DSA-65 standard. +# +# Generated with the latest available KAT code +# (https://csrc.nist.gov/Projects/post-quantum-cryptography/post-quantum-cryptography-standardization/) +# adjusted to the final standard, using the following parameters: +# - DRBG seed (count = 0): +# "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1" +# - message: +# "D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8" +# - context: empty +# - pre-hashing: none +name = Test case 1 +algorithm = ML-DSA-65 +seed = 7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D +public_key = 1483236FC9F943D98417809E95405384530ED83E151E8465D34E4638F1F8D7058D62E19AB806490883A823176D4DC8A3C10C9960D0E948A9F7B62CA8E118DE5D7A05BB18E8018B6CACB4FE7885490599939D90D004BD480B116F5D6627B6C4C1B2A1496CC3525EF9F19953EC63CDD6EBDB21D65B27C644194916AAD07CC559B08CFC1282D25D7276C9E5062E0B1C4CF111C0A9DCC49BF40F5ED3C27CB4E78E39C1F068736A788E2ED4A02E9EF23EACE802CD295B6EB97D533091B3293D9BAD2938DFDECF2C4F9F6387B38A7FD22738A010B85949688650B6F063B6BC6350A1E84C869FB3BBCDC4BF6C0D0674D7C07F7AE78E4BBB302B6DB8488B5F9164E5E264682E45E71B58FC19ADF5EA892439EB352AFDDB63D22177AEF17261909E3F87BCC7E1B1A58CD5DE8F8A886A12D7137CE5BFBD2C53ECEBFD1B9F2298583D767E0DB5178B952F4D069D66FDEDCA1FBDCF8720AAAA5313C0500ECF95B9B70E7E3D58DD2B57433D3A0637DF36E964B21F44F791B3AF9074D6DBC9A2FC041D9E22D5E387C4081E6D4CCE6AB11FC8B4F2C718EB2A19924E3F17EA1F44D0084B5D5296A97A3624E4E1F6CA05229F2888557AAB577FD72F8DC328F0E4F45DD13A191920F671ACE3BC29DC3195E951D0F5EEAA095A3D5F20E4E4EA1AC157261C1C514AEB6940E63053AD68383F14E923602E6B241E9813246B47F009DB446FBF61246BAD7ED386647D020A854CCA39ECAE5FA6D667CB6D433F02BC2FAB9F37096F3C127741EC02A46C81022E070AE1DF54623DF44C5C744EDD0D3BC66581B8E1348E75B5C52D0E41BC71EDAD5B12DDA2280724B7D704BFF2AF04505F65AE496DA86701D36BC9AFB0B199442A9C5C743D97880E89C8CCB34C51890602627924316E79D4415CC1C2ED490A7A6EBB4B507181CFF18BB53A6B8F816C15A2EA8667CE59EDBE8F42376001E31981310CA403E08328AA97828DC3A86C260819BC8DF72A3E29657CA65B7763A54067958CCD6FD73DF789B306A37185C8117F0C86CF9D1C48D102ECA8343F41F86F6084E2E72E6952357D7DC076A02A7CEF64724AE634E35712E291A24704D2939717246371B42C11A672FE8FD31DA83FC3D5DE650FB2136A13A0D6229A115EA3758E3AD0810A99944275FA8FECFD2BF1D130B40473F4ABF886485A1E36290DB437B331DB303539F98D298183509D934F1A747AF29BC36BD7CA79E5D40D098EBFE61F400620B5B1AFB81327342AADEC634F1A77DAE793D55A252D391AD155A6150AB049CBA0270F07936AC21575BE6FAD53A0DC23F462E377F2C882391BAC1C17C11D18A677C3EFFACC4C6A920596F8654BB4955750BCBC18744375656F0B594D825872BB161A1B7FDFE7D01E7A19E02F41AB9D02D1FED47161716172B8D68DB04E57C74053DAC785E9245BCC8DCA48C736457EDEB8A075C1C42254E87110CBE4A909421AE6AECECE5D65834739BE6CAC51D1023CA25C322B7B3461EC65168CCCF483A2668FB4527BCB312564C4097224DBC38AB397C3A7FD693B29992B9A773C43C0E9E94479F1762C91C367D9A079B13FDC38BD74F209E4D543ABF8C9B14CED015599DFAE94723361ACBF6C1C0434DC0EFAF22C61057775F17F36D76FD75D6BFCE7DCE922DCD7585AA33CAE7A6916C4E4AC5F86E4753F8CC798C20205C8C47656FBAD7799B6A53DAE5DCB74CDB677FFFA66CBF2873A219413714578D6DA3B61AA29C494C2F084BE1FA1C1CC40D1E4A424A4CEC73E455062B6E28C333839570D6FC6C08402A8D39F145B97C3AACC6F24702E80F66F5D2FA1530CFF2A07486B3D38D8C9994EE633C2E527AF49FBE26F634C6663CF95520E04A76F33E8876826B88887C4FE8FDEB1C50F55C7E7FBC2A5077FA029DB53B7CD8FA3576BBC219AE7D7B21518FD94FA187D39D63187BF9F2BF2592F1A7A35628137D82E50477FF3406DABFE558A3FD30D4E72D1F523EBF51DF6C7BFD9C85325897A7949113F30C9570F3A9FBAF73658430C3B2AFA43BF9D37D5410B5E416C5CF375CF9ADDCECF560E7D636C2D58B89D3E5A446201990EFFC467FFBA1009EE90D0F46BD2D7018AE92CABECF62130BD7B4A077AF31882A713C73572387533EA249C9A18F0599C06EE216CFC60F7498B2A75F3F8143D90A4ABF8651DEFAD600FD332AB09E3D8FAEFA2EC9152EAF6F2BE6B78629022C0231849BE4C13FA08B827EC301150FA380663F737418C8BF0700F4327F58C2256F8BA8B61176DFD1ACE6A81C19033E3D678A9CB234F85A5B6372EAF1A1883F5ACED3ADF58B7FABFE44D986DBEDA351EA9DE5A841CD523336F986AB8FBBECF1F52B1E87DBB3AC457A743FAE899A5BB3D10EAFC4D0808B7FA98C8068093CAE7A0BC2074BAA701273734C28E97CD1102FFBCEBB83EBB17C9200BE6DBE58BC87C522E4D24254204FD2EC52C60C1225649C3DEE17012C1CC0D5CDA0B2F0FC4F27274E04ACEDE68BACE92E294B589BE45D74C5377AFEAC7182F4B702B5A50B49F1B32BD476483957C664676A819FE6851F07768DA82261C75D53F8F04A64291A56E008B11AE09EE73923257EC195020D958F7B6D43ABA268978CB33B150A9C0DECAFBB36291257512CC7F2CB0B5564A0F81EF4686838CDBFE10475520E6EF69047CCA864E50C86E9D91FC4EAE741D4BE8AD7B12952B76C3429548169C370A7A5E2DB3FC809B9930952EF5AF9CDCCAF74FC13D0DB8D55862858E47E4C6F66FDA9DA423B884DB6ED79D012587F757F0BD974680AD8E +message = D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8 +signature = BD0D51DB2F225AC6D3DA8F0C2439B0BCDA26EFF7EFA67CFD3C2B98EFA08477A74088DC638126865E493697B6FE360FF9C55B304D15A7474C983C3D8A4E1AB28FF9925CC9073AD986D4B53C28B4CC909DC36B9334CC4510AFFDEA9548620923ED2158224AC5CA8FEF19228DBBBF12956F5422176E8A474AFBE6EC6551F1FFDE71E86C48B39BE6CA540DBD78B985E89A2F7576325E79DCF801585D30DCB3F971C827F4489745D450DF7AE34496C42C7A8778AAC7FDDB9740CD3F07A8AFAD1C1471FB9591BBCF37BEAEA10C465ADB4BD7303ED6CA41AD4848CE8A5659F7E3D4894AB0E79A0E7206C9FE278AC9CF1F6A3DA6B9FA8E03AFEEE717739CBFEB5C26EF3B1C9130C8DD46F9C8E8149DA9B0FE5AA8FD03600F87824A6F2EE8BBCA0EF6D8C38EC526E982100BB8A8974EA91129BF827FE4CCA13D7203D38AC51B2A14025948E5AC0F71394EB804C885521EE65EEA303CE30D0FA9626A914F36246A8F55EB2D866B215FC191CB734CC6B4724C8C1562F81E3678D39097871249B86833C6981FF45CEC71339E1C6F38ED1D04B6C70C21642D268B5E058F8095101C2339EE5619280F2553308DBCFEF74537DD02722E42608FFCA2E8EA8B8A2FECF46948C952D003071792845A07DBCFCC483B594CA9E0A69664498835DA427761E19F9FDF29E5319AA0FBAA7150DE0B1F951D9CC0E1B62DFB0857DB7C2129A896D65DCE0ECD3A87FABCC2A4A6FA5811CF6312DC9E3ABFD5ACC116A8A25F45AD3736FDB541276732DCD997B1B687BDAC9827A4582B8D3F0877595830E2079DCE9104E1FCFEFD0F8225BA9739C30CA7671A05688B55BCA1F9ED968E6F3F2831E3D54E596707BF63FD6AA809FE410EC38A17E3F8DE2E050A9E6B81CC386CC229041A7BE15FFC912FC4066A4D2D7FB98AF7022840E593C4E599D0309F37B65B85F10541683300779FA41124B19D4032CF8D7AF5726D3A08331D7A712DA910903C0A381F616CE5B1085F779486172EA4D7B127692557DD156B63B0E445ED8888E446397542E50C9BFE7B728E31388F7743D0F51151D4B4CB7642431ED0BAEAE264F4B2D9BAC2D5618338EE092228A251A4F99D4F95D263CAE16FB9A45A51D45BEF0F6CAD30547AB4BAA1C6F28E6FF35B195D938514F58FC2B47BEB8C895D213F11035E5FAEF85C917D7AA551FDF8D316CC4DE5A159CD4F39E3C118673984147C82BB41089CF0D9B6712E899A99CBA5DE33BF33E2C0DA03745031A48A37F7E6A7288790839461F2C58BB5ED93477834B572DCE2DD00DD31B866C2387076037053872D8CF8EB57AE81FDD84823DC69FE0A33F599846620AB74E86912759E245332EECFEFAAB9726F8A59256200BE72BC47DC3E0A4E28868842935D216334191F32E0630920D8DB05EE62813218A1E1FC5DE96719D08A00FE7D5072C8D51B3ED0AB0F9D5B45BBC2D5DD2CC7E6ECCB080D617565119C4B2A4E408A0B18EC969DCDB2BB7D8DE2EEEF3A76A0A5E437C6681AE7A00D54868E0F51EE39616AA29FEB7ABF4A3E17865003B781497BA572EDE6EA7A9479FD15C295B79C0384D4D8451043C6F67F2E10D8442F0C4E72684D6576FD41BC3756B1A8834082144760C7F609B3665C03F001073CCFEC1EB18FB9A61D82A8462D0A86FF80520053C55F2D79502F95EEE9B50F1B95179BEAB6EB1ADC4F582A9CA12C31E6F165E064AA9F289DD2A5E12F45E71C98CBC87DBF218926250D1A78DFD2B46B1DB4844AC63C5A6960F67A6BF0B270337E629AC04BA47883E52C33246863EB9F54BF2DFA5905F057490FE14F993D81EAC50E0D16DD0EB2098D0D1170FBF30892A7BFB45F6C6B7E349865CF4313D1572CA41A06C0D5561B0704AF4BCD4CBFF4045C5F76A9A760751F7B1432F8049CC9C0496F3E80026E2078CDC7BF54132C84200A4C27B23AAF69E97B25D8CBADA6F5C82748D73F8CEE44980B909EB0C11EB49FCEA972552BF5BE540DD9467EC81D70990562DC558C00CFF68DB80F3D2BBE61D7E154A2D5A4166E86546D8A82886E1CFA28CE2D8BF57D67D9B6CE32D451F9B2B4D73474C299C64FDD8D2AE15EAFC3F88179B8B364FE16B51E7B6C4DB47D796E159546BD409DD72879234578875C7940E057FB9508DDD9754D130F5CC3E32D82104DBCE1BA883FBC0C9AB9072A1A2771B0EA1152682D182D537EEEABE3F79C531A26E236AEF6479D5A7817D00723D0183E4A1A671C3285BAE7793D7FF982A6B90F7D38E40F763EDC401F2BD0618D3E305257CFADD3CCFED8DD3FD03CDBB533976FA353ABE73503EF8360964C2CA78888B4E67B0EEA68D35E64A840D136A7F0CA41CBBC52543BE45CA846F0213EEA90D932AB3A6902795B0B4FAC28C838224309E94782FA315BFBB9A535F3763FA9C3C95FFA3FFDA9C486678F7905A3637605A6929F234B9B04BDC729E14581888848930DF0D77FB1DB65D75F292E0EC78FFF3352ECF99D87E0B6FFC78F5B9CB423FCCE606D74D35D115A418EEEAE012026691B82D5B0262A1DD137ABF192683173A5615A3298A2224280C405EEE6094ADD0E1ACEE74204BC0F8170221621A71743084A072FDF03293D8FD7778E8E3282DC49A1A950404CE827C281E1F57E9DFA1F1156726DFCA3560F5C909987D6D79E831166155D5AAEE8F1ED382863195ED48EA6924D7A119EA99756434092F08E217804EB4943E56A42CC7AC5CDFA7CACE562FAC86AAF3BB5C3CF6F6DC35036B388E9EC8BE2272C2D6CA425FF23E6EF7878332042B120246271B93F87C463434921D0BF6A105A2C7E473B3C5E4BC5828403C130005B2EEDB7C161010A7A782AF3EA91700A7610DDA532DAC61DCA768B51541D2F6213B9C5047CA2AC0E1DDA275EFB58359B5AE203706BBCB1B2DB3ED8896C3721B51865A6F9B4B8949FAB4F3301AE7CBDC540F0B04FD6E27BE48748DA228DAE22353DA7CA1C464E70FB78960491279E827128BEF241C764061A5AD103EE62B26AE08066C5F20B807883C8E8A3144B7968F232627440154FED536DCC09DC9E33BB7BCDAED850F0435E1B9D943F79640BA06F21F99A1D89997BC5529D1E69095DE36958B8F186C12007DAF19115B0F971DFACB126280E1C4B956C458F9AD2EDF2226A696685A3DEACE620DBAD643B4B2E31911F53BBCC1E712B83DE8687D4956EBE1A30CF4D7E86DBE8B6E28DD6AF59BF6E83E25D9B67458ABE922181C4BFA5E5D047A7799D8F117411DA633096CE2ABF19C5317C545835B06A54759497605A0265A0396C4F069F7AAF9E677140679A265893780B0F4ACA2E48010346CDA16356E6D69F48FBD6E9763E1EAF576008BD2EDCCA2DF8808989D801F687EFC97EBD1C0FAA8555664BDD49E39B38565480D7DE0BB51E1CC5341DBF12DA73B5AA7DF954B5569272A7A3EA3AD45D8F65F718007A0C35AE3C7206E14AE7033E4DCE999F232BBB488AEFF090A1D160B10847B134FA82867114C4EFB7CC83DF601108E61457F7242FB159B0840D7711C0C50DEDBDDF346BFBA7C7EFCA4068B35B93FF81054115AE59DE3C55BBA020AD66893B88AE491F8F6BD45BDB0D506D15E050B26BDD0242F0EEC3092830E3F35D59A4B94B7A41A993F44DF9199EE6B084681D554AFD3970DD410E748F4A95F3F5A3B2827F1C587B563FF7F0D7C47AF3B9F72B8AD6A46C2CB178929F80C1852AD8247769BD4FEE274A0A07B20137CA67674E91779D9C6424F06E78A8BAC807C31CBB4677E9CC7D8755997BD19DBF053F1EB7DD6DC3875E667088B0501FDDBAB90C6A4C215E28B17DB87B0F4423C6108813AC993F69CD20953E0C6B85E308F20F1855F5993FB269159F2EE5D87316A0B744CD6530BFAF581C7FBAFD20689B702BDD4F907CD9D5ED768FAB06CD625B171D7159112E2446F8B6B2FD3B89F43D6C42B5120CFC98AE2762D241C41D32DFF80F7147119FBA9900689E1919EAD74C77F27C046B513FE143884A439F1E8399CF97C7E83F3BA585C5A0117251EFB5AFF33974D5B0FDBD61B62CA5692983643788AC31010E70E6909BE8757F6BD2E721BAC6790F8DCA7D1AFCDA291F1DA1669E8906F4880E0E1BDC2608A0DF671BA401C178A53AA6E1B2D6C90D2769E4230B60E9FF10EE38A1532090B3D5076D1D320697F4AC06FC8574136373FDF90D6872190E26F5311BAF686A95F47EF7A31F8A6AAF0196D3CCED25D5A549FE618D02F3C531FECF1C6770BE5B43FFC299519B7AA701BED350A09AF45B9268D8D5D81E8B962303C1F8E4BF15F5DE14A85312EB1C9511DF3E687CA14081754A2958324B4E5BAC035C91240F01D7719DAAE546ED56885F1F393DF95690C20618AAE3229C6488AF7820C3E8B421957CCF4F31A5173B7282FB972F7981AE53F73F2AE5747B608FB05F01888E80C1C6CA031D52E573FBCDF986471D038EE3C6E0814E24E8DF75BDBAE63F2909B47D9401107439A6B022C897763194687110D50779A9ACA6231B04D587A87CAADE5E4E91B7BCF43B2E469F52DBF19AB1D180F477D5DF2E45ED2609638E22E4F5143BB0E733F16AD183153C8460E9D0A821C9AE4AD7DB358B18E91A9022A26283F553D722F4D37B3B9EA7E5F684A1395C72EAF26150960A318B8901630E1A657479A2B1F7181A1C215678F3626BB7E2FD0F36498497A20F2D3C467E803F697DA800000000000000000000000000000000000000080F141A2024 diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 24a8eb0fa..e3e0a4b3b 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -100,6 +100,7 @@ HpkeContextSenderTest.class, HpkeSuiteTest.class, HpkeTestVectorsTest.class, + MlDsaTest.class, NativeCryptoArgTest.class, NativeCryptoTest.class, NativeRefTest.class, From c80d89f36f975a138a03ff644cba6a8171c27147 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 17 Feb 2025 15:01:34 +0000 Subject: [PATCH 2/4] Fix some comments. --- .../org/conscrypt/OpenSslMlDsaKeyFactory.java | 23 +- .../OpenSslMlDsaKeyPairGenerator.java | 8 +- .../org/conscrypt/OpenSslMlDsaPrivateKey.java | 3 +- .../org/conscrypt/OpenSslSignatureMlDsa.java | 3 +- .../test/java/org/conscrypt/MlDsaTest.java | 303 +++++++++--------- 5 files changed, 168 insertions(+), 172 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java index 80a33324a..ad6006322 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java @@ -32,8 +32,7 @@ /** An implementation of a {@link KeyFactorySpi} for MLDSA keys based on BoringSSL. */ @Internal public final class OpenSslMlDsaKeyFactory extends KeyFactorySpi { - - public OpenSslMlDsaKeyFactory() {} + public OpenSslMlDsaKeyFactory() {} @Override protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { @@ -44,7 +43,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecE return new OpenSslMlDsaPublicKey((EncodedKeySpec) keySpec); } throw new InvalidKeySpecException( - "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); + "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); } @Override @@ -56,7 +55,7 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpe return new OpenSslMlDsaPrivateKey((EncodedKeySpec) keySpec); } throw new InvalidKeySpecException( - "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); + "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); } @Override @@ -74,14 +73,16 @@ protected T engineGetKeySpec(Key key, Class keySpec) if (key instanceof OpenSslMlDsaPublicKey) { OpenSslMlDsaPublicKey conscryptKey = (OpenSslMlDsaPublicKey) key; if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { - throw new UnsupportedOperationException("X509EncodedKeySpec is currently not supported"); + throw new UnsupportedOperationException( + "X509EncodedKeySpec is currently not supported"); } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { - return makeRawKeySpec(conscryptKey.getRaw(), keySpec); + return makeRawKeySpec(conscryptKey.getRaw(), keySpec); } } else if (key instanceof OpenSslMlDsaPrivateKey) { OpenSslMlDsaPrivateKey conscryptKey = (OpenSslMlDsaPrivateKey) key; if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { - throw new UnsupportedOperationException("PKCS8EncodedKeySpec is currently not supported"); + throw new UnsupportedOperationException( + "PKCS8EncodedKeySpec is currently not supported"); } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { return makeRawKeySpec(conscryptKey.getSeed(), keySpec); } @@ -100,8 +101,8 @@ private T makeRawKeySpec(byte[] bytes, Class keySpecClass throw new InvalidKeySpecException("EncodedKeySpec class must be raw format"); } return instance; - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException + | IllegalAccessException e) { throw new InvalidKeySpecException( "Can't process KeySpec class " + keySpecClass.getName(), e); } @@ -116,6 +117,6 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { return key; } throw new InvalidKeyException( - "Key must be OpenSslMlDsaPublicKey or OpenSslMlDsaPrivateKey"); - } + "Key must be OpenSslMlDsaPublicKey or OpenSslMlDsaPrivateKey"); + } } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java index 9197d2d86..18184d89a 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java @@ -21,8 +21,8 @@ import java.security.KeyPairGenerator; /** - * An implementation of {@link KeyPairGenerator} for XDH keys which uses BoringSSL to perform all - * the operations. This only supports ML-DSA keys. + * An implementation of {@link KeyPairGenerator} for ML-DSA keys which uses BoringSSL to perform all + * the operations. */ @Internal public final class OpenSslMlDsaKeyPairGenerator extends KeyPairGenerator { @@ -45,7 +45,7 @@ public KeyPair generateKeyPair() { NativeCrypto.RAND_bytes(privateKeyBytes); byte[] publicKeyBytes = NativeCrypto.MLDSA65_public_key_from_seed(privateKeyBytes); - return new KeyPair( - new OpenSslMlDsaPublicKey(publicKeyBytes), new OpenSslMlDsaPrivateKey(privateKeyBytes)); + return new KeyPair(new OpenSslMlDsaPublicKey(publicKeyBytes), + new OpenSslMlDsaPrivateKey(privateKeyBytes)); } } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index f6dedda90..4fa3cf931 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -23,10 +23,9 @@ /** An OpenSSL ML-DSA private key. */ public class OpenSslMlDsaPrivateKey implements PrivateKey { - private byte[] seed; - public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { + public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { byte[] encoded = keySpec.getEncoded(); if ("raw".equalsIgnoreCase(keySpec.getFormat())) { seed = encoded; diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java index 93b81ae52..25480999e 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -24,12 +24,11 @@ import java.security.SignatureSpi; /** - * Implements the JDK Signature interface needed for Ed25519 signature generation and verification + * Implements the JDK Signature interface needed for ML-DSA signature generation and verification * using BoringSSL. */ @Internal public class OpenSslSignatureMlDsa extends SignatureSpi { - /** * The current OpenSSL key we're operating on. */ diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index 28dec0d90..3e86d2698 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -20,6 +20,11 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -31,167 +36,159 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.List; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class MlDsaTest { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); - private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } - @BeforeClass - public static void setUp() { - TestUtils.assumeAllowsUnsignedCrypto(); - } + public static final class RawKeySpec extends EncodedKeySpec { + public RawKeySpec(byte[] encoded) { + super(encoded); + } - public static final class RawKeySpec extends EncodedKeySpec { + @Override + public String getFormat() { + return "raw"; + } + } - public RawKeySpec(byte[] encoded) { - super(encoded); + // Example from https://openjdk.org/jeps/497. + @Test + public void example_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + byte[] msg = new byte[123]; + Signature ss = Signature.getInstance("ML-DSA", conscryptProvider); + ss.initSign(privateKey); + ss.update(msg); + byte[] sig = ss.sign(); + + Signature sv = Signature.getInstance("ML-DSA", conscryptProvider); + sv.initVerify(publicKey); + sv.update(msg); + boolean verified = sv.verify(sig); + assertTrue(verified); } - @Override - public String getFormat() { - return "raw"; + @Test + public void emptyMessage_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + byte[] emptyMessage = new byte[0]; + + Signature signature = Signature.getInstance("ML-DSA-65", conscryptProvider); + + signature.initSign(keyPair.getPrivate()); + signature.update(emptyMessage); + byte[] sig = signature.sign(); + + signature.initVerify(keyPair.getPublic()); + signature.update(emptyMessage); + assertTrue(signature.verify(sig)); + + // Create a signature without calling update. + signature.initSign(keyPair.getPrivate()); + byte[] sig2 = signature.sign(); + + signature.initVerify(keyPair.getPublic()); + assertTrue(signature.verify(sig2)); + + signature.initVerify(keyPair.getPublic()); + signature.update(emptyMessage); + assertTrue(signature.verify(sig2)); + } + + @Test + public void mldsa65_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + assertEquals("ML-DSA", privateKey.getAlgorithm()); + assertEquals("ML-DSA", publicKey.getAlgorithm()); + + byte[] msg = new byte[123]; + Signature ss = Signature.getInstance("ML-DSA-65", conscryptProvider); + ss.initSign(privateKey); + ss.update(msg); + byte[] sig = ss.sign(); + + Signature sv = Signature.getInstance("ML-DSA-65", conscryptProvider); + sv.initVerify(publicKey); + sv.update(msg); + boolean verified = sv.verify(sig); + assertTrue(verified); + } + + @Test + public void getRawKey_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertEquals(32, privateKeySpec.getEncoded().length); + + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertEquals(1952, publicKeySpec.getEncoded().length); + } + + @Test + public void x509AndPkcs8_areNotSupported() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + assertThrows(UnsupportedOperationException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(UnsupportedOperationException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + } + + @Test + public void testVectors() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/mldsa.txt"); + + for (TestVector vector : vectors) { + String errMsg = vector.getString("name"); + String algorithm = vector.getString("algorithm"); + byte[] seed = vector.getBytes("seed"); + byte[] publicKey = vector.getBytes("public_key"); + byte[] message = vector.getBytes("message"); + byte[] signature = vector.getBytes("signature"); + + assertEquals(errMsg + ", algorithm:", "ML-DSA-65", algorithm); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + + Signature signer = Signature.getInstance("ML-DSA", conscryptProvider); + signer.initSign(keyFactory.generatePrivate(new RawKeySpec(seed))); + signer.update(message); + byte[] sig = signer.sign(); + + Signature verifier = Signature.getInstance("ML-DSA", conscryptProvider); + verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); + verifier.update(message); + assertTrue(verifier.verify(sig)); + + verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); + verifier.update(message); + assertTrue(verifier.verify(signature)); + } } - } - - // Example from https://openjdk.org/jeps/497. - @Test - public void example_works() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); - KeyPair keyPair = keyGen.generateKeyPair(); - PrivateKey privateKey = keyPair.getPrivate(); - PublicKey publicKey = keyPair.getPublic(); - - byte[] msg = new byte[123]; - Signature ss = Signature.getInstance("ML-DSA", conscryptProvider); - ss.initSign(privateKey); - ss.update(msg); - byte[] sig = ss.sign(); - - Signature sv = Signature.getInstance("ML-DSA", conscryptProvider); - sv.initVerify(publicKey); - sv.update(msg); - boolean verified = sv.verify(sig); - assertTrue(verified); - } - - @Test - public void emptyMessage_works() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); - KeyPair keyPair = keyGen.generateKeyPair(); - - byte[] emptyMessage = new byte[0]; - - Signature signature = Signature.getInstance("ML-DSA-65", conscryptProvider); - - signature.initSign(keyPair.getPrivate()); - signature.update(emptyMessage); - byte[] sig = signature.sign(); - - signature.initVerify(keyPair.getPublic()); - signature.update(emptyMessage); - assertTrue(signature.verify(sig)); - - // Create a signature without calling update. - signature.initSign(keyPair.getPrivate()); - byte[] sig2 = signature.sign(); - - signature.initVerify(keyPair.getPublic()); - assertTrue(signature.verify(sig2)); - - signature.initVerify(keyPair.getPublic()); - signature.update(emptyMessage); - assertTrue(signature.verify(sig2)); - } - - @Test - public void mldsa65_works() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); - KeyPair keyPair = keyGen.generateKeyPair(); - PrivateKey privateKey = keyPair.getPrivate(); - PublicKey publicKey = keyPair.getPublic(); - - assertEquals("ML-DSA", privateKey.getAlgorithm()); - assertEquals("ML-DSA", publicKey.getAlgorithm()); - - byte[] msg = new byte[123]; - Signature ss = Signature.getInstance("ML-DSA-65", conscryptProvider); - ss.initSign(privateKey); - ss.update(msg); - byte[] sig = ss.sign(); - - Signature sv = Signature.getInstance("ML-DSA-65", conscryptProvider); - sv.initVerify(publicKey); - sv.update(msg); - boolean verified = sv.verify(sig); - assertTrue(verified); - } - - @Test - public void getRawKey_works() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); - KeyPair keyPair = keyGen.generateKeyPair(); - - KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); - - EncodedKeySpec privateKeySpec = keyFactory.getKeySpec(keyPair.getPrivate(), RawKeySpec.class); - assertEquals("raw", privateKeySpec.getFormat()); - assertEquals(32, privateKeySpec.getEncoded().length); - - EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); - assertEquals("raw", publicKeySpec.getFormat()); - assertEquals(1952, publicKeySpec.getEncoded().length); - } - - @Test - public void x509AndPkcs8_areNotSupported() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); - KeyPair keyPair = keyGen.generateKeyPair(); - - KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); - - assertThrows( - UnsupportedOperationException.class, - () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); - assertThrows( - UnsupportedOperationException.class, - () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); - } - - - @Test - public void testVectors() throws Exception { - List vectors = TestUtils.readTestVectors("crypto/mldsa.txt"); - - for (TestVector vector : vectors) { - String errMsg = vector.getString("name"); - String algorithm = vector.getString("algorithm"); - byte[] seed = vector.getBytes("seed"); - byte[] publicKey = vector.getBytes("public_key"); - byte[] message = vector.getBytes("message"); - byte[] signature = vector.getBytes("signature"); - - assertEquals(errMsg + ", algorithm:", "ML-DSA-65", algorithm); - - KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); - - Signature signer = Signature.getInstance("ML-DSA", conscryptProvider); - signer.initSign(keyFactory.generatePrivate(new RawKeySpec(seed))); - signer.update(message); - byte[] sig = signer.sign(); - - Signature verifier = Signature.getInstance("ML-DSA", conscryptProvider); - verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); - verifier.update(message); - assertTrue(verifier.verify(sig)); - - verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); - verifier.update(message); - assertTrue(verifier.verify(signature)); - } - } } From 1308243a119c46e36c18e06d521c8110f9eade31 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 17 Feb 2025 15:07:15 +0000 Subject: [PATCH 3/4] Fix dates. --- common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java | 2 +- .../main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java | 2 +- common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java | 2 +- common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java | 2 +- common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java | 2 +- common/src/test/java/org/conscrypt/MlDsaTest.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java index ad6006322..d90186729 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java index 18184d89a..2db7c460f 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index 4fa3cf931..95535ec25 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java index 9dc59de55..31c2c2261 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java index 25480999e..253aae7ff 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index 3e86d2698..fb9ad3bfa 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 1cd3ebe07456b64d48fca245cd1aaaec1c53d04a Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 17 Feb 2025 15:16:02 +0000 Subject: [PATCH 4/4] Fix format. --- .../test/java/org/conscrypt/java/security/SignatureTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java index e8a90fa8e..2bfadf6da 100644 --- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java +++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java @@ -103,7 +103,7 @@ public void test_getInstance() throws Exception { .skipAlgorithm("Ed448") .skipAlgorithm("EdDSA") // ML-DSA is skipped because it doesn't yet support getFormat() and getEncoded(). - .skipAlgorithm("ML-DSA") + .skipAlgorithm("ML-DSA") .skipAlgorithm("HSS/LMS") .run((provider, algorithm) -> { KeyPair kp = keyPair(algorithm);