Merge "Keystore 2.0: Implement delete_key in IKeystoreSecurityLevel"
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 9875d64..c69774d 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -29,6 +29,7 @@
     rustlibs: [
         "android.hardware.security.keymint-V1-rust",
         "android.hardware.security.secureclock-V1-rust",
+        "android.hardware.security.sharedsecret-V1-rust",
         "android.os.permissions_aidl-rust",
         "android.security.apc-rust",
         "android.security.authorization-rust",
diff --git a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
index 50e674d..8bec0f7 100644
--- a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
+++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
@@ -29,6 +29,7 @@
     /**
      * Allows LockSettingsService to inform keystore about adding a new user.
      * Callers require 'AddUser' permission.
+     *
      * ## Error conditions:
      * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'AddUser' permission.
      * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of an existing user with the same
@@ -41,6 +42,7 @@
     /**
      * Allows LockSettingsService to inform keystore about removing a user.
      * Callers require 'RemoveUser' permission.
+     *
      * ## Error conditions:
      * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'RemoveUser' permission.
      * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of the user being deleted.
@@ -52,8 +54,9 @@
     /**
      * Allows LockSettingsService to inform keystore about password change of a user.
      * Callers require 'ChangePassword' permission.
+     *
      * ## Error conditions:
-     * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'ChangePassword'
+     * `ResponseCode::PERMISSION_DENIED` - if the callers does not have the 'ChangePassword'
      *                                     permission.
      * `ResponseCode::SYSTEM_ERROR` - if failed to delete the super encrypted keys of the user.
      * `ResponseCode::Locked' -  if the keystore is locked for the given user.
@@ -71,11 +74,12 @@
      * @param nspace - The UID of the app that is to be cleared if domain is Domain.APP or
      *                 the SEPolicy namespace if domain is Domain.SELINUX.
      */
-     void clearNamespace(Domain domain, long nspace);
+    void clearNamespace(Domain domain, long nspace);
 
     /**
      * Allows querying user state, given user id.
      * Callers require 'GetState' permission.
+     *
      * ## Error conditions:
      * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'GetState'
      *                                     permission.
@@ -84,4 +88,14 @@
      * @param userId - Android user id
      */
     UserState getState(in int userId);
+
+    /**
+     * Informs Keystore 2.0 that the an off body event was detected.
+     *
+     * ## Error conditions:
+     * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the `ReportOffBody`
+     *                                     permission.
+     * `ResponseCode::SYSTEM_ERROR` - if an unexpected error occurred.
+     */
+    void onDeviceOffBody();
 }
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 0e8b3d7..5f35444 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -1702,11 +1702,11 @@
                     ],
                 )
                 .context("Failed to assign attestation key")?;
-            if result != 1 {
-                return Err(KsError::sys()).context(format!(
-                    "Expected to update a single entry but instead updated {}.",
-                    result
-                ));
+            if result == 0 {
+                return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS)).context("Out of keys.");
+            } else if result > 1 {
+                return Err(KsError::sys())
+                    .context(format!("Expected to update 1 entry, instead updated {}", result));
             }
             Ok(()).no_gc()
         })
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 06648f1..2cc704b 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -804,7 +804,7 @@
                     let token_valid = now_in_millis
                         .checked_sub(auth_token_entry.time_received().milli_seconds())
                         .map_or(false, |token_age_in_millis| {
-                            token_age_in_millis > auth_token_max_age_millis
+                            auth_token_max_age_millis > token_age_in_millis
                         });
                     token_valid && auth_token_entry.satisfies(&sids, auth_type)
                 })
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 5d99449..09ffecb 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -14,13 +14,13 @@
 
 //! This crate implements the Keystore 2.0 service entry point.
 
-use keystore2::apc::ApcManager;
 use keystore2::authorization::AuthorizationManager;
 use keystore2::entropy;
 use keystore2::globals::ENFORCEMENTS;
+use keystore2::maintenance::Maintenance;
 use keystore2::remote_provisioning::RemoteProvisioningService;
 use keystore2::service::KeystoreService;
-use keystore2::user_manager::Maintenance;
+use keystore2::{apc::ApcManager, shared_secret_negotiation};
 use log::{error, info};
 use std::{panic, path::Path, sync::mpsc::channel};
 use vpnprofilestore::VpnProfileStore;
