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