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.server.locksettings; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.os.RemoteException; 22 import android.provider.DeviceConfig; 23 import android.util.Slog; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; 27 28 import java.io.IOException; 29 import java.util.concurrent.TimeoutException; 30 31 import javax.crypto.SecretKey; 32 33 /** 34 * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to 35 * encrypt & decrypt the blob. 36 */ 37 class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { 38 private static final String TAG = "RebootEscrowProviderServerBased"; 39 40 // Timeout for service binding 41 private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; 42 43 /** 44 * Use the default lifetime of 10 minutes. The lifetime covers the following activities: 45 * Server wrap secret -> device reboot -> server unwrap blob. 46 */ 47 private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_000; 48 49 private final LockSettingsStorage mStorage; 50 51 private final Injector mInjector; 52 53 private byte[] mServerBlob; 54 55 static class Injector { 56 private ResumeOnRebootServiceConnection mServiceConnection = null; 57 Injector(Context context)58 Injector(Context context) { 59 mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection(); 60 if (mServiceConnection == null) { 61 Slog.e(TAG, "Failed to resolve resume on reboot server service."); 62 } 63 } 64 Injector(ResumeOnRebootServiceConnection serviceConnection)65 Injector(ResumeOnRebootServiceConnection serviceConnection) { 66 mServiceConnection = serviceConnection; 67 } 68 69 @Nullable getServiceConnection()70 private ResumeOnRebootServiceConnection getServiceConnection() { 71 return mServiceConnection; 72 } 73 getServiceTimeoutInSeconds()74 long getServiceTimeoutInSeconds() { 75 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, 76 "server_based_service_timeout_in_seconds", 77 DEFAULT_SERVICE_TIMEOUT_IN_SECONDS); 78 } 79 getServerBlobLifetimeInMillis()80 long getServerBlobLifetimeInMillis() { 81 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, 82 "server_based_server_blob_lifetime_in_millis", 83 DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS); 84 } 85 } 86 RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage)87 RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) { 88 this(storage, new Injector(context)); 89 } 90 91 @VisibleForTesting RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector)92 RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) { 93 mStorage = storage; 94 mInjector = injector; 95 } 96 97 @Override getType()98 public int getType() { 99 return TYPE_SERVER_BASED; 100 } 101 102 @Override hasRebootEscrowSupport()103 public boolean hasRebootEscrowSupport() { 104 return mInjector.getServiceConnection() != null; 105 } 106 unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey)107 private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws 108 TimeoutException, RemoteException, IOException { 109 ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); 110 if (serviceConnection == null) { 111 Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server" 112 + " service is unavailable"); 113 return null; 114 } 115 116 // Decrypt with k_k from the key store first. 117 byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob); 118 if (decryptedBlob == null) { 119 Slog.w(TAG, "Decrypted server blob should not be null"); 120 return null; 121 } 122 123 // Ask the server connection service to decrypt the inner layer, to get the reboot 124 // escrow key (k_s). 125 serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); 126 byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob, 127 mInjector.getServiceTimeoutInSeconds()); 128 serviceConnection.unbindService(); 129 130 return escrowKeyBytes; 131 } 132 133 @Override getAndClearRebootEscrowKey(SecretKey decryptionKey)134 public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException { 135 if (mServerBlob == null) { 136 mServerBlob = mStorage.readRebootEscrowServerBlob(); 137 } 138 // Delete the server blob in storage. 139 mStorage.removeRebootEscrowServerBlob(); 140 if (mServerBlob == null) { 141 Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); 142 return null; 143 } 144 if (decryptionKey == null) { 145 Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is" 146 + " null."); 147 return null; 148 } 149 150 Slog.i(TAG, "Loaded reboot escrow server blob from storage"); 151 try { 152 byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey); 153 if (escrowKeyBytes == null) { 154 Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); 155 return null; 156 } else if (escrowKeyBytes.length != 32) { 157 Slog.e(TAG, "Decrypted reboot escrow key has incorrect size " 158 + escrowKeyBytes.length); 159 return null; 160 } 161 162 return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); 163 } catch (TimeoutException | RemoteException e) { 164 Slog.w(TAG, "Failed to decrypt the server blob ", e); 165 return null; 166 } 167 } 168 169 @Override clearRebootEscrowKey()170 public void clearRebootEscrowKey() { 171 mStorage.removeRebootEscrowServerBlob(); 172 } 173 wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey)174 private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws 175 TimeoutException, RemoteException, IOException { 176 ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); 177 if (serviceConnection == null) { 178 Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server" 179 + " service is unavailable"); 180 return null; 181 } 182 183 serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); 184 // Ask the server connection service to encrypt the reboot escrow key. 185 byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes, 186 mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds()); 187 serviceConnection.unbindService(); 188 189 if (serverEncryptedBlob == null) { 190 Slog.w(TAG, "Server encrypted reboot escrow key cannot be null"); 191 return null; 192 } 193 194 // Additionally wrap the server blob with a local key. 195 return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob); 196 } 197 198 @Override storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey)199 public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) { 200 mStorage.removeRebootEscrowServerBlob(); 201 try { 202 byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey); 203 if (wrappedBlob == null) { 204 Slog.w(TAG, "Failed to encrypt the reboot escrow key"); 205 return false; 206 } 207 mStorage.writeRebootEscrowServerBlob(wrappedBlob); 208 209 Slog.i(TAG, "Reboot escrow key encrypted and stored."); 210 return true; 211 } catch (TimeoutException | RemoteException | IOException e) { 212 Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e); 213 } 214 215 return false; 216 } 217 } 218