Home | History | Annotate | Download | only in signedconfig
      1 /*
      2  * Copyright (C) 2018 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.signedconfig;
     18 
     19 import android.content.Context;
     20 import android.content.pm.ApplicationInfo;
     21 import android.os.Build;
     22 import android.provider.Settings;
     23 import android.util.ArrayMap;
     24 import android.util.ArraySet;
     25 import android.util.Slog;
     26 import android.util.StatsLog;
     27 
     28 import java.security.GeneralSecurityException;
     29 import java.util.Arrays;
     30 import java.util.Collections;
     31 import java.util.Map;
     32 import java.util.Set;
     33 
     34 class GlobalSettingsConfigApplicator {
     35 
     36     private static final String TAG = "SignedConfig";
     37 
     38     private static final Set<String> ALLOWED_KEYS = Collections.unmodifiableSet(new ArraySet<>(
     39             Arrays.asList(
     40                     Settings.Global.HIDDEN_API_POLICY,
     41                     Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS
     42             )));
     43 
     44     private static final Map<String, String> HIDDEN_API_POLICY_KEY_MAP = makeMap(
     45             "DEFAULT", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT),
     46             "DISABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED),
     47             "JUST_WARN", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_JUST_WARN),
     48             "ENABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED)
     49     );
     50 
     51     private static final Map<String, Map<String, String>> KEY_VALUE_MAPPERS = makeMap(
     52             Settings.Global.HIDDEN_API_POLICY, HIDDEN_API_POLICY_KEY_MAP
     53     );
     54 
     55     private static <K, V> Map<K, V> makeMap(Object... keyValuePairs) {
     56         if (keyValuePairs.length % 2 != 0) {
     57             throw new IllegalArgumentException();
     58         }
     59         final int len = keyValuePairs.length / 2;
     60         ArrayMap<K, V> m = new ArrayMap<>(len);
     61         for (int i = 0; i < len;  ++i) {
     62             m.put((K) keyValuePairs[i * 2], (V) keyValuePairs[(i * 2) + 1]);
     63         }
     64         return Collections.unmodifiableMap(m);
     65 
     66     }
     67 
     68     private final Context mContext;
     69     private final String mSourcePackage;
     70     private final SignedConfigEvent mEvent;
     71     private final SignatureVerifier mVerifier;
     72 
     73     GlobalSettingsConfigApplicator(Context context, String sourcePackage, SignedConfigEvent event) {
     74         mContext = context;
     75         mSourcePackage = sourcePackage;
     76         mEvent = event;
     77         mVerifier = new SignatureVerifier(mEvent);
     78     }
     79 
     80     private boolean checkSignature(String data, String signature) {
     81         try {
     82             return mVerifier.verifySignature(data, signature);
     83         } catch (GeneralSecurityException e) {
     84             Slog.e(TAG, "Failed to verify signature", e);
     85             mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SECURITY_EXCEPTION;
     86             return false;
     87         }
     88     }
     89 
     90     private int getCurrentConfigVersion() {
     91         return Settings.Global.getInt(mContext.getContentResolver(),
     92                 Settings.Global.SIGNED_CONFIG_VERSION, 0);
     93     }
     94 
     95     private void updateCurrentConfig(int version, Map<String, String> values) {
     96         for (Map.Entry<String, String> e: values.entrySet()) {
     97             Settings.Global.putString(
     98                     mContext.getContentResolver(),
     99                     e.getKey(),
    100                     e.getValue());
    101         }
    102         Settings.Global.putInt(
    103                 mContext.getContentResolver(), Settings.Global.SIGNED_CONFIG_VERSION, version);
    104     }
    105 
    106 
    107     void applyConfig(String configStr, String signature) {
    108         if (!checkSignature(configStr, signature)) {
    109             Slog.e(TAG, "Signature check on global settings in package " + mSourcePackage
    110                     + " failed; ignoring");
    111             return;
    112         }
    113         SignedConfig config;
    114         try {
    115             config = SignedConfig.parse(configStr, ALLOWED_KEYS, KEY_VALUE_MAPPERS);
    116             mEvent.version = config.version;
    117         } catch (InvalidConfigException e) {
    118             Slog.e(TAG, "Failed to parse global settings from package " + mSourcePackage, e);
    119             mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__INVALID_CONFIG;
    120             return;
    121         }
    122         int currentVersion = getCurrentConfigVersion();
    123         if (currentVersion >= config.version) {
    124             Slog.i(TAG, "Global settings from package " + mSourcePackage
    125                     + " is older than existing: " + config.version + "<=" + currentVersion);
    126             mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__OLD_CONFIG;
    127             return;
    128         }
    129         // We have new config!
    130         Slog.i(TAG, "Got new global settings from package " + mSourcePackage + ": version "
    131                 + config.version + " replacing existing version " + currentVersion);
    132         SignedConfig.PerSdkConfig matchedConfig =
    133                 config.getMatchingConfig(Build.VERSION.SDK_INT);
    134         if (matchedConfig == null) {
    135             Slog.i(TAG, "Settings is not applicable to current SDK version; ignoring");
    136             mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__NOT_APPLICABLE;
    137             return;
    138         }
    139 
    140         Slog.i(TAG, "Updating global settings to version " + config.version);
    141         updateCurrentConfig(config.version, matchedConfig.values);
    142         mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__APPLIED;
    143     }
    144 }
    145