Add westworld logging to signed config.
This will allow verification of how well the feature works in prod, as
well as tracking real time usage once Q is released.
Test: atest CtsSignedConfigHostTests
Test: ./out/host/linux-x86/bin/statsd_testdrive 123
Bug: 110509075
Bug: 122350327
Change-Id: Ibada9490e18cbeee74e18aaa93ba9d0d7d03845e
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6267ac2..f9828a2 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -181,6 +181,7 @@
DocsUISearchTypeReported docs_ui_search_type_reported = 120;
DataStallEvent data_stall_event = 121;
RescuePartyResetReported rescue_party_reset_reported = 122;
+ SignedConfigReported signed_config_reported = 123;
}
// Pulled events will start at field 10000.
@@ -3859,3 +3860,44 @@
// The rescue level of this reset. A value of 0 indicates missing or unknown level information.
optional int32 rescue_level = 1;
}
+
+/**
+ * Logs when signed config is received from an APK, and if that config was applied successfully.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/signedconfig/SignedConfigService.java
+ */
+message SignedConfigReported {
+ enum Type {
+ UNKNOWN_TYPE = 0;
+ GLOBAL_SETTINGS = 1;
+ }
+ optional Type type = 1;
+
+ // The final status of the signed config received.
+ enum Status {
+ UNKNOWN_STATUS = 0;
+ APPLIED = 1;
+ BASE64_FAILURE_CONFIG = 2;
+ BASE64_FAILURE_SIGNATURE = 3;
+ SECURITY_EXCEPTION = 4;
+ INVALID_CONFIG = 5;
+ OLD_CONFIG = 6;
+ SIGNATURE_CHECK_FAILED = 7;
+ NOT_APPLICABLE = 8;
+ }
+ optional Status status = 2;
+
+ // The version of the signed config processed.
+ optional int32 version = 3;
+
+ // The package name that the config was extracted from.
+ optional string from_package = 4;
+
+ enum Key {
+ NO_KEY = 0;
+ DEBUG = 1;
+ PRODUCTION = 2;
+ }
+ // Which key was used to verify the config.
+ optional Key verified_with = 5;
+}
diff --git a/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java b/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java
index 438c303..d77cf90 100644
--- a/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java
+++ b/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java
@@ -23,6 +23,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.StatsLog;
import java.security.GeneralSecurityException;
import java.util.Arrays;
@@ -66,12 +67,14 @@
private final Context mContext;
private final String mSourcePackage;
+ private final SignedConfigEvent mEvent;
private final SignatureVerifier mVerifier;
- GlobalSettingsConfigApplicator(Context context, String sourcePackage) {
+ GlobalSettingsConfigApplicator(Context context, String sourcePackage, SignedConfigEvent event) {
mContext = context;
mSourcePackage = sourcePackage;
- mVerifier = new SignatureVerifier();
+ mEvent = event;
+ mVerifier = new SignatureVerifier(mEvent);
}
private boolean checkSignature(String data, String signature) {
@@ -79,6 +82,7 @@
return mVerifier.verifySignature(data, signature);
} catch (GeneralSecurityException e) {
Slog.e(TAG, "Failed to verify signature", e);
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SECURITY_EXCEPTION;
return false;
}
}
@@ -109,14 +113,17 @@
SignedConfig config;
try {
config = SignedConfig.parse(configStr, ALLOWED_KEYS, KEY_VALUE_MAPPERS);
+ mEvent.version = config.version;
} catch (InvalidConfigException e) {
Slog.e(TAG, "Failed to parse global settings from package " + mSourcePackage, e);
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__INVALID_CONFIG;
return;
}
int currentVersion = getCurrentConfigVersion();
if (currentVersion >= config.version) {
Slog.i(TAG, "Global settings from package " + mSourcePackage
+ " is older than existing: " + config.version + "<=" + currentVersion);
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__OLD_CONFIG;
return;
}
// We have new config!
@@ -126,10 +133,12 @@
config.getMatchingConfig(Build.VERSION.SDK_INT);
if (matchedConfig == null) {
Slog.i(TAG, "Settings is not applicable to current SDK version; ignoring");
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__NOT_APPLICABLE;
return;
}
Slog.i(TAG, "Updating global settings to version " + config.version);
updateCurrentConfig(config.version, matchedConfig.values);
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__APPLIED;
}
}
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 944db84..5ba57b5 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -18,6 +18,7 @@
import android.os.Build;
import android.util.Slog;
+import android.util.StatsLog;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
@@ -43,9 +44,11 @@
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
+ "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+ private final SignedConfigEvent mEvent;
private final PublicKey mDebugKey;
- public SignatureVerifier() {
+ public SignatureVerifier(SignedConfigEvent event) {
+ mEvent = event;
mDebugKey = createKey(DEBUG_KEY);
}
@@ -80,6 +83,7 @@
try {
signature = Base64.getDecoder().decode(base64Signature);
} catch (IllegalArgumentException e) {
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__BASE64_FAILURE_SIGNATURE;
Slog.e(TAG, "Failed to base64 decode signature");
return false;
}
@@ -94,6 +98,7 @@
verifier.update(data);
if (verifier.verify(signature)) {
Slog.i(TAG, "Verified config using debug key");
+ mEvent.verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__DEBUG;
return true;
} else {
if (DBG) Slog.i(TAG, "Config verification failed using debug key");
@@ -104,6 +109,7 @@
}
// TODO verify production key.
Slog.w(TAG, "NO PRODUCTION KEY YET, FAILING VERIFICATION");
+ mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SIGNATURE_CHECK_FAILED;
return false;
}
}
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java b/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java
new file mode 100644
index 0000000..2f2062c
--- /dev/null
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.signedconfig;
+
+import android.util.StatsLog;
+
+/**
+ * Helper class to allow a SignedConfigReported event to be built up in stages.
+ */
+public class SignedConfigEvent {
+
+ public int type = StatsLog.SIGNED_CONFIG_REPORTED__TYPE__UNKNOWN_TYPE;
+ public int status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__UNKNOWN_STATUS;
+ public int version = 0;
+ public String fromPackage = null;
+ public int verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__NO_KEY;
+
+ /**
+ * Write this event to statslog.
+ */
+ public void send() {
+ StatsLog.write(StatsLog.SIGNED_CONFIG_REPORTED,
+ type, status, version, fromPackage, verifiedWith);
+ }
+
+}
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigService.java b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
index 6bcee14..dc39542 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
@@ -26,6 +26,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.util.Slog;
+import android.util.StatsLog;
import com.android.server.LocalServices;
@@ -82,21 +83,30 @@
}
if (metaData.containsKey(KEY_GLOBAL_SETTINGS)
&& metaData.containsKey(KEY_GLOBAL_SETTINGS_SIGNATURE)) {
- String config = metaData.getString(KEY_GLOBAL_SETTINGS);
- String signature = metaData.getString(KEY_GLOBAL_SETTINGS_SIGNATURE);
+ SignedConfigEvent event = new SignedConfigEvent();
try {
- // Base64 encoding is standard (not URL safe) encoding: RFC4648
- config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8);
- } catch (IllegalArgumentException iae) {
- Slog.e(TAG, "Failed to base64 decode global settings config from " + packageName);
- return;
+ event.type = StatsLog.SIGNED_CONFIG_REPORTED__TYPE__GLOBAL_SETTINGS;
+ event.fromPackage = packageName;
+ String config = metaData.getString(KEY_GLOBAL_SETTINGS);
+ String signature = metaData.getString(KEY_GLOBAL_SETTINGS_SIGNATURE);
+ try {
+ // Base64 encoding is standard (not URL safe) encoding: RFC4648
+ config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to base64 decode global settings config from "
+ + packageName);
+ event.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__BASE64_FAILURE_CONFIG;
+ return;
+ }
+ if (DBG) {
+ Slog.d(TAG, "Got global settings config: " + config);
+ Slog.d(TAG, "Got global settings signature: " + signature);
+ }
+ new GlobalSettingsConfigApplicator(mContext, packageName, event).applyConfig(
+ config, signature);
+ } finally {
+ event.send();
}
- if (DBG) {
- Slog.d(TAG, "Got global settings config: " + config);
- Slog.d(TAG, "Got global settings signature: " + signature);
- }
- new GlobalSettingsConfigApplicator(mContext, packageName).applyConfig(
- config, signature);
} else {
if (DBG) Slog.d(TAG, "Package has no global settings config/signature.");
}