blob: 05991bf94d8aaa369ceacf7ed06d8134214f1993 [file] [log] [blame]
/**
* Copyright (C) 2022 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.remoteprovisioner;
import android.content.Context;
import android.hardware.security.keymint.SecurityLevel;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import java.time.Duration;
/**
* Contains the metrics values that are recorded for every attempt to remotely provision keys.
* This class will automatically push the atoms on close, and is intended to be used with a
* try-with-resources block to ensure metrics are automatically logged on completion of an attempt.
*/
public final class ProvisionerMetrics implements AutoCloseable {
// The state of remote provisioning enablement
public enum Enablement {
UNKNOWN,
ENABLED_WITH_FALLBACK,
ENABLED_RKP_ONLY,
DISABLED
}
public enum Status {
UNKNOWN,
KEYS_SUCCESSFULLY_PROVISIONED,
NO_PROVISIONING_NEEDED,
PROVISIONING_DISABLED,
INTERNAL_ERROR,
NO_NETWORK_CONNECTIVITY,
OUT_OF_ERROR_BUDGET,
INTERRUPTED,
GENERATE_KEYPAIR_FAILED,
GENERATE_CSR_FAILED,
GET_POOL_STATUS_FAILED,
INSERT_CHAIN_INTO_POOL_FAILED,
FETCH_GEEK_TIMED_OUT,
FETCH_GEEK_IO_EXCEPTION,
FETCH_GEEK_HTTP_ERROR,
SIGN_CERTS_TIMED_OUT,
SIGN_CERTS_IO_EXCEPTION,
SIGN_CERTS_HTTP_ERROR,
SIGN_CERTS_DEVICE_NOT_REGISTERED
}
/**
* Restartable stopwatch class that can be used to measure multiple start->stop time
* intervals. All measured time intervals are summed and returned by getElapsedMillis.
*/
public static class StopWatch implements AutoCloseable {
private long mStartTime = 0;
private long mElapsedTime = 0;
/** Start or resume a timer. */
public void start() {
if (isRunning()) {
Log.w(TAG, "Starting a timer that's already been running for "
+ getElapsedMillis() + "ms");
} else {
mStartTime = SystemClock.elapsedRealtime();
}
}
/** Stop recording time. */
public void stop() {
if (!isRunning()) {
Log.w(TAG, "Attempting to stop a timer that hasn't been started.");
} else {
mElapsedTime += SystemClock.elapsedRealtime() - mStartTime;
mStartTime = 0;
}
}
/** Stops the timer if it's running. */
@Override
public void close() {
if (isRunning()) {
stop();
}
}
/** Get how long the timer has been recording. */
public int getElapsedMillis() {
if (isRunning()) {
return (int) (mElapsedTime + SystemClock.elapsedRealtime() - mStartTime);
} else {
return (int) mElapsedTime;
}
}
/** Is the timer currently recording time? */
public boolean isRunning() {
return mStartTime != 0;
}
}
private static final String TAG = Provisioner.TAG;
private final Context mContext;
private final int mCause;
private final StopWatch mServerWaitTimer = new StopWatch();
private final StopWatch mBinderWaitTimer = new StopWatch();
private final StopWatch mLockWaitTimer = new StopWatch();
private final StopWatch mTotalTimer = new StopWatch();
private final String mRemotelyProvisionedComponent;
private Enablement mEnablement;
private boolean mIsKeyPoolEmpty = false;
private Status mStatus = Status.UNKNOWN;
private int mHttpStatusError = 0;
private ProvisionerMetrics(Context context, int cause,
String remotelyProvisionedComponent, Enablement enablement) {
mContext = context;
mCause = cause;
mRemotelyProvisionedComponent = remotelyProvisionedComponent;
mEnablement = enablement;
mTotalTimer.start();
}
/** Start collecting metrics for scheduled provisioning. */
public static ProvisionerMetrics createScheduledAttemptMetrics(Context context) {
// Scheduled jobs (PeriodicProvisioner) intermix a lot of operations for multiple
// components, which makes it difficult to tease apart what is happening for which
// remotely provisioned component. Thus, on these calls, the component and
// component-specific enablement are not logged.
return new ProvisionerMetrics(
context,
RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_ATTEMPT__CAUSE__SCHEDULED,
"",
Enablement.UNKNOWN);
}
/** Start collecting metrics when an attestation key has been consumed from the pool. */
public static ProvisionerMetrics createKeyConsumedAttemptMetrics(Context context,
int securityLevel) {
return new ProvisionerMetrics(
context,
RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_ATTEMPT__CAUSE__KEY_CONSUMED,
getRemotelyProvisionedComponentName(securityLevel),
getEnablementForSecurityLevel(securityLevel));
}
/** Start collecting metrics when the spare attestation key pool is empty. */
public static ProvisionerMetrics createOutOfKeysAttemptMetrics(Context context,
int securityLevel) {
return new ProvisionerMetrics(
context,
RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_ATTEMPT__CAUSE__OUT_OF_KEYS,
getRemotelyProvisionedComponentName(securityLevel),
getEnablementForSecurityLevel(securityLevel));
}
/** Record the state of RKP configuration. */
public void setEnablement(Enablement enablement) {
mEnablement = enablement;
}
/** Set to true if the provisioning encountered an empty key pool. */
public void setIsKeyPoolEmpty(boolean isEmpty) {
mIsKeyPoolEmpty = isEmpty;
}
/** Set the status for this provisioning attempt. */
public void setStatus(Status status) {
mStatus = status;
}
/** Set the last HTTP status encountered. */
public void setHttpStatusError(int httpStatusError) {
mHttpStatusError = httpStatusError;
}
/**
* Starts the server wait timer, returning a reference to an object to be closed when the
* wait is over.
*/
public StopWatch startServerWait() {
mServerWaitTimer.start();
return mServerWaitTimer;
}
/**
* Starts the binder wait timer, returning a reference to an object to be closed when the
* wait is over.
*/
public StopWatch startBinderWait() {
mBinderWaitTimer.start();
return mBinderWaitTimer;
}
/**
* Starts the lock wait timer, returning a reference to an object to be closed when the
* wait is over.
*/
public StopWatch startLockWait() {
mLockWaitTimer.start();
return mLockWaitTimer;
}
/** Record the atoms for this metrics object. */
@Override
public void close() {
mTotalTimer.stop();
int transportType = getTransportTypeForActiveNetwork();
RemoteProvisionerStatsLog.write(RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_ATTEMPT,
mCause, mRemotelyProvisionedComponent, getUpTimeBucket(), getIntEnablement(),
mIsKeyPoolEmpty, getIntStatus());
RemoteProvisionerStatsLog.write(
RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_NETWORK_INFO,
transportType, getIntStatus(), mHttpStatusError);
RemoteProvisionerStatsLog.write(RemoteProvisionerStatsLog.REMOTE_KEY_PROVISIONING_TIMING,
mServerWaitTimer.getElapsedMillis(), mBinderWaitTimer.getElapsedMillis(),
mLockWaitTimer.getElapsedMillis(),
mTotalTimer.getElapsedMillis(), transportType, mRemotelyProvisionedComponent);
}
// TODO: Fix this in U when the provisioner uses the remotely provisioned component names.
private static String getRemotelyProvisionedComponentName(int securityLevel) {
switch (securityLevel) {
case SecurityLevel.SOFTWARE:
return "SOFTWARE_KEYMINT";
case SecurityLevel.TRUSTED_ENVIRONMENT:
return "TEE_KEYMINT";
case SecurityLevel.STRONGBOX:
return "STRONGBOX";
default:
return "UNKNOWN";
}
}
private static Enablement getEnablementForSecurityLevel(int securityLevel) {
switch (securityLevel) {
case SecurityLevel.SOFTWARE:
return Enablement.ENABLED_WITH_FALLBACK;
case SecurityLevel.TRUSTED_ENVIRONMENT:
return readRkpOnlyProperty("remote_provisioning.tee.rkp_only");
case SecurityLevel.STRONGBOX:
return readRkpOnlyProperty("remote_provisioning.strongbox.rkp_only");
default:
return Enablement.UNKNOWN;
}
}
private static Enablement readRkpOnlyProperty(String property) {
if (SystemProperties.getBoolean(property, false)) {
return Enablement.ENABLED_RKP_ONLY;
}
return Enablement.ENABLED_WITH_FALLBACK;
}
private int getTransportTypeForActiveNetwork() {
ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
if (cm == null) {
Log.w(TAG, "Unable to get ConnectivityManager instance");
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_UNKNOWN;
}
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
if (capabilities == null) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_UNKNOWN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_WIFI_CELLULAR_VPN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_CELLULAR_VPN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_WIFI_VPN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_BLUETOOTH_VPN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_ETHERNET_VPN;
}
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_UNKNOWN;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_CELLULAR;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_WIFI;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_BLUETOOTH;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_ETHERNET;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_WIFI_AWARE;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN)) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_LOWPAN;
}
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__TRANSPORT_TYPE__TT_UNKNOWN;
}
private int getUpTimeBucket() {
final long uptimeMillis = SystemClock.uptimeMillis();
if (uptimeMillis < Duration.ofMinutes(5).toMillis()) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__UPTIME__LESS_THAN_5_MINUTES;
} else if (uptimeMillis < Duration.ofMinutes(60).toMillis()) {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__UPTIME__BETWEEN_5_AND_60_MINUTES;
} else {
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__UPTIME__MORE_THAN_60_MINUTES;
}
}
private int getIntStatus() {
switch (mStatus) {
// A whole bunch of generated types here just don't fit in our line length limit.
// CHECKSTYLE:OFF Generated code
case UNKNOWN:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__REMOTE_KEY_PROVISIONING_STATUS_UNKNOWN;
case KEYS_SUCCESSFULLY_PROVISIONED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__KEYS_SUCCESSFULLY_PROVISIONED;
case NO_PROVISIONING_NEEDED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__NO_PROVISIONING_NEEDED;
case PROVISIONING_DISABLED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__PROVISIONING_DISABLED;
case INTERNAL_ERROR:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__INTERNAL_ERROR;
case NO_NETWORK_CONNECTIVITY:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__NO_NETWORK_CONNECTIVITY;
case OUT_OF_ERROR_BUDGET:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__OUT_OF_ERROR_BUDGET;
case INTERRUPTED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__INTERRUPTED;
case GENERATE_KEYPAIR_FAILED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__GENERATE_KEYPAIR_FAILED;
case GENERATE_CSR_FAILED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__GENERATE_CSR_FAILED;
case GET_POOL_STATUS_FAILED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__GET_POOL_STATUS_FAILED;
case INSERT_CHAIN_INTO_POOL_FAILED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__INSERT_CHAIN_INTO_POOL_FAILED;
case FETCH_GEEK_TIMED_OUT:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__FETCH_GEEK_TIMED_OUT;
case FETCH_GEEK_IO_EXCEPTION:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__FETCH_GEEK_IO_EXCEPTION;
case FETCH_GEEK_HTTP_ERROR:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__FETCH_GEEK_HTTP_ERROR;
case SIGN_CERTS_TIMED_OUT:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__SIGN_CERTS_TIMED_OUT;
case SIGN_CERTS_IO_EXCEPTION:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__SIGN_CERTS_IO_EXCEPTION;
case SIGN_CERTS_HTTP_ERROR:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__SIGN_CERTS_HTTP_ERROR;
case SIGN_CERTS_DEVICE_NOT_REGISTERED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__SIGN_CERTS_DEVICE_NOT_REGISTERED;
}
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_NETWORK_INFO__STATUS__REMOTE_KEY_PROVISIONING_STATUS_UNKNOWN;
// CHECKSTYLE:ON Generated code
}
private int getIntEnablement() {
switch (mEnablement) {
case UNKNOWN:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__ENABLEMENT__ENABLEMENT_UNKNOWN;
case ENABLED_WITH_FALLBACK:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__ENABLEMENT__ENABLED_WITH_FALLBACK;
case ENABLED_RKP_ONLY:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__ENABLEMENT__ENABLED_RKP_ONLY;
case DISABLED:
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__ENABLEMENT__DISABLED;
}
return RemoteProvisionerStatsLog
.REMOTE_KEY_PROVISIONING_ATTEMPT__ENABLEMENT__ENABLEMENT_UNKNOWN;
}
}