1 /*
2  * Copyright (C) 2016 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.wifi;
18 
19 import static java.lang.Math.toIntExact;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.AlarmManager;
25 import android.content.Context;
26 import android.net.wifi.WifiMigration;
27 import android.os.Handler;
28 import android.os.UserHandle;
29 import android.util.AtomicFile;
30 import android.util.Log;
31 import android.util.SparseArray;
32 import android.util.Xml;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.util.FastXmlSerializer;
36 import com.android.internal.util.Preconditions;
37 import com.android.server.wifi.util.EncryptedData;
38 import com.android.server.wifi.util.Environment;
39 import com.android.server.wifi.util.FileUtils;
40 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
41 import com.android.server.wifi.util.XmlUtil;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 import org.xmlpull.v1.XmlSerializer;
46 
47 import java.io.ByteArrayInputStream;
48 import java.io.ByteArrayOutputStream;
49 import java.io.File;
50 import java.io.FileDescriptor;
51 import java.io.FileNotFoundException;
52 import java.io.FileOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.PrintWriter;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.nio.charset.StandardCharsets;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.stream.Collectors;
66 import java.util.stream.Stream;
67 
68 /**
69  * This class provides a mechanism to save data to persistent store files {@link StoreFile}.
70  * Modules can register a {@link StoreData} instance indicating the {@StoreFile} into which they
71  * want to save their data to.
72  *
73  * NOTE:
74  * <li>Modules can register their {@StoreData} using
75  * {@link WifiConfigStore#registerStoreData(StoreData)} directly, but should
76  * use {@link WifiConfigManager#saveToStore(boolean)} for any writes.</li>
77  * <li>{@link WifiConfigManager} controls {@link WifiConfigStore} and initiates read at bootup and
78  * store file changes on user switch.</li>
79  * <li>Not thread safe!</li>
80  */
81 public class WifiConfigStore {
82     /**
83      * Config store file for general shared store file.
84      */
85     public static final int STORE_FILE_SHARED_GENERAL = 0;
86     /**
87      * Config store file for softap shared store file.
88      */
89     public static final int STORE_FILE_SHARED_SOFTAP = 1;
90     /**
91      * Config store file for general user store file.
92      */
93     public static final int STORE_FILE_USER_GENERAL = 2;
94     /**
95      * Config store file for network suggestions user store file.
96      */
97     public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3;
98 
99     @IntDef(prefix = { "STORE_FILE_" }, value = {
100             STORE_FILE_SHARED_GENERAL,
101             STORE_FILE_SHARED_SOFTAP,
102             STORE_FILE_USER_GENERAL,
103             STORE_FILE_USER_NETWORK_SUGGESTIONS
104     })
105     @Retention(RetentionPolicy.SOURCE)
106     public @interface StoreFileId { }
107 
108     private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
109     private static final String XML_TAG_VERSION = "Version";
110     private static final String XML_TAG_HEADER_INTEGRITY = "Integrity";
111     /**
112      * Current config store data version. This will be incremented for any additions.
113      */
114     private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 3;
115     /** This list of older versions will be used to restore data from older config store. */
116     /**
117      * First version of the config store data format.
118      */
119     public static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
120     /**
121      * Second version of the config store data format, introduced:
122      *  - Integrity info.
123      */
124     public static final int INTEGRITY_CONFIG_STORE_DATA_VERSION = 2;
125     /**
126      * Third version of the config store data format,
127      * introduced:
128      *  - Encryption of credentials
129      * removed:
130      *  - Integrity info.
131      */
132     public static final int ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION = 3;
133 
134     @IntDef(suffix = { "_VERSION" }, value = {
135             INITIAL_CONFIG_STORE_DATA_VERSION,
136             INTEGRITY_CONFIG_STORE_DATA_VERSION,
137             ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION
138     })
139     @Retention(RetentionPolicy.SOURCE)
140     public @interface Version { }
141 
142     /**
143      * Alarm tag to use for starting alarms for buffering file writes.
144      */
145     @VisibleForTesting
146     public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm";
147     /**
148      * Log tag.
149      */
150     private static final String TAG = "WifiConfigStore";
151     /**
152      * Time interval for buffering file writes for non-forced writes
153      */
154     private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000;
155     /**
156      * Config store file name for general shared store file.
157      */
158     private static final String STORE_FILE_NAME_SHARED_GENERAL = "WifiConfigStore.xml";
159     /**
160      * Config store file name for SoftAp shared store file.
161      */
162     private static final String STORE_FILE_NAME_SHARED_SOFTAP = "WifiConfigStoreSoftAp.xml";
163     /**
164      * Config store file name for general user store file.
165      */
166     private static final String STORE_FILE_NAME_USER_GENERAL = "WifiConfigStore.xml";
167     /**
168      * Config store file name for network suggestions user store file.
169      */
170     private static final String STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS =
171             "WifiConfigStoreNetworkSuggestions.xml";
172     /**
173      * Mapping of Store file Id to Store file names.
174      */
175     private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
176             new SparseArray<String>() {{
177                 put(STORE_FILE_SHARED_GENERAL, STORE_FILE_NAME_SHARED_GENERAL);
178                 put(STORE_FILE_SHARED_SOFTAP, STORE_FILE_NAME_SHARED_SOFTAP);
179                 put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL);
180                 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS);
181             }};
182     /**
183      * Handler instance to post alarm timeouts to
184      */
185     private final Handler mEventHandler;
186     /**
187      * Alarm manager instance to start buffer timeout alarms.
188      */
189     private final AlarmManager mAlarmManager;
190     /**
191      * Clock instance to retrieve timestamps for alarms.
192      */
193     private final Clock mClock;
194     private final WifiMetrics mWifiMetrics;
195     /**
196      * Shared config store file instance. There are 2 shared store files:
197      * {@link #STORE_FILE_NAME_SHARED_GENERAL} & {@link #STORE_FILE_NAME_SHARED_SOFTAP}.
198      */
199     private final List<StoreFile> mSharedStores;
200     /**
201      * User specific store file instances. There are 2 user store files:
202      * {@link #STORE_FILE_NAME_USER_GENERAL} & {@link #STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS}.
203      */
204     private List<StoreFile> mUserStores;
205     /**
206      * Verbose logging flag.
207      */
208     private boolean mVerboseLoggingEnabled = false;
209     /**
210      * Flag to indicate if there is a buffered write pending.
211      */
212     private boolean mBufferedWritePending = false;
213     /**
214      * Alarm listener for flushing out any buffered writes.
215      */
216     private final AlarmManager.OnAlarmListener mBufferedWriteListener =
217             new AlarmManager.OnAlarmListener() {
218                 public void onAlarm() {
219                     try {
220                         writeBufferedData();
221                     } catch (IOException e) {
222                         Log.wtf(TAG, "Buffered write failed", e);
223                     }
224                 }
225             };
226 
227     /**
228      * List of data containers.
229      */
230     private final List<StoreData> mStoreDataList;
231 
232     /**
233      * Create a new instance of WifiConfigStore.
234      * Note: The store file instances have been made inputs to this class to ease unit-testing.
235      *
236      * @param context     context to use for retrieving the alarm manager.
237      * @param handler     handler instance to post alarm timeouts to.
238      * @param clock       clock instance to retrieve timestamps for alarms.
239      * @param wifiMetrics Metrics instance.
240      * @param sharedStores List of {@link StoreFile} instances pointing to the shared store files.
241      *                     This should be retrieved using {@link #createSharedFiles(boolean)}
242      *                     method.
243      */
WifiConfigStore(Context context, Handler handler, Clock clock, WifiMetrics wifiMetrics, List<StoreFile> sharedStores)244     public WifiConfigStore(Context context, Handler handler, Clock clock, WifiMetrics wifiMetrics,
245             List<StoreFile> sharedStores) {
246 
247         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
248         mEventHandler = handler;
249         mClock = clock;
250         mWifiMetrics = wifiMetrics;
251         mStoreDataList = new ArrayList<>();
252 
253         // Initialize the store files.
254         mSharedStores = sharedStores;
255         // The user store is initialized to null, this will be set when the user unlocks and
256         // CE storage is accessible via |switchUserStoresAndRead|.
257         mUserStores = null;
258     }
259 
260     /**
261      * Set the user store files.
262      * (Useful for mocking in unit tests).
263      * @param userStores List of {@link StoreFile} created using
264      * {@link #createUserFiles(int, boolean)}.
265      */
setUserStores(@onNull List<StoreFile> userStores)266     public void setUserStores(@NonNull List<StoreFile> userStores) {
267         Preconditions.checkNotNull(userStores);
268         mUserStores = userStores;
269     }
270 
271     /**
272      * Register a {@link StoreData} to read/write data from/to a store. A {@link StoreData} is
273      * responsible for a block of data in the store file, and provides serialization/deserialization
274      * functions for those data.
275      *
276      * @param storeData The store data to be registered to the config store
277      * @return true if registered successfully, false if the store file name is not valid.
278      */
registerStoreData(@onNull StoreData storeData)279     public boolean registerStoreData(@NonNull StoreData storeData) {
280         if (storeData == null) {
281             Log.e(TAG, "Unable to register null store data");
282             return false;
283         }
284         int storeFileId = storeData.getStoreFileId();
285         if (STORE_ID_TO_FILE_NAME.get(storeFileId) == null) {
286             Log.e(TAG, "Invalid shared store file specified" + storeFileId);
287             return false;
288         }
289         mStoreDataList.add(storeData);
290         return true;
291     }
292 
293     /**
294      * Helper method to create a store file instance for either the shared store or user store.
295      * Note: The method creates the store directory if not already present. This may be needed for
296      * user store files.
297      *
298      * @param storeDir Base directory under which the store file is to be stored. The store file
299      *                 will be at <storeDir>/WifiConfigStore.xml.
300      * @param fileId Identifier for the file. See {@link StoreFileId}.
301      * @param userHandle User handle. Meaningful only for user specific store files.
302      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
303      * @return new instance of the store file or null if the directory cannot be created.
304      */
createFile(@onNull File storeDir, @StoreFileId int fileId, UserHandle userHandle, boolean shouldEncryptCredentials)305     private static @Nullable StoreFile createFile(@NonNull File storeDir,
306             @StoreFileId int fileId, UserHandle userHandle, boolean shouldEncryptCredentials) {
307         if (!storeDir.exists()) {
308             if (!storeDir.mkdir()) {
309                 Log.w(TAG, "Could not create store directory " + storeDir);
310                 return null;
311             }
312         }
313         File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId));
314         WifiConfigStoreEncryptionUtil encryptionUtil = null;
315         if (shouldEncryptCredentials) {
316             encryptionUtil = new WifiConfigStoreEncryptionUtil(file.getName());
317         }
318         return new StoreFile(file, fileId, userHandle, encryptionUtil);
319     }
320 
createFiles(File storeDir, List<Integer> storeFileIds, UserHandle userHandle, boolean shouldEncryptCredentials)321     private static @Nullable List<StoreFile> createFiles(File storeDir, List<Integer> storeFileIds,
322             UserHandle userHandle, boolean shouldEncryptCredentials) {
323         List<StoreFile> storeFiles = new ArrayList<>();
324         for (int fileId : storeFileIds) {
325             StoreFile storeFile =
326                     createFile(storeDir, fileId, userHandle, shouldEncryptCredentials);
327             if (storeFile == null) {
328                 return null;
329             }
330             storeFiles.add(storeFile);
331         }
332         return storeFiles;
333     }
334 
335     /**
336      * Create a new instance of the shared store file.
337      *
338      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
339      * @return new instance of the store file or null if the directory cannot be created.
340      */
createSharedFiles(boolean shouldEncryptCredentials)341     public static @NonNull List<StoreFile> createSharedFiles(boolean shouldEncryptCredentials) {
342         return createFiles(
343                 Environment.getWifiSharedDirectory(),
344                 Arrays.asList(STORE_FILE_SHARED_GENERAL, STORE_FILE_SHARED_SOFTAP),
345                 UserHandle.ALL,
346                 shouldEncryptCredentials);
347     }
348 
349     /**
350      * Create new instances of the user specific store files.
351      * The user store file is inside the user's encrypted data directory.
352      *
353      * @param userId userId corresponding to the currently logged-in user.
354      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
355      * @return List of new instances of the store files created or null if the directory cannot be
356      * created.
357      */
createUserFiles(int userId, boolean shouldEncryptCredentials)358     public static @Nullable List<StoreFile> createUserFiles(int userId,
359             boolean shouldEncryptCredentials) {
360         UserHandle userHandle = UserHandle.of(userId);
361         return createFiles(
362                 Environment.getWifiUserDirectory(userId),
363                 Arrays.asList(STORE_FILE_USER_GENERAL, STORE_FILE_USER_NETWORK_SUGGESTIONS),
364                 userHandle,
365                 shouldEncryptCredentials);
366     }
367 
368     /**
369      * Enable verbose logging.
370      */
enableVerboseLogging(boolean verbose)371     public void enableVerboseLogging(boolean verbose) {
372         mVerboseLoggingEnabled = verbose;
373     }
374 
375     /**
376      * Retrieve the list of {@link StoreData} instances registered for the provided
377      * {@link StoreFile}.
378      */
retrieveStoreDataListForStoreFile(@onNull StoreFile storeFile)379     private List<StoreData> retrieveStoreDataListForStoreFile(@NonNull StoreFile storeFile) {
380         return mStoreDataList
381                 .stream()
382                 .filter(s -> s.getStoreFileId() == storeFile.getFileId())
383                 .collect(Collectors.toList());
384     }
385 
386     /**
387      * Check if any of the provided list of {@link StoreData} instances registered
388      * for the provided {@link StoreFile }have indicated that they have new data to serialize.
389      */
hasNewDataToSerialize(@onNull StoreFile storeFile)390     private boolean hasNewDataToSerialize(@NonNull StoreFile storeFile) {
391         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
392         return storeDataList.stream().anyMatch(s -> s.hasNewDataToSerialize());
393     }
394 
395     /**
396      * API to write the data provided by registered store data to config stores.
397      * The method writes the user specific configurations to user specific config store and the
398      * shared configurations to shared config store.
399      *
400      * @param forceSync boolean to force write the config stores now. if false, the writes are
401      *                  buffered and written after the configured interval.
402      */
write(boolean forceSync)403     public void write(boolean forceSync)
404             throws XmlPullParserException, IOException {
405         boolean hasAnyNewData = false;
406         // Serialize the provided data and send it to the respective stores. The actual write will
407         // be performed later depending on the |forceSync| flag .
408         for (StoreFile sharedStoreFile : mSharedStores) {
409             if (hasNewDataToSerialize(sharedStoreFile)) {
410                 byte[] sharedDataBytes = serializeData(sharedStoreFile);
411                 sharedStoreFile.storeRawDataToWrite(sharedDataBytes);
412                 hasAnyNewData = true;
413             }
414         }
415         if (mUserStores != null) {
416             for (StoreFile userStoreFile : mUserStores) {
417                 if (hasNewDataToSerialize(userStoreFile)) {
418                     byte[] userDataBytes = serializeData(userStoreFile);
419                     userStoreFile.storeRawDataToWrite(userDataBytes);
420                     hasAnyNewData = true;
421                 }
422             }
423         }
424 
425         if (hasAnyNewData) {
426             // Every write provides a new snapshot to be persisted, so |forceSync| flag overrides
427             // any pending buffer writes.
428             if (forceSync) {
429                 writeBufferedData();
430             } else {
431                 startBufferedWriteAlarm();
432             }
433         } else if (forceSync && mBufferedWritePending) {
434             // no new data to write, but there is a pending buffered write. So, |forceSync| should
435             // flush that out.
436             writeBufferedData();
437         }
438     }
439 
440     /**
441      * Serialize all the data from all the {@link StoreData} clients registered for the provided
442      * {@link StoreFile}.
443      *
444      * This method also computes the integrity of the data being written and serializes the computed
445      * {@link EncryptedData} to the output.
446      *
447      * @param storeFile StoreFile that we want to write to.
448      * @return byte[] of serialized bytes
449      * @throws XmlPullParserException
450      * @throws IOException
451      */
serializeData(@onNull StoreFile storeFile)452     private byte[] serializeData(@NonNull StoreFile storeFile)
453             throws XmlPullParserException, IOException {
454         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
455 
456         final XmlSerializer out = new FastXmlSerializer();
457         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
458         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
459 
460         // First XML header.
461         XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
462         // Next version.
463         XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
464         for (StoreData storeData : storeDataList) {
465             String tag = storeData.getName();
466             XmlUtil.writeNextSectionStart(out, tag);
467             storeData.serializeData(out, storeFile.getEncryptionUtil());
468             XmlUtil.writeNextSectionEnd(out, tag);
469         }
470         XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
471         return outputStream.toByteArray();
472     }
473 
474     /**
475      * Helper method to start a buffered write alarm if one doesn't already exist.
476      */
startBufferedWriteAlarm()477     private void startBufferedWriteAlarm() {
478         if (!mBufferedWritePending) {
479             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
480                     mClock.getElapsedSinceBootMillis() + BUFFERED_WRITE_ALARM_INTERVAL_MS,
481                     BUFFERED_WRITE_ALARM_TAG, mBufferedWriteListener, mEventHandler);
482             mBufferedWritePending = true;
483         }
484     }
485 
486     /**
487      * Helper method to stop a buffered write alarm if one exists.
488      */
stopBufferedWriteAlarm()489     private void stopBufferedWriteAlarm() {
490         if (mBufferedWritePending) {
491             mAlarmManager.cancel(mBufferedWriteListener);
492             mBufferedWritePending = false;
493         }
494     }
495 
496     /**
497      * Helper method to actually perform the writes to the file. This flushes out any write data
498      * being buffered in the respective stores and cancels any pending buffer write alarms.
499      */
writeBufferedData()500     private void writeBufferedData() throws IOException {
501         stopBufferedWriteAlarm();
502 
503         long writeStartTime = mClock.getElapsedSinceBootMillis();
504         for (StoreFile sharedStoreFile : mSharedStores) {
505             sharedStoreFile.writeBufferedRawData();
506         }
507         if (mUserStores != null) {
508             for (StoreFile userStoreFile : mUserStores) {
509                 userStoreFile.writeBufferedRawData();
510             }
511         }
512         long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
513         try {
514             mWifiMetrics.noteWifiConfigStoreWriteDuration(toIntExact(writeTime));
515         } catch (ArithmeticException e) {
516             // Silently ignore on any overflow errors.
517         }
518         Log.d(TAG, "Writing to stores completed in " + writeTime + " ms.");
519     }
520 
521     /**
522      * Note: This is a copy of {@link AtomicFile#readFully()} modified to use the passed in
523      * {@link InputStream} which was returned using {@link AtomicFile#openRead()}.
524      */
readAtomicFileFully(InputStream stream)525     private static byte[] readAtomicFileFully(InputStream stream) throws IOException {
526         try {
527             int pos = 0;
528             int avail = stream.available();
529             byte[] data = new byte[avail];
530             while (true) {
531                 int amt = stream.read(data, pos, data.length - pos);
532                 if (amt <= 0) {
533                     return data;
534                 }
535                 pos += amt;
536                 avail = stream.available();
537                 if (avail > data.length - pos) {
538                     byte[] newData = new byte[pos + avail];
539                     System.arraycopy(data, 0, newData, 0, pos);
540                     data = newData;
541                 }
542             }
543         } finally {
544             stream.close();
545         }
546     }
547 
548     /**
549      * Conversion for file id's to use in WifiMigration API surface.
550      */
getMigrationStoreFileId(@toreFileId int fileId)551     private static Integer getMigrationStoreFileId(@StoreFileId int fileId) {
552         switch (fileId) {
553             case STORE_FILE_SHARED_GENERAL:
554                 return WifiMigration.STORE_FILE_SHARED_GENERAL;
555             case STORE_FILE_SHARED_SOFTAP:
556                 return WifiMigration.STORE_FILE_SHARED_SOFTAP;
557             case STORE_FILE_USER_GENERAL:
558                 return WifiMigration.STORE_FILE_USER_GENERAL;
559             case STORE_FILE_USER_NETWORK_SUGGESTIONS:
560                 return WifiMigration.STORE_FILE_USER_NETWORK_SUGGESTIONS;
561             default:
562                 return null;
563         }
564     }
565 
readDataFromMigrationSharedStoreFile(@toreFileId int fileId)566     private static byte[] readDataFromMigrationSharedStoreFile(@StoreFileId int fileId)
567             throws IOException {
568         Integer migrationStoreFileId = getMigrationStoreFileId(fileId);
569         if (migrationStoreFileId == null) return null;
570         InputStream migrationIs =
571                 WifiMigration.convertAndRetrieveSharedConfigStoreFile(migrationStoreFileId);
572         if (migrationIs == null) return null;
573         return readAtomicFileFully(migrationIs);
574     }
575 
readDataFromMigrationUserStoreFile(@toreFileId int fileId, UserHandle userHandle)576     private static byte[] readDataFromMigrationUserStoreFile(@StoreFileId int fileId,
577             UserHandle userHandle) throws IOException {
578         Integer migrationStoreFileId = getMigrationStoreFileId(fileId);
579         if (migrationStoreFileId == null) return null;
580         InputStream migrationIs =
581                 WifiMigration.convertAndRetrieveUserConfigStoreFile(
582                         migrationStoreFileId, userHandle);
583         if (migrationIs == null) return null;
584         return readAtomicFileFully(migrationIs);
585     }
586 
587     /**
588      * Helper method to read from the shared store files.
589      * @throws XmlPullParserException
590      * @throws IOException
591      */
readFromSharedStoreFiles()592     private void readFromSharedStoreFiles() throws XmlPullParserException, IOException {
593         for (StoreFile sharedStoreFile : mSharedStores) {
594             byte[] sharedDataBytes =
595                     readDataFromMigrationSharedStoreFile(sharedStoreFile.getFileId());
596             if (sharedDataBytes == null) {
597                 // nothing to migrate, do normal read.
598                 sharedDataBytes = sharedStoreFile.readRawData();
599             } else {
600                 Log.i(TAG, "Read data out of shared migration store file: "
601                         + sharedStoreFile.getName());
602                 // Save the migrated file contents to the regular store file and delete the
603                 // migrated stored file.
604                 sharedStoreFile.storeRawDataToWrite(sharedDataBytes);
605                 sharedStoreFile.writeBufferedRawData();
606                 // Note: If the migrated store file is at the same location as the store file,
607                 // then the OEM implementation should ignore this remove.
608                 WifiMigration.removeSharedConfigStoreFile(
609                         getMigrationStoreFileId(sharedStoreFile.getFileId()));
610             }
611             deserializeData(sharedDataBytes, sharedStoreFile);
612         }
613     }
614 
615     /**
616      * Helper method to read from the user store files.
617      * @throws XmlPullParserException
618      * @throws IOException
619      */
readFromUserStoreFiles()620     private void readFromUserStoreFiles() throws XmlPullParserException, IOException {
621         for (StoreFile userStoreFile : mUserStores) {
622             byte[] userDataBytes = readDataFromMigrationUserStoreFile(
623                     userStoreFile.getFileId(), userStoreFile.mUserHandle);
624             if (userDataBytes == null) {
625                 // nothing to migrate, do normal read.
626                 userDataBytes = userStoreFile.readRawData();
627             } else {
628                 Log.i(TAG, "Read data out of user migration store file: "
629                         + userStoreFile.getName());
630                 // Save the migrated file contents to the regular store file and delete the
631                 // migrated stored file.
632                 userStoreFile.storeRawDataToWrite(userDataBytes);
633                 userStoreFile.writeBufferedRawData();
634                 // Note: If the migrated store file is at the same location as the store file,
635                 // then the OEM implementation should ignore this remove.
636                 WifiMigration.removeUserConfigStoreFile(
637                         getMigrationStoreFileId(userStoreFile.getFileId()),
638                         userStoreFile.mUserHandle);
639             }
640             deserializeData(userDataBytes, userStoreFile);
641         }
642     }
643 
644     /**
645      * API to read the store data from the config stores.
646      * The method reads the user specific configurations from user specific config store and the
647      * shared configurations from the shared config store.
648      */
read()649     public void read() throws XmlPullParserException, IOException {
650         // Reset both share and user store data.
651         for (StoreFile sharedStoreFile : mSharedStores) {
652             resetStoreData(sharedStoreFile);
653         }
654         if (mUserStores != null) {
655             for (StoreFile userStoreFile : mUserStores) {
656                 resetStoreData(userStoreFile);
657             }
658         }
659         long readStartTime = mClock.getElapsedSinceBootMillis();
660         readFromSharedStoreFiles();
661         if (mUserStores != null) {
662             readFromUserStoreFiles();
663         }
664         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
665         try {
666             mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
667         } catch (ArithmeticException e) {
668             // Silently ignore on any overflow errors.
669         }
670         Log.d(TAG, "Reading from all stores completed in " + readTime + " ms.");
671     }
672 
673     /**
674      * Handles a user switch. This method changes the user specific store files and reads from the
675      * new user's store files.
676      *
677      * @param userStores List of {@link StoreFile} created using {@link #createUserFiles(int)}.
678      */
switchUserStoresAndRead(@onNull List<StoreFile> userStores)679     public void switchUserStoresAndRead(@NonNull List<StoreFile> userStores)
680             throws XmlPullParserException, IOException {
681         Preconditions.checkNotNull(userStores);
682         // Reset user store data.
683         if (mUserStores != null) {
684             for (StoreFile userStoreFile : mUserStores) {
685                 resetStoreData(userStoreFile);
686             }
687         }
688 
689         // Stop any pending buffered writes, if any.
690         stopBufferedWriteAlarm();
691         mUserStores = userStores;
692 
693         // Now read from the user store files.
694         long readStartTime = mClock.getElapsedSinceBootMillis();
695         readFromUserStoreFiles();
696         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
697         mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
698         Log.d(TAG, "Reading from user stores completed in " + readTime + " ms.");
699     }
700 
701     /**
702      * Reset data for all {@link StoreData} instances registered for this {@link StoreFile}.
703      */
resetStoreData(@onNull StoreFile storeFile)704     private void resetStoreData(@NonNull StoreFile storeFile) {
705         for (StoreData storeData: retrieveStoreDataListForStoreFile(storeFile)) {
706             storeData.resetData();
707         }
708     }
709 
710     // Inform all the provided store data clients that there is nothing in the store for them.
indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet, @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil)711     private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet,
712             @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil)
713             throws XmlPullParserException, IOException {
714         for (StoreData storeData : storeDataSet) {
715             storeData.deserializeData(null, 0, version, encryptionUtil);
716         }
717     }
718 
719     /**
720      * Deserialize data from a {@link StoreFile} for all {@link StoreData} instances registered.
721      *
722      * This method also computes the integrity of the incoming |dataBytes| and compare with
723      * {@link EncryptedData} parsed from |dataBytes|. If the integrity check fails, the data
724      * is discarded.
725      *
726      * @param dataBytes The data to parse
727      * @param storeFile StoreFile that we read from. Will be used to retrieve the list of clients
728      *                  who have data to deserialize from this file.
729      *
730      * @throws XmlPullParserException
731      * @throws IOException
732      */
deserializeData(@onNull byte[] dataBytes, @NonNull StoreFile storeFile)733     private void deserializeData(@NonNull byte[] dataBytes, @NonNull StoreFile storeFile)
734             throws XmlPullParserException, IOException {
735         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
736         if (dataBytes == null) {
737             indicateNoDataForStoreDatas(storeDataList, -1 /* unknown */,
738                     storeFile.getEncryptionUtil());
739             return;
740         }
741         final XmlPullParser in = Xml.newPullParser();
742         final ByteArrayInputStream inputStream = new ByteArrayInputStream(dataBytes);
743         in.setInput(inputStream, StandardCharsets.UTF_8.name());
744 
745         // Start parsing the XML stream.
746         int rootTagDepth = in.getDepth() + 1;
747         XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
748 
749         @Version int version = parseVersionFromXml(in);
750         // Version 2 contains the now unused integrity data, parse & then discard the information.
751         if (version == INTEGRITY_CONFIG_STORE_DATA_VERSION) {
752             parseAndDiscardIntegrityDataFromXml(in, rootTagDepth);
753         }
754 
755         String[] headerName = new String[1];
756         Set<StoreData> storeDatasInvoked = new HashSet<>();
757         while (XmlUtil.gotoNextSectionOrEnd(in, headerName, rootTagDepth)) {
758             // There can only be 1 store data matching the tag, O indicates a previous StoreData
759             // module that no longer exists (ignore this XML section).
760             StoreData storeData = storeDataList.stream()
761                     .filter(s -> s.getName().equals(headerName[0]))
762                     .findAny()
763                     .orElse(null);
764             if (storeData == null) {
765                 Log.e(TAG, "Unknown store data: " + headerName[0] + ". List of store data: "
766                         + storeDataList);
767                 continue;
768             }
769             storeData.deserializeData(in, rootTagDepth + 1, version,
770                     storeFile.getEncryptionUtil());
771             storeDatasInvoked.add(storeData);
772         }
773         // Inform all the other registered store data clients that there is nothing in the store
774         // for them.
775         Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList);
776         storeDatasNotInvoked.removeAll(storeDatasInvoked);
777         indicateNoDataForStoreDatas(storeDatasNotInvoked, version, storeFile.getEncryptionUtil());
778     }
779 
780     /**
781      * Parse the version from the XML stream.
782      * This is used for both the shared and user config store data.
783      *
784      * @param in XmlPullParser instance pointing to the XML stream.
785      * @return version number retrieved from the Xml stream.
786      */
parseVersionFromXml(XmlPullParser in)787     private static @Version int parseVersionFromXml(XmlPullParser in)
788             throws XmlPullParserException, IOException {
789         int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
790         if (version < INITIAL_CONFIG_STORE_DATA_VERSION
791                 || version > CURRENT_CONFIG_STORE_DATA_VERSION) {
792             throw new XmlPullParserException("Invalid version of data: " + version);
793         }
794         return version;
795     }
796 
797     /**
798      * Parse the integrity data structure from the XML stream and discard it.
799      *
800      * @param in XmlPullParser instance pointing to the XML stream.
801      * @param outerTagDepth Outer tag depth.
802      */
parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth)803     private static void parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth)
804             throws XmlPullParserException, IOException {
805         XmlUtil.gotoNextSectionWithName(in, XML_TAG_HEADER_INTEGRITY, outerTagDepth);
806         XmlUtil.EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
807     }
808 
809     /**
810      * Dump the local log buffer and other internal state of WifiConfigManager.
811      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)812     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
813         pw.println("Dump of WifiConfigStore");
814         pw.println("WifiConfigStore - Store File Begin ----");
815         Stream.of(mSharedStores, mUserStores)
816                 .flatMap(List::stream)
817                 .forEach((storeFile) -> {
818                     pw.print("Name: " + storeFile.mFileName);
819                     pw.print(", File Id: " + storeFile.mFileId);
820                     pw.println(", Credentials encrypted: "
821                             + (storeFile.getEncryptionUtil() != null));
822                 });
823         pw.println("WifiConfigStore - Store Data Begin ----");
824         for (StoreData storeData : mStoreDataList) {
825             pw.print("StoreData =>");
826             pw.print(" ");
827             pw.print("Name: " + storeData.getName());
828             pw.print(", ");
829             pw.print("File Id: " + storeData.getStoreFileId());
830             pw.print(", ");
831             pw.println("File Name: " + STORE_ID_TO_FILE_NAME.get(storeData.getStoreFileId()));
832         }
833         pw.println("WifiConfigStore - Store Data End ----");
834     }
835 
836     /**
837      * Class to encapsulate all file writes. This is a wrapper over {@link AtomicFile} to write/read
838      * raw data from the persistent file with integrity. This class provides helper methods to
839      * read/write the entire file into a byte array.
840      * This helps to separate out the processing, parsing, and integrity checking from the actual
841      * file writing.
842      */
843     public static class StoreFile {
844         /**
845          * File permissions to lock down the file.
846          */
847         private static final int FILE_MODE = 0600;
848         /**
849          * The store file to be written to.
850          */
851         private final AtomicFile mAtomicFile;
852         /**
853          * This is an intermediate buffer to store the data to be written.
854          */
855         private byte[] mWriteData;
856         /**
857          * Store the file name for setting the file permissions/logging purposes.
858          */
859         private final String mFileName;
860         /**
861          * {@link StoreFileId} Type of store file.
862          */
863         private final @StoreFileId int mFileId;
864         /**
865          * User handle. Meaningful only for user specific store files.
866          */
867         private final UserHandle mUserHandle;
868         /**
869          * Integrity checking for the store file.
870          */
871         private final WifiConfigStoreEncryptionUtil mEncryptionUtil;
872 
StoreFile(File file, @StoreFileId int fileId, @NonNull UserHandle userHandle, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)873         public StoreFile(File file, @StoreFileId int fileId,
874                 @NonNull UserHandle userHandle,
875                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) {
876             mAtomicFile = new AtomicFile(file);
877             mFileName = file.getAbsolutePath();
878             mFileId = fileId;
879             mUserHandle = userHandle;
880             mEncryptionUtil = encryptionUtil;
881         }
882 
getName()883         public String getName() {
884             return mAtomicFile.getBaseFile().getName();
885         }
886 
getFileId()887         public @StoreFileId int getFileId() {
888             return mFileId;
889         }
890 
891         /**
892          * @return Returns the encryption util used for this store file.
893          */
getEncryptionUtil()894         public @Nullable WifiConfigStoreEncryptionUtil getEncryptionUtil() {
895             return mEncryptionUtil;
896         }
897 
898         /**
899          * Read the entire raw data from the store file and return in a byte array.
900          *
901          * @return raw data read from the file or null if the file is not found or the data has
902          *  been altered.
903          * @throws IOException if an error occurs. The input stream is always closed by the method
904          * even when an exception is encountered.
905          */
readRawData()906         public byte[] readRawData() throws IOException {
907             byte[] bytes = null;
908             try {
909                 bytes = mAtomicFile.readFully();
910             } catch (FileNotFoundException e) {
911                 return null;
912             }
913             return bytes;
914         }
915 
916         /**
917          * Store the provided byte array to be written when {@link #writeBufferedRawData()} method
918          * is invoked.
919          * This intermediate step is needed to help in buffering file writes.
920          *
921          * @param data raw data to be written to the file.
922          */
storeRawDataToWrite(byte[] data)923         public void storeRawDataToWrite(byte[] data) {
924             mWriteData = data;
925         }
926 
927         /**
928          * Write the stored raw data to the store file.
929          * After the write to file, the mWriteData member is reset.
930          * @throws IOException if an error occurs. The output stream is always closed by the method
931          * even when an exception is encountered.
932          */
writeBufferedRawData()933         public void writeBufferedRawData() throws IOException {
934             if (mWriteData == null) return; // No data to write for this file.
935             // Write the data to the atomic file.
936             FileOutputStream out = null;
937             try {
938                 out = mAtomicFile.startWrite();
939                 FileUtils.chmod(mFileName, FILE_MODE);
940                 out.write(mWriteData);
941                 mAtomicFile.finishWrite(out);
942             } catch (IOException e) {
943                 if (out != null) {
944                     mAtomicFile.failWrite(out);
945                 }
946                 throw e;
947             }
948             // Reset the pending write data after write.
949             mWriteData = null;
950         }
951     }
952 
953     /**
954      * Interface to be implemented by a module that contained data in the config store file.
955      *
956      * The module will be responsible for serializing/deserializing their own data.
957      * Whenever {@link WifiConfigStore#read()} is invoked, all registered StoreData instances will
958      * be notified that a read was performed via {@link StoreData#deserializeData(
959      * XmlPullParser, int)} regardless of whether there is any data for them or not in the
960      * store file.
961      *
962      * Note: StoreData clients that need a config store read to kick-off operations should wait
963      * for the {@link StoreData#deserializeData(XmlPullParser, int)} invocation.
964      */
965     public interface StoreData {
966         /**
967          * Serialize a XML data block to the output stream.
968          *
969          * @param out The output stream to serialize the data to
970          * @param encryptionUtil Utility to help encrypt any credential data.
971          */
serializeData(XmlSerializer out, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)972         void serializeData(XmlSerializer out,
973                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
974                 throws XmlPullParserException, IOException;
975 
976         /**
977          * Deserialize a XML data block from the input stream.
978          *
979          * @param in The input stream to read the data from. This could be null if there is
980          *           nothing in the store.
981          * @param outerTagDepth The depth of the outer tag in the XML document
982          * @param version Version of config store file.
983          * @param encryptionUtil Utility to help decrypt any credential data.
984          *
985          * Note: This will be invoked every time a store file is read, even if there is nothing
986          *                      in the store for them.
987          */
deserializeData(@ullable XmlPullParser in, int outerTagDepth, @Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)988         void deserializeData(@Nullable XmlPullParser in, int outerTagDepth, @Version int version,
989                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
990                 throws XmlPullParserException, IOException;
991 
992         /**
993          * Reset configuration data.
994          */
resetData()995         void resetData();
996 
997         /**
998          * Check if there is any new data to persist from the last write.
999          *
1000          * @return true if the module has new data to persist, false otherwise.
1001          */
hasNewDataToSerialize()1002         boolean hasNewDataToSerialize();
1003 
1004         /**
1005          * Return the name of this store data.  The data will be enclosed under this tag in
1006          * the XML block.
1007          *
1008          * @return The name of the store data
1009          */
getName()1010         String getName();
1011 
1012         /**
1013          * File Id where this data needs to be written to.
1014          * This should be one of {@link #STORE_FILE_SHARED_GENERAL},
1015          * {@link #STORE_FILE_USER_GENERAL} or
1016          * {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}.
1017          *
1018          * Note: For most uses, the shared or user general store is sufficient. Creating and
1019          * managing store files are expensive. Only use specific store files if you have a large
1020          * amount of data which may not need to be persisted frequently (or at least not as
1021          * frequently as the general store).
1022          * @return Id of the file where this data needs to be persisted.
1023          */
getStoreFileId()1024         @StoreFileId int getStoreFileId();
1025     }
1026 }
1027