diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-12 03:08:53 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-12 03:08:53 +0000 |
commit | 02492861dec3c648ddea56a360b12a1e1cacabc0 (patch) | |
tree | 29feefb41752f6511093280b553283971f7f6653 | |
parent | a62e3c938071fc0d640890e88db2910d8c30fae5 (diff) | |
parent | 55d528ec8f040daf19f02564cd6c6cd5b27359ca (diff) | |
download | RemoteProvisioner-gki13-boot-release.tar.gz |
Snap for 9051340 from 55d528ec8f040daf19f02564cd6c6cd5b27359ca to gki13-boot-releasegki13-boot-release
Change-Id: I5330fa6b0d710e1b9ee4f758f1390014066acfd8
3 files changed, 514 insertions, 215 deletions
diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java index ce47880..d1f7364 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java @@ -16,6 +16,7 @@ package com.android.remoteprovisioner.unittest; +import static android.hardware.security.keymint.SecurityLevel.STRONGBOX; import static android.hardware.security.keymint.SecurityLevel.TRUSTED_ENVIRONMENT; import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC; import static android.security.keystore.KeyProperties.PURPOSE_SIGN; @@ -137,6 +138,7 @@ public class ServerToSystemTest { private static Context sContext; private static IRemoteProvisioning sBinder; private static int sCurve = 0; + private static ImplInfo[] sInfo; private Duration mDuration; @@ -161,8 +163,9 @@ public class ServerToSystemTest { } private void assertPoolStatus(int total, int attested, - int unassigned, int expiring, Duration time) throws Exception { - AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), TRUSTED_ENVIRONMENT); + int unassigned, int expiring, Duration time, + int securityLevel) throws Exception { + AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), securityLevel); assertEquals(total, pool.total); assertEquals(attested, pool.attested); assertEquals(unassigned, pool.unassigned); @@ -187,16 +190,49 @@ public class ServerToSystemTest { return certs; } + private void testFullRoundTrip(int securityLevel) throws Exception { + ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext); + int numTestKeys = 1; + int sCurve = 0; + for (int i = 0; i < sInfo.length; i++) { + if (sInfo[i].secLevel == securityLevel) { + sCurve = sInfo[i].supportedCurve; + break; + } + } + Assume.assumeFalse( + "Skipping this test as there is no implementation for the provided security level: " + + securityLevel, + (sCurve == 0)); + assertPoolStatus(0, 0, 0, 0, mDuration, securityLevel); + sBinder.generateKeyPair(IS_TEST_MODE, securityLevel); + assertPoolStatus(numTestKeys, 0, 0, 0, mDuration, securityLevel); + GeekResponse geek = ServerInterface.fetchGeek(sContext, metrics); + assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext)); + assertNotNull(geek); + int numProvisioned = + Provisioner.provisionCerts(numTestKeys, securityLevel, + geek.getGeekChain(sCurve), geek.getChallenge(), sBinder, + sContext, metrics); + assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext)); + assertEquals(numTestKeys, numProvisioned); + assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration, securityLevel); + // Certificate duration sent back from the server may change, however ~6 months should be + // pretty safe. + assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, + numTestKeys, mDuration.plusDays(180), securityLevel); + } + @BeforeClass public static void init() throws Exception { sContext = ApplicationProvider.getApplicationContext(); sBinder = - IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); + IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); assertNotNull(sBinder); - ImplInfo[] info = sBinder.getImplementationInfo(); - for (int i = 0; i < info.length; i++) { - if (info[i].secLevel == TRUSTED_ENVIRONMENT) { - sCurve = info[i].supportedCurve; + sInfo = sBinder.getImplementationInfo(); + for (int i = 0; i < sInfo.length; i++) { + if (sInfo[i].secLevel == TRUSTED_ENVIRONMENT) { + sCurve = sInfo[i].supportedCurve; break; } } @@ -216,26 +252,13 @@ public class ServerToSystemTest { } @Test - public void testFullRoundTrip() throws Exception { - ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext); - int numTestKeys = 1; - assertPoolStatus(0, 0, 0, 0, mDuration); - sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT); - assertPoolStatus(numTestKeys, 0, 0, 0, mDuration); - GeekResponse geek = ServerInterface.fetchGeek(sContext, metrics); - assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext)); - assertNotNull(geek); - int numProvisioned = - Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT, - geek.getGeekChain(sCurve), geek.getChallenge(), sBinder, - sContext, metrics); - assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext)); - assertEquals(numTestKeys, numProvisioned); - assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration); - // Certificate duration sent back from the server may change, however ~6 months should be - // pretty safe. - assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, - numTestKeys, mDuration.plusDays(180)); + public void testFullRoundTripTee() throws Exception { + testFullRoundTrip(TRUSTED_ENVIRONMENT); + } + + @Test + public void testFullRoundTripStrongbox() throws Exception { + testFullRoundTrip(STRONGBOX); } @Test @@ -245,12 +268,14 @@ public class ServerToSystemTest { PeriodicProvisioner.class, Executors.newSingleThreadExecutor()).build(); assertEquals(provisioner.doWork(), ListenableWorker.Result.success()); - AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(), - TRUSTED_ENVIRONMENT); - assertTrue("Pool must not be empty", pool.total > 0); - assertEquals("All keys must be attested", pool.total, pool.attested); - assertEquals("Nobody should have consumed keys yet", pool.total, pool.unassigned); - assertEquals("All keys should be freshly generated", 0, pool.expiring); + for (int i = 0; i < sInfo.length; i++) { + AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(), + sInfo[i].secLevel); + assertTrue("Pool must not be empty", pool.total > 0); + assertEquals("All keys must be attested", pool.total, pool.attested); + assertEquals("Nobody should have consumed keys yet", pool.total, pool.unassigned); + assertEquals("All keys should be freshly generated", 0, pool.expiring); + } } @Test @@ -262,17 +287,25 @@ public class ServerToSystemTest { PeriodicProvisioner.class, Executors.newSingleThreadExecutor()).build(); assertEquals(provisioner.doWork(), ListenableWorker.Result.success()); - final AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(), - TRUSTED_ENVIRONMENT); - assertTrue("Pool must not be empty", pool.total > 0); - assertEquals("All keys must be attested", pool.total, pool.attested); - assertEquals("Nobody should have consumed keys yet", pool.total, pool.unassigned); - assertEquals("All keys should be freshly generated", 0, pool.expiring); + AttestationPoolStatus[] pools = new AttestationPoolStatus[sInfo.length]; + for (int i = 0; i < sInfo.length; i++) { + pools[i] = sBinder.getPoolStatus(mDuration.toMillis(), + sInfo[i].secLevel); + assertTrue("Pool must not be empty", pools[i].total > 0); + assertEquals("All keys must be attested", pools[i].total, pools[i].attested); + assertEquals("Nobody should have consumed keys yet", pools[i].total, + pools[i].unassigned); + assertEquals("All keys should be freshly generated", 0, pools[i].expiring); + } // The metrics host test will perform additional validation by ensuring correct metrics // are recorded. assertEquals(provisioner.doWork(), ListenableWorker.Result.success()); - assertPoolStatus(pool.total, pool.attested, pool.unassigned, pool.expiring, mDuration); + for (int i = 0; i < pools.length; i++) { + assertPoolStatus(pools[i].total, pools[i].attested, pools[i].unassigned, + pools[i].expiring, mDuration, + sInfo[i].secLevel); + } } @Test @@ -286,12 +319,14 @@ public class ServerToSystemTest { PeriodicProvisioner.class, Executors.newSingleThreadExecutor()).build(); assertEquals(provisioner.doWork(), ListenableWorker.Result.failure()); - AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(), - TRUSTED_ENVIRONMENT); - assertTrue("Keys should have been generated", pool.total > 0); - assertEquals("No keys should be attested", 0, pool.attested); - assertEquals("No keys should have been assigned", 0, pool.unassigned); - assertEquals("No keys can possibly be expiring yet", 0, pool.expiring); + for (int i = 0; i < sInfo.length; i++) { + AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(), + sInfo[i].secLevel); + assertTrue("Keys should have been generated", pool.total > 0); + assertEquals("No keys should be attested", 0, pool.attested); + assertEquals("No keys should have been assigned", 0, pool.unassigned); + assertEquals("No keys can possibly be expiring yet", 0, pool.expiring); + } } @Test @@ -331,7 +366,9 @@ public class ServerToSystemTest { PeriodicProvisioner.class, Executors.newSingleThreadExecutor()).build(); assertEquals(provisioner.doWork(), ListenableWorker.Result.success()); - assertPoolStatus(0, 0, 0, 0, mDuration); + for (int i = 0; i < sInfo.length; i++) { + assertPoolStatus(0, 0, 0, 0, mDuration, sInfo[i].secLevel); + } } @Test @@ -345,7 +382,7 @@ public class ServerToSystemTest { SettingsManager.setDeviceConfig(sContext, 1 /* extraKeys */, mDuration /* expiringBy */, "Not even a URL" /* url */); int numTestKeys = 1; - assertPoolStatus(0, 0, 0, 0, mDuration); + assertPoolStatus(0, 0, 0, 0, mDuration, TRUSTED_ENVIRONMENT); // Note that due to the GenerateRkpKeyService, this call to generate an attested key will // still cause the service to generate keys up the number specified as `extraKeys` in the // `setDeviceConfig`. This will provide us 1 key for the followup call to provisionCerts. @@ -358,7 +395,7 @@ public class ServerToSystemTest { geek.getGeekChain(sCurve), geek.getChallenge(), sBinder, sContext, metrics); assertEquals(numTestKeys, numProvisioned); - assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration); + assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration, TRUSTED_ENVIRONMENT); Certificate[] provisionedKeyCerts = generateKeyStoreKey("test2"); sBinder.deleteAllKeys(); sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT); @@ -472,7 +509,7 @@ public class ServerToSystemTest { public void testRetryWithoutNetworkTee() throws Exception { setAirplaneMode(true); try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) { - assertPoolStatus(0, 0, 0, 0, mDuration); + assertPoolStatus(0, 0, 0, 0, mDuration, TRUSTED_ENVIRONMENT); generateKeyStoreKey("should-never-succeed"); Assert.fail("Expected a keystore exception"); } catch (ProviderException e) { @@ -534,7 +571,7 @@ public class ServerToSystemTest { "http://localhost:" + server.getListeningPort() + "/"); try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) { - assertPoolStatus(0, 0, 0, 0, mDuration); + assertPoolStatus(0, 0, 0, 0, mDuration, TRUSTED_ENVIRONMENT); generateKeyStoreKey("should-never-succeed"); Assert.fail("Expected a keystore exception"); } catch (ProviderException e) { diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java index 9203803..52991b6 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java @@ -19,6 +19,8 @@ package com.android.remoteprovisioner.unittest; import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC; import static android.security.keystore.KeyProperties.PURPOSE_SIGN; +import static com.android.remoteprovisioner.unittest.Utils.CURVE_ED25519; +import static com.android.remoteprovisioner.unittest.Utils.CURVE_P256; import static com.android.remoteprovisioner.unittest.Utils.generateEcdsaKeyPair; import static com.android.remoteprovisioner.unittest.Utils.getP256PubKeyFromBytes; import static com.android.remoteprovisioner.unittest.Utils.signPublicKey; @@ -35,30 +37,37 @@ import android.os.ServiceManager; import android.platform.test.annotations.Presubmit; import android.security.keystore.KeyGenParameterSpec; import android.security.remoteprovisioning.IRemoteProvisioning; +import android.security.remoteprovisioning.ImplInfo; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; +import com.android.remoteprovisioner.CborUtils; import com.android.remoteprovisioner.ProvisionerMetrics; import com.android.remoteprovisioner.SystemInterface; import com.android.remoteprovisioner.X509Utils; +import com.google.crypto.tink.subtle.EllipticCurves; import com.google.crypto.tink.subtle.Hkdf; import com.google.crypto.tink.subtle.X25519; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.time.Duration; import java.time.Instant; import java.util.Arrays; @@ -86,12 +95,14 @@ public class SystemInterfaceTest { private static final String SERVICE = "android.security.remoteprovisioning"; private IRemoteProvisioning mBinder; + private ImplInfo[] mInfo; @Before public void setUp() throws Exception { mBinder = IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); assertNotNull(mBinder); + mInfo = mBinder.getImplementationInfo(); mBinder.deleteAllKeys(); } @@ -100,52 +111,116 @@ public class SystemInterfaceTest { mBinder.deleteAllKeys(); } - private byte[] generateEekChain(byte[] eek) throws Exception { - com.google.crypto.tink.subtle.Ed25519Sign.KeyPair kp = - com.google.crypto.tink.subtle.Ed25519Sign.KeyPair.newKeyPair(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .addArray() + private byte[] generateEekChain(int curve, byte[] eek) throws Exception { + if (curve == Utils.CURVE_ED25519) { + com.google.crypto.tink.subtle.Ed25519Sign.KeyPair kp = + com.google.crypto.tink.subtle.Ed25519Sign.KeyPair.newKeyPair(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CborEncoder(baos).encode(new CborBuilder() + .addArray() .add(Utils.encodeAndSignSign1Ed25519( Utils.encodeEd25519PubKey(kp.getPublicKey()), kp.getPrivateKey())) .add(Utils.encodeAndSignSign1Ed25519( Utils.encodeX25519PubKey(eek), kp.getPrivateKey())) .end() - .build()); - return baos.toByteArray(); + .build()); + return baos.toByteArray(); + } else if (curve == Utils.CURVE_P256) { // P256 + // Root + KeyPair ecdsaKeyPair = generateEcdsaKeyPair(); + ECPublicKey pubKey = (ECPublicKey) ecdsaKeyPair.getPublic(); + ECPrivateKey privKey = (ECPrivateKey) ecdsaKeyPair.getPrivate(); + byte[] pubKeyBytes = Utils.getBytesFromP256PublicKey(pubKey); + byte[] pubx = new byte[32]; + byte[] puby = new byte[32]; + System.arraycopy(pubKeyBytes, 0, pubx, 0, 32); + System.arraycopy(pubKeyBytes, 32, puby, 0, 32); + + BigInteger priv = privKey.getS(); + byte[] privBytes = priv.toByteArray(); + byte[] signingKey = new byte[32]; + if (privBytes.length <= 32) { + System.arraycopy(privBytes, 0, signingKey, 32 + - privBytes.length, privBytes.length); + } else if (privBytes.length == 33 && privBytes[0] == 0) { + System.arraycopy(privBytes, 1, signingKey, 0, 32); + } else { + throw new IllegalStateException("EC private key value is too large"); + } + + byte[] eekPubX = new byte[32]; + byte[] eekPubY = new byte[32]; + System.arraycopy(eek, 0, eekPubX, 0, 32); + System.arraycopy(eek, 32, eekPubY, 0, 32); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CborEncoder(baos).encode(new CborBuilder() + .addArray() + .add(Utils.encodeAndSignSign1Ecdsa256( + Utils.encodeP256PubKey(pubx, puby, false), signingKey)) + .add(Utils.encodeAndSignSign1Ecdsa256( + Utils.encodeP256PubKey(eekPubX, eekPubY, true), signingKey)) + .end() + .build()); + return baos.toByteArray(); + } else { + Assert.fail("Unsupported curve: " + curve); + } + return null; } @Presubmit @Test public void testGenerateCSR() throws Exception { - ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( - ApplicationProvider.getApplicationContext(), SecurityLevel.TRUSTED_ENVIRONMENT); - DeviceInfo deviceInfo = new DeviceInfo(); - ProtectedData encryptedBundle = new ProtectedData(); - byte[] eek = new byte[32]; - new Random().nextBytes(eek); - byte[] bundle = - SystemInterface.generateCsr(true /* testMode */, 0 /* numKeys */, - SecurityLevel.TRUSTED_ENVIRONMENT, - generateEekChain(eek), - new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder, - metrics); - // encryptedBundle should contain a COSE_Encrypt message - ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - assertEquals(1, dataItems.size()); - assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); - Array encMsg = (Array) dataItems.get(0); - assertEquals(4, encMsg.getDataItems().size()); + for (int i = 0; i < mInfo.length; i++) { + if (mInfo[i].supportedCurve == 0) { + continue; + } + ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( + ApplicationProvider.getApplicationContext(), mInfo[i].secLevel); + DeviceInfo deviceInfo = new DeviceInfo(); + ProtectedData encryptedBundle = new ProtectedData(); + byte[] eekChain = null; + byte[] eekPub; + if (mInfo[i].supportedCurve == CborUtils.EC_CURVE_P256) { + KeyPair eekEcdsaKeyPair = generateEcdsaKeyPair(); + ECPublicKey eekPubKey = (ECPublicKey) eekEcdsaKeyPair.getPublic(); + eekPub = Utils.getBytesFromP256PublicKey(eekPubKey); + eekChain = generateEekChain(CURVE_P256, eekPub); + } else if (mInfo[i].supportedCurve == CborUtils.EC_CURVE_25519) { + eekPub = new byte[32]; + new Random().nextBytes(eekPub); + eekChain = generateEekChain(CURVE_ED25519, eekPub); + } else { + Assert.fail("Unsupported curve: " + mInfo[i].supportedCurve); + } + assertNotNull(eekChain); + byte[] bundle = + SystemInterface.generateCsr(true /* testMode */, 0 /* numKeys */, + mInfo[i].secLevel, + eekChain, + new byte[]{0x02}, encryptedBundle, + deviceInfo, mBinder, metrics); + // encryptedBundle should contain a COSE_Encrypt message + ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); + List<DataItem> dataItems = new CborDecoder(bais).decode(); + assertEquals(1, dataItems.size()); + assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); + Array encMsg = (Array) dataItems.get(0); + assertEquals(4, encMsg.getDataItems().size()); + } } - private static Certificate[] generateKeyStoreKey(String alias) throws Exception { + private static Certificate[] generateKeyStoreKey(String alias, int securityLevel) + throws Exception { + final boolean isStrongboxBacked = (securityLevel == SecurityLevel.STRONGBOX) ? true : false; KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC, "AndroidKeyStore"); KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN) .setAttestationChallenge("challenge".getBytes()) + .setIsStrongBoxBacked(isStrongboxBacked) .build(); keyPairGenerator.initialize(spec); keyPairGenerator.generateKeyPair(); @@ -157,84 +232,108 @@ public class SystemInterfaceTest { @Presubmit @Test public void testGenerateCSRProvisionAndUseKey() throws Exception { - ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( - ApplicationProvider.getApplicationContext(), SecurityLevel.TRUSTED_ENVIRONMENT); - DeviceInfo deviceInfo = new DeviceInfo(); - ProtectedData encryptedBundle = new ProtectedData(); int numKeys = 10; - byte[] eek = new byte[32]; - new Random().nextBytes(eek); - for (int i = 0; i < numKeys; i++) { - mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT); - } - byte[] bundle = - SystemInterface.generateCsr(true /* testMode */, numKeys, - SecurityLevel.TRUSTED_ENVIRONMENT, - generateEekChain(eek), - new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder, - metrics); - assertNotNull(bundle); - // The return value of generateCsr should be a COSE_Mac0 message - ByteArrayInputStream bais = new ByteArrayInputStream(bundle); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - assertEquals(1, dataItems.size()); - assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); - Array macMsg = (Array) dataItems.get(0); - assertEquals(4, macMsg.getDataItems().size()); - - // The payload for the COSE_Mac0 should contain the array of public keys - bais = new ByteArrayInputStream(((ByteString) macMsg.getDataItems().get(2)).getBytes()); - List<DataItem> publicKeysArr = new CborDecoder(bais).decode(); - assertEquals(1, publicKeysArr.size()); - assertEquals(MajorType.ARRAY, publicKeysArr.get(0).getMajorType()); - Array publicKeys = (Array) publicKeysArr.get(0); - assertEquals(numKeys, publicKeys.getDataItems().size()); - KeyPair rootKeyPair = generateEcdsaKeyPair(); - KeyPair intermediateKeyPair = generateEcdsaKeyPair(); - X509Certificate[][] certChain = new X509Certificate[numKeys][3]; - for (int i = 0; i < numKeys; i++) { - Map publicKey = (Map) publicKeys.getDataItems().get(i); - byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes(); - byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes(); - assertEquals(xPub.length, 32); - assertEquals(yPub.length, 32); - PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub); - certChain[i][0] = signPublicKey(intermediateKeyPair, leafKeyToSign); - certChain[i][1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic()); - certChain[i][2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic()); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - for (int j = 0; j < certChain[i].length; j++) { - os.write(certChain[i][j].getEncoded()); + int securityLevel; + for (int k = 0; k < mInfo.length; k++) { + if (mInfo[k].supportedCurve == 0) { + continue; } - Instant expiringBy = Instant.now().plusMillis(Duration.ofDays(4).toMillis()); - SystemInterface.provisionCertChain(X509Utils.getAndFormatRawPublicKey(certChain[i][0]), - certChain[i][0].getEncoded() /* leafCert */, - os.toByteArray() /* certChain */, - expiringBy.toEpochMilli() /* validity */, - SecurityLevel.TRUSTED_ENVIRONMENT, - mBinder, metrics); - } - // getPoolStatus will clean the key pool before we go to assign a new provisioned key - mBinder.getPoolStatus(0, SecurityLevel.TRUSTED_ENVIRONMENT); - Certificate[] provisionedCerts1 = generateKeyStoreKey("alias"); - Certificate[] provisionedCerts2 = generateKeyStoreKey("alias2"); - assertEquals(4, provisionedCerts1.length); - assertEquals(4, provisionedCerts2.length); - boolean matched = false; - for (int i = 0; i < certChain.length; i++) { - if (Arrays.equals(provisionedCerts1[1].getEncoded(), certChain[i][0].getEncoded())) { - matched = true; - assertArrayEquals("Second key: j = 0", - provisionedCerts2[1].getEncoded(), certChain[i][0].getEncoded()); - for (int j = 1; j < certChain[i].length; j++) { - assertArrayEquals("First key: j = " + j, - provisionedCerts1[j + 1].getEncoded(), certChain[i][j].getEncoded()); - assertArrayEquals("Second key: j = " + j, - provisionedCerts2[j + 1].getEncoded(), certChain[i][j].getEncoded()); + ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( + ApplicationProvider.getApplicationContext(), mInfo[k].secLevel); + securityLevel = mInfo[k].secLevel; + DeviceInfo deviceInfo = new DeviceInfo(); + ProtectedData encryptedBundle = new ProtectedData(); + byte[] eekChain = null; + byte[] eekPub; + if (mInfo[k].supportedCurve == CborUtils.EC_CURVE_P256) { + KeyPair eekEcdsaKeyPair = generateEcdsaKeyPair(); + ECPublicKey eekPubKey = (ECPublicKey) eekEcdsaKeyPair.getPublic(); + eekPub = Utils.getBytesFromP256PublicKey(eekPubKey); + eekChain = generateEekChain(CURVE_P256, eekPub); + } else if (mInfo[k].supportedCurve == CborUtils.EC_CURVE_25519) { + eekPub = new byte[32]; + new Random().nextBytes(eekPub); + eekChain = generateEekChain(CURVE_ED25519, eekPub); + } else { + Assert.fail("Unsupported curve: " + mInfo[k].supportedCurve); + } + assertNotNull(eekChain); + for (int i = 0; i < numKeys; i++) { + mBinder.generateKeyPair(true /* testMode */, securityLevel); + } + byte[] bundle = + SystemInterface.generateCsr(true /* testMode */, numKeys, + securityLevel, + eekChain, + new byte[]{0x02}, encryptedBundle, + deviceInfo, mBinder, metrics); + assertNotNull(bundle); + // The return value of generateCsr should be a COSE_Mac0 message + ByteArrayInputStream bais = new ByteArrayInputStream(bundle); + List<DataItem> dataItems = new CborDecoder(bais).decode(); + assertEquals(1, dataItems.size()); + assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); + Array macMsg = (Array) dataItems.get(0); + assertEquals(4, macMsg.getDataItems().size()); + + // The payload for the COSE_Mac0 should contain the array of public keys + bais = new ByteArrayInputStream(((ByteString) macMsg.getDataItems().get(2)).getBytes()); + List<DataItem> publicKeysArr = new CborDecoder(bais).decode(); + assertEquals(1, publicKeysArr.size()); + assertEquals(MajorType.ARRAY, publicKeysArr.get(0).getMajorType()); + Array publicKeys = (Array) publicKeysArr.get(0); + assertEquals(numKeys, publicKeys.getDataItems().size()); + KeyPair rootKeyPair = generateEcdsaKeyPair(); + KeyPair intermediateKeyPair = generateEcdsaKeyPair(); + X509Certificate[][] certChain = new X509Certificate[numKeys][3]; + for (int i = 0; i < numKeys; i++) { + Map publicKey = (Map) publicKeys.getDataItems().get(i); + byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes(); + byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes(); + assertEquals(xPub.length, 32); + assertEquals(yPub.length, 32); + PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub); + certChain[i][0] = signPublicKey(intermediateKeyPair, leafKeyToSign); + certChain[i][1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic()); + certChain[i][2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + for (int j = 0; j < certChain[i].length; j++) { + os.write(certChain[i][j].getEncoded()); + } + Instant expiringBy = Instant.now().plusMillis(Duration.ofDays(4).toMillis()); + SystemInterface.provisionCertChain( + X509Utils.getAndFormatRawPublicKey(certChain[i][0]), + certChain[i][0].getEncoded() /* leafCert */, + os.toByteArray() /* certChain */, + expiringBy.toEpochMilli() /* validity */, + securityLevel, + mBinder, metrics); + } + // getPoolStatus will clean the key pool before we go to assign a new provisioned key + mBinder.getPoolStatus(0, securityLevel); + Certificate[] provisionedCerts1 = generateKeyStoreKey("alias", securityLevel); + Certificate[] provisionedCerts2 = generateKeyStoreKey("alias2", securityLevel); + assertEquals(4, provisionedCerts1.length); + assertEquals(4, provisionedCerts2.length); + boolean matched = false; + for (int i = 0; i < certChain.length; i++) { + if (Arrays.equals(provisionedCerts1[1].getEncoded(), + certChain[i][0].getEncoded())) { + matched = true; + assertArrayEquals("Second key: j = 0", + provisionedCerts2[1].getEncoded(), certChain[i][0].getEncoded()); + for (int j = 1; j < certChain[i].length; j++) { + assertArrayEquals("First key: j = " + j, + provisionedCerts1[j + 1].getEncoded(), + certChain[i][j].getEncoded()); + assertArrayEquals("Second key: j = " + j, + provisionedCerts2[j + 1].getEncoded(), + certChain[i][j].getEncoded()); + } } } + assertTrue(matched); } - assertTrue(matched); } private static byte[] extractRecipientKey(Array recipients) { @@ -242,7 +341,18 @@ public class SystemInterfaceTest { Map recipientUnprotectedHeaders = (Map) ((Array) recipients.getDataItems().get(0)) .getDataItems().get(1); Map recipientKeyMap = (Map) recipientUnprotectedHeaders.get(new NegativeInteger(-1)); - return ((ByteString) recipientKeyMap.get(new NegativeInteger(-2))).getBytes(); + byte[] pubx = ((ByteString) recipientKeyMap.get(new NegativeInteger(-2))).getBytes(); + DataItem di = recipientKeyMap.get(new NegativeInteger(-3)); + if (di != null) { + byte[] puby = ((ByteString) di).getBytes(); + assertNotNull(puby); + assertEquals(puby.length, 32); + byte[] ret = new byte[64]; + System.arraycopy(pubx, 0, ret, 0, 32); + System.arraycopy(puby, 0, ret, 32, 32); + return ret; + } + return pubx; } private static byte[] buildKdfContext(byte[] serverPub, byte[] ephemeralPub) throws Exception { @@ -285,50 +395,92 @@ public class SystemInterfaceTest { @Presubmit @Test public void testDecryptProtectedPayload() throws Exception { - DeviceInfo deviceInfo = new DeviceInfo(); - ProtectedData encryptedBundle = new ProtectedData(); - ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( - ApplicationProvider.getApplicationContext(), SecurityLevel.TRUSTED_ENVIRONMENT); - int numKeys = 1; - byte[] eekPriv = X25519.generatePrivateKey(); - byte[] eekPub = X25519.publicFromPrivate(eekPriv); - mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT); - byte[] bundle = - SystemInterface.generateCsr(true /* testMode */, numKeys, - SecurityLevel.TRUSTED_ENVIRONMENT, - generateEekChain(eekPub), - new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder, - metrics); - ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - // Parse encMsg into components: protected and unprotected headers, payload, and recipient - List<DataItem> encMsg = ((Array) dataItems.get(0)).getDataItems(); - byte[] protectedHeaders = ((ByteString) encMsg.get(0)).getBytes(); - Map unprotectedHeaders = (Map) encMsg.get(1); - byte[] encryptedContent = ((ByteString) encMsg.get(2)).getBytes(); - Array recipients = (Array) encMsg.get(3); - - byte[] iv = ((ByteString) unprotectedHeaders.get(new UnsignedInteger(5))).getBytes(); - byte[] ephemeralPub = extractRecipientKey(recipients); - assertEquals(32, ephemeralPub.length); - byte[] sharedSecret = X25519.computeSharedSecret(eekPriv, ephemeralPub); - byte[] context = buildKdfContext(eekPub, ephemeralPub); - byte[] decryptionKey = Hkdf.computeHkdf("HMACSHA256", sharedSecret, null /* salt */, - context, 32); - - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init( - Cipher.DECRYPT_MODE, - new SecretKeySpec(decryptionKey, "AES"), - new GCMParameterSpec(128 /* iv length */, iv)); - cipher.updateAAD(buildEncStructure(protectedHeaders, new byte[0])); - - byte[] protectedData = cipher.doFinal(encryptedContent); - bais = new ByteArrayInputStream(protectedData); - List<DataItem> protectedDataArray = new CborDecoder(bais).decode(); - assertEquals(1, protectedDataArray.size()); - assertEquals(MajorType.ARRAY, protectedDataArray.get(0).getMajorType()); - List<DataItem> protectedDataPayload = ((Array) protectedDataArray.get(0)).getDataItems(); - assertTrue(protectedDataPayload.size() == 2 || protectedDataPayload.size() == 3); + int securityLevel; + for (int i = 0; i < mInfo.length; i++) { + if (mInfo[i].supportedCurve == 0) { + continue; + } + ProvisionerMetrics metrics = ProvisionerMetrics.createOutOfKeysAttemptMetrics( + ApplicationProvider.getApplicationContext(), mInfo[i].secLevel); + securityLevel = mInfo[i].secLevel; + DeviceInfo deviceInfo = new DeviceInfo(); + ProtectedData encryptedBundle = new ProtectedData(); + byte[] eekPriv = null; + byte[] eekPub = null; + byte[] eekChain = null; + int numKeys = 1; + if (mInfo[i].supportedCurve == CborUtils.EC_CURVE_P256) { + KeyPair eekEcdsaKeyPair = generateEcdsaKeyPair(); + ECPublicKey eekPubKey = (ECPublicKey) eekEcdsaKeyPair.getPublic(); + ECPrivateKey eekPrivKey = (ECPrivateKey) eekEcdsaKeyPair.getPrivate(); + eekPub = Utils.getBytesFromP256PublicKey(eekPubKey); + eekPriv = eekPrivKey.getS().toByteArray(); + eekChain = generateEekChain(CURVE_P256, eekPub); + } else if (mInfo[i].supportedCurve == CborUtils.EC_CURVE_25519) { + eekPriv = X25519.generatePrivateKey(); + eekPub = X25519.publicFromPrivate(eekPriv); + eekChain = generateEekChain(CURVE_ED25519, eekPub); + } else { + Assert.fail("Unsupported curve: " + mInfo[i].supportedCurve); + } + assertNotNull(eekChain); + assertNotNull(eekPriv); + assertNotNull(eekPub); + mBinder.generateKeyPair(true /* testMode */, securityLevel); + byte[] bundle = + SystemInterface.generateCsr(true /* testMode */, numKeys, + securityLevel, + eekChain, + new byte[]{0x02}, encryptedBundle, + deviceInfo, mBinder, metrics); + ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); + List<DataItem> dataItems = new CborDecoder(bais).decode(); + // Parse encMsg into components: protected and unprotected headers, payload, + // and recipient + List<DataItem> encMsg = ((Array) dataItems.get(0)).getDataItems(); + byte[] protectedHeaders = ((ByteString) encMsg.get(0)).getBytes(); + Map unprotectedHeaders = (Map) encMsg.get(1); + byte[] encryptedContent = ((ByteString) encMsg.get(2)).getBytes(); + Array recipients = (Array) encMsg.get(3); + + byte[] iv = ((ByteString) unprotectedHeaders.get( + new UnsignedInteger(5))).getBytes(); + byte[] ephemeralPub = extractRecipientKey(recipients); + byte[] sharedSecret; + if (mInfo[i].supportedCurve == CborUtils.EC_CURVE_P256) { + assertEquals(64, ephemeralPub.length); + ECPrivateKey privKey = EllipticCurves.getEcPrivateKey( + EllipticCurves.CurveType.NIST_P256, eekPriv); + byte[] pubx = new byte[32]; + byte[] puby = new byte[32]; + System.arraycopy(ephemeralPub, 0, pubx, 0, 32); + System.arraycopy(ephemeralPub, 32, puby, 0, 32); + ECPublicKey pubKey = EllipticCurves.getEcPublicKey( + EllipticCurves.CurveType.NIST_P256, pubx, puby); + sharedSecret = EllipticCurves.computeSharedSecret(privKey, pubKey); + } else { // CborUtils.EC_CURVE_25519 + assertEquals(32, ephemeralPub.length); + sharedSecret = X25519.computeSharedSecret(eekPriv, ephemeralPub); + } + byte[] context = buildKdfContext(eekPub, ephemeralPub); + byte[] decryptionKey = Hkdf.computeHkdf("HMACSHA256", sharedSecret, + null /* salt */, context, 32); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init( + Cipher.DECRYPT_MODE, + new SecretKeySpec(decryptionKey, "AES"), + new GCMParameterSpec(128 /* iv length */, iv)); + cipher.updateAAD(buildEncStructure(protectedHeaders, new byte[0])); + + byte[] protectedData = cipher.doFinal(encryptedContent); + bais = new ByteArrayInputStream(protectedData); + List<DataItem> protectedDataArray = new CborDecoder(bais).decode(); + assertEquals(1, protectedDataArray.size()); + assertEquals(MajorType.ARRAY, protectedDataArray.get(0).getMajorType()); + List<DataItem> protectedDataPayload = ((Array) protectedDataArray.get( + 0)).getDataItems(); + assertTrue(protectedDataPayload.size() == 2 || protectedDataPayload.size() == 3); + } } } diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java index 7ece397..b341352 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java @@ -16,11 +16,13 @@ package com.android.remoteprovisioner.unittest; -import com.google.crypto.tink.subtle.Ed25519Sign; -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborEncoder; -import co.nstant.in.cbor.model.Array; +import static com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding.IEEE_P1363; +import static com.google.crypto.tink.subtle.Enums.HashType.SHA256; + +import com.google.crypto.tink.subtle.EcdsaSignJce; +import com.google.crypto.tink.subtle.Ed25519Sign; +import com.google.crypto.tink.subtle.EllipticCurves; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; @@ -36,6 +38,8 @@ import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; @@ -43,23 +47,34 @@ import java.security.spec.ECPublicKeySpec; import java.time.Duration; import java.time.Instant; import java.util.Date; +import java.util.List; import javax.security.auth.x500.X500Principal; +import co.nstant.in.cbor.CborBuilder; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.builder.MapBuilder; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.DataItem; + /** * Utility class for unit testing. */ public class Utils { private static final int KEY_TYPE = 1; private static final int KEY_TYPE_OKP = 1; + private static final int KEY_TYPE_EC2 = 2; private static final int KID = 2; private static final int ALGORITHM = 3; private static final int ALGORITHM_EDDSA = -8; + private static final int ALGORITHM_ES256 = -7; private static final int ALGORITHM_ECDH_ES_HKDF_256 = -25; private static final int CURVE = -1; - private static final int CURVE_X25519 = 4; - private static final int CURVE_ED25519 = 6; + public static final int CURVE_X25519 = 4; + public static final int CURVE_ED25519 = 6; + public static final int CURVE_P256 = 1; private static final int X_COORDINATE = -2; + private static final int Y_COORDINATE = -3; public static PublicKey getP256PubKeyFromBytes(byte[] xPub, byte[] yPub) throws Exception { BigInteger x = new BigInteger(1, xPub); @@ -73,6 +88,53 @@ public class Utils { return keyFactory.generatePublic(keySpec); } + public static byte[] getBytesFromP256PrivateKey(ECPrivateKey privateKey) throws Exception { + int keySizeBytes = (privateKey.getParams().getOrder().bitLength() + Byte.SIZE - 1) + / Byte.SIZE; + final byte[] rawPublicKey = new byte[keySizeBytes]; + + final byte[] priv = privateKey.getS().toByteArray(); + if (priv.length <= keySizeBytes) { + System.arraycopy(priv, 0, rawPublicKey, keySizeBytes + - priv.length, priv.length); + } else if (priv.length == keySizeBytes + 1 && priv[0] == 0) { + System.arraycopy(priv, 1, rawPublicKey, 0, keySizeBytes); + } else { + throw new IllegalStateException("private value is too large"); + } + return rawPublicKey; + } + + public static byte[] getBytesFromP256PublicKey(ECPublicKey publicKey) throws Exception { + int keySizeBytes = + (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1) / Byte.SIZE; + + final byte[] rawPublicKey = new byte[2 * keySizeBytes]; + int offset = 0; + + final byte[] x = publicKey.getW().getAffineX().toByteArray(); + if (x.length <= keySizeBytes) { + System.arraycopy(x, 0, rawPublicKey, offset + keySizeBytes + - x.length, x.length); + } else if (x.length == keySizeBytes + 1 && x[0] == 0) { + System.arraycopy(x, 1, rawPublicKey, offset, keySizeBytes); + } else { + throw new IllegalStateException("x value is too large"); + } + offset += keySizeBytes; + + final byte[] y = publicKey.getW().getAffineY().toByteArray(); + if (y.length <= keySizeBytes) { + System.arraycopy(y, 0, rawPublicKey, offset + keySizeBytes + - y.length, y.length); + } else if (y.length == keySizeBytes + 1 && y[0] == 0) { + System.arraycopy(y, 1, rawPublicKey, offset, keySizeBytes); + } else { + throw new IllegalStateException("y value is too large"); + } + return rawPublicKey; + } + public static KeyPair generateEcdsaKeyPair() throws Exception { KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); ECGenParameterSpec params = new ECGenParameterSpec("secp256r1"); @@ -112,30 +174,54 @@ public class Utils { .end() .add(encodedPublicKey) .add(encodeAndSignSigStructure( - encodedProtectedHeaders, encodedPublicKey, privateKey)) + encodedProtectedHeaders, encodedPublicKey, privateKey, CURVE_ED25519)) + .end() + .build().get(0)); + } + + public static Array encodeAndSignSign1Ecdsa256(byte[] encodedPublicKey, byte[] privateKey) + throws Exception { + byte[] encodedProtectedHeaders = encodeSimpleMap(1, -7); + return (Array) (new CborBuilder() + .addArray() + .add(encodedProtectedHeaders) // Protected headers + .addMap() // Empty unprotected Headers + .end() + .add(encodedPublicKey) + .add(encodeAndSignSigStructure( + encodedProtectedHeaders, encodedPublicKey, privateKey, CURVE_P256)) .end() .build().get(0)); } private static byte[] encodeAndSignSigStructure( - byte[] protectedHeaders, byte[] payload, byte[] privateKey) throws Exception { - return encodeAndSignSigStructure(protectedHeaders, null, payload, privateKey); + byte[] protectedHeaders, byte[] payload, byte[] privateKey, + int curve) throws Exception { + return encodeAndSignSigStructure(protectedHeaders, null, payload, + privateKey, curve); } private static byte[] encodeAndSignSigStructure(byte[] protectedHeaders, byte[] externalAad, - byte[] payload, byte[] privateKey) + byte[] payload, byte[] privateKey, int curve) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); new CborEncoder(baos).encode(new CborBuilder() .addArray() - .add("Signature1") // context string - .add(protectedHeaders) // protected headers - .add(null == externalAad ? new byte[0] : externalAad) // external aad - .add(payload) // payload - .end() + .add("Signature1") // context string + .add(protectedHeaders) // protected headers + .add(null == externalAad ? new byte[0] : externalAad) // external aad + .add(payload) // payload + .end() .build()); - Ed25519Sign signer = new Ed25519Sign(privateKey); - return signer.sign(baos.toByteArray()); + if (curve == CURVE_ED25519) { + Ed25519Sign signer = new Ed25519Sign(privateKey); + return signer.sign(baos.toByteArray()); + } else { + ECPrivateKey privKey = EllipticCurves.getEcPrivateKey( + EllipticCurves.CurveType.NIST_P256, privateKey); + EcdsaSignJce ecdsaSigner = new EcdsaSignJce(privKey, SHA256, IEEE_P1363); + return ecdsaSigner.sign(baos.toByteArray()); + } } public static byte[] encodeEd25519PubKey(byte[] publicKey) throws Exception { @@ -151,6 +237,30 @@ public class Utils { return baos.toByteArray(); } + public static byte[] encodeP256PubKey(byte[] pubX, byte[] pubY, boolean isEek) + throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + MapBuilder<CborBuilder> cborBuilder = new CborBuilder() + .addMap() + .put(KEY_TYPE, KEY_TYPE_EC2) + .put(ALGORITHM, isEek ? ALGORITHM_ECDH_ES_HKDF_256 : ALGORITHM_ES256) + .put(CURVE, CURVE_P256) + .put(X_COORDINATE, pubX) + .put(Y_COORDINATE, pubY); + List<DataItem> coseKey; + if (isEek) { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(pubX); + byte[] kid = digest.digest(pubY); + coseKey = cborBuilder.put(KID, kid).end().build(); + } else { + coseKey = cborBuilder.end().build(); + } + new CborEncoder(baos).encode(coseKey); + return baos.toByteArray(); + } + + public static byte[] encodeX25519PubKey(byte[] publicKey) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); MessageDigest digest = MessageDigest.getInstance("SHA-256"); |