@@ -76,6 +76,7 @@
     });
 
     entropy::register_feeder();
+    shared_secret_negotiation::perform_shared_secret_negotiation();
 
     info!("Starting thread pool now.");
     binder::ProcessState::start_thread_pool();
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index bdb599d..cb47e3e 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -27,12 +27,13 @@
 pub mod key_parameter;
 pub mod legacy_blob;
 pub mod legacy_migrator;
+pub mod maintenance;
 pub mod operation;
 pub mod permission;
 pub mod remote_provisioning;
 pub mod security_level;
 pub mod service;
-pub mod user_manager;
+pub mod shared_secret_negotiation;
 pub mod utils;
 
 mod attestation_key_utils;
diff --git a/keystore2/src/user_manager.rs b/keystore2/src/maintenance.rs
similarity index 89%
rename from keystore2/src/user_manager.rs
rename to keystore2/src/maintenance.rs
index 0cc2e92..1c206fc 100644
--- a/keystore2/src/user_manager.rs
+++ b/keystore2/src/maintenance.rs
@@ -14,12 +14,12 @@
 
 //! This module implements IKeystoreMaintenance AIDL interface.
 
-use crate::error::map_or_log_err;
 use crate::error::Error as KeystoreError;
 use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
 use crate::permission::KeystorePerm;
 use crate::super_key::UserState;
 use crate::utils::check_keystore_permission;
+use crate::{database::MonotonicRawTime, error::map_or_log_err};
 use android_security_maintenance::aidl::android::security::maintenance::{
     IKeystoreMaintenance::{BnKeystoreMaintenance, IKeystoreMaintenance},
     UserState::UserState as AidlUserState,
@@ -116,6 +116,15 @@
             UserState::LskfLocked => Ok(AidlUserState::LSKF_LOCKED),
         }
     }
+
+    fn on_device_off_body() -> Result<()> {
+        // Security critical permission check. This statement must return on fail.
+        check_keystore_permission(KeystorePerm::report_off_body())
+            .context("In on_device_off_body.")?;
+
+        DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()))
+            .context("In on_device_off_body: Trying to update last off body time.")
+    }
 }
 
 impl Interface for Maintenance {}
@@ -137,7 +146,11 @@
         map_or_log_err(Self::clear_namespace(domain, nspace), Ok)
     }
 
-    fn getState(&self, user_id: i32) -> binder::public_api::Result<AidlUserState> {
+    fn getState(&self, user_id: i32) -> BinderResult<AidlUserState> {
         map_or_log_err(Self::get_state(user_id), Ok)
     }
+
+    fn onDeviceOffBody(&self) -> BinderResult<()> {
+        map_or_log_err(Self::on_device_off_body(), Ok)
+    }
 }
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index a8753d3..f0a4c87 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -311,6 +311,8 @@
         ClearUID = 0x200,    selinux name: clear_uid;
         /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens.
         GetAuthToken = 0x400,  selinux name: get_auth_token;
+        /// Checked when IKeystoreMaintenance::onDeviceOffBody is called.
+        ReportOffBody = 0x1000, selinux name: report_off_body;
     }
 );
 
diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs
new file mode 100644
index 0000000..afce533
--- /dev/null
+++ b/keystore2/src/shared_secret_negotiation.rs
@@ -0,0 +1,263 @@
+// Copyright 2021, 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.
+
+//! This module implements the shared secret negotiation.
+
+use crate::error::{map_binder_status, map_binder_status_code, Error};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::binder::Strong;
+use android_hardware_security_sharedsecret::aidl::android::hardware::security::sharedsecret::{
+    ISharedSecret::ISharedSecret, SharedSecretParameters::SharedSecretParameters,
+};
+use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
+use anyhow::{Context, Result};
+use keystore2_vintf::{get_aidl_instances, get_hidl_instances};
+use std::fmt::{self, Display, Formatter};
+
+/// This function initiates the shared secret negotiation. It starts a thread and then returns
+/// immediately. The thread consults the vintf manifest to enumerate expected negotiation
+/// participants. It then attempts to connect to all of these participants. If any connection
+/// fails the thread will retry once per second to connect to the failed instance(s) until all of
+/// the instances are connected. It then performs the negotiation.
+///
+/// During the first phase of the negotiation it will again try every second until
+/// all instances have responded successfully to account for instances that register early but
+/// are not fully functioning at this time due to hardware delays or boot order dependency issues.
+/// An error during the second phase or a checksum mismatch leads to a panic.
+pub fn perform_shared_secret_negotiation() {
+    std::thread::spawn(|| {
+        let participants = list_participants()
+            .expect("In perform_shared_secret_negotiation: Trying to list participants.");
+        let connected = connect_participants(participants);
+        negotiate_shared_secret(connected);
+        log::info!("Shared secret negotiation concluded successfully.");
+    });
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum SharedSecretParticipant {
+    /// Represents an instance of android.hardware.security.sharedsecret.ISharedSecret.
+    Aidl(String),
+    /// In the legacy case there can be at most one TEE and one Strongbox hal.
+    Hidl { is_strongbox: bool, version: (usize, usize) },
+}
+
+impl Display for SharedSecretParticipant {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match self {
+            Self::Aidl(instance) => write!(
+                f,
+                "{}.{}/{}",
+                SHARED_SECRET_PACKAGE_NAME, SHARED_SECRET_INTERFACE_NAME, instance
+            ),
+            Self::Hidl { is_strongbox, version: (ma, mi) } => write!(
+                f,
+                "{}@V{}.{}::{}/{}",
+                KEYMASTER_PACKAGE_NAME,
+                ma,
+                mi,
+                KEYMASTER_INTERFACE_NAME,
+                if *is_strongbox { "strongbox" } else { "default" }
+            ),
+        }
+    }
+}
+
+#[derive(thiserror::Error, Debug)]
+enum SharedSecretError {
+    #[error("Shared parameter retrieval failed on instance {p} with error {e:?}.")]
+    ParameterRetrieval { e: Error, p: SharedSecretParticipant },
+    #[error("Shared secret computation failed on instance {p} with error {e:?}.")]
+    Computation { e: Error, p: SharedSecretParticipant },
+    #[error("Checksum comparison failed on instance {0}.")]
+    Checksum(SharedSecretParticipant),
+}
+
+fn filter_map_legacy_km_instances(
+    name: String,
+    version: (usize, usize),
+) -> Option<SharedSecretParticipant> {
+    match name.as_str() {
+        "default" => Some(SharedSecretParticipant::Hidl { is_strongbox: false, version }),
+        "strongbox" => Some(SharedSecretParticipant::Hidl { is_strongbox: true, version }),
+        _ => {
+            log::warn!("Found unexpected keymaster instance: \"{}\"", name);
+            log::warn!("Device is misconfigured. Allowed instances are:");
+            log::warn!("   * default");
+            log::warn!("   * strongbox");
+            None
+        }
+    }
+}
+
+static KEYMASTER_PACKAGE_NAME: &str = "android.hardware.keymaster";
+static KEYMASTER_INTERFACE_NAME: &str = "IKeymasterDevice";
+static SHARED_SECRET_PACKAGE_NAME: &str = "android.hardware.security.sharedsecret";
+static SHARED_SECRET_INTERFACE_NAME: &str = "ISharedSecret";
+static COMPAT_PACKAGE_NAME: &str = "android.security.compat";
+
+/// Lists participants.
+fn list_participants() -> Result<Vec<SharedSecretParticipant>> {
+    Ok([(4, 0), (4, 1)]
+        .iter()
+        .map(|(ma, mi)| {
+            get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME)
+                .as_vec()
+                .with_context(|| format!("Trying to convert KM{}.{} names to vector.", *ma, *mi))
+                .map(|instances| {
+                    instances
+                        .into_iter()
+                        .filter_map(|name| {
+                            filter_map_legacy_km_instances(name.to_string(), (*ma, *mi))
+                        })
+                        .collect::<Vec<SharedSecretParticipant>>()
+                })
+        })
+        .collect::<Result<Vec<_>>>()
+        .map(|v| v.into_iter().flatten())
+        .and_then(|i| {
+            let participants_aidl: Vec<SharedSecretParticipant> =
+                get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME)
+                    .as_vec()
+                    .context("In list_participants: Trying to convert KM1.0 names to vector.")?
+                    .into_iter()
+                    .map(|name| SharedSecretParticipant::Aidl(name.to_string()))
+                    .collect();
+            Ok(i.chain(participants_aidl.into_iter()))
+        })
+        .context("In list_participants.")?
+        .collect())
+}
+
+fn connect_participants(
+    mut participants: Vec<SharedSecretParticipant>,
+) -> Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)> {
+    let mut connected_participants: Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)> =
+        vec![];
+    loop {
+        let (connected, not_connected) = participants.into_iter().fold(
+            (connected_participants, vec![]),
+            |(mut connected, mut failed), e| {
+                match e {
+                    SharedSecretParticipant::Aidl(instance_name) => {
+                        let service_name =
+                            format!("{}/{}", SHARED_SECRET_PACKAGE_NAME, instance_name);
+                        match map_binder_status_code(binder::get_interface(&service_name)) {
+                            Err(e) => {
+                                log::warn!(
+                                    "Unable to connect \"{}\" with error:\n{:?}\nRetrying later.",
+                                    service_name,
+                                    e
+                                );
+                                failed.push(SharedSecretParticipant::Aidl(instance_name));
+                            }
+                            Ok(service) => connected
+                                .push((service, SharedSecretParticipant::Aidl(instance_name))),
+                        }
+                    }
+                    SharedSecretParticipant::Hidl { is_strongbox, version } => {
+                        // This is a no-op if it was called before.
+                        keystore2_km_compat::add_keymint_device_service();
+
+                        // If we cannot connect to the compatibility service there is no way to
+                        // recover.
+                        // PANIC! - Unless you brought your towel.
+                        let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
+                            map_binder_status_code(binder::get_interface(COMPAT_PACKAGE_NAME))
+                                .expect(
+                                    "In connect_participants: Trying to connect to compat service.",
+                                );
+
+                        match map_binder_status(keystore_compat_service.getSharedSecret(
+                            if is_strongbox {
+                                SecurityLevel::STRONGBOX
+                            } else {
+                                SecurityLevel::TRUSTED_ENVIRONMENT
+                            },
+                        )) {
+                            Err(e) => {
+                                log::warn!(
+                                    concat!(
+                                        "Unable to connect keymaster device \"{}\" ",
+                                        "with error:\n{:?}\nRetrying later."
+                                    ),
+                                    if is_strongbox { "strongbox" } else { "TEE" },
+                                    e
+                                );
+                                failed
+                                    .push(SharedSecretParticipant::Hidl { is_strongbox, version });
+                            }
+                            Ok(service) => connected.push((
+                                service,
+                                SharedSecretParticipant::Hidl { is_strongbox, version },
+                            )),
+                        }
+                    }
+                }
+                (connected, failed)
+            },
+        );
+        participants = not_connected;
+        connected_participants = connected;
+        if participants.is_empty() {
+            break;
+        }
+        std::thread::sleep(std::time::Duration::from_millis(1000));
+    }
+    connected_participants
+}
+
+fn negotiate_shared_secret(
+    participants: Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)>,
+) {
+    // Phase 1: Get the sharing parameters from all participants.
+    let mut params = loop {
+        let result: Result<Vec<SharedSecretParameters>, SharedSecretError> = participants
+            .iter()
+            .map(|(s, p)| {
+                map_binder_status(s.getSharedSecretParameters())
+                    .map_err(|e| SharedSecretError::ParameterRetrieval { e, p: (*p).clone() })
+            })
+            .collect();
+
+        match result {
+            Err(e) => {
+                log::warn!("{:?}", e);
+                log::warn!("Retrying in one second.");
+                std::thread::sleep(std::time::Duration::from_millis(1000));
+            }
+            Ok(params) => break params,
+        }
+    };
+
+    params.sort_unstable();
+
+    // Phase 2: Send the sorted sharing parameters to all participants.
+    participants
+        .into_iter()
+        .try_fold(None, |acc, (s, p)| {
+            match (acc, map_binder_status(s.computeSharedSecret(&params))) {
+                (None, Ok(new_sum)) => Ok(Some(new_sum)),
+                (Some(old_sum), Ok(new_sum)) => {
+                    if old_sum == new_sum {
+                        Ok(Some(old_sum))
+                    } else {
+                        Err(SharedSecretError::Checksum(p))
+                    }
+                }
+                (_, Err(e)) => Err(SharedSecretError::Computation { e, p }),
+            }
+        })
+        .expect("Fatal: Shared secret computation failed.");
+}
diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp
index 77ec57d..feec8ae 100644
--- a/keystore2/src/vintf/Android.bp
+++ b/keystore2/src/vintf/Android.bp
@@ -55,6 +55,7 @@
         "--size_t-is-usize",
         "--whitelist-function", "getHalNames",
         "--whitelist-function", "getHalNamesAndVersions",
