From 08e56f80e8d80bac0e7bad5ff2dda1acc71fb42a Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 08:45:58 +0000 Subject: [PATCH 1/6] Support key pairs for all Ec2Key subclasses Move the private key handling into Ec2Key so that both signing and key agreement keys can have private keys. --- src/com/google/cose/Ec2Key.java | 74 +++++++++++- src/com/google/cose/Ec2SigningKey.java | 112 +----------------- .../google/cose/Ec2KeyAgreementKeyTest.java | 37 ++++-- 3 files changed, 97 insertions(+), 126 deletions(-) diff --git a/src/com/google/cose/Ec2Key.java b/src/com/google/cose/Ec2Key.java index 2d45a1c..730b2ce 100644 --- a/src/com/google/cose/Ec2Key.java +++ b/src/com/google/cose/Ec2Key.java @@ -12,6 +12,9 @@ import com.google.cose.utils.CoseUtils; import com.google.cose.utils.Headers; import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; /** @@ -20,7 +23,7 @@ public abstract class Ec2Key extends CoseKey { private static final int SIGN_POSITIVE = 1; - private ECPublicKey publicKey; + protected KeyPair keyPair; Ec2Key(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -35,8 +38,27 @@ void populateKeyFromCbor() throws CborException, CoseException { // Get curve information int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); + // Get private key. + final ECPrivateKey privateKey; + if (labels.containsKey(Headers.KEY_PARAMETER_D)) { + byte[] key = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_D)).getBytes(); + if (key.length == 0) { + throw new CoseException("Cannot decode private key. Missing coordinate information."); + } + privateKey = CoseUtils.getEc2PrivateKeyFromInteger(curve, new BigInteger(SIGN_POSITIVE, key)); + } else { + privateKey = null; + } + if (!labels.containsKey(Headers.KEY_PARAMETER_X)) { - throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE); + if (privateKey == null) { + throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE); + } else { + keyPair = new KeyPair( + CoseUtils.getEc2PublicKeyFromPrivateKey(curve, privateKey), + privateKey); + return; + } } final ByteString xCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_X)); @@ -46,15 +68,16 @@ void populateKeyFromCbor() throws CborException, CoseException { throw new IllegalStateException("X coordinate provided but Y coordinate is missing."); } final ByteString yCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_Y)); - publicKey = (ECPublicKey) CoseUtils.getEc2PublicKeyFromCoordinates( + final PublicKey publicKey = CoseUtils.getEc2PublicKeyFromCoordinates( curve, new BigInteger(SIGN_POSITIVE, xCor.getBytes()), new BigInteger(SIGN_POSITIVE, yCor.getBytes()) ); + keyPair = new KeyPair(publicKey, privateKey); } public ECPublicKey getPublicKey() { - return publicKey; + return (ECPublicKey) this.keyPair.getPublic(); } void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, CoseException { @@ -82,6 +105,7 @@ void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, Cose /** Recursive builder to build out the Ec2 key and its subclasses. */ abstract static class Builder> extends CoseKey.Builder { private Integer curve = null; + private byte[] dParameter; private byte[] xCor; private byte[] yCor; @@ -96,7 +120,7 @@ void verifyKeyMaterialPresentAndComplete() throws CoseException { } boolean isKeyMaterialPresent() { - return xCor != null && yCor != null; + return dParameter != null || (xCor != null && yCor != null); } boolean isPublicKeyMaterialIncomplete() { @@ -113,6 +137,9 @@ protected Map compile() throws CoseException { Map cborKey = super.compile(); cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_CURVE), new UnsignedInteger(curve)); + if (dParameter != null) { + cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(dParameter)); + } if (xCor != null) { cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_X), new ByteString(xCor)); } @@ -139,5 +166,42 @@ public T withYCoordinate(byte[] yCor) { this.yCor = yCor; return self(); } + + public PrivateKeyRepresentationBuilder withPrivateKeyRepresentation() { + return new PrivateKeyRepresentationBuilder(this); + } + + /** + * Helper class to get the raw bytes out of the encoded private keys. + */ + public static class PrivateKeyRepresentationBuilder> { + Builder builder; + + PrivateKeyRepresentationBuilder(Builder builder) { + this.builder = builder; + } + + public T withPrivateKey(ECPrivateKey privateKey) { + builder.dParameter = privateKey.getS().toByteArray(); + return builder.self(); + } + + public T withPkcs8EncodedBytes(byte[] keyBytes) throws CoseException { + ECPrivateKey key = CoseUtils.getEc2PrivateKeyFromEncodedKeyBytes(keyBytes); + builder.dParameter = key.getS().toByteArray(); + return builder.self(); + } + + /** + * This function expects the BigInteger byte array of the private key. This is typically the + * multiplier in the EC2 private key which can generate EC2 public key from generator point. + * @param rawBytes byte array representation of BigInteger + * @return {@link Builder} + */ + public T withDParameter(byte[] rawBytes) { + builder.dParameter = rawBytes; + return builder.self(); + } + } } } diff --git a/src/com/google/cose/Ec2SigningKey.java b/src/com/google/cose/Ec2SigningKey.java index e0f55ae..310b40d 100644 --- a/src/com/google/cose/Ec2SigningKey.java +++ b/src/com/google/cose/Ec2SigningKey.java @@ -17,14 +17,10 @@ package com.google.cose; import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.model.ByteString; import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.NegativeInteger; import com.google.cose.exceptions.CoseException; import com.google.cose.utils.Algorithm; import com.google.cose.utils.CborUtils; -import com.google.cose.utils.CoseUtils; import com.google.cose.utils.Headers; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; @@ -33,20 +29,14 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; -import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECPoint; /** Implements EC2 COSE_Key spec for signing purposes. */ public final class Ec2SigningKey extends Ec2Key { - private static final int SIGN_POSITIVE = 1; - - private KeyPair keyPair; - public Ec2SigningKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -57,53 +47,6 @@ public Ec2SigningKey(DataItem cborKey) throws CborException, CoseException { } } - @Override - void populateKeyFromCbor() throws CborException, CoseException { - if (getKeyType() != Headers.KEY_TYPE_EC2) { - throw new CoseException("Expecting EC2 key (type 2), found type " + getKeyType()); - } - - // Get curve information - int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); - - // Get private key. - final ECPrivateKey privateKey; - if (labels.containsKey(Headers.KEY_PARAMETER_D)) { - byte[] key = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_D)).getBytes(); - if (key.length == 0) { - throw new CoseException("Cannot decode private key. Missing coordinate information."); - } - privateKey = CoseUtils.getEc2PrivateKeyFromInteger(curve, new BigInteger(SIGN_POSITIVE, key)); - } else { - privateKey = null; - } - - if (!labels.containsKey(Headers.KEY_PARAMETER_X)) { - if (privateKey == null) { - throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE); - } else { - keyPair = new KeyPair( - CoseUtils.getEc2PublicKeyFromPrivateKey(curve, privateKey), - privateKey); - return; - } - } - - final ByteString xCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_X)); - // Get the public key for EC2 key. - // We should not have a case where x is provided but y is not. - if (!labels.containsKey(Headers.KEY_PARAMETER_Y)) { - throw new IllegalStateException("X coordinate provided but Y coordinate is missing."); - } - final ByteString yCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_Y)); - final PublicKey publicKey = CoseUtils.getEc2PublicKeyFromCoordinates( - curve, - new BigInteger(SIGN_POSITIVE, xCor.getBytes()), - new BigInteger(SIGN_POSITIVE, yCor.getBytes()) - ); - keyPair = new KeyPair(publicKey, privateKey); - } - public static Ec2SigningKey parse(byte[] keyBytes) throws CborException, CoseException { DataItem dataItem = CborUtils.decode(keyBytes); return decode(dataItem); @@ -113,11 +56,6 @@ public static Ec2SigningKey decode(DataItem cborKey) throws CborException, CoseE return new Ec2SigningKey(cborKey); } - @Override - public ECPublicKey getPublicKey() { - return (ECPublicKey) this.keyPair.getPublic(); - } - // Big endian: Do not reuse for little endian encodings private static byte[] arrayFromBigNum(BigInteger num, int keySize) throws IllegalArgumentException { @@ -204,25 +142,14 @@ public static Ec2SigningKey generateKey(Algorithm algorithm) throws CborExceptio /** Implements builder for Ec2SigningKey. */ public static class Builder extends Ec2Key.Builder { - private byte[] dParameter; - @Override public Builder self() { return this; } - @Override - boolean isKeyMaterialPresent() { - return dParameter != null || super.isKeyMaterialPresent(); - } - @Override public Ec2SigningKey build() throws CborException, CoseException { - Map cborKey = compile(); - if (dParameter != null) { - cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(dParameter)); - } - return new Ec2SigningKey(cborKey); + return new Ec2SigningKey(compile()); } @Override @@ -235,43 +162,6 @@ public Builder withOperations(Integer...operations) throws CoseException { } return super.withOperations(operations); } - - public PrivateKeyRepresentationBuilder withPrivateKeyRepresentation() { - return new PrivateKeyRepresentationBuilder(this); - } - - /** - * Helper class to get the raw bytes out of the encoded private keys. - */ - public static class PrivateKeyRepresentationBuilder { - Builder builder; - - PrivateKeyRepresentationBuilder(Builder builder) { - this.builder = builder; - } - - public Builder withPrivateKey(ECPrivateKey privateKey) { - builder.dParameter = privateKey.getS().toByteArray(); - return builder; - } - - public Builder withPkcs8EncodedBytes(byte[] keyBytes) throws CoseException { - ECPrivateKey key = CoseUtils.getEc2PrivateKeyFromEncodedKeyBytes(keyBytes); - builder.dParameter = key.getS().toByteArray(); - return builder; - } - - /** - * This function expects the BigInteger byte array of the private key. This is typically the - * multiplier in the EC2 private key which can generate EC2 public key from generator point. - * @param rawBytes byte array representation of BigInteger - * @return {@link Builder} - */ - public Builder withDParameter(byte[] rawBytes) { - builder.dParameter = rawBytes; - return builder; - } - } } public static Builder builder() { diff --git a/test/com/google/cose/Ec2KeyAgreementKeyTest.java b/test/com/google/cose/Ec2KeyAgreementKeyTest.java index d9b1d97..96d9290 100644 --- a/test/com/google/cose/Ec2KeyAgreementKeyTest.java +++ b/test/com/google/cose/Ec2KeyAgreementKeyTest.java @@ -31,7 +31,7 @@ import org.junit.runners.JUnit4; /** - * Test class for testing {@link Ec2SigningKey}. Key values used in test cases are referenced from + * Test class for testing {@link Ec2KeyAgreement}. Key values used in test cases are referenced from * https://datatracker.ietf.org/doc/html/rfc8152#appendix-C */ @RunWith(JUnit4.class) @@ -59,7 +59,7 @@ public void testRoundTrip() throws CborException, CoseException { final Ec2KeyAgreementKey key = new Ec2KeyAgreementKey(map); byte[] a = key.serialize(); - final Ec2SigningKey newKey = Ec2SigningKey.parse(a); + final Ec2KeyAgreement newKey = Ec2KeyAgreement.parse(a); Assert.assertEquals(Headers.KEY_TYPE_EC2, newKey.getKeyType()); Assert.assertArrayEquals(keyId, newKey.getKeyId()); @@ -100,6 +100,31 @@ public void testBuilder() throws CborException, CoseException { Assert.assertEquals(cborString, TestUtilities.bytesToHexString(key.serialize())); } + @Test + public void testBuilderPkcs8EncodedPrivateKey() throws CborException, CoseException { + String cborString = "A30102200123582100DE7B7261710775352BF3C0669FA54229D9B2998EE9265645A3AF9F2" + + "FEFC93968"; + byte[] d = TestUtilities.hexStringToByteArray("3041020100301306072A8648CE3D020106082A8648CE3D0" + + "30107042730250201010420DE7B7261710775352BF3C0669FA54229D9B2998EE9265645A3AF9F2FEFC93968"); + Ec2KeyAgreementKey keyAgreementKey = Ec2KeyAgreementKey.builder() + .withCurve(Headers.CURVE_EC2_P256) + .withPrivateKeyRepresentation().withPkcs8EncodedBytes(d) + .build(); + Assert.assertEquals(cborString, TestUtilities.bytesToHexString(keyAgreementKey.serialize())); + Assert.assertNotNull(keyAgreementKey.getPublicKey()); + } + + @Test + public void testBuilderDParamPrivateKey() throws CborException, CoseException { + String cborString = "A3010220012358205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA444B6243" + + "43167FE"; + Ec2KeyAgreementKey keyAgreementKey = Ec2KeyAgreementKey.builder() + .withCurve(Headers.CURVE_EC2_P256) + .withPrivateKeyRepresentation().withDParameter(D_BYTES) + .build(); + Assert.assertEquals(cborString, TestUtilities.bytesToHexString(keyAgreementKey.serialize())); + } + @Test public void testBuilderFailureWrongOperation() throws CoseException { Ec2KeyAgreementKey.Builder builder = Ec2KeyAgreementKey.builder() @@ -129,12 +154,4 @@ public void testEc2KeyParsingWithIncorrectCurve() throws CborException { CoseException.class, () -> Ec2KeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); } - - @Test - public void testKeyParsingWithNullDParameterBytes() throws CborException, CoseException { - String cborString = "A5010220012158205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA444B6243" - + "43167FE225820B16E8CF858DDC7690407BA61D4C338237A8CFCF3DE6AA672FC60A557AA32FC672340"; - // Even if D parameter is null, we don't care since it will not be used for key agreement. - Ec2KeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString)); - } } From 6c16f6682c858a12ca8b1b1da5aa37dcf9b489b2 Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 12:47:10 +0000 Subject: [PATCH 2/6] Move key pair generation into Ec2Key for all subclasses Add a builder method that generates a key pair for the key being built. Use this from EcSigningKey and Ec2KeyAgreementKey to implement handy key generation methods. --- src/com/google/cose/Ec2Key.java | 67 ++++++++++++++++ src/com/google/cose/Ec2KeyAgreementKey.java | 16 ++++ src/com/google/cose/Ec2SigningKey.java | 78 +++---------------- .../google/cose/Ec2KeyAgreementKeyTest.java | 28 ++++++- 4 files changed, 119 insertions(+), 70 deletions(-) diff --git a/src/com/google/cose/Ec2Key.java b/src/com/google/cose/Ec2Key.java index 730b2ce..5c366ea 100644 --- a/src/com/google/cose/Ec2Key.java +++ b/src/com/google/cose/Ec2Key.java @@ -12,10 +12,14 @@ import com.google.cose.utils.CoseUtils; import com.google.cose.utils.Headers; import java.math.BigInteger; +import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECPoint; /** * Abstract class for generic Ec2 key @@ -102,6 +106,28 @@ void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, Cose } } + // Big endian: Do not reuse for little endian encodings + private static byte[] arrayFromBigNum(BigInteger num, int keySize) + throws IllegalArgumentException { + // Roundup arithmetic from bits to bytes. + byte[] keyBytes = new byte[(keySize + 7) / 8]; + byte[] keyBytes2 = num.toByteArray(); + if (keyBytes.length == keyBytes2.length) { + return keyBytes2; + } + if (keyBytes2.length > keyBytes.length) { + // There should be no more than one padding(0) byte, invalid key otherwise. + if (keyBytes2.length - keyBytes.length > 1 && keyBytes2[0] != 0) { + throw new IllegalArgumentException(); + } + System.arraycopy(keyBytes2, keyBytes2.length - keyBytes.length, keyBytes, 0, keyBytes.length); + } else { + System.arraycopy( + keyBytes2, 0, keyBytes, keyBytes.length - keyBytes2.length, keyBytes2.length); + } + return keyBytes; + } + /** Recursive builder to build out the Ec2 key and its subclasses. */ abstract static class Builder> extends CoseKey.Builder { private Integer curve = null; @@ -167,6 +193,47 @@ public T withYCoordinate(byte[] yCor) { return self(); } + public T withGeneratedKeyPair(int curve) throws CoseException { + KeyPair keyPair; + int keySize; + String curveName; + + switch (curve) { + case Headers.CURVE_EC2_P256: + curveName = "secp256r1"; + keySize = 256; + break; + + case Headers.CURVE_EC2_P384: + curveName = "secp384r1"; + keySize = 384; + break; + + case Headers.CURVE_EC2_P521: + curveName = "secp521r1"; + keySize = 521; + break; + + default: + throw new CoseException("Unsupported curve: " + curve); + } + try { + ECGenParameterSpec paramSpec = new ECGenParameterSpec(curveName); + KeyPairGenerator gen = KeyPairGenerator.getInstance("EC"); + gen.initialize(paramSpec); + keyPair = gen.genKeyPair(); + ECPoint pubPoint = ((ECPublicKey) keyPair.getPublic()).getW(); + + return withPrivateKeyRepresentation() + .withPkcs8EncodedBytes(keyPair.getPrivate().getEncoded()) + .withXCoordinate(arrayFromBigNum(pubPoint.getAffineX(), keySize)) + .withYCoordinate(arrayFromBigNum(pubPoint.getAffineY(), keySize)) + .withCurve(curve); + } catch (GeneralSecurityException e) { + throw new CoseException("Failed to generate key pari for curve: " + curve, e); + } + } + public PrivateKeyRepresentationBuilder withPrivateKeyRepresentation() { return new PrivateKeyRepresentationBuilder(this); } diff --git a/src/com/google/cose/Ec2KeyAgreementKey.java b/src/com/google/cose/Ec2KeyAgreementKey.java index db877dd..01c9c5a 100644 --- a/src/com/google/cose/Ec2KeyAgreementKey.java +++ b/src/com/google/cose/Ec2KeyAgreementKey.java @@ -19,6 +19,7 @@ import co.nstant.in.cbor.CborException; import co.nstant.in.cbor.model.DataItem; import com.google.cose.exceptions.CoseException; +import com.google.cose.utils.Algorithm; import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; @@ -42,6 +43,21 @@ public static Ec2KeyAgreementKey decode(DataItem cborKey) throws CborException, return new Ec2KeyAgreementKey(cborKey); } + /** Generates a COSE formatted Ec2 key agreement key given a specific algorithm and curve. */ + public static Ec2KeyAgreementKey generateKey(Algorithm algorithm, int curve) + throws CborException, CoseException { + switch (algorithm) { + case ECDH_ES_HKDF_256: + return builder() + .withGeneratedKeyPair(curve) + .withAlgorithm(algorithm) + .build(); + + default: + throw new CoseException("Unsupported algorithm: " + algorithm.getJavaAlgorithmId()); + } + } + public static class Builder extends Ec2Key.Builder { @Override public Builder self() { diff --git a/src/com/google/cose/Ec2SigningKey.java b/src/com/google/cose/Ec2SigningKey.java index 310b40d..9f88e52 100644 --- a/src/com/google/cose/Ec2SigningKey.java +++ b/src/com/google/cose/Ec2SigningKey.java @@ -23,17 +23,11 @@ import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Signature; import java.security.SignatureException; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECPoint; /** Implements EC2 COSE_Key spec for signing purposes. */ public final class Ec2SigningKey extends Ec2Key { @@ -56,88 +50,34 @@ public static Ec2SigningKey decode(DataItem cborKey) throws CborException, CoseE return new Ec2SigningKey(cborKey); } - // Big endian: Do not reuse for little endian encodings - private static byte[] arrayFromBigNum(BigInteger num, int keySize) - throws IllegalArgumentException { - // Roundup arithmetic from bits to bytes. - byte[] keyBytes = new byte[(keySize + 7) / 8]; - byte[] keyBytes2 = num.toByteArray(); - if (keyBytes.length == keyBytes2.length) { - return keyBytes2; - } - if (keyBytes2.length > keyBytes.length) { - // There should be no more than one padding(0) byte, invalid key otherwise. - if (keyBytes2.length - keyBytes.length > 1 && keyBytes2[0] != 0) { - throw new IllegalArgumentException(); - } - System.arraycopy(keyBytes2, keyBytes2.length - keyBytes.length, keyBytes, 0, keyBytes.length); - } else { - System.arraycopy( - keyBytes2, 0, keyBytes, keyBytes.length - keyBytes2.length, keyBytes2.length); - } - return keyBytes; - } - /** * Generates a COSE formatted Ec2 signing key given a specific algorithm. The selected key size is * chosen based on section 6.2.1 of RFC 5656 */ public static Ec2SigningKey generateKey(Algorithm algorithm) throws CborException, CoseException { - KeyPair keyPair; - int keySize; - int header; - String curveName; + int curve; switch (algorithm) { case SIGNING_ALGORITHM_ECDSA_SHA_256: - curveName = "secp256r1"; - keySize = 256; - header = Headers.CURVE_EC2_P256; + curve = Headers.CURVE_EC2_P256; break; case SIGNING_ALGORITHM_ECDSA_SHA_384: - curveName = "secp384r1"; - keySize = 384; - header = Headers.CURVE_EC2_P384; + curve = Headers.CURVE_EC2_P384; break; case SIGNING_ALGORITHM_ECDSA_SHA_512: - curveName = "secp521r1"; - keySize = 521; - header = Headers.CURVE_EC2_P521; + curve = Headers.CURVE_EC2_P521; break; default: throw new CoseException("Unsupported algorithm curve: " + algorithm.getJavaAlgorithmId()); } - try { - ECGenParameterSpec paramSpec = new ECGenParameterSpec(curveName); - KeyPairGenerator gen = KeyPairGenerator.getInstance("EC"); - gen.initialize(paramSpec); - keyPair = gen.genKeyPair(); - - ECPoint pubPoint = ((ECPublicKey) keyPair.getPublic()).getW(); - byte[] x = arrayFromBigNum(pubPoint.getAffineX(), keySize); - byte[] y = arrayFromBigNum(pubPoint.getAffineY(), keySize); - - byte[] privEncodedKey = keyPair.getPrivate().getEncoded(); - - return Ec2SigningKey.builder() - .withPrivateKeyRepresentation() - .withPkcs8EncodedBytes(privEncodedKey) - .withXCoordinate(x) - .withYCoordinate(y) - .withCurve(header) - .withAlgorithm(algorithm) - .build(); - } catch (NoSuchAlgorithmException e) { - throw new CoseException("No provider for algorithm: " + algorithm.getJavaAlgorithmId(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new CoseException("The curve is not supported: " + algorithm.getJavaAlgorithmId(), e); - } catch (IllegalArgumentException e) { - throw new CoseException( - "Invalid Coordinates generated for: " + algorithm.getJavaAlgorithmId(), e); - } + + return Ec2SigningKey.builder() + .withGeneratedKeyPair(curve) + .withAlgorithm(algorithm) + .build(); } /** Implements builder for Ec2SigningKey. */ diff --git a/test/com/google/cose/Ec2KeyAgreementKeyTest.java b/test/com/google/cose/Ec2KeyAgreementKeyTest.java index 96d9290..eadf6dc 100644 --- a/test/com/google/cose/Ec2KeyAgreementKeyTest.java +++ b/test/com/google/cose/Ec2KeyAgreementKeyTest.java @@ -24,6 +24,7 @@ import co.nstant.in.cbor.model.NegativeInteger; import co.nstant.in.cbor.model.UnsignedInteger; import com.google.cose.exceptions.CoseException; +import com.google.cose.utils.Algorithm; import com.google.cose.utils.Headers; import org.junit.Assert; import org.junit.Test; @@ -59,7 +60,7 @@ public void testRoundTrip() throws CborException, CoseException { final Ec2KeyAgreementKey key = new Ec2KeyAgreementKey(map); byte[] a = key.serialize(); - final Ec2KeyAgreement newKey = Ec2KeyAgreement.parse(a); + final Ec2KeyAgreementKey newKey = Ec2KeyAgreementKey.parse(a); Assert.assertEquals(Headers.KEY_TYPE_EC2, newKey.getKeyType()); Assert.assertArrayEquals(keyId, newKey.getKeyId()); @@ -154,4 +155,29 @@ public void testEc2KeyParsingWithIncorrectCurve() throws CborException { CoseException.class, () -> Ec2KeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); } + + @Test + public void testP256Ec2GeneratedKey() throws CborException, CoseException { + Ec2KeyAgreementKey p256Key = + Ec2KeyAgreementKey.generateKey(Algorithm.ECDH_ES_HKDF_256, Headers.CURVE_EC2_P256); + } + + @Test + public void testP384Ec2GeneratedKey() throws CborException, CoseException { + Ec2KeyAgreementKey p384Key = + Ec2KeyAgreementKey.generateKey(Algorithm.ECDH_ES_HKDF_256, Headers.CURVE_EC2_P384); + } + + @Test + public void testP521Ec2GeneratedKey() throws CborException, CoseException { + Ec2KeyAgreementKey p521Key = + Ec2KeyAgreementKey.generateKey(Algorithm.ECDH_ES_HKDF_256, Headers.CURVE_EC2_P521); + } + + @Test + public void testEc2GeneratedKey_invalidInput() throws CborException { + assertThrows( + CoseException.class, + () -> Ec2KeyAgreementKey.generateKey(Algorithm.MAC_ALGORITHM_HMAC_SHA_256_256)); + } } From 70c4a60674148014619601b058cfb5a13a9d6b8e Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 13:22:19 +0000 Subject: [PATCH 3/6] Support key pairs for all OkpKey subclasses Move the private key handling into OkpKey so that both signing and key agreement keys can have a private key. --- src/com/google/cose/OkpKey.java | 38 +++- src/com/google/cose/OkpKeyAgreementKey.java | 8 + src/com/google/cose/OkpSigningKey.java | 70 +------ .../google/cose/OkpKeyAgreementKeyTest.java | 179 ++++++++++++++++++ test/com/google/cose/OkpSigningKeyTest.java | 3 +- 5 files changed, 230 insertions(+), 68 deletions(-) create mode 100644 test/com/google/cose/OkpKeyAgreementKeyTest.java diff --git a/src/com/google/cose/OkpKey.java b/src/com/google/cose/OkpKey.java index 58552f1..223a965 100644 --- a/src/com/google/cose/OkpKey.java +++ b/src/com/google/cose/OkpKey.java @@ -15,7 +15,8 @@ * Abstract class for generic Ec2 key */ public abstract class OkpKey extends CoseKey { - private byte[] publicKeyBytes; + protected byte[] privateKeyBytes; + protected byte[] publicKeyBytes; OkpKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -26,17 +27,37 @@ public abstract class OkpKey extends CoseKey { } void populateKeyFromCbor() throws CborException, CoseException { + privateKeyBytes = getPrivateKeyBytesFromCbor(); + publicKeyBytes = getPublicKeyBytesFromCbor(); + } + + private byte[] getPrivateKeyBytesFromCbor() throws CborException, CoseException { + if (labels.containsKey(Headers.KEY_PARAMETER_D)) { + byte[] keyMaterial = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_D)).getBytes(); + if (keyMaterial.length == 0) { + throw new CoseException("Could not decode private key. Expected key material."); + } + return keyMaterial; + } + return null; + } + + private byte[] getPublicKeyBytesFromCbor() throws CborException, CoseException { if (labels.containsKey(Headers.KEY_PARAMETER_X)) { byte[] keyMaterial = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_X)).getBytes(); if (keyMaterial.length == 0) { throw new CoseException("Could not decode public key. Expected key material."); } - publicKeyBytes = keyMaterial; - } else { + return keyMaterial; + } + if (privateKeyBytes == null) { throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE); } + return publicFromPrivate(privateKeyBytes); } + protected abstract byte[] publicFromPrivate(byte[] privateKey) throws CoseException; + public byte[] getPublicKeyBytes() { return Arrays.copyOf(publicKeyBytes, publicKeyBytes.length); } @@ -44,10 +65,11 @@ public byte[] getPublicKeyBytes() { /** Recursive builder to build out the Ec2 key and its subclasses. */ abstract static class Builder> extends CoseKey.Builder { private Integer curve = null; + private byte[] dParameter; private byte[] xCor; boolean isKeyMaterialPresent() { - return xCor != null && xCor.length != 0; + return (dParameter != null && dParameter.length != 0) || (xCor != null && xCor.length != 0); } @Override @@ -67,6 +89,9 @@ protected Map compile() throws CoseException { Map cborKey = super.compile(); cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_CURVE), new UnsignedInteger(curve)); + if (dParameter != null) { + cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(dParameter)); + } if (xCor != null && xCor.length != 0) { cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_X), new ByteString(xCor)); } @@ -81,6 +106,11 @@ public T withCurve(int curve) throws CoseException { return self(); } + public T withDParameter(byte[] dParam) { + this.dParameter = dParam; + return self(); + } + public T withXCoordinate(byte[] xCor) { this.xCor = xCor; return self(); diff --git a/src/com/google/cose/OkpKeyAgreementKey.java b/src/com/google/cose/OkpKeyAgreementKey.java index ee0675c..83abda5 100644 --- a/src/com/google/cose/OkpKeyAgreementKey.java +++ b/src/com/google/cose/OkpKeyAgreementKey.java @@ -21,6 +21,7 @@ import com.google.cose.exceptions.CoseException; import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; +import org.bouncycastle.math.ec.rfc7748.X25519; /** * Implements OKP COSE_Key spec for key wrapping purposes. @@ -49,6 +50,13 @@ public static OkpKeyAgreementKey decode(DataItem cborKey) throws CborException, return new OkpKeyAgreementKey(cborKey); } + @Override + protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { + byte[] r = new byte[32]; + X25519.generatePublicKey(privateKeyBytes, 0, r, 0); + return r; + } + public static class Builder extends OkpKey.Builder { @Override public Builder self() { diff --git a/src/com/google/cose/OkpSigningKey.java b/src/com/google/cose/OkpSigningKey.java index f5ada59..2bbd2d9 100644 --- a/src/com/google/cose/OkpSigningKey.java +++ b/src/com/google/cose/OkpSigningKey.java @@ -17,10 +17,7 @@ package com.google.cose; import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.model.ByteString; import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.NegativeInteger; import com.google.cose.exceptions.CoseException; import com.google.cose.utils.Algorithm; import com.google.cose.utils.CborUtils; @@ -36,9 +33,6 @@ * Currently only supports Ed25519 curve. */ public final class OkpSigningKey extends OkpKey { - private byte[] privateKeyBytes; - private byte[] publicKeyBytes; - public OkpSigningKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -47,8 +41,6 @@ public OkpSigningKey(DataItem cborKey) throws CborException, CoseException { throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE); } - populateKeyFromCbor(); - if ((operations != null) && !operations.contains(Headers.KEY_OPERATIONS_VERIFY) && !operations.contains(Headers.KEY_OPERATIONS_SIGN)) { @@ -56,41 +48,6 @@ public OkpSigningKey(DataItem cborKey) throws CborException, CoseException { } } - @Override - void populateKeyFromCbor() throws CborException, CoseException { - privateKeyBytes = getPrivateKeyBytesFromCbor(); - publicKeyBytes = getPublicKeyBytesFromCbor(); - } - - private byte[] getPrivateKeyBytesFromCbor() throws CborException, CoseException { - if (labels.containsKey(Headers.KEY_PARAMETER_D)) { - byte[] keyMaterial = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_D)).getBytes(); - if (keyMaterial.length == 0) { - throw new CoseException("Could not decode private key. Expected key material."); - } - return keyMaterial; - } - return null; - } - - private byte[] getPublicKeyBytesFromCbor() throws CborException, CoseException { - if (labels.containsKey(Headers.KEY_PARAMETER_X)) { - byte[] keyMaterial = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_X)).getBytes(); - if (keyMaterial.length == 0) { - throw new CoseException("Could not decode public key. Expected key material."); - } - return keyMaterial; - } - if (privateKeyBytes == null) { - throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE); - } - try { - return KeyPair.newKeyPairFromSeed(privateKeyBytes).getPublicKey(); - } catch (GeneralSecurityException e) { - throw new CoseException("Error while generating public key from private key bytes.", e); - } - } - public static OkpSigningKey parse(byte[] keyBytes) throws CborException, CoseException { DataItem dataItem = CborUtils.decode(keyBytes); return decode(dataItem); @@ -101,8 +58,12 @@ public static OkpSigningKey decode(DataItem cborKey) throws CborException, CoseE } @Override - public byte[] getPublicKeyBytes() { - return Arrays.copyOf(publicKeyBytes, publicKeyBytes.length); + protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { + try { + return KeyPair.newKeyPairFromSeed(privateKeyBytes).getPublicKey(); + } catch (GeneralSecurityException e) { + throw new CoseException("Error while generating public key from private key bytes.", e); + } } /** Generates a COSE formatted OKP signing key from scratch */ @@ -124,18 +85,11 @@ public static OkpSigningKey generateKey() throws CborException, CoseException { } public static class Builder extends OkpKey.Builder { - private byte[] dParameter; - @Override public Builder self() { return this; } - @Override - boolean isKeyMaterialPresent() { - return (dParameter != null && dParameter.length != 0) || super.isKeyMaterialPresent(); - } - @Override public Builder withOperations(Integer...operations) throws CoseException { for (int operation : operations) { @@ -150,17 +104,7 @@ public Builder withOperations(Integer...operations) throws CoseException { @Override public OkpSigningKey build() throws CborException, CoseException { withCurve(Headers.CURVE_OKP_ED25519); - - Map cborKey = compile(); - if (dParameter != null) { - cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(dParameter)); - } - return new OkpSigningKey(cborKey); - } - - public Builder withDParameter(byte[] dParam) { - this.dParameter = dParam; - return this; + return new OkpSigningKey(compile()); } } diff --git a/test/com/google/cose/OkpKeyAgreementKeyTest.java b/test/com/google/cose/OkpKeyAgreementKeyTest.java new file mode 100644 index 0000000..05ff705 --- /dev/null +++ b/test/com/google/cose/OkpKeyAgreementKeyTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2023 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 org.junit.Assert.assertThrows; + +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.ByteString; +import co.nstant.in.cbor.model.Map; +import co.nstant.in.cbor.model.NegativeInteger; +import co.nstant.in.cbor.model.UnsignedInteger; +import com.google.cose.exceptions.CoseException; +import com.google.cose.utils.Algorithm; +import com.google.cose.utils.Headers; +import java.nio.charset.StandardCharsets; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test class for testing {@link OkpKeyAgreementKey}. */ +@RunWith(JUnit4.class) +public class OkpKeyAgreementKeyTest { + static final String X_VAL = "8520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA4A98EAA9B4E6A"; + private static final byte[] X_BYTES = TestUtilities.hexStringToByteArray(X_VAL); + static final String D_VAL = "77076D0A7318A57D3C16C17251B26645DF4C2F87EBC0992AB177FBA51DB92C2A"; + private static final byte[] D_BYTES = TestUtilities.hexStringToByteArray(D_VAL); + + @Test + public void testRoundTrip() throws CborException, CoseException { + final byte[] keyId = "29".getBytes(StandardCharsets.UTF_8); + final Map map = new Map(); + map.put(new UnsignedInteger(Headers.KEY_PARAMETER_KEY_TYPE), + new UnsignedInteger(Headers.KEY_TYPE_OKP)); + map.put(new UnsignedInteger(Headers.KEY_PARAMETER_KEY_ID), new ByteString(keyId)); + map.put(new NegativeInteger(Headers.KEY_PARAMETER_CURVE), + new UnsignedInteger(Headers.CURVE_OKP_X25519)); + map.put(new NegativeInteger(Headers.KEY_PARAMETER_X), new ByteString(X_BYTES)); + map.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(D_BYTES)); + + final OkpKeyAgreementKey keyWithConstructor = new OkpKeyAgreementKey(map); + OkpKeyAgreementKey keyWithBuilder = OkpKeyAgreementKey.builder() + .withDParameter(D_BYTES) + .withXCoordinate(X_BYTES) + .withKeyId(keyId) + .build(); + Assert.assertArrayEquals(keyWithConstructor.serialize(), keyWithBuilder.serialize()); + + final OkpKeyAgreementKey rebuiltKey = OkpKeyAgreementKey.parse(keyWithConstructor.serialize()); + + Assert.assertEquals(Headers.KEY_TYPE_OKP, rebuiltKey.getKeyType()); + Assert.assertEquals(new UnsignedInteger(Headers.CURVE_OKP_X25519), + rebuiltKey.labels.get(Headers.KEY_PARAMETER_CURVE)); + Assert.assertEquals(new ByteString(X_BYTES), rebuiltKey.labels.get(Headers.KEY_PARAMETER_X)); + Assert.assertEquals(new ByteString(D_BYTES), rebuiltKey.labels.get(Headers.KEY_PARAMETER_D)); + + Assert.assertArrayEquals(keyId, rebuiltKey.getKeyId()); + Assert.assertArrayEquals(keyWithConstructor.serialize(), rebuiltKey.serialize()); + } + + @Test + public void testConversion() throws CborException, CoseException { + final String cborString = "A4010120042158208520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA" + + "4A98EAA9B4E6A23582077076D0A7318A57D3C16C17251B26645DF4C2F87EBC0992AB177FBA51DB92C2A"; + final OkpKeyAgreementKey sKey = OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString)); + Assert.assertEquals(Headers.KEY_TYPE_OKP, sKey.getKeyType()); + Assert.assertEquals(new UnsignedInteger(Headers.CURVE_OKP_X25519), + sKey.getLabels().get(Headers.KEY_PARAMETER_CURVE)); + Assert.assertEquals(new ByteString(X_BYTES), sKey.getLabels().get(Headers.KEY_PARAMETER_X)); + Assert.assertEquals(new ByteString(D_BYTES), sKey.getLabels().get(Headers.KEY_PARAMETER_D)); + } + + @Test + public void testBuilder() throws CborException, CoseException { + final String cborString = "A3010120042158208520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA" + + "4A98EAA9B4E6A"; + OkpKeyAgreementKey key = OkpKeyAgreementKey.builder() + .withXCoordinate(X_BYTES) + .build(); + Assert.assertEquals(cborString, TestUtilities.bytesToHexString(key.serialize())); + } + + @Test + public void testBuilderPrivateKey() throws CborException, CoseException { + final String cborString = "A4010120042158208520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA" + + "4A98EAA9B4E6A23582077076D0A7318A57D3C16C17251B26645DF4C2F87EBC0992AB177FBA51DB92C2A"; + OkpKeyAgreementKey key = OkpKeyAgreementKey.builder() + .withXCoordinate(X_BYTES) + .withDParameter(D_BYTES) + .build(); + Assert.assertEquals(cborString, TestUtilities.bytesToHexString(key.serialize())); + } + + @Test + public void testEmptyBuilderFailure() throws CborException { + OkpKeyAgreementKey.Builder builder = OkpKeyAgreementKey.builder(); + assertThrows( + "Expected failure when building on empty builder.", + CoseException.class, + () -> builder.build()); + } + + @Test + public void testBuilderPassOnMissingX() throws CborException, CoseException { + OkpKeyAgreementKey keyAgreementKey = OkpKeyAgreementKey.builder() + .withDParameter(D_BYTES) + .build(); + Assert.assertArrayEquals(keyAgreementKey.getPublicKeyBytes(), X_BYTES); + } + + @Test + public void testBuilderPassOnMissingD() throws CborException, CoseException { + OkpKeyAgreementKey.builder() + .withXCoordinate(X_BYTES) + .build(); + } + + @Test + public void testBuilderFailureOnWrongOperation() throws CborException, CoseException { + OkpKeyAgreementKey.Builder builder = OkpKeyAgreementKey.builder() + .withXCoordinate(X_BYTES) + .withDParameter(D_BYTES); + assertThrows( + "Expected builder to fail with wrong operations.", + CoseException.class, + () -> builder.withOperations(Headers.KEY_OPERATIONS_DECRYPT, Headers.KEY_OPERATIONS_SIGN)); + builder.build(); + } + + @Test + public void testEc2KeyParsingInOkpKeyAgreementKey() throws CborException { + String cborString = "A4010220012158205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA44" + + "4B624343167FE225820B16E8CF858DDC7690407BA61D4C338237A8CFCF3DE6AA672FC60A557AA32FC67"; + assertThrows( + CoseException.class, + () -> OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); + } + + @Test + public void testOkpKeyParsingWithIncorrectCurve() throws CborException { + String cborString = "A4010220012158205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA44" + + "4B624343167FE225820B16E8CF858DDC7690407BA61D4C338237A8CFCF3DE6AA672FC60A557AA32FC67"; + assertThrows( + CoseException.class, + () -> OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); + } + + @Test + public void testEmptyPrivateKeyBytes() throws CborException { + String cborString = "A401012004215820D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F" + + "707511A2340"; + assertThrows( + CoseException.class, + () -> OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); + } + + @Test + public void testEmptyPublicKeyBytes() throws CborException { + String cborString = "A40101200421402358209D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BA" + + "C031CAE7F60"; + assertThrows( + CoseException.class, + () -> OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); + } +} diff --git a/test/com/google/cose/OkpSigningKeyTest.java b/test/com/google/cose/OkpSigningKeyTest.java index c0e88ee..bd5e8d2 100644 --- a/test/com/google/cose/OkpSigningKeyTest.java +++ b/test/com/google/cose/OkpSigningKeyTest.java @@ -119,9 +119,10 @@ public void testEmptyBuilderFailure() throws CborException { @Test public void testBuilderPassOnMissingX() throws CborException, CoseException { - OkpSigningKey.builder() + OkpSigningKey signingKey = OkpSigningKey.builder() .withDParameter(D_BYTES) .build(); + Assert.assertArrayEquals(signingKey.getPublicKeyBytes(), X_BYTES); } @Test From b7d858bf3294402133ab34f8d5aedce9b88fa8e2 Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 14:24:38 +0000 Subject: [PATCH 4/6] Add more key pair generation options for OKP keys Provide a method for generating OkpKeyAgreementKey instances and enhance the builds with a method that populates the fields with a generated key pair. --- src/com/google/cose/OkpKeyAgreementKey.java | 35 +++++++++++++++++++ src/com/google/cose/OkpSigningKey.java | 28 ++++++++------- .../google/cose/OkpKeyAgreementKeyTest.java | 6 ++++ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/com/google/cose/OkpKeyAgreementKey.java b/src/com/google/cose/OkpKeyAgreementKey.java index 83abda5..ac6c874 100644 --- a/src/com/google/cose/OkpKeyAgreementKey.java +++ b/src/com/google/cose/OkpKeyAgreementKey.java @@ -19,8 +19,15 @@ import co.nstant.in.cbor.CborException; import co.nstant.in.cbor.model.DataItem; import com.google.cose.exceptions.CoseException; +import com.google.cose.utils.Algorithm; import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.XECPrivateKey; +import java.security.interfaces.XECPublicKey; +import java.security.spec.XECPublicKeySpec; import org.bouncycastle.math.ec.rfc7748.X25519; /** @@ -57,6 +64,21 @@ protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { return r; } + /** Generates a COSE formatted OKP key agreement key from scratch. */ + public static OkpKeyAgreementKey generateKey(Algorithm algorithm, int curve) + throws CborException, CoseException { + switch (algorithm) { + case ECDH_ES_HKDF_256: + return builder() + .withGeneratedKeyPair(curve) + .withAlgorithm(algorithm) + .build(); + + default: + throw new CoseException("Unsupported algorithm: " + algorithm.getJavaAlgorithmId()); + } + } + public static class Builder extends OkpKey.Builder { @Override public Builder self() { @@ -79,6 +101,19 @@ public Builder withOperations(Integer...operations) throws CoseException { } return super.withOperations(operations); } + + public Builder withGeneratedKeyPair(int curve) throws CoseException { + if (curve != Headers.CURVE_OKP_X25519) { + throw new CoseException("Unsupported curve: " + curve); + } + try { + KeyPair keyPair = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + return withDParameter(((XECPrivateKey) keyPair.getPrivate()).getScalar().get()) + .withXCoordinate(((XECPublicKey) keyPair.getPublic()).getU().toByteArray()); + } catch (GeneralSecurityException e) { + throw new CoseException("Failed to generate key pair", e); + } + } } public static Builder builder() { diff --git a/src/com/google/cose/OkpSigningKey.java b/src/com/google/cose/OkpSigningKey.java index 2bbd2d9..92fd5df 100644 --- a/src/com/google/cose/OkpSigningKey.java +++ b/src/com/google/cose/OkpSigningKey.java @@ -68,18 +68,8 @@ protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { /** Generates a COSE formatted OKP signing key from scratch */ public static OkpSigningKey generateKey() throws CborException, CoseException { - KeyPair keyPair; - try { - keyPair = KeyPair.newKeyPair(); - } catch (GeneralSecurityException e) { - throw new CoseException("Error while generating key pair: ", e); - } - byte[] privateKey = keyPair.getPrivateKey(); - byte[] publicKey = keyPair.getPublicKey(); - - return OkpSigningKey.builder() - .withXCoordinate(publicKey) - .withDParameter(privateKey) + return builder() + .withGeneratedKeyPair(Headers.CURVE_OKP_ED25519) .withAlgorithm(Algorithm.SIGNING_ALGORITHM_EDDSA) .build(); } @@ -106,6 +96,20 @@ public OkpSigningKey build() throws CborException, CoseException { withCurve(Headers.CURVE_OKP_ED25519); return new OkpSigningKey(compile()); } + + public Builder withGeneratedKeyPair(int curve) throws CoseException { + if (curve != Headers.CURVE_OKP_ED25519) { + throw new CoseException("Unsupported curve: " + curve); + } + KeyPair keyPair; + try { + keyPair = KeyPair.newKeyPair(); + } catch (GeneralSecurityException e) { + throw new CoseException("Error while generating key pair: ", e); + } + return withDParameter(keyPair.getPrivateKey()) + .withXCoordinate(keyPair.getPublicKey()); + } } public static Builder builder() { diff --git a/test/com/google/cose/OkpKeyAgreementKeyTest.java b/test/com/google/cose/OkpKeyAgreementKeyTest.java index 05ff705..fd4b29a 100644 --- a/test/com/google/cose/OkpKeyAgreementKeyTest.java +++ b/test/com/google/cose/OkpKeyAgreementKeyTest.java @@ -176,4 +176,10 @@ public void testEmptyPublicKeyBytes() throws CborException { CoseException.class, () -> OkpKeyAgreementKey.parse(TestUtilities.hexStringToByteArray(cborString))); } + + @Test + public void testOkpGeneratedKey_verificationWithSignature() throws CborException, CoseException { + OkpKeyAgreementKey okpKey = + OkpKeyAgreementKey.generateKey(Algorithm.ECDH_ES_HKDF_256, Headers.CURVE_OKP_X25519); + } } From 7c4e7ad1d0683a5cb59deb1ec4fc87dfe2df099a Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 07:38:28 +0000 Subject: [PATCH 5/6] Add an OkpKey method to convert to a PublicKey Each type of OkpKey overrides the method to provide the appropriate publick key. If using Java 15 or newer than the standard security providers are all that a client needs, otherwise a provider will needs to be installed that can handle these keys. --- src/com/google/cose/OkpKey.java | 4 +++ src/com/google/cose/OkpKeyAgreementKey.java | 19 ++++++++++++ src/com/google/cose/OkpSigningKey.java | 29 +++++++++++++++++++ .../google/cose/OkpKeyAgreementKeyTest.java | 1 + test/com/google/cose/OkpSigningKeyTest.java | 1 + 5 files changed, 54 insertions(+) diff --git a/src/com/google/cose/OkpKey.java b/src/com/google/cose/OkpKey.java index 223a965..a16ec89 100644 --- a/src/com/google/cose/OkpKey.java +++ b/src/com/google/cose/OkpKey.java @@ -9,6 +9,7 @@ import com.google.cose.exceptions.CoseException; import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; +import java.security.PublicKey; import java.util.Arrays; /** @@ -56,8 +57,11 @@ private byte[] getPublicKeyBytesFromCbor() throws CborException, CoseException { return publicFromPrivate(privateKeyBytes); } + protected abstract byte[] publicFromPrivate(byte[] privateKey) throws CoseException; + public abstract PublicKey getPublicKey() throws CoseException; + public byte[] getPublicKeyBytes() { return Arrays.copyOf(publicKeyBytes, publicKeyBytes.length); } diff --git a/src/com/google/cose/OkpKeyAgreementKey.java b/src/com/google/cose/OkpKeyAgreementKey.java index ac6c874..19417b0 100644 --- a/src/com/google/cose/OkpKeyAgreementKey.java +++ b/src/com/google/cose/OkpKeyAgreementKey.java @@ -22,11 +22,17 @@ import com.google.cose.utils.Algorithm; import com.google.cose.utils.CborUtils; import com.google.cose.utils.Headers; +import java.math.BigInteger; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.interfaces.XECPrivateKey; import java.security.interfaces.XECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.NamedParameterSpec; import java.security.spec.XECPublicKeySpec; import org.bouncycastle.math.ec.rfc7748.X25519; @@ -35,6 +41,8 @@ * Currently, only supports X25519 curve. */ public final class OkpKeyAgreementKey extends OkpKey { + private static final int SIGN_POSITIVE = 1; + public OkpKeyAgreementKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -64,6 +72,17 @@ protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { return r; } + @Override + public PublicKey getPublicKey() throws CoseException { + try { + BigInteger u = new BigInteger(SIGN_POSITIVE, publicKeyBytes); + XECPublicKeySpec spec = new XECPublicKeySpec(NamedParameterSpec.X25519, u); + return KeyFactory.getInstance("X25519").generatePublic(spec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new CoseException("Failed to generate X25519 public key", e); + } + } + /** Generates a COSE formatted OKP key agreement key from scratch. */ public static OkpKeyAgreementKey generateKey(Algorithm algorithm, int curve) throws CborException, CoseException { diff --git a/src/com/google/cose/OkpSigningKey.java b/src/com/google/cose/OkpSigningKey.java index 92fd5df..19b67c7 100644 --- a/src/com/google/cose/OkpSigningKey.java +++ b/src/com/google/cose/OkpSigningKey.java @@ -26,6 +26,11 @@ import com.google.crypto.tink.subtle.Ed25519Sign.KeyPair; import com.google.crypto.tink.subtle.Ed25519Verify; import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; /** @@ -66,6 +71,30 @@ protected byte[] publicFromPrivate(byte[] privateKey) throws CoseException { } } + @Override + public PublicKey getPublicKey() throws CoseException { + try { + // Ed25519 support was added to Java 15 with the EdECPublicKeySpec but, in order to support + // older versions, generate the key with an x509EncodedKeySpec with an encoded key as + // defined in rfc8410. Before Java 15, a security provider that can handle Ed25519 keys needs + // to be installed. + byte[] subjectPublicKeyInfo = + new byte[] { + 0x30, 0x2a, // SEQUENCE + 0x30, 0x05, // SEQUENCE + 0x06, 0x03, 0x2b, 0x65, 0x70, // OBJECT IDENTIFIER { 1 3 101 112 } + 0x03, 0x21, 0x00, // BIT STRING + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + System.arraycopy(publicKeyBytes, 0, subjectPublicKeyInfo, 12, 32); + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(subjectPublicKeyInfo); + return KeyFactory.getInstance("Ed25519").generatePublic(x509EncodedKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new CoseException("Failed to generate Ed25519 public key", e); + } + } + /** Generates a COSE formatted OKP signing key from scratch */ public static OkpSigningKey generateKey() throws CborException, CoseException { return builder() diff --git a/test/com/google/cose/OkpKeyAgreementKeyTest.java b/test/com/google/cose/OkpKeyAgreementKeyTest.java index fd4b29a..3b5add4 100644 --- a/test/com/google/cose/OkpKeyAgreementKeyTest.java +++ b/test/com/google/cose/OkpKeyAgreementKeyTest.java @@ -120,6 +120,7 @@ public void testBuilderPassOnMissingX() throws CborException, CoseException { .withDParameter(D_BYTES) .build(); Assert.assertArrayEquals(keyAgreementKey.getPublicKeyBytes(), X_BYTES); + Assert.assertNotNull(keyAgreementKey.getPublicKey()); } @Test diff --git a/test/com/google/cose/OkpSigningKeyTest.java b/test/com/google/cose/OkpSigningKeyTest.java index bd5e8d2..7dd8675 100644 --- a/test/com/google/cose/OkpSigningKeyTest.java +++ b/test/com/google/cose/OkpSigningKeyTest.java @@ -123,6 +123,7 @@ public void testBuilderPassOnMissingX() throws CborException, CoseException { .withDParameter(D_BYTES) .build(); Assert.assertArrayEquals(signingKey.getPublicKeyBytes(), X_BYTES); + Assert.assertNotNull(signingKey.getPublicKey()); } @Test From fd502788cb3b4d1cfde9449ce8639bcfab1ba70f Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Fri, 15 Sep 2023 10:22:09 +0000 Subject: [PATCH 6/6] Add a method to remove the private part of a key To enable a keypair to be encoded without exposing the private part, provide a method to return the same key with just the public information. This is a no-op if there is not private part to the key. This also adds methods to the builders that copy the values from an existing key. --- src/com/google/cose/CoseKey.java | 12 +++++++ src/com/google/cose/Ec2Key.java | 33 ++++++++++++++++++- src/com/google/cose/Ec2KeyAgreementKey.java | 9 +++++ src/com/google/cose/Ec2SigningKey.java | 9 +++++ src/com/google/cose/OkpKey.java | 15 +++++++++ src/com/google/cose/OkpKeyAgreementKey.java | 10 +++++- src/com/google/cose/OkpSigningKey.java | 10 +++++- .../google/cose/Ec2KeyAgreementKeyTest.java | 16 +++++++++ test/com/google/cose/Ec2SigningKeyTest.java | 16 +++++++++ .../google/cose/OkpKeyAgreementKeyTest.java | 14 ++++++++ test/com/google/cose/OkpSigningKeyTest.java | 14 ++++++++ 11 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/com/google/cose/CoseKey.java b/src/com/google/cose/CoseKey.java index a5a3e3c..53aa96a 100644 --- a/src/com/google/cose/CoseKey.java +++ b/src/com/google/cose/CoseKey.java @@ -180,6 +180,18 @@ protected Map compile() throws CoseException { return cborKey; } + public T copyFrom(CoseKey key) { + keyType = key.keyType; + keyId = key.keyId; + algorithm = (key.algorithm == null) ? null : Algorithm.fromCoseAlgorithmId(key.algorithm); + operations.clear(); + if (key.operations != null) { + operations.addAll(key.operations); + } + baseIv = key.baseIv; + return self(); + } + public T withKeyType(int keyType) { this.keyType = keyType; return self(); diff --git a/src/com/google/cose/Ec2Key.java b/src/com/google/cose/Ec2Key.java index 5c366ea..6027a53 100644 --- a/src/com/google/cose/Ec2Key.java +++ b/src/com/google/cose/Ec2Key.java @@ -27,6 +27,7 @@ public abstract class Ec2Key extends CoseKey { private static final int SIGN_POSITIVE = 1; + private int curve; protected KeyPair keyPair; Ec2Key(DataItem cborKey) throws CborException, CoseException { @@ -40,7 +41,7 @@ void populateKeyFromCbor() throws CborException, CoseException { } // Get curve information - int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); + curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); // Get private key. final ECPrivateKey privateKey; @@ -84,6 +85,12 @@ public ECPublicKey getPublicKey() { return (ECPublicKey) this.keyPair.getPublic(); } + public abstract Ec2Key getPublic() throws CborException, CoseException; + + public int getCurve() { + return curve; + } + void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, CoseException { Map keyMap = CborUtils.asMap(encode()); int curve = CborUtils.asInteger(keyMap.get(new NegativeInteger(Headers.KEY_PARAMETER_CURVE))); @@ -175,6 +182,30 @@ protected Map compile() throws CoseException { return cborKey; } + public T copyFrom(Ec2Key key) { + curve = key.curve; + int keySize; + switch (curve) { + case Headers.CURVE_EC2_P256: + keySize = 256; + break; + case Headers.CURVE_EC2_P384: + keySize = 384; + break; + case Headers.CURVE_EC2_P521: + keySize = 521; + break; + default: + throw new AssertionError(); + } + ECPublicKey pubKey = (ECPublicKey) key.keyPair.getPublic(); + ECPrivateKey privKey = (ECPrivateKey) key.keyPair.getPrivate(); + xCor = arrayFromBigNum(pubKey.getW().getAffineX(), keySize); + yCor = arrayFromBigNum(pubKey.getW().getAffineY(), keySize); + dParameter = (privKey == null) ? null : privKey.getS().toByteArray(); + return super.copyFrom(key); + } + public T withCurve(int curve) throws CoseException { if ((curve < 0) || (curve > Headers.CURVE_EC2_P521)) { throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE); diff --git a/src/com/google/cose/Ec2KeyAgreementKey.java b/src/com/google/cose/Ec2KeyAgreementKey.java index 01c9c5a..7b8cc3d 100644 --- a/src/com/google/cose/Ec2KeyAgreementKey.java +++ b/src/com/google/cose/Ec2KeyAgreementKey.java @@ -43,6 +43,15 @@ public static Ec2KeyAgreementKey decode(DataItem cborKey) throws CborException, return new Ec2KeyAgreementKey(cborKey); } + @Override + public Ec2KeyAgreementKey getPublic() throws CborException, CoseException { + if (keyPair.getPrivate() == null) { + return this; + } else { + return builder().copyFrom(this).withPrivateKeyRepresentation().withDParameter(null).build(); + } + } + /** Generates a COSE formatted Ec2 key agreement key given a specific algorithm and curve. */ public static Ec2KeyAgreementKey generateKey(Algorithm algorithm, int curve) throws CborException, CoseException { diff --git a/src/com/google/cose/Ec2SigningKey.java b/src/com/google/cose/Ec2SigningKey.java index 9f88e52..18d7f6a 100644 --- a/src/com/google/cose/Ec2SigningKey.java +++ b/src/com/google/cose/Ec2SigningKey.java @@ -50,6 +50,15 @@ public static Ec2SigningKey decode(DataItem cborKey) throws CborException, CoseE return new Ec2SigningKey(cborKey); } + @Override + public Ec2SigningKey getPublic() throws CborException, CoseException { + if (keyPair.getPrivate() == null) { + return this; + } else { + return builder().copyFrom(this).withPrivateKeyRepresentation().withDParameter(null).build(); + } + } + /** * Generates a COSE formatted Ec2 signing key given a specific algorithm. The selected key size is * chosen based on section 6.2.1 of RFC 5656 diff --git a/src/com/google/cose/OkpKey.java b/src/com/google/cose/OkpKey.java index a16ec89..3b65115 100644 --- a/src/com/google/cose/OkpKey.java +++ b/src/com/google/cose/OkpKey.java @@ -18,6 +18,7 @@ public abstract class OkpKey extends CoseKey { protected byte[] privateKeyBytes; protected byte[] publicKeyBytes; + protected int curve; OkpKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); @@ -30,6 +31,7 @@ public abstract class OkpKey extends CoseKey { void populateKeyFromCbor() throws CborException, CoseException { privateKeyBytes = getPrivateKeyBytesFromCbor(); publicKeyBytes = getPublicKeyBytesFromCbor(); + curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); } private byte[] getPrivateKeyBytesFromCbor() throws CborException, CoseException { @@ -62,10 +64,16 @@ private byte[] getPublicKeyBytesFromCbor() throws CborException, CoseException { public abstract PublicKey getPublicKey() throws CoseException; + public abstract OkpKey getPublic() throws CborException, CoseException; + public byte[] getPublicKeyBytes() { return Arrays.copyOf(publicKeyBytes, publicKeyBytes.length); } + public int getCurve() { + return curve; + } + /** Recursive builder to build out the Ec2 key and its subclasses. */ abstract static class Builder> extends CoseKey.Builder { private Integer curve = null; @@ -102,6 +110,13 @@ protected Map compile() throws CoseException { return cborKey; } + public T copyFrom(OkpKey key) { + curve = key.curve; + dParameter = key.privateKeyBytes; + xCor = key.publicKeyBytes; + return super.copyFrom(key); + } + public T withCurve(int curve) throws CoseException { if ((curve != Headers.CURVE_OKP_X25519) && (curve != Headers.CURVE_OKP_ED25519)) { throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE); diff --git a/src/com/google/cose/OkpKeyAgreementKey.java b/src/com/google/cose/OkpKeyAgreementKey.java index 19417b0..58018b9 100644 --- a/src/com/google/cose/OkpKeyAgreementKey.java +++ b/src/com/google/cose/OkpKeyAgreementKey.java @@ -46,7 +46,6 @@ public final class OkpKeyAgreementKey extends OkpKey { public OkpKeyAgreementKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); - int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); if (curve != Headers.CURVE_OKP_X25519) { throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE); } @@ -83,6 +82,15 @@ public PublicKey getPublicKey() throws CoseException { } } + @Override + public OkpKeyAgreementKey getPublic() throws CborException, CoseException { + if (privateKeyBytes == null) { + return this; + } else { + return builder().copyFrom(this).withDParameter(null).build(); + } + } + /** Generates a COSE formatted OKP key agreement key from scratch. */ public static OkpKeyAgreementKey generateKey(Algorithm algorithm, int curve) throws CborException, CoseException { diff --git a/src/com/google/cose/OkpSigningKey.java b/src/com/google/cose/OkpSigningKey.java index 19b67c7..c3c7eb1 100644 --- a/src/com/google/cose/OkpSigningKey.java +++ b/src/com/google/cose/OkpSigningKey.java @@ -41,7 +41,6 @@ public final class OkpSigningKey extends OkpKey { public OkpSigningKey(DataItem cborKey) throws CborException, CoseException { super(cborKey); - int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE)); if (curve != Headers.CURVE_OKP_ED25519) { throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE); } @@ -95,6 +94,15 @@ public PublicKey getPublicKey() throws CoseException { } } + @Override + public OkpSigningKey getPublic() throws CborException, CoseException { + if (privateKeyBytes == null ) { + return this; + } else { + return builder().copyFrom(this).withDParameter(null).build(); + } + } + /** Generates a COSE formatted OKP signing key from scratch */ public static OkpSigningKey generateKey() throws CborException, CoseException { return builder() diff --git a/test/com/google/cose/Ec2KeyAgreementKeyTest.java b/test/com/google/cose/Ec2KeyAgreementKeyTest.java index eadf6dc..df750b9 100644 --- a/test/com/google/cose/Ec2KeyAgreementKeyTest.java +++ b/test/com/google/cose/Ec2KeyAgreementKeyTest.java @@ -138,6 +138,22 @@ public void testBuilderFailureWrongOperation() throws CoseException { () -> builder.withOperations(Headers.KEY_OPERATIONS_DECRYPT, Headers.KEY_OPERATIONS_SIGN)); } + @Test + public void testGetPublic() throws CborException, CoseException { + Ec2KeyAgreementKey.Builder builder = Ec2KeyAgreementKey.builder() + .withCurve(Headers.CURVE_EC2_P256) + .withXCoordinate(X_BYTES) + .withYCoordinate(Y_BYTES); + Ec2KeyAgreementKey publicKey = builder.build(); + Ec2KeyAgreementKey keyPairPublic = builder + .withPrivateKeyRepresentation().withDParameter(D_BYTES) + .build() + .getPublic(); + + Assert.assertSame(publicKey.getPublic(), publicKey); + Assert.assertArrayEquals(keyPairPublic.serialize(), publicKey.serialize()); + } + @Test public void testOkpKeyParsingInEc2KeyAgreementKey() throws CborException { String cborString = "A401012006215820D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F" diff --git a/test/com/google/cose/Ec2SigningKeyTest.java b/test/com/google/cose/Ec2SigningKeyTest.java index 52a36eb..619448e 100644 --- a/test/com/google/cose/Ec2SigningKeyTest.java +++ b/test/com/google/cose/Ec2SigningKeyTest.java @@ -192,6 +192,22 @@ public void testBuilderFailureWrongOperation() throws CoseException { () -> builder.withOperations(Headers.KEY_OPERATIONS_DECRYPT, Headers.KEY_OPERATIONS_SIGN)); } + @Test + public void testGetPublic() throws CborException, CoseException { + Ec2KeyAgreementKey.Builder builder = Ec2KeyAgreementKey.builder() + .withCurve(Headers.CURVE_EC2_P256) + .withXCoordinate(X_BYTES) + .withYCoordinate(Y_BYTES); + Ec2KeyAgreementKey publicKey = builder.build(); + Ec2KeyAgreementKey keyPairPublic = builder + .withPrivateKeyRepresentation().withDParameter(D_BYTES) + .build() + .getPublic(); + + Assert.assertSame(publicKey.getPublic(), publicKey); + Assert.assertArrayEquals(keyPairPublic.serialize(), publicKey.serialize()); + } + @Test public void testOkpKeyParsingInEc2SigningKey() throws CborException { String cborString = "A401012006215820D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F" diff --git a/test/com/google/cose/OkpKeyAgreementKeyTest.java b/test/com/google/cose/OkpKeyAgreementKeyTest.java index 3b5add4..65edaed 100644 --- a/test/com/google/cose/OkpKeyAgreementKeyTest.java +++ b/test/com/google/cose/OkpKeyAgreementKeyTest.java @@ -142,6 +142,20 @@ public void testBuilderFailureOnWrongOperation() throws CborException, CoseExcep builder.build(); } + @Test + public void testGetPublic() throws CborException, CoseException { + OkpKeyAgreementKey.Builder builder = OkpKeyAgreementKey.builder() + .withXCoordinate(X_BYTES); + OkpKeyAgreementKey publicKey = builder.build(); + OkpKeyAgreementKey keyPairPublic = builder + .withDParameter(D_BYTES) + .build() + .getPublic(); + + Assert.assertSame(publicKey.getPublic(), publicKey); + Assert.assertArrayEquals(keyPairPublic.serialize(), publicKey.serialize()); + } + @Test public void testEc2KeyParsingInOkpKeyAgreementKey() throws CborException { String cborString = "A4010220012158205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA44" diff --git a/test/com/google/cose/OkpSigningKeyTest.java b/test/com/google/cose/OkpSigningKeyTest.java index 7dd8675..55232ff 100644 --- a/test/com/google/cose/OkpSigningKeyTest.java +++ b/test/com/google/cose/OkpSigningKeyTest.java @@ -145,6 +145,20 @@ public void testBuilderFailureOnWrongOperation() throws CborException, CoseExcep builder.build(); } + @Test + public void testGetPublic() throws CborException, CoseException { + OkpSigningKey.Builder builder = OkpSigningKey.builder() + .withXCoordinate(X_BYTES); + OkpSigningKey publicKey = builder.build(); + OkpSigningKey keyPairPublic = builder + .withDParameter(D_BYTES) + .build() + .getPublic(); + + Assert.assertSame(publicKey.getPublic(), publicKey); + Assert.assertArrayEquals(keyPairPublic.serialize(), publicKey.serialize()); + } + @Test public void testEc2KeyParsingInOkpSigningKey() throws CborException { String cborString = "A4010220012158205A88D182BCE5F42EFA59943F33359D2E8A968FF289D93E5FA44"