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 android.security; 18 19 import android.annotation.CheckResult; 20 import android.annotation.IntDef; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.os.IBinder; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.Executor; 33 import java.util.concurrent.Executors; 34 import java.util.concurrent.TimeUnit; 35 36 /** 37 * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner 38 * app. There are two cases where Keystore should use this class. 39 * 40 * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the 41 * RemoteProvisioner app check if the state of the attestation key pool is getting low enough 42 * to warrant provisioning more attestation certificates early. 43 * 44 * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of 45 * attestation key pairs and cannot provide one for the given application. Keystore can then 46 * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another 47 * attestation certificate chain provisioned. 48 * 49 * In most cases, the proper usage of (1) should preclude the need for (2). 50 * 51 * @hide 52 */ 53 public class GenerateRkpKey { 54 private static final String TAG = "GenerateRkpKey"; 55 56 private static final int NOTIFY_EMPTY = 0; 57 private static final int NOTIFY_KEY_GENERATED = 1; 58 private static final int TIMEOUT_MS = 1000; 59 60 private IGenerateRkpKeyService mBinder; 61 private Context mContext; 62 private CountDownLatch mCountDownLatch; 63 64 /** @hide */ 65 @Retention(RetentionPolicy.SOURCE) 66 @IntDef(flag = true, value = { 67 IGenerateRkpKeyService.Status.OK, 68 IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY, 69 IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR, 70 IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED, 71 IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR, 72 IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR, 73 IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR, 74 IGenerateRkpKeyService.Status.INTERNAL_ERROR, 75 }) 76 public @interface Status { 77 } 78 79 private ServiceConnection mConnection = new ServiceConnection() { 80 @Override 81 public void onServiceConnected(ComponentName className, IBinder service) { 82 mBinder = IGenerateRkpKeyService.Stub.asInterface(service); 83 mCountDownLatch.countDown(); 84 } 85 86 @Override public void onBindingDied(ComponentName className) { 87 mCountDownLatch.countDown(); 88 } 89 90 @Override 91 public void onServiceDisconnected(ComponentName className) { 92 mBinder = null; 93 } 94 }; 95 96 /** 97 * Constructor which takes a Context object. 98 */ GenerateRkpKey(Context context)99 public GenerateRkpKey(Context context) { 100 mContext = context; 101 } 102 103 @Status bindAndSendCommand(int command, int securityLevel)104 private int bindAndSendCommand(int command, int securityLevel) throws RemoteException { 105 Intent intent = new Intent(IGenerateRkpKeyService.class.getName()); 106 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 107 int returnCode = IGenerateRkpKeyService.Status.OK; 108 if (comp == null) { 109 // On a system that does not use RKP, the RemoteProvisioner app won't be installed. 110 return returnCode; 111 } 112 intent.setComponent(comp); 113 mCountDownLatch = new CountDownLatch(1); 114 Executor executor = Executors.newCachedThreadPool(); 115 if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) { 116 throw new RemoteException("Failed to bind to GenerateRkpKeyService"); 117 } 118 try { 119 mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); 120 } catch (InterruptedException e) { 121 Log.e(TAG, "Interrupted: ", e); 122 } 123 if (mBinder != null) { 124 switch (command) { 125 case NOTIFY_EMPTY: 126 returnCode = mBinder.generateKey(securityLevel); 127 break; 128 case NOTIFY_KEY_GENERATED: 129 mBinder.notifyKeyGenerated(securityLevel); 130 break; 131 default: 132 Log.e(TAG, "Invalid case for command"); 133 } 134 } else { 135 Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService."); 136 returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR; 137 } 138 mContext.unbindService(mConnection); 139 return returnCode; 140 } 141 142 /** 143 * Fulfills the use case of (2) described in the class documentation. Blocks until the 144 * RemoteProvisioner application can get new attestation keys signed by the server. 145 * @return the status of the key generation 146 */ 147 @CheckResult 148 @Status notifyEmpty(int securityLevel)149 public int notifyEmpty(int securityLevel) throws RemoteException { 150 return bindAndSendCommand(NOTIFY_EMPTY, securityLevel); 151 } 152 153 /** 154 * Fulfills the use case of (1) described in the class documentation. Non blocking call. 155 */ notifyKeyGenerated(int securityLevel)156 public void notifyKeyGenerated(int securityLevel) throws RemoteException { 157 bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel); 158 } 159 } 160