From 7bdfeff5edcdc22f1480824ccb056474cb9d908f Mon Sep 17 00:00:00 2001 From: Vikram Gaur Date: Mon, 21 Sep 2026 15:07:06 -0700 Subject: [PATCH 1/2] fix: add cose sign1 support for AKP keys --- src/com/google/cose/CoseKey.java | 2 + src/com/google/cose/utils/CoseUtils.java | 19 ++++-- test/com/google/cose/CoseKeyTest.java | 59 +++++++++++++++++ test/com/google/cose/utils/CoseUtilsTest.java | 66 +++++++++++++++---- 4 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 test/com/google/cose/CoseKeyTest.java diff --git a/src/com/google/cose/CoseKey.java b/src/com/google/cose/CoseKey.java index ac2dce1..0823c99 100644 --- a/src/com/google/cose/CoseKey.java +++ b/src/com/google/cose/CoseKey.java @@ -132,6 +132,8 @@ public static CoseKey generateKey(Algorithm algorithm) throws CborException, Cos switch (algorithm) { case SIGNING_ALGORITHM_EDDSA: return OkpSigningKey.generateKey(); + case SIGNING_ALGORITHM_MLDSA_87: + return AkpSigningKey.generateKey(algorithm, AkpKey.CONSCRYPT_PROVIDER); case SIGNING_ALGORITHM_ECDSA_SHA_256: case SIGNING_ALGORITHM_ECDSA_SHA_384: case SIGNING_ALGORITHM_ECDSA_SHA_512: diff --git a/src/com/google/cose/utils/CoseUtils.java b/src/com/google/cose/utils/CoseUtils.java index ff00dc2..0051a7d 100644 --- a/src/com/google/cose/utils/CoseUtils.java +++ b/src/com/google/cose/utils/CoseUtils.java @@ -26,6 +26,8 @@ import co.nstant.in.cbor.model.Number; import co.nstant.in.cbor.model.UnsignedInteger; import com.google.common.collect.ImmutableMap; +import com.google.cose.AkpKey; +import com.google.cose.AkpSigningKey; import com.google.cose.CoseKey; import com.google.cose.Ec2SigningKey; import com.google.cose.Encrypt0Message; @@ -268,7 +270,9 @@ public static Encrypt0Message generateCoseEncrypt0(EncryptionKey key, Map protec public static Sign1Message generateCoseSign1(CoseKey key, Map protectedHeaders, Map unprotectedHeaders, byte[] payloadMessage, byte[] detachedContent, byte[] externalAad, Algorithm algorithm) throws CborException, CoseException { - if (!(key instanceof Ec2SigningKey || key instanceof OkpSigningKey)) { + if (!(key instanceof Ec2SigningKey + || key instanceof OkpSigningKey + || key instanceof AkpSigningKey)) { throw new CoseException("Incompatible key used."); } @@ -279,10 +283,12 @@ public static Sign1Message generateCoseSign1(CoseKey key, Map protectedHeaders, byte[] signature; if (key instanceof OkpSigningKey) { signature = ((OkpSigningKey) key).sign(algorithm, toBeSigned); - } else { + } else if (key instanceof Ec2SigningKey) { signature = signatureDerToCose( ((Ec2SigningKey) key).sign(algorithm, toBeSigned, null), algorithm); + } else { + signature = ((AkpSigningKey) key).sign(algorithm, toBeSigned, AkpKey.CONSCRYPT_PROVIDER); } return Sign1Message.builder() @@ -296,7 +302,9 @@ public static Sign1Message generateCoseSign1(CoseKey key, Map protectedHeaders, public static void verifyCoseSign1Message(CoseKey key, Sign1Message message, byte[] detachedContent, byte[] externalAad, Algorithm algorithm) throws CborException, CoseException { - if (!(key instanceof Ec2SigningKey || key instanceof OkpSigningKey)) { + if (!(key instanceof Ec2SigningKey + || key instanceof OkpSigningKey + || key instanceof AkpSigningKey)) { throw new CoseException("Incompatible key used."); } @@ -315,8 +323,11 @@ public static void verifyCoseSign1Message(CoseKey key, Sign1Message message, if (key instanceof Ec2SigningKey) { byte[] signature = signatureCoseToDer(message.getSignature()); ((Ec2SigningKey) key).verify(algorithm, encodedStructure, signature, null); - } else { + } else if (key instanceof OkpSigningKey) { ((OkpSigningKey) key).verify(algorithm, encodedStructure, message.getSignature()); + } else { + ((AkpSigningKey) key) + .verify(algorithm, encodedStructure, message.getSignature(), AkpKey.CONSCRYPT_PROVIDER); } } diff --git a/test/com/google/cose/CoseKeyTest.java b/test/com/google/cose/CoseKeyTest.java new file mode 100644 index 0000000..adf324f --- /dev/null +++ b/test/com/google/cose/CoseKeyTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * https://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 com.google.cose; + +import static com.google.common.truth.Truth.assertThat; + +import co.nstant.in.cbor.CborException; +import com.google.cose.exceptions.CoseException; +import com.google.cose.utils.Algorithm; +import java.security.Security; +import org.conscrypt.Conscrypt; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CoseKeyTest { + + @Before + public void setUp() { + Security.addProvider(Conscrypt.newProvider()); + } + + @Test + public void testGenerateOkpSigningKey() throws CborException, CoseException { + CoseKey key = CoseKey.generateKey(Algorithm.SIGNING_ALGORITHM_EDDSA); + assertThat(key).isNotNull(); + assertThat(key).isInstanceOf(OkpSigningKey.class); + } + + @Test + public void testGenerateAkpSigningKey() throws CborException, CoseException { + CoseKey key = CoseKey.generateKey(Algorithm.SIGNING_ALGORITHM_MLDSA_87); + assertThat(key).isNotNull(); + assertThat(key).isInstanceOf(AkpSigningKey.class); + } + + @Test + public void testGenerateEc2SigningKey() throws CborException, CoseException { + CoseKey key = CoseKey.generateKey(Algorithm.SIGNING_ALGORITHM_ECDSA_SHA_256); + assertThat(key).isNotNull(); + assertThat(key).isInstanceOf(Ec2SigningKey.class); + } +} diff --git a/test/com/google/cose/utils/CoseUtilsTest.java b/test/com/google/cose/utils/CoseUtilsTest.java index b21af08..d5dfce3 100644 --- a/test/com/google/cose/utils/CoseUtilsTest.java +++ b/test/com/google/cose/utils/CoseUtilsTest.java @@ -1,30 +1,39 @@ package com.google.cose.utils; -import com.google.cose.Ec2SigningKey; -import com.google.cose.Sign1Message; -import com.google.cose.exceptions.CoseException; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import co.nstant.in.cbor.CborException; import co.nstant.in.cbor.model.Map; import co.nstant.in.cbor.model.UnsignedInteger; - -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; - +import com.google.cose.AkpKey; +import com.google.cose.AkpSigningKey; +import com.google.cose.Ec2SigningKey; +import com.google.cose.OkpSigningKey; +import com.google.cose.Sign1Message; +import com.google.cose.exceptions.CoseException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.Security; import java.security.interfaces.ECPrivateKey; import java.security.spec.ECGenParameterSpec; +import org.conscrypt.Conscrypt; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class CoseUtilsTest { + @Before + public void setUp() { + Security.addProvider(Conscrypt.newProvider()); + } + @Test public void testECPublicKeyGenerationFromPrivateKey() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, CoseException { @@ -57,10 +66,10 @@ public void testSign1WithDetachedPayload() throws CborException, CoseException { CoseUtils.verifyCoseSign1Message(key, coseSign1, detachedContent, null, algorithm); assertThrows( - "Signature verification should fail when Sign1Message doesn't contain payload and detached content is not provided", - CoseException.class, - () -> CoseUtils.verifyCoseSign1Message(key, coseSign1, null, null, algorithm) - ); + "Signature verification should fail when Sign1Message doesn't contain payload and detached" + + " content is not provided", + CoseException.class, + () -> CoseUtils.verifyCoseSign1Message(key, coseSign1, null, null, algorithm)); } @Test @@ -73,4 +82,39 @@ public void testSign1WithAlgorithmHeader() throws CborException, CoseException { // Signature verification should succeed when no algorithm is passed CoseUtils.verifyCoseSign1Message(key, coseSign1, null, null, null); } + + @Test + public void testSign1WithAkpKey() throws CborException, CoseException { + AkpSigningKey key = + AkpSigningKey.generateKey(Algorithm.SIGNING_ALGORITHM_MLDSA_87, AkpKey.CONSCRYPT_PROVIDER); + Algorithm algorithm = Algorithm.SIGNING_ALGORITHM_MLDSA_87; + Map protectedHeaders = + new Map() + .put( + new UnsignedInteger(Headers.MESSAGE_HEADER_ALGORITHM), + algorithm.getCoseAlgorithmId()); + Sign1Message coseSign1 = + CoseUtils.generateCoseSign1( + key, protectedHeaders, new Map(), "test".getBytes(), null, null, algorithm); + + // Signature verification should succeed when no algorithm is passed + CoseUtils.verifyCoseSign1Message(key, coseSign1, null, null, null); + } + + @Test + public void testSign1WithOkpKey() throws CborException, CoseException { + OkpSigningKey key = OkpSigningKey.generateKey(); + Algorithm algorithm = Algorithm.SIGNING_ALGORITHM_EDDSA; + Map protectedHeaders = + new Map() + .put( + new UnsignedInteger(Headers.MESSAGE_HEADER_ALGORITHM), + algorithm.getCoseAlgorithmId()); + Sign1Message coseSign1 = + CoseUtils.generateCoseSign1( + key, protectedHeaders, new Map(), "test".getBytes(), null, null, algorithm); + + // Signature verification should succeed when no algorithm is passed + CoseUtils.verifyCoseSign1Message(key, coseSign1, null, null, null); + } } \ No newline at end of file From 171ed77e1292558c4f0f451f8882d31169789a52 Mon Sep 17 00:00:00 2001 From: Vikram Gaur Date: Mon, 21 Sep 2026 15:19:30 -0700 Subject: [PATCH 2/2] improve readability --- src/com/google/cose/utils/CoseUtils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/com/google/cose/utils/CoseUtils.java b/src/com/google/cose/utils/CoseUtils.java index 0051a7d..7ac108f 100644 --- a/src/com/google/cose/utils/CoseUtils.java +++ b/src/com/google/cose/utils/CoseUtils.java @@ -287,8 +287,10 @@ public static Sign1Message generateCoseSign1(CoseKey key, Map protectedHeaders, signature = signatureDerToCose( ((Ec2SigningKey) key).sign(algorithm, toBeSigned, null), algorithm); - } else { + } else if (key instanceof AkpSigningKey) { signature = ((AkpSigningKey) key).sign(algorithm, toBeSigned, AkpKey.CONSCRYPT_PROVIDER); + } else { + throw new CoseException("Incompatible key used."); } return Sign1Message.builder() @@ -325,9 +327,11 @@ public static void verifyCoseSign1Message(CoseKey key, Sign1Message message, ((Ec2SigningKey) key).verify(algorithm, encodedStructure, signature, null); } else if (key instanceof OkpSigningKey) { ((OkpSigningKey) key).verify(algorithm, encodedStructure, message.getSignature()); - } else { + } else if (key instanceof AkpSigningKey) { ((AkpSigningKey) key) .verify(algorithm, encodedStructure, message.getSignature(), AkpKey.CONSCRYPT_PROVIDER); + } else { + new CoseException("Incompatible key used."); } }