summaryrefslogtreecommitdiff
path: root/keystore2/src/error.rs
blob: 3ca3942a558fef831f89659e790bf3f360453c98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
// Copyright 2020, 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.

//! Keystore error provides convenience methods and types for Keystore error handling.
//! Clients of Keystore expect one of two error codes, i.e., a Keystore ResponseCode as
//! defined by the Keystore AIDL interface, or a Keymint ErrorCode as defined by
//! the Keymint HAL specification.
//! This crate provides `Error` which can wrap both. It is to be used
//! internally by Keystore to diagnose error conditions that need to be reported to
//! the client. To report the error condition to the client the Keystore AIDL
//! interface defines a wire type `Result` which is distinctly different from Rust's
//! `enum Result<T,E>`.
//!
//! This crate provides the convenience method `map_or_log_err` to convert `anyhow::Error`
//! into this wire type. In addition to handling the conversion of `Error`
//! to the `Result` wire type it handles any other error by mapping it to
//! `ResponseCode::SYSTEM_ERROR` and logs any error condition.
//!
//! Keystore functions should use `anyhow::Result` to return error conditions, and
//! context should be added every time an error is forwarded.

pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint::ErrorCode::ErrorCode;
pub use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
use android_system_keystore2::binder::{
    ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
};
use keystore2_selinux as selinux;
use std::cmp::PartialEq;
use std::ffi::CString;

/// 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.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
    /// Wraps a Keystore `ResponseCode` as defined by the Keystore AIDL interface specification.
    #[error("Error::Rc({0:?})")]
    Rc(ResponseCode),
    /// 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),
    /// Wraps a Binder status code.
    #[error("Binder transaction error {0:?}")]
    BinderTransaction(StatusCode),
    /// Wraps a Remote Provisioning ErrorCode as defined by the IRemotelyProvisionedComponent
    /// AIDL interface spec.
    #[error("Error::Rp({0:?})")]
    Rp(ErrorCode),
}

impl Error {
    /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)`
    pub fn sys() -> Self {
        Error::Rc(ResponseCode::SYSTEM_ERROR)
    }

    /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED)`
    pub fn perm() -> Self {
        Error::Rc(ResponseCode::PERMISSION_DENIED)
    }
}

/// 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(ErrorCode(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),
        }
    })
}

/// Helper function to map the binder status we get from calls into a RemotelyProvisionedComponent
/// to a Keystore Error. We don't create an anyhow error here to make
/// it easier to evaluate service specific errors.
pub fn map_rem_prov_error<T>(r: BinderResult<T>) -> Result<T, Error> {
    r.map_err(|s| match s.exception_code() {
        ExceptionCode::SERVICE_SPECIFIC => Error::Rp(ErrorCode(s.service_specific_error())),
        e_code => Error::Binder(e_code, 0),
    })
}

/// This function is similar to map_km_error only that we don't expect
/// any KeyMint error codes, we simply preserve the exception code and optional
/// service specific exception.
pub fn map_binder_status<T>(r: BinderResult<T>) -> Result<T, Error> {
    r.map_err(|s| match s.exception_code() {
        ExceptionCode::SERVICE_SPECIFIC => {
            let se = s.service_specific_error();
            Error::Binder(ExceptionCode::SERVICE_SPECIFIC, se)
        }
        ExceptionCode::TRANSACTION_FAILED => {
            let e = s.transaction_error();
            Error::BinderTransaction(e)
        }
        e_code => Error::Binder(e_code, 0),
    })
}

/// This function maps a status code onto a Keystore Error.
pub fn map_binder_status_code<T>(r: Result<T, StatusCode>) -> Result<T, Error> {
    r.map_err(Error::BinderTransaction)
}

/// This function should be used by Keystore service calls to translate error conditions
/// into service specific exceptions.
///
/// All error conditions get logged by this function, except for KEY_NOT_FOUND error.
///
/// All `Error::Rc(x)` and `Error::Km(x)` variants get mapped onto a service specific error
/// code of x. This is possible because KeyMint `ErrorCode` errors are always negative and
/// `ResponseCode` codes are always positive.
/// `selinux::Error::PermissionDenied` is mapped on `ResponseCode::PERMISSION_DENIED`.
///
/// All non `Error` error conditions and the Error::Binder variant get mapped onto
/// ResponseCode::SYSTEM_ERROR`.
///
/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
/// typically returns Ok(value).
///
/// # Examples
///
/// ```
/// fn loadKey() -> anyhow::Result<Vec<u8>> {
///     if (good_but_auth_required) {
///         Ok(vec!['k', 'e', 'y'])
///     } else {
///         Err(anyhow!(Error::Rc(ResponseCode::KEY_NOT_FOUND)))
///     }
/// }
///
/// map_or_log_err(loadKey(), Ok)
/// ```
pub fn map_or_log_err<T, U, F>(result: anyhow::Result<U>, handle_ok: F) -> BinderResult<T>
where
    F: FnOnce(U) -> BinderResult<T>,
{
    map_err_with(
        result,
        |e| {
            // Make the key not found errors silent.
            if !matches!(
                e.root_cause().downcast_ref::<Error>(),
                Some(Error::Rc(ResponseCode::KEY_NOT_FOUND))
            ) {
                log::error!("{:?}", e);
            }
            e
        },
        handle_ok,
    )
}

/// This function turns an anyhow error into an optional CString.
/// This is especially useful to add a message string to a service specific error.
/// If the formatted string was not convertible because it contained a nul byte,
/// None is returned and a warning is logged.
pub fn anyhow_error_to_cstring(e: &anyhow::Error) -> Option<CString> {
    match CString::new(format!("{:?}", e)) {
        Ok(msg) => Some(msg),
        Err(_) => {
            log::warn!("Cannot convert error message to CStr. It contained a nul byte.");
            None
        }
    }
}

