Keystore2: Handle errors from binder service calls.

This is required for handling Keymint errors received by the Keystore
service.

Test: keystore2_test
Change-Id: I7cf1b0d53db465a738c60594d929944379179836
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index f4da749..0326610 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -40,7 +40,9 @@
 
 use keystore2_selinux as selinux;
 
-use android_security_keystore2::binder::{Result as BinderResult, Status as BinderStatus};
+use android_security_keystore2::binder::{
+    ExceptionCode, Result as BinderResult, Status as BinderStatus,
+};
 
 /// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
 /// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
@@ -52,6 +54,9 @@
     /// Wraps a Keymint `ErrorCode` as defined by the Keymint AIDL interface specification.
     #[error("Error::Km({0:?})")]
     Km(ErrorCode),
+    /// Wraps a Binder exception code other than a service specific exception.
+    #[error("Binder exception code {0:?}, {1:?}")]
+    Binder(ExceptionCode, i32),
 }
 
 impl Error {
@@ -66,6 +71,36 @@
     }
 }
 
+/// Helper function to map the binder status we get from calls into KeyMint
+/// to a Keystore Error. We don't create an anyhow error here to make
+/// it easier to evaluate KeyMint errors, which we must do in some cases, e.g.,
+/// when diagnosing authentication requirements, update requirements, and running
+/// out of operation slots.
+pub fn map_km_error<T>(r: BinderResult<T>) -> Result<T, Error> {
+    r.map_err(|s| {
+        match s.exception_code() {
+            ExceptionCode::SERVICE_SPECIFIC => {
+                let se = s.service_specific_error();
+                if se < 0 {
+                    // Negative service specific errors are KM error codes.
+                    Error::Km(s.service_specific_error())
+                } else {
+                    // Non negative error codes cannot be KM error codes.
+                    // So we create an `Error::Binder` variant to preserve
+                    // the service specific error code for logging.
+                    // `map_or_log_err` will map this on a system error,
+                    // but not before logging the details to logcat.
+                    Error::Binder(ExceptionCode::SERVICE_SPECIFIC, se)
+                }
+            }
+            // We create `Error::Binder` to preserve the exception code
+            // for logging.
+            // `map_or_log_err` will map this on a system error.
+            e_code => Error::Binder(e_code, 0),
+        }
+    })
+}
+
 /// This function should be used by Keystore service calls to translate error conditions
 /// into `android.security.keystore2.Result` which is imported here as `aidl::Result`
 /// and newtyped as AidlResult.
@@ -107,6 +142,10 @@
             let rc = match root_cause.downcast_ref::<Error>() {
                 Some(Error::Rc(rcode)) => *rcode,
                 Some(Error::Km(ec)) => *ec,
+                // If an Error::Binder reaches this stage we report a system error.
+                // The exception code and possible service specific error will be
+                // printed in the error log above.
+                Some(Error::Binder(_, _)) => Rc::SystemError,
                 None => match root_cause.downcast_ref::<selinux::Error>() {
                     Some(selinux::Error::PermissionDenied) => Rc::PermissionDenied,
                     _ => Rc::SystemError,
@@ -122,6 +161,9 @@
 pub mod tests {
 
     use super::*;
+    use android_security_keystore2::binder::{
+        ExceptionCode, Result as BinderResult, Status as BinderStatus,
+    };
     use anyhow::{anyhow, Context};
 
     fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
@@ -170,6 +212,14 @@
         nested_nested_other_error().context("nested other error")
     }
 
+    fn binder_sse_error(sse: i32) -> BinderResult<()> {
+        Err(BinderStatus::new_service_specific_error(sse, None))
+    }
+
+    fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
+        Err(BinderStatus::new_exception(ex, None))
+    }
+
     #[test]
     fn keystore_error_test() -> anyhow::Result<(), String> {
         android_logger::init_once(
@@ -187,7 +237,7 @@
             );
         }
 
-        // All KeystoreKerror::Km(x) get mapped on a service
+        // All Keystore Error::Km(x) get mapped on a service
         // specific error of x.
         for ec in Ec::UNKNOWN_ERROR..Ec::ROOT_OF_TRUST_ALREADY_SET {
             assert_eq!(
@@ -197,6 +247,46 @@
             );
         }
 
+        // All Keymint errors x received through a Binder Result get mapped on
+        // a service specific error of x.
+        for ec in Ec::UNKNOWN_ERROR..Ec::ROOT_OF_TRUST_ALREADY_SET {
+            assert_eq!(
+                Result::<(), i32>::Err(ec),
+                map_or_log_err(
+                    map_km_error(binder_sse_error(ec))
+                        .with_context(|| format!("Km error code: {}.", ec)),
+                    |_| Err(BinderStatus::ok())
+                )
+                .map_err(|s| s.service_specific_error())
+            );
+        }
+
+        // map_km_error creates an Error::Binder variant storing
+        // ExceptionCode::SERVICE_SPECIFIC and the given
+        // service specific error.
+        let sse = map_km_error(binder_sse_error(1));
+        assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
+        // map_or_log_err then maps it on a service specific error of Rc::SystemError.
+        assert_eq!(
+            Result::<(), i32>::Err(Rc::SystemError),
+            map_or_log_err(sse.context("Non negative service specific error."), |_| Err(
+                BinderStatus::ok()
+            ))
+            .map_err(|s| s.service_specific_error())
+        );
+
+        // map_km_error creates a Error::Binder variant storing the given exception code.
+        let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
+        assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
+        // map_or_log_err then maps it on a service specific error of Rc::SystemError.
+        assert_eq!(
+            Result::<(), i32>::Err(Rc::SystemError),
+            map_or_log_err(binder_exception.context("Binder Exception."), |_| Err(
+                BinderStatus::ok()
+            ))
+            .map_err(|s| s.service_specific_error())
+        );
+
         // selinux::Error::Perm() needs to be mapped to Rc::PermissionDenied
         assert_eq!(
             Result::<(), i32>::Err(Rc::PermissionDenied),