+        "--whitelist-function", "getHidlInstances",
         "--whitelist-function", "getAidlInstances",
         "--whitelist-function", "freeNames",
     ],
diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs
index c3d6d8a..8730a3e 100644
--- a/keystore2/src/vintf/lib.rs
+++ b/keystore2/src/vintf/lib.rs
@@ -14,7 +14,9 @@
 
 //! Bindings for getting the list of HALs.
 
-use keystore2_vintf_bindgen::{freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions};
+use keystore2_vintf_bindgen::{
+    freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions, getHidlInstances,
+};
 use std::ffi::{CStr, CString};
 use std::os::raw::c_char;
 use std::str::Utf8Error;
@@ -65,6 +67,32 @@
 
 /// Gets the instances of the given package, version, and interface tuple.
 /// Note that this is not a zero-cost shim: it will make copies of the strings.
+pub fn get_hidl_instances(
+    package: &str,
+    major_version: usize,
+    minor_version: usize,
+    interface_name: &str,
+) -> HalNames {
+    let mut len: usize = 0;
+    let packages = CString::new(package).expect("Failed to make CString from package.");
+    let interface_name =
+        CString::new(interface_name).expect("Failed to make CString from interface_name.");
+    // Safety: We'll wrap this in HalNames to free the memory it allocates.
+    // It stores the size of the array it returns in len.
+    let raw_strs = unsafe {
+        getHidlInstances(
+            &mut len,
+            packages.as_ptr(),
+            major_version,
+            minor_version,
+            interface_name.as_ptr(),
+        )
+    };
+    HalNames { data: raw_strs, len }
+}
+
+/// Gets the instances of the given package, version, and interface tuple.
+/// Note that this is not a zero-cost shim: it will make copies of the strings.
 pub fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> HalNames {
     let mut len: usize = 0;
     let packages = CString::new(package).expect("Failed to make CString from package.");
diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp
index dbdc046..e407efa 100644
--- a/keystore2/src/vintf/vintf.cpp
+++ b/keystore2/src/vintf/vintf.cpp
@@ -43,6 +43,15 @@
     return convert(names);
 }
 
