1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.remoteprovisioner.unittest; 18 19 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC; 20 import static android.security.keystore.KeyProperties.PURPOSE_SIGN; 21 22 import static com.android.remoteprovisioner.unittest.Utils.generateEcdsaKeyPair; 23 import static com.android.remoteprovisioner.unittest.Utils.getP256PubKeyFromBytes; 24 import static com.android.remoteprovisioner.unittest.Utils.signPublicKey; 25 26 import static org.junit.Assert.assertArrayEquals; 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertNotNull; 29 import static org.junit.Assert.assertTrue; 30 31 import android.hardware.security.keymint.DeviceInfo; 32 import android.hardware.security.keymint.ProtectedData; 33 import android.hardware.security.keymint.SecurityLevel; 34 import android.os.ServiceManager; 35 import android.platform.test.annotations.Presubmit; 36 import android.security.keystore.KeyGenParameterSpec; 37 import android.security.remoteprovisioning.IRemoteProvisioning; 38 39 import androidx.test.runner.AndroidJUnit4; 40 41 import com.android.remoteprovisioner.CborUtils; 42 import com.android.remoteprovisioner.SystemInterface; 43 import com.android.remoteprovisioner.X509Utils; 44 45 import com.google.crypto.tink.subtle.Hkdf; 46 import com.google.crypto.tink.subtle.X25519; 47 48 import co.nstant.in.cbor.CborBuilder; 49 import co.nstant.in.cbor.CborDecoder; 50 import co.nstant.in.cbor.CborEncoder; 51 import co.nstant.in.cbor.model.Array; 52 import co.nstant.in.cbor.model.ByteString; 53 import co.nstant.in.cbor.model.DataItem; 54 import co.nstant.in.cbor.model.MajorType; 55 import co.nstant.in.cbor.model.Map; 56 import co.nstant.in.cbor.model.NegativeInteger; 57 import co.nstant.in.cbor.model.UnsignedInteger; 58 59 import org.junit.After; 60 import org.junit.Before; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 64 import java.io.ByteArrayInputStream; 65 import java.io.ByteArrayOutputStream; 66 import java.security.KeyPair; 67 import java.security.KeyPairGenerator; 68 import java.security.KeyStore; 69 import java.security.PublicKey; 70 import java.security.cert.Certificate; 71 import java.security.cert.X509Certificate; 72 import java.util.Arrays; 73 import java.util.List; 74 import java.util.Random; 75 76 import javax.crypto.Cipher; 77 import javax.crypto.spec.GCMParameterSpec; 78 import javax.crypto.spec.SecretKeySpec; 79 80 @RunWith(AndroidJUnit4.class) 81 public class SystemInterfaceTest { 82 83 private static final String SERVICE = "android.security.remoteprovisioning"; 84 85 private IRemoteProvisioning mBinder; 86 87 @Before setUp()88 public void setUp() throws Exception { 89 mBinder = 90 IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); 91 assertNotNull(mBinder); 92 mBinder.deleteAllKeys(); 93 } 94 95 @After tearDown()96 public void tearDown() throws Exception { 97 mBinder.deleteAllKeys(); 98 } 99 generateEekChain(byte[] eek)100 private byte[] generateEekChain(byte[] eek) throws Exception { 101 com.google.crypto.tink.subtle.Ed25519Sign.KeyPair kp = 102 com.google.crypto.tink.subtle.Ed25519Sign.KeyPair.newKeyPair(); 103 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 104 new CborEncoder(baos).encode(new CborBuilder() 105 .addArray() 106 .add(Utils.encodeAndSignSign1Ed25519( 107 Utils.encodeEd25519PubKey(kp.getPublicKey()), kp.getPrivateKey())) 108 .add(Utils.encodeAndSignSign1Ed25519( 109 Utils.encodeX25519PubKey(eek), kp.getPrivateKey())) 110 .end() 111 .build()); 112 return baos.toByteArray(); 113 } 114 115 @Presubmit 116 @Test testGenerateCSR()117 public void testGenerateCSR() throws Exception { 118 DeviceInfo deviceInfo = new DeviceInfo(); 119 ProtectedData encryptedBundle = new ProtectedData(); 120 byte[] eek = new byte[32]; 121 new Random().nextBytes(eek); 122 byte[] bundle = 123 SystemInterface.generateCsr(true /* testMode */, 0 /* numKeys */, 124 SecurityLevel.TRUSTED_ENVIRONMENT, 125 generateEekChain(eek), 126 new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder); 127 // encryptedBundle should contain a COSE_Encrypt message 128 ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); 129 List<DataItem> dataItems = new CborDecoder(bais).decode(); 130 assertEquals(1, dataItems.size()); 131 assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); 132 Array encMsg = (Array) dataItems.get(0); 133 assertEquals(4, encMsg.getDataItems().size()); 134 } 135 generateKeyStoreKey(String alias)136 private static Certificate[] generateKeyStoreKey(String alias) throws Exception { 137 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 138 keyStore.load(null); 139 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC, 140 "AndroidKeyStore"); 141 KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN) 142 .setAttestationChallenge("challenge".getBytes()) 143 .build(); 144 keyPairGenerator.initialize(spec); 145 keyPairGenerator.generateKeyPair(); 146 Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias()); 147 keyStore.deleteEntry(alias); 148 return certs; 149 } 150 151 @Presubmit 152 @Test testGenerateCSRProvisionAndUseKey()153 public void testGenerateCSRProvisionAndUseKey() throws Exception { 154 DeviceInfo deviceInfo = new DeviceInfo(); 155 ProtectedData encryptedBundle = new ProtectedData(); 156 int numKeys = 10; 157 byte[] eek = new byte[32]; 158 new Random().nextBytes(eek); 159 for (int i = 0; i < numKeys; i++) { 160 mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT); 161 } 162 byte[] bundle = 163 SystemInterface.generateCsr(true /* testMode */, numKeys, 164 SecurityLevel.TRUSTED_ENVIRONMENT, 165 generateEekChain(eek), 166 new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder); 167 assertNotNull(bundle); 168 // The return value of generateCsr should be a COSE_Mac0 message 169 ByteArrayInputStream bais = new ByteArrayInputStream(bundle); 170 List<DataItem> dataItems = new CborDecoder(bais).decode(); 171 assertEquals(1, dataItems.size()); 172 assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); 173 Array macMsg = (Array) dataItems.get(0); 174 assertEquals(4, macMsg.getDataItems().size()); 175 176 // The payload for the COSE_Mac0 should contain the array of public keys 177 bais = new ByteArrayInputStream(((ByteString) macMsg.getDataItems().get(2)).getBytes()); 178 List<DataItem> publicKeysArr = new CborDecoder(bais).decode(); 179 assertEquals(1, publicKeysArr.size()); 180 assertEquals(MajorType.ARRAY, publicKeysArr.get(0).getMajorType()); 181 Array publicKeys = (Array) publicKeysArr.get(0); 182 assertEquals(numKeys, publicKeys.getDataItems().size()); 183 KeyPair rootKeyPair = generateEcdsaKeyPair(); 184 KeyPair intermediateKeyPair = generateEcdsaKeyPair(); 185 X509Certificate[][] certChain = new X509Certificate[numKeys][3]; 186 for (int i = 0; i < numKeys; i++) { 187 Map publicKey = (Map) publicKeys.getDataItems().get(i); 188 byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes(); 189 byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes(); 190 assertEquals(xPub.length, 32); 191 assertEquals(yPub.length, 32); 192 PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub); 193 certChain[i][0] = signPublicKey(intermediateKeyPair, leafKeyToSign); 194 certChain[i][1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic()); 195 certChain[i][2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic()); 196 ByteArrayOutputStream os = new ByteArrayOutputStream(); 197 for (int j = 0; j < certChain[i].length; j++) { 198 os.write(certChain[i][j].getEncoded()); 199 } 200 SystemInterface.provisionCertChain(X509Utils.getAndFormatRawPublicKey(certChain[i][0]), 201 certChain[i][0].getEncoded() /* leafCert */, 202 os.toByteArray() /* certChain */, 203 System.currentTimeMillis() + 2000 /* validity */, 204 SecurityLevel.TRUSTED_ENVIRONMENT, 205 mBinder); 206 } 207 // getPoolStatus will clean the key pool before we go to assign a new provisioned key 208 mBinder.getPoolStatus(0, SecurityLevel.TRUSTED_ENVIRONMENT); 209 Certificate[] provisionedCerts1 = generateKeyStoreKey("alias"); 210 Certificate[] provisionedCerts2 = generateKeyStoreKey("alias2"); 211 assertEquals(4, provisionedCerts1.length); 212 assertEquals(4, provisionedCerts2.length); 213 boolean matched = false; 214 for (int i = 0; i < certChain.length; i++) { 215 if (Arrays.equals(provisionedCerts1[1].getEncoded(), certChain[i][0].getEncoded())) { 216 matched = true; 217 assertArrayEquals("Second key: j = 0", 218 provisionedCerts2[1].getEncoded(), certChain[i][0].getEncoded()); 219 for (int j = 1; j < certChain[i].length; j++) { 220 assertArrayEquals("First key: j = " + j, 221 provisionedCerts1[j + 1].getEncoded(), certChain[i][j].getEncoded()); 222 assertArrayEquals("Second key: j = " + j, 223 provisionedCerts2[j + 1].getEncoded(), certChain[i][j].getEncoded()); 224 } 225 } 226 } 227 assertTrue(matched); 228 } 229 extractRecipientKey(Array recipients)230 private static byte[] extractRecipientKey(Array recipients) { 231 // Recipients is an Array of recipient Arrays 232 Map recipientUnprotectedHeaders = (Map) ((Array) recipients.getDataItems().get(0)) 233 .getDataItems().get(1); 234 Map recipientKeyMap = (Map) recipientUnprotectedHeaders.get(new NegativeInteger(-1)); 235 return ((ByteString) recipientKeyMap.get(new NegativeInteger(-2))).getBytes(); 236 } 237 buildKdfContext(byte[] serverPub, byte[] ephemeralPub)238 private static byte[] buildKdfContext(byte[] serverPub, byte[] ephemeralPub) throws Exception { 239 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 240 new CborEncoder(baos).encode(new CborBuilder() 241 .addArray() 242 .add(3) // AlgorithmID: AES-GCM 256 243 .addArray() 244 .add("client".getBytes("UTF8")) 245 .add(new byte[0]) 246 .add(ephemeralPub) 247 .end() 248 .addArray() 249 .add("server".getBytes("UTF8")) 250 .add(new byte[0]) 251 .add(serverPub) 252 .end() 253 .addArray() 254 .add(256) // key length 255 .add(new byte[0]) 256 .end() 257 .end() 258 .build()); 259 return baos.toByteArray(); 260 } 261 buildEncStructure(byte[] protectedHeaders, byte[] externalAad)262 private static byte[] buildEncStructure(byte[] protectedHeaders, byte[] externalAad) 263 throws Exception { 264 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 265 new CborEncoder(baos).encode(new CborBuilder() 266 .addArray() 267 .add("Encrypt") 268 .add(protectedHeaders) 269 .add(externalAad) 270 .end() 271 .build()); 272 return baos.toByteArray(); 273 } 274 275 @Presubmit 276 @Test testDecryptProtectedPayload()277 public void testDecryptProtectedPayload() throws Exception { 278 DeviceInfo deviceInfo = new DeviceInfo(); 279 ProtectedData encryptedBundle = new ProtectedData(); 280 int numKeys = 1; 281 byte[] eekPriv = X25519.generatePrivateKey(); 282 byte[] eekPub = X25519.publicFromPrivate(eekPriv); 283 mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT); 284 byte[] bundle = 285 SystemInterface.generateCsr(true /* testMode */, numKeys, 286 SecurityLevel.TRUSTED_ENVIRONMENT, 287 generateEekChain(eekPub), 288 new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder); 289 ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData); 290 List<DataItem> dataItems = new CborDecoder(bais).decode(); 291 // Parse encMsg into components: protected and unprotected headers, payload, and recipient 292 List<DataItem> encMsg = ((Array) dataItems.get(0)).getDataItems(); 293 byte[] protectedHeaders = ((ByteString) encMsg.get(0)).getBytes(); 294 Map unprotectedHeaders = (Map) encMsg.get(1); 295 byte[] encryptedContent = ((ByteString) encMsg.get(2)).getBytes(); 296 Array recipients = (Array) encMsg.get(3); 297 298 byte[] iv = ((ByteString) unprotectedHeaders.get(new UnsignedInteger(5))).getBytes(); 299 byte[] ephemeralPub = extractRecipientKey(recipients); 300 assertEquals(32, ephemeralPub.length); 301 byte[] sharedSecret = X25519.computeSharedSecret(eekPriv, ephemeralPub); 302 byte[] context = buildKdfContext(eekPub, ephemeralPub); 303 byte[] decryptionKey = Hkdf.computeHkdf("HMACSHA256", sharedSecret, null /* salt */, 304 context, 32); 305 306 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); 307 cipher.init( 308 Cipher.DECRYPT_MODE, 309 new SecretKeySpec(decryptionKey, "AES"), 310 new GCMParameterSpec(128 /* iv length */, iv)); 311 cipher.updateAAD(buildEncStructure(protectedHeaders, new byte[0])); 312 313 byte[] protectedData = cipher.doFinal(encryptedContent); 314 bais = new ByteArrayInputStream(protectedData); 315 List<DataItem> protectedDataArray = new CborDecoder(bais).decode(); 316 assertEquals(1, protectedDataArray.size()); 317 assertEquals(MajorType.ARRAY, protectedDataArray.get(0).getMajorType()); 318 List<DataItem> protectedDataPayload = ((Array) protectedDataArray.get(0)).getDataItems(); 319 assertTrue(protectedDataPayload.size() == 2 || protectedDataPayload.size() == 3); 320 } 321 } 322