Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/com/google/cose/CoseKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
23 changes: 19 additions & 4 deletions src/com/google/cose/utils/CoseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
}

Expand All @@ -279,10 +283,14 @@ 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 if (key instanceof AkpSigningKey) {
signature = ((AkpSigningKey) key).sign(algorithm, toBeSigned, AkpKey.CONSCRYPT_PROVIDER);
} else {
throw new CoseException("Incompatible key used.");
}

return Sign1Message.builder()
Expand All @@ -296,7 +304,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.");
}

Expand All @@ -315,8 +325,13 @@ 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 if (key instanceof AkpSigningKey) {
((AkpSigningKey) key)
.verify(algorithm, encodedStructure, message.getSignature(), AkpKey.CONSCRYPT_PROVIDER);
} else {
new CoseException("Incompatible key used.");
}
}

Expand Down
59 changes: 59 additions & 0 deletions test/com/google/cose/CoseKeyTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
66 changes: 55 additions & 11 deletions test/com/google/cose/utils/CoseUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Loading