+char** getHidlInstances(size_t* len, const char* package, size_t major_version,
+                        size_t minor_version, const char* interfaceName) {
+    android::vintf::Version version(major_version, minor_version);
+    auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+    const auto names = manifest->getHidlInstances(package, version, interfaceName);
+    *len = names.size();
+    return convert(names);
+}
+
 char** getAidlInstances(size_t* len, const char* package, size_t version,
                         const char* interfaceName) {
     auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp
index 75e80f6..091e8e8 100644
--- a/keystore2/src/vintf/vintf.hpp
+++ b/keystore2/src/vintf/vintf.hpp
@@ -23,6 +23,8 @@
 
 char** getHalNames(size_t* len);
 char** getHalNamesAndVersions(size_t* len);
+char** getHidlInstances(size_t* len, const char* package, size_t major_version,
+                        size_t minor_version, const char* interfaceName);
 char** getAidlInstances(size_t* len, const char* package, size_t version,
                         const char* interfaceName);
 void freeNames(char** names, size_t len);
diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index 1c3706d..2e5e02e 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -90,6 +90,8 @@
     "VerityUtils.cpp",
   ],
 
+  header_libs: ["odrefresh_headers"],
+
   static_libs: [
     "libmini_keyctl_static", // TODO need static?
     "libc++fs",
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
index 33d04ca..6cab8b6 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -29,6 +29,7 @@
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <logwrap/logwrap.h>
+#include <odrefresh/odrefresh.h>
 
 #include "CertUtils.h"
 #include "KeymasterSigningKey.h"
@@ -101,18 +102,11 @@
     return {};
 }
 
