aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-09-12 03:08:53 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-09-12 03:08:53 +0000
commit02492861dec3c648ddea56a360b12a1e1cacabc0 (patch)
tree29feefb41752f6511093280b553283971f7f6653
parenta62e3c938071fc0d640890e88db2910d8c30fae5 (diff)
parent55d528ec8f040daf19f02564cd6c6cd5b27359ca (diff)
downloadRemoteProvisioner-gki13-boot-release.tar.gz
Snap for 9051340 from 55d528ec8f040daf19f02564cd6c6cd5b27359ca to gki13-boot-releasegki13-boot-release
Change-Id: I5330fa6b0d710e1b9ee4f758f1390014066acfd8
-rw-r--r--tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java139
-rw-r--r--tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java446
-rw-r--r--tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java144
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");