/// This function behaves similar to map_or_log_error, but it does not log the errors, instead
/// it calls map_err on the error before mapping it to a binder result allowing callers to
/// log or transform the error before mapping it.
pub fn map_err_with<T, U, F1, F2>(
    result: anyhow::Result<U>,
    map_err: F1,
    handle_ok: F2,
) -> BinderResult<T>
where
    F1: FnOnce(anyhow::Error) -> anyhow::Error,
    F2: FnOnce(U) -> BinderResult<T>,
{
    result.map_or_else(
        |e| {
            let e = map_err(e);
            let rc = get_error_code(&e);
            Err(BinderStatus::new_service_specific_error(
                rc,
                anyhow_error_to_cstring(&e).as_deref(),
            ))
        },
        handle_ok,
    )
}

/// Returns the error code given a reference to the error
pub fn get_error_code(e: &anyhow::Error) -> i32 {
    let root_cause = e.root_cause();
    match root_cause.downcast_ref::<Error>() {
        Some(Error::Rc(rcode)) => rcode.0,
        Some(Error::Km(ec)) => ec.0,
        Some(Error::Rp(_)) => ResponseCode::SYSTEM_ERROR.0,
        // 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(_, _)) | Some(Error::BinderTransaction(_)) => {
            ResponseCode::SYSTEM_ERROR.0
        }
        None => match root_cause.downcast_ref::<selinux::Error>() {
            Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
            _ => ResponseCode::SYSTEM_ERROR.0,
        },
    }
}

#[cfg(test)]
pub mod tests {

    use super::*;
    use android_system_keystore2::binder::{
        ExceptionCode, Result as BinderResult, Status as BinderStatus,
    };
    use anyhow::{anyhow, Context};

    fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
        Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
    }

    fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
        nested_nested_rc(rc).context("nested rc")
    }

    fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
        Err(anyhow!(Error::Km(ec))).context("nested nested ec")
    }

    fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
        nested_nested_ec(ec).context("nested ec")
    }

    fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
        Ok(rc)
    }

    fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
        nested_nested_ok(rc).context("nested ok")
    }

    fn nested_nested_selinux_perm() -> anyhow::Result<()> {
        Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
    }

    fn nested_selinux_perm() -> anyhow::Result<()> {
        nested_nested_selinux_perm().context("nested selinux permission denied")
    }

    #[derive(Debug, thiserror::Error)]
    enum TestError {
        #[error("TestError::Fail")]
        Fail = 0,
    }

    fn nested_nested_other_error() -> anyhow::Result<()> {
        Err(anyhow!(TestError::Fail)).context("nested nested other error")
    }

    fn nested_other_error() -> anyhow::Result<()> {
        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(
            android_logger::Config::default()
                .with_tag("keystore_error_tests")
                .with_min_level(log::Level::Debug),
        );
        // All Error::Rc(x) get mapped on a service specific error
        // code of x.
        for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
            assert_eq!(
                Result::<(), i32>::Err(rc),
                map_or_log_err(nested_rc(ResponseCode(rc)), |_| Err(BinderStatus::ok()))
                    .map_err(|s| s.service_specific_error())
            );
        }

        // All Keystore Error::Km(x) get mapped on a service
        // specific error of x.
        for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
            assert_eq!(
                Result::<(), i32>::Err(ec),
                map_or_log_err(nested_ec(ErrorCode(ec)), |_| Err(BinderStatus::ok()))
                    .map_err(|s| s.service_specific_error())
            );
        }

        // All Keymint errors x received through a Binder Result get mapped on
        // a service specific error of x.
        for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
            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 ResponseCode::SYSTEM_ERROR.
        assert_eq!(
            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
            map_or_log_err(sse.context("Non negative service specific error."), |_| Err(
                BinderStatus::ok()
            ))
            .map_err(|s| ResponseCode(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 ResponseCode::SYSTEM_ERROR.
        assert_eq!(
            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
            map_or_log_err(binder_exception.context("Binder Exception."), |_| Err(
                BinderStatus::ok()
            ))
            .map_err(|s| ResponseCode(s.service_specific_error()))
        );

        // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
        assert_eq!(
            Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
            map_or_log_err(nested_selinux_perm(), |_| Err(BinderStatus::ok()))
                .map_err(|s| ResponseCode(s.service_specific_error()))
        );

        // All other errors get mapped on System Error.
        assert_eq!(
            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
            map_or_log_err(nested_other_error(), |_| Err(BinderStatus::ok()))
                .map_err(|s| ResponseCode(s.service_specific_error()))
        );

        // Result::Ok variants get passed to the ok handler.
        assert_eq!(Ok(ResponseCode::LOCKED), map_or_log_err(nested_ok(ResponseCode::LOCKED), Ok));
        assert_eq!(
            Ok(ResponseCode::SYSTEM_ERROR),
            map_or_log_err(nested_ok(ResponseCode::SYSTEM_ERROR), Ok)
        );

        Ok(())
    }

    //Helper function to test whether error cases are handled as expected.
    pub fn check_result_contains_error_string<T>(
        result: anyhow::Result<T>,
        expected_error_string: &str,
    ) {
        let error_str = format!(
            "{:#?}",
            result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
        );
        assert!(
            error_str.contains(expected_error_string),
            "The string \"{}\" should contain \"{}\"",
            error_str,
            expected_error_string
        );
    }
} // mod tests