-bool compileArtifacts(bool force) {
+art::odrefresh::ExitCode compileArtifacts(bool force) {
     const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
-
-    return logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr) ==
-           0;
-}
-
-bool validateArtifacts() {
-    const char* const argv[] = {kOdrefreshPath, "--check"};
-
-    return logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr) ==
-           0;
+    const int exit_code =
+        logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+    return static_cast<art::odrefresh::ExitCode>(exit_code);
 }
 
 static std::string toHex(const std::vector<uint8_t>& digest) {
@@ -303,6 +297,11 @@
     };
     auto scope_guard = android::base::make_scope_guard(errorScopeGuard);
 
+    if (!android::base::GetBoolProperty("ro.apex.updatable", false)) {
+        LOG(INFO) << "Device doesn't support updatable APEX, exiting.";
+        return 0;
+    }
+
     SigningKey* key;
     if (kUseKeystore) {
         auto keystoreResult = KeystoreKey::getInstance();
@@ -349,30 +348,27 @@
         }
     }
 
-    // Ask ART whether it considers the artifacts valid
-    LOG(INFO) << "Asking odrefresh to verify artifacts (if present)...";
-    bool artifactsValid = validateArtifacts();
-    LOG(INFO) << "odrefresh said they are " << (artifactsValid ? "VALID" : "INVALID");
-
-    // A post-condition of validating artifacts is that if the ones on /system
-    // are used, kArtArtifactsDir is removed. Conversely, if kArtArtifactsDir
-    // exists, those are artifacts that will be used, and we should verify them.
-    int err = access(kArtArtifactsDir.c_str(), F_OK);
-    // If we receive any error other than ENOENT, be suspicious
-    bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
-    if (artifactsPresent) {
-        auto verificationResult = verifyArtifacts(*key, supportsFsVerity);
-        if (!verificationResult.ok()) {
-            LOG(ERROR) << verificationResult.error().message();
-            return -1;
+    art::odrefresh::ExitCode odrefresh_status = compileArtifacts(kForceCompilation);
+    if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+        LOG(INFO) << "odrefresh said artifacts are VALID";
+        // A post-condition of validating artifacts is that if the ones on /system
+        // are used, kArtArtifactsDir is removed. Conversely, if kArtArtifactsDir
+        // exists, those are artifacts that will be used, and we should verify them.
+        int err = access(kArtArtifactsDir.c_str(), F_OK);
+        // If we receive any error other than ENOENT, be suspicious
+        bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
+        if (artifactsPresent) {
+            auto verificationResult = verifyArtifacts(*key, supportsFsVerity);
+            if (!verificationResult.ok()) {
+                LOG(ERROR) << verificationResult.error().message();
+                return -1;
+            }
         }
-    }
-
-    if (!artifactsValid || kForceCompilation) {
-        LOG(INFO) << "Starting compilation... ";
-        bool ret = compileArtifacts(kForceCompilation);
-        LOG(INFO) << "Compilation done, returned " << ret;
-
+    } else if (odrefresh_status == art::odrefresh::ExitCode::kCompilationSuccess ||
+               odrefresh_status == art::odrefresh::ExitCode::kCompilationFailed) {
+        const bool compiled_all = odrefresh_status == art::odrefresh::ExitCode::kCompilationSuccess;
+        LOG(INFO) << "odrefresh compiled " << (compiled_all ? "all" : "partial")
+                  << " artifacts, returned " << odrefresh_status;
         Result<std::map<std::string, std::string>> digests;
         if (supportsFsVerity) {
             digests = addFilesToVerityRecursive(kArtArtifactsDir, *key);
@@ -385,12 +381,17 @@
             LOG(ERROR) << digests.error().message();
             return -1;
         }
-
         auto persistStatus = persistDigests(*digests, *key);
         if (!persistStatus.ok()) {
             LOG(ERROR) << persistStatus.error().message();
             return -1;
         }
+    } else if (odrefresh_status == art::odrefresh::ExitCode::kCleanupFailed) {
+        LOG(ERROR) << "odrefresh failed cleaning up existing artifacts";
+        return -1;
+    } else {
+        LOG(ERROR) << "odrefresh exited unexpectedly, returned " << odrefresh_status;
+        return -1;
     }
 
     LOG(INFO) << "On-device signing done.";