diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:23:40 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:23:40 +0000 |
commit | 9584fd12665c101a15e19b6634efa6e8626f1f8a (patch) | |
tree | 8b781c60507b3ee2fcb43fe3513b40f8e31cce15 | |
parent | 0ff060631ce48570c1552b88ab5d638a9a008704 (diff) | |
parent | 1c6058cc0da2c2f03d7114ec9e7417b30bc97811 (diff) | |
download | security-android14-mainline-uwb-release.tar.gz |
Snap for 10453563 from 1c6058cc0da2c2f03d7114ec9e7417b30bc97811 to mainline-uwb-releaseaml_uwb_341513070aml_uwb_341511050aml_uwb_341310300aml_uwb_341310030aml_uwb_341111010aml_uwb_341011000android14-mainline-uwb-release
Change-Id: I2fa710983f11b7ae8df32d273ae18fee77ab8260
183 files changed, 15721 insertions, 7588 deletions
@@ -1,8 +1,9 @@ alanstokes@google.com cbrubaker@google.com +drysdale@google.com +eranm@google.com hasinitg@google.com jbires@google.com -jdanis@google.com jeffv@google.com kroot@google.com sethmo@google.com diff --git a/diced/Android.bp b/diced/Android.bp deleted file mode 100644 index e13d863d..00000000 --- a/diced/Android.bp +++ /dev/null @@ -1,228 +0,0 @@ -// 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. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "system_security_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["system_security_license"], -} - -rust_library { - name: "libdiced_utils", - crate_name: "diced_utils", - srcs: ["src/utils.rs"], - vendor_available: true, - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libkeystore2_crypto_rust", - ], -} - -rust_test { - name: "diced_utils_test", - crate_name: "diced_utils_test", - srcs: ["src/utils.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libkeystore2_crypto_rust", - ], -} - -rust_library { - name: "libdiced_sample_inputs", - crate_name: "diced_sample_inputs", - srcs: ["src/sample_inputs.rs"], - vendor_available: true, - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - ], -} - -rust_test { - name: "diced_sample_inputs_test", - crate_name: "diced_sample_inputs_test", - srcs: ["src/sample_inputs.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - ], -} - -rust_library { - name: "libdiced", - crate_name: "diced", - srcs: ["src/lib.rs"], - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libdiced_open_dice_cbor", - "libanyhow", - "libbinder_rs", - "libdiced_utils", - "libkeystore2_crypto_rust", - "libkeystore2_selinux", - "liblibc", - "liblog_rust", - "libthiserror", - ], -} - -rust_library { - name: "libdiced_vendor", - crate_name: "diced", - srcs: ["src/lib_vendor.rs"], - - vendor_available: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libdiced_open_dice_cbor", - "libanyhow", - "libbinder_rs", - "libdiced_utils", - "libkeystore2_crypto_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_binary { - name: "diced", - srcs: ["src/diced_main.rs"], - prefer_rlib: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libandroid_logger", - "libbinder_rs", - "libdiced", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "liblog_rust", - ], - init_rc: ["diced.rc"], -} - -rust_binary { - name: "diced.microdroid", - srcs: ["src/diced_main.rs"], - prefer_rlib: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libandroid_logger", - "libbinder_rs", - "libdiced", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "liblog_rust", - ], - init_rc: ["diced.microdroid.rc"], - bootstrap: true, -} - -rust_test { - name: "diced_test", - crate_name: "diced_test", - srcs: ["src/lib.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libanyhow", - "libbinder_rs", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - "libkeystore2_selinux", - "libkeystore2_vintf_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_test { - name: "diced_vendor_test", - crate_name: "diced_vendor_test", - srcs: ["src/lib_vendor.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "libbinder_rs", - "libkeystore2_crypto_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_test { - name: "diced_client_test", - srcs: [ - "src/diced_client_test.rs", - ], - require_root: true, - auto_gen_config: true, - test_suites: [ - "general-tests", - ], - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libanyhow", - "libbinder_rs", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "libnix", - ], -} diff --git a/diced/OWNERS b/diced/OWNERS new file mode 100644 index 00000000..387cd934 --- /dev/null +++ b/diced/OWNERS @@ -0,0 +1,3 @@ +alanstokes@google.com +aliceywang@google.com +ascull@google.com diff --git a/diced/TEST_MAPPING b/diced/TEST_MAPPING new file mode 100644 index 00000000..caf847f4 --- /dev/null +++ b/diced/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "libdiced_open_dice.integration_test" + }, + { + "name": "libdiced_open_dice_nostd.integration_test" + }, + { + "name": "libopen_dice_cbor_bindgen_test" + }, + { + "name": "libopen_dice_bcc_bindgen_test" + }, + { + "name": "libdiced_sample_inputs.integration_test" + } + ] +} diff --git a/diced/aidl/android/security/dice/IDiceMaintenance.aidl b/diced/aidl/android/security/dice/IDiceMaintenance.aidl deleted file mode 100644 index c81fdea1..00000000 --- a/diced/aidl/android/security/dice/IDiceMaintenance.aidl +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.security.dice; - -import android.hardware.security.dice.InputValues; - -/** - * The maintenance allows callers to prompt the DICE node to demote itself. - * - * @hide - */ -@SensitiveData -interface IDiceMaintenance { - /** - * The implementation must demote itself by deriving new effective artifacts - * based on the list of input data passed to the function. - * As opposed to the IDiceNode::demote, this function effects all clients of - * the implementation. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the demote_self permission. - * May produce any ResponseCode if anything went wrong. - */ - void demoteSelf(in InputValues[] input_values); -} diff --git a/diced/aidl/android/security/dice/IDiceNode.aidl b/diced/aidl/android/security/dice/IDiceNode.aidl deleted file mode 100644 index 2b3ef764..00000000 --- a/diced/aidl/android/security/dice/IDiceNode.aidl +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.security.dice; - -import android.hardware.security.dice.Bcc; -import android.hardware.security.dice.BccHandover; -import android.hardware.security.dice.InputValues; -import android.hardware.security.dice.Signature; - -/** - * An implementation of IDiceNode provides access to DICE secrets to its clients. It - * uses binder's caller UID and security context to identify its callers and assures - * That clients can only access their specific DICE secrets. - * It may operate in two different modes, resident mode and proxy mode. - * - * ## Resident mode. - * In resident mode, the node is in possession of the secrets corresponding to its level in - * the dice tree. It can act as root of the sub tree that it serves. The secrets are memory - * resident in the node. It identifies its callers and prepends the caller's identity to the - * request's vector of input values. It then derives the required secrets by iterating through - * the request's vector of input values in ascending order. - * - * ## Proxy mode. - * In proxy mode, the node has a connection to a parent node. It serves its callers by verifying - * their identity, by prefixing the client's vector of input values with client's identity, and - * forwarding the request to the next level up. - * - * The modes are implementation details that are completely transparent to the clients. - * - * Privacy: Unprivileged apps may not use this service ever because it may provide access to a - * device specific id that is stable across reinstalls, reboots, and applications. - * - * @hide - */ -@SensitiveData -interface IDiceNode { - /** - * Uses the a key derived from the caller's attestation secret to sign the payload using - * RFC 8032 PureEd25519 and returns the signature. The payload is limited to 1024 bytes. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the use_sign permission. - */ - Signature sign(in InputValues[] id, in byte[] payload); - - /** - * Returns the attestation certificate chain of the caller if `inputValues` is empty or the - * chain to the given child of the caller identified by the `inputValues` vector. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the get_attestation_chain - * permission. - */ - Bcc getAttestationChain(in InputValues[] inputValues); - - /** - * This function allows a client to become a resident node. Called with empty InputValues - * vectors, an implementation returns the client's DICE secrets. If inputValues is - * not empty, the appropriate derivations are performed starting from the client's level. - * The function must never return secrets pertaining to the implementation or a parent - * thereof in the DICE hierarchy. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the implementation does not allow resident nodes - * at the client's level. - */ - BccHandover derive(in InputValues[] inputValues); - - /** - * The client demotes itself to the given identity. When serving the calling client, - * the implementation must append the given identities. Essentially, the client assumes - * the identity of one of its children. This operation is not reversible, i.e., there - * is no promotion. Further demotion is possible. - * - * If the operation fails for any reason. No further services must be provided. Ideally, - * a device shutdown/reboot is triggered. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the demote permission. - */ - void demote(in InputValues[] inputValues); -} diff --git a/diced/aidl/android/security/dice/ResponseCode.aidl b/diced/aidl/android/security/dice/ResponseCode.aidl deleted file mode 100644 index 7c660580..00000000 --- a/diced/aidl/android/security/dice/ResponseCode.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ - -package android.security.dice; - -@Backing(type="int") -/** - * Service specific error codes. - * @hide - */ -enum ResponseCode { - /** - * The caller has insufficient privilege to access the DICE API. - */ - PERMISSION_DENIED = 1, - /** - * An unexpected error occurred, likely with IO or IPC. - */ - SYSTEM_ERROR = 2, - /** - * Returned if the called function is not implemented. - */ - NOT_IMPLEMENTED = 3, -} diff --git a/diced/diced.microdroid.rc b/diced/diced.microdroid.rc deleted file mode 100644 index 2226f473..00000000 --- a/diced/diced.microdroid.rc +++ /dev/null @@ -1,13 +0,0 @@ -# Start the Diced service. -# -# See system/core/init/README.md for information on the init.rc language. - -service diced /system/bin/diced.microdroid - class main - user diced - group diced - # The diced service must not be allowed to restart. - # If it crashes for any reason security critical state is lost. - # The only remedy is to restart the device. - oneshot - writepid /dev/cpuset/foreground/tasks diff --git a/diced/diced.rc b/diced/diced.rc deleted file mode 100644 index 8c43fa5e..00000000 --- a/diced/diced.rc +++ /dev/null @@ -1,13 +0,0 @@ -# Start the Diced service. -# -# See system/core/init/README.md for information on the init.rc language. - -service diced /system/bin/diced - class main - user diced - group diced - # The diced service must not be allowed to restart. - # If it crashes for any reason security critical state is lost. - # The only remedy is to restart the device. - oneshot - writepid /dev/cpuset/foreground/tasks diff --git a/diced/open_dice/Android.bp b/diced/open_dice/Android.bp new file mode 100644 index 00000000..2505b426 --- /dev/null +++ b/diced/open_dice/Android.bp @@ -0,0 +1,256 @@ +package { + default_visibility: [":__subpackages__"], + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_defaults { + name: "libdiced_open_dice_defaults", + crate_name: "diced_open_dice", + srcs: ["src/lib.rs"], + static_libs: [ + "libopen_dice_cbor", + ], + vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], +} + +rust_library_rlib { + name: "libdiced_open_dice_nostd", + defaults: ["libdiced_open_dice_defaults"], + rustlibs: [ + "libopen_dice_bcc_bindgen_nostd", + "libopen_dice_cbor_bindgen_nostd", + "libzeroize_nostd", + ], + whole_static_libs: [ + "libopen_dice_cbor", + "libcrypto_baremetal", + ], + visibility: [ + "//packages/modules/Virtualization:__subpackages__", + ], +} + +rust_library { + name: "libdiced_open_dice", + defaults: ["libdiced_open_dice_defaults"], + rustlibs: [ + "libopen_dice_bcc_bindgen", + "libopen_dice_cbor_bindgen", + "libzeroize", + ], + features: [ + "std", + ], + shared_libs: [ + "libcrypto", + ], + whole_static_libs: [ + "libopen_dice_bcc", + ], + visibility: [ + "//system/security/diced:__subpackages__", + "//packages/modules/Virtualization:__subpackages__", + "//hardware/interfaces/security/dice/aidl:__subpackages__", + ], +} + +rust_defaults { + name: "libdiced_open_dice_test_defaults", + crate_name: "diced_open_dice_test", + srcs: ["tests/*.rs"], + test_suites: ["general-tests"], +} + +rust_test { + name: "libdiced_open_dice.integration_test", + defaults: ["libdiced_open_dice_test_defaults"], + rustlibs: [ + "libdiced_open_dice", + ], +} + +rust_test { + name: "libdiced_open_dice_nostd.integration_test", + defaults: ["libdiced_open_dice_test_defaults"], + rustlibs: [ + "libdiced_open_dice_nostd", + ], +} + +rust_defaults { + name: "libopen_dice_bindgen_nostd.rust_defaults", + bindgen_flags: [ + "--use-core", + "--ctypes-prefix=core::ffi", + "--raw-line=#![no_std]", + ], + no_stdlibs: true, + prefer_rlib: true, + stdlibs: [ + "libcore.rust_sysroot", + "libcompiler_builtins.rust_sysroot", + ], + target: { + musl: { + enabled: false, + }, + glibc: { + enabled: false, + }, + darwin: { + enabled: false, + }, + }, +} + +rust_defaults { + name: "libopen_dice.rust_defaults", + host_supported: true, + vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt", + ], +} + +rust_defaults { + name: "libopen_dice_cbor_bindgen.rust_defaults", + defaults: ["libopen_dice.rust_defaults"], + wrapper_src: "bindgen/dice.h", + crate_name: "open_dice_cbor_bindgen", + source_stem: "bindings", + bindgen_flags: [ + "--size_t-is-usize", + "--rustified-enum DiceConfigType", + "--rustified-enum DiceMode", + "--rustified-enum DiceResult", + + // By generating only essential functions, we can make bindings concise and + // optimize compilation time. + "--allowlist-function=DiceDeriveCdiPrivateKeySeed", + "--allowlist-function=DiceDeriveCdiCertificateId", + "--allowlist-function=DiceMainFlow", + "--allowlist-function=DiceHash", + "--allowlist-function=DiceKdf", + "--allowlist-function=DiceKeypairFromSeed", + "--allowlist-function=DiceSign", + "--allowlist-function=DiceVerify", + "--allowlist-function=DiceGenerateCertificate", + + // We also need some constants in addition to the functions. + "--allowlist-var=DICE_CDI_SIZE", + "--allowlist-var=DICE_HASH_SIZE", + "--allowlist-var=DICE_HIDDEN_SIZE", + "--allowlist-var=DICE_INLINE_CONFIG_SIZE", + "--allowlist-var=DICE_PRIVATE_KEY_SEED_SIZE", + "--allowlist-var=DICE_ID_SIZE", + "--allowlist-var=DICE_PUBLIC_KEY_SIZE", + "--allowlist-var=DICE_PRIVATE_KEY_SIZE", + "--allowlist-var=DICE_SIGNATURE_SIZE", + ], +} + +rust_bindgen { + name: "libopen_dice_cbor_bindgen", + defaults: ["libopen_dice_cbor_bindgen.rust_defaults"], + whole_static_libs: ["libopen_dice_cbor"], +} + +rust_bindgen { + name: "libopen_dice_cbor_bindgen_nostd", + defaults: [ + "libopen_dice_cbor_bindgen.rust_defaults", + "libopen_dice_bindgen_nostd.rust_defaults", + ], + whole_static_libs: ["libopen_dice_cbor_baremetal"], +} + +rust_defaults { + name: "libopen_dice_bcc_bindgen.rust_defaults", + defaults: ["libopen_dice.rust_defaults"], + wrapper_src: "bindgen/android/bcc.h", + crate_name: "open_dice_bcc_bindgen", + source_stem: "bindings", + bindgen_flags: [ + "--size_t-is-usize", + + // By generating only essential functions, we can make bindings concise and + // optimize compilation time. + "--allowlist-function=BccFormatConfigDescriptor", + "--allowlist-function=BccMainFlow", + "--allowlist-function=BccHandoverMainFlow", + "--allowlist-function=BccHandoverParse", + + // We also need some constants in addition to the functions. + "--allowlist-var=BCC_INPUT_COMPONENT_NAME", + "--allowlist-var=BCC_INPUT_COMPONENT_VERSION", + "--allowlist-var=BCC_INPUT_RESETTABLE", + + // Prevent DiceInputValues from being generated a second time and + // import it instead from open_dice_cbor_bindgen. + "--blocklist-type=DiceInputValues_", + "--blocklist-type=DiceInputValues", + "--raw-line", + "pub use open_dice_cbor_bindgen::DiceInputValues;", + + // Prevent DiceResult from being generated a second time and + // import it instead from open_dice_cbor_bindgen. + "--blocklist-type=DiceResult", + "--raw-line", + "pub use open_dice_cbor_bindgen::DiceResult;", + ], + +} + +rust_bindgen { + name: "libopen_dice_bcc_bindgen", + defaults: ["libopen_dice_bcc_bindgen.rust_defaults"], + rustlibs: [ + "libopen_dice_cbor_bindgen", + ], + whole_static_libs: ["libopen_dice_bcc"], +} + +rust_bindgen { + name: "libopen_dice_bcc_bindgen_nostd", + defaults: [ + "libopen_dice_bcc_bindgen.rust_defaults", + "libopen_dice_bindgen_nostd.rust_defaults", + ], + rustlibs: [ + "libopen_dice_cbor_bindgen_nostd", + ], + whole_static_libs: ["libopen_dice_bcc_baremetal"], +} + +rust_test { + name: "libopen_dice_cbor_bindgen_test", + srcs: [ + ":libopen_dice_cbor_bindgen", + ], + crate_name: "open_dice_cbor_bindgen_test", + test_suites: ["general-tests"], + auto_gen_config: true, + clippy_lints: "none", + lints: "none", +} + +rust_test { + name: "libopen_dice_bcc_bindgen_test", + srcs: [ + ":libopen_dice_bcc_bindgen", + ], + crate_name: "open_dice_bcc_bindgen_test", + rustlibs: [ + "libopen_dice_cbor_bindgen", + ], + test_suites: ["general-tests"], + auto_gen_config: true, + clippy_lints: "none", + lints: "none", +} diff --git a/diced/open_dice/bindgen/android/bcc.h b/diced/open_dice/bindgen/android/bcc.h new file mode 100644 index 00000000..4dfc8626 --- /dev/null +++ b/diced/open_dice/bindgen/android/bcc.h @@ -0,0 +1,17 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. + +#pragma once + +#include <dice/android/bcc.h> diff --git a/diced/open_dice/bindgen/dice.h b/diced/open_dice/bindgen/dice.h new file mode 100644 index 00000000..47fe9119 --- /dev/null +++ b/diced/open_dice/bindgen/dice.h @@ -0,0 +1,18 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. + +#pragma once + +#include <dice/dice.h> +#include <dice/ops.h> diff --git a/diced/open_dice/src/bcc.rs b/diced/open_dice/src/bcc.rs new file mode 100644 index 00000000..1575113e --- /dev/null +++ b/diced/open_dice/src/bcc.rs @@ -0,0 +1,192 @@ +// Copyright 2023, 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 mirrors the content in open-dice/include/dice/android/bcc.h + +use crate::dice::{Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE}; +use crate::error::{check_result, DiceError, Result}; +use open_dice_bcc_bindgen::{ + BccConfigValues, BccFormatConfigDescriptor, BccHandoverMainFlow, BccHandoverParse, BccMainFlow, + BCC_INPUT_COMPONENT_NAME, BCC_INPUT_COMPONENT_VERSION, BCC_INPUT_RESETTABLE, +}; +use std::{ffi::CStr, ptr}; + +/// Formats a configuration descriptor following the BCC's specification. +/// See https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl +pub fn bcc_format_config_descriptor( + name: Option<&CStr>, + version: Option<u64>, + resettable: bool, + buffer: &mut [u8], +) -> Result<usize> { + let mut inputs = 0; + if name.is_some() { + inputs |= BCC_INPUT_COMPONENT_NAME; + } + if version.is_some() { + inputs |= BCC_INPUT_COMPONENT_VERSION; + } + if resettable { + inputs |= BCC_INPUT_RESETTABLE; + } + + let values = BccConfigValues { + inputs, + component_name: name.map_or(ptr::null(), |p| p.as_ptr()), + component_version: version.unwrap_or(0), + }; + + let mut buffer_size = 0; + // SAFETY: The function writes to the buffer, within the given bounds, and only reads the + // input values. It writes its result to buffer_size. + check_result(unsafe { + BccFormatConfigDescriptor(&values, buffer.len(), buffer.as_mut_ptr(), &mut buffer_size) + })?; + Ok(buffer_size) +} + +/// Executes the main BCC flow. +/// +/// Given a full set of input values along with the current BCC and CDI values, +/// computes the next CDI values and matching updated BCC. +pub fn bcc_main_flow( + current_cdi_attest: &Cdi, + current_cdi_seal: &Cdi, + current_bcc: &[u8], + input_values: &InputValues, + next_cdi_values: &mut CdiValues, + next_bcc: &mut [u8], +) -> Result<usize> { + let mut next_bcc_size = 0; + // SAFETY: `BccMainFlow` only reads the current `bcc` and CDI values and writes + // to `next_bcc` and next CDI values within its bounds. It also reads + // `input_values` as a constant input and doesn't store any pointer. + // The first argument can be null and is not used in the current implementation. + check_result(unsafe { + BccMainFlow( + ptr::null_mut(), // context + current_cdi_attest.as_ptr(), + current_cdi_seal.as_ptr(), + current_bcc.as_ptr(), + current_bcc.len(), + input_values.as_ptr(), + next_bcc.len(), + next_bcc.as_mut_ptr(), + &mut next_bcc_size, + next_cdi_values.cdi_attest.as_mut_ptr(), + next_cdi_values.cdi_seal.as_mut_ptr(), + ) + })?; + Ok(next_bcc_size) +} + +/// Executes the main BCC handover flow. +/// +/// A BCC handover combines the BCC and CDIs in a single CBOR object. +/// This function takes the current boot stage's BCC handover bundle and produces a +/// bundle for the next stage. +pub fn bcc_handover_main_flow( + current_bcc_handover: &[u8], + input_values: &InputValues, + next_bcc_handover: &mut [u8], +) -> Result<usize> { + let mut next_bcc_handover_size = 0; + // SAFETY - The function only reads `current_bcc_handover` and writes to `next_bcc_handover` + // within its bounds, + // It also reads `input_values` as a constant input and doesn't store any pointer. + // The first argument can be null and is not used in the current implementation. + check_result(unsafe { + BccHandoverMainFlow( + ptr::null_mut(), // context + current_bcc_handover.as_ptr(), + current_bcc_handover.len(), + input_values.as_ptr(), + next_bcc_handover.len(), + next_bcc_handover.as_mut_ptr(), + &mut next_bcc_handover_size, + ) + })?; + + Ok(next_bcc_handover_size) +} + +/// A BCC handover combines the BCC and CDIs in a single CBOR object. +/// This struct is used as return of the function `bcc_handover_parse`, its lifetime is tied +/// to the lifetime of the raw BCC handover slice. +#[derive(Debug)] +pub struct BccHandover<'a> { + /// Attestation CDI. + cdi_attest: &'a [u8; CDI_SIZE], + /// Sealing CDI. + cdi_seal: &'a [u8; CDI_SIZE], + /// Boot Certificate Chain. + bcc: Option<&'a [u8]>, +} + +impl<'a> DiceArtifacts for BccHandover<'a> { + fn cdi_attest(&self) -> &[u8; CDI_SIZE] { + self.cdi_attest + } + + fn cdi_seal(&self) -> &[u8; CDI_SIZE] { + self.cdi_seal + } + + fn bcc(&self) -> Option<&[u8]> { + self.bcc + } +} + +/// A BCC handover combines the BCC and CDIs in a single CBOR object. +/// This function parses the `bcc_handover` to extracts the BCC and CDIs. +/// The lifetime of the returned `BccHandover` is tied to the given `bcc_handover` slice. +pub fn bcc_handover_parse(bcc_handover: &[u8]) -> Result<BccHandover> { + let mut cdi_attest: *const u8 = ptr::null(); + let mut cdi_seal: *const u8 = ptr::null(); + let mut bcc: *const u8 = ptr::null(); + let mut bcc_size = 0; + // SAFETY: The `bcc_handover` is only read and never stored and the returned pointers should all + // point within the address range of the `bcc_handover` or be NULL. + check_result(unsafe { + BccHandoverParse( + bcc_handover.as_ptr(), + bcc_handover.len(), + &mut cdi_attest, + &mut cdi_seal, + &mut bcc, + &mut bcc_size, + ) + })?; + let cdi_attest = sub_slice(bcc_handover, cdi_attest, CDI_SIZE)?; + let cdi_seal = sub_slice(bcc_handover, cdi_seal, CDI_SIZE)?; + let bcc = sub_slice(bcc_handover, bcc, bcc_size).ok(); + Ok(BccHandover { + cdi_attest: cdi_attest.try_into().map_err(|_| DiceError::PlatformError)?, + cdi_seal: cdi_seal.try_into().map_err(|_| DiceError::PlatformError)?, + bcc, + }) +} + +/// Gets a slice the `addr` points to and of length `len`. +/// The slice should be contained in the buffer. +fn sub_slice(buffer: &[u8], addr: *const u8, len: usize) -> Result<&[u8]> { + if addr.is_null() || !buffer.as_ptr_range().contains(&addr) { + return Err(DiceError::PlatformError); + } + // SAFETY: This is safe because addr is not null and is within the range of the buffer. + let start: usize = unsafe { + addr.offset_from(buffer.as_ptr()).try_into().map_err(|_| DiceError::PlatformError)? + }; + start.checked_add(len).and_then(|end| buffer.get(start..end)).ok_or(DiceError::PlatformError) +} diff --git a/diced/open_dice/src/dice.rs b/diced/open_dice/src/dice.rs new file mode 100644 index 00000000..9266b6fc --- /dev/null +++ b/diced/open_dice/src/dice.rs @@ -0,0 +1,270 @@ +// Copyright 2023, 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. + +//! Structs and functions about the types used in DICE. +//! This module mirrors the content in open-dice/include/dice/dice.h + +use crate::error::{check_result, Result}; +pub use open_dice_cbor_bindgen::DiceMode; +use open_dice_cbor_bindgen::{ + DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, DiceInputValues, + DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE, + DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE, + DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE, +}; +use std::ptr; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// The size of a DICE hash. +pub const HASH_SIZE: usize = DICE_HASH_SIZE as usize; +/// The size of the DICE hidden value. +pub const HIDDEN_SIZE: usize = DICE_HIDDEN_SIZE as usize; +/// The size of a DICE inline config. +const INLINE_CONFIG_SIZE: usize = DICE_INLINE_CONFIG_SIZE as usize; +/// The size of a CDI. +pub const CDI_SIZE: usize = DICE_CDI_SIZE as usize; +/// The size of a private key seed. +pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize; +/// The size of a private key. +pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize; +/// The size of a public key. +pub const PUBLIC_KEY_SIZE: usize = DICE_PUBLIC_KEY_SIZE as usize; +/// The size of a signature. +pub const SIGNATURE_SIZE: usize = DICE_SIGNATURE_SIZE as usize; +/// The size of an ID. +pub const ID_SIZE: usize = DICE_ID_SIZE as usize; + +/// Array type of hashes used by DICE. +pub type Hash = [u8; HASH_SIZE]; +/// Array type of additional input. +pub type Hidden = [u8; HIDDEN_SIZE]; +/// Array type of inline configuration values. +pub type InlineConfig = [u8; INLINE_CONFIG_SIZE]; +/// Array type of CDIs. +pub type Cdi = [u8; CDI_SIZE]; +/// Array type of the public key. +pub type PublicKey = [u8; PUBLIC_KEY_SIZE]; +/// Array type of the signature. +pub type Signature = [u8; SIGNATURE_SIZE]; +/// Array type of DICE ID. +pub type DiceId = [u8; ID_SIZE]; + +/// A trait for types that represent Dice artifacts, which include: +/// +/// - Attestation CDI +/// - Sealing CDI +/// - Boot Certificate Chain +/// +/// Types that implement this trait provide an access these artifacts. +pub trait DiceArtifacts { + /// Returns a reference to the attestation CDI. + fn cdi_attest(&self) -> &[u8; CDI_SIZE]; + + /// Returns a reference to the sealing CDI. + fn cdi_seal(&self) -> &[u8; CDI_SIZE]; + + /// Returns a reference to the Boot Certificate Chain, if present. + fn bcc(&self) -> Option<&[u8]>; +} + +/// TODO(b/268587826): Clean up the memory cache after zeroing out the memory +/// for sensitive data like CDI values and private key. +/// CDI Values. +#[derive(Debug, Zeroize, ZeroizeOnDrop, Default)] +pub struct CdiValues { + /// Attestation CDI. + pub cdi_attest: [u8; CDI_SIZE], + /// Sealing CDI. + pub cdi_seal: [u8; CDI_SIZE], +} + +/// Private key seed. The data is zeroed out when the struct is dropped. +#[derive(Zeroize, ZeroizeOnDrop, Default)] +pub struct PrivateKeySeed([u8; PRIVATE_KEY_SEED_SIZE]); + +impl PrivateKeySeed { + /// Returns an array reference of the private key seed. + pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SEED_SIZE] { + &self.0 + } + + /// Returns a mutable pointer to the slice buffer of the private key seed. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.0.as_mut_ptr() + } +} + +/// Private key. The data is zeroed out when the struct is dropped. +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct PrivateKey([u8; PRIVATE_KEY_SIZE]); + +impl Default for PrivateKey { + /// Creates a new `PrivateKey` instance with all bytes set to 0. + /// + /// Since the size of the private key array is too large to be initialized + /// with a default value, this implementation sets all the bytes in the array + /// to 0 using the `[0u8; PRIVATE_KEY_SIZE]` syntax. + fn default() -> Self { + Self([0u8; PRIVATE_KEY_SIZE]) + } +} + +impl PrivateKey { + /// Returns an array reference of the private key. + pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SIZE] { + &self.0 + } + + /// Returns a mutable pointer to the slice buffer of the private key. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.0.as_mut_ptr() + } +} + +/// Configuration descriptor for DICE input values. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Config<'a> { + /// Reference to an inline descriptor. + Inline(&'a InlineConfig), + /// Reference to a free form descriptor that will be hashed by the implementation. + Descriptor(&'a [u8]), +} + +impl Config<'_> { + fn dice_config_type(&self) -> DiceConfigType { + match self { + Self::Inline(_) => DiceConfigType::kDiceConfigTypeInline, + Self::Descriptor(_) => DiceConfigType::kDiceConfigTypeDescriptor, + } + } + + fn inline_config(&self) -> InlineConfig { + match self { + Self::Inline(inline) => **inline, + Self::Descriptor(_) => [0u8; INLINE_CONFIG_SIZE], + } + } + + fn descriptor_ptr(&self) -> *const u8 { + match self { + Self::Descriptor(descriptor) => descriptor.as_ptr(), + _ => ptr::null(), + } + } + + fn descriptor_size(&self) -> usize { + match self { + Self::Descriptor(descriptor) => descriptor.len(), + _ => 0, + } + } +} + +/// Wrap of `DiceInputValues`. +#[derive(Clone, Debug)] +pub struct InputValues(DiceInputValues); + +impl InputValues { + /// Creates a new `InputValues`. + pub fn new( + code_hash: Hash, + config: Config, + authority_hash: Hash, + mode: DiceMode, + hidden: Hidden, + ) -> Self { + Self(DiceInputValues { + code_hash, + code_descriptor: ptr::null(), + code_descriptor_size: 0, + config_type: config.dice_config_type(), + config_value: config.inline_config(), + config_descriptor: config.descriptor_ptr(), + config_descriptor_size: config.descriptor_size(), + authority_hash, + authority_descriptor: ptr::null(), + authority_descriptor_size: 0, + mode, + hidden, + }) + } + + /// Returns a raw pointer to the wrapped `DiceInputValues`. + pub fn as_ptr(&self) -> *const DiceInputValues { + &self.0 as *const DiceInputValues + } +} + +/// Derives a CDI private key seed from a `cdi_attest` value. +pub fn derive_cdi_private_key_seed(cdi_attest: &Cdi) -> Result<PrivateKeySeed> { + let mut seed = PrivateKeySeed::default(); + // SAFETY: The function writes to the buffer within the given bounds, and only reads the + // input values. The first argument context is not used in this function. + check_result(unsafe { + DiceDeriveCdiPrivateKeySeed( + ptr::null_mut(), // context + cdi_attest.as_ptr(), + seed.as_mut_ptr(), + ) + })?; + Ok(seed) +} + +/// Derives an ID from the given `cdi_public_key` value. +pub fn derive_cdi_certificate_id(cdi_public_key: &[u8]) -> Result<DiceId> { + let mut id = [0u8; ID_SIZE]; + // SAFETY: The function writes to the buffer within the given bounds, and only reads the + // input values. The first argument context is not used in this function. + check_result(unsafe { + DiceDeriveCdiCertificateId( + ptr::null_mut(), // context + cdi_public_key.as_ptr(), + cdi_public_key.len(), + id.as_mut_ptr(), + ) + })?; + Ok(id) +} + +/// Executes the main DICE flow. +/// +/// Given a full set of input values and the current CDI values, computes the +/// next CDI values and a matching certificate. +/// Returns the actual size of the next CDI certificate. +pub fn dice_main_flow( + current_cdi_attest: &Cdi, + current_cdi_seal: &Cdi, + input_values: &InputValues, + next_cdi_certificate: &mut [u8], + next_cdi_values: &mut CdiValues, +) -> Result<usize> { + let mut next_cdi_certificate_actual_size = 0; + // SAFETY: The function only reads the current CDI values and inputs and writes + // to `next_cdi_certificate` and next CDI values within its bounds. + // The first argument can be null and is not used in the current implementation. + check_result(unsafe { + DiceMainFlow( + ptr::null_mut(), // context + current_cdi_attest.as_ptr(), + current_cdi_seal.as_ptr(), + input_values.as_ptr(), + next_cdi_certificate.len(), + next_cdi_certificate.as_mut_ptr(), + &mut next_cdi_certificate_actual_size, + next_cdi_values.cdi_attest.as_mut_ptr(), + next_cdi_values.cdi_seal.as_mut_ptr(), + ) + })?; + Ok(next_cdi_certificate_actual_size) +} diff --git a/diced/open_dice/src/error.rs b/diced/open_dice/src/error.rs new file mode 100644 index 00000000..4c673354 --- /dev/null +++ b/diced/open_dice/src/error.rs @@ -0,0 +1,59 @@ +// Copyright 2023, 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. + +//! Errors and relating functions thrown in this library. + +use open_dice_cbor_bindgen::DiceResult; +use std::{fmt, result}; + +#[cfg(feature = "std")] +use std::error::Error; + +/// Error type used by DICE. +#[derive(Debug)] +pub enum DiceError { + /// Provided input was invalid. + InvalidInput, + /// Provided buffer was too small. + BufferTooSmall, + /// Platform error. + PlatformError, +} + +/// This makes `DiceError` accepted by anyhow. +#[cfg(feature = "std")] +impl Error for DiceError {} + +impl fmt::Display for DiceError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::InvalidInput => write!(f, "invalid input"), + Self::BufferTooSmall => write!(f, "buffer too small"), + Self::PlatformError => write!(f, "platform error"), + } + } +} + +/// DICE result type. +pub type Result<T> = result::Result<T, DiceError>; + +/// Checks the given `DiceResult`. Returns an error if it's not OK. +pub fn check_result(result: DiceResult) -> Result<()> { + match result { + DiceResult::kDiceResultOk => Ok(()), + DiceResult::kDiceResultInvalidInput => Err(DiceError::InvalidInput), + DiceResult::kDiceResultBufferTooSmall => Err(DiceError::BufferTooSmall), + DiceResult::kDiceResultPlatformError => Err(DiceError::PlatformError), + } +} diff --git a/diced/open_dice/src/lib.rs b/diced/open_dice/src/lib.rs new file mode 100644 index 00000000..e7ec56be --- /dev/null +++ b/diced/open_dice/src/lib.rs @@ -0,0 +1,45 @@ +// Copyright 2023, 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. + +//! Implements safe wrappers around the public API of libopen-dice for +//! both std and nostd usages. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate core as std; + +mod bcc; +mod dice; +mod error; +mod ops; +#[cfg(feature = "std")] +mod retry; + +pub use bcc::{ + bcc_format_config_descriptor, bcc_handover_main_flow, bcc_handover_parse, bcc_main_flow, + BccHandover, +}; +pub use dice::{ + derive_cdi_certificate_id, derive_cdi_private_key_seed, dice_main_flow, Cdi, CdiValues, Config, + DiceArtifacts, DiceMode, Hash, Hidden, InlineConfig, InputValues, PrivateKey, PrivateKeySeed, + PublicKey, Signature, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE, +}; +pub use error::{check_result, DiceError, Result}; +pub use ops::{generate_certificate, hash, kdf, keypair_from_seed, sign, verify}; +#[cfg(feature = "std")] +pub use retry::{ + retry_bcc_format_config_descriptor, retry_bcc_main_flow, retry_dice_main_flow, + retry_generate_certificate, OwnedDiceArtifacts, +}; diff --git a/diced/open_dice/src/ops.rs b/diced/open_dice/src/ops.rs new file mode 100644 index 00000000..8222b266 --- /dev/null +++ b/diced/open_dice/src/ops.rs @@ -0,0 +1,142 @@ +// Copyright 2023, 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 mirrors the content in open-dice/include/dice/ops.h +//! It contains the set of functions that implement various operations that the +//! main DICE functions depend on. + +use crate::dice::{ + Hash, InputValues, PrivateKey, PublicKey, Signature, HASH_SIZE, PRIVATE_KEY_SEED_SIZE, + PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE, SIGNATURE_SIZE, +}; +use crate::error::{check_result, Result}; +use open_dice_cbor_bindgen::{ + DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed, DiceSign, DiceVerify, +}; +use std::ptr; + +/// Hashes the provided input using DICE's hash function `DiceHash`. +pub fn hash(input: &[u8]) -> Result<Hash> { + let mut output: Hash = [0; HASH_SIZE]; + // SAFETY: DiceHash takes a sized input buffer and writes to a constant-sized output buffer. + // The first argument context is not used in this function. + check_result(unsafe { + DiceHash( + ptr::null_mut(), // context + input.as_ptr(), + input.len(), + output.as_mut_ptr(), + ) + })?; + Ok(output) +} + +/// An implementation of HKDF-SHA512. Derives a key of `derived_key.len()` bytes from `ikm`, `salt`, +/// and `info`. The derived key is written to the `derived_key`. +pub fn kdf(ikm: &[u8], salt: &[u8], info: &[u8], derived_key: &mut [u8]) -> Result<()> { + // SAFETY: The function writes to the `derived_key`, within the given bounds, and only reads the + // input values. The first argument context is not used in this function. + check_result(unsafe { + DiceKdf( + ptr::null_mut(), // context + derived_key.len(), + ikm.as_ptr(), + ikm.len(), + salt.as_ptr(), + salt.len(), + info.as_ptr(), + info.len(), + derived_key.as_mut_ptr(), + ) + }) +} + +/// Deterministically generates a public and private key pair from `seed`. +/// Since this is deterministic, `seed` is as sensitive as a private key and can +/// be used directly as the private key. +pub fn keypair_from_seed(seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(PublicKey, PrivateKey)> { + let mut public_key = [0u8; PUBLIC_KEY_SIZE]; + let mut private_key = PrivateKey::default(); + // SAFETY: The function writes to the `public_key` and `private_key` within the given bounds, + // and only reads the `seed`. The first argument context is not used in this function. + check_result(unsafe { + DiceKeypairFromSeed( + ptr::null_mut(), // context + seed.as_ptr(), + public_key.as_mut_ptr(), + private_key.as_mut_ptr(), + ) + })?; + Ok((public_key, private_key)) +} + +/// Signs the `message` with the give `private_key` using `DiceSign`. +pub fn sign(message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Signature> { + let mut signature = [0u8; SIGNATURE_SIZE]; + // SAFETY: The function writes to the `signature` within the given bounds, and only reads the + // message and the private key. The first argument context is not used in this function. + check_result(unsafe { + DiceSign( + ptr::null_mut(), // context + message.as_ptr(), + message.len(), + private_key.as_ptr(), + signature.as_mut_ptr(), + ) + })?; + Ok(signature) +} + +/// Verifies the `signature` of the `message` with the given `public_key` using `DiceVerify`. +pub fn verify(message: &[u8], signature: &Signature, public_key: &PublicKey) -> Result<()> { + // SAFETY: only reads the messages, signature and public key as constant values. + // The first argument context is not used in this function. + check_result(unsafe { + DiceVerify( + ptr::null_mut(), // context + message.as_ptr(), + message.len(), + signature.as_ptr(), + public_key.as_ptr(), + ) + }) +} + +/// Generates an X.509 certificate from the given `subject_private_key_seed` and +/// `input_values`, and signed by `authority_private_key_seed`. +/// The subject private key seed is supplied here so the implementation can choose +/// between asymmetric mechanisms, for example ECDSA vs Ed25519. +/// Returns the actual size of the generated certificate. +pub fn generate_certificate( + subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], + authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], + input_values: &InputValues, + certificate: &mut [u8], +) -> Result<usize> { + let mut certificate_actual_size = 0; + // SAFETY: The function writes to the `certificate` within the given bounds, and only reads the + // input values and the key seeds. The first argument context is not used in this function. + check_result(unsafe { + DiceGenerateCertificate( + ptr::null_mut(), // context + subject_private_key_seed.as_ptr(), + authority_private_key_seed.as_ptr(), + input_values.as_ptr(), + certificate.len(), + certificate.as_mut_ptr(), + &mut certificate_actual_size, + ) + })?; + Ok(certificate_actual_size) +} diff --git a/diced/open_dice/src/retry.rs b/diced/open_dice/src/retry.rs new file mode 100644 index 00000000..76a214c9 --- /dev/null +++ b/diced/open_dice/src/retry.rs @@ -0,0 +1,160 @@ +// Copyright 2023, 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 a retry version for multiple DICE functions that +//! require preallocated output buffer. As the retry functions require +//! memory allocation on heap, currently we only expose these functions in +//! std environment. + +use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow}; +use crate::dice::{ + dice_main_flow, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE, PRIVATE_KEY_SEED_SIZE, +}; +use crate::error::{DiceError, Result}; +use crate::ops::generate_certificate; +use std::ffi::CStr; + +/// Artifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL, +/// and the BCC formatted attestation certificate chain. +/// As we align with the DICE standards today, this is the certificate chain +/// is also called DICE certificate chain. +#[derive(Debug)] +pub struct OwnedDiceArtifacts { + /// CDI Values. + cdi_values: CdiValues, + /// Boot Certificate Chain. + bcc: Vec<u8>, +} + +impl DiceArtifacts for OwnedDiceArtifacts { + fn cdi_attest(&self) -> &[u8; CDI_SIZE] { + &self.cdi_values.cdi_attest + } + + fn cdi_seal(&self) -> &[u8; CDI_SIZE] { + &self.cdi_values.cdi_seal + } + + fn bcc(&self) -> Option<&[u8]> { + Some(&self.bcc) + } +} + +/// Retries the given function with bigger output buffer size. +fn retry_with_bigger_buffer<F>(mut f: F) -> Result<Vec<u8>> +where + F: FnMut(&mut Vec<u8>) -> Result<usize>, +{ + const INITIAL_BUFFER_SIZE: usize = 256; + const MAX_BUFFER_SIZE: usize = 64 * 1024 * 1024; + + let mut buffer = vec![0u8; INITIAL_BUFFER_SIZE]; + while buffer.len() <= MAX_BUFFER_SIZE { + match f(&mut buffer) { + Err(DiceError::BufferTooSmall) => { + let new_size = buffer.len() * 2; + buffer.resize(new_size, 0); + } + Err(e) => return Err(e), + Ok(actual_size) => { + if actual_size > buffer.len() { + panic!( + "actual_size larger than buffer size: open-dice function + may have written past the end of the buffer." + ); + } + buffer.truncate(actual_size); + return Ok(buffer); + } + } + } + Err(DiceError::PlatformError) +} + +/// Formats a configuration descriptor following the BCC's specification. +pub fn retry_bcc_format_config_descriptor( + name: Option<&CStr>, + version: Option<u64>, + resettable: bool, +) -> Result<Vec<u8>> { + retry_with_bigger_buffer(|buffer| { + bcc_format_config_descriptor(name, version, resettable, buffer) + }) +} + +/// Executes the main BCC flow. +/// +/// Given a full set of input values along with the current BCC and CDI values, +/// computes the next CDI values and matching updated BCC. +pub fn retry_bcc_main_flow( + current_cdi_attest: &Cdi, + current_cdi_seal: &Cdi, + bcc: &[u8], + input_values: &InputValues, +) -> Result<OwnedDiceArtifacts> { + let mut next_cdi_values = CdiValues::default(); + let next_bcc = retry_with_bigger_buffer(|next_bcc| { + bcc_main_flow( + current_cdi_attest, + current_cdi_seal, + bcc, + input_values, + &mut next_cdi_values, + next_bcc, + ) + })?; + Ok(OwnedDiceArtifacts { cdi_values: next_cdi_values, bcc: next_bcc }) +} + +/// Executes the main DICE flow. +/// +/// Given a full set of input values and the current CDI values, computes the +/// next CDI values and a matching certificate. +pub fn retry_dice_main_flow( + current_cdi_attest: &Cdi, + current_cdi_seal: &Cdi, + input_values: &InputValues, +) -> Result<(CdiValues, Vec<u8>)> { + let mut next_cdi_values = CdiValues::default(); + let next_cdi_certificate = retry_with_bigger_buffer(|next_cdi_certificate| { + dice_main_flow( + current_cdi_attest, + current_cdi_seal, + input_values, + next_cdi_certificate, + &mut next_cdi_values, + ) + })?; + Ok((next_cdi_values, next_cdi_certificate)) +} + +/// Generates an X.509 certificate from the given `subject_private_key_seed` and +/// `input_values`, and signed by `authority_private_key_seed`. +/// The subject private key seed is supplied here so the implementation can choose +/// between asymmetric mechanisms, for example ECDSA vs Ed25519. +/// Returns the generated certificate. +pub fn retry_generate_certificate( + subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], + authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], + input_values: &InputValues, +) -> Result<Vec<u8>> { + retry_with_bigger_buffer(|certificate| { + generate_certificate( + subject_private_key_seed, + authority_private_key_seed, + input_values, + certificate, + ) + }) +} diff --git a/diced/open_dice/tests/api_test.rs b/diced/open_dice/tests/api_test.rs new file mode 100644 index 00000000..a47265b3 --- /dev/null +++ b/diced/open_dice/tests/api_test.rs @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 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. + */ + +use diced_open_dice::{ + derive_cdi_certificate_id, derive_cdi_private_key_seed, hash, kdf, keypair_from_seed, sign, + verify, CDI_SIZE, HASH_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE, +}; + +#[test] +fn hash_succeeds() { + const EXPECTED_HASH: [u8; HASH_SIZE] = [ + 0x30, 0x9e, 0xcc, 0x48, 0x9c, 0x12, 0xd6, 0xeb, 0x4c, 0xc4, 0x0f, 0x50, 0xc9, 0x02, 0xf2, + 0xb4, 0xd0, 0xed, 0x77, 0xee, 0x51, 0x1a, 0x7c, 0x7a, 0x9b, 0xcd, 0x3c, 0xa8, 0x6d, 0x4c, + 0xd8, 0x6f, 0x98, 0x9d, 0xd3, 0x5b, 0xc5, 0xff, 0x49, 0x96, 0x70, 0xda, 0x34, 0x25, 0x5b, + 0x45, 0xb0, 0xcf, 0xd8, 0x30, 0xe8, 0x1f, 0x60, 0x5d, 0xcf, 0x7d, 0xc5, 0x54, 0x2e, 0x93, + 0xae, 0x9c, 0xd7, 0x6f, + ]; + assert_eq!(EXPECTED_HASH, hash(b"hello world").expect("hash failed")); +} + +#[test] +fn kdf_succeeds() { + let mut derived_key = [0u8; PRIVATE_KEY_SEED_SIZE]; + kdf(b"myInitialKeyMaterial", b"mySalt", b"myInfo", &mut derived_key).unwrap(); + const EXPECTED_DERIVED_KEY: [u8; PRIVATE_KEY_SEED_SIZE] = [ + 0x91, 0x9b, 0x8d, 0x29, 0xc4, 0x1b, 0x93, 0xd7, 0xeb, 0x09, 0xfa, 0xd7, 0xc9, 0x87, 0xb0, + 0xd1, 0xcc, 0x26, 0xef, 0x07, 0x83, 0x42, 0xcf, 0xa3, 0x45, 0x0a, 0x57, 0xe9, 0x19, 0x86, + 0xef, 0x48, + ]; + assert_eq!(EXPECTED_DERIVED_KEY, derived_key); +} + +#[test] +fn derive_cdi_certificate_id_succeeds() { + const EXPECTED_ID: [u8; ID_SIZE] = [ + 0x7a, 0x36, 0x45, 0x2c, 0x02, 0xf6, 0x2b, 0xec, 0xf9, 0x80, 0x06, 0x75, 0x87, 0xa5, 0xc1, + 0x44, 0x0c, 0xd3, 0xc0, 0x6d, + ]; + assert_eq!(EXPECTED_ID, derive_cdi_certificate_id(b"MyPubKey").unwrap()); +} + +const EXPECTED_SEED: &[u8] = &[ + 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa, + 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08, + 0x84, 0x8a, 0x98, 0x85, 0x6d, 0xf5, 0x69, 0x21, 0x03, 0xcd, 0x09, 0xc3, 0x28, 0xd6, 0x06, 0xa7, + 0x57, 0xbd, 0x48, 0x4b, 0x0f, 0x79, 0x0f, 0xf8, 0x2f, 0xf0, 0x0a, 0x41, 0x94, 0xd8, 0x8c, 0xa8, +]; + +const EXPECTED_CDI_ATTEST: &[u8] = &[ + 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa, + 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08, +]; + +const EXPECTED_CDI_PRIVATE_KEY_SEED: &[u8] = &[ + 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d, + 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e, +]; + +const EXPECTED_PUB_KEY: &[u8] = &[ + 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9, + 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37, +]; +const EXPECTED_PRIV_KEY: &[u8] = &[ + 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d, + 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e, + 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9, + 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37, +]; + +const EXPECTED_SIGNATURE: &[u8] = &[ + 0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2, 0x32, + 0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29, 0x3c, 0x26, + 0xe6, 0x0d, 0x9c, 0xa5, 0xc2, 0x73, 0xcd, 0x8c, 0xb8, 0x3c, 0x3e, 0x5b, 0xfd, 0x62, 0x8d, 0xf6, + 0xc4, 0x27, 0xa6, 0xe9, 0x11, 0x06, 0x5a, 0xb2, 0x2b, 0x64, 0xf7, 0xfc, 0xbb, 0xab, 0x4a, 0x0e, +]; + +#[test] +fn hash_derive_sign_verify() { + let seed = hash(b"MySeedString").unwrap(); + assert_eq!(seed, EXPECTED_SEED); + let cdi_attest = &seed[..CDI_SIZE]; + assert_eq!(cdi_attest, EXPECTED_CDI_ATTEST); + let cdi_private_key_seed = derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap(); + assert_eq!(cdi_private_key_seed.as_array(), EXPECTED_CDI_PRIVATE_KEY_SEED); + let (pub_key, priv_key) = keypair_from_seed(cdi_private_key_seed.as_array()).unwrap(); + assert_eq!(&pub_key, EXPECTED_PUB_KEY); + assert_eq!(priv_key.as_array(), EXPECTED_PRIV_KEY); + let mut signature = sign(b"MyMessage", priv_key.as_array()).unwrap(); + assert_eq!(&signature, EXPECTED_SIGNATURE); + assert!(verify(b"MyMessage", &signature, &pub_key).is_ok()); + assert!(verify(b"MyMessage_fail", &signature, &pub_key).is_err()); + signature[0] += 1; + assert!(verify(b"MyMessage", &signature, &pub_key).is_err()); +} diff --git a/diced/open_dice_cbor/Android.bp b/diced/open_dice_cbor/Android.bp deleted file mode 100644 index 3e67045a..00000000 --- a/diced/open_dice_cbor/Android.bp +++ /dev/null @@ -1,55 +0,0 @@ -// 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. - -package { - default_applicable_licenses: ["system_security_license"], -} - -rust_library { - name: "libdiced_open_dice_cbor", - crate_name: "diced_open_dice_cbor", - srcs: ["lib.rs"], - - rustlibs: [ - // For ZVec - "libkeystore2_crypto_rust", - "libopen_dice_bcc_bindgen", - "libopen_dice_cbor_bindgen", - "libthiserror", - ], - static_libs: [ - "libopen_dice_bcc", - "libopen_dice_cbor", - ], - vendor_available: true, -} - -rust_test { - name: "diced_open_dice_cbor_test", - crate_name: "diced_open_dice_cbor_test", - srcs: ["lib.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "libdiced_sample_inputs", - "libkeystore2_crypto_rust", - "libopen_dice_bcc_bindgen", - "libopen_dice_cbor_bindgen", - "libthiserror", - ], - static_libs: [ - "libopen_dice_bcc", - "libopen_dice_cbor", - ], -} diff --git a/diced/open_dice_cbor/lib.rs b/diced/open_dice_cbor/lib.rs deleted file mode 100644 index 7122ca51..00000000 --- a/diced/open_dice_cbor/lib.rs +++ /dev/null @@ -1,1037 +0,0 @@ -// 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. - -//! Implements safe wrappers around the public API of libopen-dice. -//! ## Example: -//! ``` -//! use diced_open_dice_cbor as dice; -//! -//! let context = dice::dice::OpenDiceCborContext::new() -//! let parent_cdi_attest = [1u8, dice::CDI_SIZE]; -//! let parent_cdi_seal = [2u8, dice::CDI_SIZE]; -//! let input_values = dice::InputValuesOwned { -//! code_hash: [3u8, dice::HASH_SIZE], -//! config: dice::ConfigOwned::Descriptor("My descriptor".as_bytes().to_vec()), -//! authority_hash: [0u8, dice::HASH_SIZE], -//! mode: dice::Mode::Normal, -//! hidden: [0u8, dice::HIDDEN_SIZE], -//! }; -//! let (cdi_attest, cdi_seal, cert_chain) = context -//! .main_flow(&parent_cdi_attest, &parent_cdi_seal, &input_values)?; -//! ``` - -use keystore2_crypto::{zvec, ZVec}; -use open_dice_bcc_bindgen::BccMainFlow; -use open_dice_cbor_bindgen::{ - DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, - DiceGenerateCertificate, DiceHash, DiceInputValues, DiceKdf, DiceKeypairFromSeed, DiceMainFlow, - DiceMode, DiceResult, DiceSign, DiceVerify, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, - DICE_ID_SIZE, DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE, - DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE, -}; -use open_dice_cbor_bindgen::{ - DiceConfigType_kDiceConfigTypeDescriptor as DICE_CONFIG_TYPE_DESCRIPTOR, - DiceConfigType_kDiceConfigTypeInline as DICE_CONFIG_TYPE_INLINE, - DiceMode_kDiceModeDebug as DICE_MODE_DEBUG, - DiceMode_kDiceModeMaintenance as DICE_MODE_RECOVERY, - DiceMode_kDiceModeNormal as DICE_MODE_NORMAL, - DiceMode_kDiceModeNotInitialized as DICE_MODE_NOT_CONFIGURED, - DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL, - DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT, - DiceResult_kDiceResultOk as DICE_RESULT_OK, - DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR, -}; -use std::ffi::{c_void, NulError}; - -/// The size of a DICE hash. -pub const HASH_SIZE: usize = DICE_HASH_SIZE as usize; -/// The size of the DICE hidden value. -pub const HIDDEN_SIZE: usize = DICE_HIDDEN_SIZE as usize; -/// The size of a DICE inline config. -pub const INLINE_CONFIG_SIZE: usize = DICE_INLINE_CONFIG_SIZE as usize; -/// The size of a private key seed. -pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize; -/// The size of a CDI. -pub const CDI_SIZE: usize = DICE_CDI_SIZE as usize; -/// The size of an ID. -pub const ID_SIZE: usize = DICE_ID_SIZE as usize; -/// The size of a private key. -pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize; -/// The size of a public key. -pub const PUBLIC_KEY_SIZE: usize = DICE_PUBLIC_KEY_SIZE as usize; -/// The size of a signature. -pub const SIGNATURE_SIZE: usize = DICE_SIGNATURE_SIZE as usize; - -/// Open dice wrapper error type. -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum Error { - /// The libopen-dice backend reported InvalidInput. - #[error("Open dice backend: Invalid input")] - InvalidInput, - /// The libopen-dice backend reported BufferTooSmall. - #[error("Open dice backend: Buffer too small")] - BufferTooSmall, - /// The libopen-dice backend reported PlatformError. - #[error("Open dice backend: Platform error")] - PlatformError, - /// The libopen-dice backend reported an error that is outside of the defined range of errors. - /// The returned error code is embedded in this value. - #[error("Open dice backend returned an unexpected error code: {0:?}")] - Unexpected(u32), - - /// The allocation of a ZVec failed. Most likely due to a failure during the call to mlock. - #[error("ZVec allocation failed")] - ZVec(#[from] zvec::Error), - - /// Functions that have to convert str to CString may fail if the string has an interior - /// nul byte. - #[error("Input string has an interior nul byte.")] - CStrNulError(#[from] NulError), -} - -/// Open dice result type. -pub type Result<T> = std::result::Result<T, Error>; - -impl From<DiceResult> for Error { - fn from(result: DiceResult) -> Self { - match result { - DICE_RESULT_INVALID_INPUT => Error::InvalidInput, - DICE_RESULT_BUFFER_TOO_SMALL => Error::BufferTooSmall, - DICE_RESULT_PLATFORM_ERROR => Error::PlatformError, - r => Error::Unexpected(r), - } - } -} - -fn check_result(result: DiceResult) -> Result<()> { - if result == DICE_RESULT_OK { - Ok(()) - } else { - Err(result.into()) - } -} - -/// Configuration descriptor for dice input values. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Config<'a> { - /// A reference to an inline descriptor. - Inline(&'a [u8; INLINE_CONFIG_SIZE]), - /// A reference to a free form descriptor that will be hashed by the implementation. - Descriptor(&'a [u8]), -} - -enum ConfigOwned { - Inline([u8; INLINE_CONFIG_SIZE]), - Descriptor(Vec<u8>), -} - -impl Config<'_> { - fn get_type(&self) -> DiceConfigType { - match self { - Self::Inline(_) => DICE_CONFIG_TYPE_INLINE, - Self::Descriptor(_) => DICE_CONFIG_TYPE_DESCRIPTOR, - } - } - - fn get_inline(&self) -> [u8; INLINE_CONFIG_SIZE] { - match self { - Self::Inline(inline) => **inline, - _ => [0u8; INLINE_CONFIG_SIZE], - } - } - - fn get_descriptor_as_ptr(&self) -> *const u8 { - match self { - Self::Descriptor(descriptor) => descriptor.as_ptr(), - _ => std::ptr::null(), - } - } - - fn get_descriptor_size(&self) -> usize { - match self { - Self::Descriptor(descriptor) => descriptor.len(), - _ => 0, - } - } -} - -impl From<Config<'_>> for ConfigOwned { - fn from(config: Config) -> Self { - match config { - Config::Inline(inline) => ConfigOwned::Inline(*inline), - Config::Descriptor(descriptor) => ConfigOwned::Descriptor(descriptor.to_owned()), - } - } -} - -/// DICE modes as defined here: -/// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#mode-value-details -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Mode { - /// See documentation linked above. - NotConfigured = 0, - /// See documentation linked above. - Normal = 1, - /// See documentation linked above. - Debug = 2, - /// See documentation linked above. - Recovery = 3, -} - -impl Mode { - fn get_internal(&self) -> DiceMode { - match self { - Self::NotConfigured => DICE_MODE_NOT_CONFIGURED, - Self::Normal => DICE_MODE_NORMAL, - Self::Debug => DICE_MODE_DEBUG, - Self::Recovery => DICE_MODE_RECOVERY, - } - } -} - -/// This trait allows API users to supply DICE input values without copying. -pub trait InputValues { - /// Returns the code hash. - fn code_hash(&self) -> &[u8; HASH_SIZE]; - /// Returns the config. - fn config(&self) -> Config; - /// Returns the authority hash. - fn authority_hash(&self) -> &[u8; HASH_SIZE]; - /// Returns the authority descriptor. - fn authority_descriptor(&self) -> Option<&[u8]>; - /// Returns the mode. - fn mode(&self) -> Mode; - /// Returns the hidden value. - fn hidden(&self) -> &[u8; HIDDEN_SIZE]; -} - -/// An owning convenience type implementing `InputValues`. -pub struct InputValuesOwned { - code_hash: [u8; HASH_SIZE], - config: ConfigOwned, - authority_hash: [u8; HASH_SIZE], - authority_descriptor: Option<Vec<u8>>, - mode: Mode, - hidden: [u8; HIDDEN_SIZE], -} - -impl InputValuesOwned { - /// Construct a new instance of InputValuesOwned. - pub fn new( - code_hash: [u8; HASH_SIZE], - config: Config, - authority_hash: [u8; HASH_SIZE], - authority_descriptor: Option<Vec<u8>>, - mode: Mode, - hidden: [u8; HIDDEN_SIZE], - ) -> Self { - Self { - code_hash, - config: config.into(), - authority_hash, - authority_descriptor, - mode, - hidden, - } - } -} - -impl InputValues for InputValuesOwned { - fn code_hash(&self) -> &[u8; HASH_SIZE] { - &self.code_hash - } - fn config(&self) -> Config { - match &self.config { - ConfigOwned::Inline(inline) => Config::Inline(inline), - ConfigOwned::Descriptor(descriptor) => Config::Descriptor(descriptor.as_slice()), - } - } - fn authority_hash(&self) -> &[u8; HASH_SIZE] { - &self.authority_hash - } - fn authority_descriptor(&self) -> Option<&[u8]> { - self.authority_descriptor.as_deref() - } - fn mode(&self) -> Mode { - self.mode - } - fn hidden(&self) -> &[u8; HIDDEN_SIZE] { - &self.hidden - } -} - -fn call_with_input_values<T: InputValues + ?Sized, F, R>(input_values: &T, f: F) -> Result<R> -where - F: FnOnce(*const DiceInputValues) -> Result<R>, -{ - let input_values = DiceInputValues { - code_hash: *input_values.code_hash(), - code_descriptor: std::ptr::null(), - code_descriptor_size: 0, - config_type: input_values.config().get_type(), - config_value: input_values.config().get_inline(), - config_descriptor: input_values.config().get_descriptor_as_ptr(), - config_descriptor_size: input_values.config().get_descriptor_size(), - authority_hash: *input_values.authority_hash(), - authority_descriptor: input_values - .authority_descriptor() - .map_or_else(std::ptr::null, <[u8]>::as_ptr), - authority_descriptor_size: input_values.authority_descriptor().map_or(0, <[u8]>::len), - mode: input_values.mode().get_internal(), - hidden: *input_values.hidden(), - }; - - f(&input_values as *const DiceInputValues) -} - -/// Multiple of the open dice function required preallocated output buffer -/// which may be too small, this function implements the retry logic to handle -/// too small buffer allocations. -/// The callback `F` must expect a mutable reference to a buffer and a size hint -/// field. The callback is called repeatedly as long as it returns -/// `Err(Error::BufferTooSmall)`. If the size hint remains 0, the buffer size is -/// doubled with each iteration. If the size hint is set by the callback, the buffer -/// will be set to accommodate at least this many bytes. -/// If the callback returns `Ok(())`, the buffer is truncated to the size hint -/// exactly. -/// The function panics if the callback returns `Ok(())` and the size hint is -/// larger than the buffer size. -fn retry_while_adjusting_output_buffer<F>(mut f: F) -> Result<Vec<u8>> -where - F: FnMut(&mut Vec<u8>, &mut usize) -> Result<()>, -{ - let mut buffer = vec![0; INITIAL_OUT_BUFFER_SIZE]; - let mut actual_size: usize = 0; - loop { - match f(&mut buffer, &mut actual_size) { - // If Error::BufferTooSmall was returned, the allocated certificate - // buffer was to small for the output. So the buffer is resized to the actual - // size, and a second attempt is made with the new buffer. - Err(Error::BufferTooSmall) => { - let new_size = if actual_size == 0 { - // Due to an off spec implementation of open dice cbor, actual size - // does not return the required size if the buffer was too small. So - // we have to try and approach it gradually. - buffer.len() * 2 - } else { - actual_size - }; - buffer.resize(new_size, 0); - continue; - } - Err(e) => return Err(e), - Ok(()) => { - if actual_size > buffer.len() { - panic!( - "actual_size larger than buffer size: open-dice function - may have written past the end of the buffer." - ); - } - // Truncate the certificate buffer to the actual size because it may be - // smaller than the original allocation. - buffer.truncate(actual_size); - return Ok(buffer); - } - } - } -} - -/// Some libopen-dice variants use a context. Developers that want to customize these -/// bindings may want to implement their own Context factory that creates a context -/// useable by their preferred backend. -pub trait Context { - /// # Safety - /// The return value of get_context is passed to any open dice function. - /// Implementations must explain why the context pointer returned is safe - /// to be used by the open dice library. - unsafe fn get_context(&mut self) -> *mut c_void; -} - -impl<T: Context + Send> ContextImpl for T {} - -/// This represents a context for the open dice library. The wrapped open dice instance, which -/// is based on boringssl and cbor, does not use a context, so that this type is empty. -#[derive(Default)] -pub struct OpenDiceCborContext(); - -impl OpenDiceCborContext { - /// Construct a new instance of OpenDiceCborContext. - pub fn new() -> Self { - Default::default() - } -} - -impl Context for OpenDiceCborContext { - unsafe fn get_context(&mut self) -> *mut c_void { - // # Safety - // The open dice cbor implementation does not use a context. It is safe - // to return NULL. - std::ptr::null_mut() - } -} - -/// Type alias for ZVec indicating that it holds a CDI_ATTEST secret. -pub type CdiAttest = ZVec; - -/// Type alias for ZVec indicating that it holds a CDI_SEAL secret. -pub type CdiSeal = ZVec; - -/// Type alias for Vec<u8> indicating that it hold a DICE certificate. -pub type Cert = Vec<u8>; - -/// Type alias for Vec<u8> indicating that it holds a BCC certificate chain. -pub type Bcc = Vec<u8>; - -const INITIAL_OUT_BUFFER_SIZE: usize = 1024; - -/// ContextImpl is a mixin trait that implements the safe wrappers around the open dice -/// library calls. Implementations must implement Context::get_context(). As of -/// this writing, the only implementation is OpenDiceCborContext, which returns NULL. -pub trait ContextImpl: Context + Send { - /// Safe wrapper around open-dice DiceDeriveCdiPrivateKeySeed, see open dice - /// documentation for details. - fn derive_cdi_private_key_seed(&mut self, cdi_attest: &[u8; CDI_SIZE]) -> Result<ZVec> { - let mut seed = ZVec::new(PRIVATE_KEY_SEED_SIZE)?; - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is expected to be a const array of size CDI_SIZE. - // * The third argument is expected to be a non const array of size - // PRIVATE_KEY_SEED_SIZE which is fulfilled if the call to ZVec::new above - // succeeds. - // * No pointers are expected to be valid beyond the scope of the function - // call. - check_result(unsafe { - DiceDeriveCdiPrivateKeySeed(self.get_context(), cdi_attest.as_ptr(), seed.as_mut_ptr()) - })?; - Ok(seed) - } - - /// Safe wrapper around open-dice DiceDeriveCdiCertificateId, see open dice - /// documentation for details. - fn derive_cdi_certificate_id(&mut self, cdi_public_key: &[u8]) -> Result<ZVec> { - let mut id = ZVec::new(ID_SIZE)?; - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is expected to be a const array with a size given by the - // third argument. - // * The fourth argument is expected to be a non const array of size - // ID_SIZE which is fulfilled if the call to ZVec::new above succeeds. - // * No pointers are expected to be valid beyond the scope of the function - // call. - check_result(unsafe { - DiceDeriveCdiCertificateId( - self.get_context(), - cdi_public_key.as_ptr(), - cdi_public_key.len(), - id.as_mut_ptr(), - ) - })?; - Ok(id) - } - - /// Safe wrapper around open-dice DiceMainFlow, see open dice - /// documentation for details. - /// Returns a tuple of: - /// * The next attestation CDI, - /// * the next seal CDI, and - /// * the next attestation certificate. - /// `(next_attest_cdi, next_seal_cdi, next_attestation_cert)` - fn main_flow<T: InputValues + ?Sized>( - &mut self, - current_cdi_attest: &[u8; CDI_SIZE], - current_cdi_seal: &[u8; CDI_SIZE], - input_values: &T, - ) -> Result<(CdiAttest, CdiSeal, Cert)> { - let mut next_attest = CdiAttest::new(CDI_SIZE)?; - let mut next_seal = CdiSeal::new(CDI_SIZE)?; - - // SAFETY (DiceMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size CDI_SIZE. - // This is fulfilled as per the definition of the arguments `current_cdi_attest` - // and `current_cdi_seal. - // * The fourth argument is a pointer to `DiceInputValues`. It, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The fifth and sixth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return - // the generated certificate. - // * The seventh argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * The eighth argument and the ninth argument are pointers to mutable buffers of size - // CDI_SIZE. This is fulfilled if the allocation above succeeded. - // * No pointers are expected to be valid beyond the scope of the function - // call. - call_with_input_values(input_values, |input_values| { - let cert = retry_while_adjusting_output_buffer(|cert, actual_size| { - check_result(unsafe { - DiceMainFlow( - self.get_context(), - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - input_values, - cert.len(), - cert.as_mut_ptr(), - actual_size as *mut _, - next_attest.as_mut_ptr(), - next_seal.as_mut_ptr(), - ) - }) - })?; - Ok((next_attest, next_seal, cert)) - }) - } - - /// Safe wrapper around open-dice DiceHash, see open dice - /// documentation for details. - fn hash(&mut self, input: &[u8]) -> Result<Vec<u8>> { - let mut output: Vec<u8> = vec![0; HASH_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // input buffer respectively. - // * The fourth argument must be a pointer to a mutable buffer of size HASH_SIZE - // which is fulfilled by the allocation above. - check_result(unsafe { - DiceHash(self.get_context(), input.as_ptr(), input.len(), output.as_mut_ptr()) - })?; - Ok(output) - } - - /// Safe wrapper around open-dice DiceKdf, see open dice - /// documentation for details. - fn kdf(&mut self, length: usize, input_key: &[u8], salt: &[u8], info: &[u8]) -> Result<ZVec> { - let mut output = ZVec::new(length)?; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is primitive. - // * The third argument and the fourth argument are the pointer to and length of the given - // input key. - // * The fifth argument and the sixth argument are the pointer to and length of the given - // salt. - // * The seventh argument and the eighth argument are the pointer to and length of the - // given info field. - // * The ninth argument is a pointer to the output buffer which must have the - // length given by the `length` argument (see second argument). This is - // fulfilled if the allocation of `output` succeeds. - // * All pointers must be valid for the duration of the function call, but not - // longer. - check_result(unsafe { - DiceKdf( - self.get_context(), - length, - input_key.as_ptr(), - input_key.len(), - salt.as_ptr(), - salt.len(), - info.as_ptr(), - info.len(), - output.as_mut_ptr(), - ) - })?; - Ok(output) - } - - /// Safe wrapper around open-dice DiceKeyPairFromSeed, see open dice - /// documentation for details. - fn keypair_from_seed(&mut self, seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(Vec<u8>, ZVec)> { - let mut private_key = ZVec::new(PRIVATE_KEY_SIZE)?; - let mut public_key = vec![0u8; PUBLIC_KEY_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is a pointer to a const buffer of size `PRIVATE_KEY_SEED_SIZE` - // fulfilled by the definition of the argument. - // * The third argument and the fourth argument are mutable buffers of size - // `PRIVATE_KEY_SIZE` and `PUBLIC_KEY_SIZE` respectively. This is fulfilled by the - // allocations above. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceKeypairFromSeed( - self.get_context(), - seed.as_ptr(), - public_key.as_mut_ptr(), - private_key.as_mut_ptr(), - ) - })?; - Ok((public_key, private_key)) - } - - /// Safe wrapper around open-dice DiceSign, see open dice - /// documentation for details. - fn sign(&mut self, message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Vec<u8>> { - let mut signature = vec![0u8; SIGNATURE_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // message buffer. - // * The fourth argument is a const buffer of size `PRIVATE_KEY_SIZE`. This is fulfilled - // by the definition of `private key`. - // * The fifth argument is mutable buffer of size `SIGNATURE_SIZE`. This is fulfilled - // by the allocation above. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceSign( - self.get_context(), - message.as_ptr(), - message.len(), - private_key.as_ptr(), - signature.as_mut_ptr(), - ) - })?; - Ok(signature) - } - - /// Safe wrapper around open-dice DiceVerify, see open dice - /// documentation for details. - fn verify( - &mut self, - message: &[u8], - signature: &[u8; SIGNATURE_SIZE], - public_key: &[u8; PUBLIC_KEY_SIZE], - ) -> Result<()> { - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // message buffer. - // * The fourth argument is a const buffer of size `SIGNATURE_SIZE`. This is fulfilled - // by the definition of `signature`. - // * The fifth argument is a const buffer of size `PUBLIC_KEY_SIZE`. This is fulfilled - // by the definition of `public_key`. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceVerify( - self.get_context(), - message.as_ptr(), - message.len(), - signature.as_ptr(), - public_key.as_ptr(), - ) - }) - } - - /// Safe wrapper around open-dice DiceGenerateCertificate, see open dice - /// documentation for details. - fn generate_certificate<T: InputValues>( - &mut self, - subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - input_values: &T, - ) -> Result<Vec<u8>> { - // SAFETY (DiceMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size - // `PRIVATE_KEY_SEED_SIZE`. This is fulfilled as per the definition of the arguments. - // * The fourth argument is a pointer to `DiceInputValues` it, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The fifth argument and the sixth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return - // the generated certificate. - // * The seventh argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * All pointers must be valid for the duration of the function call but not beyond. - call_with_input_values(input_values, |input_values| { - let cert = retry_while_adjusting_output_buffer(|cert, actual_size| { - check_result(unsafe { - DiceGenerateCertificate( - self.get_context(), - subject_private_key_seed.as_ptr(), - authority_private_key_seed.as_ptr(), - input_values, - cert.len(), - cert.as_mut_ptr(), - actual_size as *mut _, - ) - }) - })?; - Ok(cert) - }) - } - - /// Safe wrapper around open-dice BccDiceMainFlow, see open dice - /// documentation for details. - /// Returns a tuple of: - /// * The next attestation CDI, - /// * the next seal CDI, and - /// * the next bcc adding the new certificate to the given bcc. - /// `(next_attest_cdi, next_seal_cdi, next_bcc)` - fn bcc_main_flow<T: InputValues + ?Sized>( - &mut self, - current_cdi_attest: &[u8; CDI_SIZE], - current_cdi_seal: &[u8; CDI_SIZE], - bcc: &[u8], - input_values: &T, - ) -> Result<(CdiAttest, CdiSeal, Bcc)> { - let mut next_attest = CdiAttest::new(CDI_SIZE)?; - let mut next_seal = CdiSeal::new(CDI_SIZE)?; - - // SAFETY (BccMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size CDI_SIZE. - // This is fulfilled as per the definition of the arguments `current_cdi_attest` - // and `current_cdi_seal`. - // * The fourth argument and the fifth argument are the pointer to and size of the buffer - // holding the current bcc. - // * The sixth argument is a pointer to `DiceInputValues` it, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The seventh argument and the eighth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return the generated - // certificate. - // * The ninth argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * The tenth argument and the eleventh argument are pointers to mutable buffers of - // size CDI_SIZE. This is fulfilled if the allocation above succeeded. - // * No pointers are expected to be valid beyond the scope of the function - // call. - call_with_input_values(input_values, |input_values| { - let next_bcc = retry_while_adjusting_output_buffer(|next_bcc, actual_size| { - check_result(unsafe { - BccMainFlow( - self.get_context(), - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - bcc.as_ptr(), - bcc.len(), - input_values, - next_bcc.len(), - next_bcc.as_mut_ptr(), - actual_size as *mut _, - next_attest.as_mut_ptr(), - next_seal.as_mut_ptr(), - ) - }) - })?; - Ok((next_attest, next_seal, next_bcc)) - }) - } -} - -/// This submodule provides additional support for the Boot Certificate Chain (BCC) -/// specification. -/// See https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl -pub mod bcc { - use super::{check_result, retry_while_adjusting_output_buffer, Result}; - use open_dice_bcc_bindgen::{ - BccConfigValues, BccFormatConfigDescriptor, BCC_INPUT_COMPONENT_NAME, - BCC_INPUT_COMPONENT_VERSION, BCC_INPUT_RESETTABLE, - }; - use std::ffi::CString; - - /// Safe wrapper around BccFormatConfigDescriptor, see open dice documentation for details. - pub fn format_config_descriptor( - component_name: Option<&str>, - component_version: Option<u64>, - resettable: bool, - ) -> Result<Vec<u8>> { - let component_name = match component_name { - Some(n) => Some(CString::new(n)?), - None => None, - }; - let input = BccConfigValues { - inputs: if component_name.is_some() { BCC_INPUT_COMPONENT_NAME } else { 0 } - | if component_version.is_some() { BCC_INPUT_COMPONENT_VERSION } else { 0 } - | if resettable { BCC_INPUT_RESETTABLE } else { 0 }, - // SAFETY: The as_ref() in the line below is vital to keep the component_name object - // alive. Removing as_ref will move the component_name and the pointer will - // become invalid after this statement. - component_name: component_name.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), - component_version: component_version.unwrap_or(0), - }; - - // SAFETY: - // * The first argument is a pointer to the BccConfigValues input assembled above. - // It and its indirections must be valid for the duration of the function call. - // * The second argument and the third argument are the length of and the pointer to the - // allocated output buffer respectively. The buffer must be at least as long - // as indicated by the size argument. - // * The forth argument is a pointer to the actual size returned by the function. - // * All pointers must be valid for the duration of the function call but not beyond. - retry_while_adjusting_output_buffer(|config_descriptor, actual_size| { - check_result(unsafe { - BccFormatConfigDescriptor( - &input as *const BccConfigValues, - config_descriptor.len(), - config_descriptor.as_mut_ptr(), - actual_size as *mut _, - ) - }) - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use diced_sample_inputs::make_sample_bcc_and_cdis; - use std::convert::TryInto; - - static SEED_TEST_VECTOR: &[u8] = &[ - 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, - 0xaa, 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, - 0x3a, 0x08, 0x84, 0x8a, 0x98, 0x85, 0x6d, 0xf5, 0x69, 0x21, 0x03, 0xcd, 0x09, 0xc3, 0x28, - 0xd6, 0x06, 0xa7, 0x57, 0xbd, 0x48, 0x4b, 0x0f, 0x79, 0x0f, 0xf8, 0x2f, 0xf0, 0x0a, 0x41, - 0x94, 0xd8, 0x8c, 0xa8, - ]; - - static CDI_ATTEST_TEST_VECTOR: &[u8] = &[ - 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, - 0xaa, 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, - 0x3a, 0x08, - ]; - static CDI_PRIVATE_KEY_SEED_TEST_VECTOR: &[u8] = &[ - 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, - 0x0d, 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, - 0x02, 0x6e, - ]; - - static PUB_KEY_TEST_VECTOR: &[u8] = &[ - 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, - 0xc9, 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, - 0x06, 0x37, - ]; - static PRIV_KEY_TEST_VECTOR: &[u8] = &[ - 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, - 0x0d, 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, - 0x02, 0x6e, 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, - 0xfc, 0x23, 0xc9, 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, - 0xf1, 0x61, 0x06, 0x37, - ]; - - static SIGNATURE_TEST_VECTOR: &[u8] = &[ - 0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2, - 0x32, 0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29, - 0x3c, 0x26, 0xe6, 0x0d, 0x9c, 0xa5, 0xc2, 0x73, 0xcd, 0x8c, 0xb8, 0x3c, 0x3e, 0x5b, 0xfd, - 0x62, 0x8d, 0xf6, 0xc4, 0x27, 0xa6, 0xe9, 0x11, 0x06, 0x5a, 0xb2, 0x2b, 0x64, 0xf7, 0xfc, - 0xbb, 0xab, 0x4a, 0x0e, - ]; - - #[test] - fn hash_derive_sign_verify() { - let mut ctx = OpenDiceCborContext::new(); - let seed = ctx.hash("MySeedString".as_bytes()).unwrap(); - assert_eq!(seed, SEED_TEST_VECTOR); - let cdi_attest = &seed[..CDI_SIZE]; - assert_eq!(cdi_attest, CDI_ATTEST_TEST_VECTOR); - let cdi_private_key_seed = - ctx.derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap(); - assert_eq!(&cdi_private_key_seed[..], CDI_PRIVATE_KEY_SEED_TEST_VECTOR); - let (pub_key, priv_key) = - ctx.keypair_from_seed(cdi_private_key_seed[..].try_into().unwrap()).unwrap(); - assert_eq!(&pub_key, PUB_KEY_TEST_VECTOR); - assert_eq!(&priv_key[..], PRIV_KEY_TEST_VECTOR); - let mut signature = - ctx.sign("MyMessage".as_bytes(), priv_key[..].try_into().unwrap()).unwrap(); - assert_eq!(&signature, SIGNATURE_TEST_VECTOR); - assert!(ctx - .verify( - "MyMessage".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_ok()); - assert!(ctx - .verify( - "MyMessage_fail".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_err()); - signature[0] += 1; - assert!(ctx - .verify( - "MyMessage".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_err()); - } - - static SAMPLE_CDI_ATTEST_TEST_VECTOR: &[u8] = &[ - 0x3e, 0x57, 0x65, 0x5d, 0x48, 0x02, 0xbd, 0x5c, 0x66, 0xcc, 0x1f, 0x0f, 0xbe, 0x5e, 0x32, - 0xb6, 0x9e, 0x3d, 0x04, 0xaf, 0x00, 0x15, 0xbc, 0xdd, 0x1f, 0xbc, 0x59, 0xe4, 0xc3, 0x87, - 0x95, 0x5e, - ]; - - static SAMPLE_CDI_SEAL_TEST_VECTOR: &[u8] = &[ - 0x36, 0x1b, 0xd2, 0xb3, 0xc4, 0xda, 0x77, 0xb2, 0x9c, 0xba, 0x39, 0x53, 0x82, 0x93, 0xd9, - 0xb8, 0x9f, 0x73, 0x2d, 0x27, 0x06, 0x15, 0xa8, 0xcb, 0x6d, 0x1d, 0xf2, 0xb1, 0x54, 0xbb, - 0x62, 0xf1, - ]; - - static SAMPLE_BCC_TEST_VECTOR: &[u8] = &[ - 0x84, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e, 0x85, - 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, 0x74, 0x1e, - 0x1d, 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, 0xd7, 0x33, 0xf9, - 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x34, 0x32, - 0x64, 0x38, 0x38, 0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, 0x35, 0x34, 0x37, 0x61, 0x35, - 0x30, 0x63, 0x31, 0x65, 0x30, 0x61, 0x37, 0x34, 0x39, 0x66, 0x38, 0x65, 0x66, 0x38, 0x62, - 0x38, 0x31, 0x65, 0x63, 0x36, 0x32, 0x61, 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, - 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, - 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, - 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x16, 0x48, - 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, 0x0f, - 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25, - 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, - 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, - 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x63, - 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, - 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, 0xcb, 0x65, 0x4d, - 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, 0xea, 0x3e, 0x60, 0xee, - 0x97, 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, 0x22, 0xc4, 0xc6, 0x67, 0x7a, - 0x22, 0x27, 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67, 0x3c, - 0xd0, 0xc5, 0xed, 0x0f, 0xdd, 0xe7, 0x2e, 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, 0x44, - 0x54, 0x58, 0x40, 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, - 0x4a, 0xcb, 0x3c, 0xe7, 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, - 0xb1, 0x23, 0xe6, 0xc8, 0xdf, 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, - 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, - 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, - 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, - 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, 0x3b, 0xe9, 0xc1, 0xd3, 0x95, - 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, 0x7f, 0xeb, 0xd4, 0xca, - 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, - 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, 0x57, 0x1e, 0xf0, - 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, 0xad, 0x08, 0x48, 0xde, - 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, 0x71, 0xfe, 0x17, 0x3d, 0xca, - 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38, 0x46, - 0x33, 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, - 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, - 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, - 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, 0x28, - 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, - 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, - 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, - 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, - 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, - 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, - 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, - 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, - 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, - 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1, 0x11, - 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, 0xb5, - 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, 0x0e, - 0xa7, 0x99, 0x16, 0x89, 0x97, 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, 0x1f, - 0xda, 0xe2, 0xf3, 0xe0, 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, 0xf6, - 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, - 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, - 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, - 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, 0x6f, - 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47, 0x44, - 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, - 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, 0x6d, 0x96, 0x42, 0xda, 0x64, 0x51, - 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76, 0x01, 0xe6, 0xbd, 0xc0, 0x26, - 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, 0x3a, 0x00, 0x47, 0x44, 0x58, - 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, 0xbd, 0x52, 0x81, 0x28, 0x8d, 0x24, - 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e, 0x6d, 0x96, - 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63, 0xbb, - 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, 0x4d, - 0x14, 0xac, 0x65, 0xf4, 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, - 0x01, 0x8f, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, - 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, - 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, - 0x35, 0x02, 0x78, 0x28, 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, 0x61, - 0x39, 0x35, 0x34, 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, 0x35, - 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36, 0x3a, - 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x53, - 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, - 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, - 0x47, 0x44, 0x52, 0x58, 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2, 0x9e, - 0x49, 0x4d, 0x93, 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, 0xdf, - 0x2b, 0xb3, 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, 0x78, - 0x98, 0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, 0xd2, - 0xb1, 0xf7, 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, - 0x40, 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, - 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, - 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, - 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, - 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, - 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, - 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, 0xb0, 0x9c, 0xf8, 0x40, 0x8c, 0xb0, 0x9c, - 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, 0x21, 0x11, 0x92, 0xe1, 0xf8, 0xc5, 0x09, 0x02, - 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xc4, 0xc1, - 0xd7, 0x1c, 0x2d, 0x26, 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, 0x86, 0x27, 0x59, - 0x8f, 0xd8, 0x08, 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, 0xeb, 0xbb, - 0xda, 0xf2, 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a, 0x78, - 0x76, 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, 0x61, - 0x42, 0x06, - ]; - - // This test invokes make_sample_bcc_and_cdis and compares the result bitwise to the target - // vectors. The function uses main_flow, bcc_main_flow, format_config_descriptor, - // derive_cdi_private_key_seed, and keypair_from_seed. This test is sensitive to errors - // and changes in any of those functions. - #[test] - fn main_flow_and_bcc_main_flow() { - let (cdi_attest, cdi_seal, bcc) = make_sample_bcc_and_cdis().unwrap(); - assert_eq!(&cdi_attest[..], SAMPLE_CDI_ATTEST_TEST_VECTOR); - assert_eq!(&cdi_seal[..], SAMPLE_CDI_SEAL_TEST_VECTOR); - assert_eq!(&bcc[..], SAMPLE_BCC_TEST_VECTOR); - } - - static DERIVED_KEY_TEST_VECTOR: &[u8] = &[ - 0x0e, 0xd6, 0x07, 0x0e, 0x1c, 0x38, 0x2c, 0x76, 0x13, 0xc6, 0x76, 0x25, 0x7e, 0x07, 0x6f, - 0xdb, 0x1d, 0xb1, 0x0f, 0x3f, 0xed, 0xc5, 0x2b, 0x95, 0xd1, 0x32, 0xf1, 0x63, 0x2f, 0x2a, - 0x01, 0x5e, - ]; - - #[test] - fn kdf() { - let mut ctx = OpenDiceCborContext::new(); - let derived_key = ctx - .kdf( - PRIVATE_KEY_SEED_SIZE, - "myKey".as_bytes(), - "mySalt".as_bytes(), - "myInfo".as_bytes(), - ) - .unwrap(); - assert_eq!(&derived_key[..], DERIVED_KEY_TEST_VECTOR); - } - - static CERT_ID_TEST_VECTOR: &[u8] = &[ - 0x7a, 0x36, 0x45, 0x2c, 0x02, 0xf6, 0x2b, 0xec, 0xf9, 0x80, 0x06, 0x75, 0x87, 0xa5, 0xc1, - 0x44, 0x0c, 0xd3, 0xc0, 0x6d, - ]; - - #[test] - fn derive_cdi_certificate_id() { - let mut ctx = OpenDiceCborContext::new(); - let cert_id = ctx.derive_cdi_certificate_id("MyPubKey".as_bytes()).unwrap(); - assert_eq!(&cert_id[..], CERT_ID_TEST_VECTOR); - } -} diff --git a/diced/aidl/Android.bp b/diced/sample_inputs/Android.bp index 75c18564..cf6ef5f1 100644 --- a/diced/aidl/Android.bp +++ b/diced/sample_inputs/Android.bp @@ -21,30 +21,25 @@ package { default_applicable_licenses: ["system_security_license"], } -aidl_interface { - name: "android.security.dice", - srcs: [ "android/security/dice/*.aidl" ], - unstable: true, - imports: ["android.hardware.security.dice-V1"], - backend: { - java: { - enabled: false, - platform_apis: false, - }, - rust: { - enabled: true, - apex_available: [ - "//apex_available:platform", - "com.android.compos", - ], - }, - ndk: { - enabled: true, - apps_enabled: false, - apex_available: [ - "//apex_available:platform", - "com.android.compos", - ], - } - }, +rust_library { + name: "libdiced_sample_inputs", + crate_name: "diced_sample_inputs", + srcs: ["src/lib.rs"], + rustlibs: [ + "libanyhow", + "libciborium", + "libcoset", + "libdiced_open_dice", + ], +} + +rust_test { + name: "libdiced_sample_inputs.integration_test", + crate_name: "diced_sample_inputs_test", + srcs: ["tests/*.rs"], + test_suites: ["general-tests"], + rustlibs: [ + "libdiced_open_dice", + "libdiced_sample_inputs", + ], } diff --git a/keystore2/aidl/android/security/metrics/PoolStatus.aidl b/diced/sample_inputs/src/lib.rs index 35301639..ebbfd29a 100644 --- a/keystore2/aidl/android/security/metrics/PoolStatus.aidl +++ b/diced/sample_inputs/src/lib.rs @@ -1,11 +1,11 @@ /* - * Copyright 2021, The Android Open Source Project + * Copyright (C) 2023 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 + * 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, @@ -14,17 +14,9 @@ * limitations under the License. */ -package android.security.metrics; +//! Provides a set of sample inputs for a DICE chain and CDI values derived +//! from it. -/** - * Status of the remotely provisioned keys, as defined in RkpPoolStats of - * frameworks/proto_logging/stats/atoms.proto. - * @hide - */ -@Backing(type="int") -enum PoolStatus { - EXPIRING = 1, - UNASSIGNED = 2, - ATTESTED = 3, - TOTAL = 4, -}
\ No newline at end of file +mod sample_inputs; + +pub use sample_inputs::make_sample_bcc_and_cdis; diff --git a/diced/sample_inputs/src/sample_inputs.rs b/diced/sample_inputs/src/sample_inputs.rs new file mode 100644 index 00000000..c665eb30 --- /dev/null +++ b/diced/sample_inputs/src/sample_inputs.rs @@ -0,0 +1,167 @@ +// 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 provides a set of sample input values for a DICE chain, a sample UDS, +//! as well as tuple of CDIs and BCC derived thereof. + +use anyhow::{anyhow, Context, Result}; +use ciborium::{de, ser, value::Value}; +use coset::{iana, Algorithm, AsCborValue, CoseKey, KeyOperation, KeyType, Label}; +use diced_open_dice::{ + derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor, + retry_bcc_main_flow, retry_dice_main_flow, Config, DiceArtifacts, DiceMode, InputValues, + OwnedDiceArtifacts, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE, +}; +use std::ffi::CStr; + +/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`. +const UDS: &[u8; CDI_SIZE] = &[ + 0x65, 0x4f, 0xab, 0xa9, 0xa5, 0xad, 0x0f, 0x5e, 0x15, 0xc3, 0x12, 0xf7, 0x77, 0x45, 0xfa, 0x55, + 0x18, 0x6a, 0xa6, 0x34, 0xb6, 0x7c, 0x82, 0x7b, 0x89, 0x4c, 0xc5, 0x52, 0xd3, 0x27, 0x35, 0x8e, +]; + +const CODE_HASH_ABL: [u8; HASH_SIZE] = [ + 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, + 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25, + 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb, + 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14, +]; +const AUTHORITY_HASH_ABL: [u8; HASH_SIZE] = [ + 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, + 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, + 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, + 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, +]; +const HIDDEN_ABL: [u8; HIDDEN_SIZE] = [ + 0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, 0x5f, 0x1f, + 0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, 0xc8, 0x07, 0x97, 0x4d, + 0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e, + 0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e, +]; +const CODE_HASH_AVB: [u8; HASH_SIZE] = [ + 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d, + 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa, + 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, + 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, +]; +const AUTHORITY_HASH_AVB: [u8; HASH_SIZE] = [ + 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, + 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, + 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, + 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, +]; +const HIDDEN_AVB: [u8; HIDDEN_SIZE] = [ + 0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, 0x21, 0x09, + 0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, 0x7d, 0x7e, 0xf5, 0x8e, + 0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a, + 0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a, +]; +const AUTHORITY_HASH_ANDROID: [u8; HASH_SIZE] = [ + 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, 0xb8, 0xd6, + 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0, + 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, + 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, +]; + +fn ed25519_public_key_to_cbor_value(public_key: &[u8]) -> Result<Value> { + let key = CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::EdDSA)), + key_ops: vec![KeyOperation::Assigned(iana::KeyOperation::Verify)].into_iter().collect(), + params: vec![ + ( + Label::Int(iana::Ec2KeyParameter::Crv as i64), + Value::from(iana::EllipticCurve::Ed25519 as u64), + ), + (Label::Int(iana::Ec2KeyParameter::X as i64), Value::Bytes(public_key.to_vec())), + ], + ..Default::default() + }; + key.to_cbor_value() + .map_err(|e| anyhow!(format!("Failed to serialize the key to CBOR data. Error: {e}"))) +} + +/// Makes a DICE chain (BCC) from the sample input. +/// +/// The DICE chain is of the following format: +/// public key derived from UDS -> ABL certificate -> AVB certificate -> Android certificate +pub fn make_sample_bcc_and_cdis() -> Result<OwnedDiceArtifacts> { + let private_key_seed = derive_cdi_private_key_seed(UDS) + .context("In make_sample_bcc_and_cdis: Trying to derive private key seed.")?; + + // Gets the root public key in DICE chain (BCC). + let (public_key, _) = keypair_from_seed(private_key_seed.as_array()) + .context("In make_sample_bcc_and_cids: Failed to generate key pair.")?; + let ed25519_public_key_value = ed25519_public_key_to_cbor_value(&public_key)?; + + // Gets the ABL certificate to as the root certificate of DICE chain. + let config_descriptor = retry_bcc_format_config_descriptor( + Some(CStr::from_bytes_with_nul(b"ABL\0").unwrap()), + Some(1), // version + true, + )?; + let input_values = InputValues::new( + CODE_HASH_ABL, + Config::Descriptor(config_descriptor.as_slice()), + AUTHORITY_HASH_ABL, + DiceMode::kDiceModeNormal, + HIDDEN_ABL, + ); + let (cdi_values, cert) = retry_dice_main_flow(UDS, UDS, &input_values) + .context("In make_sample_bcc_and_cdis: Trying to run first main flow.")?; + let bcc_value = Value::Array(vec![ + ed25519_public_key_value, + de::from_reader(&cert[..]).context("Deserialize root DICE certificate failed")?, + ]); + let mut bcc: Vec<u8> = vec![]; + ser::into_writer(&bcc_value, &mut bcc)?; + + // Appends AVB certificate to DICE chain. + let config_descriptor = retry_bcc_format_config_descriptor( + Some(CStr::from_bytes_with_nul(b"AVB\0").unwrap()), + Some(1), // version + true, + )?; + let input_values = InputValues::new( + CODE_HASH_AVB, + Config::Descriptor(config_descriptor.as_slice()), + AUTHORITY_HASH_AVB, + DiceMode::kDiceModeNormal, + HIDDEN_AVB, + ); + let dice_artifacts = + retry_bcc_main_flow(&cdi_values.cdi_attest, &cdi_values.cdi_seal, &bcc, &input_values) + .context("In make_sample_bcc_and_cdis: Trying to run first bcc main flow.")?; + + // Appends Android certificate to DICE chain. + let config_descriptor = retry_bcc_format_config_descriptor( + Some(CStr::from_bytes_with_nul(b"Android\0").unwrap()), + Some(12), // version + true, + )?; + let input_values = InputValues::new( + [0u8; HASH_SIZE], // code_hash + Config::Descriptor(config_descriptor.as_slice()), + AUTHORITY_HASH_ANDROID, + DiceMode::kDiceModeNormal, + [0u8; HIDDEN_SIZE], // hidden + ); + retry_bcc_main_flow( + dice_artifacts.cdi_attest(), + dice_artifacts.cdi_seal(), + dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?, + &input_values, + ) + .context("In make_sample_bcc_and_cdis: Trying to run second bcc main flow.") +} diff --git a/diced/sample_inputs/tests/api_test.rs b/diced/sample_inputs/tests/api_test.rs new file mode 100644 index 00000000..f0d6c0d0 --- /dev/null +++ b/diced/sample_inputs/tests/api_test.rs @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 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. + */ + +use diced_open_dice::DiceArtifacts; +use diced_sample_inputs::make_sample_bcc_and_cdis; + +const EXPECTED_SAMPLE_CDI_ATTEST: &[u8] = &[ + 0x3e, 0x57, 0x65, 0x5d, 0x48, 0x02, 0xbd, 0x5c, 0x66, 0xcc, 0x1f, 0x0f, 0xbe, 0x5e, 0x32, 0xb6, + 0x9e, 0x3d, 0x04, 0xaf, 0x00, 0x15, 0xbc, 0xdd, 0x1f, 0xbc, 0x59, 0xe4, 0xc3, 0x87, 0x95, 0x5e, +]; + +const EXPECTED_SAMPLE_CDI_SEAL: &[u8] = &[ + 0x36, 0x1b, 0xd2, 0xb3, 0xc4, 0xda, 0x77, 0xb2, 0x9c, 0xba, 0x39, 0x53, 0x82, 0x93, 0xd9, 0xb8, + 0x9f, 0x73, 0x2d, 0x27, 0x06, 0x15, 0xa8, 0xcb, 0x6d, 0x1d, 0xf2, 0xb1, 0x54, 0xbb, 0x62, 0xf1, +]; + +const EXPECTED_SAMPLE_BCC: &[u8] = &[ + 0x84, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e, 0x85, + 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, 0x74, 0x1e, 0x1d, + 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, 0xd7, 0x33, 0xf9, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x34, 0x32, 0x64, 0x38, 0x38, + 0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, 0x35, 0x34, 0x37, 0x61, 0x35, 0x30, 0x63, 0x31, 0x65, + 0x30, 0x61, 0x37, 0x34, 0x39, 0x66, 0x38, 0x65, 0x66, 0x38, 0x62, 0x38, 0x31, 0x65, 0x63, 0x36, + 0x32, 0x61, 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, + 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, + 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x3a, 0x00, + 0x47, 0x44, 0x50, 0x58, 0x40, 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, + 0xc3, 0x64, 0x38, 0x63, 0x26, 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, + 0x4c, 0x6d, 0xa2, 0xbe, 0x25, 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, + 0xb3, 0x91, 0x4d, 0xd3, 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, + 0x30, 0xf7, 0x15, 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, + 0x71, 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73, + 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, 0xcb, 0x65, 0x4d, + 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, 0xea, 0x3e, 0x60, 0xee, 0x97, + 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, 0x22, 0xc4, 0xc6, 0x67, 0x7a, 0x22, 0x27, + 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67, 0x3c, 0xd0, 0xc5, 0xed, + 0x0f, 0xdd, 0xe7, 0x2e, 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf9, + 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b, + 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, 0xfb, + 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1, + 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a, + 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, + 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, + 0x3b, 0xe9, 0xc1, 0xd3, 0x95, 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, + 0x7f, 0xeb, 0xd4, 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, + 0x20, 0x58, 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, 0x57, + 0x1e, 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, 0xad, 0x08, 0x48, + 0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, 0x71, 0xfe, 0x17, 0x3d, 0xca, + 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38, 0x46, 0x33, + 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, + 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, + 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, + 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, + 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, + 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, + 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, + 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, + 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, + 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, + 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, + 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, + 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1, + 0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, 0xb5, + 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, 0x0e, 0xa7, + 0x99, 0x16, 0x89, 0x97, 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, 0x1f, 0xda, 0xe2, + 0xf3, 0xe0, 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, 0xf6, 0x3a, 0x00, 0x47, + 0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, + 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, + 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, + 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, + 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, + 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, + 0x6d, 0x96, 0x42, 0xda, 0x64, 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76, + 0x01, 0xe6, 0xbd, 0xc0, 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, 0x3a, + 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, 0xbd, 0x52, 0x81, + 0x28, 0x8d, 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e, + 0x6d, 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63, + 0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, 0x4d, + 0x14, 0xac, 0x65, 0xf4, 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, + 0x8f, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, + 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, + 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x02, 0x78, 0x28, + 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, 0x61, 0x39, 0x35, 0x34, 0x61, 0x31, + 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, + 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, + 0x00, 0x47, 0x44, 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64, + 0x72, 0x6f, 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, + 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2, + 0x9e, 0x49, 0x4d, 0x93, 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, 0xdf, + 0x2b, 0xb3, 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, 0x78, 0x98, + 0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, 0xd2, 0xb1, 0xf7, + 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0x04, 0x25, + 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, 0xb8, 0xd6, 0xe1, 0x99, + 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, + 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, + 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, + 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, + 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, 0xb0, + 0x9c, 0xf8, 0x40, 0x8c, 0xb0, 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, 0x21, 0x11, 0x92, + 0xe1, 0xf8, 0xc5, 0x09, 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, + 0x58, 0x40, 0xc4, 0xc1, 0xd7, 0x1c, 0x2d, 0x26, 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, + 0x86, 0x27, 0x59, 0x8f, 0xd8, 0x08, 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, + 0xeb, 0xbb, 0xda, 0xf2, 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a, + 0x78, 0x76, 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, 0x61, + 0x42, 0x06, +]; + +#[test] +fn sample_bcc_and_cdis_are_as_expected() { + let dice_artifacts = make_sample_bcc_and_cdis().unwrap(); + assert_eq!(dice_artifacts.cdi_attest(), EXPECTED_SAMPLE_CDI_ATTEST); + assert_eq!(dice_artifacts.cdi_seal(), EXPECTED_SAMPLE_CDI_SEAL); + assert_eq!(dice_artifacts.bcc(), Some(EXPECTED_SAMPLE_BCC)); +} diff --git a/diced/src/diced_client_test.rs b/diced/src/diced_client_test.rs deleted file mode 100644 index 39155088..00000000 --- a/diced/src/diced_client_test.rs +++ /dev/null @@ -1,188 +0,0 @@ -// 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. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues, - Mode::Mode as BinderMode, -}; -use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance; -use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode; -use binder::Strong; -use diced_open_dice_cbor as dice; -use nix::libc::uid_t; -use std::convert::TryInto; - -static DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode"; -static DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance"; - -fn get_dice_node() -> Strong<dyn IDiceNode> { - binder::get_interface(DICE_NODE_SERVICE_NAME).unwrap() -} - -fn get_dice_maintenance() -> Strong<dyn IDiceMaintenance> { - binder::get_interface(DICE_MAINTENANCE_SERVICE_NAME).unwrap() -} - -static TEST_MESSAGE: &[u8] = &[ - // "My test message!" - 0x4d, 0x79, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x21, - 0x0a, -]; - -// This test calls derive with an empty argument vector and with a set of three input values. -// It then performs the same three derivation steps on the result of the former and compares -// the result to the result of the latter. -fn equivalence_test() { - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - let latter = node.derive(&input_values).expect("Trying to call derive with input values."); - let artifacts = - diced_utils::ResidentArtifacts::new(&former.cdiAttest, &former.cdiSeal, &former.bcc.data) - .unwrap(); - - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -fn sign_and_verify() { - let node = get_dice_node(); - let _signature = node.sign(&[], TEST_MESSAGE).expect("Trying to call sign."); - - let _bcc = node.getAttestationChain(&[]).expect("Trying to call getAttestationChain."); - // TODO b/204938506 check the signature with the bcc when the verifier is available. -} - -// This test calls derive with an empty argument vector, then demotes the itself using -// a set of three input values, and then calls derive with empty argument vector again. -// It then performs the same three derivation steps on the result of the former and compares -// the result to the result of the latter. -fn demote_test() { - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - node.demote(&input_values).expect("Trying to call demote with input values."); - - let latter = node.derive(&[]).expect("Trying to call derive after demote."); - - let artifacts = diced_utils::ResidentArtifacts::new( - former.cdiAttest[..].try_into().unwrap(), - former.cdiSeal[..].try_into().unwrap(), - &former.bcc.data, - ) - .unwrap(); - - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO b/204938506 when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -fn client_input_values(uid: uid_t) -> BinderInputValues { - BinderInputValues { - codeHash: [0; dice::HASH_SIZE], - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(&format!("{}", uid)), None, true) - .unwrap(), - }, - authorityHash: [0; dice::HASH_SIZE], - authorityDescriptor: None, - mode: BinderMode::NORMAL, - hidden: [0; dice::HIDDEN_SIZE], - } -} - -// This test calls derive with an empty argument vector `former` which look like this: -// <common root> | <caller> -// It then demotes diced using a set of three input values prefixed with the uid based input -// values that diced would add to any call. It then calls derive with empty argument vector -// again which will add another step using the identity of the caller. If diced was demoted -// correctly the chain of `latter` will -// look as follows: -// <common root> | <caller> | <the three sample inputs> | <caller> -// -// It then performs the same three derivation steps followed by a set of caller input values -// on `former` and compares it to `latter`. -fn demote_self_test() { - let maintenance = get_dice_maintenance(); - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - - let client = client_input_values(nix::unistd::getuid().into()); - - let mut demote_vector = vec![client.clone()]; - demote_vector.append(&mut input_values.clone()); - maintenance.demoteSelf(&demote_vector).expect("Trying to call demote_self with input values."); - - let latter = node.derive(&[]).expect("Trying to call derive after demote."); - - let artifacts = diced_utils::ResidentArtifacts::new( - former.cdiAttest[..].try_into().unwrap(), - former.cdiSeal[..].try_into().unwrap(), - &former.bcc.data, - ) - .unwrap(); - - let client = [client]; - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().chain(client.iter()).map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO b/204938506 when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -#[test] -fn run_serialized_test() { - equivalence_test(); - sign_and_verify(); - // The demote self test must run before the demote test or the test fails. - // And since demotion is not reversible the test can only pass once per boot. - demote_self_test(); - demote_test(); -} diff --git a/diced/src/diced_main.rs b/diced/src/diced_main.rs deleted file mode 100644 index c2cf02c8..00000000 --- a/diced/src/diced_main.rs +++ /dev/null @@ -1,76 +0,0 @@ -// 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. - -//! Main entry point for diced, the friendly neighborhood DICE service. - -use binder::get_interface; -use diced::{DiceMaintenance, DiceNode, DiceNodeImpl, ProxyNodeHal, ResidentNode}; -use std::convert::TryInto; -use std::panic; -use std::sync::Arc; - -static DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode"; -static DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance"; -static DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default"; - -fn main() { - android_logger::init_once( - android_logger::Config::default().with_tag("diced").with_min_level(log::Level::Debug), - ); - // Redirect panic messages to logcat. - panic::set_hook(Box::new(|panic_info| { - log::error!("{}", panic_info); - })); - - // Saying hi. - log::info!("Diced, your friendly neighborhood DICE service, is starting."); - - let node_impl: Arc<dyn DiceNodeImpl + Send + Sync> = match get_interface(DICE_HAL_SERVICE_NAME) - { - Ok(dice_device) => { - Arc::new(ProxyNodeHal::new(dice_device).expect("Failed to construct a proxy node.")) - } - Err(e) => { - log::warn!("Failed to connect to DICE HAL: {:?}", e); - log::warn!("Using sample dice artifacts."); - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis() - .expect("Failed to create sample dice artifacts."); - Arc::new( - ResidentNode::new( - cdi_attest[..] - .try_into() - .expect("Failed to convert cdi_attest into array ref."), - cdi_seal[..].try_into().expect("Failed to convert cdi_seal into array ref."), - bcc, - ) - .expect("Failed to construct a resident node."), - ) - } - }; - - let node = DiceNode::new_as_binder(node_impl.clone()) - .expect("Failed to create IDiceNode service instance."); - - let maintenance = DiceMaintenance::new_as_binder(node_impl) - .expect("Failed to create IDiceMaintenance service instance."); - - binder::add_service(DICE_NODE_SERVICE_NAME, node.as_binder()) - .expect("Failed to register IDiceNode Service"); - - binder::add_service(DICE_MAINTENANCE_SERVICE_NAME, maintenance.as_binder()) - .expect("Failed to register IDiceMaintenance Service"); - - log::info!("Joining thread pool now."); - binder::ProcessState::join_thread_pool(); -} diff --git a/diced/src/error.rs b/diced/src/error.rs deleted file mode 100644 index 3e230e4f..00000000 --- a/diced/src/error.rs +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -use android_security_dice::aidl::android::security::dice::ResponseCode::ResponseCode; -use anyhow::Result; -use binder::{ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode}; -use keystore2_selinux as selinux; -use std::ffi::CString; - -/// This is the main Diced error type. It wraps the Diced `ResponseCode` generated -/// from AIDL in the `Rc` variant and Binder and BinderTransaction errors in the respective -/// variants. -#[allow(dead_code)] // Binder error forwarding will be needed when proxy nodes are implemented. -#[derive(Debug, thiserror::Error, Eq, PartialEq, Clone)] -pub enum Error { - /// Wraps a dice `ResponseCode` as defined by the android.security.dice AIDL interface - /// specification. - #[error("Error::Rc({0:?})")] - Rc(ResponseCode), - /// 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), -} - -/// This function should be used by dice service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. -/// -/// All `Error::Rc(x)` variants get mapped onto a service specific error code of x. -/// `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 do_something() -> anyhow::Result<Vec<u8>> { -/// Err(anyhow!(Error::Rc(ResponseCode::NOT_IMPLEMENTED))) -/// } -/// -/// map_or_log_err(do_something(), Ok) -/// ``` -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - map_err_with( - result, - |e| { - log::error!("{:?}", e); - e - }, - handle_ok, - ) -} - -/// 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. -fn map_err_with<T, U, F1, F2>(result: 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 msg = match CString::new(format!("{:?}", e)) { - Ok(msg) => Some(msg), - Err(_) => { - log::warn!( - "Cannot convert error message to CStr. It contained a nul byte. - Omitting message from service specific error." - ); - None - } - }; - let rc = get_error_code(&e); - Err(BinderStatus::new_service_specific_error(rc, msg.as_deref())) - }, - handle_ok, - ) -} - -/// Extracts the error code from an `anyhow::Error` mapping any error that does not have a -/// root cause of `Error::Rc` onto `ResponseCode::SYSTEM_ERROR` and to `e` with `Error::Rc(e)` -/// otherwise. -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, - // 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, - }, - } -} diff --git a/diced/src/error_vendor.rs b/diced/src/error_vendor.rs deleted file mode 100644 index e8657e0a..00000000 --- a/diced/src/error_vendor.rs +++ /dev/null @@ -1,119 +0,0 @@ -// 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. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::ResponseCode::ResponseCode; -use anyhow::Result; -use binder::{ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode}; -use std::ffi::CString; - -/// This is the error type for DICE HAL implementations. It wraps -/// `android::hardware::security::dice::ResponseCode` generated -/// from AIDL in the `Rc` variant and Binder and BinderTransaction errors in the respective -/// variants. -#[allow(dead_code)] // Binder error forwarding will be needed when proxy nodes are implemented. -#[derive(Debug, thiserror::Error, Eq, PartialEq, Clone)] -pub enum Error { - /// Wraps a dice `ResponseCode` as defined by the Keystore AIDL interface specification. - #[error("Error::Rc({0:?})")] - Rc(ResponseCode), - /// 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), -} - -/// This function should be used by dice service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. -/// -/// All `Error::Rc(x)` variants get mapped onto a service specific error code of x. -/// `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 do_something() -> anyhow::Result<Vec<u8>> { -/// Err(anyhow!(Error::Rc(ResponseCode::NOT_IMPLEMENTED))) -/// } -/// -/// map_or_log_err(do_something(), Ok) -/// ``` -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - map_err_with( - result, - |e| { - log::error!("{:?}", e); - e - }, - handle_ok, - ) -} - -/// 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. -fn map_err_with<T, U, F1, F2>(result: 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 msg = match CString::new(format!("{:?}", e)) { - Ok(msg) => Some(msg), - Err(_) => { - log::warn!( - "Cannot convert error message to CStr. It contained a nul byte. - Omitting message from service specific error." - ); - None - } - }; - let rc = get_error_code(&e); - Err(BinderStatus::new_service_specific_error(rc, msg.as_deref())) - }, - handle_ok, - ) -} - -/// Extracts the error code from an `anyhow::Error` mapping any error that does not have a -/// root cause of `Error::Rc` onto `ResponseCode::SYSTEM_ERROR` and to `e` with `Error::Rc(e)` -/// otherwise. -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, - // 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 => ResponseCode::SYSTEM_ERROR.0, - } -} diff --git a/diced/src/hal_node.rs b/diced/src/hal_node.rs deleted file mode 100644 index 01a75777..00000000 --- a/diced/src/hal_node.rs +++ /dev/null @@ -1,725 +0,0 @@ -// 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 provides `ResidentHal`, an implementation of a IDiceDevice HAL Interface. -//! While the name implies that the DICE secrets are memory resident, the residency -//! is augmented by the implementation of the traits `DiceArtifacts` and -//! `UpdatableDiceArtifacts`. The implementation outsources all operations that -//! involve the DICE secrets to a short lived child process. By implementing -//! `UpdatableDiceArtifacts` accordingly, integrators can limit the exposure of -//! the resident DICE secrets to user space memory. E.g., an implementation might only -//! hold a path to a securefs file allowing the child to read and update the kernel state -//! through this path directly. -//! -//! ## Important Safety Note. -//! The module is not safe to use in multi threaded processes. It uses fork and runs -//! code that is not async signal safe in the child. Implementing a HAL service without -//! starting a thread pool is safe, but no secondary thread must be created. - -use crate::error_vendor::map_or_log_err; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, IDiceDevice::BnDiceDevice, IDiceDevice::IDiceDevice, - InputValues::InputValues as BinderInputValues, Signature::Signature, -}; -use anyhow::{Context, Result}; -use binder::{BinderFeatures, Result as BinderResult, Strong}; -use dice::{ContextImpl, OpenDiceCborContext}; -use diced_open_dice_cbor as dice; -use diced_utils as utils; -use nix::sys::wait::{waitpid, WaitStatus}; -use nix::unistd::{ - close, fork, pipe as nix_pipe, read as nix_read, write as nix_write, ForkResult, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::convert::TryInto; -use std::io::{Read, Write}; -use std::os::unix::io::RawFd; -use std::sync::{Arc, RwLock}; -use utils::ResidentArtifacts; -pub use utils::{DiceArtifacts, UpdatableDiceArtifacts}; - -/// PipeReader is a simple wrapper around raw pipe file descriptors. -/// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which -/// reads from the pipe into an expending vector, until no more data can be read. -struct PipeReader(RawFd); - -impl Read for PipeReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes = nix_read(self.0, buf)?; - Ok(bytes) - } -} - -impl Drop for PipeReader { - fn drop(&mut self) { - close(self.0).expect("Failed to close reader pipe fd."); - } -} - -/// PipeWriter is a simple wrapper around raw pipe file descriptors. -/// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which -/// writes the given buffer into the pipe, returning the number of bytes written. -struct PipeWriter(RawFd); - -impl Write for PipeWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let written = nix_write(self.0, buf)?; - Ok(written) - } - - fn flush(&mut self) -> std::io::Result<()> { - // Flush is a NO-OP. - Ok(()) - } -} - -impl Drop for PipeWriter { - fn drop(&mut self) { - close(self.0).expect("Failed to close writer pipe fd."); - } -} - -fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> { - let (read_fd, write_fd) = nix_pipe()?; - Ok((PipeReader(read_fd), PipeWriter(write_fd))) -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, thiserror::Error)] -enum RunForkedError { - #[error("RunForkedError::String({0:?})")] - String(String), -} - -/// Run the given closure in a new process. -/// Safety: The function runs code that is not async-signal-safe in the child after forking. -/// This means, that this function must not be called by a multi threaded process. -fn run_forked<F, R>(f: F) -> Result<R> -where - R: Serialize + DeserializeOwned, - F: FnOnce() -> Result<R>, -{ - let (reader, writer) = pipe().expect("Failed to create pipe."); - - match unsafe { fork() } { - Ok(ForkResult::Parent { child, .. }) => { - drop(writer); - let status = waitpid(child, None).expect("Failed while waiting for child."); - if let WaitStatus::Exited(_, 0) = status { - // Child exited successfully. - // Read the result from the pipe. - // Deserialize the result and return it. - let result: Result<R, RunForkedError> = - serde_cbor::from_reader(reader).expect("Failed to deserialize result."); - - result.context("In run_forked:") - } else { - panic!("Child did not exit as expected {:?}", status); - } - } - Ok(ForkResult::Child) => { - // Run the closure. - let result = f() - .map_err(|err| RunForkedError::String(format! {"Nested anyhow error {:?}", err})); - - // Serialize the result of the closure. - serde_cbor::to_writer(writer, &result).expect("Result serialization failed"); - - // Set exit status to `0`. - std::process::exit(0); - } - Err(errno) => { - panic!("Failed to fork: {:?}", errno); - } - } -} - -/// A DiceHal backend implementation. -/// All functions, except `demote`, derive effective dice artifacts starting from -/// this node and iterating through `input_values` in ascending order. -pub trait DiceHalImpl { - /// Signs the message using the effective dice artifacts and Ed25519Pure. - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature>; - /// Returns the effective attestation chain. - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc>; - /// Returns the effective dice artifacts. - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover>; - /// This demotes the implementation itself. I.e. a resident node would replace its resident - /// artifacts with the effective artifacts derived using `input_values`. A proxy node would - /// simply call `demote` on its parent node. This is not reversible and changes - /// the effective dice artifacts of all clients. - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()>; -} - -/// The ResidentHal implements a IDiceDevice backend with memory resident DICE secrets. -pub struct ResidentHal<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> { - artifacts: RwLock<T>, -} - -impl<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> ResidentHal<T> { - /// Creates a new Resident node with the given dice secrets and certificate chain. - /// ## Safety - /// It is not safe to use implementations of ResidentHal in multi threaded environments. - /// If using this library to implement a HAL service make sure not to start a thread pool. - pub unsafe fn new(artifacts: T) -> Result<Self> { - Ok(ResidentHal { artifacts: RwLock::new(artifacts) }) - } - - fn with_effective_artifacts<R, F>(&self, input_values: &[BinderInputValues], f: F) -> Result<R> - where - R: Serialize + DeserializeOwned, - F: FnOnce(ResidentArtifacts) -> Result<R>, - { - let artifacts = self.artifacts.read().unwrap().clone(); - - // Safety: run_forked must not be be called by a multi threaded process. - // This requirement is propagated to the public interface of this module through - // `ResidentHal::new` - run_forked(move || { - let artifacts = artifacts.with_artifacts(|a| ResidentArtifacts::new_from(a))?; - let input_values: Vec<utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - let artifacts = artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentHal::get_effective_artifacts:")?; - f(artifacts) - }) - } -} - -impl<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> DiceHalImpl - for ResidentHal<T> -{ - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature> { - let signature: Vec<u8> = self - .with_effective_artifacts(input_values, |artifacts| { - let (cdi_attest, _, _) = artifacts.into_tuple(); - let mut dice = OpenDiceCborContext::new(); - let seed = dice - .derive_cdi_private_key_seed(cdi_attest[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert cdi_attest (length: {}).", - cdi_attest.len() - ) - })?) - .context("In ResidentHal::sign: Failed to derive seed from cdi_attest.")?; - let (_public_key, private_key) = dice - .keypair_from_seed(seed[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert seed (length: {}).", - seed.len() - ) - })?) - .context("In ResidentHal::sign: Failed to derive keypair from seed.")?; - dice.sign( - message, - private_key[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert private_key (length: {}).", - private_key.len() - ) - })?, - ) - .context("In ResidentHal::sign: Failed to sign.") - }) - .context("In ResidentHal::sign:")?; - Ok(Signature { data: signature }) - } - - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc> { - let bcc = self - .with_effective_artifacts(input_values, |artifacts| { - let (_, _, bcc) = artifacts.into_tuple(); - Ok(bcc) - }) - .context("In ResidentHal::get_attestation_chain: Failed to get effective_artifacts.")?; - - Ok(Bcc { data: bcc }) - } - - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover> { - let (cdi_attest, cdi_seal, bcc): (Vec<u8>, Vec<u8>, Vec<u8>) = self - .with_effective_artifacts(input_values, |artifacts| { - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - Ok((cdi_attest[..].to_vec(), cdi_seal[..].to_vec(), bcc)) - })?; - - utils::make_bcc_handover( - &cdi_attest - .as_slice() - .try_into() - .context("In ResidentHal::derive: Trying to convert cdi_attest to sized array.")?, - &cdi_seal - .as_slice() - .try_into() - .context("In ResidentHal::derive: Trying to convert cdi_seal to sized array.")?, - &bcc, - ) - .context("In ResidentHal::derive: Trying to construct BccHandover.") - } - - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()> { - let mut artifacts = self.artifacts.write().unwrap(); - - let artifacts_clone = (*artifacts).clone(); - - // Safety: run_forked may not be called from a multi threaded process. - // This requirement is propagated to the public interface of this module through - // `ResidentHal::new` - *artifacts = run_forked(|| { - let new_artifacts = - artifacts_clone.with_artifacts(|a| ResidentArtifacts::new_from(a))?; - let input_values: Vec<utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let new_artifacts = new_artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentHal::get_effective_artifacts:")?; - artifacts_clone.update(&new_artifacts) - })?; - - Ok(()) - } -} - -/// Implements android.hardware.security.dice.IDiceDevice. Forwards public API calls -/// to the given DiceHalImpl backend. -pub struct DiceDevice { - hal_impl: Arc<dyn DiceHalImpl + Sync + Send>, -} - -impl DiceDevice { - /// Constructs an instance of DiceDevice, wraps it with a BnDiceDevice object and - /// returns a strong pointer to the binder. The result can be used to register - /// the service with service manager. - pub fn new_as_binder( - hal_impl: Arc<dyn DiceHalImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceDevice>> { - let result = BnDiceDevice::new_binder(DiceDevice { hal_impl }, BinderFeatures::default()); - Ok(result) - } -} - -impl binder::Interface for DiceDevice {} - -impl IDiceDevice for DiceDevice { - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> BinderResult<Signature> { - map_or_log_err(self.hal_impl.sign(input_values, message), Ok) - } - fn getAttestationChain(&self, input_values: &[BinderInputValues]) -> BinderResult<Bcc> { - map_or_log_err(self.hal_impl.get_attestation_chain(input_values), Ok) - } - fn derive(&self, input_values: &[BinderInputValues]) -> BinderResult<BccHandover> { - map_or_log_err(self.hal_impl.derive(input_values), Ok) - } - fn demote(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.hal_impl.demote(input_values), Ok) - } -} - -#[cfg(test)] -mod test { - use super::*; - use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - BccHandover::BccHandover, Config::Config as BinderConfig, - InputValues::InputValues as BinderInputValues, Mode::Mode as BinderMode, - }; - use anyhow::{Context, Result}; - use diced_open_dice_cbor as dice; - use diced_sample_inputs; - use diced_utils as utils; - - #[derive(Debug, Serialize, Deserialize, Clone)] - struct InsecureSerializableArtifacts { - cdi_attest: [u8; dice::CDI_SIZE], - cdi_seal: [u8; dice::CDI_SIZE], - bcc: Vec<u8>, - } - - impl DiceArtifacts for InsecureSerializableArtifacts { - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] { - &self.cdi_attest - } - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] { - &self.cdi_seal - } - fn bcc(&self) -> Vec<u8> { - self.bcc.clone() - } - } - - impl UpdatableDiceArtifacts for InsecureSerializableArtifacts { - fn with_artifacts<F, T>(&self, f: F) -> Result<T> - where - F: FnOnce(&dyn DiceArtifacts) -> Result<T>, - { - f(self) - } - fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> { - Ok(Self { - cdi_attest: *new_artifacts.cdi_attest(), - cdi_seal: *new_artifacts.cdi_seal(), - bcc: new_artifacts.bcc(), - }) - } - } - - fn make_input_values( - code: &str, - config_name: &str, - authority: &str, - ) -> Result<BinderInputValues> { - let mut dice_ctx = dice::OpenDiceCborContext::new(); - Ok(BinderInputValues { - codeHash: dice_ctx - .hash(code.as_bytes()) - .context("In make_input_values: code hash failed.")? - .as_slice() - .try_into()?, - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(config_name), None, true) - .context("In make_input_values: Failed to format config descriptor.")?, - }, - authorityHash: dice_ctx - .hash(authority.as_bytes()) - .context("In make_input_values: authority hash failed.")? - .as_slice() - .try_into()?, - authorityDescriptor: None, - mode: BinderMode::NORMAL, - hidden: [0; dice::HIDDEN_SIZE], - }) - } - - /// Test the resident artifact batched derivation in process. - #[test] - fn derive_with_resident_artifacts() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - let artifacts = - ResidentArtifacts::new(cdi_attest[..].try_into()?, cdi_seal[..].try_into()?, &bcc)?; - - let input_values = &[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - make_input_values("component 3 code", "component 3", "component 3 authority")?, - ]; - - let input_values: Vec<utils::InputValues> = input_values.iter().map(|v| v.into()).collect(); - - let new_artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues))?; - - let result = utils::make_bcc_handover( - new_artifacts.cdi_attest(), - new_artifacts.cdi_seal(), - &new_artifacts.bcc(), - )?; - - assert_eq!(result, make_derive_test_vector()); - Ok(()) - } - - /// Test the ResidentHal hal implementation which performs the derivation in a separate - /// process and returns the result through a pipe. This test compares the result against - /// the same test vector as the in process test above. - #[test] - fn derive_with_insecure_artifacts() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - // Safety: ResidentHal can only be used in single threaded environments. - // On-device Rust tests run each test in a separate process. - let hal_impl = unsafe { - ResidentHal::new(InsecureSerializableArtifacts { - cdi_attest: cdi_attest[..].try_into()?, - cdi_seal: cdi_seal[..].try_into()?, - bcc, - }) - } - .expect("Failed to create ResidentHal."); - - let bcc_handover = hal_impl - .derive(&[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - make_input_values("component 3 code", "component 3", "component 3 authority")?, - ]) - .expect("Failed to derive artifacts."); - - assert_eq!(bcc_handover, make_derive_test_vector()); - Ok(()) - } - - /// Demoting the implementation two steps and then performing one step of child derivation - /// must yield the same outcome as three derivations with the same input values. - #[test] - fn demote() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - // Safety: ResidentHal can only be used in single threaded environments. - // On-device Rust tests run each test in a separate process. - let hal_impl = unsafe { - ResidentHal::new(InsecureSerializableArtifacts { - cdi_attest: cdi_attest[..].try_into()?, - cdi_seal: cdi_seal[..].try_into()?, - bcc, - }) - } - .expect("Failed to create ResidentHal."); - - hal_impl - .demote(&[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - ]) - .expect("Failed to demote implementation."); - - let bcc_handover = hal_impl - .derive(&[make_input_values( - "component 3 code", - "component 3", - "component 3 authority", - )?]) - .expect("Failed to derive artifacts."); - - assert_eq!(bcc_handover, make_derive_test_vector()); - Ok(()) - } - - fn make_derive_test_vector() -> BccHandover { - utils::make_bcc_handover( - &[ - // cdi_attest - 0x8f, 0xdf, 0x93, 0x67, 0xd7, 0x0e, 0xf8, 0xb8, 0xd2, 0x9c, 0x30, 0xeb, 0x4e, 0x9b, - 0x71, 0x5f, 0x9a, 0x5b, 0x67, 0xa6, 0x29, 0xe0, 0x00, 0x9b, 0x4d, 0xe6, 0x95, 0xcf, - 0xf9, 0xed, 0x5e, 0x9b, - ], - &[ - // cdi_seal - 0x15, 0x3e, 0xd6, 0x30, 0x5a, 0x8d, 0x4b, 0x6f, 0x07, 0x3f, 0x5d, 0x89, 0xc5, 0x6e, - 0x30, 0xba, 0x05, 0x56, 0xfc, 0x66, 0xf4, 0xae, 0xce, 0x7f, 0x81, 0xb9, 0xc5, 0x21, - 0x9b, 0x49, 0x3d, 0xe1, - ], - &[ - // bcc - 0x87, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e, - 0x85, 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, - 0x74, 0x1e, 0x1d, 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, - 0xd7, 0x33, 0xf9, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, - 0x78, 0x28, 0x34, 0x32, 0x64, 0x38, 0x38, 0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, - 0x35, 0x34, 0x37, 0x61, 0x35, 0x30, 0x63, 0x31, 0x65, 0x30, 0x61, 0x37, 0x34, 0x39, - 0x66, 0x38, 0x65, 0x66, 0x38, 0x62, 0x38, 0x31, 0x65, 0x63, 0x36, 0x32, 0x61, 0x66, - 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, - 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, - 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, - 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, - 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, 0x0f, 0xcf, 0x5b, 0xd1, - 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25, 0x1c, 0xb0, - 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb, - 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, - 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, - 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, - 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, - 0xcb, 0x65, 0x4d, 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, - 0xea, 0x3e, 0x60, 0xee, 0x97, 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, - 0x22, 0xc4, 0xc6, 0x67, 0x7a, 0x22, 0x27, 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, - 0x19, 0x4f, 0x96, 0x37, 0x67, 0x3c, 0xd0, 0xc5, 0xed, 0x0f, 0xdd, 0xe7, 0x2e, 0x4f, - 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf9, 0x00, 0x9d, 0xc2, - 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b, 0x24, - 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, - 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, - 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, - 0xae, 0xfd, 0xaa, 0x0d, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, - 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, - 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, 0x3b, 0xe9, 0xc1, 0xd3, 0x95, - 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, 0x7f, 0xeb, 0xd4, - 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, - 0x58, 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, - 0x57, 0x1e, 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, - 0xad, 0x08, 0x48, 0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, - 0x71, 0xfe, 0x17, 0x3d, 0xca, 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, - 0xf2, 0x5a, 0x99, 0x53, 0x38, 0x46, 0x33, 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, - 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, - 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, - 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, - 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, - 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, - 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, - 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, - 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, - 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, - 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, - 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, - 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, - 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, - 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, - 0x58, 0x40, 0x93, 0x17, 0xe1, 0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, - 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, 0xb5, 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, - 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, 0x0e, 0xa7, 0x99, 0x16, 0x89, 0x97, 0x21, - 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, 0x1f, 0xda, 0xe2, 0xf3, 0xe0, 0x54, - 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, 0xf6, 0x3a, 0x00, 0x47, 0x44, - 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, - 0xd9, 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, - 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, - 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, 0x6f, - 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47, - 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, - 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, 0x6d, 0x96, 0x42, - 0xda, 0x64, 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76, 0x01, - 0xe6, 0xbd, 0xc0, 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, - 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, - 0xbd, 0x52, 0x81, 0x28, 0x8d, 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, - 0x68, 0x86, 0x55, 0xfb, 0x2e, 0x6d, 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, - 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63, 0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, - 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, 0x4d, 0x14, 0xac, 0x65, 0xf4, 0xfa, - 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8f, 0xa9, 0x01, - 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, - 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, - 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, - 0x02, 0x78, 0x28, 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, 0x61, - 0x39, 0x35, 0x34, 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, - 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, - 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, - 0x71, 0x67, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, - 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, - 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2, 0x9e, 0x49, 0x4d, 0x93, 0x23, - 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, 0xdf, 0x2b, 0xb3, 0x69, - 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, 0x78, 0x98, 0xf1, - 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, 0xd2, 0xb1, - 0xf7, 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, - 0x40, 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, - 0x03, 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, - 0x37, 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, - 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, - 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, 0x47, 0x44, 0x56, - 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, - 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, - 0xb0, 0x9c, 0xf8, 0x40, 0x8c, 0xb0, 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, - 0x21, 0x11, 0x92, 0xe1, 0xf8, 0xc5, 0x09, 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, - 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xc4, 0xc1, 0xd7, 0x1c, 0x2d, 0x26, 0x89, - 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, 0x86, 0x27, 0x59, 0x8f, 0xd8, 0x08, 0x75, - 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, 0xeb, 0xbb, 0xda, 0xf2, 0xc8, - 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a, 0x78, 0x76, 0xab, - 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, 0x61, 0x42, - 0x06, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8d, 0xa9, 0x01, 0x78, 0x28, - 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, 0x61, 0x39, 0x35, 0x34, - 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, 0x35, 0x61, 0x66, - 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36, 0x02, 0x78, - 0x28, 0x36, 0x39, 0x62, 0x31, 0x37, 0x36, 0x37, 0x35, 0x38, 0x61, 0x36, 0x66, 0x34, - 0x34, 0x62, 0x35, 0x65, 0x38, 0x39, 0x39, 0x63, 0x64, 0x65, 0x33, 0x63, 0x66, 0x34, - 0x35, 0x31, 0x39, 0x61, 0x39, 0x33, 0x35, 0x62, 0x63, 0x39, 0x66, 0x65, 0x34, 0x3a, - 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x31, 0x0d, 0x31, 0xfa, 0x78, 0x58, 0x33, 0xf2, - 0xf8, 0x58, 0x6b, 0xe9, 0x68, 0x32, 0x44, 0xd0, 0xfc, 0x2d, 0xe1, 0xfc, 0xe1, 0xc2, - 0x4e, 0x2b, 0xa8, 0x2c, 0xa1, 0xc1, 0x48, 0xc6, 0xaa, 0x91, 0x89, 0x4f, 0xb7, 0x9c, - 0x40, 0x74, 0x21, 0x36, 0x31, 0x45, 0x09, 0xdf, 0x0c, 0xb4, 0xf9, 0x9a, 0x59, 0xae, - 0x4f, 0x21, 0x10, 0xc1, 0x38, 0xa8, 0xa2, 0xbe, 0xc6, 0x36, 0xf0, 0x56, 0x58, 0xdb, - 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x18, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x31, 0x3a, 0x00, 0x01, - 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0xce, 0x8a, 0x30, 0x4e, - 0x31, 0x53, 0xea, 0xdd, 0x2f, 0xbd, 0x15, 0xbc, 0x6b, 0x0f, 0xe7, 0x43, 0x50, 0xef, - 0x65, 0xec, 0x4e, 0x21, 0x64, 0x6e, 0x41, 0x22, 0xac, 0x87, 0xda, 0xf1, 0xf2, 0x80, - 0xc6, 0x8a, 0xd8, 0x7b, 0xe8, 0xe2, 0x9b, 0x87, 0x21, 0x5e, 0x26, 0x23, 0x11, 0x89, - 0x86, 0x57, 0x2d, 0x47, 0x73, 0x3f, 0x47, 0x87, 0xfa, 0x58, 0x5c, 0x78, 0x7b, 0xa3, - 0xfc, 0x2b, 0x6c, 0xed, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xd8, 0x40, 0xa0, - 0x60, 0x45, 0x28, 0x5d, 0xd4, 0xc1, 0x08, 0x3c, 0xbc, 0x91, 0xf4, 0xa6, 0xa4, 0xde, - 0xd3, 0x3d, 0xbb, 0x24, 0x46, 0xa3, 0x58, 0x49, 0x57, 0x4d, 0x2e, 0x6d, 0x7a, 0x78, - 0x4b, 0x9d, 0x28, 0x9a, 0x4e, 0xf1, 0x23, 0x06, 0x35, 0xff, 0x8e, 0x1e, 0xb3, 0x02, - 0x63, 0x62, 0x9a, 0x50, 0x6d, 0x18, 0x70, 0x8e, 0xe3, 0x2e, 0x29, 0xb4, 0x22, 0x71, - 0x31, 0x39, 0x65, 0xd5, 0xb5, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, - 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, - 0x06, 0x21, 0x58, 0x20, 0x51, 0x3c, 0x4b, 0x56, 0x0b, 0x49, 0x0b, 0xee, 0xc5, 0x71, - 0xd4, 0xe7, 0xbc, 0x44, 0x27, 0x4f, 0x4e, 0x67, 0xfc, 0x3a, 0xb9, 0x47, 0x8c, 0x6f, - 0x24, 0x29, 0xf8, 0xb8, 0x2f, 0xa7, 0xb3, 0x4d, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, - 0x20, 0x58, 0x40, 0x4e, 0x6d, 0x0e, 0x2b, 0x1d, 0x44, 0x99, 0xb6, 0x63, 0x07, 0x86, - 0x1a, 0xce, 0x4b, 0xdc, 0xd1, 0x3a, 0xdc, 0xbf, 0xaa, 0xb3, 0x06, 0xd9, 0xb5, 0x5c, - 0x75, 0xf0, 0x14, 0x63, 0xa9, 0x1e, 0x7c, 0x56, 0x62, 0x2c, 0xa5, 0xda, 0xc9, 0x81, - 0xcb, 0x3d, 0x63, 0x32, 0x6b, 0x76, 0x81, 0xd2, 0x93, 0xeb, 0xac, 0xfe, 0x0c, 0x87, - 0x66, 0x9e, 0x87, 0x82, 0xb4, 0x81, 0x6e, 0x33, 0xf1, 0x08, 0x01, 0x84, 0x43, 0xa1, - 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8d, 0xa9, 0x01, 0x78, 0x28, 0x36, 0x39, 0x62, 0x31, - 0x37, 0x36, 0x37, 0x35, 0x38, 0x61, 0x36, 0x66, 0x34, 0x34, 0x62, 0x35, 0x65, 0x38, - 0x39, 0x39, 0x63, 0x64, 0x65, 0x33, 0x63, 0x66, 0x34, 0x35, 0x31, 0x39, 0x61, 0x39, - 0x33, 0x35, 0x62, 0x63, 0x39, 0x66, 0x65, 0x34, 0x02, 0x78, 0x28, 0x32, 0x39, 0x65, - 0x34, 0x62, 0x61, 0x63, 0x33, 0x30, 0x31, 0x65, 0x66, 0x36, 0x35, 0x61, 0x38, 0x31, - 0x31, 0x62, 0x39, 0x39, 0x62, 0x30, 0x33, 0x64, 0x65, 0x39, 0x35, 0x34, 0x65, 0x61, - 0x37, 0x36, 0x61, 0x38, 0x39, 0x31, 0x37, 0x38, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, - 0x58, 0x40, 0xa4, 0x03, 0xe3, 0xde, 0x44, 0x96, 0xed, 0x31, 0x41, 0xa0, 0xba, 0x59, - 0xee, 0x2b, 0x03, 0x65, 0xcb, 0x63, 0x14, 0x78, 0xbe, 0xad, 0x24, 0x33, 0xb8, 0x6b, - 0x52, 0xd8, 0xab, 0xd5, 0x79, 0x84, 0x98, 0x6c, 0xc2, 0x66, 0xeb, 0x6c, 0x24, 0xa6, - 0xfa, 0x32, 0xa8, 0x16, 0xb8, 0x64, 0x37, 0x2b, 0xd4, 0xc0, 0xc4, 0xc2, 0x63, 0x25, - 0x10, 0xce, 0x47, 0xe3, 0x49, 0xad, 0x41, 0xf5, 0xc8, 0xf6, 0x3a, 0x00, 0x47, 0x44, - 0x53, 0x58, 0x18, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, 0x63, 0x6f, 0x6d, 0x70, - 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x32, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, - 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0xc7, 0x50, 0x09, 0xd0, 0xe0, 0xdd, 0x80, 0x77, - 0xae, 0xa7, 0xc8, 0x88, 0x1e, 0x88, 0xd0, 0xc7, 0x0d, 0x7c, 0x49, 0xc5, 0xb5, 0x64, - 0x32, 0x28, 0x2c, 0x48, 0x94, 0xc0, 0xd6, 0x7d, 0x9c, 0x86, 0xda, 0xf7, 0x98, 0xc7, - 0xae, 0xa4, 0x0e, 0x61, 0xc8, 0xb0, 0x8b, 0x8a, 0xe4, 0xad, 0xcf, 0xcf, 0x6d, 0x60, - 0x60, 0x31, 0xdd, 0xa7, 0x24, 0x9b, 0x27, 0x16, 0x31, 0x90, 0x80, 0x70, 0xc3, 0xba, - 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf8, 0x86, 0xc6, 0x94, 0xf9, 0x3f, 0x66, - 0x3c, 0x43, 0x01, 0x29, 0x27, 0x8d, 0x3c, 0xb2, 0x11, 0xf2, 0x04, 0xb6, 0x67, 0x4f, - 0x5f, 0x90, 0xcb, 0xc6, 0x73, 0xe6, 0x25, 0x14, 0x63, 0xa7, 0x95, 0x11, 0x0e, 0xa0, - 0x1d, 0x3f, 0x6a, 0x58, 0x0a, 0x53, 0xaa, 0x68, 0x3b, 0x92, 0x64, 0x2b, 0x2e, 0x79, - 0x80, 0x70, 0x0e, 0x41, 0xf5, 0xe9, 0x2a, 0x36, 0x0a, 0xa4, 0xe8, 0xb4, 0xe5, 0xdd, - 0xa6, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, - 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, - 0x9e, 0x04, 0x11, 0x24, 0x34, 0xba, 0x40, 0xed, 0x86, 0xe9, 0x48, 0x70, 0x3b, 0xe7, - 0x76, 0xfa, 0xc5, 0xf6, 0x6d, 0xab, 0x86, 0x12, 0x00, 0xbe, 0xc7, 0x00, 0x69, 0x0e, - 0x97, 0x97, 0xa6, 0x12, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xb7, - 0x31, 0xd5, 0x4c, 0x7d, 0xf5, 0xd7, 0xb8, 0xb4, 0x4f, 0x93, 0x47, 0x2c, 0x3d, 0x50, - 0xcc, 0xad, 0x28, 0x23, 0x68, 0xcf, 0xc2, 0x90, 0xd7, 0x02, 0x00, 0xd8, 0xf1, 0x00, - 0x14, 0x03, 0x90, 0x9e, 0x0b, 0x91, 0xa7, 0x22, 0x28, 0xfe, 0x55, 0x42, 0x30, 0x93, - 0x05, 0x66, 0xcd, 0xce, 0xb8, 0x48, 0x07, 0x56, 0x54, 0x67, 0xa5, 0xd7, 0xe3, 0x16, - 0xd6, 0x75, 0x7c, 0x94, 0x98, 0x1b, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, - 0x01, 0x8d, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x39, 0x65, 0x34, 0x62, 0x61, 0x63, 0x33, - 0x30, 0x31, 0x65, 0x66, 0x36, 0x35, 0x61, 0x38, 0x31, 0x31, 0x62, 0x39, 0x39, 0x62, - 0x30, 0x33, 0x64, 0x65, 0x39, 0x35, 0x34, 0x65, 0x61, 0x37, 0x36, 0x61, 0x38, 0x39, - 0x31, 0x37, 0x38, 0x35, 0x02, 0x78, 0x28, 0x31, 0x38, 0x37, 0x36, 0x63, 0x61, 0x63, - 0x34, 0x32, 0x33, 0x39, 0x35, 0x37, 0x66, 0x33, 0x62, 0x66, 0x62, 0x32, 0x62, 0x32, - 0x63, 0x39, 0x33, 0x37, 0x64, 0x31, 0x34, 0x62, 0x62, 0x38, 0x30, 0x64, 0x30, 0x36, - 0x37, 0x33, 0x65, 0x66, 0x66, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xf4, 0x7d, - 0x11, 0x21, 0xc1, 0x19, 0x57, 0x23, 0x08, 0x6e, 0x5f, 0xe4, 0x55, 0xc5, 0x08, 0x16, - 0x40, 0x5f, 0x2a, 0x6f, 0x04, 0x1e, 0x6f, 0x22, 0xde, 0x53, 0xbd, 0x37, 0xe2, 0xfb, - 0xb4, 0x0b, 0x65, 0xf4, 0xdc, 0xc9, 0xf4, 0xce, 0x2d, 0x82, 0x2a, 0xbc, 0xaf, 0x37, - 0x80, 0x0b, 0x7f, 0xff, 0x3a, 0x98, 0x9c, 0xa7, 0x70, 0x4f, 0xbc, 0x59, 0x4f, 0x4e, - 0xb1, 0x6d, 0xdf, 0x60, 0x39, 0x11, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x18, 0xa2, - 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x20, 0x33, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, - 0x58, 0x40, 0xa4, 0xd5, 0x6f, 0xc8, 0xd6, 0xc7, 0xe4, 0x22, 0xb4, 0x7a, 0x26, 0x49, - 0xd5, 0xb4, 0xc1, 0xc6, 0x1b, 0xfa, 0x14, 0x8c, 0x49, 0x72, 0x2f, 0xfe, 0xbc, 0xc1, - 0xc8, 0xc6, 0x65, 0x62, 0x86, 0xf7, 0xf2, 0x74, 0x45, 0x9b, 0x1a, 0xa0, 0x2b, 0xc4, - 0x27, 0x13, 0xc5, 0xc3, 0xe5, 0x28, 0xc2, 0x16, 0xcd, 0x90, 0x6d, 0xa0, 0xf7, 0x27, - 0x04, 0xa8, 0xa2, 0x62, 0xaa, 0x2c, 0x0c, 0x75, 0xd5, 0x9d, 0x3a, 0x00, 0x47, 0x44, - 0x54, 0x58, 0x40, 0x1d, 0x92, 0x34, 0xfb, 0xfe, 0x74, 0xb7, 0xce, 0x3a, 0x95, 0x45, - 0xe5, 0x3e, 0x1f, 0x5f, 0x18, 0x53, 0x5f, 0xe1, 0x85, 0xb0, 0x1d, 0xe3, 0x8d, 0x53, - 0x77, 0xdc, 0x86, 0x32, 0x3d, 0x9b, 0xf9, 0xa5, 0x51, 0x17, 0x51, 0x9a, 0xd8, 0xa6, - 0x7d, 0x45, 0x98, 0x47, 0xa2, 0x73, 0x54, 0x66, 0x28, 0x66, 0x92, 0x1d, 0x28, 0x8a, - 0xe7, 0x5d, 0xb8, 0x96, 0x4b, 0x6a, 0x9d, 0xee, 0xc2, 0xe9, 0x20, 0x3a, 0x00, 0x47, - 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, - 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x4d, 0xf5, 0x61, 0x1e, - 0xa6, 0x64, 0x74, 0x0b, 0x6c, 0x99, 0x8b, 0x6d, 0x34, 0x42, 0x21, 0xdd, 0x82, 0x26, - 0x13, 0xb4, 0xf0, 0xbc, 0x9a, 0x0b, 0xf6, 0x56, 0xbd, 0x5d, 0xea, 0xd5, 0x07, 0x7a, - 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x40, 0x4d, 0x09, 0x0d, 0x80, - 0xba, 0x12, 0x94, 0x05, 0xfb, 0x1a, 0x23, 0xa3, 0xcb, 0x28, 0x6f, 0xd7, 0x29, 0x95, - 0xda, 0x83, 0x07, 0x3c, 0xbe, 0x7c, 0x37, 0xeb, 0x9c, 0xb2, 0x77, 0x10, 0x3f, 0x6a, - 0x41, 0x80, 0xce, 0x56, 0xb7, 0x55, 0x22, 0x81, 0x77, 0x2d, 0x3c, 0xf8, 0x16, 0x38, - 0x49, 0xcc, 0x9a, 0xe8, 0x3a, 0x03, 0x33, 0x4c, 0xe6, 0x87, 0x72, 0xf6, 0x5a, 0x4a, - 0x3f, 0x4e, 0x0a, - ], - ) - .unwrap() - } -} diff --git a/diced/src/lib.rs b/diced/src/lib.rs deleted file mode 100644 index 50e0e969..00000000 --- a/diced/src/lib.rs +++ /dev/null @@ -1,203 +0,0 @@ -// 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. - -//! Implement the android.security.dice.IDiceNode service. - -mod error; -mod permission; -mod proxy_node_hal; -mod resident_node; - -pub use crate::proxy_node_hal::ProxyNodeHal; -pub use crate::resident_node::ResidentNode; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, Config::Config as BinderConfig, - InputValues::InputValues as BinderInputValues, Mode::Mode, Signature::Signature, -}; -use android_security_dice::aidl::android::security::dice::{ - IDiceMaintenance::BnDiceMaintenance, IDiceMaintenance::IDiceMaintenance, IDiceNode::BnDiceNode, - IDiceNode::IDiceNode, ResponseCode::ResponseCode, -}; -use anyhow::{Context, Result}; -use binder::{BinderFeatures, Result as BinderResult, Strong, ThreadState}; -pub use diced_open_dice_cbor as dice; -use error::{map_or_log_err, Error}; -use keystore2_selinux as selinux; -use libc::uid_t; -use permission::Permission; -use std::sync::Arc; - -/// A DiceNode backend implementation. -/// All functions except demote_self derive effective dice artifacts staring from -/// this node and iterating through `{ [client | demotion path], input_values }` -/// in ascending order. -pub trait DiceNodeImpl { - /// Signs the message using the effective dice artifacts and Ed25519Pure. - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature>; - /// Returns the effective attestation chain. - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc>; - /// Returns the effective dice artifacts. - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover>; - /// Adds [ `client` | `input_values` ] to the demotion path of the given client. - /// This changes the effective dice artifacts for all subsequent API calls of the - /// given client. - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()>; - /// This demotes the implementation itself. I.e. a resident node would replace its resident - /// with the effective artifacts derived using `input_values`. A proxy node would - /// simply call `demote` on its parent node. This is not reversible and changes - /// the effective dice artifacts of all clients. - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()>; -} - -/// Wraps a DiceNodeImpl and implements the actual IDiceNode AIDL API. -pub struct DiceNode { - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, -} - -/// This function uses its namesake in the permission module and in -/// combination with with_calling_sid from the binder crate to check -/// if the caller has the given keystore permission. -pub fn check_caller_permission<T: selinux::ClassPermission>(perm: T) -> Result<()> { - ThreadState::with_calling_sid(|calling_sid| { - let target_context = - selinux::getcon().context("In check_caller_permission: getcon failed.")?; - - selinux::check_permission( - calling_sid.ok_or(Error::Rc(ResponseCode::SYSTEM_ERROR)).context( - "In check_keystore_permission: Cannot check permission without calling_sid.", - )?, - &target_context, - perm, - ) - }) -} - -fn client_input_values(uid: uid_t) -> Result<BinderInputValues> { - Ok(BinderInputValues { - codeHash: [0; dice::HASH_SIZE], - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(&format!("{}", uid)), None, false) - .context("In client_input_values: failed to format config descriptor")?, - }, - authorityHash: [0; dice::HASH_SIZE], - authorityDescriptor: None, - hidden: [0; dice::HIDDEN_SIZE], - mode: Mode::NORMAL, - }) -} - -impl DiceNode { - /// Constructs an instance of DiceNode, wraps it with a BnDiceNode object and - /// returns a strong pointer to the binder. The result can be used to register - /// the service with service manager. - pub fn new_as_binder( - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceNode>> { - let result = BnDiceNode::new_binder( - DiceNode { node_impl }, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - ); - Ok(result) - } - - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature> { - check_caller_permission(Permission::UseSign).context("In DiceNode::sign:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::sign:")?; - self.node_impl.sign(client, input_values, message) - } - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc> { - check_caller_permission(Permission::GetAttestationChain) - .context("In DiceNode::get_attestation_chain:")?; - let client = client_input_values(ThreadState::get_calling_uid()) - .context("In DiceNode::get_attestation_chain:")?; - self.node_impl.get_attestation_chain(client, input_values) - } - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover> { - check_caller_permission(Permission::Derive).context("In DiceNode::derive:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::extend:")?; - self.node_impl.derive(client, input_values) - } - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()> { - check_caller_permission(Permission::Demote).context("In DiceNode::demote:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::demote:")?; - self.node_impl.demote(client, input_values) - } -} - -impl binder::Interface for DiceNode {} - -impl IDiceNode for DiceNode { - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> BinderResult<Signature> { - map_or_log_err(self.sign(input_values, message), Ok) - } - fn getAttestationChain(&self, input_values: &[BinderInputValues]) -> BinderResult<Bcc> { - map_or_log_err(self.get_attestation_chain(input_values), Ok) - } - fn derive(&self, input_values: &[BinderInputValues]) -> BinderResult<BccHandover> { - map_or_log_err(self.derive(input_values), Ok) - } - fn demote(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.demote(input_values), Ok) - } -} - -/// Wraps a DiceNodeImpl and implements the IDiceMaintenance AIDL API. -pub struct DiceMaintenance { - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, -} - -impl DiceMaintenance { - /// Constructs an instance of DiceMaintenance, wraps it with a BnDiceMaintenance object and - /// returns a strong pointer to the binder. The result can be used to register the service - /// with service manager. - pub fn new_as_binder( - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceMaintenance>> { - let result = BnDiceMaintenance::new_binder( - DiceMaintenance { node_impl }, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - ); - Ok(result) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - check_caller_permission(Permission::DemoteSelf) - .context("In DiceMaintenance::demote_self:")?; - self.node_impl.demote_self(input_values) - } -} - -impl binder::Interface for DiceMaintenance {} - -impl IDiceMaintenance for DiceMaintenance { - fn demoteSelf(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.demote_self(input_values), Ok) - } -} diff --git a/diced/src/permission.rs b/diced/src/permission.rs deleted file mode 100644 index 116df1b9..00000000 --- a/diced/src/permission.rs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 crate provides convenience wrappers for the SELinux permission -//! defined in the diced SELinux access class. - -use keystore2_selinux as selinux; -use selinux::{implement_class, ClassPermission}; - -implement_class!( - /// Permission provides a convenient abstraction from the SELinux class `diced`. - #[selinux(class_name = diced)] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum Permission { - /// Checked when a client attempts to call seal or unseal. - #[selinux(name = use_seal)] - UseSeal, - /// Checked when a client attempts to call IDiceNode::sign. - #[selinux(name = use_sign)] - UseSign, - /// Checked when a client attempts to call IDiceNode::getAttestationChain. - #[selinux(name = get_attestation_chain)] - GetAttestationChain, - /// Checked when a client attempts to call IDiceNode::derive. - #[selinux(name = derive)] - Derive, - /// Checked when a client wants to demote itself by calling IDiceNode::demote. - #[selinux(name = demote)] - Demote, - /// Checked when a client calls IDiceMaintenance::demote in an attempt to - /// demote this dice node. - #[selinux(name = demote_self)] - DemoteSelf, - } -); diff --git a/diced/src/proxy_node_hal.rs b/diced/src/proxy_node_hal.rs deleted file mode 100644 index 8d883d27..00000000 --- a/diced/src/proxy_node_hal.rs +++ /dev/null @@ -1,119 +0,0 @@ -// 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. - -//! A proxy dice node delegates all accesses to CDI_attest and CDI_seal to a parent -//! node, here an implementation of android.hardware.security.dice.IDiceDevice. - -#![allow(dead_code)] - -use crate::DiceNodeImpl; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, IDiceDevice::IDiceDevice, - InputValues::InputValues as BinderInputValues, Signature::Signature, -}; -use anyhow::{Context, Result}; -use binder::Strong; -use std::collections::HashMap; -use std::sync::RwLock; - -/// The ProxyNodeHal implements a IDiceNode backend delegating crypto operations -/// to the corresponding HAL. -pub struct ProxyNodeHal { - parent: Strong<dyn IDiceDevice>, - demotion_db: RwLock<HashMap<BinderInputValues, Vec<BinderInputValues>>>, -} - -impl ProxyNodeHal { - /// Creates a new proxy node with a reference to the parent service. - pub fn new(parent: Strong<dyn IDiceDevice>) -> Result<Self> { - Ok(ProxyNodeHal { parent, demotion_db: Default::default() }) - } - - fn get_effective_input_values( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Vec<BinderInputValues> { - let demotion_db = self.demotion_db.read().unwrap(); - - let client_arr = [client]; - - demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values.iter()) - .cloned() - .collect() - } -} - -impl DiceNodeImpl for ProxyNodeHal { - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature> { - self.parent - .sign(&self.get_effective_input_values(client, input_values), message) - .context("In ProxyNodeHal::sign:") - } - - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc> { - self.parent - .getAttestationChain(&self.get_effective_input_values(client, input_values)) - .context("In ProxyNodeHal::get_attestation_chain:") - } - - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover> { - self.parent - .derive(&self.get_effective_input_values(client, input_values)) - .context("In ProxyNodeHal::derive:") - } - - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()> { - let mut demotion_db = self.demotion_db.write().unwrap(); - - let client_arr = [client]; - - // The following statement consults demotion database which yields an optional demotion - // path. It then constructs an iterator over the following elements, then clones and - // collects them into a new vector: - // [ demotion path | client ], input_values - let new_path: Vec<BinderInputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values) - .cloned() - .collect(); - - let [client] = client_arr; - demotion_db.insert(client, new_path); - Ok(()) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - self.parent.demote(input_values).context("In ProxyNodeHal::demote_self:") - } -} diff --git a/diced/src/resident_node.rs b/diced/src/resident_node.rs deleted file mode 100644 index 99a6dc9d..00000000 --- a/diced/src/resident_node.rs +++ /dev/null @@ -1,191 +0,0 @@ -// 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. - -//! A resident dice node keeps CDI_attest and CDI_seal memory resident and can serve -//! its clients directly by performing all crypto operations including derivations and -//! certificate generation itself. - -use crate::DiceNodeImpl; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues, - Signature::Signature, -}; -use anyhow::{Context, Result}; -use dice::{ContextImpl, OpenDiceCborContext}; -use diced_open_dice_cbor as dice; -use diced_utils::{self as utils, InputValues, ResidentArtifacts}; -use std::collections::HashMap; -use std::convert::TryInto; -use std::sync::RwLock; - -/// The ResidentNode implements a IDiceNode backend with memory resident DICE secrets. -pub struct ResidentNode { - artifacts: RwLock<ResidentArtifacts>, - demotion_db: RwLock<HashMap<BinderInputValues, Vec<BinderInputValues>>>, -} - -impl ResidentNode { - /// Creates a new Resident node with the given dice secrets and certificate chain. - pub fn new( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: Vec<u8>, - ) -> Result<Self> { - Ok(ResidentNode { - artifacts: RwLock::new( - ResidentArtifacts::new(cdi_attest, cdi_seal, &bcc) - .context("In ResidentNode::new: Trying to initialize ResidentArtifacts")?, - ), - demotion_db: Default::default(), - }) - } - - fn get_effective_artifacts( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<ResidentArtifacts> { - let artifacts = self.artifacts.read().unwrap().try_clone()?; - let demotion_db = self.demotion_db.read().unwrap(); - - let client_arr = [client]; - - let input_values: Vec<utils::InputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values.iter()) - .map(|v| v.into()) - .collect(); - - artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In get_effective_artifacts:") - } -} - -impl DiceNodeImpl for ResidentNode { - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature> { - let (cdi_attest, _, _) = self - .get_effective_artifacts(client, input_values) - .context("In ResidentNode::sign: Failed to get effective_artifacts.")? - .into_tuple(); - let mut dice = OpenDiceCborContext::new(); - let seed = dice - .derive_cdi_private_key_seed(cdi_attest[..].try_into().with_context(|| { - format!( - "In ResidentNode::sign: Failed to convert cdi_attest (length: {}).", - cdi_attest.len() - ) - })?) - .context("In ResidentNode::sign: Failed to derive seed from cdi_attest.")?; - let (_public_key, private_key) = dice - .keypair_from_seed(seed[..].try_into().with_context(|| { - format!("In ResidentNode::sign: Failed to convert seed (length: {}).", seed.len()) - })?) - .context("In ResidentNode::sign: Failed to derive keypair from seed.")?; - Ok(Signature { - data: dice - .sign( - message, - private_key[..].try_into().with_context(|| { - format!( - "In ResidentNode::sign: Failed to convert private_key (length: {}).", - private_key.len() - ) - })?, - ) - .context("In ResidentNode::sign: Failed to sign.")?, - }) - } - - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc> { - let (_, _, bcc) = self - .get_effective_artifacts(client, input_values) - .context("In ResidentNode::get_attestation_chain: Failed to get effective_artifacts.")? - .into_tuple(); - - Ok(Bcc { data: bcc }) - } - - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover> { - let (cdi_attest, cdi_seal, bcc) = - self.get_effective_artifacts(client, input_values)?.into_tuple(); - - utils::make_bcc_handover( - &cdi_attest[..] - .try_into() - .context("In ResidentNode::derive: Trying to convert cdi_attest to sized array.")?, - &cdi_seal[..] - .try_into() - .context("In ResidentNode::derive: Trying to convert cdi_attest to sized array.")?, - &bcc, - ) - .context("In ResidentNode::derive: Trying to format bcc handover.") - } - - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()> { - let mut demotion_db = self.demotion_db.write().unwrap(); - - let client_arr = [client]; - - // The following statement consults demotion database which yields an optional demotion - // path. It then constructs an iterator over the following elements, then clones and - // collects them into a new vector: - // [ demotion path | client ], input_values - let new_path: Vec<BinderInputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values) - .cloned() - .collect(); - - let [client] = client_arr; - demotion_db.insert(client, new_path); - Ok(()) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - let mut artifacts = self.artifacts.write().unwrap(); - - let input_values = input_values - .iter() - .map(|v| { - v.try_into().with_context(|| format!("Failed to convert input values: {:#?}", v)) - }) - .collect::<Result<Vec<InputValues>>>() - .context("In ResidentNode::demote_self:")?; - - *artifacts = artifacts - .try_clone() - .context("In ResidentNode::demote_self: Failed to clone resident artifacts")? - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentNode::demote_self:")?; - Ok(()) - } -} diff --git a/diced/src/sample_inputs.rs b/diced/src/sample_inputs.rs deleted file mode 100644 index 93897a6f..00000000 --- a/diced/src/sample_inputs.rs +++ /dev/null @@ -1,255 +0,0 @@ -// 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 provides a set of sample input values for a DICE chain, a sample UDS, -//! as well as tuple of CDIs and BCC derived thereof. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues, Mode::Mode, -}; -use anyhow::{Context, Result}; -use dice::ContextImpl; -use diced_open_dice_cbor as dice; -use diced_utils::cbor; -use diced_utils::InputValues; -use keystore2_crypto::ZVec; -use std::convert::{TryFrom, TryInto}; -use std::io::Write; - -/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`. -pub static UDS: &[u8; dice::CDI_SIZE] = &[ - 0x65, 0x4f, 0xab, 0xa9, 0xa5, 0xad, 0x0f, 0x5e, 0x15, 0xc3, 0x12, 0xf7, 0x77, 0x45, 0xfa, 0x55, - 0x18, 0x6a, 0xa6, 0x34, 0xb6, 0x7c, 0x82, 0x7b, 0x89, 0x4c, 0xc5, 0x52, 0xd3, 0x27, 0x35, 0x8e, -]; - -fn encode_pub_key_ed25519(pub_key: &[u8], stream: &mut dyn Write) -> Result<()> { - cbor::encode_header(5 /* CBOR MAP */, 5, stream) - .context("In encode_pub_key_ed25519: Trying to encode map header.")?; - cbor::encode_number(1, stream) - .context("In encode_pub_key_ed25519: Trying to encode Key type tag.")?; - cbor::encode_number(1, stream) - .context("In encode_pub_key_ed25519: Trying to encode Key type.")?; - cbor::encode_number(3, stream) - .context("In encode_pub_key_ed25519: Trying to encode algorithm tag.")?; - // Encoding a -8 for AlgorithmEdDSA. The encoded number is -1 - <header argument>, - // the an argument of 7 below. - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 7 /* -1 -7 = -8*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode algorithm.")?; - cbor::encode_number(4, stream) - .context("In encode_pub_key_ed25519: Trying to encode ops tag.")?; - // Ops 2 for verify. - cbor::encode_number(2, stream).context("In encode_pub_key_ed25519: Trying to encode ops.")?; - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 0 /* -1 -0 = -1*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode curve tag.")?; - // Curve 6 for Ed25519 - cbor::encode_number(6, stream).context("In encode_pub_key_ed25519: Trying to encode curve.")?; - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 1 /* -1 -1 = -2*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode X coordinate tag.")?; - cbor::encode_bstr(pub_key, stream) - .context("In encode_pub_key_ed25519: Trying to encode X coordinate.")?; - Ok(()) -} - -/// Derives a tuple of (CDI_ATTEST, CDI_SEAL, BCC) derived of the vector of input values returned -/// by `get_input_values_vector`. -pub fn make_sample_bcc_and_cdis() -> Result<(ZVec, ZVec, Vec<u8>)> { - let mut dice_ctx = dice::OpenDiceCborContext::new(); - let private_key_seed = dice_ctx - .derive_cdi_private_key_seed(UDS) - .context("In make_sample_bcc_and_cdis: Trying to derive private key seed.")?; - - let (public_key, _) = - dice_ctx - .keypair_from_seed(&private_key_seed[..].try_into().context( - "In make_sample_bcc_and_cids: Failed to convert seed to array reference.", - )?) - .context("In make_sample_bcc_and_cids: Failed to generate key pair.")?; - - let input_values_vector = get_input_values_vector(); - - let (cdi_attest, cdi_seal, mut cert) = dice_ctx - .main_flow( - UDS, - UDS, - &InputValues::try_from(&input_values_vector[0]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (0)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run first main flow.")?; - - let mut bcc: Vec<u8> = vec![]; - - cbor::encode_header(4 /* CBOR ARRAY */, 2, &mut bcc) - .context("In make_sample_bcc_and_cdis: Trying to encode array header.")?; - encode_pub_key_ed25519(&public_key, &mut bcc) - .context("In make_sample_bcc_and_cdis: Trying encode pub_key.")?; - - bcc.append(&mut cert); - - let (cdi_attest, cdi_seal, bcc) = dice_ctx - .bcc_main_flow( - &cdi_attest[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (1)", - )?, - &cdi_seal[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (1)", - )?, - &bcc, - &InputValues::try_from(&input_values_vector[1]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (1)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run first bcc main flow.")?; - dice_ctx - .bcc_main_flow( - &cdi_attest[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (2)", - )?, - &cdi_seal[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (2)", - )?, - &bcc, - &InputValues::try_from(&input_values_vector[2]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (2)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run second bcc main flow.") -} - -fn make_input_values( - code_hash: &[u8; dice::HASH_SIZE], - authority_hash: &[u8; dice::HASH_SIZE], - config_name: &str, - config_version: u64, - config_resettable: bool, - mode: Mode, - hidden: &[u8; dice::HIDDEN_SIZE], -) -> Result<BinderInputValues> { - Ok(BinderInputValues { - codeHash: *code_hash, - config: BinderConfig { - desc: dice::bcc::format_config_descriptor( - Some(config_name), - Some(config_version), - config_resettable, - ) - .context("In make_input_values: Failed to format config descriptor.")?, - }, - authorityHash: *authority_hash, - authorityDescriptor: None, - hidden: *hidden, - mode, - }) -} - -/// Returns a set of sample input for a dice chain comprising the android boot loader ABL, -/// the verified boot information AVB, and Android S. -pub fn get_input_values_vector() -> Vec<BinderInputValues> { - vec![ - make_input_values( - &[ - // code hash - 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, - 0x63, 0x26, 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, - 0x6d, 0xa2, 0xbe, 0x25, 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, - 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, - 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14, - ], - &[ - // authority hash - 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, - 0x3c, 0xe7, 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, - 0x23, 0xe6, 0xc8, 0xdf, 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, - 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, - 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, - ], - "ABL", // config name - 1, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, - 0x5f, 0x1f, 0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, - 0xc8, 0x07, 0x97, 0x4d, 0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, - 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e, 0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, - 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e, - ], - ) - .unwrap(), - make_input_values( - &[ - // code hash - 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, - 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, - 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, - 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, - 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, - ], - &[ - // authority hash - 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, - 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, - 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, - 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, - 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, - ], - "AVB", // config name - 1, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, - 0x21, 0x09, 0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, - 0x7d, 0x7e, 0xf5, 0x8e, 0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, - 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a, 0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, - 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a, - ], - ) - .unwrap(), - make_input_values( - &[ - // code hash - 0; dice::HASH_SIZE - ], - &[ - // authority hash - 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, - 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, - 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, - 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, - 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, - ], - "Android", // config name - 12, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 0; dice::HIDDEN_SIZE - ], - ) - .unwrap(), - ] -} - -#[cfg(test)] -mod test { - use super::*; - - // This simple test checks if the invocation succeeds, essentially it tests - // if the initial bcc is accepted by `DiceContext::bcc_main_flow`. - #[test] - fn make_sample_bcc_and_cdis_test() { - make_sample_bcc_and_cdis().unwrap(); - } -} diff --git a/diced/src/utils.rs b/diced/src/utils.rs deleted file mode 100644 index 03e8969b..00000000 --- a/diced/src/utils.rs +++ /dev/null @@ -1,381 +0,0 @@ -// 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. - -//! Implements utility functions and types for diced and the dice HAL. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues, - Mode::Mode as BinderMode, -}; -use anyhow::{Context, Result}; -use dice::ContextImpl; -use diced_open_dice_cbor as dice; -use keystore2_crypto::ZVec; -use std::convert::TryInto; - -/// This new type wraps a reference to BinderInputValues and implements the open dice -/// InputValues trait. -#[derive(Debug)] -pub struct InputValues<'a>(&'a BinderInputValues); - -impl<'a> From<&'a BinderInputValues> for InputValues<'a> { - fn from(input_values: &'a BinderInputValues) -> InputValues<'a> { - Self(input_values) - } -} - -impl From<&InputValues<'_>> for BinderInputValues { - fn from(input_values: &InputValues) -> BinderInputValues { - input_values.0.clone() - } -} -impl From<InputValues<'_>> for BinderInputValues { - fn from(input_values: InputValues) -> BinderInputValues { - input_values.0.clone() - } -} - -impl dice::InputValues for InputValues<'_> { - fn code_hash(&self) -> &[u8; dice::HASH_SIZE] { - &self.0.codeHash - } - - fn config(&self) -> dice::Config { - dice::Config::Descriptor(self.0.config.desc.as_slice()) - } - - fn authority_hash(&self) -> &[u8; dice::HASH_SIZE] { - &self.0.authorityHash - } - - fn authority_descriptor(&self) -> Option<&[u8]> { - self.0.authorityDescriptor.as_deref() - } - - fn mode(&self) -> dice::Mode { - match self.0.mode { - BinderMode::NOT_INITIALIZED => dice::Mode::NotConfigured, - BinderMode::NORMAL => dice::Mode::Normal, - BinderMode::DEBUG => dice::Mode::Debug, - BinderMode::RECOVERY => dice::Mode::Recovery, - _ => dice::Mode::NotConfigured, - } - } - - fn hidden(&self) -> &[u8; dice::HIDDEN_SIZE] { - // If `self` was created using try_from the length was checked and this cannot panic. - &self.0.hidden - } -} - -/// Initializes an aidl defined BccHandover object with the arguments `cdi_attest`, `cdi_seal`, -/// and `bcc`. -pub fn make_bcc_handover( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: &[u8], -) -> Result<BccHandover> { - Ok(BccHandover { cdiAttest: *cdi_attest, cdiSeal: *cdi_seal, bcc: Bcc { data: bcc.to_vec() } }) -} - -/// ResidentArtifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL, -/// and the BCC formatted attestation certificate chain. The sensitive secrets are -/// stored in zeroing vectors, and it implements functionality to perform DICE -/// derivation steps using libopen-dice-cbor. -pub struct ResidentArtifacts { - cdi_attest: ZVec, - cdi_seal: ZVec, - bcc: Vec<u8>, -} - -impl ResidentArtifacts { - /// Create a ResidentArtifacts object. The parameters ensure that the stored secrets - /// can only have the appropriate size, so that subsequent casts to array references - /// cannot fail. - pub fn new( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: &[u8], - ) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: cdi_attest[..] - .try_into() - .context("In ResidentArtifacts::new: Trying to convert cdi_attest to ZVec.")?, - cdi_seal: cdi_seal[..] - .try_into() - .context("In ResidentArtifacts::new: Trying to convert cdi_seal to ZVec.")?, - bcc: bcc.to_vec(), - }) - } - - /// Creates a ResidentArtifacts object from another one implementing the DiceArtifacts - /// trait. Like `new` this function can only create artifacts of appropriate size - /// because DiceArtifacts returns array references of appropriate size. - pub fn new_from<T: DiceArtifacts + ?Sized>(artifacts: &T) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: artifacts.cdi_attest()[..].try_into()?, - cdi_seal: artifacts.cdi_seal()[..].try_into()?, - bcc: artifacts.bcc(), - }) - } - - /// Attempts to clone the artifacts. This operation is fallible due to the fallible - /// nature of ZVec. - pub fn try_clone(&self) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: self - .cdi_attest - .try_clone() - .context("In ResidentArtifacts::new: Trying to clone cdi_attest.")?, - cdi_seal: self - .cdi_seal - .try_clone() - .context("In ResidentArtifacts::new: Trying to clone cdi_seal.")?, - bcc: self.bcc.clone(), - }) - } - - /// Deconstruct the Artifacts into a tuple. - /// (CDI_ATTEST, CDI_SEAL, BCC) - pub fn into_tuple(self) -> (ZVec, ZVec, Vec<u8>) { - let ResidentArtifacts { cdi_attest, cdi_seal, bcc } = self; - (cdi_attest, cdi_seal, bcc) - } - - fn execute_step(self, input_values: &dyn dice::InputValues) -> Result<Self> { - let ResidentArtifacts { cdi_attest, cdi_seal, bcc } = self; - - let (cdi_attest, cdi_seal, bcc) = dice::OpenDiceCborContext::new() - .bcc_main_flow( - cdi_attest[..].try_into().with_context(|| { - format!("Trying to convert cdi_attest. (length: {})", cdi_attest.len()) - })?, - cdi_seal[..].try_into().with_context(|| { - format!("Trying to convert cdi_seal. (length: {})", cdi_seal.len()) - })?, - &bcc, - input_values, - ) - .context("In ResidentArtifacts::execute_step:")?; - Ok(ResidentArtifacts { cdi_attest, cdi_seal, bcc }) - } - - /// Iterate through the iterator of dice input values performing one - /// BCC main flow step on each element. - pub fn execute_steps<'a, Iter>(self, input_values: Iter) -> Result<Self> - where - Iter: IntoIterator<Item = &'a dyn dice::InputValues>, - { - input_values - .into_iter() - .try_fold(self, |acc, input_values| acc.execute_step(input_values)) - .context("In ResidentArtifacts::execute_step:") - } -} - -/// An object that implements this trait provides the typical DICE artifacts. -/// CDI_ATTEST, CDI_SEAL, and a certificate chain up to the public key that -/// can be derived from CDI_ATTEST. Implementations should check the length of -/// the stored CDI_* secrets on creation so that any valid instance returns the -/// correct secrets in an infallible way. -pub trait DiceArtifacts { - /// Returns CDI_ATTEST. - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE]; - /// Returns CDI_SEAL. - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE]; - /// Returns the attestation certificate chain in BCC format. - fn bcc(&self) -> Vec<u8>; -} - -/// Implement this trait to provide read and write access to a secure artifact -/// storage that can be used by the ResidentHal implementation. -pub trait UpdatableDiceArtifacts { - /// With artifacts provides access to the stored artifacts for the duration - /// of the function call by means of calling the callback. - fn with_artifacts<F, T>(&self, f: F) -> Result<T> - where - F: FnOnce(&dyn DiceArtifacts) -> Result<T>; - - /// Consumes the object and returns a an updated version of itself. - fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> - where - Self: Sized; -} - -impl DiceArtifacts for ResidentArtifacts { - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] { - self.cdi_attest[..].try_into().unwrap() - } - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] { - self.cdi_seal[..].try_into().unwrap() - } - fn bcc(&self) -> Vec<u8> { - self.bcc.clone() - } -} - -/// This submodule implements a limited set of CBOR generation functionality. Essentially, -/// a cbor header generator and some convenience functions for number and BSTR encoding. -pub mod cbor { - use anyhow::{anyhow, Context, Result}; - use std::convert::TryInto; - use std::io::Write; - - /// CBOR encodes a positive number. - pub fn encode_number(n: u64, buffer: &mut dyn Write) -> Result<()> { - encode_header(0, n, buffer) - } - - /// CBOR encodes a binary string. - pub fn encode_bstr(bstr: &[u8], buffer: &mut dyn Write) -> Result<()> { - encode_header( - 2, - bstr.len().try_into().context("In encode_bstr: Failed to convert usize to u64.")?, - buffer, - ) - .context("In encode_bstr: While writing header.")?; - let written = buffer.write(bstr).context("In encode_bstr: While writing payload.")?; - if written != bstr.len() { - return Err(anyhow!("In encode_bstr: Buffer too small. ({}, {})", written, bstr.len())); - } - Ok(()) - } - - /// Formats a CBOR header. `t` is the type, and n is the header argument. - pub fn encode_header(t: u8, n: u64, buffer: &mut dyn Write) -> Result<()> { - match n { - n if n < 24 => { - let written = buffer - .write(&u8::to_be_bytes(((t as u8) << 5) | (n as u8 & 0x1F))) - .with_context(|| { - format!("In encode_header: Failed to write header ({}, {})", t, n) - })?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - } - n if n <= 0xFF => { - let written = - buffer.write(&u8::to_be_bytes(((t as u8) << 5) | (24u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u8::to_be_bytes(n as u8)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 1 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n if n <= 0xFFFF => { - let written = - buffer.write(&u8::to_be_bytes(((t as u8) << 5) | (25u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u16::to_be_bytes(n as u16)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 2 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n if n <= 0xFFFFFFFF => { - let written = - buffer.write(&u8::to_be_bytes(((t as u8) << 5) | (26u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u32::to_be_bytes(n as u32)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 4 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n => { - let written = - buffer.write(&u8::to_be_bytes(((t as u8) << 5) | (27u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u64::to_be_bytes(n as u64)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 8 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - } - Ok(()) - } - - #[cfg(test)] - mod test { - use super::*; - - fn encode_header_helper(t: u8, n: u64) -> Vec<u8> { - let mut b: Vec<u8> = vec![]; - encode_header(t, n, &mut b).unwrap(); - b - } - - #[test] - fn encode_header_test() { - assert_eq!(&encode_header_helper(0, 0), &[0b000_00000]); - assert_eq!(&encode_header_helper(0, 23), &[0b000_10111]); - assert_eq!(&encode_header_helper(0, 24), &[0b000_11000, 24]); - assert_eq!(&encode_header_helper(0, 0xff), &[0b000_11000, 0xff]); - assert_eq!(&encode_header_helper(0, 0x100), &[0b000_11001, 0x01, 0x00]); - assert_eq!(&encode_header_helper(0, 0xffff), &[0b000_11001, 0xff, 0xff]); - assert_eq!(&encode_header_helper(0, 0x10000), &[0b000_11010, 0x00, 0x01, 0x00, 0x00]); - assert_eq!( - &encode_header_helper(0, 0xffffffff), - &[0b000_11010, 0xff, 0xff, 0xff, 0xff] - ); - assert_eq!( - &encode_header_helper(0, 0x100000000), - &[0b000_11011, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00] - ); - assert_eq!( - &encode_header_helper(0, 0xffffffffffffffff), - &[0b000_11011, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] - ); - } - } -} diff --git a/fsverity/Android.bp b/fsverity/Android.bp index 2fc3c01c..ce3b4995 100644 --- a/fsverity/Android.bp +++ b/fsverity/Android.bp @@ -32,14 +32,12 @@ python_library_host { proto: { canonical_path_from_root: false, }, - version: { - py2: { - enabled: true, - }, - py3: { - enabled: true, - }, - }, +} + +python_binary_host { + name: "fsverity_manifest_generator", + srcs: ["fsverity_manifest_generator.py"], + libs: ["fsverity_digests_proto_python"], } rust_protobuf { diff --git a/fsverity/TEST_MAPPING b/fsverity/TEST_MAPPING new file mode 100644 index 00000000..b327cb86 --- /dev/null +++ b/fsverity/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "ComposHostTestCases" + } + ] +} diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py new file mode 100644 index 00000000..181758aa --- /dev/null +++ b/fsverity/fsverity_manifest_generator.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright 2022 Google Inc. All rights reserved. +# +# 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. + +""" +`fsverity_manifest_generator` generates the a manifest file containing digests +of target files. +""" + +import argparse +import os +import subprocess +import sys +from fsverity_digests_pb2 import FSVerityDigests + +HASH_ALGORITHM = 'sha256' + +def _digest(fsverity_path, input_file): + cmd = [fsverity_path, 'digest', input_file] + cmd.extend(['--compact']) + cmd.extend(['--hash-alg', HASH_ALGORITHM]) + out = subprocess.check_output(cmd, universal_newlines=True).strip() + return bytes(bytearray.fromhex(out)) + +if __name__ == '__main__': + p = argparse.ArgumentParser() + p.add_argument( + '--output', + help='Path to the output manifest', + required=True) + p.add_argument( + '--fsverity-path', + help='path to the fsverity program', + required=True) + p.add_argument( + '--base-dir', + help='directory to use as a relative root for the inputs', + required=True) + p.add_argument( + 'inputs', + nargs='*', + help='input file for the build manifest') + args = p.parse_args(sys.argv[1:]) + + digests = FSVerityDigests() + for f in sorted(args.inputs): + # f is a full path for now; make it relative so it starts with {mount_point}/ + digest = digests.digests[os.path.relpath(f, args.base_dir)] + digest.digest = _digest(args.fsverity_path, f) + digest.hash_alg = HASH_ALGORITHM + + manifest = digests.SerializeToString() + + with open(args.output, "wb") as f: + f.write(manifest) diff --git a/fsverity/libfsverity_rs/Android.bp b/fsverity/libfsverity_rs/Android.bp new file mode 100644 index 00000000..91b12486 --- /dev/null +++ b/fsverity/libfsverity_rs/Android.bp @@ -0,0 +1,17 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_library { + name: "libfsverity_rs", + crate_name: "fsverity", + srcs: ["lib.rs"], + edition: "2021", + rustlibs: [ + "libnix", + ], + apex_available: [ + "com.android.compos", + "com.android.virt", + ], +} diff --git a/fsverity/libfsverity_rs/lib.rs b/fsverity/libfsverity_rs/lib.rs new file mode 100644 index 00000000..473b2d5c --- /dev/null +++ b/fsverity/libfsverity_rs/lib.rs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 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. + */ + +//! A wrapper library to use fs-verity + +mod sys; + +use crate::sys::*; +use std::io; +use std::os::fd::AsRawFd; +use std::os::unix::io::BorrowedFd; + +fn read_metadata(fd: i32, metadata_type: u64, offset: u64, buf: &mut [u8]) -> io::Result<usize> { + let mut arg = fsverity_read_metadata_arg { + metadata_type, + offset, + length: buf.len() as u64, + buf_ptr: buf.as_mut_ptr() as u64, + __reserved: 0, + }; + // SAFETY: the ioctl doesn't change the sematics in the current process + Ok(unsafe { read_verity_metadata(fd, &mut arg) }? as usize) +} + +/// Read the raw Merkle tree from the fd, if it exists. The API semantics is similar to a regular +/// pread(2), and may not return full requested buffer. +pub fn read_merkle_tree(fd: i32, offset: u64, buf: &mut [u8]) -> io::Result<usize> { + read_metadata(fd, FS_VERITY_METADATA_TYPE_MERKLE_TREE, offset, buf) +} + +/// Read the fs-verity signature from the fd (if exists). The returned signature should be complete. +pub fn read_signature(fd: i32, buf: &mut [u8]) -> io::Result<usize> { + read_metadata(fd, FS_VERITY_METADATA_TYPE_SIGNATURE, 0 /* offset */, buf) +} + +/// Enable fs-verity to the `fd`, with sha256 hash algorithm and 4KB block size. +pub fn enable(fd: BorrowedFd) -> io::Result<()> { + let arg = fsverity_enable_arg { + version: 1, + hash_algorithm: FS_VERITY_HASH_ALG_SHA256, + block_size: 4096, + salt_size: 0, + salt_ptr: 0, + sig_size: 0, + __reserved1: 0, + sig_ptr: 0, + __reserved2: [0; 11], + }; + // SAFETY: the ioctl doesn't change the sematics in the current process + if unsafe { enable_verity(fd.as_raw_fd(), &arg) } == Ok(0) { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} diff --git a/fsverity/libfsverity_rs/sys.rs b/fsverity/libfsverity_rs/sys.rs new file mode 100644 index 00000000..8ce0836d --- /dev/null +++ b/fsverity/libfsverity_rs/sys.rs @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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. + */ + +//! Stable API definition copied from uapi/linux/fsverity.h + +use nix::{ioctl_readwrite, ioctl_write_ptr}; + +const FS_IOCTL_MAGIC: u8 = b'f'; +const FS_IOC_ENABLE_VERITY: u8 = 133; +const FS_IOCTL_READ_VERITY_METADATA: u8 = 135; + +pub const FS_VERITY_HASH_ALG_SHA256: u32 = 1; +pub const FS_VERITY_METADATA_TYPE_MERKLE_TREE: u64 = 1; +pub const FS_VERITY_METADATA_TYPE_SIGNATURE: u64 = 3; + +#[repr(C)] +pub struct fsverity_read_metadata_arg { + pub metadata_type: u64, + pub offset: u64, + pub length: u64, + pub buf_ptr: u64, + pub __reserved: u64, +} + +ioctl_readwrite!( + read_verity_metadata, + FS_IOCTL_MAGIC, + FS_IOCTL_READ_VERITY_METADATA, + fsverity_read_metadata_arg +); + +#[repr(C)] +pub struct fsverity_enable_arg { + pub version: u32, + pub hash_algorithm: u32, + pub block_size: u32, + pub salt_size: u32, + pub salt_ptr: u64, + pub sig_size: u32, + pub __reserved1: u32, + pub sig_ptr: u64, + pub __reserved2: [u64; 11], +} + +ioctl_write_ptr!(enable_verity, FS_IOCTL_MAGIC, FS_IOC_ENABLE_VERITY, fsverity_enable_arg); diff --git a/fsverity_init/main.cpp b/fsverity_init/main.cpp index 3f75dca9..b502b91c 100644 --- a/fsverity_init/main.cpp +++ b/fsverity_init/main.cpp @@ -48,12 +48,6 @@ int main(int argc, const char** argv) { return -1; } } else if (command == "--lock") { - // Requires files backed by fs-verity to be verified with a key in .fs-verity - // keyring. - if (!android::base::WriteStringToFile("1", "/proc/sys/fs/verity/require_signatures")) { - PLOG(ERROR) << "Failed to enforce fs-verity signature"; - } - if (!android::base::GetBoolProperty("ro.debuggable", false)) { if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) { PLOG(ERROR) << "Cannot restrict .fs-verity keyring"; diff --git a/identity/Android.bp b/identity/Android.bp index b3b704fb..f4fcc0a3 100644 --- a/identity/Android.bp +++ b/identity/Android.bp @@ -19,47 +19,50 @@ cc_defaults { sanitize: { misc_undefined : ["integer"], }, - clang : true, + } cc_binary { name: "credstore", defaults: [ "identity_defaults", + "identity_use_latest_hal_aidl_cpp_static", "keymint_use_latest_hal_aidl_ndk_shared", "keymint_use_latest_hal_aidl_cpp_static", ], srcs: [ - "main.cpp", - "CredentialStore.cpp", - "CredentialStoreFactory.cpp", - "WritableCredential.cpp", "Credential.cpp", "CredentialData.cpp", + "CredentialStore.cpp", + "CredentialStoreFactory.cpp", "Session.cpp", "Util.cpp", + "WritableCredential.cpp", + "main.cpp", ], init_rc: ["credstore.rc"], shared_libs: [ + "android.hardware.identity-support-lib", + "android.hardware.keymaster@4.0", + "android.security.authorization-ndk", "libbase", "libbinder", "libbinder_ndk", - "android.hardware.keymaster@4.0", "libcredstore_aidl", "libcrypto", - "libutils", "libhidlbase", - "android.hardware.identity-support-lib", "libkeymaster4support", "libkeystore-attestation-application-id", - "android.security.authorization-ndk", - "android.security.remoteprovisioning-cpp", + "librkp_support", + "libutils", "libutilscallstack", + "libvintf", ], static_libs: [ - "android.hardware.identity-V4-cpp", + "android.hardware.security.rkp-V3-cpp", "android.hardware.keymaster-V3-cpp", + "android.security.rkp_aidl-cpp", "libcppbor_external", ], } diff --git a/identity/Credential.cpp b/identity/Credential.cpp index c67fe4a3..0b1d171f 100644 --- a/identity/Credential.cpp +++ b/identity/Credential.cpp @@ -554,9 +554,18 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, ret.resultNamespaces.push_back(resultNamespaceParcel); } - status = halBinder->finishRetrieval(&ret.mac, &ret.deviceNameSpaces); - if (!status.isOk()) { - return halStatusToGenericError(status); + // API version 5 (feature version 202301) supports both MAC and ECDSA signature. + if (halApiVersion_ >= 5) { + status = halBinder->finishRetrievalWithSignature(&ret.mac, &ret.deviceNameSpaces, + &ret.signature); + if (!status.isOk()) { + return halStatusToGenericError(status); + } + } else { + status = halBinder->finishRetrieval(&ret.mac, &ret.deviceNameSpaces); + if (!status.isOk()) { + return halStatusToGenericError(status); + } } ret.staticAuthenticationData = selectedAuthKeyStaticAuthData_; @@ -694,7 +703,8 @@ Status Credential::setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) return Status::ok(); } -Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) { +Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey, + int64_t minValidTimeMillis) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); @@ -706,7 +716,7 @@ Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxU return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error loading data for credential"); } - data->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey); + data->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis); if (!data->saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving data"); @@ -777,11 +787,6 @@ Status Credential::storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& authenticationKey, int64_t expirationDateMillisSinceEpoch, const vector<uint8_t>& staticAuthData) { - if (halApiVersion_ < 3) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, - "Not implemented by HAL"); - } - if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); @@ -828,6 +833,29 @@ Status Credential::getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return return Status::ok(); } +Status Credential::getAuthenticationDataExpirations(vector<int64_t>* _aidl_return) { + if (halSessionBinder_) { + return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, + "Cannot be used with session"); + } + + sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); + if (!data->loadFromDisk()) { + LOG(ERROR) << "Error loading data for credential"; + return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, + "Error loading data for credential"); + } + const vector<AuthKeyData>& authKeyDatas = data->getAuthKeyDatas(); + vector<int64_t> ret; + ret.reserve(authKeyDatas.size()); + for (const AuthKeyData& authKeyData : authKeyDatas) { + // Note: value is INT64_MAX if expiration date is not set. + ret.push_back(authKeyData.expirationDateMillisSinceEpoch); + } + *_aidl_return = ret; + return Status::ok(); +} + optional<string> extractDocType(const vector<uint8_t>& credentialData) { auto [item, _ /* newPos */, message] = cppbor::parse(credentialData); if (item == nullptr) { @@ -887,8 +915,8 @@ Status Credential::update(sp<IWritableCredential>* _aidl_return) { dataPath_, credentialName_, docType.value(), true, hwInfo_, halWritableCredential); writableCredential->setAttestationCertificate(data->getAttestationCertificate()); - auto [keyCount, maxUsesPerKey] = data->getAvailableAuthenticationKeys(); - writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey); + auto [keyCount, maxUsesPerKey, minValidTimeMillis] = data->getAvailableAuthenticationKeys(); + writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis); // Because its data has changed, we need to replace the binder for the // IIdentityCredential when the credential has been updated... otherwise the diff --git a/identity/Credential.h b/identity/Credential.h index 0906fea2..4ecf92e0 100644 --- a/identity/Credential.h +++ b/identity/Credential.h @@ -78,7 +78,8 @@ class Credential : public BnCredential { bool allowUsingExpiredKeys, bool incrementUsageCount, GetEntriesResultParcel* _aidl_return) override; - Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) override; + Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey, + int64_t minValidTimeMillis) override; Status getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) override; Status storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey, const vector<uint8_t>& staticAuthData) override; @@ -87,6 +88,7 @@ class Credential : public BnCredential { int64_t expirationDateMillisSinceEpoch, const vector<uint8_t>& staticAuthData) override; Status getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) override; + Status getAuthenticationDataExpirations(vector<int64_t>* _aidl_return) override; Status update(sp<IWritableCredential>* _aidl_return) override; diff --git a/identity/CredentialData.cpp b/identity/CredentialData.cpp index 2189f909..1bf1527b 100644 --- a/identity/CredentialData.cpp +++ b/identity/CredentialData.cpp @@ -117,6 +117,7 @@ bool CredentialData::saveToDisk() const { map.add("entryData", std::move(encryptedBlobsMap)); map.add("authKeyCount", keyCount_); map.add("maxUsesPerAuthKey", maxUsesPerKey_); + map.add("minValidTimeMillis", minValidTimeMillis_); cppbor::Array authKeyDatasArray; for (const AuthKeyData& data : authKeyDatas_) { @@ -253,6 +254,7 @@ bool CredentialData::loadFromDisk() { authKeyDatas_.clear(); keyCount_ = 0; maxUsesPerKey_ = 1; + minValidTimeMillis_ = 0; optional<vector<uint8_t>> data = fileGetContents(fileName_); if (!data) { @@ -398,6 +400,14 @@ bool CredentialData::loadFromDisk() { return false; } maxUsesPerKey_ = number->value(); + + } else if (key == "minValidTimeMillis") { + const cppbor::Int* number = valueItem->asInt(); + if (number == nullptr) { + LOG(ERROR) << "Value for minValidTimeMillis is not a number"; + return false; + } + minValidTimeMillis_ = number->value(); } } @@ -479,9 +489,11 @@ optional<bool> CredentialData::credentialExists(const string& dataPath, uid_t ow // --- -void CredentialData::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { +void CredentialData::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey, + int64_t minValidTimeMillis) { keyCount_ = keyCount; maxUsesPerKey_ = maxUsesPerKey; + minValidTimeMillis_ = minValidTimeMillis; // If growing the number of auth keys (prevKeyCount < keyCount_ case) we'll add // new AuthKeyData structs to |authKeyDatas_| and each struct will have empty |certificate| @@ -499,8 +511,9 @@ const vector<AuthKeyData>& CredentialData::getAuthKeyDatas() const { return authKeyDatas_; } -pair<int /* keyCount */, int /*maxUsersPerKey */> CredentialData::getAvailableAuthenticationKeys() { - return std::make_pair(keyCount_, maxUsesPerKey_); +tuple<int /* keyCount */, int /*maxUsersPerKey */, int64_t /* minValidTimeMillis */> +CredentialData::getAvailableAuthenticationKeys() const { + return std::make_tuple(keyCount_, maxUsesPerKey_, minValidTimeMillis_); } AuthKeyData* CredentialData::findAuthKey_(bool allowUsingExhaustedKeys, @@ -568,14 +581,19 @@ CredentialData::getAuthKeysNeedingCertification(const sp<IIdentityCredential>& h vector<vector<uint8_t>> keysNeedingCert; - int64_t nowMilliSeconds = - std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) * 1000; + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + int64_t nowMilliseconds; + if (__builtin_mul_overflow(int64_t(now), int64_t(1000), &nowMilliseconds)) { + LOG(ERROR) << "Overflow converting " << now << " to milliseconds"; + return {}; + } for (AuthKeyData& data : authKeyDatas_) { bool keyExceedUseCount = (data.useCount >= maxUsesPerKey_); - bool keyBeyondExpirationDate = (nowMilliSeconds > data.expirationDateMillisSinceEpoch); + int64_t expirationDateAdjusted = data.expirationDateMillisSinceEpoch - minValidTimeMillis_; + bool keyBeyondAdjustedExpirationDate = (nowMilliseconds > expirationDateAdjusted); bool newKeyNeeded = - (data.certificate.size() == 0) || keyExceedUseCount || keyBeyondExpirationDate; + (data.certificate.size() == 0) || keyExceedUseCount || keyBeyondAdjustedExpirationDate; bool certificationPending = (data.pendingCertificate.size() > 0); if (newKeyNeeded && !certificationPending) { vector<uint8_t> signingKeyBlob; diff --git a/identity/CredentialData.h b/identity/CredentialData.h index e240e473..3f7cd3af 100644 --- a/identity/CredentialData.h +++ b/identity/CredentialData.h @@ -37,7 +37,6 @@ using ::android::hardware::identity::IIdentityCredential; using ::android::hardware::identity::SecureAccessControlProfile; using ::std::map; using ::std::optional; -using ::std::pair; using ::std::string; using ::std::tuple; using ::std::vector; @@ -89,7 +88,8 @@ class CredentialData : public RefBase { bool deleteCredential(); - void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); + void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey, + int64_t minValidTimeMillis); // Getters @@ -107,7 +107,8 @@ class CredentialData : public RefBase { const vector<AuthKeyData>& getAuthKeyDatas() const; - pair<int /* keyCount */, int /*maxUsersPerKey */> getAvailableAuthenticationKeys(); + tuple<int /* keyCount */, int /*maxUsersPerKey */, int64_t /* minValidTimeMillis */> + getAvailableAuthenticationKeys() const; // Returns |nullptr| if a suitable key cannot be found. Otherwise returns // the authentication and increases its use-count. @@ -143,6 +144,7 @@ class CredentialData : public RefBase { int keyCount_ = 0; int maxUsesPerKey_ = 1; + int64_t minValidTimeMillis_ = 0; vector<AuthKeyData> authKeyDatas_; // Always |keyCount_| long. }; diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp index c5c429b7..cb2e8c7f 100644 --- a/identity/CredentialStore.cpp +++ b/identity/CredentialStore.cpp @@ -22,10 +22,10 @@ #include <android-base/logging.h> #include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> #include <android/hardware/security/keymint/RpcHardwareInfo.h> -#include <android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.h> -#include <android/security/remoteprovisioning/RemotelyProvisionedKey.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> +#include <rkp/support/rkpd_client.h> +#include <vintf/VintfObject.h> #include "Credential.h" #include "CredentialData.h" @@ -39,42 +39,8 @@ namespace security { namespace identity { namespace { -using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; -using ::android::hardware::security::keymint::RpcHardwareInfo; -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; -using ::android::security::remoteprovisioning::RemotelyProvisionedKey; - -std::optional<std::string> -getRemotelyProvisionedComponentId(const sp<IIdentityCredentialStore>& hal) { - auto init = [](const sp<IIdentityCredentialStore>& hal) -> std::optional<std::string> { - sp<IRemotelyProvisionedComponent> remotelyProvisionedComponent; - Status status = hal->getRemotelyProvisionedComponent(&remotelyProvisionedComponent); - if (!status.isOk()) { - LOG(ERROR) << "Error getting remotely provisioned component: " << status; - return std::nullopt; - } - - RpcHardwareInfo rpcHwInfo; - status = remotelyProvisionedComponent->getHardwareInfo(&rpcHwInfo); - if (!status.isOk()) { - LOG(ERROR) << "Error getting remotely provisioned component hardware info: " << status; - return std::nullopt; - } - - if (!rpcHwInfo.uniqueId) { - LOG(ERROR) << "Remotely provisioned component is missing a unique id, which is " - << "required for credential key remotely provisioned attestation keys. " - << "This is a bug in the vendor implementation."; - return std::nullopt; - } - - // This id is required to later fetch remotely provisioned attestation keys. - return *rpcHwInfo.uniqueId; - }; - - static std::optional<std::string> id = init(hal); - return id; -} +using ::android::security::rkp::RemotelyProvisionedKey; +using ::android::security::rkp::support::getRpcKey; } // namespace @@ -90,11 +56,9 @@ bool CredentialStore::init() { halApiVersion_ = hal_->getInterfaceVersion(); if (hwInfo_.isRemoteKeyProvisioningSupported) { - keyPool_ = android::waitForService<IRemotelyProvisionedKeyPool>( - IRemotelyProvisionedKeyPool::descriptor); - if (keyPool_.get() == nullptr) { - LOG(ERROR) << "Error getting IRemotelyProvisionedKeyPool HAL with service name '" - << IRemotelyProvisionedKeyPool::descriptor << "'"; + status = hal_->getRemotelyProvisionedComponent(&rpc_); + if (!status.isOk()) { + LOG(ERROR) << "Error getting remotely provisioned component: " << status; return false; } } @@ -102,7 +66,9 @@ bool CredentialStore::init() { LOG(INFO) << "Connected to Identity Credential HAL with API version " << halApiVersion_ << " and name '" << hwInfo_.credentialStoreName << "' authored by '" << hwInfo_.credentialStoreAuthorName << "' with chunk size " << hwInfo_.dataChunkSize - << " and directoAccess set to " << (hwInfo_.isDirectAccess ? "true" : "false"); + << " directoAccess set to " << (hwInfo_.isDirectAccess ? "true" : "false") + << " and remote key provisioning support " + << (hwInfo_.isRemoteKeyProvisioningSupported ? "enabled" : "disabled"); return true; } @@ -148,7 +114,9 @@ Status CredentialStore::createCredential(const std::string& credentialName, if (hwInfo_.isRemoteKeyProvisioningSupported) { status = setRemotelyProvisionedAttestationKey(halWritableCredential.get()); if (!status.isOk()) { - return halStatusToGenericError(status); + LOG(WARNING) << status.toString8() + << "\nUnable to fetch remotely provisioned attestation key, falling back " + << "to the factory-provisioned attestation key."; } } @@ -209,28 +177,32 @@ Status CredentialStore::createPresentationSession(int32_t cipherSuite, sp<ISessi Status CredentialStore::setRemotelyProvisionedAttestationKey( IWritableIdentityCredential* halWritableCredential) { - std::optional<std::string> rpcId = getRemotelyProvisionedComponentId(hal_); - if (!rpcId) { - return Status::fromServiceSpecificError(ERROR_GENERIC, - "Error getting remotely provisioned component id"); - } + std::vector<uint8_t> keyBlob; + std::vector<uint8_t> encodedCertChain; + Status status; + + LOG(INFO) << "Fetching attestation key from RKPD"; uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); - RemotelyProvisionedKey key; - Status status = keyPool_->getAttestationKey(callingUid, *rpcId, &key); - if (!status.isOk()) { - LOG(WARNING) << "Unable to fetch remotely provisioned attestation key, falling back " - << "to the factory-provisioned attestation key."; - return Status::ok(); + std::optional<RemotelyProvisionedKey> key = getRpcKey(rpc_, callingUid); + if (!key) { + return Status::fromServiceSpecificError( + ERROR_GENERIC, "Failed to get remotely provisioned attestation key"); } - status = halWritableCredential->setRemotelyProvisionedAttestationKey(key.keyBlob, - key.encodedCertChain); + if (key->keyBlob.empty()) { + return Status::fromServiceSpecificError( + ERROR_GENERIC, "Remotely provisioned attestation key blob is empty"); + } + + keyBlob = std::move(key->keyBlob); + encodedCertChain = std::move(key->encodedCertChain); + + status = halWritableCredential->setRemotelyProvisionedAttestationKey(keyBlob, encodedCertChain); if (!status.isOk()) { LOG(ERROR) << "Error setting remotely provisioned attestation key on credential"; return status; } - return Status::ok(); } diff --git a/identity/CredentialStore.h b/identity/CredentialStore.h index df7928e5..8bc02e8e 100644 --- a/identity/CredentialStore.h +++ b/identity/CredentialStore.h @@ -22,7 +22,6 @@ #include <android/hardware/identity/IIdentityCredentialStore.h> #include <android/security/identity/BnCredentialStore.h> -#include <android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.h> namespace android { namespace security { @@ -39,7 +38,7 @@ using ::android::hardware::identity::HardwareInformation; using ::android::hardware::identity::IIdentityCredentialStore; using ::android::hardware::identity::IPresentationSession; using ::android::hardware::identity::IWritableIdentityCredential; -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; +using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; class CredentialStore : public BnCredentialStore { public: @@ -73,9 +72,9 @@ class CredentialStore : public BnCredentialStore { sp<IIdentityCredentialStore> hal_; int halApiVersion_; - sp<IRemotelyProvisionedKeyPool> keyPool_; - HardwareInformation hwInfo_; + + sp<IRemotelyProvisionedComponent> rpc_; }; } // namespace identity diff --git a/identity/WritableCredential.cpp b/identity/WritableCredential.cpp index 9827d754..d863494e 100644 --- a/identity/WritableCredential.cpp +++ b/identity/WritableCredential.cpp @@ -101,9 +101,11 @@ void WritableCredential::setAttestationCertificate(const vector<uint8_t>& attest attestationCertificate_ = attestationCertificate; } -void WritableCredential::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { +void WritableCredential::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey, + int64_t minValidTimeMillis) { keyCount_ = keyCount; maxUsesPerKey_ = maxUsesPerKey; + minValidTimeMillis_ = minValidTimeMillis; } ssize_t WritableCredential::calcExpectedProofOfProvisioningSize( @@ -260,7 +262,7 @@ WritableCredential::personalize(const vector<AccessControlProfileParcel>& access } data.setCredentialData(credentialData); - data.setAvailableAuthenticationKeys(keyCount_, maxUsesPerKey_); + data.setAvailableAuthenticationKeys(keyCount_, maxUsesPerKey_, minValidTimeMillis_); if (!data.saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, diff --git a/identity/WritableCredential.h b/identity/WritableCredential.h index 838b9564..c92d58ab 100644 --- a/identity/WritableCredential.h +++ b/identity/WritableCredential.h @@ -45,7 +45,8 @@ class WritableCredential : public BnWritableCredential { // Used when updating a credential void setAttestationCertificate(const vector<uint8_t>& attestationCertificate); - void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); + void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey, + int64_t minValidTimeMillis); // Used by Credential::update() void setCredentialToReloadWhenUpdated(sp<Credential> credential); @@ -69,6 +70,7 @@ class WritableCredential : public BnWritableCredential { vector<uint8_t> attestationCertificate_; int keyCount_ = 0; int maxUsesPerKey_ = 1; + int64_t minValidTimeMillis_ = 0; sp<Credential> credentialToReloadWhenUpdated_; diff --git a/identity/binder/android/security/identity/GetEntriesResultParcel.aidl b/identity/binder/android/security/identity/GetEntriesResultParcel.aidl index 03b363c9..51281b9e 100644 --- a/identity/binder/android/security/identity/GetEntriesResultParcel.aidl +++ b/identity/binder/android/security/identity/GetEntriesResultParcel.aidl @@ -26,4 +26,5 @@ parcelable GetEntriesResultParcel { byte[] deviceNameSpaces; byte[] mac; byte[] staticAuthenticationData; + byte[] signature; // Added in Android 14 / U } diff --git a/identity/binder/android/security/identity/ICredential.aidl b/identity/binder/android/security/identity/ICredential.aidl index e6a9fae0..875b9341 100644 --- a/identity/binder/android/security/identity/ICredential.aidl +++ b/identity/binder/android/security/identity/ICredential.aidl @@ -60,7 +60,9 @@ interface ICredential { in boolean allowUsingExpiredKeys, in boolean incrementUsageCount); - void setAvailableAuthenticationKeys(in int keyCount, in int maxUsesPerKey); + void setAvailableAuthenticationKeys(in int keyCount, + in int maxUsesPerKey, + in long minValidTimeMillis); AuthKeyParcel[] getAuthKeysNeedingCertification(); @@ -73,6 +75,8 @@ interface ICredential { int[] getAuthenticationDataUsageCount(); + long[] getAuthenticationDataExpirations(); + IWritableCredential update(); } diff --git a/identity/main.cpp b/identity/main.cpp index 25597898..b3a41eca 100644 --- a/identity/main.cpp +++ b/identity/main.cpp @@ -23,6 +23,7 @@ #include <android-base/logging.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> +#include <binder/ProcessState.h> #include "CredentialStoreFactory.h" @@ -32,6 +33,7 @@ using ::std::string; using ::android::IPCThreadState; using ::android::IServiceManager; +using ::android::ProcessState; using ::android::sp; using ::android::String16; using ::android::base::InitLogging; @@ -53,8 +55,10 @@ int main(int argc, char* argv[]) { CHECK(ret == ::android::OK) << "Couldn't register binder service"; LOG(INFO) << "Registered binder service"; - // Credstore is a single-threaded process. So devote the main thread - // to handling binder messages. + // Credstore needs one thread to handle binder messages and one to handle + // asynchronous responses from RKPD. + ProcessState::self()->setThreadPoolMaxThreadCount(2); + ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0; diff --git a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java index 6da90e52..b47009be 100644 --- a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java +++ b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java @@ -145,14 +145,11 @@ public class Iso18013 { // encoded DeviceEngagement ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); baos.write(new byte[]{41}); - baos.write(x); - baos.write(y); + + ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); + baos.write(Util.convertP256PublicKeyToDERFormat(w)); + baos.write(new byte[]{42, 44}); } catch (IOException e) { e.printStackTrace(); @@ -279,18 +276,4 @@ public class Iso18013 { throw new IllegalStateException("Error performing key agreement", e); } } - - private static byte[] stripLeadingZeroes(byte[] value) { - int n = 0; - while (n < value.length && value[n] == 0) { - n++; - } - int newLen = value.length - n; - byte[] ret = new byte[newLen]; - int m = 0; - while (n < value.length) { - ret[m++] = value[n++]; - } - return ret; - } } diff --git a/identity/util/src/java/com/android/security/identity/internal/Util.java b/identity/util/src/java/com/android/security/identity/internal/Util.java index 4ec54a72..ee12cd07 100644 --- a/identity/util/src/java/com/android/security/identity/internal/Util.java +++ b/identity/util/src/java/com/android/security/identity/internal/Util.java @@ -1130,6 +1130,48 @@ Certificate: Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString()); } + // Convert EC P256 public key to DER format binary format + public static byte[] convertP256PublicKeyToDERFormat(ECPoint w) { + byte[] ret = new byte[64]; + + // Each coordinate may be encoded in 33*, 32, or fewer bytes. + // + // * : it can be 33 bytes because toByteArray() guarantees "The array will contain the + // minimum number of bytes required to represent this BigInteger, including at + // least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that + // the MSB is always 0x00. This is taken care of by calling calling + // stripLeadingZeroes(). + // + // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2 + // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y + // where X and Y are encoded in exactly 32 byte, big endian integer values each. + // + byte[] xBytes = stripLeadingZeroes(w.getAffineX().toByteArray()); + if (xBytes.length > 32) { + throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected"); + } + int numLeadingZeroBytes = 32 - xBytes.length; + for (int n = 0; n < numLeadingZeroBytes; n++) { + ret[n] = 0x00; + } + for (int n = 0; n < xBytes.length; n++) { + ret[numLeadingZeroBytes + n] = xBytes[n]; + } + + byte[] yBytes = stripLeadingZeroes(w.getAffineY().toByteArray()); + if (yBytes.length > 32) { + throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected"); + } + numLeadingZeroBytes = 32 - yBytes.length; + for (int n = 0; n < numLeadingZeroBytes; n++) { + ret[32 + n] = 0x00; + } + for (int n = 0; n < yBytes.length; n++) { + ret[32 + numLeadingZeroBytes + n] = yBytes[n]; + } + + return ret; + } // This returns a SessionTranscript which satisfy the requirement // that the uncompressed X and Y coordinates of the public key for the @@ -1141,14 +1183,11 @@ Certificate: // encoded DeviceEngagement ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); baos.write(new byte[]{42}); - baos.write(x); - baos.write(y); + + ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); + baos.write(convertP256PublicKeyToDERFormat(w)); + baos.write(new byte[]{43, 44}); } catch (IOException e) { e.printStackTrace(); diff --git a/keystore-engine/keystore2_engine.cpp b/keystore-engine/keystore2_engine.cpp index 69caf510..dc90be30 100644 --- a/keystore-engine/keystore2_engine.cpp +++ b/keystore-engine/keystore2_engine.cpp @@ -146,11 +146,13 @@ bssl::UniquePtr<EVP_PKEY> wrap_rsa(std::shared_ptr<Keystore2KeyBackend> key_back return nullptr; } - rsa->n = BN_dup(public_rsa->n); - rsa->e = BN_dup(public_rsa->e); - if (rsa->n == nullptr || rsa->e == nullptr) { + bssl::UniquePtr<BIGNUM> n(BN_dup(RSA_get0_n(public_rsa))); + bssl::UniquePtr<BIGNUM> e(BN_dup(RSA_get0_e(public_rsa))); + if (n == nullptr || e == nullptr || !RSA_set0_key(rsa.get(), n.get(), e.get(), nullptr)) { return nullptr; } + OWNERSHIP_TRANSFERRED(n); + OWNERSHIP_TRANSFERRED(e); bssl::UniquePtr<EVP_PKEY> result(EVP_PKEY_new()); if (result.get() == nullptr || !EVP_PKEY_assign_RSA(result.get(), rsa.get())) { @@ -420,19 +422,19 @@ extern "C" EVP_PKEY* EVP_PKEY_from_keystore2(const char* key_id) { Keystore2KeyBackend{response.metadata.key, response.iSecurityLevel}); bssl::UniquePtr<EVP_PKEY> result; - switch (EVP_PKEY_type(pkey->type)) { + switch (EVP_PKEY_id(pkey.get())) { case EVP_PKEY_RSA: { - bssl::UniquePtr<RSA> public_rsa(EVP_PKEY_get1_RSA(pkey.get())); - result = wrap_rsa(key_backend, public_rsa.get()); + RSA* public_rsa = EVP_PKEY_get0_RSA(pkey.get()); + result = wrap_rsa(key_backend, public_rsa); break; } case EVP_PKEY_EC: { - bssl::UniquePtr<EC_KEY> public_ecdsa(EVP_PKEY_get1_EC_KEY(pkey.get())); - result = wrap_ecdsa(key_backend, public_ecdsa.get()); + EC_KEY* public_ecdsa = EVP_PKEY_get0_EC_KEY(pkey.get()); + result = wrap_ecdsa(key_backend, public_ecdsa); break; } default: - LOG(ERROR) << AT << "Unsupported key type " << EVP_PKEY_type(pkey->type); + LOG(ERROR) << AT << "Unsupported key type " << EVP_PKEY_id(pkey.get()); return nullptr; } diff --git a/keystore/Android.bp b/keystore/Android.bp index 892c5b41..221ead9b 100644 --- a/keystore/Android.bp +++ b/keystore/Android.bp @@ -31,7 +31,6 @@ cc_defaults { ], }, - clang: true, } cc_binary { diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp index d01c67d4..ab3e22c3 100644 --- a/keystore/keystore_cli_v2.cpp +++ b/keystore/keystore_cli_v2.cpp @@ -59,6 +59,16 @@ struct TestCase { constexpr const char keystore2_service_name[] = "android.system.keystore2.IKeystoreService/default"; +std::string string_replace_all(std::string str, const std::string& from, + const std::string& to) { + size_t start = 0; + while ((start = str.find(from, start)) != std::string::npos) { + str.replace(start, from.length(), to); + start += to.length(); + } + return str; +} + int unwrapError(const ndk::ScopedAStatus& status) { if (status.isOk()) return 0; if (status.getExceptionCode() == EX_SERVICE_SPECIFIC) { @@ -1028,7 +1038,8 @@ int Confirmation(const std::string& promptText, const std::string& extraDataHex, auto listener = ndk::SharedRefBase::make<ConfirmationListener>(); auto future = listener->get_future(); - auto rc = apcService->presentPrompt(listener, promptText, extraData, locale, uiOptionsAsFlags); + auto rc = apcService->presentPrompt(listener, string_replace_all(promptText, "\\n", "\n"), + extraData, locale, uiOptionsAsFlags); if (!rc.isOk()) { std::cerr << "Presenting confirmation prompt failed: " << rc.getDescription() << std::endl; diff --git a/keystore/tests/fuzzer/keystoreCommon.h b/keystore/tests/fuzzer/keystoreCommon.h index 7af3ba8c..e1265bf3 100644 --- a/keystore/tests/fuzzer/keystoreCommon.h +++ b/keystore/tests/fuzzer/keystoreCommon.h @@ -71,7 +71,7 @@ inline PackageInfoData initPackageInfoData(FuzzedDataProvider* fdp) { } } packageInfoData.sharedSignaturesVector = - make_shared<KeyAttestationPackageInfo::SignaturesVector>(move(signatureVector)); + make_shared<KeyAttestationPackageInfo::SignaturesVector>(std::move(signatureVector)); return packageInfoData; } #endif // KEYSTORECOMMON_H diff --git a/keystore2/Android.bp b/keystore2/Android.bp index e6cb4fb5..4084aced 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -27,6 +27,7 @@ rust_defaults { srcs: ["src/lib.rs"], defaults: [ "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", ], rustlibs: [ @@ -38,8 +39,7 @@ rust_defaults { "android.security.compat-rust", "android.security.maintenance-rust", "android.security.metrics-rust", - "android.security.remoteprovisioning-rust", - "android.system.keystore2-V2-rust", + "android.security.rkp_aidl-rust", "libanyhow", "libbinder_rs", "libkeystore2_aaid-rust", @@ -57,6 +57,7 @@ rust_defaults { "libserde", "libserde_cbor", "libthiserror", + "libtokio", ], shared_libs: [ "libcutils", @@ -79,9 +80,11 @@ rust_library { name: "libkeystore2_test_utils", crate_name: "keystore2_test_utils", srcs: ["test_utils/lib.rs"], - defaults: ["keymint_use_latest_hal_aidl_rust"], + defaults: [ + "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", + ], rustlibs: [ - "android.system.keystore2-V2-rust", "libbinder_rs", "libkeystore2_selinux", "liblog_rust", @@ -89,6 +92,8 @@ rust_library { "librand", "libserde", "libserde_cbor", + "libthiserror", + "libanyhow", ], } @@ -108,13 +113,15 @@ rust_library { rust_test { name: "keystore2_test_utils_test", srcs: ["test_utils/lib.rs"], - defaults: ["keymint_use_latest_hal_aidl_rust"], + defaults: [ + "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", + ], test_suites: ["general-tests"], require_root: true, auto_gen_config: true, compile_multilib: "first", rustlibs: [ - "android.system.keystore2-V2-rust", "libbinder_rs", "libkeystore2_selinux", "liblog_rust", @@ -122,6 +129,8 @@ rust_test { "librand", "libserde", "libserde_cbor", + "libthiserror", + "libanyhow", ], } @@ -145,6 +154,7 @@ rust_test { "watchdog", "keystore2_blob_test_utils", ], + require_root: true, } rust_defaults { diff --git a/keystore2/aaid/lib.rs b/keystore2/aaid/lib.rs index 3187198c..8f6a09e5 100644 --- a/keystore2/aaid/lib.rs +++ b/keystore2/aaid/lib.rs @@ -29,7 +29,7 @@ pub fn get_aaid(uid: u32) -> Result<Vec<u8>, u32> { // in the second pointer argument. let status = unsafe { aaid_keystore_attestation_id(uid, buffer.as_mut_ptr(), &mut size) }; match status { - 0 => Ok(buffer[0..size as usize].to_vec()), + 0 => Ok(buffer[0..size].to_vec()), status => Err(status), } } diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp index ae08567d..8f5c13b0 100644 --- a/keystore2/aidl/Android.bp +++ b/keystore2/aidl/Android.bp @@ -24,7 +24,7 @@ package { aidl_interface { name: "android.security.attestationmanager", srcs: [ "android/security/attestationmanager/*.aidl", ], - imports: [ "android.hardware.security.keymint-V2" ], + imports: [ "android.hardware.security.keymint-V3" ], unstable: true, backend: { java: { @@ -44,7 +44,7 @@ aidl_interface { name: "android.security.authorization", srcs: [ "android/security/authorization/*.aidl" ], imports: [ - "android.hardware.security.keymint-V2", + "android.hardware.security.keymint-V3", "android.hardware.security.secureclock-V1", ], unstable: true, @@ -83,7 +83,7 @@ aidl_interface { name: "android.security.compat", srcs: [ "android/security/compat/*.aidl" ], imports: [ - "android.hardware.security.keymint-V2", + "android.hardware.security.keymint-V3", "android.hardware.security.secureclock-V1", "android.hardware.security.sharedsecret-V1", ], @@ -103,31 +103,10 @@ aidl_interface { } aidl_interface { - name: "android.security.remoteprovisioning", - srcs: [ "android/security/remoteprovisioning/*.aidl" ], - imports: [ - "android.hardware.security.keymint-V2", - ], - unstable: true, - backend: { - java: { - platform_apis: true, - }, - ndk: { - enabled: true, - apps_enabled: false, - }, - rust: { - enabled: true, - }, - }, -} - -aidl_interface { name: "android.security.maintenance", srcs: [ "android/security/maintenance/*.aidl" ], imports: [ - "android.system.keystore2-V2", + "android.system.keystore2-V3", ], unstable: true, backend: { @@ -166,7 +145,7 @@ aidl_interface { name: "android.security.metrics", srcs: [ "android/security/metrics/*.aidl" ], imports: [ - "android.system.keystore2-V2", + "android.system.keystore2-V3", ], unstable: true, backend: { @@ -183,19 +162,68 @@ aidl_interface { }, } +// java_defaults that includes the latest Keystore2 AIDL library. +// Modules that depend on KeyMint directly can include this java_defaults to avoid +// managing dependency versions explicitly. +java_defaults { + name: "keystore2_use_latest_aidl_java_static", + static_libs: [ + "android.system.keystore2-V3-java-source" + ], +} + +java_defaults { + name: "keystore2_use_latest_aidl_java_shared", + libs: [ + "android.system.keystore2-V3-java-source" + ], +} + +java_defaults { + name: "keystore2_use_latest_aidl_java", + libs: [ + "android.system.keystore2-V3-java" + ], +} + // cc_defaults that includes the latest Keystore2 AIDL library. // Modules that depend on KeyMint directly can include this cc_defaults to avoid // managing dependency versions explicitly. cc_defaults { name: "keystore2_use_latest_aidl_ndk_static", static_libs: [ - "android.system.keystore2-V2-ndk", + "android.system.keystore2-V3-ndk", ], } cc_defaults { name: "keystore2_use_latest_aidl_ndk_shared", shared_libs: [ - "android.system.keystore2-V2-ndk", + "android.system.keystore2-V3-ndk", + ], +} + +cc_defaults { + name: "keystore2_use_latest_aidl_cpp_shared", + shared_libs: [ + "android.system.keystore2-V3-cpp", + ], +} + +cc_defaults { + name: "keystore2_use_latest_aidl_cpp_static", + static_libs: [ + "android.system.keystore2-V3-cpp", + ], +} + + +// A rust_defaults that includes the latest Keystore2 AIDL library. +// Modules that depend on Keystore2 directly can include this rust_defaults to avoid +// managing dependency versions explicitly. +rust_defaults { + name: "keystore2_use_latest_aidl_rust", + rustlibs: [ + "android.system.keystore2-V3-rust", ], } diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl index 3f334317..e3b7d11d 100644 --- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl +++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl @@ -41,8 +41,26 @@ interface IKeystoreAuthorization { /** * Unlocks the keystore for the given user id. + * * Callers require 'Unlock' permission. - * If a password was set, a password must be given on unlock or the operation fails. + * + * Super-Encryption Key: + * When the device is unlocked (and password is non-null), Keystore stores in memory + * a super-encryption key derived from the password that protects UNLOCKED_DEVICE_REQUIRED + * keys; this key is wiped from memory when the device is locked. + * + * If unlockingSids is non-empty on lock, then before the super-encryption key is wiped from + * memory, a copy of it is stored in memory encrypted with a fresh AES key. This key is then + * imported into KM, tagged such that it can be used given a valid, recent auth token for any + * of the unlockingSids. + * + * Options for unlock: + * - If the password is non-null, the super-encryption key is re-derived as above. + * - If the password is null, then if a suitable auth token to access the encrypted + * Super-encryption key stored in KM has been sent to keystore (via addAuthToken), the + * encrypted super-encryption key is recovered so that UNLOCKED_DEVICE_REQUIRED keys can + * be used once again. + * - If neither of these are met, then the operation fails. * * ## Error conditions: * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'Unlock' permission. @@ -50,33 +68,10 @@ interface IKeystoreAuthorization { * `ResponseCode::VALUE_CORRUPTED` - if the super key can not be decrypted. * `ResponseCode::KEY_NOT_FOUND` - if the super key is not found. * - * @lockScreenEvent - Indicates what happened. - * * LockScreenEvent.UNLOCK if the screen was unlocked. - * * LockScreenEvent.LOCK if the screen was locked. - * - * @param userId - Android user id - * - * @param password - synthetic password derived by the user denoted by the user id - * - * @param unlockingSids - list of biometric SIDs for this user. This will be null when - * lockScreenEvent is UNLOCK, but may be non-null when - * lockScreenEvent is LOCK. - * - * When the device is unlocked, Keystore stores in memory - * a super-encryption key that protects UNLOCKED_DEVICE_REQUIRED - * keys; this key is wiped from memory when the device is locked. - * - * If unlockingSids is non-empty on lock, then before the - * super-encryption key is wiped from memory, a copy of it - * is stored in memory encrypted with a fresh AES key. - * This key is then imported into KM, tagged such that it can be - * used given a valid, recent auth token for any of the - * unlockingSids. - * - * Then, when the device is unlocked again, if a suitable auth token - * has been sent to keystore, it is used to recover the - * super-encryption key, so that UNLOCKED_DEVICE_REQUIRED keys can - * be used once again. + * @param lockScreenEvent whether the lock screen locked or unlocked + * @param userId android user id + * @param password synthetic password derived from the user's LSKF, must be null on lock + * @param unlockingSids list of biometric SIDs for this user, ignored on unlock */ void onLockScreenEvent(in LockScreenEvent lockScreenEvent, in int userId, in @nullable byte[] password, in @nullable long[] unlockingSids); diff --git a/keystore2/aidl/android/security/metrics/AtomID.aidl b/keystore2/aidl/android/security/metrics/AtomID.aidl index 166e7538..3043ed3a 100644 --- a/keystore2/aidl/android/security/metrics/AtomID.aidl +++ b/keystore2/aidl/android/security/metrics/AtomID.aidl @@ -23,7 +23,7 @@ package android.security.metrics; @Backing(type="int") enum AtomID { STORAGE_STATS = 10103, - RKP_POOL_STATS = 10104, + // reserved 10104 KEY_CREATION_WITH_GENERAL_INFO = 10118, KEY_CREATION_WITH_AUTH_INFO = 10119, KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO = 10120, @@ -32,4 +32,4 @@ enum AtomID { KEY_OPERATION_WITH_GENERAL_INFO = 10123, RKP_ERROR_STATS = 10124, CRASH_STATS = 10125, -}
\ No newline at end of file +} diff --git a/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl b/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl index 266267ac..843e80b2 100644 --- a/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl +++ b/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl @@ -22,7 +22,7 @@ import android.security.metrics.KeystoreAtomPayload; * Encapsulates a particular atom object of type KeystoreAtomPayload its count. Note that * the field: count is only relevant for the atom types that are stored in the * in-memory metrics store. E.g. count field is not relevant for the atom types such as StorageStats - * and RkpPoolStats that are not stored in the metrics store. + * that are not stored in the metrics store. * @hide */ @RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) diff --git a/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl b/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl index a3e4dd68..2f89a2d1 100644 --- a/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl +++ b/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl @@ -24,14 +24,12 @@ import android.security.metrics.KeyOperationWithPurposeAndModesInfo; import android.security.metrics.StorageStats; import android.security.metrics.Keystore2AtomWithOverflow; import android.security.metrics.RkpErrorStats; -import android.security.metrics.RkpPoolStats; import android.security.metrics.CrashStats; /** @hide */ @RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) union KeystoreAtomPayload { StorageStats storageStats; - RkpPoolStats rkpPoolStats; KeyCreationWithGeneralInfo keyCreationWithGeneralInfo; KeyCreationWithAuthInfo keyCreationWithAuthInfo; KeyCreationWithPurposeAndModesInfo keyCreationWithPurposeAndModesInfo; @@ -40,4 +38,4 @@ union KeystoreAtomPayload { KeyOperationWithGeneralInfo keyOperationWithGeneralInfo; RkpErrorStats rkpErrorStats; CrashStats crashStats; -}
\ No newline at end of file +} diff --git a/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl b/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl deleted file mode 100644 index 016b6ff3..00000000 --- a/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package android.security.metrics; - -import android.security.metrics.SecurityLevel; - -/** - * Count of keys in the attestation key pool related to Remote Key Provisioning (RKP). - * @hide - */ -@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) -parcelable RkpPoolStats { - SecurityLevel security_level; - int expiring; - int unassigned; - int attested; - int total; -}
\ No newline at end of file diff --git a/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl b/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl deleted file mode 100644 index 3528b423..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -package android.security.remoteprovisioning; - -/** - * This parcelable provides information about the state of the attestation key pool. - * @hide - */ -parcelable AttestationPoolStatus { - /** - * The number of signed attestation certificate chains which will expire when the date provided - * to keystore to check against is reached. - */ - int expiring; - /** - * The number of signed attestation certificate chains which have not yet been assigned to an - * app. This should be less than or equal to signed keys. The remainder of `signed` - - * `unassigned` gives the number of signed keys that have been assigned to an app. - */ - int unassigned; - /** - * The number of signed attestation keys. This should be less than or equal to `total`. The - * remainder of `total` - `attested` gives the number of keypairs available to be sent off to - * the server for signing. - */ - int attested; - /** - * The total number of attestation keys. - */ - int total; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl deleted file mode 100644 index ecdc7901..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.security.remoteprovisioning; - -import android.hardware.security.keymint.DeviceInfo; -import android.hardware.security.keymint.ProtectedData; -import android.hardware.security.keymint.SecurityLevel; -import android.security.remoteprovisioning.AttestationPoolStatus; -import android.security.remoteprovisioning.ImplInfo; - -/** - * `IRemoteProvisioning` is the interface provided to use the remote provisioning functionality - * provided through KeyStore. The intent is for a higher level system component to use these - * functions in order to drive the process through which the device can receive functioning - * attestation certificates. - * - * ## Error conditions - * Error conditions are reported as service specific errors. - * Positive codes correspond to `android.security.remoteprovisioning.ResponseCode` - * and indicate error conditions diagnosed by the Keystore 2.0 service. - * TODO: Remote Provisioning HAL error code info - * - * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permissions - * to use the RemoteProvisioning API. This permission is defined under access_vectors in SEPolicy - * in the keystore2 class: remotely_provision - * - * `ResponseCode::SYSTEM_ERROR` for any unexpected errors like IO or IPC failures. - * - * @hide - */ -interface IRemoteProvisioning { - - /** - * Returns the status of the attestation key pool in the database. - * - * @param expiredBy The date as seconds since epoch by which to judge expiration status of - * certificates. - * - * @param secLevel The security level to specify which KM instance to get the pool for. - * - * @return The `AttestationPoolStatus` parcelable contains fields communicating information - * relevant to making decisions about when to generate and provision - * more attestation keys. - */ - AttestationPoolStatus getPoolStatus(in long expiredBy, in SecurityLevel secLevel); - - /** - * This is the primary entry point for beginning a remote provisioning flow. The caller - * specifies how many CSRs should be generated and provides an X25519 ECDH public key along - * with a challenge to encrypt privacy sensitive portions of the returned CBOR blob and - * guarantee freshness of the request to the certifying third party. - * - * ## Error conditions - * `ResponseCode::NO_UNSIGNED_KEYS` if there are no unsigned keypairs in the database that can - * be used for the CSRs. - * - * A RemoteProvisioning HAL response code may indicate backend errors such as failed EEK - * verification. - * - * @param testMode Whether or not the TA implementing the Remote Provisioning HAL should accept - * any EEK (Endpoint Encryption Key), or only one signed by a chain - * that verifies back to the Root of Trust baked into the TA. True - * means that any key is accepted. - * - * @param numCsr How many certificate signing requests should be generated. - * - * @param eek A chain of certificates terminating in an X25519 public key, the Endpoint - * Encryption Key. - * - * @param challenge A challenge to be included and MACed in the returned CBOR blob. - * - * @param secLevel The security level to specify which KM instance from which to generate a - * CSR. - * - * @param protectedData The encrypted CBOR blob generated by the remote provisioner - * - * @return A CBOR blob composed of various elements required by the server to verify the - * request. - */ - byte[] generateCsr(in boolean testMode, in int numCsr, in byte[] eek, in byte[] challenge, - in SecurityLevel secLevel, out ProtectedData protectedData, out DeviceInfo deviceInfo); - - /** - * This method provides a way for the returned attestation certificate chains to be provisioned - * to the attestation key database. When an app requests an attesation key, it will be assigned - * one of these certificate chains along with the corresponding private key. - * - * @param publicKey The raw public key encoded in the leaf certificate. - * - * @param batchCert The batch certificate corresponding to the attestation key. Separated for - * the purpose of making Subject lookup for KM attestation easier. - * - * @param certs An X.509, DER encoded certificate chain for the attestation key. - * - * @param expirationDate The expiration date on the certificate chain, provided by the caller - * for convenience. - * - * @param secLevel The security level representing the KM instance containing the key that this - * chain corresponds to. - */ - void provisionCertChain(in byte[] publicKey, in byte[] batchCert, in byte[] certs, - in long expirationDate, in SecurityLevel secLevel); - - /** - * This method allows the caller to instruct KeyStore to generate and store a key pair to be - * used for attestation in the `generateCsr` method. The caller should handle spacing out these - * requests so as not to jam up the KeyStore work queue. - * - * @param is_test_mode Instructs the underlying HAL interface to mark the generated key with a - * tag to indicate that it's for testing. - * - * @param secLevel The security level to specify which KM instance should generate a key pair. - */ - void generateKeyPair(in boolean is_test_mode, in SecurityLevel secLevel); - - /** - * This method returns implementation information for whichever instances of - * IRemotelyProvisionedComponent are running on the device. The RemoteProvisioner app needs to - * know which KM instances it should be generating and managing attestation keys for, and which - * EC curves are supported in those instances. - * - * @return The array of ImplInfo parcelables. - */ - ImplInfo[] getImplementationInfo(); - - /** - * This method deletes all remotely provisioned attestation keys in the database, regardless - * of what state in their life cycle they are in. This is primarily useful to facilitate - * testing. - * - * @return Number of keys deleted - */ - long deleteAllKeys(); -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl deleted file mode 100644 index 7d45e52e..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.security.remoteprovisioning; - -import android.security.remoteprovisioning.RemotelyProvisionedKey; - -/** - * This is the interface providing access to remotely-provisioned attestation keys - * for an `IRemotelyProvisionedComponent`. - * - * @hide - */ -interface IRemotelyProvisionedKeyPool { - - /** - * Fetches an attestation key for the given uid and `IRemotelyProvisionedComponent`, as - * identified by the given id. - - * Callers require the keystore2::get_attestation_key permission. - * - * ## Error conditions - * `android.system.keystore2.ResponseCode::PERMISSION_DENIED` if the caller does not have the - * `keystore2::get_attestation_key` permission - * - * @param clientUid The client application for which an attestation key is needed. - * - * @param irpcId The unique identifier for the `IRemotelyProvisionedComponent` for which a key - * is requested. This id may be retrieved from a given component via the - * `IRemotelyProvisionedComponent::getHardwareInfo` function. - * - * @return A `RemotelyProvisionedKey` parcelable containing a key and certification chain for - * the given `IRemotelyProvisionedComponent`. - */ - RemotelyProvisionedKey getAttestationKey(in int clientUid, in @utf8InCpp String irpcId); -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl b/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl deleted file mode 100644 index 9baeb24b..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ - -package android.security.remoteprovisioning; - -import android.hardware.security.keymint.SecurityLevel; - -/** - * This parcelable provides information about the underlying IRemotelyProvisionedComponent - * implementation. - * @hide - */ -parcelable ImplInfo { - /** - * The security level of the underlying implementation: TEE or StrongBox. - */ - SecurityLevel secLevel; - /** - * An integer denoting which EC curve is supported in the underlying implementation. The current - * options are either P256 or 25519, with values defined in - * hardware/interfaces/security/keymint/aidl/.../RpcHardwareInfo.aidl - */ - int supportedCurve; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl b/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl deleted file mode 100644 index ae218550..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -package android.security.remoteprovisioning; - -/** - * A `RemotelyProvisionedKey` holds an attestation key and the corresponding remotely provisioned - * certificate chain. - * - * @hide - */ -@RustDerive(Eq=true, PartialEq=true) -parcelable RemotelyProvisionedKey { - /** - * The remotely-provisioned key that may be used to sign attestations. The format of this key - * is opaque, and need only be understood by the IRemotelyProvisionedComponent that generated - * it. - * - * Any private key material contained within this blob must be encrypted. - */ - byte[] keyBlob; - - /** - * Sequence of DER-encoded X.509 certificates that make up the attestation key's certificate - * chain. This is the binary encoding for a chain that is supported by Java's - * CertificateFactory.generateCertificates API. - */ - byte[] encodedCertChain; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl b/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl deleted file mode 100644 index c9877db5..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package android.security.remoteprovisioning; - -@Backing(type="int") -/** @hide */ -enum ResponseCode { - /** - * Returned if there are no keys available in the database to be used in a CSR - */ - NO_UNSIGNED_KEYS = 1, - /** - * The caller has imrproper SELinux permissions to access the Remote Provisioning API. - */ - PERMISSION_DENIED = 2, - /** - * An unexpected error occurred, likely with IO or IPC. - */ - SYSTEM_ERROR = 3, -} diff --git a/keystore2/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml index 20c2fba9..45f995c8 100644 --- a/keystore2/android.system.keystore2-service.xml +++ b/keystore2/android.system.keystore2-service.xml @@ -1,7 +1,7 @@ <manifest version="1.0" type="framework"> <hal format="aidl"> <name>android.system.keystore2</name> - <version>2</version> + <version>3</version> <interface> <name>IKeystoreService</name> <instance>default</instance> diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp index df7521e1..61697a85 100644 --- a/keystore2/apc_compat/Android.bp +++ b/keystore2/apc_compat/Android.bp @@ -27,7 +27,9 @@ cc_library { "apc_compat.cpp", ], shared_libs: [ + "libbinder_ndk", "android.hardware.confirmationui@1.0", + "android.hardware.confirmationui-V1-ndk", "libbase", "libhidlbase", "libutils", diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp index 08a8e451..9f60db2e 100644 --- a/keystore2/apc_compat/apc_compat.cpp +++ b/keystore2/apc_compat/apc_compat.cpp @@ -19,6 +19,12 @@ #include <android/hardware/confirmationui/1.0/IConfirmationUI.h> #include <hwbinder/IBinder.h> +#include <aidl/android/hardware/confirmationui/BnConfirmationResultCallback.h> +#include <aidl/android/hardware/confirmationui/IConfirmationResultCallback.h> +#include <aidl/android/hardware/confirmationui/IConfirmationUI.h> +#include <aidl/android/hardware/confirmationui/UIOption.h> +#include <android/binder_manager.h> + #include <memory> #include <string> #include <thread> @@ -33,41 +39,52 @@ using android::hardware::hidl_death_recipient; using android::hardware::hidl_vec; using android::hardware::Return; using android::hardware::Status; -using android::hardware::confirmationui::V1_0::IConfirmationResultCallback; -using android::hardware::confirmationui::V1_0::IConfirmationUI; +using HidlConfirmationResultCb = + android::hardware::confirmationui::V1_0::IConfirmationResultCallback; +using HidlConfirmationUI = android::hardware::confirmationui::V1_0::IConfirmationUI; using android::hardware::confirmationui::V1_0::ResponseCode; -using android::hardware::confirmationui::V1_0::UIOption; - -static uint32_t responseCode2Compat(ResponseCode rc) { - switch (rc) { - case ResponseCode::OK: - return APC_COMPAT_ERROR_OK; - case ResponseCode::Canceled: - return APC_COMPAT_ERROR_CANCELLED; - case ResponseCode::Aborted: - return APC_COMPAT_ERROR_ABORTED; - case ResponseCode::OperationPending: - return APC_COMPAT_ERROR_OPERATION_PENDING; - case ResponseCode::Ignored: - return APC_COMPAT_ERROR_IGNORED; - case ResponseCode::SystemError: - case ResponseCode::Unimplemented: - case ResponseCode::Unexpected: - case ResponseCode::UIError: - case ResponseCode::UIErrorMissingGlyph: - case ResponseCode::UIErrorMessageTooLong: - case ResponseCode::UIErrorMalformedUTF8Encoding: - default: - return APC_COMPAT_ERROR_SYSTEM_ERROR; +using HidlUIOptions = android::hardware::confirmationui::V1_0::UIOption; + +using AidlConfirmationUI = ::aidl::android::hardware::confirmationui::IConfirmationUI; +using AidlBnConfirmationResultCb = + ::aidl::android::hardware::confirmationui::BnConfirmationResultCallback; +using AidlUIOptions = ::aidl::android::hardware::confirmationui::UIOption; + +class CompatSessionCB { + public: + void + finalize(uint32_t responseCode, ApcCompatCallback callback, + std::optional<std::reference_wrapper<const std::vector<uint8_t>>> dataConfirmed, + std::optional<std::reference_wrapper<const std::vector<uint8_t>>> confirmationToken) { + if (callback.result != nullptr) { + size_t dataConfirmedSize = 0; + const uint8_t* dataConfirmedPtr = nullptr; + size_t confirmationTokenSize = 0; + const uint8_t* confirmationTokenPtr = nullptr; + if (responseCode == APC_COMPAT_ERROR_OK) { + if (dataConfirmed) { + dataConfirmedPtr = dataConfirmed->get().data(); + dataConfirmedSize = dataConfirmed->get().size(); + } + if (confirmationToken) { + confirmationTokenPtr = confirmationToken->get().data(); + confirmationTokenSize = confirmationToken->get().size(); + } + } + callback.result(callback.data, responseCode, dataConfirmedPtr, dataConfirmedSize, + confirmationTokenPtr, confirmationTokenSize); + } } -} +}; -class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_death_recipient { +class ConfuiHidlCompatSession : public HidlConfirmationResultCb, + public hidl_death_recipient, + public CompatSessionCB { public: - static sp<ConfuiCompatSession>* tryGetService() { - sp<IConfirmationUI> service = IConfirmationUI::tryGetService(); + static sp<ConfuiHidlCompatSession> tryGetService() { + sp<HidlConfirmationUI> service = HidlConfirmationUI::tryGetService(); if (service) { - return new sp(new ConfuiCompatSession(std::move(service))); + return sp<ConfuiHidlCompatSession>(new ConfuiHidlCompatSession(std::move(service))); } else { return nullptr; } @@ -78,13 +95,12 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat const char* locale, ApcCompatUiOptions ui_options) { std::string hidl_prompt(prompt_text); std::vector<uint8_t> hidl_extra(extra_data, extra_data + extra_data_size); - std::string hidl_locale(locale); - std::vector<UIOption> hidl_ui_options; + std::vector<HidlUIOptions> hidl_ui_options; if (ui_options.inverted) { - hidl_ui_options.push_back(UIOption::AccessibilityInverted); + hidl_ui_options.push_back(HidlUIOptions::AccessibilityInverted); } if (ui_options.magnified) { - hidl_ui_options.push_back(UIOption::AccessibilityMagnified); + hidl_ui_options.push_back(HidlUIOptions::AccessibilityMagnified); } auto lock = std::lock_guard(callback_lock_); if (callback_.result != nullptr) { @@ -98,7 +114,7 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat return APC_COMPAT_ERROR_SYSTEM_ERROR; } - auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, hidl_locale, + auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, locale, hidl_ui_options); if (!rc.isOk()) { LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.description(); @@ -111,10 +127,8 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat void abort() { service_->abort(); } - void - finalize(ResponseCode responseCode, - std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> dataConfirmed, - std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> confirmationToken) { + void finalize(ResponseCode responseCode, const hidl_vec<uint8_t>& dataConfirmed, + const hidl_vec<uint8_t>& confirmationToken) { ApcCompatCallback callback; { auto lock = std::lock_guard(callback_lock_); @@ -128,26 +142,14 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat if (callback.result != nullptr) { service_->unlinkToDeath(sp(this)); - size_t dataConfirmedSize = 0; - const uint8_t* dataConfirmedPtr = nullptr; - size_t confirmationTokenSize = 0; - const uint8_t* confirmationTokenPtr = nullptr; - if (responseCode == ResponseCode::OK) { - if (dataConfirmed) { - dataConfirmedPtr = dataConfirmed->get().data(); - dataConfirmedSize = dataConfirmed->get().size(); - } - if (dataConfirmed) { - confirmationTokenPtr = confirmationToken->get().data(); - confirmationTokenSize = confirmationToken->get().size(); - } - } - callback.result(callback.data, responseCode2Compat(responseCode), dataConfirmedPtr, - dataConfirmedSize, confirmationTokenPtr, confirmationTokenSize); + std::vector<uint8_t> data = dataConfirmed; + std::vector<uint8_t> token = confirmationToken; + + CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, data, token); } } - // IConfirmationResultCallback overrides: + // HidlConfirmationResultCb overrides: android::hardware::Return<void> result(ResponseCode responseCode, const hidl_vec<uint8_t>& dataConfirmed, const hidl_vec<uint8_t>& confirmationToken) override { @@ -160,10 +162,34 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat finalize(ResponseCode::SystemError, {}, {}); } + static uint32_t responseCode2Compat(ResponseCode rc) { + switch (rc) { + case ResponseCode::OK: + return APC_COMPAT_ERROR_OK; + case ResponseCode::Canceled: + return APC_COMPAT_ERROR_CANCELLED; + case ResponseCode::Aborted: + return APC_COMPAT_ERROR_ABORTED; + case ResponseCode::OperationPending: + return APC_COMPAT_ERROR_OPERATION_PENDING; + case ResponseCode::Ignored: + return APC_COMPAT_ERROR_IGNORED; + case ResponseCode::SystemError: + case ResponseCode::Unimplemented: + case ResponseCode::Unexpected: + case ResponseCode::UIError: + case ResponseCode::UIErrorMissingGlyph: + case ResponseCode::UIErrorMessageTooLong: + case ResponseCode::UIErrorMalformedUTF8Encoding: + default: + return APC_COMPAT_ERROR_SYSTEM_ERROR; + } + } + private: - ConfuiCompatSession(sp<IConfirmationUI> service) + ConfuiHidlCompatSession(sp<HidlConfirmationUI> service) : service_(service), callback_{nullptr, nullptr} {} - sp<IConfirmationUI> service_; + sp<HidlConfirmationUI> service_; // The callback_lock_ protects the callback_ field against concurrent modification. // IMPORTANT: It must never be held while calling the call back. @@ -171,34 +197,248 @@ class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_deat ApcCompatCallback callback_; }; +class ConfuiAidlCompatSession : public AidlBnConfirmationResultCb, public CompatSessionCB { + public: + static std::shared_ptr<ConfuiAidlCompatSession> tryGetService() { + constexpr const char confirmationUIServiceName[] = + "android.hardware.confirmationui.IConfirmationUI/default"; + if (!AServiceManager_isDeclared(confirmationUIServiceName)) { + LOG(INFO) << confirmationUIServiceName << " is not declared in VINTF"; + return nullptr; + } + std::shared_ptr<AidlConfirmationUI> aidlService = AidlConfirmationUI::fromBinder( + ndk::SpAIBinder(AServiceManager_waitForService(confirmationUIServiceName))); + if (aidlService) { + return ::ndk::SharedRefBase::make<ConfuiAidlCompatSession>(aidlService); + } + + return nullptr; + } + + uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text, + const uint8_t* extra_data, size_t extra_data_size, + const char* locale, ApcCompatUiOptions ui_options) { + std::vector<uint8_t> aidl_prompt(prompt_text, prompt_text + strlen(prompt_text)); + std::vector<uint8_t> aidl_extra(extra_data, extra_data + extra_data_size); + std::vector<AidlUIOptions> aidl_ui_options; + if (ui_options.inverted) { + aidl_ui_options.push_back(AidlUIOptions::ACCESSIBILITY_INVERTED); + } + if (ui_options.magnified) { + aidl_ui_options.push_back(AidlUIOptions::ACCESSIBILITY_MAGNIFIED); + } + auto lock = std::lock_guard(callback_lock_); + if (callback_.result != nullptr) { + return APC_COMPAT_ERROR_OPERATION_PENDING; + } + + if (!aidlService_) { + return APC_COMPAT_ERROR_SYSTEM_ERROR; + } + auto linkRet = + AIBinder_linkToDeath(aidlService_->asBinder().get(), death_recipient_.get(), this); + if (linkRet != STATUS_OK) { + LOG(ERROR) << "Communication error: promptUserConfirmation: " + "Trying to register death recipient: "; + return APC_COMPAT_ERROR_SYSTEM_ERROR; + } + + auto rc = aidlService_->promptUserConfirmation(ref<ConfuiAidlCompatSession>(), aidl_prompt, + aidl_extra, locale, aidl_ui_options); + int ret = getReturnCode(rc); + if (ret == AidlConfirmationUI::OK) { + callback_ = callback; + } else { + LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.getDescription(); + } + return responseCode2Compat(ret); + } + + void abort() { + if (aidlService_) { + aidlService_->abort(); + } + } + + void + finalize(int32_t responseCode, + std::optional<std::reference_wrapper<const std::vector<uint8_t>>> dataConfirmed, + std::optional<std::reference_wrapper<const std::vector<uint8_t>>> confirmationToken) { + ApcCompatCallback callback; + { + auto lock = std::lock_guard(callback_lock_); + // Calling the callback consumes the callback data structure. We have to make + // sure that it can only be called once. + callback = callback_; + callback_ = {nullptr, nullptr}; + // Unlock the callback_lock_ here. It must never be held while calling the callback. + } + + if (callback.result != nullptr) { + if (aidlService_) { + AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(), + this); + } + CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, dataConfirmed, + confirmationToken); + } + } + + // AidlBnConfirmationResultCb overrides: + ::ndk::ScopedAStatus result(int32_t responseCode, const std::vector<uint8_t>& dataConfirmed, + const std::vector<uint8_t>& confirmationToken) override { + finalize(responseCode, dataConfirmed, confirmationToken); + return ::ndk::ScopedAStatus::ok(); + }; + + void serviceDied() { + aidlService_.reset(); + aidlService_ = nullptr; + finalize(AidlConfirmationUI::SYSTEM_ERROR, {}, {}); + } + + static void binderDiedCallbackAidl(void* ptr) { + LOG(ERROR) << __func__ << " : ConfuiAidlCompatSession Service died."; + auto aidlSession = static_cast<ConfuiAidlCompatSession*>(ptr); + if (aidlSession == nullptr) { + LOG(ERROR) << __func__ << ": Null ConfuiAidlCompatSession HAL died."; + return; + } + aidlSession->serviceDied(); + } + + int getReturnCode(const ::ndk::ScopedAStatus& result) { + if (result.isOk()) return AidlConfirmationUI::OK; + + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast<int>(result.getServiceSpecificError()); + } + return result.getStatus(); + } + + uint32_t responseCode2Compat(int32_t rc) { + switch (rc) { + case AidlConfirmationUI::OK: + return APC_COMPAT_ERROR_OK; + case AidlConfirmationUI::CANCELED: + return APC_COMPAT_ERROR_CANCELLED; + case AidlConfirmationUI::ABORTED: + return APC_COMPAT_ERROR_ABORTED; + case AidlConfirmationUI::OPERATION_PENDING: + return APC_COMPAT_ERROR_OPERATION_PENDING; + case AidlConfirmationUI::IGNORED: + return APC_COMPAT_ERROR_IGNORED; + case AidlConfirmationUI::SYSTEM_ERROR: + case AidlConfirmationUI::UNIMPLEMENTED: + case AidlConfirmationUI::UNEXPECTED: + case AidlConfirmationUI::UI_ERROR: + case AidlConfirmationUI::UI_ERROR_MISSING_GLYPH: + case AidlConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG: + case AidlConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING: + default: + return APC_COMPAT_ERROR_SYSTEM_ERROR; + } + } + + ConfuiAidlCompatSession(std::shared_ptr<AidlConfirmationUI> service) + : aidlService_(service), callback_{nullptr, nullptr} { + death_recipient_ = ::ndk::ScopedAIBinder_DeathRecipient( + AIBinder_DeathRecipient_new(binderDiedCallbackAidl)); + } + + virtual ~ConfuiAidlCompatSession() = default; + ConfuiAidlCompatSession(const ConfuiAidlCompatSession&) = delete; + ConfuiAidlCompatSession& operator=(const ConfuiAidlCompatSession&) = delete; + + private: + std::shared_ptr<AidlConfirmationUI> aidlService_; + + // The callback_lock_ protects the callback_ field against concurrent modification. + // IMPORTANT: It must never be held while calling the call back. + std::mutex callback_lock_; + ApcCompatCallback callback_; + + ::ndk::ScopedAIBinder_DeathRecipient death_recipient_; +}; + +class ApcCompatSession { + public: + static ApcCompatServiceHandle getApcCompatSession() { + auto aidlCompatSession = ConfuiAidlCompatSession::tryGetService(); + if (aidlCompatSession) { + return new ApcCompatSession(std::move(aidlCompatSession), nullptr); + } + + sp<ConfuiHidlCompatSession> hidlCompatSession = ConfuiHidlCompatSession::tryGetService(); + if (hidlCompatSession) { + return new ApcCompatSession(nullptr, std::move(hidlCompatSession)); + } + + LOG(ERROR) << "ConfirmationUI: Not found Service"; + return nullptr; + } + + uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text, + const uint8_t* extra_data, size_t extra_data_size, + char const* locale, ApcCompatUiOptions ui_options) { + if (aidlCompatSession_) { + return aidlCompatSession_->promptUserConfirmation(callback, prompt_text, extra_data, + extra_data_size, locale, ui_options); + } else { + return hidlCompatSession_->promptUserConfirmation(callback, prompt_text, extra_data, + extra_data_size, locale, ui_options); + } + } + + void abortUserConfirmation() { + if (aidlCompatSession_) { + return aidlCompatSession_->abort(); + } else { + return hidlCompatSession_->abort(); + } + } + + void closeUserConfirmationService() { + // Closing the handle implicitly aborts an ongoing sessions. + // Note that a resulting callback is still safely conducted, because we only delete a + // StrongPointer below. libhwbinder still owns another StrongPointer to this session. + abortUserConfirmation(); + } + + ApcCompatSession(std::shared_ptr<ConfuiAidlCompatSession> aidlCompatSession, + sp<ConfuiHidlCompatSession> hidlCompatSession) + : aidlCompatSession_(aidlCompatSession), hidlCompatSession_(hidlCompatSession) {} + + private: + std::shared_ptr<ConfuiAidlCompatSession> aidlCompatSession_; + sp<ConfuiHidlCompatSession> hidlCompatSession_; +}; } // namespace keystore2 using namespace keystore2; ApcCompatServiceHandle tryGetUserConfirmationService() { - return reinterpret_cast<ApcCompatServiceHandle>(ConfuiCompatSession::tryGetService()); + return reinterpret_cast<ApcCompatServiceHandle>(ApcCompatSession::getApcCompatSession()); } uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, ApcCompatCallback callback, const char* prompt_text, const uint8_t* extra_data, size_t extra_data_size, char const* locale, ApcCompatUiOptions ui_options) { - auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle); - return (*session)->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size, - locale, ui_options); + auto session = reinterpret_cast<ApcCompatSession*>(handle); + return session->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size, + locale, ui_options); } void abortUserConfirmation(ApcCompatServiceHandle handle) { - auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle); - (*session)->abort(); + auto session = reinterpret_cast<ApcCompatSession*>(handle); + session->abortUserConfirmation(); } void closeUserConfirmationService(ApcCompatServiceHandle handle) { - // Closing the handle implicitly aborts an ongoing sessions. - // Note that a resulting callback is still safely conducted, because we only delete a - // StrongPointer below. libhwbinder still owns another StrongPointer to this session. - abortUserConfirmation(handle); - delete reinterpret_cast<sp<ConfuiCompatSession>*>(handle); + auto session = reinterpret_cast<ApcCompatSession*>(handle); + session->closeUserConfirmationService(); + delete reinterpret_cast<ApcCompatSession*>(handle); } const ApcCompatServiceHandle INVALID_SERVICE_HANDLE = nullptr; diff --git a/keystore2/apc_compat/apc_compat.rs b/keystore2/apc_compat/apc_compat.rs index 57f8710e..480f14dd 100644 --- a/keystore2/apc_compat/apc_compat.rs +++ b/keystore2/apc_compat/apc_compat.rs @@ -19,7 +19,7 @@ //! client. use keystore2_apc_compat_bindgen::{ - abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation, size_t, + abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation, tryGetUserConfirmationService, ApcCompatCallback, ApcCompatServiceHandle, }; pub use keystore2_apc_compat_bindgen::{ @@ -76,9 +76,9 @@ extern "C" fn confirmation_result_callback( handle: *mut ::std::os::raw::c_void, rc: u32, tbs_message: *const u8, - tbs_message_size: size_t, + tbs_message_size: usize, confirmation_token: *const u8, - confirmation_token_size: size_t, + confirmation_token_size: usize, ) { // # Safety: // The C/C++ implementation must pass to us the handle that was created @@ -94,7 +94,7 @@ extern "C" fn confirmation_result_callback( // If the pointer and size is not nullptr and not 0 respectively, the C/C++ // implementation must pass a valid pointer to an allocation of at least size bytes, // and the pointer must be valid until this function returns. - unsafe { slice::from_raw_parts(tbs_message, s as usize) }, + unsafe { slice::from_raw_parts(tbs_message, s) }, ), }; let confirmation_token = match (confirmation_token.is_null(), confirmation_token_size) { @@ -104,7 +104,7 @@ extern "C" fn confirmation_result_callback( // If the pointer and size is not nullptr and not 0 respectively, the C/C++ // implementation must pass a valid pointer to an allocation of at least size bytes, // and the pointer must be valid until this function returns. - unsafe { slice::from_raw_parts(confirmation_token, s as usize) }, + unsafe { slice::from_raw_parts(confirmation_token, s) }, ), }; hal_cb(rc, tbs_message, confirmation_token) @@ -178,7 +178,7 @@ impl ApcHal { cb, prompt_text.as_ptr(), extra_data.as_ptr(), - extra_data.len() as size_t, + extra_data.len(), locale.as_ptr(), ui_opts, ) diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs index e2d952d9..464f0a29 100644 --- a/keystore2/legacykeystore/lib.rs +++ b/keystore2/legacykeystore/lib.rs @@ -108,6 +108,12 @@ impl DB { .prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;") .context("In list: Failed to prepare statement.")?; + // This allow is necessary to avoid the following error: + // + // error[E0597]: `stmt` does not live long enough + // + // See: https://github.com/rust-lang/rust-clippy/issues/8114 + #[allow(clippy::let_and_return)] let aliases = stmt .query_map(params![caller_uid], |row| row.get(0))? .collect::<rusqlite::Result<Vec<String>>>() @@ -172,7 +178,7 @@ impl DB { /// This is the main LegacyKeystore error type, it wraps binder exceptions and the /// LegacyKeystore errors. -#[derive(Debug, thiserror::Error, PartialEq)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum Error { /// Wraps a LegacyKeystore error code. #[error("Error::Error({0:?})")] @@ -387,7 +393,7 @@ impl LegacyKeystore { let uid = Self::get_effective_uid(uid).context("In list.")?; let mut result = self.list_legacy(uid).context("In list.")?; result.append(&mut db.list(uid).context("In list: Trying to get list of entries.")?); - result = result.into_iter().filter(|s| s.starts_with(prefix)).collect(); + result.retain(|s| s.starts_with(prefix)); result.sort_unstable(); result.dedup(); Ok(result) @@ -496,10 +502,8 @@ impl LegacyKeystore { ) -> Result<bool> { let blob = legacy_loader .read_legacy_keystore_entry(uid, alias, |ciphertext, iv, tag, _salt, _key_size| { - if let Some(key) = SUPER_KEY - .read() - .unwrap() - .get_per_boot_key_by_user_id(uid_to_android_user(uid as u32)) + if let Some(key) = + SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(uid)) { key.decrypt(ciphertext, iv, tag) } else { diff --git a/keystore2/rustfmt.toml b/keystore2/rustfmt.toml new file mode 100644 index 00000000..4335d660 --- /dev/null +++ b/keystore2/rustfmt.toml @@ -0,0 +1,5 @@ +# Android Format Style + +edition = "2021" +use_small_heuristics = "Max" +newline_style = "Unix"
\ No newline at end of file diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs index c0593b7a..e5c3091b 100644 --- a/keystore2/selinux/src/lib.rs +++ b/keystore2/selinux/src/lib.rs @@ -65,7 +65,7 @@ fn init_logger_once() { } /// Selinux Error code. -#[derive(thiserror::Error, Debug, PartialEq)] +#[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum Error { /// Indicates that an access check yielded no access. #[error("Permission Denied")] diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs index 7d56dc9f..5d2083da 100644 --- a/keystore2/src/apc.rs +++ b/keystore2/src/apc.rs @@ -22,6 +22,7 @@ use std::{ }; use crate::error::anyhow_error_to_cstring; +use crate::ks_err; use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd}; use android_security_apc::aidl::android::security::apc::{ IConfirmationCallback::IConfirmationCallback, @@ -39,7 +40,7 @@ use std::time::{Duration, Instant}; /// This is the main APC error type, it wraps binder exceptions and the /// APC ResponseCode. -#[derive(Debug, thiserror::Error, PartialEq)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum Error { /// Wraps an Android Protected Confirmation (APC) response code as defined by the /// android.security.apc AIDL interface specification. @@ -259,13 +260,10 @@ impl ApcManager { if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() { if let Err(e) = listener.onCompleted(rc, data_confirmed) { - log::error!( - "In ApcManagerCallback::result: Reporting completion to client failed {:?}", - e - ) + log::error!("Reporting completion to client failed {:?}", e) } } else { - log::error!("In ApcManagerCallback::result: SpIBinder is not a IConfirmationCallback."); + log::error!("SpIBinder is not a IConfirmationCallback."); } } @@ -279,8 +277,7 @@ impl ApcManager { ) -> Result<()> { let mut state = self.state.lock().unwrap(); if state.session.is_some() { - return Err(Error::pending()) - .context("In ApcManager::present_prompt: Session pending."); + return Err(Error::pending()).context(ks_err!("APC Session pending.")); } // Perform rate limiting. @@ -289,8 +286,8 @@ impl ApcManager { None => {} Some(rate_info) => { if let Some(back_off) = rate_info.get_remaining_back_off() { - return Err(Error::sys()).context(format!( - "In ApcManager::present_prompt: Cooling down. Remaining back-off: {}s", + return Err(Error::sys()).context(ks_err!( + "APC Cooling down. Remaining back-off: {}s", back_off.as_secs() )); } @@ -300,8 +297,7 @@ impl ApcManager { let hal = ApcHal::try_get_service(); let hal = match hal { None => { - return Err(Error::unimplemented()) - .context("In ApcManager::present_prompt: APC not supported.") + return Err(Error::unimplemented()).context(ks_err!("APC not supported.")); } Some(h) => Arc::new(h), }; @@ -319,7 +315,7 @@ impl ApcManager { }, ) .map_err(|rc| Error::Rc(compat_2_response_code(rc))) - .context("In present_prompt: Failed to present prompt.")?; + .context(ks_err!("APC Failed to present prompt."))?; state.session = Some(ApcSessionState { hal, cb: listener.as_binder(), @@ -335,13 +331,12 @@ impl ApcManager { let hal = match &mut state.session { None => { return Err(Error::ignored()) - .context("In cancel_prompt: Attempt to cancel non existing session. Ignoring.") + .context(ks_err!("Attempt to cancel non existing session. Ignoring.")); } Some(session) => { if session.cb != listener.as_binder() { - return Err(Error::ignored()).context(concat!( - "In cancel_prompt: Attempt to cancel session not belonging to caller. ", - "Ignoring." + return Err(Error::ignored()).context(ks_err!( + "Attempt to cancel session not belonging to caller. Ignoring." )); } session.client_aborted = true; diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs index 0515c8f8..6548445f 100644 --- a/keystore2/src/async_task.rs +++ b/keystore2/src/async_task.rs @@ -67,7 +67,7 @@ impl Shelf { pub fn get_mut<T: Any + Send + Default>(&mut self) -> &mut T { self.0 .entry(TypeId::of::<T>()) - .or_insert_with(|| Box::new(T::default()) as Box<dyn Any + Send>) + .or_insert_with(|| Box::<T>::default() as Box<dyn Any + Send>) .downcast_mut::<T>() .unwrap() } diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs index 34089425..184b3cbd 100644 --- a/keystore2/src/attestation_key_utils.rs +++ b/keystore2/src/attestation_key_utils.rs @@ -18,6 +18,7 @@ use crate::database::{BlobMetaData, KeyEntryLoadBits, KeyType}; use crate::database::{KeyIdGuard, KeystoreDB}; use crate::error::{Error, ErrorCode}; +use crate::ks_err; use crate::permission::KeyPerm; use crate::remote_provisioning::RemProvState; use crate::utils::check_key_permission; @@ -25,7 +26,7 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin AttestationKey::AttestationKey, Certificate::Certificate, KeyParameter::KeyParameter, Tag::Tag, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; use anyhow::{Context, Result}; use keystore2_crypto::parse_subject_from_certificate; @@ -34,8 +35,7 @@ use keystore2_crypto::parse_subject_from_certificate; /// and those that have been generated by the user. Unfortunately, they need to be /// handled quite differently, thus the different representations. pub enum AttestationKeyInfo { - RemoteProvisioned { - key_id_guard: KeyIdGuard, + RkpdProvisioned { attestation_key: AttestationKey, attestation_certs: Certificate, }, @@ -64,23 +64,16 @@ pub fn get_attest_key_info( match attest_key_descriptor { // Do not select an RKP key if DEVICE_UNIQUE_ATTESTATION is present. None if challenge_present && !is_device_unique_attestation => rem_prov_state - .get_remotely_provisioned_attestation_key_and_certs(key, caller_uid, params, db) - .context(concat!( - "In get_attest_key_and_cert_chain: ", - "Trying to get remotely provisioned attestation key." - )) + .get_rkpd_attestation_key_and_certs(key, caller_uid, params) + .context(ks_err!("Trying to get attestation key from RKPD.")) .map(|result| { - result.map(|(key_id_guard, attestation_key, attestation_certs)| { - AttestationKeyInfo::RemoteProvisioned { - key_id_guard, - attestation_key, - attestation_certs, - } + result.map(|(attestation_key, attestation_certs)| { + AttestationKeyInfo::RkpdProvisioned { attestation_key, attestation_certs } }) }), None => Ok(None), Some(attest_key) => get_user_generated_attestation_key(attest_key, caller_uid, db) - .context("In get_attest_key_and_cert_chain: Trying to load attest key") + .context(ks_err!("Trying to load attest key")) .map(Some), } } @@ -92,11 +85,10 @@ fn get_user_generated_attestation_key( ) -> Result<AttestationKeyInfo> { let (key_id_guard, blob, cert, blob_metadata) = load_attest_key_blob_and_cert(key, caller_uid, db) - .context("In get_user_generated_attestation_key: Failed to load blob and cert")?; + .context(ks_err!("Failed to load blob and cert"))?; - let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert).context( - "In get_user_generated_attestation_key: Failed to parse subject from certificate.", - )?; + let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert) + .context(ks_err!("Failed to parse subject from certificate"))?; Ok(AttestationKeyInfo::UserGenerated { key_id_guard, blob, issuer_subject, blob_metadata }) } @@ -107,9 +99,8 @@ fn load_attest_key_blob_and_cert( db: &mut KeystoreDB, ) -> Result<(KeyIdGuard, Vec<u8>, Vec<u8>, BlobMetaData)> { match key.domain { - Domain::BLOB => Err(Error::Km(ErrorCode::INVALID_ARGUMENT)).context( - "In load_attest_key_blob_and_cert: Domain::BLOB attestation keys not supported", - ), + Domain::BLOB => Err(Error::Km(ErrorCode::INVALID_ARGUMENT)) + .context(ks_err!("Domain::BLOB attestation keys not supported")), _ => { let (key_id_guard, mut key_entry) = db .load_key_entry( @@ -119,17 +110,16 @@ fn load_attest_key_blob_and_cert( caller_uid, |k, av| check_key_permission(KeyPerm::Use, k, &av), ) - .context("In load_attest_key_blob_and_cert: Failed to load key.")?; + .context(ks_err!("Failed to load key."))?; - let (blob, blob_metadata) = - key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!( - "In load_attest_key_blob_and_cert: Successfully loaded key entry,", - " but KM blob was missing." - ))?; - let cert = key_entry.take_cert().ok_or_else(Error::sys).context(concat!( - "In load_attest_key_blob_and_cert: Successfully loaded key entry,", - " but cert was missing." - ))?; + let (blob, blob_metadata) = key_entry + .take_key_blob_info() + .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(ks_err!("Successfully loaded key entry, but KM blob was missing"))?; + let cert = key_entry + .take_cert() + .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(ks_err!("Successfully loaded key entry, but cert was missing"))?; Ok((key_id_guard, blob, cert, blob_metadata)) } } diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs index 3d7d26e5..07509d36 100644 --- a/keystore2/src/audit_log.rs +++ b/keystore2/src/audit_log.rs @@ -67,7 +67,7 @@ pub fn log_key_integrity_violation(key: &KeyDescriptor) { fn log_key_event(tag: u32, key: &KeyDescriptor, calling_app: uid_t, success: bool) { with_log_context(tag, |ctx| { let owner = key_owner(key.domain, key.nspace, calling_app as i32); - ctx.append_i32(if success { 1 } else { 0 }) + ctx.append_i32(i32::from(success)) .append_str(key.alias.as_ref().map_or("none", String::as_str)) .append_i32(owner) }) diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs index 8265dd02..19539201 100644 --- a/keystore2/src/authorization.rs +++ b/keystore2/src/authorization.rs @@ -14,6 +14,7 @@ //! This module implements IKeystoreAuthorization AIDL interface. +use crate::ks_err; use crate::error::Error as KeystoreError; use crate::error::anyhow_error_to_cstring; use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_IMPORTER}; @@ -23,22 +24,22 @@ use crate::utils::{check_keystore_permission, watchdog as wd}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, }; -use android_security_authorization::binder::{BinderFeatures,ExceptionCode, Interface, Result as BinderResult, - Strong, Status as BinderStatus}; +use android_security_authorization::binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, + Strong, Status as BinderStatus}; use android_security_authorization::aidl::android::security::authorization::{ IKeystoreAuthorization::BnKeystoreAuthorization, IKeystoreAuthorization::IKeystoreAuthorization, LockScreenEvent::LockScreenEvent, AuthorizationTokens::AuthorizationTokens, ResponseCode::ResponseCode, }; use android_system_keystore2::aidl::android::system::keystore2::{ - ResponseCode::ResponseCode as KsResponseCode }; + ResponseCode::ResponseCode as KsResponseCode}; use anyhow::{Context, Result}; use keystore2_crypto::Password; use keystore2_selinux as selinux; /// This is the Authorization error type, it wraps binder exceptions and the /// Authorization ResponseCode -#[derive(Debug, thiserror::Error, PartialEq)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum Error { /// Wraps an IKeystoreAuthorization response code as defined by /// android.security.authorization AIDL interface specification. @@ -126,7 +127,7 @@ impl AuthorizationManager { fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> { // Check keystore permission. - check_keystore_permission(KeystorePerm::AddAuth).context("In add_auth_token.")?; + check_keystore_permission(KeystorePerm::AddAuth).context(ks_err!())?; ENFORCEMENTS.add_auth_token(auth_token.clone()); Ok(()) @@ -151,7 +152,7 @@ impl AuthorizationManager { // This corresponds to the unlock() method in legacy keystore API. // check permission check_keystore_permission(KeystorePerm::Unlock) - .context("In on_lock_screen_event: Unlock with password.")?; + .context(ks_err!("Unlock with password."))?; ENFORCEMENTS.set_device_locked(user_id, false); let mut skm = SUPER_KEY.write().unwrap(); @@ -163,7 +164,7 @@ impl AuthorizationManager { &password, ) }) - .context("In on_lock_screen_event: unlock_screen_lock_bound_key failed")?; + .context(ks_err!("unlock_screen_lock_bound_key failed"))?; // Unlock super key. if let UserState::Uninitialized = DB @@ -175,7 +176,7 @@ impl AuthorizationManager { &password, ) }) - .context("In on_lock_screen_event: Unlock with password.")? + .context(ks_err!("Unlock with password."))? { log::info!( "In on_lock_screen_event. Trying to unlock when LSKF is uninitialized." @@ -185,19 +186,17 @@ impl AuthorizationManager { Ok(()) } (LockScreenEvent::UNLOCK, None) => { - check_keystore_permission(KeystorePerm::Unlock) - .context("In on_lock_screen_event: Unlock.")?; + check_keystore_permission(KeystorePerm::Unlock).context(ks_err!("Unlock."))?; ENFORCEMENTS.set_device_locked(user_id, false); let mut skm = SUPER_KEY.write().unwrap(); DB.with(|db| { skm.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32) }) - .context("In on_lock_screen_event: try_unlock_user_with_biometric failed")?; + .context(ks_err!("try_unlock_user_with_biometric failed"))?; Ok(()) } (LockScreenEvent::LOCK, None) => { - check_keystore_permission(KeystorePerm::Lock) - .context("In on_lock_screen_event: Lock")?; + check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?; ENFORCEMENTS.set_device_locked(user_id, true); let mut skm = SUPER_KEY.write().unwrap(); DB.with(|db| { @@ -211,8 +210,7 @@ impl AuthorizationManager { } _ => { // Any other combination is not supported. - Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("In on_lock_screen_event: Unknown event.") + Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!("Unknown event.")) } } } @@ -225,13 +223,12 @@ impl AuthorizationManager { ) -> Result<AuthorizationTokens> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::GetAuthToken) - .context("In get_auth_tokens_for_credstore.")?; + check_keystore_permission(KeystorePerm::GetAuthToken).context(ks_err!("GetAuthToken"))?; // If the challenge is zero, return error if challenge == 0 { return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("In get_auth_tokens_for_credstore. Challenge can not be zero."); + .context(ks_err!("Challenge can not be zero.")); } // Obtain the auth token and the timestamp token from the enforcement module. let (auth_token, ts_token) = diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs index 08c52af1..e2e67ff7 100644 --- a/keystore2/src/boot_level_keys.rs +++ b/keystore2/src/boot_level_keys.rs @@ -14,6 +14,7 @@ //! Offer keys based on the "boot level" for superencryption. +use crate::ks_err; use crate::{ database::{KeyType, KeystoreDB}, key_parameter::KeyParameterValue, @@ -21,26 +22,86 @@ use crate::{ }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter as KmKeyParameter, - KeyParameterValue::KeyParameterValue as KmKeyParameterValue, KeyPurpose::KeyPurpose, - SecurityLevel::SecurityLevel, Tag::Tag, + KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, }; use anyhow::{Context, Result}; use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH}; use std::{collections::VecDeque, convert::TryFrom}; -fn get_preferred_km_instance_for_level_zero_key() -> Result<KeyMintDevice> { +/// Strategies used to prevent later boot stages from using the KM key that protects the level 0 +/// key +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum DenyLaterStrategy { + /// set MaxUsesPerBoot to 1. This is much less secure, since the attacker can replace the key + /// itself, and therefore create artifacts which appear to come from early boot. + MaxUsesPerBoot, + /// set the EarlyBootOnly property. This property is only supported in KM from 4.1 on, but + /// it ensures that the level 0 key was genuinely created in early boot + EarlyBootOnly, +} + +/// Generally the L0 KM and strategy are chosen by probing KM versions in TEE and Strongbox. +/// However, once a device is launched the KM and strategy must never change, even if the +/// KM version in TEE or Strongbox is updated. Setting this property at build time using +/// `PRODUCT_VENDOR_PROPERTIES` means that the strategy can be fixed no matter what versions +/// of KM are present. +const PROPERTY_NAME: &str = "ro.keystore.boot_level_key.strategy"; + +fn lookup_level_zero_km_and_strategy() -> Result<Option<(SecurityLevel, DenyLaterStrategy)>> { + let property_val = rustutils::system_properties::read(PROPERTY_NAME) + .with_context(|| ks_err!("property read failed: {}", PROPERTY_NAME))?; + // TODO: use feature(let_else) when that's stabilized. + let property_val = if let Some(p) = property_val { + p + } else { + log::info!("{} not set, inferring from installed KM instances", PROPERTY_NAME); + return Ok(None); + }; + let (level, strategy) = if let Some(c) = property_val.split_once(':') { + c + } else { + log::error!("Missing colon in {}: {:?}", PROPERTY_NAME, property_val); + return Ok(None); + }; + let level = match level { + "TRUSTED_ENVIRONMENT" => SecurityLevel::TRUSTED_ENVIRONMENT, + "STRONGBOX" => SecurityLevel::STRONGBOX, + _ => { + log::error!("Unknown security level in {}: {:?}", PROPERTY_NAME, level); + return Ok(None); + } + }; + let strategy = match strategy { + "EARLY_BOOT_ONLY" => DenyLaterStrategy::EarlyBootOnly, + "MAX_USES_PER_BOOT" => DenyLaterStrategy::MaxUsesPerBoot, + _ => { + log::error!("Unknown DenyLaterStrategy in {}: {:?}", PROPERTY_NAME, strategy); + return Ok(None); + } + }; + log::info!("Set from {}: {}", PROPERTY_NAME, property_val); + Ok(Some((level, strategy))) +} + +fn get_level_zero_key_km_and_strategy() -> Result<(KeyMintDevice, DenyLaterStrategy)> { + if let Some((level, strategy)) = lookup_level_zero_km_and_strategy()? { + return Ok(( + KeyMintDevice::get(level).context(ks_err!("Get KM instance failed."))?, + strategy, + )); + } let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) - .context("In get_preferred_km_instance_for_level_zero_key: Get TEE instance failed.")?; + .context(ks_err!("Get TEE instance failed."))?; if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 { - Ok(tee) + Ok((tee, DenyLaterStrategy::EarlyBootOnly)) } else { - match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX).context( - "In get_preferred_km_instance_for_level_zero_key: Get Strongbox instance failed.", - )? { + match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX) + .context(ks_err!("Get Strongbox instance failed."))? + { Some(strongbox) if strongbox.version() >= KeyMintDevice::KEY_MASTER_V4_1 => { - Ok(strongbox) + Ok((strongbox, DenyLaterStrategy::EarlyBootOnly)) } - _ => Ok(tee), + _ => Ok((tee, DenyLaterStrategy::MaxUsesPerBoot)), } } } @@ -49,55 +110,52 @@ fn get_preferred_km_instance_for_level_zero_key() -> Result<KeyMintDevice> { /// In practice the caller is SuperKeyManager and the lock is the /// Mutex on its internal state. pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> { - let km_dev = get_preferred_km_instance_for_level_zero_key() - .context("In get_level_zero_key: get preferred KM instance failed")?; - - let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string()); - let mut params = vec![ + let (km_dev, deny_later_strategy) = get_level_zero_key_km_and_strategy() + .context(ks_err!("get preferred KM instance failed"))?; + log::info!( + "In get_level_zero_key: security_level={:?}, deny_later_strategy={:?}", + km_dev.security_level(), + deny_later_strategy + ); + let required_security_level = km_dev.security_level(); + let required_param: KmKeyParameter = match deny_later_strategy { + DenyLaterStrategy::EarlyBootOnly => KeyParameterValue::EarlyBootOnly, + DenyLaterStrategy::MaxUsesPerBoot => KeyParameterValue::MaxUsesPerBoot(1), + } + .into(); + let params = vec![ KeyParameterValue::Algorithm(Algorithm::HMAC).into(), KeyParameterValue::Digest(Digest::SHA_2_256).into(), KeyParameterValue::KeySize(256).into(), KeyParameterValue::MinMacLength(256).into(), KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(), KeyParameterValue::NoAuthRequired.into(), + required_param.clone(), ]; - let has_early_boot_only = km_dev.version() >= KeyMintDevice::KEY_MASTER_V4_1; - - if has_early_boot_only { - params.push(KeyParameterValue::EarlyBootOnly.into()); - } else { - params.push(KeyParameterValue::MaxUsesPerBoot(1).into()) - } - + let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string()); let (key_id_guard, key_entry) = km_dev .lookup_or_generate_key(db, &key_desc, KeyType::Client, ¶ms, |key_characteristics| { key_characteristics.iter().any(|kc| { - if kc.securityLevel == km_dev.security_level() { - kc.authorizations.iter().any(|a| { - matches!( - (has_early_boot_only, a), - ( - true, - KmKeyParameter { - tag: Tag::EARLY_BOOT_ONLY, - value: KmKeyParameterValue::BoolValue(true) - } - ) | ( - false, - KmKeyParameter { - tag: Tag::MAX_USES_PER_BOOT, - value: KmKeyParameterValue::Integer(1) - } - ) - ) - }) - } else { - false + if kc.securityLevel != required_security_level { + log::error!( + "In get_level_zero_key: security level expected={:?} got={:?}", + required_security_level, + kc.securityLevel + ); + return false; + } + if !kc.authorizations.iter().any(|a| a == &required_param) { + log::error!( + "In get_level_zero_key: required param absent {:?}", + required_param + ); + return false; } + true }) }) - .context("In get_level_zero_key: lookup_or_generate_key failed")?; + .context(ks_err!("lookup_or_generate_key failed"))?; let params = [ KeyParameterValue::MacLength(256).into(), @@ -113,11 +171,11 @@ pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> { None, b"Create boot level key", ) - .context("In get_level_zero_key: use_key_in_one_step failed")?; + .context(ks_err!("use_key_in_one_step failed"))?; // TODO: this is rather unsatisfactory, we need a better way to handle // sensitive binder returns. - let level_zero_key = ZVec::try_from(level_zero_key) - .context("In get_level_zero_key: conversion to ZVec failed")?; + let level_zero_key = + ZVec::try_from(level_zero_key).context(ks_err!("conversion to ZVec failed"))?; Ok(level_zero_key) } @@ -169,7 +227,7 @@ impl BootLevelKeyCache { // so this must unwrap. let highest_key = self.cache.back().unwrap(); let next_key = hkdf_expand(Self::HKDF_KEY_SIZE, highest_key, Self::HKDF_ADVANCE) - .context("In BootLevelKeyCache::get_hkdf_key: Advancing key one step")?; + .context(ks_err!("Advancing key one step"))?; self.cache.push_back(next_key); } @@ -182,10 +240,7 @@ impl BootLevelKeyCache { pub fn advance_boot_level(&mut self, new_boot_level: usize) -> Result<()> { if !self.level_accessible(new_boot_level) { log::error!( - concat!( - "In BootLevelKeyCache::advance_boot_level: ", - "Failed to advance boot level to {}, current is {}, cache size {}" - ), + "Failed to advance boot level to {}, current is {}, cache size {}", new_boot_level, self.current, self.cache.len() @@ -195,8 +250,7 @@ impl BootLevelKeyCache { // We `get` the new boot level for the side effect of advancing the cache to a point // where the new boot level is present. - self.get_hkdf_key(new_boot_level) - .context("In BootLevelKeyCache::advance_boot_level: Advancing cache")?; + self.get_hkdf_key(new_boot_level).context(ks_err!("Advancing cache"))?; // Then we split the queue at the index of the new boot level and discard the front, // keeping only the keys with the current boot level or higher. @@ -222,16 +276,16 @@ impl BootLevelKeyCache { info: &[u8], ) -> Result<Option<ZVec>> { self.get_hkdf_key(boot_level) - .context("In BootLevelKeyCache::expand_key: Looking up HKDF key")? + .context(ks_err!("Looking up HKDF key"))? .map(|k| hkdf_expand(out_len, k, info)) .transpose() - .context("In BootLevelKeyCache::expand_key: Calling hkdf_expand") + .context(ks_err!("Calling hkdf_expand")) } /// Return the AES-256-GCM key for the current boot level. pub fn aes_key(&mut self, boot_level: usize) -> Result<Option<ZVec>> { self.expand_key(boot_level, AES_256_KEY_LENGTH, BootLevelKeyCache::HKDF_AES) - .context("In BootLevelKeyCache::aes_key: expand_key failed") + .context(ks_err!("expand_key failed")) } } diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp index c3f6f3c8..1ac64674 100644 --- a/keystore2/src/crypto/Android.bp +++ b/keystore2/src/crypto/Android.bp @@ -36,6 +36,10 @@ rust_library { "libcrypto", ], vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } cc_library { @@ -50,6 +54,10 @@ cc_library { "liblog", ], vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } rust_bindgen { @@ -86,6 +94,10 @@ rust_bindgen { "--allowlist-var", "EVP_MAX_MD_SIZE", ], cflags: ["-DBORINGSSL_NO_CXX"], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } rust_test { @@ -138,4 +150,8 @@ rust_test { auto_gen_config: true, clippy_lints: "none", lints: "none", + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp index 34a9a403..7feeaff6 100644 --- a/keystore2/src/crypto/crypto.cpp +++ b/keystore2/src/crypto/crypto.cpp @@ -18,6 +18,7 @@ #include "crypto.hpp" +#include <assert.h> #include <log/log.h> #include <openssl/aes.h> #include <openssl/ec.h> @@ -192,16 +193,6 @@ static constexpr size_t SALT_SIZE = 16; void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_t pw_len, const uint8_t* salt) { - size_t saltSize; - if (salt != nullptr) { - saltSize = SALT_SIZE; - } else { - // Pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found - salt = reinterpret_cast<const uint8_t*>("keystore"); - // sizeof = 9, not strlen = 8 - saltSize = sizeof("keystore"); - } - const EVP_MD* digest = EVP_sha256(); // SHA1 was used prior to increasing the key size @@ -209,7 +200,7 @@ void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_ digest = EVP_sha1(); } - PKCS5_PBKDF2_HMAC(pw, pw_len, salt, saltSize, 8192, digest, key_len, key); + PKCS5_PBKDF2_HMAC(pw, pw_len, salt, SALT_SIZE, 8192, digest, key_len, key); } // New code. diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp index d66532f7..4a161e6c 100644 --- a/keystore2/src/crypto/crypto.hpp +++ b/keystore2/src/crypto/crypto.hpp @@ -36,6 +36,7 @@ extern "C" { bool CreateKeyId(const uint8_t* key_blob, size_t len, km_id_t* out_id); + // The salt parameter must be non-nullptr and point to 16 bytes of data. void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_t pw_len, const uint8_t* salt); diff --git a/keystore2/src/crypto/include/certificate_utils.h b/keystore2/src/crypto/include/certificate_utils.h index cad82b61..13d3ef04 100644 --- a/keystore2/src/crypto/include/certificate_utils.h +++ b/keystore2/src/crypto/include/certificate_utils.h @@ -20,6 +20,7 @@ #include <openssl/x509.h> #include <stdint.h> +#include <functional> #include <memory> #include <optional> #include <variant> diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs index 14bdf045..08b7589e 100644 --- a/keystore2/src/crypto/lib.rs +++ b/keystore2/src/crypto/lib.rs @@ -190,31 +190,23 @@ impl<'a> Password<'a> { fn get_key(&'a self) -> &'a [u8] { match self { Self::Ref(b) => b, - Self::Owned(z) => &*z, + Self::Owned(z) => z, } } /// Generate a key from the given password and salt. /// The salt must be exactly 16 bytes long. /// Two key sizes are accepted: 16 and 32 bytes. - pub fn derive_key(&self, salt: Option<&[u8]>, key_length: usize) -> Result<ZVec, Error> { - let pw = self.get_key(); - - let salt: *const u8 = match salt { - Some(s) => { - if s.len() != SALT_LENGTH { - return Err(Error::InvalidSaltLength); - } - s.as_ptr() - } - None => std::ptr::null(), - }; - + pub fn derive_key(&self, salt: &[u8], key_length: usize) -> Result<ZVec, Error> { + if salt.len() != SALT_LENGTH { + return Err(Error::InvalidSaltLength); + } match key_length { AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} _ => return Err(Error::InvalidKeyLength), } + let pw = self.get_key(); let mut result = ZVec::new(key_length)?; unsafe { @@ -223,7 +215,7 @@ impl<'a> Password<'a> { result.len(), pw.as_ptr() as *const std::os::raw::c_char, pw.len(), - salt, + salt.as_ptr(), ) }; @@ -368,8 +360,7 @@ pub fn ec_key_marshal_private_key(key: &ECKey) -> Result<ZVec, Error> { // Safety: the key is valid. // This will not write past the specified length of the buffer; if the // len above is too short, it returns 0. - let written_len = - unsafe { ECKEYMarshalPrivateKey(key.0, buf.as_mut_ptr(), buf.len()) } as usize; + let written_len = unsafe { ECKEYMarshalPrivateKey(key.0, buf.as_mut_ptr(), buf.len()) }; if written_len == len { Ok(buf) } else { @@ -541,9 +532,9 @@ mod tests { fn test_generate_key_from_password() { let mut key = vec![0; 16]; let pw = vec![0; 16]; - let mut salt = vec![0; 16]; + let salt = vec![0; 16]; unsafe { - generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_mut_ptr()); + generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_ptr()); } assert_ne!(key, vec![0; 16]); } diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp index bd949282..a8517987 100644 --- a/keystore2/src/crypto/tests/certificate_utils_test.cpp +++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp @@ -313,7 +313,15 @@ TEST_P(CertificateUtilsWithRsa, CertSigningWithCallbackRsa) { const uint8_t* p = encCert.data(); X509_Ptr decoded_cert(d2i_X509(nullptr, &p, (long)encCert.size())); EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get())); - ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get())); + if ((padding == Padding::PSS) && (digest == Digest::SHA1 || digest == Digest::SHA224)) { + // BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865 + // does not support these PSS combinations, so skip certificate verification for them + // and just check _something_ was returned. + EXPECT_NE(decoded_cert.get(), nullptr); + EXPECT_NE(decoded_pkey.get(), nullptr); + } else { + ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get())); + } } TEST(TimeStringTests, toTimeStringTest) { diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs index a3979bd5..61082211 100644 --- a/keystore2/src/database.rs +++ b/keystore2/src/database.rs @@ -49,6 +49,7 @@ use crate::gc::Gc; use crate::globals::get_keymint_dev_by_uuid; use crate::impl_metadata; // This is in db_utils.rs use crate::key_parameter::{KeyParameter, Tag}; +use crate::ks_err; use crate::metrics_store::log_rkp_error_stats; use crate::permission::KeyPermSet; use crate::utils::{get_current_time_in_milliseconds, watchdog as wd, AID_USER_OFFSET}; @@ -62,19 +63,15 @@ use utils as db_utils; use utils::SqlField; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, -}; -use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, -}; -use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{ - AttestationPoolStatus::AttestationPoolStatus, + HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, + SecurityLevel::SecurityLevel, }; use android_security_metrics::aidl::android::security::metrics::{ + RkpError::RkpError as MetricsRkpError, Storage::Storage as MetricsStorage, StorageStats::StorageStats, - Storage::Storage as MetricsStorage, - RkpError::RkpError as MetricsRkpError, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, }; use keystore2_crypto::ZVec; @@ -133,12 +130,13 @@ impl KeyMetaData { "SELECT tag, data from persistent.keymetadata WHERE keyentryid = ?;", ) - .context("In KeyMetaData::load_from_db: prepare statement failed.")?; + .context(ks_err!("KeyMetaData::load_from_db: prepare statement failed."))?; let mut metadata: HashMap<i64, KeyMetaEntry> = Default::default(); - let mut rows = - stmt.query(params![key_id]).context("In KeyMetaData::load_from_db: query failed.")?; + let mut rows = stmt + .query(params![key_id]) + .context(ks_err!("KeyMetaData::load_from_db: query failed."))?; db_utils::with_rows_extract_all(&mut rows, |row| { let db_tag: i64 = row.get(0).context("Failed to read tag.")?; metadata.insert( @@ -148,7 +146,7 @@ impl KeyMetaData { ); Ok(()) }) - .context("In KeyMetaData::load_from_db.")?; + .context(ks_err!("KeyMetaData::load_from_db."))?; Ok(Self { data: metadata }) } @@ -159,12 +157,12 @@ impl KeyMetaData { "INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data) VALUES (?, ?, ?);", ) - .context("In KeyMetaData::store_in_db: Failed to prepare statement.")?; + .context(ks_err!("KeyMetaData::store_in_db: Failed to prepare statement."))?; let iter = self.data.iter(); for (tag, entry) in iter { stmt.insert(params![key_id, tag, entry,]).with_context(|| { - format!("In KeyMetaData::store_in_db: Failed to insert {:?}", entry) + ks_err!("KeyMetaData::store_in_db: Failed to insert {:?}", entry) })?; } Ok(()) @@ -208,12 +206,11 @@ impl BlobMetaData { "SELECT tag, data from persistent.blobmetadata WHERE blobentryid = ?;", ) - .context("In BlobMetaData::load_from_db: prepare statement failed.")?; + .context(ks_err!("BlobMetaData::load_from_db: prepare statement failed."))?; let mut metadata: HashMap<i64, BlobMetaEntry> = Default::default(); - let mut rows = - stmt.query(params![blob_id]).context("In BlobMetaData::load_from_db: query failed.")?; + let mut rows = stmt.query(params![blob_id]).context(ks_err!("query failed."))?; db_utils::with_rows_extract_all(&mut rows, |row| { let db_tag: i64 = row.get(0).context("Failed to read tag.")?; metadata.insert( @@ -223,7 +220,7 @@ impl BlobMetaData { ); Ok(()) }) - .context("In BlobMetaData::load_from_db.")?; + .context(ks_err!("BlobMetaData::load_from_db"))?; Ok(Self { data: metadata }) } @@ -234,12 +231,12 @@ impl BlobMetaData { "INSERT or REPLACE INTO persistent.blobmetadata (blobentryid, tag, data) VALUES (?, ?, ?);", ) - .context("In BlobMetaData::store_in_db: Failed to prepare statement.")?; + .context(ks_err!("BlobMetaData::store_in_db: Failed to prepare statement.",))?; let iter = self.data.iter(); for (tag, entry) in iter { stmt.insert(params![blob_id, tag, entry,]).with_context(|| { - format!("In BlobMetaData::store_in_db: Failed to insert {:?}", entry) + ks_err!("BlobMetaData::store_in_db: Failed to insert {:?}", entry) })?; } Ok(()) @@ -324,7 +321,7 @@ pub static KEYSTORE_UUID: Uuid = Uuid([ 0x41, 0xe3, 0xb9, 0xce, 0x27, 0x58, 0x4e, 0x91, 0xbc, 0xfd, 0xa5, 0x5d, 0x91, 0x85, 0xab, 0x11, ]); -static EXPIRATION_BUFFER_MS: i64 = 20000; +static EXPIRATION_BUFFER_MS: i64 = 12 * 60 * 60 * 1000; /// Indicates how the sensitive part of this key blob is encrypted. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -829,7 +826,7 @@ impl AuthTokenEntry { pub fn satisfies(&self, user_secure_ids: &[i64], auth_type: HardwareAuthenticatorType) -> bool { user_secure_ids.iter().any(|&sid| { (sid == self.auth_token.userId || sid == self.auth_token.authenticatorId) - && (((auth_type.0 as i32) & (self.auth_token.authenticatorType.0 as i32)) != 0) + && ((auth_type.0 & self.auth_token.authenticatorType.0) != 0) }) } @@ -881,7 +878,7 @@ impl KeystoreDB { let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() }; db.with_transaction(TransactionBehavior::Immediate, |tx| { versioning::upgrade_database(tx, Self::CURRENT_DB_VERSION, Self::UPGRADERS) - .context("In KeystoreDB::new: trying to upgrade database.")?; + .context(ks_err!("KeystoreDB::new: trying to upgrade database."))?; Self::init_tables(tx).context("Trying to initialize tables.").no_gc() })?; Ok(db) @@ -903,7 +900,7 @@ impl KeystoreDB { );", params![KeyLifeCycle::Unreferenced, Tag::MAX_BOOT_LEVEL.0, BlobMetaData::MaxBootLevel], ) - .context("In from_0_to_1: Failed to delete logical boot level keys.")?; + .context(ks_err!("Failed to delete logical boot level keys."))?; Ok(1) } @@ -1064,7 +1061,7 @@ impl KeystoreDB { let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| { tx.query_row(query, params_from_iter(params), |row| Ok((row.get(0)?, row.get(1)?))) .with_context(|| { - format!("get_storage_stat: Error size of storage type {}", storage_type.0) + ks_err!("get_storage_stat: Error size of storage type {}", storage_type.0) }) .no_gc() })?; @@ -1239,7 +1236,7 @@ impl KeystoreDB { Ok(vec![]).no_gc() }) - .context("In handle_next_superseded_blobs.") + .context(ks_err!()) } /// This maintenance function should be called only once before the database is used for the @@ -1261,7 +1258,7 @@ impl KeystoreDB { .context("Failed to execute query.") .need_gc() }) - .context("In cleanup_leftovers.") + .context(ks_err!()) } /// Checks if a key exists with given key type and key descriptor properties. @@ -1282,12 +1279,12 @@ impl KeystoreDB { Ok(_) => Ok(true), Err(error) => match error.root_cause().downcast_ref::<KsError>() { Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(false), - _ => Err(error).context("In key_exists: Failed to find if the key exists."), + _ => Err(error).context(ks_err!("Failed to find if the key exists.")), }, } .no_gc() }) - .context("In key_exists.") + .context(ks_err!()) } /// Stores a super key in the database. @@ -1335,7 +1332,7 @@ impl KeystoreDB { .context("Trying to load key components.") .no_gc() }) - .context("In store_super_key.") + .context(ks_err!()) } /// Loads super key of a given user, if exists @@ -1357,17 +1354,17 @@ impl KeystoreDB { match id { Ok(id) => { let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, id) - .context("In load_super_key. Failed to load key entry.")?; + .context(ks_err!("Failed to load key entry."))?; Ok(Some((KEY_ID_LOCK.get(id), key_entry))) } Err(error) => match error.root_cause().downcast_ref::<KsError>() { Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None), - _ => Err(error).context("In load_super_key."), + _ => Err(error).context(ks_err!()), }, } .no_gc() }) - .context("In load_super_key.") + .context(ks_err!()) } /// Atomically loads a key entry and associated metadata or creates it using the @@ -1399,10 +1396,10 @@ impl KeystoreDB { AND alias = ? AND state = ?;", ) - .context("In get_or_create_key_with: Failed to select from keyentry table.")?; + .context(ks_err!("Failed to select from keyentry table."))?; let mut rows = stmt .query(params![KeyType::Super, domain.0, namespace, alias, KeyLifeCycle::Live]) - .context("In get_or_create_key_with: Failed to query from keyentry table.")?; + .context(ks_err!("Failed to query from keyentry table."))?; db_utils::with_rows_extract_one(&mut rows, |row| { Ok(match row { @@ -1410,14 +1407,13 @@ impl KeystoreDB { None => None, }) }) - .context("In get_or_create_key_with.")? + .context(ks_err!())? }; let (id, entry) = match id { Some(id) => ( id, - Self::load_key_components(tx, KeyEntryLoadBits::KM, id) - .context("In get_or_create_key_with.")?, + Self::load_key_components(tx, KeyEntryLoadBits::KM, id).context(ks_err!())?, ), None => { @@ -1437,10 +1433,9 @@ impl KeystoreDB { ], ) }) - .context("In get_or_create_key_with.")?; + .context(ks_err!())?; - let (blob, metadata) = - create_new_key().context("In get_or_create_key_with.")?; + let (blob, metadata) = create_new_key().context(ks_err!())?; Self::set_blob_internal( tx, id, @@ -1448,7 +1443,7 @@ impl KeystoreDB { Some(&blob), Some(&metadata), ) - .context("In get_or_create_key_with.")?; + .context(ks_err!())?; ( id, KeyEntry { @@ -1462,7 +1457,7 @@ impl KeystoreDB { }; Ok((KEY_ID_LOCK.get(id), entry)).no_gc() }) - .context("In get_or_create_key_with.") + .context(ks_err!()) } /// Creates a transaction with the given behavior and executes f with the new transaction. @@ -1476,10 +1471,10 @@ impl KeystoreDB { match self .conn .transaction_with_behavior(behavior) - .context("In with_transaction.") + .context(ks_err!()) .and_then(|tx| f(&tx).map(|result| (result, tx))) .and_then(|(result, tx)| { - tx.commit().context("In with_transaction: Failed to commit transaction.")?; + tx.commit().context(ks_err!("Failed to commit transaction."))?; Ok(result) }) { Ok(result) => break Ok(result), @@ -1488,7 +1483,7 @@ impl KeystoreDB { std::thread::sleep(std::time::Duration::from_micros(500)); continue; } else { - return Err(e).context("In with_transaction."); + return Err(e).context(ks_err!()); } } } @@ -1529,7 +1524,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { Self::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc() }) - .context("In create_key_entry.") + .context(ks_err!()) } fn create_key_entry_internal( @@ -1543,7 +1538,7 @@ impl KeystoreDB { Domain::APP | Domain::SELINUX => {} _ => { return Err(KsError::sys()) - .context(format!("Domain {:?} must be either App or SELinux.", domain)); + .context(ks_err!("Domain {:?} must be either App or SELinux.", domain)); } } Ok(KEY_ID_LOCK.get( @@ -1562,7 +1557,7 @@ impl KeystoreDB { ], ) }) - .context("In create_key_entry_internal")?, + .context(ks_err!())?, )) } @@ -1590,7 +1585,7 @@ impl KeystoreDB { params![id, KeyType::Attestation, KeyLifeCycle::Live, km_uuid], ) }) - .context("In create_key_entry")?, + .context(ks_err!())?, ); Self::set_blob_internal( tx, @@ -1605,7 +1600,7 @@ impl KeystoreDB { metadata.store_in_db(key_id.0, tx)?; Ok(()).no_gc() }) - .context("In create_attestation_key_entry") + .context(ks_err!()) } /// Set a new blob and associates it with the given key id. Each blob @@ -1627,7 +1622,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc() }) - .context("In set_blob.") + .context(ks_err!()) } /// Why would we insert a deleted blob? This weird function is for the purpose of legacy @@ -1647,7 +1642,7 @@ impl KeystoreDB { ) .need_gc() }) - .context("In set_deleted_blob.") + .context(ks_err!()) } fn set_blob_internal( @@ -1664,16 +1659,16 @@ impl KeystoreDB { (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);", params![sc_type, key_id, blob], ) - .context("In set_blob_internal: Failed to insert blob.")?; + .context(ks_err!("Failed to insert blob."))?; if let Some(blob_metadata) = blob_metadata { let blob_id = tx .query_row("SELECT MAX(id) FROM persistent.blobentry;", NO_PARAMS, |row| { row.get(0) }) - .context("In set_blob_internal: Failed to get new blob id.")?; + .context(ks_err!("Failed to get new blob id."))?; blob_metadata .store_in_db(blob_id, tx) - .context("In set_blob_internal: Trying to store blob metadata.")?; + .context(ks_err!("Trying to store blob metadata."))?; } } (None, SubComponentType::CERT) | (None, SubComponentType::CERT_CHAIN) => { @@ -1682,11 +1677,11 @@ impl KeystoreDB { WHERE subcomponent_type = ? AND keyentryid = ?;", params![sc_type, key_id], ) - .context("In set_blob_internal: Failed to delete blob.")?; + .context(ks_err!("Failed to delete blob."))?; } (None, _) => { return Err(KsError::sys()) - .context("In set_blob_internal: Other blobs cannot be deleted in this way."); + .context(ks_err!("Other blobs cannot be deleted in this way.")); } } Ok(()) @@ -1699,7 +1694,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { Self::insert_keyparameter_internal(tx, key_id, params).no_gc() }) - .context("In insert_keyparameter.") + .context(ks_err!()) } fn insert_keyparameter_internal( @@ -1712,7 +1707,7 @@ impl KeystoreDB { "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) VALUES (?, ?, ?, ?);", ) - .context("In insert_keyparameter_internal: Failed to prepare statement.")?; + .context(ks_err!("Failed to prepare statement."))?; for p in params.iter() { stmt.insert(params![ @@ -1721,9 +1716,7 @@ impl KeystoreDB { p.key_parameter_value(), p.security_level().0 ]) - .with_context(|| { - format!("In insert_keyparameter_internal: Failed to insert {:?}", p) - })?; + .with_context(|| ks_err!("Failed to insert {:?}", p))?; } Ok(()) } @@ -1734,7 +1727,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { metadata.store_in_db(key_id.0, tx).no_gc() }) - .context("In insert_key_metadata.") + .context(ks_err!()) } /// Stores a signed certificate chain signed by a remote provisioning server, keyed @@ -1807,7 +1800,7 @@ impl KeystoreDB { .context("Failed to insert cert")?; Ok(()).no_gc() }) - .context("In store_signed_attestation_certificate_chain: ") + .context(ks_err!()) } /// Assigns the next unassigned attestation key to a domain/namespace combo that does not @@ -1823,13 +1816,8 @@ impl KeystoreDB { match domain { Domain::APP | Domain::SELINUX => {} _ => { - return Err(KsError::sys()).context(format!( - concat!( - "In assign_attestation_key: Domain {:?} ", - "must be either App or SELinux.", - ), - domain - )); + return Err(KsError::sys()) + .context(ks_err!("Domain {:?} must be either App or SELinux.", domain)); } } self.with_transaction(TransactionBehavior::Immediate, |tx| { @@ -1867,14 +1855,15 @@ impl KeystoreDB { let (_, hw_info) = get_keymint_dev_by_uuid(km_uuid) .context("Error in retrieving keymint device by UUID.")?; log_rkp_error_stats(MetricsRkpError::OUT_OF_KEYS, &hw_info.securityLevel); - return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS)).context("Out of keys."); + return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) + .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() }) - .context("In assign_attestation_key: ") + .context(ks_err!()) } /// Retrieves num_keys number of attestation keys that have not yet been signed by a remote @@ -1918,7 +1907,7 @@ impl KeystoreDB { .context("Failed to execute statement")?; Ok(rows).no_gc() }) - .context("In fetch_unsigned_attestation_keys") + .context(ks_err!()) } /// Removes any keys that have expired as of the current time. Returns the number of keys @@ -1958,7 +1947,7 @@ impl KeystoreDB { } Ok(num_deleted).do_gc(num_deleted != 0) }) - .context("In delete_expired_attestation_keys: ") + .context(ks_err!()) } /// Deletes all remotely provisioned attestation keys in the system, regardless of the state @@ -1987,74 +1976,7 @@ impl KeystoreDB { .count() as i64; Ok(num_deleted).do_gc(num_deleted != 0) }) - .context("In delete_all_attestation_keys: ") - } - - /// Counts the number of keys that will expire by the provided epoch date and the number of - /// keys not currently assigned to a domain. - pub fn get_attestation_pool_status( - &mut self, - date: i64, - km_uuid: &Uuid, - ) -> Result<AttestationPoolStatus> { - let _wp = wd::watch_millis("KeystoreDB::get_attestation_pool_status", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx.prepare( - "SELECT data - FROM persistent.keymetadata - WHERE tag = ? AND keyentryid IN - (SELECT id - FROM persistent.keyentry - WHERE alias IS NOT NULL - AND key_type = ? - AND km_uuid = ? - AND state = ?);", - )?; - let times = stmt - .query_map( - params![ - KeyMetaData::AttestationExpirationDate, - KeyType::Attestation, - km_uuid, - KeyLifeCycle::Live - ], - |row| row.get(0), - )? - .collect::<rusqlite::Result<Vec<DateTime>>>() - .context("Failed to execute metadata statement")?; - let expiring = - times.iter().filter(|time| time < &&DateTime::from_millis_epoch(date)).count() - as i32; - stmt = tx.prepare( - "SELECT alias, domain - FROM persistent.keyentry - WHERE key_type = ? AND km_uuid = ? AND state = ?;", - )?; - let rows = stmt - .query_map(params![KeyType::Attestation, km_uuid, KeyLifeCycle::Live], |row| { - Ok((row.get(0)?, row.get(1)?)) - })? - .collect::<rusqlite::Result<Vec<(Option<String>, Option<u32>)>>>() - .context("Failed to execute keyentry statement")?; - let mut unassigned = 0i32; - let mut attested = 0i32; - let total = rows.len() as i32; - for (alias, domain) in rows { - match (alias, domain) { - (Some(_alias), None) => { - attested += 1; - unassigned += 1; - } - (Some(_alias), Some(_domain)) => { - attested += 1; - } - _ => {} - } - } - Ok(AttestationPoolStatus { expiring, unassigned, attested, total }).no_gc() - }) - .context("In get_attestation_pool_status: ") + .context(ks_err!()) } fn query_kid_for_attestation_key_and_cert_chain( @@ -2111,24 +2033,24 @@ impl KeystoreDB { } } - self.delete_expired_attestation_keys().context( - "In retrieve_attestation_key_and_cert_chain: failed to prune expired attestation keys", - )?; - let tx = self.conn.unchecked_transaction().context( - "In retrieve_attestation_key_and_cert_chain: Failed to initialize transaction.", - )?; + self.delete_expired_attestation_keys() + .context(ks_err!("Failed to prune expired attestation keys",))?; + let tx = self + .conn + .unchecked_transaction() + .context(ks_err!("Failed to initialize transaction."))?; let key_id: i64 = match self .query_kid_for_attestation_key_and_cert_chain(&tx, domain, namespace, km_uuid)? { None => return Ok(None), Some(kid) => kid, }; - tx.commit() - .context("In retrieve_attestation_key_and_cert_chain: Failed to commit keyid query")?; + tx.commit().context(ks_err!("Failed to commit keyid query"))?; let key_id_guard = KEY_ID_LOCK.get(key_id); - let tx = self.conn.unchecked_transaction().context( - "In retrieve_attestation_key_and_cert_chain: Failed to initialize transaction.", - )?; + let tx = self + .conn + .unchecked_transaction() + .context(ks_err!("Failed to initialize transaction."))?; let mut stmt = tx.prepare( "SELECT subcomponent_type, blob FROM persistent.blobentry @@ -2193,10 +2115,8 @@ impl KeystoreDB { match *domain { Domain::APP | Domain::SELINUX => {} _ => { - return Err(KsError::sys()).context(format!( - "In rebind_alias: Domain {:?} must be either App or SELinux.", - domain - )); + return Err(KsError::sys()) + .context(ks_err!("Domain {:?} must be either App or SELinux.", domain)); } } let updated = tx @@ -2206,7 +2126,7 @@ impl KeystoreDB { WHERE alias = ? AND domain = ? AND namespace = ? AND key_type = ?;", params![KeyLifeCycle::Unreferenced, alias, domain.0 as u32, namespace, key_type], ) - .context("In rebind_alias: Failed to rebind existing entry.")?; + .context(ks_err!("Failed to rebind existing entry."))?; let result = tx .execute( "UPDATE persistent.keyentry @@ -2222,10 +2142,10 @@ impl KeystoreDB { key_type, ], ) - .context("In rebind_alias: Failed to set alias.")?; + .context(ks_err!("Failed to set alias."))?; if result != 1 { - return Err(KsError::sys()).context(format!( - "In rebind_alias: Expected to update a single entry but instead updated {}.", + return Err(KsError::sys()).context(ks_err!( + "Expected to update a single entry but instead updated {}.", result )); } @@ -2253,14 +2173,13 @@ impl KeystoreDB { }; // Security critical: Must return immediately on failure. Do not remove the '?'; - check_permission(&destination) - .context("In migrate_key_namespace: Trying to check permission.")?; + check_permission(&destination).context(ks_err!("Trying to check permission."))?; let alias = destination .alias .as_ref() .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("In migrate_key_namespace: Alias must be specified.")?; + .context(ks_err!("Alias must be specified."))?; self.with_transaction(TransactionBehavior::Immediate, |tx| { // Query the destination location. If there is a key, the migration request fails. @@ -2294,7 +2213,7 @@ impl KeystoreDB { } Ok(()).no_gc() }) - .context("In migrate_key_namespace:") + .context(ks_err!()) } /// Store a new key in a single transaction. @@ -2322,7 +2241,7 @@ impl KeystoreDB { } _ => { return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("In store_new_key: Need alias and domain must be APP or SELINUX.") + .context(ks_err!("Need alias and domain must be APP or SELINUX.")); } }; self.with_transaction(TransactionBehavior::Immediate, |tx| { @@ -2379,7 +2298,7 @@ impl KeystoreDB { || need_gc; Ok(key_id).do_gc(need_gc) }) - .context("In store_new_key.") + .context(ks_err!()) } /// Store a new certificate @@ -2400,9 +2319,8 @@ impl KeystoreDB { (alias, key.domain, nspace) } _ => { - return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In store_new_certificate: Need alias and domain must be APP or SELINUX.", - ) + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(ks_err!("Need alias and domain must be APP or SELINUX.")); } }; self.with_transaction(TransactionBehavior::Immediate, |tx| { @@ -2429,7 +2347,7 @@ impl KeystoreDB { .context("Trying to rebind alias.")?; Ok(key_id).do_gc(need_gc) }) - .context("In store_new_certificate.") + .context(ks_err!()) } // Helper function loading the key_id given the key descriptor @@ -2460,7 +2378,7 @@ impl KeystoreDB { .get(0) .context("Failed to unpack id.") }) - .context("In load_key_entry_id.") + .context(ks_err!()) } /// This helper function completes the access tuple of a key, which is required @@ -2581,7 +2499,7 @@ impl KeystoreDB { Ok((key_id, access_key, access_vector)) } - _ => Err(anyhow!(KsError::sys())), + _ => Err(anyhow!(KsError::Rc(ResponseCode::INVALID_ARGUMENT))), } } @@ -2595,10 +2513,9 @@ impl KeystoreDB { "SELECT MAX(id), subcomponent_type, blob FROM persistent.blobentry WHERE keyentryid = ? GROUP BY subcomponent_type;", ) - .context("In load_blob_components: prepare statement failed.")?; + .context(ks_err!("prepare statement failed."))?; - let mut rows = - stmt.query(params![key_id]).context("In load_blob_components: query failed.")?; + let mut rows = stmt.query(params![key_id]).context(ks_err!("query failed."))?; let mut key_blob: Option<(i64, Vec<u8>)> = None; let mut cert_blob: Option<Vec<u8>> = None; @@ -2630,13 +2547,13 @@ impl KeystoreDB { } Ok(()) }) - .context("In load_blob_components.")?; + .context(ks_err!())?; let blob_info = key_blob.map_or::<Result<_>, _>(Ok(None), |(blob_id, blob)| { Ok(Some(( blob, BlobMetaData::load_from_db(blob_id, tx) - .context("In load_blob_components: Trying to load blob_metadata.")?, + .context(ks_err!("Trying to load blob_metadata."))?, ))) })?; @@ -2664,7 +2581,7 @@ impl KeystoreDB { ); Ok(()) }) - .context("In load_key_parameters.")?; + .context(ks_err!())?; Ok(parameters) } @@ -2706,7 +2623,7 @@ impl KeystoreDB { _ => Ok(()).no_gc(), } }) - .context("In check_and_update_key_usage_count.") + .context(ks_err!()) } /// Load a key entry by the given key descriptor. @@ -2738,7 +2655,7 @@ impl KeystoreDB { std::thread::sleep(std::time::Duration::from_micros(500)); continue; } else { - return Err(e).context("In load_key_entry."); + return Err(e).context(ks_err!()); } } } @@ -2764,16 +2681,15 @@ impl KeystoreDB { let tx = self .conn .unchecked_transaction() - .context("In load_key_entry: Failed to initialize transaction.")?; + .context(ks_err!("Failed to initialize transaction."))?; // Load the key_id and complete the access control tuple. let (key_id, access_key_descriptor, access_vector) = - Self::load_access_tuple(&tx, key, key_type, caller_uid) - .context("In load_key_entry.")?; + Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?; // Perform access control. It is vital that we return here if the permission is denied. // So do not touch that '?' at the end. - check_permission(&access_key_descriptor, access_vector).context("In load_key_entry.")?; + check_permission(&access_key_descriptor, access_vector).context(ks_err!())?; // KEY ID LOCK 2/2 // If we did not get a key id lock by now, it was because we got a key descriptor @@ -2790,7 +2706,7 @@ impl KeystoreDB { None => match KEY_ID_LOCK.try_get(key_id) { None => { // Roll back the transaction. - tx.rollback().context("In load_key_entry: Failed to roll back transaction.")?; + tx.rollback().context(ks_err!("Failed to roll back transaction."))?; // Block until we have a key id lock. let key_id_guard = KEY_ID_LOCK.get(key_id); @@ -2799,7 +2715,7 @@ impl KeystoreDB { let tx = self .conn .unchecked_transaction() - .context("In load_key_entry: Failed to initialize transaction.")?; + .context(ks_err!("Failed to initialize transaction."))?; Self::load_access_tuple( &tx, @@ -2813,7 +2729,7 @@ impl KeystoreDB { key_type, caller_uid, ) - .context("In load_key_entry. (deferred key lock)")?; + .context(ks_err!("(deferred key lock)"))?; (key_id_guard, tx) } Some(l) => (l, tx), @@ -2821,10 +2737,10 @@ impl KeystoreDB { Some(key_id_guard) => (key_id_guard, tx), }; - let key_entry = Self::load_key_components(&tx, load_bits, key_id_guard.id()) - .context("In load_key_entry.")?; + let key_entry = + Self::load_key_components(&tx, load_bits, key_id_guard.id()).context(ks_err!())?; - tx.commit().context("In load_key_entry: Failed to commit transaction.")?; + tx.commit().context(ks_err!("Failed to commit transaction."))?; Ok((key_id_guard, key_entry)) } @@ -2867,7 +2783,7 @@ impl KeystoreDB { .map(|need_gc| (need_gc, ())) .context("Trying to mark the key unreferenced.") }) - .context("In unbind_key.") + .context(ks_err!()) } fn get_key_km_uuid(tx: &Transaction, key_id: i64) -> Result<Uuid> { @@ -2876,7 +2792,7 @@ impl KeystoreDB { params![key_id], |row| row.get(0), ) - .context("In get_key_km_uuid.") + .context(ks_err!()) } /// Delete all artifacts belonging to the namespace given by the domain-namespace tuple. @@ -2885,8 +2801,7 @@ impl KeystoreDB { let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_namespace", 500); if !(domain == Domain::APP || domain == Domain::SELINUX) { - return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("In unbind_keys_for_namespace."); + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!()); } self.with_transaction(TransactionBehavior::Immediate, |tx| { tx.execute( @@ -2924,7 +2839,7 @@ impl KeystoreDB { .context("Trying to delete keyentry.")?; Ok(()).need_gc() }) - .context("In unbind_keys_for_namespace") + .context(ks_err!()) } fn cleanup_unreferenced(tx: &Transaction) -> Result<()> { @@ -2965,7 +2880,7 @@ impl KeystoreDB { .context("Trying to delete keyentry.")?; Result::<()>::Ok(()) } - .context("In cleanup_unreferenced") + .context(ks_err!()) } /// Delete the keys created on behalf of the user, denoted by the user id. @@ -3012,7 +2927,7 @@ impl KeystoreDB { user_id, KeyLifeCycle::Live ]) - .context("In unbind_keys_for_user. Failed to query the keys created by apps.")?; + .context(ks_err!("Failed to query the keys created by apps."))?; let mut key_ids: Vec<i64> = Vec::new(); db_utils::with_rows_extract_all(&mut rows, |row| { @@ -3020,7 +2935,7 @@ impl KeystoreDB { .push(row.get(0).context("Failed to read key id of a key created by an app.")?); Ok(()) }) - .context("In unbind_keys_for_user.")?; + .context(ks_err!())?; let mut notify_gc = false; for key_id in key_ids { @@ -3028,7 +2943,7 @@ impl KeystoreDB { // Load metadata and filter out non-super-encrypted keys. if let (_, Some((_, blob_metadata)), _, _) = Self::load_blob_components(key_id, KeyEntryLoadBits::KM, tx) - .context("In unbind_keys_for_user: Trying to load blob info.")? + .context(ks_err!("Trying to load blob info."))? { if blob_metadata.encrypted_by().is_none() { continue; @@ -3041,7 +2956,7 @@ impl KeystoreDB { } Ok(()).do_gc(notify_gc) }) - .context("In unbind_keys_for_user.") + .context(ks_err!()) } fn load_key_components( @@ -3072,32 +2987,50 @@ impl KeystoreDB { }) } - /// Returns a list of KeyDescriptors in the selected domain/namespace. + /// Returns a list of KeyDescriptors in the selected domain/namespace whose + /// aliases are greater than the specified 'start_past_alias'. If no value + /// is provided, returns all KeyDescriptors. /// The key descriptors will have the domain, nspace, and alias field set. + /// The returned list will be sorted by alias. /// Domain must be APP or SELINUX, the caller must make sure of that. - pub fn list( + pub fn list_past_alias( &mut self, domain: Domain, namespace: i64, key_type: KeyType, + start_past_alias: Option<&str>, ) -> Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("KeystoreDB::list", 500); + let _wp = wd::watch_millis("KeystoreDB::list_past_alias", 500); - self.with_transaction(TransactionBehavior::Deferred, |tx| { - let mut stmt = tx - .prepare( - "SELECT alias FROM persistent.keyentry + let query = format!( + "SELECT DISTINCT alias FROM persistent.keyentry WHERE domain = ? AND namespace = ? AND alias IS NOT NULL AND state = ? - AND key_type = ?;", - ) - .context("In list: Failed to prepare.")?; + AND key_type = ? + {} + ORDER BY alias ASC;", + if start_past_alias.is_some() { " AND alias > ?" } else { "" } + ); - let mut rows = stmt - .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type]) - .context("In list: Failed to query.")?; + self.with_transaction(TransactionBehavior::Deferred, |tx| { + let mut stmt = tx.prepare(&query).context(ks_err!("Failed to prepare."))?; + + let mut rows = match start_past_alias { + Some(past_alias) => stmt + .query(params![ + domain.0 as u32, + namespace, + KeyLifeCycle::Live, + key_type, + past_alias + ]) + .context(ks_err!("Failed to query."))?, + None => stmt + .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type,]) + .context(ks_err!("Failed to query."))?, + }; let mut descriptors: Vec<KeyDescriptor> = Vec::new(); db_utils::with_rows_extract_all(&mut rows, |row| { @@ -3109,11 +3042,38 @@ impl KeystoreDB { }); Ok(()) }) - .context("In list: Failed to extract rows.")?; + .context(ks_err!("Failed to extract rows."))?; Ok(descriptors).no_gc() }) } + /// Returns a number of KeyDescriptors in the selected domain/namespace. + /// Domain must be APP or SELINUX, the caller must make sure of that. + pub fn count_keys( + &mut self, + domain: Domain, + namespace: i64, + key_type: KeyType, + ) -> Result<usize> { + let _wp = wd::watch_millis("KeystoreDB::countKeys", 500); + + let num_keys = self.with_transaction(TransactionBehavior::Deferred, |tx| { + tx.query_row( + "SELECT COUNT(alias) FROM persistent.keyentry + WHERE domain = ? + AND namespace = ? + AND alias IS NOT NULL + AND state = ? + AND key_type = ?;", + params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type], + |row| row.get(0), + ) + .context(ks_err!("Failed to count number of keys.")) + .no_gc() + })?; + Ok(num_keys) + } + /// Adds a grant to the grant table. /// Like `load_key_entry` this function loads the access tuple before /// it uses the callback for a permission check. Upon success, @@ -3141,8 +3101,7 @@ impl KeystoreDB { // But even if we load the access tuple by grant here, the permission // check denies the attempt to create a grant by grant descriptor. let (key_id, access_key_descriptor, _) = - Self::load_access_tuple(tx, key, KeyType::Client, caller_uid) - .context("In grant")?; + Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?; // Perform access control. It is vital that we return here if the permission // was denied. So do not touch that '?' at the end of the line. @@ -3150,7 +3109,7 @@ impl KeystoreDB { // for the given key and in addition to all of the permissions // expressed in `access_vector`. check_permission(&access_key_descriptor, &access_vector) - .context("In grant: check_permission failed.")?; + .context(ks_err!("check_permission failed"))?; let grant_id = if let Some(grant_id) = tx .query_row( @@ -3160,7 +3119,7 @@ impl KeystoreDB { |row| row.get(0), ) .optional() - .context("In grant: Failed get optional existing grant id.")? + .context(ks_err!("Failed get optional existing grant id."))? { tx.execute( "UPDATE persistent.grant @@ -3168,7 +3127,7 @@ impl KeystoreDB { WHERE id = ?;", params![i32::from(access_vector), grant_id], ) - .context("In grant: Failed to update existing grant.")?; + .context(ks_err!("Failed to update existing grant."))?; grant_id } else { Self::insert_with_retry(|id| { @@ -3178,7 +3137,7 @@ impl KeystoreDB { params![id, grantee_uid, key_id, i32::from(access_vector)], ) }) - .context("In grant")? + .context(ks_err!())? }; Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None }) @@ -3201,13 +3160,12 @@ impl KeystoreDB { // Load the key_id and complete the access control tuple. // We ignore the access vector here because grants cannot be granted. let (key_id, access_key_descriptor, _) = - Self::load_access_tuple(tx, key, KeyType::Client, caller_uid) - .context("In ungrant.")?; + Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?; // Perform access control. We must return here if the permission // was denied. So do not touch the '?' at the end of this line. check_permission(&access_key_descriptor) - .context("In grant: check_permission failed.")?; + .context(ks_err!("check_permission failed."))?; tx.execute( "DELETE FROM persistent.grant @@ -3239,7 +3197,7 @@ impl KeystoreDB { _, )) => (), Err(e) => { - return Err(e).context("In insert_with_retry: failed to insert into database.") + return Err(e).context(ks_err!("failed to insert into database.")); } _ => return Ok(newid), } @@ -3298,7 +3256,7 @@ impl KeystoreDB { .context("Trying to load key descriptor") .no_gc() }) - .context("In load_key_descriptor.") + .context(ks_err!()) } } @@ -3366,7 +3324,7 @@ pub mod tests { db.with_transaction(TransactionBehavior::Immediate, |tx| { KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc() }) - .context("In rebind_alias.") + .context(ks_err!()) } #[test] @@ -3518,15 +3476,15 @@ pub mod tests { // Test that we must pass in a valid Domain. check_result_is_error_containing_string( db.create_key_entry(&Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID), - "Domain Domain(1) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), ); check_result_is_error_containing_string( db.create_key_entry(&Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID), - "Domain Domain(3) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), ); check_result_is_error_containing_string( db.create_key_entry(&Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID), - "Domain Domain(4) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), ); Ok(()) @@ -3572,62 +3530,6 @@ pub mod tests { } #[test] - fn test_get_attestation_pool_status() -> Result<()> { - let mut db = new_test_db()?; - let namespace: i64 = 30; - load_attestation_key_pool( - &mut db, 10, /* expiration */ - namespace, 0x01, /* base_byte */ - )?; - load_attestation_key_pool(&mut db, 20 /* expiration */, namespace + 1, 0x02)?; - load_attestation_key_pool(&mut db, 40 /* expiration */, namespace + 2, 0x03)?; - let mut status = db.get_attestation_pool_status(9 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.expiring, 0); - assert_eq!(status.attested, 3); - assert_eq!(status.unassigned, 0); - assert_eq!(status.total, 3); - assert_eq!( - db.get_attestation_pool_status(15 /* expiration */, &KEYSTORE_UUID)?.expiring, - 1 - ); - assert_eq!( - db.get_attestation_pool_status(25 /* expiration */, &KEYSTORE_UUID)?.expiring, - 2 - ); - assert_eq!( - db.get_attestation_pool_status(60 /* expiration */, &KEYSTORE_UUID)?.expiring, - 3 - ); - let public_key: Vec<u8> = vec![0x01, 0x02, 0x03]; - let private_key: Vec<u8> = vec![0x04, 0x05, 0x06]; - let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09]; - let cert_chain: Vec<u8> = vec![0x0a, 0x0b, 0x0c]; - let batch_cert: Vec<u8> = vec![0x0d, 0x0e, 0x0f]; - db.create_attestation_key_entry( - &public_key, - &raw_public_key, - &private_key, - &KEYSTORE_UUID, - )?; - status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.attested, 3); - assert_eq!(status.unassigned, 0); - assert_eq!(status.total, 4); - db.store_signed_attestation_certificate_chain( - &raw_public_key, - &batch_cert, - &cert_chain, - 20, - &KEYSTORE_UUID, - )?; - status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.attested, 4); - assert_eq!(status.unassigned, 1); - assert_eq!(status.total, 4); - Ok(()) - } - - #[test] fn test_remove_expired_certs() -> Result<()> { let temp_dir = TempDir::new("test_remove_expired_certs_").expect("Failed to create temp dir."); @@ -3825,15 +3727,15 @@ pub mod tests { // Test that we must pass in a valid Domain. check_result_is_error_containing_string( rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42), - "Domain Domain(1) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), ); check_result_is_error_containing_string( rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42), - "Domain Domain(3) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), ); check_result_is_error_containing_string( rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42), - "Domain Domain(4) must be either App or SELinux.", + &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), ); // Test that we correctly handle setting an alias for something that does not exist. @@ -4579,7 +4481,7 @@ pub mod tests { DESTINATION_UID, |k, av| { assert_eq!(Domain::SELINUX, k.domain); - assert_eq!(DESTINATION_NAMESPACE as i64, k.nspace); + assert_eq!(DESTINATION_NAMESPACE, k.nspace); assert!(av.is_none()); Ok(()) }, @@ -5039,8 +4941,8 @@ pub mod tests { let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES .iter() .map(|(domain, ns, alias)| { - let entry = make_test_key_entry(&mut db, *domain, *ns, *alias, None) - .unwrap_or_else(|e| { + let entry = + make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| { panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e) }); (entry.id(), *ns) @@ -5063,7 +4965,7 @@ pub mod tests { }) .collect(); list_o_descriptors.sort(); - let mut list_result = db.list(*domain, *namespace, KeyType::Client)?; + let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?; list_result.sort(); assert_eq!(list_o_descriptors, list_result); @@ -5093,7 +4995,10 @@ pub mod tests { loaded_entries.sort_unstable(); assert_eq!(list_o_ids, loaded_entries); } - assert_eq!(Vec::<KeyDescriptor>::new(), db.list(Domain::SELINUX, 101, KeyType::Client)?); + assert_eq!( + Vec::<KeyDescriptor>::new(), + db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)? + ); Ok(()) } @@ -5352,6 +5257,10 @@ pub mod tests { SecurityLevel::TRUSTED_ENVIRONMENT, ), KeyParameter::new( + KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]), SecurityLevel::TRUSTED_ENVIRONMENT, ), @@ -5613,11 +5522,11 @@ pub mod tests { make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?; db.unbind_keys_for_user(2, false)?; - assert_eq!(1, db.list(Domain::APP, 110000, KeyType::Client)?.len()); - assert_eq!(0, db.list(Domain::APP, 210000, KeyType::Client)?.len()); + assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); + assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len()); db.unbind_keys_for_user(1, true)?; - assert_eq!(0, db.list(Domain::APP, 110000, KeyType::Client)?.len()); + assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); Ok(()) } diff --git a/keystore2/src/ec_crypto.rs b/keystore2/src/ec_crypto.rs index 0425d4a5..4fb37473 100644 --- a/keystore2/src/ec_crypto.rs +++ b/keystore2/src/ec_crypto.rs @@ -14,6 +14,7 @@ //! Implement ECDH-based encryption. +use crate::ks_err; use anyhow::{Context, Result}; use keystore2_crypto::{ aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key, @@ -28,29 +29,23 @@ pub struct ECDHPrivateKey(ECKey); impl ECDHPrivateKey { /// Randomly generate a fresh keypair. pub fn generate() -> Result<ECDHPrivateKey> { - ec_key_generate_key() - .map(ECDHPrivateKey) - .context("In ECDHPrivateKey::generate: generation failed") + ec_key_generate_key().map(ECDHPrivateKey).context(ks_err!("generation failed")) } /// Deserialize bytes into an ECDH keypair pub fn from_private_key(buf: &[u8]) -> Result<ECDHPrivateKey> { - ec_key_parse_private_key(buf) - .map(ECDHPrivateKey) - .context("In ECDHPrivateKey::from_private_key: parsing failed") + ec_key_parse_private_key(buf).map(ECDHPrivateKey).context(ks_err!("parsing failed")) } /// Serialize the ECDH key into bytes pub fn private_key(&self) -> Result<ZVec> { - ec_key_marshal_private_key(&self.0) - .context("In ECDHPrivateKey::private_key: marshalling failed") + ec_key_marshal_private_key(&self.0).context(ks_err!("marshalling failed")) } /// Generate the serialization of the corresponding public key pub fn public_key(&self) -> Result<Vec<u8>> { let point = ec_key_get0_public_key(&self.0); - ec_point_point_to_oct(point.get_point()) - .context("In ECDHPrivateKey::public_key: marshalling failed") + ec_point_point_to_oct(point.get_point()).context(ks_err!("marshalling failed")) } /// Use ECDH to agree an AES key with another party whose public key we have. @@ -64,18 +59,17 @@ impl ECDHPrivateKey { recipient_public_key: &[u8], ) -> Result<ZVec> { let hkdf = hkdf_extract(sender_public_key, salt) - .context("In ECDHPrivateKey::agree_key: hkdf_extract on sender_public_key failed")?; + .context(ks_err!("hkdf_extract on sender_public_key failed"))?; let hkdf = hkdf_extract(recipient_public_key, &hkdf) - .context("In ECDHPrivateKey::agree_key: hkdf_extract on recipient_public_key failed")?; + .context(ks_err!("hkdf_extract on recipient_public_key failed"))?; let other_public_key = ec_point_oct_to_point(other_public_key) - .context("In ECDHPrivateKey::agree_key: ec_point_oct_to_point failed")?; + .context(ks_err!("ec_point_oct_to_point failed"))?; let secret = ecdh_compute_key(other_public_key.get_point(), &self.0) - .context("In ECDHPrivateKey::agree_key: ecdh_compute_key failed")?; - let prk = hkdf_extract(&secret, &hkdf) - .context("In ECDHPrivateKey::agree_key: hkdf_extract on secret failed")?; + .context(ks_err!("ecdh_compute_key failed"))?; + let prk = hkdf_extract(&secret, &hkdf).context(ks_err!("hkdf_extract on secret failed"))?; let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key") - .context("In ECDHPrivateKey::agree_key: hkdf_expand failed")?; + .context(ks_err!("hkdf_expand failed"))?; Ok(aes_key) } @@ -84,18 +78,14 @@ impl ECDHPrivateKey { recipient_public_key: &[u8], message: &[u8], ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)> { - let sender_key = - Self::generate().context("In ECDHPrivateKey::encrypt_message: generate failed")?; - let sender_public_key = sender_key - .public_key() - .context("In ECDHPrivateKey::encrypt_message: public_key failed")?; - let salt = - generate_salt().context("In ECDHPrivateKey::encrypt_message: generate_salt failed")?; + let sender_key = Self::generate().context(ks_err!("generate failed"))?; + let sender_public_key = sender_key.public_key().context(ks_err!("public_key failed"))?; + let salt = generate_salt().context(ks_err!("generate_salt failed"))?; let aes_key = sender_key .agree_key(&salt, recipient_public_key, &sender_public_key, recipient_public_key) - .context("In ECDHPrivateKey::encrypt_message: agree_key failed")?; - let (ciphertext, iv, tag) = aes_gcm_encrypt(message, &aes_key) - .context("In ECDHPrivateKey::encrypt_message: aes_gcm_encrypt failed")?; + .context(ks_err!("agree_key failed"))?; + let (ciphertext, iv, tag) = + aes_gcm_encrypt(message, &aes_key).context(ks_err!("aes_gcm_encrypt failed"))?; Ok((sender_public_key, salt, iv, ciphertext, tag)) } @@ -111,9 +101,8 @@ impl ECDHPrivateKey { let recipient_public_key = self.public_key()?; let aes_key = self .agree_key(salt, sender_public_key, sender_public_key, &recipient_public_key) - .context("In ECDHPrivateKey::decrypt_message: agree_key failed")?; - aes_gcm_decrypt(ciphertext, iv, tag, &aes_key) - .context("In ECDHPrivateKey::decrypt_message: aes_gcm_decrypt failed") + .context(ks_err!("agree_key failed"))?; + aes_gcm_decrypt(ciphertext, iv, tag, &aes_key).context(ks_err!("aes_gcm_decrypt failed")) } } diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs index cb6a2667..8d5e9855 100644 --- a/keystore2/src/enforcements.rs +++ b/keystore2/src/enforcements.rs @@ -14,6 +14,7 @@ //! This is the Keystore 2.0 Enforcements module. // TODO: more description to follow. +use crate::ks_err; use crate::error::{map_binder_status, Error, ErrorCode}; use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS}; use crate::key_parameter::{KeyParameter, KeyParameterValue}; @@ -95,14 +96,14 @@ impl AuthRequest { .unwrap() .take() .ok_or(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)) - .context("In get_auth_tokens: No operation auth token received.")?; + .context(ks_err!("No operation auth token received."))?; let tst = match &self.state { AuthRequestState::TimeStampedOpAuth(recv) | AuthRequestState::TimeStamp(recv) => { let result = recv.recv().context("In get_auth_tokens: Sender disconnected.")?; - Some(result.context(concat!( - "In get_auth_tokens: Worker responded with error ", - "from generating timestamp token." + Some(result.context(ks_err!( + "Worker responded with error \ + from generating timestamp token.", ))?) } AuthRequestState::OpAuth => None, @@ -228,10 +229,7 @@ fn get_timestamp_token(challenge: i64) -> Result<TimeStampToken, Error> { fn timestamp_token_request(challenge: i64, sender: Sender<Result<TimeStampToken, Error>>) { if let Err(e) = sender.send(get_timestamp_token(challenge)) { log::info!( - concat!( - "In timestamp_token_request: Receiver hung up ", - "before timestamp token could be delivered. {:?}" - ), + concat!("Receiver hung up ", "before timestamp token could be delivered. {:?}"), e ); } @@ -322,7 +320,7 @@ impl AuthInfo { .check_and_update_key_usage_count(key_id) .context("Trying to update key usage count.") }) - .context("In after_finish.")?; + .context(ks_err!())?; } Ok(()) } @@ -349,14 +347,14 @@ impl AuthInfo { DeferredAuthState::OpAuthRequired | DeferredAuthState::TimeStampedOpAuthRequired | DeferredAuthState::TimeStampRequired(_) => { - Err(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(concat!( - "In AuthInfo::get_auth_tokens: No operation auth token requested??? ", - "This should not happen." + Err(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!( + "No operation auth token requested??? \ + This should not happen." )) } // This should not be reachable, because it should have been handled above. DeferredAuthState::Waiting(_) => { - Err(Error::sys()).context("In AuthInfo::get_auth_tokens: Cannot be reached.") + Err(Error::sys()).context(ks_err!("AuthInfo::get_auth_tokens: Cannot be reached.",)) } } } @@ -418,7 +416,7 @@ impl Enforcements { key_usage_limited: None, confirmation_token_receiver: None, }, - )) + )); } }; @@ -428,7 +426,7 @@ impl Enforcements { // Rule out WRAP_KEY purpose KeyPurpose::WRAP_KEY => { return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE)) - .context("In authorize_create: WRAP_KEY purpose is not allowed here."); + .context(ks_err!("WRAP_KEY purpose is not allowed here.",)); } // Allow AGREE_KEY for EC keys only. KeyPurpose::AGREE_KEY => { @@ -436,9 +434,8 @@ impl Enforcements { if kp.get_tag() == Tag::ALGORITHM && *kp.key_parameter_value() != KeyParameterValue::Algorithm(Algorithm::EC) { - return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context( - "In authorize_create: key agreement is only supported for EC keys.", - ); + return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)) + .context(ks_err!("key agreement is only supported for EC keys.",)); } } } @@ -449,10 +446,10 @@ impl Enforcements { match *kp.key_parameter_value() { KeyParameterValue::Algorithm(Algorithm::RSA) | KeyParameterValue::Algorithm(Algorithm::EC) => { - return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context( - "In authorize_create: public operations on asymmetric keys are not \ - supported.", - ); + return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context(ks_err!( + "public operations on asymmetric keys are \ + not supported." + )); } _ => {} } @@ -460,7 +457,7 @@ impl Enforcements { } _ => { return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)) - .context("In authorize_create: specified purpose is not supported."); + .context(ks_err!("authorize_create: specified purpose is not supported.")); } } // The following variables are to record information from key parameters to be used in @@ -505,23 +502,21 @@ impl Enforcements { KeyParameterValue::ActiveDateTime(a) => { if !Enforcements::is_given_time_passed(*a, true) { return Err(Error::Km(Ec::KEY_NOT_YET_VALID)) - .context("In authorize_create: key is not yet active."); + .context(ks_err!("key is not yet active.")); } } KeyParameterValue::OriginationExpireDateTime(o) => { if (purpose == KeyPurpose::ENCRYPT || purpose == KeyPurpose::SIGN) && Enforcements::is_given_time_passed(*o, false) { - return Err(Error::Km(Ec::KEY_EXPIRED)) - .context("In authorize_create: key is expired."); + return Err(Error::Km(Ec::KEY_EXPIRED)).context(ks_err!("key is expired.")); } } KeyParameterValue::UsageExpireDateTime(u) => { if (purpose == KeyPurpose::DECRYPT || purpose == KeyPurpose::VERIFY) && Enforcements::is_given_time_passed(*u, false) { - return Err(Error::Km(Ec::KEY_EXPIRED)) - .context("In authorize_create: key is expired."); + return Err(Error::Km(Ec::KEY_EXPIRED)).context(ks_err!("key is expired.")); } } KeyParameterValue::UserSecureID(s) => { @@ -560,24 +555,23 @@ impl Enforcements { // authorize the purpose if !key_purpose_authorized { return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE)) - .context("In authorize_create: the purpose is not authorized."); + .context(ks_err!("the purpose is not authorized.")); } // if both NO_AUTH_REQUIRED and USER_SECURE_ID tags are present, return error if !user_secure_ids.is_empty() && no_auth_required { - return Err(Error::Km(Ec::INVALID_KEY_BLOB)).context( - "In authorize_create: key has both NO_AUTH_REQUIRED and USER_SECURE_ID tags.", - ); + return Err(Error::Km(Ec::INVALID_KEY_BLOB)) + .context(ks_err!("key has both NO_AUTH_REQUIRED and USER_SECURE_ID tags.")); } // if either of auth_type or secure_id is present and the other is not present, return error if (user_auth_type.is_some() && user_secure_ids.is_empty()) || (user_auth_type.is_none() && !user_secure_ids.is_empty()) { - return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context( - "In authorize_create: Auth required, but either auth type or secure ids \ - are not present.", - ); + return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!( + "Auth required, but either auth type or secure ids \ + are not present." + )); } // validate caller nonce for origination purposes @@ -585,24 +579,22 @@ impl Enforcements { && !caller_nonce_allowed && op_params.iter().any(|kp| kp.tag == Tag::NONCE) { - return Err(Error::Km(Ec::CALLER_NONCE_PROHIBITED)).context( - "In authorize_create, NONCE is present, although CALLER_NONCE is not present", - ); + return Err(Error::Km(Ec::CALLER_NONCE_PROHIBITED)) + .context(ks_err!("NONCE is present, although CALLER_NONCE is not present")); } if unlocked_device_required { // check the device locked status. If locked, operations on the key are not // allowed. if self.is_device_locked(user_id) { - return Err(Error::Km(Ec::DEVICE_LOCKED)) - .context("In authorize_create: device is locked."); + return Err(Error::Km(Ec::DEVICE_LOCKED)).context(ks_err!("device is locked.")); } } if let Some(level) = max_boot_level { if !SUPER_KEY.read().unwrap().level_accessible(level) { return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED)) - .context("In authorize_create: boot level is too late."); + .context(ks_err!("boot level is too late.")); } } @@ -636,7 +628,7 @@ impl Enforcements { Some( hat_and_last_off_body .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context("In authorize_create: No suitable auth token found.")?, + .context(ks_err!("No suitable auth token found."))?, ) } else { None @@ -649,16 +641,16 @@ impl Enforcements { let token_age = now .checked_sub(&hat.time_received()) .ok_or_else(Error::sys) - .context(concat!( - "In authorize_create: Overflow while computing Auth token validity. ", - "Validity cannot be established." + .context(ks_err!( + "Overflow while computing Auth token validity. \ + Validity cannot be established." ))?; let on_body_extended = allow_while_on_body && last_off_body < hat.time_received(); if token_age.seconds() > key_time_out && !on_body_extended { return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context("In authorize_create: matching auth token is expired."); + .context(ks_err!("matching auth token is expired.")); } Some(hat) } @@ -832,20 +824,20 @@ impl Enforcements { auth_token_entry.take_auth_token() } else { return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)) - .context("In get_auth_tokens: No auth token found."); + .context(ks_err!("No auth token found.")); } } else { return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)).context( - concat!( - "In get_auth_tokens: No auth token found for ", - "the given challenge and passed-in auth token max age is zero." + ks_err!( + "No auth token found for \ + the given challenge and passed-in auth token max age is zero." ), ); } }; // Wait and obtain the timestamp token from secure clock service. - let tst = get_timestamp_token(challenge) - .context("In get_auth_tokens. Error in getting timestamp token.")?; + let tst = + get_timestamp_token(challenge).context(ks_err!("Error in getting timestamp token."))?; Ok((auth_token, tst)) } } diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs index f34c5daa..3ca3942a 100644 --- a/keystore2/src/error.rs +++ b/keystore2/src/error.rs @@ -41,7 +41,7 @@ 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)] +#[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:?})")] @@ -71,11 +71,6 @@ impl Error { pub fn perm() -> Self { Error::Rc(ResponseCode::PERMISSION_DENIED) } - - /// Short hand for `Error::Rc(ResponseCode::OUT_OF_KEYS)` - pub fn out_of_keys() -> Self { - Error::Rc(ResponseCode::OUT_OF_KEYS) - } } /// Helper function to map the binder status we get from calls into KeyMint diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp index 384ab77f..4ac83e3b 100644 --- a/keystore2/src/fuzzers/Android.bp +++ b/keystore2/src/fuzzers/Android.bp @@ -17,13 +17,51 @@ package { } rust_fuzz { - name: "legacy_blob_fuzzer", - srcs: ["legacy_blob_fuzzer.rs"], + name: "keystore2_unsafe_fuzzer", + srcs: ["keystore2_unsafe_fuzzer.rs"], rustlibs: [ + "libbinder_rs", "libkeystore2", + "libkeystore2_crypto_rust", + "libkeystore2_vintf_rust", + "libkeystore2_aaid-rust", + "libkeystore2_apc_compat-rust", + "libkeystore2_selinux", + "libarbitrary", ], fuzz_config: { fuzz_on_haiku_device: true, fuzz_on_haiku_host: false, + cc: [ + "android-media-fuzzing-reports@google.com", + ], + componentid: 155276, + }, +} + + +rust_fuzz { + name: "authorization_service_fuzzer", + srcs: ["aidl-fuzzers/authorization_service_fuzzer.rs"], + rustlibs: [ + "libkeystore2", + "libkeystore2_crypto_rust", + "libkeystore2_vintf_rust", + "libkeystore2_aaid-rust", + "libkeystore2_apc_compat-rust", + "libkeystore2_selinux", + "libbinder_rs", + "libbinder_random_parcel_rs", + ], + fuzz_config: { + fuzz_on_haiku_device: true, + fuzz_on_haiku_host: false, + cc: [ + "android-media-fuzzing-reports@google.com", + "smoreland@google.com", + "waghpawan@google.com" + ], + // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer + hotlists: ["4637097"], }, } diff --git a/keystore2/src/fuzzers/README.md b/keystore2/src/fuzzers/README.md new file mode 100644 index 00000000..a4ed095b --- /dev/null +++ b/keystore2/src/fuzzers/README.md @@ -0,0 +1,18 @@ +# Fuzzers for libkeystore2 +## Table of contents ++ [keystore2_unsafe_fuzzer](#Keystore2Unsafe) + +# <a name="Keystore2Unsafe"></a> Fuzzer for Keystore2Unsafe +All the parameters of Keystore2Unsafe are populated randomly from libfuzzer. You can find the possible values in the fuzzer's source code. + +#### Steps to run +1. Build the fuzzer +``` +$ m -j$(nproc) keystore2_unsafe_fuzzer +``` + +2. Run on device +``` +$ adb sync data +$ adb shell /data/fuzz/${TARGET_ARCH}/keystore2_unsafe_fuzzer/keystore2_unsafe_fuzzer +``` diff --git a/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs b/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs new file mode 100644 index 00000000..c1b2098f --- /dev/null +++ b/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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. + */ + +#![allow(missing_docs)] +#![no_main] +#[macro_use] +extern crate libfuzzer_sys; + +use binder_random_parcel_rs::fuzz_service; +use keystore2::authorization::AuthorizationManager; + +fuzz_target!(|data: &[u8]| { + let authorization_service = AuthorizationManager::new_native_binder().unwrap_or_else(|e| { + panic!("Failed to create android.security.authorization service because of {:?}", e); + }); + fuzz_service(&mut authorization_service.as_binder(), data); +}); diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs new file mode 100644 index 00000000..0dca3a24 --- /dev/null +++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs @@ -0,0 +1,247 @@ +// Copyright 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. + +//! Fuzzes unsafe APIs of libkeystore2 module + +#![no_main] + +use binder::get_declared_instances; +use keystore2::{legacy_blob::LegacyBlobLoader, utils::ui_opts_2_compat}; +use keystore2_aaid::get_aaid; +use keystore2_apc_compat::ApcHal; +use keystore2_crypto::{ + aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key, + ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point, + ec_point_point_to_oct, ecdh_compute_key, generate_random_data, hkdf_expand, hkdf_extract, + hmac_sha256, parse_subject_from_certificate, Password, ZVec, +}; +use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend}; +use keystore2_vintf::get_hidl_instances; +use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target}; +use std::{ffi::CString, sync::Arc}; + +// Avoid allocating too much memory and crashing the fuzzer. +const MAX_SIZE_MODIFIER: usize = 1024; + +/// CString does not contain any internal 0 bytes +fn get_valid_cstring_data(data: &[u8]) -> &[u8] { + match data.iter().position(|&b| b == 0) { + Some(idx) => &data[0..idx], + None => data, + } +} + +#[derive(Arbitrary, Debug)] +enum FuzzCommand<'a> { + DecodeAlias { + string: String, + }, + TryFrom { + vector_data: Vec<u8>, + }, + GenerateRandomData { + size: usize, + }, + HmacSha256 { + key_hmac: &'a [u8], + msg: &'a [u8], + }, + AesGcmDecrypt { + data: &'a [u8], + iv: &'a [u8], + tag: &'a [u8], + key_aes_decrypt: &'a [u8], + }, + AesGcmEecrypt { + plaintext: &'a [u8], + key_aes_encrypt: &'a [u8], + }, + Password { + pw: &'a [u8], + salt: &'a [u8], + key_length: usize, + }, + HkdfExtract { + hkdf_secret: &'a [u8], + hkdf_salt: &'a [u8], + }, + HkdfExpand { + out_len: usize, + hkdf_prk: &'a [u8], + hkdf_info: &'a [u8], + }, + PublicPrivateKey { + ec_priv_buf: &'a [u8], + ec_oct_buf: &'a [u8], + }, + ParseSubjectFromCertificate { + parse_buf: &'a [u8], + }, + GetHidlInstances { + hidl_package: &'a str, + major_version: usize, + minor_version: usize, + hidl_interface_name: &'a str, + }, + GetAidlInstances { + aidl_package: &'a str, + aidl_interface_name: &'a str, + }, + GetAaid { + aaid_uid: u32, + }, + Hal { + opt: i32, + prompt_text: &'a str, + locale: &'a str, + extra_data: &'a [u8], + }, + Context { + context: &'a str, + }, + Backend { + namespace: &'a str, + }, + GetPidCon { + pid: i32, + }, + CheckAccess { + source: &'a [u8], + target: &'a [u8], + tclass: &'a str, + perm: &'a str, + }, + SetCon { + set_target: &'a [u8], + }, +} + +fuzz_target!(|commands: Vec<FuzzCommand>| { + for command in commands { + match command { + FuzzCommand::DecodeAlias { string } => { + let _res = LegacyBlobLoader::decode_alias(&string); + } + FuzzCommand::TryFrom { vector_data } => { + let _res = ZVec::try_from(vector_data); + } + FuzzCommand::GenerateRandomData { size } => { + let _res = generate_random_data(size % MAX_SIZE_MODIFIER); + } + FuzzCommand::HmacSha256 { key_hmac, msg } => { + let _res = hmac_sha256(key_hmac, msg); + } + FuzzCommand::AesGcmDecrypt { data, iv, tag, key_aes_decrypt } => { + let _res = aes_gcm_decrypt(data, iv, tag, key_aes_decrypt); + } + FuzzCommand::AesGcmEecrypt { plaintext, key_aes_encrypt } => { + let _res = aes_gcm_encrypt(plaintext, key_aes_encrypt); + } + FuzzCommand::Password { pw, salt, key_length } => { + let _res = Password::from(pw).derive_key(salt, key_length % MAX_SIZE_MODIFIER); + } + FuzzCommand::HkdfExtract { hkdf_secret, hkdf_salt } => { + let _res = hkdf_extract(hkdf_secret, hkdf_salt); + } + FuzzCommand::HkdfExpand { out_len, hkdf_prk, hkdf_info } => { + let _res = hkdf_expand(out_len % MAX_SIZE_MODIFIER, hkdf_prk, hkdf_info); + } + FuzzCommand::PublicPrivateKey { ec_priv_buf, ec_oct_buf } => { + let check_private_key = { + let mut check_private_key = ec_key_parse_private_key(ec_priv_buf); + if check_private_key.is_err() { + check_private_key = ec_key_generate_key(); + }; + check_private_key + }; + let check_ecpoint = ec_point_oct_to_point(ec_oct_buf); + if check_private_key.is_ok() { + let private_key = check_private_key.unwrap(); + ec_key_get0_public_key(&private_key); + let _res = ec_key_marshal_private_key(&private_key); + + if check_ecpoint.is_ok() { + let public_key = check_ecpoint.unwrap(); + let _res = ec_point_point_to_oct(public_key.get_point()); + let _res = ecdh_compute_key(public_key.get_point(), &private_key); + } + } + } + FuzzCommand::ParseSubjectFromCertificate { parse_buf } => { + let _res = parse_subject_from_certificate(parse_buf); + } + FuzzCommand::GetHidlInstances { + hidl_package, + major_version, + minor_version, + hidl_interface_name, + } => { + get_hidl_instances(hidl_package, major_version, minor_version, hidl_interface_name); + } + FuzzCommand::GetAidlInstances { aidl_package, aidl_interface_name } => { + get_declared_instances( + format!("{}.{}", aidl_package, aidl_interface_name).as_str(), + ) + .unwrap(); + } + FuzzCommand::GetAaid { aaid_uid } => { + let _res = get_aaid(aaid_uid); + } + FuzzCommand::Hal { opt, prompt_text, locale, extra_data } => { + let hal = ApcHal::try_get_service(); + if hal.is_some() { + let hal = Arc::new(hal.unwrap()); + let apc_compat_options = ui_opts_2_compat(opt); + let prompt_text = + std::str::from_utf8(get_valid_cstring_data(prompt_text.as_bytes())) + .unwrap(); + let locale = + std::str::from_utf8(get_valid_cstring_data(locale.as_bytes())).unwrap(); + let _res = hal.prompt_user_confirmation( + prompt_text, + extra_data, + locale, + apc_compat_options, + move |_, _, _| {}, + ); + } + } + FuzzCommand::Context { context } => { + let _res = Context::new(context); + } + FuzzCommand::Backend { namespace } => { + let backend = KeystoreKeyBackend::new(); + if let Ok(backend) = backend { + let _res = backend.lookup(namespace); + } + } + FuzzCommand::GetPidCon { pid } => { + let _res = getpidcon(pid); + } + FuzzCommand::CheckAccess { source, target, tclass, perm } => { + let source = get_valid_cstring_data(source); + let target = get_valid_cstring_data(target); + let _res = check_access( + &CString::new(source).unwrap(), + &CString::new(target).unwrap(), + tclass, + perm, + ); + } + FuzzCommand::SetCon { set_target } => { + let _res = setcon(&CString::new(get_valid_cstring_data(set_target)).unwrap()); + } + } + } +}); diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs index 341aa0a6..a0333568 100644 --- a/keystore2/src/gc.rs +++ b/keystore2/src/gc.rs @@ -18,6 +18,7 @@ //! optionally dispose of sensitive key material appropriately, and then delete //! the key entry from the database. +use crate::ks_err; use crate::{ async_task, database::{BlobMetaData, KeystoreDB, Uuid}, @@ -103,7 +104,7 @@ impl GcInternal { let blobs = self .db .handle_next_superseded_blobs(&self.deleted_blob_ids, 20) - .context("In process_one_key: Trying to handle superseded blob.")?; + .context(ks_err!("Trying to handle superseded blob."))?; self.deleted_blob_ids = vec![]; self.superseded_blobs = blobs; } @@ -124,9 +125,8 @@ impl GcInternal { .read() .unwrap() .unwrap_key_if_required(&blob_metadata, &blob) - .context("In process_one_key: Trying to unwrap to-be-deleted blob.")?; - (self.invalidate_key)(uuid, &*blob) - .context("In process_one_key: Trying to invalidate key.")?; + .context(ks_err!("Trying to unwrap to-be-deleted blob.",))?; + (self.invalidate_key)(uuid, &blob).context(ks_err!("Trying to invalidate key."))?; } } Ok(()) diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs index 14b36010..10d6f463 100644 --- a/keystore2/src/globals.rs +++ b/keystore2/src/globals.rs @@ -16,6 +16,7 @@ //! database connections and connections to services that Keystore needs //! to talk to. +use crate::ks_err; use crate::gc::Gc; use crate::legacy_blob::LegacyBlobLoader; use crate::legacy_importer::LegacyImporter; @@ -30,7 +31,7 @@ use crate::{ use crate::km_compat::{KeyMintV1, BacklevelKeyMintWrapper}; use crate::{enforcements::Enforcements, error::map_km_error}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - IKeyMintDevice::IKeyMintDevice, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, + IKeyMintDevice::BpKeyMintDevice, IKeyMintDevice::IKeyMintDevice, KeyMintHardwareInfo::KeyMintHardwareInfo, SecurityLevel::SecurityLevel, }; use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ @@ -40,7 +41,7 @@ use android_hardware_security_keymint::binder::{StatusCode, Strong}; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::{Context, Result}; use binder::FromIBinder; -use keystore2_vintf::get_aidl_instances; +use binder::get_declared_instances; use lazy_static::lazy_static; use std::sync::{Arc, Mutex, RwLock}; use std::{cell::RefCell, sync::Once}; @@ -132,26 +133,6 @@ impl<T: FromIBinder + ?Sized> Default for DevicesMap<T> { } } -struct RemotelyProvisionedDevicesMap<T: FromIBinder + ?Sized> { - devices_by_sec_level: HashMap<SecurityLevel, Strong<T>>, -} - -impl<T: FromIBinder + ?Sized> Default for RemotelyProvisionedDevicesMap<T> { - fn default() -> Self { - Self { devices_by_sec_level: HashMap::<SecurityLevel, Strong<T>>::new() } - } -} - -impl<T: FromIBinder + ?Sized> RemotelyProvisionedDevicesMap<T> { - fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Strong<T>> { - self.devices_by_sec_level.get(sec_level).map(|dev| (*dev).clone()) - } - - fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>) { - self.devices_by_sec_level.insert(sec_level, dev); - } -} - lazy_static! { /// The path where keystore stores all its keys. pub static ref DB_PATH: RwLock<PathBuf> = RwLock::new( @@ -162,10 +143,6 @@ lazy_static! { static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default(); /// Timestamp service. static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default(); - /// RemotelyProvisionedComponent HAL devices. - static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: - Mutex<RemotelyProvisionedDevicesMap<dyn IRemotelyProvisionedComponent>> = - Default::default(); /// A single on-demand worker thread that handles deferred tasks with two different /// priorities. pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default(); @@ -186,8 +163,8 @@ lazy_static! { Box::new(|uuid, blob| { let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?; let _wp = wd::watch_millis("In invalidate key closure: calling deleteKey", 500); - map_km_error(km_dev.deleteKey(&*blob)) - .context("In invalidate key closure: Trying to invalidate key blob.") + map_km_error(km_dev.deleteKey(blob)) + .context(ks_err!("Trying to invalidate key blob.")) }), KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None) .expect("Failed to open database."), @@ -196,42 +173,37 @@ lazy_static! { })); } -static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice"; - /// Determine the service name for a KeyMint device of the given security level /// which implements at least the specified version of the `IKeyMintDevice` /// interface. -fn keymint_service_name_by_version( - security_level: &SecurityLevel, - version: i32, -) -> Result<Option<(i32, String)>> { - let keymint_instances = - get_aidl_instances("android.hardware.security.keymint", version as usize, "IKeyMintDevice"); +fn keymint_service_name(security_level: &SecurityLevel) -> Result<Option<String>> { + let keymint_descriptor: &str = <BpKeyMintDevice as IKeyMintDevice>::get_descriptor(); + let keymint_instances = get_declared_instances(keymint_descriptor).unwrap(); let service_name = match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { if keymint_instances.iter().any(|instance| *instance == "default") { - Some(format!("{}/default", KEYMINT_SERVICE_NAME)) + Some(format!("{}/default", keymint_descriptor)) } else { None } } SecurityLevel::STRONGBOX => { if keymint_instances.iter().any(|instance| *instance == "strongbox") { - Some(format!("{}/strongbox", KEYMINT_SERVICE_NAME)) + Some(format!("{}/strongbox", keymint_descriptor)) } else { None } } _ => { - return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(format!( - "In keymint_service_name_by_version: Trying to find keymint V{} for security level: {:?}", - version, security_level + return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(ks_err!( + "Trying to find keymint for security level: {:?}", + security_level )); } }; - Ok(service_name.map(|service_name| (version, service_name))) + Ok(service_name) } /// Make a new connection to a KeyMint device of the given security level. @@ -240,35 +212,29 @@ fn keymint_service_name_by_version( fn connect_keymint( security_level: &SecurityLevel, ) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> { - // Count down from the current interface version back to one in order to - // also find out the interface version -- an implementation of V2 will show - // up in the list of V1-capable devices, but not vice-versa. - let service_name = keymint_service_name_by_version(security_level, 2) - .and_then(|sl| { - if sl.is_none() { - keymint_service_name_by_version(security_level, 1) - } else { - Ok(sl) - } - }) - .context("In connect_keymint.")?; + // Connects to binder to get the current keymint interface and + // based on the security level returns a service name to connect + // to. + let service_name = keymint_service_name(security_level).context(ks_err!("Get service name"))?; - let (keymint, hal_version) = if let Some((version, service_name)) = service_name { + let (keymint, hal_version) = if let Some(service_name) = service_name { let km: Strong<dyn IKeyMintDevice> = map_binder_status_code(binder::get_interface(&service_name)) - .context("In connect_keymint: Trying to connect to genuine KeyMint service.")?; + .context(ks_err!("Trying to connect to genuine KeyMint service."))?; // Map the HAL version code for KeyMint to be <AIDL version> * 100, so // - V1 is 100 // - V2 is 200 + // - V3 is 300 // etc. - (km, Some(version * 100)) + let km_version = km.getInterfaceVersion()?; + (km, Some(km_version * 100)) } else { // This is a no-op if it was called before. keystore2_km_compat::add_keymint_device_service(); let keystore_compat_service: Strong<dyn IKeystoreCompatService> = map_binder_status_code(binder::get_interface("android.security.compat")) - .context("In connect_keymint: Trying to connect to compat service.")?; + .context(ks_err!("Trying to connect to compat service."))?; ( map_binder_status(keystore_compat_service.getKeyMintDevice(*security_level)) .map_err(|e| match e { @@ -277,7 +243,7 @@ fn connect_keymint( } e => e, }) - .context("In connect_keymint: Trying to get Legacy wrapper.")?, + .context(ks_err!("Trying to get Legacy wrapper."))?, None, ) }; @@ -285,8 +251,17 @@ fn connect_keymint( // If the KeyMint device is back-level, use a wrapper that intercepts and // emulates things that are not supported by the hardware. let keymint = match hal_version { + Some(300) => { + // Current KeyMint version: use as-is as v3 Keymint is current version + log::info!( + "KeyMint device is current version ({:?}) for security level: {:?}", + hal_version, + security_level + ); + keymint + } Some(200) => { - // Current KeyMint version: use as-is. + // Previous KeyMint version: use as-is as we don't have any software emulation of v3-specific KeyMint features. log::info!( "KeyMint device is current version ({:?}) for security level: {:?}", hal_version, @@ -302,7 +277,7 @@ fn connect_keymint( security_level ); BacklevelKeyMintWrapper::wrap(KeyMintV1::new(*security_level), keymint) - .context("In connect_keymint: Trying to create V1 compatibility wrapper.")? + .context(ks_err!("Trying to create V1 compatibility wrapper."))? } None => { // Compatibility wrapper around a KeyMaster device: this roughly @@ -312,21 +287,21 @@ fn connect_keymint( "Add emulation wrapper around Keymaster device for security level: {:?}", security_level ); - BacklevelKeyMintWrapper::wrap(KeyMintV1::new(*security_level), keymint).context( - "In connect_keymint: Trying to create km_compat V1 compatibility wrapper .", - )? + BacklevelKeyMintWrapper::wrap(KeyMintV1::new(*security_level), keymint) + .context(ks_err!("Trying to create km_compat V1 compatibility wrapper ."))? } _ => { - return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(format!( - "In connect_keymint: unexpected hal_version {:?} for security level: {:?}", - hal_version, security_level - )) + return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(ks_err!( + "unexpected hal_version {:?} for security level: {:?}", + hal_version, + security_level + )); } }; let wp = wd::watch_millis("In connect_keymint: calling getHardwareInfo()", 500); - let mut hw_info = map_km_error(keymint.getHardwareInfo()) - .context("In connect_keymint: Failed to get hardware info.")?; + let mut hw_info = + map_km_error(keymint.getHardwareInfo()).context(ks_err!("Failed to get hardware info."))?; drop(wp); // The legacy wrapper sets hw_info.versionNumber to the underlying HAL version like so: @@ -356,7 +331,8 @@ pub fn get_keymint_device( if let Some((dev, hw_info, uuid)) = devices_map.dev_by_sec_level(security_level) { Ok((dev, hw_info, uuid)) } else { - let (dev, hw_info) = connect_keymint(security_level).context("In get_keymint_device.")?; + let (dev, hw_info) = + connect_keymint(security_level).context(ks_err!("Cannot connect to Keymint"))?; devices_map.insert(*security_level, dev, hw_info); // Unwrap must succeed because we just inserted it. Ok(devices_map.dev_by_sec_level(security_level).unwrap()) @@ -374,7 +350,7 @@ pub fn get_keymint_dev_by_uuid( if let Some((dev, hw_info, _)) = devices_map.dev_by_uuid(uuid) { Ok((dev, hw_info)) } else { - Err(Error::sys()).context("In get_keymint_dev_by_uuid: No KeyMint instance found.") + Err(Error::sys()).context(ks_err!("No KeyMint instance found.")) } } @@ -390,7 +366,7 @@ static TIME_STAMP_SERVICE_NAME: &str = "android.hardware.security.secureclock.IS /// to connect to the legacy wrapper. fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { let secureclock_instances = - get_aidl_instances("android.hardware.security.secureclock", 1, "ISecureClock"); + get_declared_instances("android.hardware.security.secureclock.ISecureClock").unwrap(); let secure_clock_available = secureclock_instances.iter().any(|instance| *instance == "default"); @@ -399,14 +375,14 @@ fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { let secureclock = if secure_clock_available { map_binder_status_code(binder::get_interface(&default_time_stamp_service_name)) - .context("In connect_secureclock: Trying to connect to genuine secure clock service.") + .context(ks_err!("Trying to connect to genuine secure clock service.")) } else { // This is a no-op if it was called before. keystore2_km_compat::add_keymint_device_service(); let keystore_compat_service: Strong<dyn IKeystoreCompatService> = map_binder_status_code(binder::get_interface("android.security.compat")) - .context("In connect_secureclock: Trying to connect to compat service.")?; + .context(ks_err!("Trying to connect to compat service."))?; // Legacy secure clock services were only implemented by TEE. map_binder_status(keystore_compat_service.getSecureClock()) @@ -416,7 +392,7 @@ fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { } e => e, }) - .context("In connect_secureclock: Trying to get Legacy wrapper.") + .context(ks_err!("Trying to get Legacy wrapper.")) }?; Ok(secureclock) @@ -429,7 +405,7 @@ pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { if let Some(dev) = &*ts_device { Ok(dev.clone()) } else { - let dev = connect_secureclock().context("In get_timestamp_service.")?; + let dev = connect_secureclock().context(ks_err!())?; *ts_device = Some(dev.clone()); Ok(dev) } @@ -438,13 +414,12 @@ pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { static REMOTE_PROVISIONING_HAL_SERVICE_NAME: &str = "android.hardware.security.keymint.IRemotelyProvisionedComponent"; -fn connect_remotely_provisioned_component( - security_level: &SecurityLevel, -) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { +/// Get the service name of a remotely provisioned component corresponding to given security level. +pub fn get_remotely_provisioned_component_name(security_level: &SecurityLevel) -> Result<String> { let remotely_prov_instances = - get_aidl_instances("android.hardware.security.keymint", 1, "IRemotelyProvisionedComponent"); + get_declared_instances(REMOTE_PROVISIONING_HAL_SERVICE_NAME).unwrap(); - let service_name = match *security_level { + match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { if remotely_prov_instances.iter().any(|instance| *instance == "default") { Some(format!("{}/default", REMOTE_PROVISIONING_HAL_SERVICE_NAME)) @@ -462,31 +437,5 @@ fn connect_remotely_provisioned_component( _ => None, } .ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context("In connect_remotely_provisioned_component.")?; - - let rem_prov_hal: Strong<dyn IRemotelyProvisionedComponent> = - map_binder_status_code(binder::get_interface(&service_name)) - .context(concat!( - "In connect_remotely_provisioned_component: Trying to connect to", - " RemotelyProvisionedComponent service." - )) - .map_err(|e| e)?; - Ok(rem_prov_hal) -} - -/// Get a remote provisiong component device for the given security level either from the cache or -/// by making a new connection. Returns the device. -pub fn get_remotely_provisioned_component( - security_level: &SecurityLevel, -) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { - let mut devices_map = REMOTELY_PROVISIONED_COMPONENT_DEVICES.lock().unwrap(); - if let Some(dev) = devices_map.dev_by_sec_level(security_level) { - Ok(dev) - } else { - let dev = connect_remotely_provisioned_component(security_level) - .context("In get_remotely_provisioned_component.")?; - devices_map.insert(*security_level, dev); - // Unwrap must succeed because we just inserted it. - Ok(devices_map.dev_by_sec_level(security_level).unwrap()) - } + .context(ks_err!()) } diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs index e3992d89..460caa77 100644 --- a/keystore2/src/id_rotation.rs +++ b/keystore2/src/id_rotation.rs @@ -20,6 +20,8 @@ //! It is assumed that the timestamp file does not exist after a factory reset. So the creation //! time of the timestamp file provides a lower bound for the time since factory reset. +use crate::ks_err; + use anyhow::{Context, Result}; use std::fs; use std::io::ErrorKind; @@ -66,7 +68,7 @@ impl IdRotationState { _ => Err(e).context("Failed to open timestamp file."), }, } - .context("In had_factory_reset_since_id_rotation:") + .context(ks_err!()) } } diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs index 9854974d..5da95d96 100644 --- a/keystore2/src/key_parameter.rs +++ b/keystore2/src/key_parameter.rs @@ -837,6 +837,11 @@ pub enum KeyParameterValue { #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = DIGEST, field = Digest)] Digest(Digest), + /// Digest algorithms that can be used for MGF in RSA-OAEP. + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = RSA_OAEP_MGF_DIGEST, field = Digest)] + RsaOaepMgfDigest(Digest), /// Padding modes that may be used with the key. Relevant to RSA, AES and 3DES keys. #[serde(deserialize_with = "deserialize_primitive")] #[serde(serialize_with = "serialize_primitive")] @@ -966,9 +971,12 @@ pub enum KeyParameterValue { /// Provides the device's serial number, to attestKey() #[key_param(tag = ATTESTATION_ID_SERIAL, field = Blob)] AttestationIdSerial(Vec<u8>), - /// Provides the IMEIs for all radios on the device, to attestKey() + /// Provides the primary IMEI for the device, to attestKey() #[key_param(tag = ATTESTATION_ID_IMEI, field = Blob)] AttestationIdIMEI(Vec<u8>), + /// Provides a second IMEI for the device, to attestKey() + #[key_param(tag = ATTESTATION_ID_SECOND_IMEI, field = Blob)] + AttestationIdSecondIMEI(Vec<u8>), /// Provides the MEIDs for all radios on the device, to attestKey() #[key_param(tag = ATTESTATION_ID_MEID, field = Blob)] AttestationIdMEID(Vec<u8>), @@ -1095,6 +1103,7 @@ mod generated_key_parameter_tests { Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()), Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()), Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()), + Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()), Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()), Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()), Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()), diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs index 55f5d152..31c1e295 100644 --- a/keystore2/src/keystore2_main.rs +++ b/keystore2/src/keystore2_main.rs @@ -19,9 +19,6 @@ use keystore2::globals::ENFORCEMENTS; use keystore2::maintenance::Maintenance; use keystore2::metrics::Metrics; use keystore2::metrics_store; -use keystore2::remote_provisioning::{ - RemoteProvisioningService, RemotelyProvisionedKeyPoolService, -}; use keystore2::service::KeystoreService; use keystore2::{apc::ApcManager, shared_secret_negotiation}; use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState}; @@ -34,9 +31,6 @@ static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/defau static APC_SERVICE_NAME: &str = "android.security.apc"; static AUTHORIZATION_SERVICE_NAME: &str = "android.security.authorization"; static METRICS_SERVICE_NAME: &str = "android.security.metrics"; -static REMOTE_PROVISIONING_SERVICE_NAME: &str = "android.security.remoteprovisioning"; -static REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME: &str = - "android.security.remoteprovisioning.IRemotelyProvisionedKeyPool"; static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; static LEGACY_KEYSTORE_SERVICE_NAME: &str = "android.security.legacykeystore"; @@ -47,7 +41,16 @@ fn main() { android_logger::Config::default() .with_tag("keystore2") .with_min_level(log::Level::Debug) - .with_log_id(android_logger::LogId::System), + .with_log_id(android_logger::LogId::System) + .format(|buf, record| { + writeln!( + buf, + "{}:{} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }), ); // Redirect panic messages to logcat. panic::set_hook(Box::new(|panic_info| { @@ -137,40 +140,6 @@ fn main() { panic!("Failed to register service {} because of {:?}.", METRICS_SERVICE_NAME, e); }); - // Devices with KS2 and KM 1.0 may not have any IRemotelyProvisionedComponent HALs at all. Do - // not panic if new_native_binder returns failure because it could not find the TEE HAL. - if let Ok(remote_provisioning_service) = RemoteProvisioningService::new_native_binder() { - binder::add_service( - REMOTE_PROVISIONING_SERVICE_NAME, - remote_provisioning_service.as_binder(), - ) - .unwrap_or_else(|e| { - panic!( - "Failed to register service {} because of {:?}.", - REMOTE_PROVISIONING_SERVICE_NAME, e - ); - }); - } - - // Even if the IRemotelyProvisionedComponent HAL is implemented, it doesn't mean that the keys - // may be fetched via the key pool. The HAL must be a new version that exports a unique id. If - // none of the HALs support this, then the key pool service is not published. - match RemotelyProvisionedKeyPoolService::new_native_binder() { - Ok(key_pool_service) => { - binder::add_service( - REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME, - key_pool_service.as_binder(), - ) - .unwrap_or_else(|e| { - panic!( - "Failed to register service {} because of {:?}.", - REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME, e - ); - }); - } - Err(e) => log::info!("Not publishing IRemotelyProvisionedKeyPool service: {:?}", e), - } - binder::add_service(LEGACY_KEYSTORE_SERVICE_NAME, legacykeystore.as_binder()).unwrap_or_else( |e| { panic!( diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs index 788beefe..035edd90 100644 --- a/keystore2/src/km_compat.rs +++ b/keystore2/src/km_compat.rs @@ -15,6 +15,7 @@ //! Provide a wrapper around a KeyMint device that allows up-level features to //! be emulated on back-level devices. +use crate::ks_err; use crate::error::{map_binder_status, map_binder_status_code, map_or_log_err, Error, ErrorCode}; use android_hardware_security_keymint::binder::{BinderFeatures, StatusCode, Strong}; use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::TimeStampToken::TimeStampToken; @@ -81,7 +82,7 @@ fn wrap_keyblob(keyblob: &[u8]) -> anyhow::Result<Vec<u8>> { result.extend_from_slice(KEYBLOB_PREFIX); result.extend_from_slice(keyblob); let tag = hmac_sha256(KEYBLOB_HMAC_KEY, keyblob) - .context("In wrap_keyblob, failed to calculate HMAC-SHA256")?; + .context(ks_err!("failed to calculate HMAC-SHA256"))?; result.extend_from_slice(&tag); Ok(result) } @@ -138,10 +139,9 @@ where // This is a no-op if it was called before. keystore2_km_compat::add_keymint_device_service(); - let keystore_compat_service: Strong<dyn IKeystoreCompatService> = map_binder_status_code( - binder::get_interface("android.security.compat"), - ) - .context("In BacklevelKeyMintWrapper::wrap: Trying to connect to compat service.")?; + let keystore_compat_service: Strong<dyn IKeystoreCompatService> = + map_binder_status_code(binder::get_interface("android.security.compat")) + .context(ks_err!("Trying to connect to compat service."))?; let soft = map_binder_status(keystore_compat_service.getKeyMintDevice(SecurityLevel::SOFTWARE)) .map_err(|e| match e { @@ -150,7 +150,7 @@ where } e => e, }) - .context("In BacklevelKeyMintWrapper::wrap: Trying to get software device.")?; + .context(ks_err!("Trying to get software device."))?; Ok(BnKeyMintDevice::new_binder( Self { real, soft, emu }, diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp index 6d0630b4..e27cd1c7 100644 --- a/keystore2/src/km_compat/km_compat.cpp +++ b/keystore2/src/km_compat/km_compat.cpp @@ -500,8 +500,30 @@ ScopedAStatus KeyMintDevice::generateKey(const std::vector<KeyParameter>& inKeyP ScopedAStatus KeyMintDevice::importKey(const std::vector<KeyParameter>& inKeyParams, KeyFormat in_inKeyFormat, const std::vector<uint8_t>& in_inKeyData, - const std::optional<AttestationKey>& /* in_attestationKey */, + const std::optional<AttestationKey>& in_attestationKey, KeyCreationResult* out_creationResult) { + // Since KeyMaster doesn't support ECDH, route all ECDH key import requests to + // soft-KeyMint. + // + // For this to work we'll need to also route begin() and deleteKey() calls to + // soft-KM. In order to do that, we'll prefix all keyblobs with whether it was + // created by the real underlying KeyMaster HAL or whether it was created by + // soft-KeyMint. + // + // See keyBlobPrefix() for more discussion. + // + for (const auto& keyParam : inKeyParams) { + if (keyParam.tag == Tag::PURPOSE && + keyParam.value.get<KeyParameterValue::Tag::keyPurpose>() == KeyPurpose::AGREE_KEY) { + auto ret = softKeyMintDevice_->importKey(inKeyParams, in_inKeyFormat, in_inKeyData, + in_attestationKey, out_creationResult); + if (ret.isOk()) { + out_creationResult->keyBlob = keyBlobPrefix(out_creationResult->keyBlob, true); + } + return ret; + } + } + auto legacyKeyGENParams = convertKeyParametersToLegacy(extractGenerationParams(inKeyParams)); auto legacyKeyFormat = convertKeyFormatToLegacy(in_inKeyFormat); KMV1::ErrorCode errorCode; @@ -574,6 +596,17 @@ ScopedAStatus KeyMintDevice::upgradeKey(const std::vector<uint8_t>& in_inKeyBlob auto legacyUpgradeParams = convertKeyParametersToLegacy(in_inUpgradeParams); V4_0_ErrorCode errorCode; + if (prefixedKeyBlobIsSoftKeyMint(in_inKeyBlobToUpgrade)) { + auto status = softKeyMintDevice_->upgradeKey( + prefixedKeyBlobRemovePrefix(in_inKeyBlobToUpgrade), in_inUpgradeParams, _aidl_return); + if (!status.isOk()) { + LOG(ERROR) << __func__ << " transaction failed. " << status.getDescription(); + } else { + *_aidl_return = keyBlobPrefix(*_aidl_return, true); + } + return status; + } + auto result = mDevice->upgradeKey(prefixedKeyBlobRemovePrefix(in_inKeyBlobToUpgrade), legacyUpgradeParams, [&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& upgradedKeyBlob) { diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h index 33248a40..5db7e3d8 100644 --- a/keystore2/src/km_compat/km_compat_type_conversion.h +++ b/keystore2/src/km_compat/km_compat_type_conversion.h @@ -750,6 +750,7 @@ static V4_0::KeyParameter convertKeyParameterToLegacy(const KMV1::KeyParameter& case KMV1::Tag::CERTIFICATE_SUBJECT: case KMV1::Tag::CERTIFICATE_NOT_BEFORE: case KMV1::Tag::CERTIFICATE_NOT_AFTER: + case KMV1::Tag::ATTESTATION_ID_SECOND_IMEI: // These tags do not exist in KM < KeyMint 1.0. break; case KMV1::Tag::MAX_BOOT_LEVEL: diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs index 13f77607..2632ec49 100644 --- a/keystore2/src/km_compat/lib.rs +++ b/keystore2/src/km_compat/lib.rs @@ -450,6 +450,10 @@ mod tests { ))); assert!(sec_level_enforced.iter().any(|kp| matches!( kp, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KeyParameterValue::Integer(_) } + ))); + assert!(sec_level_enforced.iter().any(|kp| matches!( + kp, KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KeyParameterValue::Integer(_) } ))); } diff --git a/keystore2/src/ks_err.rs b/keystore2/src/ks_err.rs new file mode 100644 index 00000000..c9c38c0d --- /dev/null +++ b/keystore2/src/ks_err.rs @@ -0,0 +1,35 @@ +// 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. + +//! A ks_err macro that expands error messages to include the file and line number + +/// +/// # Examples +/// +/// ``` +/// use crate::ks_err; +/// +/// ks_err!("Key is expired."); +/// Result: +/// "src/lib.rs:7 Key is expired." +/// ``` +#[macro_export] +macro_rules! ks_err { + { $($arg:tt)+ } => { + format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+)) + }; + {} => { + format!("{}:{}", file!(), line!()) + }; +} diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs index d75bfd2c..2ffcc711 100644 --- a/keystore2/src/legacy_blob.rs +++ b/keystore2/src/legacy_blob.rs @@ -14,6 +14,7 @@ //! This module implements methods to load legacy keystore key blob files. +use crate::ks_err; use crate::{ error::{Error as KsError, ResponseCode}, key_parameter::{KeyParameter, KeyParameterValue}, @@ -320,7 +321,7 @@ impl LegacyBlobLoader { acc.push(c as char); } c => { - acc.push((b'+' + (c as u8 >> 6)) as char); + acc.push((b'+' + (c >> 6)) as char); acc.push((b'0' + (c & 0x3F)) as char); } }; @@ -348,24 +349,23 @@ impl LegacyBlobLoader { None } _ => { - return Err(Error::BadEncoding) - .context("In decode_alias: could not decode filename.") + return Err(Error::BadEncoding).context(ks_err!("could not decode filename.")); } }; } if multi.is_some() { - return Err(Error::BadEncoding).context("In decode_alias: could not decode filename."); + return Err(Error::BadEncoding).context(ks_err!("could not decode filename.")); } - String::from_utf8(s).context("In decode_alias: encoded alias was not valid UTF-8.") + String::from_utf8(s).context(ks_err!("encoded alias was not valid UTF-8.")) } fn new_from_stream(stream: &mut dyn Read) -> Result<Blob> { let mut buffer = Vec::new(); - stream.read_to_end(&mut buffer).context("In new_from_stream.")?; + stream.read_to_end(&mut buffer).context(ks_err!())?; if buffer.len() < Self::COMMON_HEADER_SIZE { - return Err(Error::BadLen).context("In new_from_stream.")?; + return Err(Error::BadLen).context(ks_err!())?; } let version: u8 = buffer[Self::VERSION_OFFSET]; @@ -380,15 +380,15 @@ impl LegacyBlobLoader { if version != SUPPORTED_LEGACY_BLOB_VERSION { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context(format!("In new_from_stream: Unknown blob version: {}.", version)); + .context(ks_err!("Unknown blob version: {}.", version)); } let length = u32::from_be_bytes( buffer[Self::LENGTH_OFFSET..Self::LENGTH_OFFSET + 4].try_into().unwrap(), ) as usize; if buffer.len() < Self::COMMON_HEADER_SIZE + length { - return Err(Error::BadLen).context(format!( - "In new_from_stream. Expected: {} got: {}.", + return Err(Error::BadLen).context(ks_err!( + "Expected: {} got: {}.", Self::COMMON_HEADER_SIZE + length, buffer.len() )); @@ -457,11 +457,12 @@ impl LegacyBlobLoader { }), (blob_types::SUPER_KEY, _, None) | (blob_types::SUPER_KEY_AES256, _, None) => { Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In new_from_stream: Super key without salt for key derivation.") + .context(ks_err!("Super key without salt for key derivation.")) } - _ => Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!( - "In new_from_stream: Unknown blob type. {} {}", - blob_type, is_encrypted + _ => Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( + "Unknown blob type. {} {}", + blob_type, + is_encrypted )), } } @@ -482,30 +483,23 @@ impl LegacyBlobLoader { where F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>, { - let blob = - Self::new_from_stream(&mut stream).context("In new_from_stream_decrypt_with.")?; + let blob = Self::new_from_stream(&mut stream).context(ks_err!())?; match blob.value() { BlobValue::Encrypted { iv, tag, data } => Ok(Blob { flags: blob.flags, - value: BlobValue::Decrypted( - decrypt(data, iv, tag, None, None) - .context("In new_from_stream_decrypt_with.")?, - ), + value: BlobValue::Decrypted(decrypt(data, iv, tag, None, None).context(ks_err!())?), }), BlobValue::PwEncrypted { iv, tag, data, salt, key_size } => Ok(Blob { flags: blob.flags, value: BlobValue::Decrypted( - decrypt(data, iv, tag, Some(salt), Some(*key_size)) - .context("In new_from_stream_decrypt_with.")?, + decrypt(data, iv, tag, Some(salt), Some(*key_size)).context(ks_err!())?, ), }), BlobValue::EncryptedGeneric { iv, tag, data } => Ok(Blob { flags: blob.flags, value: BlobValue::Generic( - decrypt(data, iv, tag, None, None) - .context("In new_from_stream_decrypt_with.")?[..] - .to_vec(), + decrypt(data, iv, tag, None, None).context(ks_err!())?[..].to_vec(), ), }), @@ -548,33 +542,30 @@ impl LegacyBlobLoader { /// | 32 bit indirect_offset | Offset from the beginning of the indirect section. /// +------------------------+ pub fn read_key_parameters(stream: &mut &[u8]) -> Result<Vec<KeyParameterValue>> { - let indirect_size = - read_ne_u32(stream).context("In read_key_parameters: While reading indirect size.")?; + let indirect_size = read_ne_u32(stream).context(ks_err!("While reading indirect size."))?; let indirect_buffer = stream .get(0..indirect_size as usize) .ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In read_key_parameters: While reading indirect buffer.")?; + .context(ks_err!("While reading indirect buffer."))?; // update the stream position. *stream = &stream[indirect_size as usize..]; - let element_count = - read_ne_u32(stream).context("In read_key_parameters: While reading element count.")?; - let element_size = - read_ne_u32(stream).context("In read_key_parameters: While reading element size.")?; + let element_count = read_ne_u32(stream).context(ks_err!("While reading element count."))?; + let element_size = read_ne_u32(stream).context(ks_err!("While reading element size."))?; let mut element_stream = stream .get(0..element_size as usize) .ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In read_key_parameters: While reading elements buffer.")?; + .context(ks_err!("While reading elements buffer."))?; // update the stream position. *stream = &stream[element_size as usize..]; let mut params: Vec<KeyParameterValue> = Vec::new(); for _ in 0..element_count { - let tag = Tag(read_ne_i32(&mut element_stream).context("In read_key_parameters.")?); + let tag = Tag(read_ne_i32(&mut element_stream).context(ks_err!())?); let param = match Self::tag_type(tag) { TagType::ENUM | TagType::ENUM_REP | TagType::UINT | TagType::UINT_REP => { KeyParameterValue::new_from_tag_primitive_pair( @@ -617,7 +608,7 @@ impl LegacyBlobLoader { TagType::INVALID => Err(anyhow::anyhow!("Invalid.")), _ => { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In read_key_parameters: Encountered bogus tag type."); + .context(ks_err!("Encountered bogus tag type.")); } }; if let Ok(p) = param { @@ -647,9 +638,11 @@ impl LegacyBlobLoader { { Ok(Blob { value: BlobValue::Characteristics( - super_key.as_ref().unwrap().decrypt(&data, &iv, &tag).context( - "In decrypt_if_required: Failed to decrypt EncryptedCharacteristics", - )?[..] + super_key + .as_ref() + .unwrap() + .decrypt(&data, &iv, &tag) + .context(ks_err!("Failed to decrypt EncryptedCharacteristics"))?[..] .to_vec(), ), flags, @@ -664,7 +657,7 @@ impl LegacyBlobLoader { .as_ref() .unwrap() .decrypt(&data, &iv, &tag) - .context("In decrypt_if_required: Failed to decrypt Encrypted")?, + .context(ks_err!("Failed to decrypt Encrypted"))?, ), flags, }) @@ -678,7 +671,7 @@ impl LegacyBlobLoader { .as_ref() .unwrap() .decrypt(&data, &iv, &tag) - .context("In decrypt_if_required: Failed to decrypt Encrypted")?[..] + .context(ks_err!("Failed to decrypt Encrypted"))?[..] .to_vec(), ), flags, @@ -687,7 +680,7 @@ impl LegacyBlobLoader { // This arm catches all encrypted cases where super key is not present or cannot // decrypt the blob, the latter being BlobValue::PwEncrypted. _ => Err(Error::LockedComponent) - .context("In decrypt_if_required: Encountered encrypted blob without super key."), + .context(ks_err!("Encountered encrypted blob without super key.")), } } @@ -700,7 +693,7 @@ impl LegacyBlobLoader { super_key: &Option<Arc<dyn AesGcm>>, ) -> Result<LegacyKeyCharacteristics> { let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix)) - .context("In read_characteristics_file")?; + .context(ks_err!())?; let blob = match blob { None => return Ok(LegacyKeyCharacteristics::Cache(Vec::new())), @@ -708,16 +701,14 @@ impl LegacyBlobLoader { }; let blob = Self::decrypt_if_required(super_key, blob) - .context("In read_characteristics_file: Trying to decrypt blob.")?; + .context(ks_err!("Trying to decrypt blob."))?; let (mut stream, is_cache) = match blob.value() { BlobValue::Characteristics(data) => (&data[..], false), BlobValue::CharacteristicsCache(data) => (&data[..], true), _ => { - return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!( - "In read_characteristics_file: ", - "Characteristics file does not hold key characteristics." - )) + return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) + .context(ks_err!("Characteristics file does not hold key characteristics.")); } }; @@ -726,7 +717,7 @@ impl LegacyBlobLoader { // the hardware enforced list. BlobValue::CharacteristicsCache(_) => Some( Self::read_key_parameters(&mut stream) - .context("In read_characteristics_file.")? + .context(ks_err!())? .into_iter() .map(|value| KeyParameter::new(value, hw_sec_level)), ), @@ -734,7 +725,7 @@ impl LegacyBlobLoader { }; let sw_list = Self::read_key_parameters(&mut stream) - .context("In read_characteristics_file.")? + .context(ks_err!())? .into_iter() .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE)); @@ -786,11 +777,11 @@ impl LegacyBlobLoader { Ok(file) => file, Err(e) => match e.kind() { ErrorKind::NotFound => return Ok(None), - _ => return Err(e).context("In read_generic_blob."), + _ => return Err(e).context(ks_err!()), }, }; - Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?)) + Ok(Some(Self::new_from_stream(&mut file).context(ks_err!())?)) } fn read_generic_blob_decrypt_with<F>(path: &Path, decrypt: F) -> Result<Option<Blob>> @@ -801,14 +792,11 @@ impl LegacyBlobLoader { Ok(file) => file, Err(e) => match e.kind() { ErrorKind::NotFound => return Ok(None), - _ => return Err(e).context("In read_generic_blob_decrypt_with."), + _ => return Err(e).context(ks_err!()), }, }; - Ok(Some( - Self::new_from_stream_decrypt_with(&mut file, decrypt) - .context("In read_generic_blob_decrypt_with.")?, - )) + Ok(Some(Self::new_from_stream_decrypt_with(&mut file, decrypt).context(ks_err!())?)) } /// Read a legacy keystore entry blob. @@ -827,7 +815,7 @@ impl LegacyBlobLoader { }; let blob = Self::read_generic_blob_decrypt_with(&path, decrypt) - .context("In read_legacy_keystore_entry: Failed to read blob.")?; + .context(ks_err!("Failed to read blob."))?; Ok(blob.and_then(|blob| match blob.value { BlobValue::Generic(blob) => Some(blob), @@ -848,13 +836,13 @@ impl LegacyBlobLoader { if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) { match e.kind() { ErrorKind::NotFound => return Ok(false), - _ => return Err(e).context("In remove_legacy_keystore_entry."), + _ => return Err(e).context(ks_err!()), } } let user_id = uid_to_android_user(uid); self.remove_user_dir_if_empty(user_id) - .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")?; + .context(ks_err!("Trying to remove empty user dir."))?; Ok(true) } @@ -869,27 +857,21 @@ impl LegacyBlobLoader { Err(e) => match e.kind() { ErrorKind::NotFound => return Ok(Default::default()), _ => { - return Err(e).context(format!( - concat!( - "In list_legacy_keystore_entries_for_uid: ,", - "Failed to open legacy blob database: {:?}" - ), - path - )) + return Err(e) + .context(ks_err!("Failed to open legacy blob database: {:?}", path)); } }, }; let mut result: Vec<String> = Vec::new(); for entry in dir { - let file_name = entry - .context("In list_legacy_keystore_entries_for_uid: Trying to access dir entry")? - .file_name(); + let file_name = entry.context(ks_err!("Trying to access dir entry"))?.file_name(); if let Some(f) = file_name.to_str() { let encoded_alias = &f[uid_str.len() + 1..]; if f.starts_with(&uid_str) && !Self::is_keystore_alias(encoded_alias) { - result.push(Self::decode_alias(encoded_alias).context( - "In list_legacy_keystore_entries_for_uid: Trying to decode alias.", - )?) + result.push( + Self::decode_alias(encoded_alias) + .context(ks_err!("Trying to decode alias."))?, + ) } } } @@ -911,9 +893,7 @@ impl LegacyBlobLoader { &self, user_id: u32, ) -> Result<HashMap<u32, HashSet<String>>> { - let user_entries = self - .list_user(user_id) - .context("In list_legacy_keystore_entries_for_user: Trying to list user.")?; + let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?; let result = user_entries.into_iter().fold(HashMap::<u32, HashSet<String>>::new(), |mut acc, v| { @@ -986,9 +966,9 @@ impl LegacyBlobLoader { /// in the database dir. pub fn is_empty(&self) -> Result<bool> { let dir = Self::with_retry_interrupted(|| fs::read_dir(self.path.as_path())) - .context("In is_empty: Failed to open legacy blob database.")?; + .context(ks_err!("Failed to open legacy blob database."))?; for entry in dir { - if (*entry.context("In is_empty: Trying to access dir entry")?.file_name()) + if (*entry.context(ks_err!("Trying to access dir entry"))?.file_name()) .to_str() .map_or(false, |f| f.starts_with("user_")) { @@ -1007,7 +987,7 @@ impl LegacyBlobLoader { return Ok(true); } Ok(Self::with_retry_interrupted(|| user_path.read_dir()) - .context("In is_empty_user: Failed to open legacy user dir.")? + .context(ks_err!("Failed to open legacy user dir."))? .next() .is_none()) } @@ -1032,16 +1012,14 @@ impl LegacyBlobLoader { Err(e) => match e.kind() { ErrorKind::NotFound => return Ok(Default::default()), _ => { - return Err(e).context(format!( - "In list_user: Failed to open legacy blob database. {:?}", - path - )) + return Err(e) + .context(ks_err!("Failed to open legacy blob database. {:?}", path)); } }, }; let mut result: Vec<String> = Vec::new(); for entry in dir { - let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name(); + let file_name = entry.context(ks_err!("Trying to access dir entry"))?.file_name(); if let Some(f) = file_name.to_str() { result.push(f.to_string()) } @@ -1055,9 +1033,7 @@ impl LegacyBlobLoader { &self, user_id: u32, ) -> Result<HashMap<u32, HashSet<String>>> { - let user_entries = self - .list_user(user_id) - .context("In list_keystore_entries_for_user: Trying to list user.")?; + let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?; let result = user_entries.into_iter().fold(HashMap::<u32, HashSet<String>>::new(), |mut acc, v| { @@ -1078,9 +1054,7 @@ impl LegacyBlobLoader { pub fn list_keystore_entries_for_uid(&self, uid: u32) -> Result<Vec<String>> { let user_id = uid_to_android_user(uid); - let user_entries = self - .list_user(user_id) - .context("In list_keystore_entries_for_uid: Trying to list user.")?; + let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?; let uid_str = format!("{}_", uid); @@ -1163,7 +1137,7 @@ impl LegacyBlobLoader { if something_was_deleted { let user_id = uid_to_android_user(uid); self.remove_user_dir_if_empty(user_id) - .context("In remove_keystore_entry: Trying to remove empty user dir.")?; + .context(ks_err!("Trying to remove empty user dir."))?; } Ok(something_was_deleted) @@ -1188,7 +1162,7 @@ impl LegacyBlobLoader { let dest_path = make_filename(dest_uid, dest_alias, prefix); match Self::with_retry_interrupted(|| fs::rename(&src_path, &dest_path)) { Err(e) if e.kind() == ErrorKind::NotFound => Ok(()), - r => r.context("In move_keystore_file_if_exists: Trying to rename."), + r => r.context(ks_err!("Trying to rename.")), } } @@ -1207,7 +1181,7 @@ impl LegacyBlobLoader { } if uid_to_android_user(src_uid) != uid_to_android_user(dest_uid) { - return Err(Error::AndroidUserMismatch).context("In move_keystore_entry."); + return Err(Error::AndroidUserMismatch).context(ks_err!()); } let prefixes = ["USRPKEY", "USRSKEY", "USRCERT", "CACERT"]; @@ -1220,12 +1194,7 @@ impl LegacyBlobLoader { prefix, |uid, alias, prefix| self.make_blob_filename(uid, alias, prefix), ) - .with_context(|| { - format!( - "In move_keystore_entry: Trying to move blob file with prefix: \"{}\"", - prefix - ) - })?; + .with_context(|| ks_err!("Trying to move blob file with prefix: \"{}\"", prefix))?; } let prefixes = ["USRPKEY", "USRSKEY"]; @@ -1240,8 +1209,8 @@ impl LegacyBlobLoader { |uid, alias, prefix| self.make_chr_filename(uid, alias, prefix), ) .with_context(|| { - format!( - "In move_keystore_entry: Trying to move characteristics file with \ + ks_err!( + "Trying to move characteristics file with \ prefix: \"{}\"", prefix ) @@ -1252,10 +1221,7 @@ impl LegacyBlobLoader { } fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> { - if self - .is_empty_user(user_id) - .context("In remove_user_dir_if_empty: Trying to check for empty user dir.")? - { + if self.is_empty_user(user_id).context(ks_err!("Trying to check for empty user dir."))? { let user_path = self.make_user_path_name(user_id); Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok(); } @@ -1273,14 +1239,14 @@ impl LegacyBlobLoader { let km_blob = match km_blob { Some((km_blob, prefix)) => { - let km_blob = - match km_blob { - Blob { flags: _, value: BlobValue::Decrypted(_) } - | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob, - _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context( - "In load_by_uid_alias: Found wrong blob type in legacy key blob file.", - ), - }; + let km_blob = match km_blob { + Blob { flags: _, value: BlobValue::Decrypted(_) } + | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob, + _ => { + return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) + .context(ks_err!("Found wrong blob type in legacy key blob file.")) + } + }; let hw_sec_level = match km_blob.is_strongbox() { true => SecurityLevel::STRONGBOX, @@ -1288,7 +1254,7 @@ impl LegacyBlobLoader { }; let key_parameters = self .read_characteristics_file(uid, &prefix, alias, hw_sec_level, super_key) - .context("In load_by_uid_alias.")?; + .context(ks_err!())?; Some((km_blob, key_parameters)) } None => None, @@ -1296,34 +1262,34 @@ impl LegacyBlobLoader { let user_cert_blob = Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT")) - .context("In load_by_uid_alias: While loading user cert.")?; + .context(ks_err!("While loading user cert."))?; let user_cert = if let Some(blob) = user_cert_blob { let blob = Self::decrypt_if_required(super_key, blob) - .context("In load_by_uid_alias: While decrypting user cert.")?; + .context(ks_err!("While decrypting user cert."))?; if let Blob { value: BlobValue::Generic(data), .. } = blob { Some(data) } else { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In load_by_uid_alias: Found unexpected blob type in USRCERT file"); + .context(ks_err!("Found unexpected blob type in USRCERT file")); } } else { None }; let ca_cert_blob = Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT")) - .context("In load_by_uid_alias: While loading ca cert.")?; + .context(ks_err!("While loading ca cert."))?; let ca_cert = if let Some(blob) = ca_cert_blob { let blob = Self::decrypt_if_required(super_key, blob) - .context("In load_by_uid_alias: While decrypting ca cert.")?; + .context(ks_err!("While decrypting ca cert."))?; if let Blob { value: BlobValue::Generic(data), .. } = blob { Some(data) } else { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In load_by_uid_alias: Found unexpected blob type in CACERT file"); + .context(ks_err!("Found unexpected blob type in CACERT file")); } } else { None @@ -1340,32 +1306,26 @@ impl LegacyBlobLoader { /// Load and decrypt legacy super key blob. pub fn load_super_key(&self, user_id: u32, pw: &Password) -> Result<Option<ZVec>> { let path = self.make_super_key_filename(user_id); - let blob = Self::read_generic_blob(&path) - .context("In load_super_key: While loading super key.")?; + let blob = Self::read_generic_blob(&path).context(ks_err!("While loading super key."))?; let blob = match blob { Some(blob) => match blob { Blob { flags, value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size } } => { if (flags & flags::ENCRYPTED) != 0 { let key = pw - .derive_key(Some(&salt), key_size) - .context("In load_super_key: Failed to derive key from password.")?; - let blob = aes_gcm_decrypt(&data, &iv, &tag, &key).context( - "In load_super_key: while trying to decrypt legacy super key blob.", - )?; + .derive_key(&salt, key_size) + .context(ks_err!("Failed to derive key from password."))?; + let blob = aes_gcm_decrypt(&data, &iv, &tag, &key) + .context(ks_err!("while trying to decrypt legacy super key blob."))?; Some(blob) } else { // In 2019 we had some unencrypted super keys due to b/141955555. - Some( - data.try_into() - .context("In load_super_key: Trying to convert key into ZVec")?, - ) + Some(data.try_into().context(ks_err!("Trying to convert key into ZVec"))?) } } _ => { - return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context( - "In load_super_key: Found wrong blob type in legacy super key blob file.", - ) + return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) + .context(ks_err!("Found wrong blob type in legacy super key blob file.")); } }, None => None, @@ -1993,7 +1953,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); + let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); @@ -2080,7 +2040,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); + let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); @@ -2168,7 +2128,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); + let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs index 93e17358..9eb702dc 100644 --- a/keystore2/src/legacy_importer.rs +++ b/keystore2/src/legacy_importer.rs @@ -20,6 +20,7 @@ use crate::database::{ }; use crate::error::{map_km_error, Error}; use crate::key_parameter::{KeyParameter, KeyParameterValue}; +use crate::ks_err; use crate::legacy_blob::{self, Blob, BlobValue, LegacyKeyCharacteristics}; use crate::super_key::USER_SUPER_KEY; use crate::utils::{ @@ -185,9 +186,8 @@ impl LegacyImporter { } (Self::STATE_UNINITIALIZED, false) => { // Okay, tough luck. The legacy loader was really completely uninitialized. - return Err(Error::sys()).context( - "In check_state: Legacy loader should not be called uninitialized.", - ); + return Err(Error::sys()) + .context(ks_err!("Legacy loader should not be called uninitialized.")); } (Self::STATE_READY, _) => return Ok(Self::STATE_READY), (s, _) => panic!("Unknown legacy importer state. {} ", s), @@ -227,7 +227,7 @@ impl LegacyImporter { F: FnOnce(&mut LegacyImporterState) -> Result<T> + Send + 'static, { // Short circuit if the database is empty or not initialized (error case). - match self.check_state().context("In do_serialized: Checking state.") { + match self.check_state().context(ks_err!("Checking state.")) { Ok(LegacyImporter::STATE_EMPTY) => return None, Ok(LegacyImporter::STATE_READY) => {} Err(e) => return Some(Err(e)), @@ -266,7 +266,7 @@ impl LegacyImporter { let (new_state, result) = match receiver.recv() { Err(e) => { - return Some(Err(e).context("In do_serialized. Failed to receive from the sender.")) + return Some(Err(e).context(ks_err!("Failed to receive from the sender."))); } Ok(r) => r, }; @@ -357,7 +357,7 @@ impl LegacyImporter { Ok(None) => {} Err(e) => return Err(e), } - let pw = pw.try_clone().context("In with_try_import_super_key: Cloning password.")?; + let pw = pw.try_clone().context(ks_err!("Cloning password."))?; let result = self.do_serialized(move |importer_state| { importer_state.check_and_import_super_key(user_id, &pw) }); @@ -424,7 +424,7 @@ impl LegacyImporterState { }; self.sec_level_to_km_uuid.get(&sec_level).copied().ok_or_else(|| { - anyhow::anyhow!(Error::sys()).context("In get_km_uuid: No KM instance for blob.") + anyhow::anyhow!(Error::sys()).context(ks_err!("No KM instance for blob.")) }) } @@ -446,7 +446,7 @@ impl LegacyImporterState { match self .db .load_super_key(&USER_SUPER_KEY, user_id) - .context("In get_super_key_id_check_unlockable_or_delete: Failed to load super key")? + .context(ks_err!("Failed to load super key"))? { Some((_, entry)) => Ok(entry.id()), None => { @@ -460,17 +460,14 @@ impl LegacyImporterState { // key and return NotFound, because the key will never // be unlocked again. if self.legacy_loader.has_super_key(user_id) { - Err(Error::Rc(ResponseCode::LOCKED)).context( - "In get_super_key_id_check_unlockable_or_delete: \ - Cannot import super key of this key while user is locked.", - ) + Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!( + "Cannot import super key of this key while user is locked." + )) } else { - self.legacy_loader.remove_keystore_entry(uid, alias).context( - "In get_super_key_id_check_unlockable_or_delete: \ - Trying to remove obsolete key.", - )?; - Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In get_super_key_id_check_unlockable_or_delete: Obsolete key.") + self.legacy_loader + .remove_keystore_entry(uid, alias) + .context(ks_err!("Trying to remove obsolete key."))?; + Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context(ks_err!("Obsolete key.")) } } } @@ -487,99 +484,91 @@ impl LegacyImporterState { let (km_blob, params) = match km_blob_params { Some((km_blob, LegacyKeyCharacteristics::File(params))) => (km_blob, params), Some((km_blob, LegacyKeyCharacteristics::Cache(params))) => { - return Ok((Some((km_blob, params)), None)) + return Ok((Some((km_blob, params)), None)); } None => return Ok((None, None)), }; - let km_uuid = self - .get_km_uuid(km_blob.is_strongbox()) - .context("In characteristics_file_to_cache: Trying to get KM UUID")?; + let km_uuid = + self.get_km_uuid(km_blob.is_strongbox()).context(ks_err!("Trying to get KM UUID"))?; let blob = match (&km_blob.value(), super_key.as_ref()) { (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => { - let blob = super_key - .decrypt(data, iv, tag) - .context("In characteristics_file_to_cache: Decryption failed.")?; + let blob = + super_key.decrypt(data, iv, tag).context(ks_err!("Decryption failed."))?; LegacyBlob::ZVec(blob) } (BlobValue::Encrypted { .. }, None) => { - return Err(Error::Rc(ResponseCode::LOCKED)).context( - "In characteristics_file_to_cache: Oh uh, so close. \ - This ancient key cannot be imported unless the user is unlocked.", - ); + return Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!( + "Oh uh, so close. \ + This ancient key cannot be imported unless the user is unlocked." + )); } (BlobValue::Decrypted(data), _) => LegacyBlob::Ref(data), _ => { - return Err(Error::sys()) - .context("In characteristics_file_to_cache: Unexpected blob type.") + return Err(Error::sys()).context(ks_err!("Unexpected blob type.")); } }; - let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &*blob) - .context( - "In characteristics_file_to_cache: Failed to get key characteristics from device.", - )?; + let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &blob) + .context(ks_err!("Failed to get key characteristics from device.",))?; let flags = km_blob.get_flags(); - let (current_blob, superseded_blob) = if let Some(upgraded_blob) = upgraded_blob { - match (km_blob.take_value(), super_key.as_ref()) { - (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => { - let super_key_id = - self.get_super_key_id_check_unlockable_or_delete(uid, alias).context( - "In characteristics_file_to_cache: \ - How is there a super key but no super key id?", - )?; - - let mut superseded_metadata = BlobMetaData::new(); - superseded_metadata.add(BlobMetaEntry::Iv(iv.to_vec())); - superseded_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec())); - superseded_metadata - .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); - superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); - let superseded_blob = (LegacyBlob::Vec(data), superseded_metadata); - - let (data, iv, tag) = super_key.encrypt(&upgraded_blob).context( - "In characteristics_file_to_cache: \ - Failed to encrypt upgraded key blob.", - )?; - ( - Blob::new(flags, BlobValue::Encrypted { data, iv, tag }), - Some(superseded_blob), - ) - } - (BlobValue::Encrypted { .. }, None) => { - return Err(Error::sys()).context( - "In characteristics_file_to_cache: This should not be reachable. \ - The blob could not have been decrypted above.", - ); - } - (BlobValue::Decrypted(data), _) => { - let mut superseded_metadata = BlobMetaData::new(); - superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); - let superseded_blob = (LegacyBlob::ZVec(data), superseded_metadata); - ( - Blob::new( - flags, - BlobValue::Decrypted(upgraded_blob.try_into().context( - "In characteristics_file_to_cache: \ - Failed to convert upgraded blob to ZVec.", - )?), - ), - Some(superseded_blob), - ) - } - _ => { - return Err(Error::sys()).context( - "In characteristics_file_to_cache: This should not be reachable. \ - Any other variant should have resulted in a different error.", - ) + let (current_blob, superseded_blob) = + if let Some(upgraded_blob) = upgraded_blob { + match (km_blob.take_value(), super_key.as_ref()) { + (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => { + let super_key_id = self + .get_super_key_id_check_unlockable_or_delete(uid, alias) + .context(ks_err!("How is there a super key but no super key id?"))?; + + let mut superseded_metadata = BlobMetaData::new(); + superseded_metadata.add(BlobMetaEntry::Iv(iv.to_vec())); + superseded_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec())); + superseded_metadata + .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); + superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); + let superseded_blob = (LegacyBlob::Vec(data), superseded_metadata); + + let (data, iv, tag) = super_key + .encrypt(&upgraded_blob) + .context(ks_err!("Failed to encrypt upgraded key blob."))?; + ( + Blob::new(flags, BlobValue::Encrypted { data, iv, tag }), + Some(superseded_blob), + ) + } + (BlobValue::Encrypted { .. }, None) => { + return Err(Error::sys()).context(ks_err!( + "This should not be reachable. \ + The blob could not have been decrypted above." + )); + } + (BlobValue::Decrypted(data), _) => { + let mut superseded_metadata = BlobMetaData::new(); + superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); + let superseded_blob = (LegacyBlob::ZVec(data), superseded_metadata); + ( + Blob::new( + flags, + BlobValue::Decrypted(upgraded_blob.try_into().context(ks_err!( + "Failed to convert upgraded blob to ZVec." + ))?), + ), + Some(superseded_blob), + ) + } + _ => { + return Err(Error::sys()).context(ks_err!( + "This should not be reachable. \ + Any other variant should have resulted in a different error." + )); + } } - } - } else { - (km_blob, None) - }; + } else { + (km_blob, None) + }; let params = augment_legacy_characteristics_file_with_key_characteristics(km_params, params); @@ -595,10 +584,10 @@ impl LegacyImporterState { super_key: Option<Arc<dyn AesGcm>>, ) -> Result<()> { let alias = key.alias.clone().ok_or_else(|| { - anyhow::anyhow!(Error::sys()).context( - "In check_and_import: Must be Some because \ - our caller must not have called us otherwise.", - ) + anyhow::anyhow!(Error::sys()).context(ks_err!( + "Must be Some because \ + our caller must not have called us otherwise." + )) })?; if self.recently_imported.contains(&RecentImport::new(uid, alias.clone())) { @@ -632,11 +621,11 @@ impl LegacyImporterState { e } }) - .context("In check_and_import: Trying to load legacy blob.")?; + .context(ks_err!("Trying to load legacy blob."))?; let (km_blob_params, superseded_blob) = self .characteristics_file_to_cache(km_blob_params, &super_key, uid, &alias) - .context("In check_and_import: Trying to update legacy charateristics.")?; + .context(ks_err!("Trying to update legacy characteristics."))?; let result = match km_blob_params { Some((km_blob, params)) => { @@ -647,7 +636,7 @@ impl LegacyImporterState { // Get super key id for user id. let super_key_id = self .get_super_key_id_check_unlockable_or_delete(uid, &alias) - .context("In check_and_import: Failed to get super key id.")?; + .context(ks_err!("Failed to get super key id."))?; let mut blob_metadata = BlobMetaData::new(); blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec())); @@ -659,18 +648,17 @@ impl LegacyImporterState { BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()), _ => { return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import: Legacy key has unexpected type.") + .context(ks_err!("Legacy key has unexpected type.")); } }; - let km_uuid = self - .get_km_uuid(is_strongbox) - .context("In check_and_import: Trying to get KM UUID")?; + let km_uuid = + self.get_km_uuid(is_strongbox).context(ks_err!("Trying to get KM UUID"))?; blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); let mut metadata = KeyMetaData::new(); - let creation_date = DateTime::now() - .context("In check_and_import: Trying to make creation time.")?; + let creation_date = + DateTime::now().context(ks_err!("Trying to make creation time."))?; metadata.add(KeyMetaEntry::CreationDate(creation_date)); let blob_info = BlobInfo::new_with_superseded( @@ -689,18 +677,18 @@ impl LegacyImporterState { &metadata, &km_uuid, ) - .context("In check_and_import.")?; + .context(ks_err!())?; Ok(()) } None => { if let Some(ca_cert) = ca_cert { self.db .store_new_certificate(&key, KeyType::Client, &ca_cert, &KEYSTORE_UUID) - .context("In check_and_import: Failed to insert new certificate.")?; + .context(ks_err!("Failed to insert new certificate."))?; Ok(()) } else { Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import: Legacy key not found.") + .context(ks_err!("Legacy key not found.")) } } }; @@ -712,7 +700,7 @@ impl LegacyImporterState { // Delete legacy key from the file system self.legacy_loader .remove_keystore_entry(uid, &alias) - .context("In check_and_import: Trying to remove imported key.")?; + .context(ks_err!("Trying to remove imported key."))?; Ok(()) } Err(e) => Err(e), @@ -727,11 +715,11 @@ impl LegacyImporterState { if let Some(super_key) = self .legacy_loader .load_super_key(user_id, pw) - .context("In check_and_import_super_key: Trying to load legacy super key.")? + .context(ks_err!("Trying to load legacy super key."))? { let (blob, blob_metadata) = crate::super_key::SuperKeyManager::encrypt_with_password(&super_key, pw) - .context("In check_and_import_super_key: Trying to encrypt super key.")?; + .context(ks_err!("Trying to encrypt super key."))?; self.db .store_super_key( @@ -741,16 +729,12 @@ impl LegacyImporterState { &blob_metadata, &KeyMetaData::new(), ) - .context(concat!( - "In check_and_import_super_key: ", - "Trying to insert legacy super_key into the database." - ))?; + .context(ks_err!("Trying to insert legacy super_key into the database."))?; self.legacy_loader.remove_super_key(user_id); self.recently_imported_super_key.insert(user_id); Ok(()) } else { - Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import_super_key: No key found do import.") + Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context(ks_err!("No key found do import.")) } } @@ -765,7 +749,7 @@ impl LegacyImporterState { BulkDeleteRequest::Uid(uid) => ( self.legacy_loader .list_keystore_entries_for_uid(uid) - .context("In bulk_delete: Trying to get aliases for uid.") + .context(ks_err!("Trying to get aliases for uid.")) .map(|aliases| { let mut h = HashMap::<u32, HashSet<String>>::new(); h.insert(uid, aliases.into_iter().collect()); @@ -776,7 +760,7 @@ impl LegacyImporterState { BulkDeleteRequest::User(user_id) => ( self.legacy_loader .list_keystore_entries_for_user(user_id) - .context("In bulk_delete: Trying to get aliases for user_id.")?, + .context(ks_err!("Trying to get aliases for user_id."))?, user_id, ), }; @@ -784,7 +768,7 @@ impl LegacyImporterState { let super_key_id = self .db .load_super_key(&USER_SUPER_KEY, user_id) - .context("In bulk_delete: Failed to load super key")? + .context(ks_err!("Failed to load super key"))? .map(|(_, entry)| entry.id()); for (uid, alias) in aliases @@ -794,7 +778,7 @@ impl LegacyImporterState { let (km_blob_params, _, _) = self .legacy_loader .load_by_uid_alias(uid, &alias, &None) - .context("In bulk_delete: Trying to load legacy blob.")?; + .context(ks_err!("Trying to load legacy blob."))?; // Determine if the key needs special handling to be deleted. let (need_gc, is_super_encrypted) = km_blob_params @@ -848,16 +832,16 @@ impl LegacyImporterState { }; if let Some((blob, blob_metadata)) = mark_deleted { - self.db.set_deleted_blob(&blob, &blob_metadata).context(concat!( - "In bulk_delete: Trying to insert deleted ", - "blob into the database for garbage collection." + self.db.set_deleted_blob(&blob, &blob_metadata).context(ks_err!( + "Trying to insert deleted \ + blob into the database for garbage collection." ))?; } } self.legacy_loader .remove_keystore_entry(uid, &alias) - .context("In bulk_delete: Trying to remove imported key.")?; + .context(ks_err!("Trying to remove imported key."))?; } Ok(()) } @@ -926,18 +910,18 @@ fn get_key_characteristics_without_app_data( blob: &[u8], ) -> Result<(Vec<KeyParameter>, Option<Vec<u8>>)> { let (km_dev, _) = crate::globals::get_keymint_dev_by_uuid(uuid) - .with_context(|| format!("In foo: Trying to get km device for id {:?}", uuid))?; + .with_context(|| ks_err!("Trying to get km device for id {:?}", uuid))?; let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with( &*km_dev, blob, &[], |blob| { - let _wd = wd::watch_millis("In foo: Calling GetKeyCharacteristics.", 500); + let _wd = wd::watch_millis("Calling GetKeyCharacteristics.", 500); map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[])) }, |_| Ok(()), ) - .context("In foo.")?; + .context(ks_err!())?; Ok((key_characteristics_to_internal(characteristics), upgraded_blob)) } diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs index 4a23843f..97948899 100644 --- a/keystore2/src/lib.rs +++ b/keystore2/src/lib.rs @@ -28,6 +28,7 @@ pub mod globals; pub mod id_rotation; /// Internal Representation of Key Parameter and convenience functions. pub mod key_parameter; +pub mod ks_err; pub mod legacy_blob; pub mod legacy_importer; pub mod maintenance; @@ -37,6 +38,7 @@ pub mod operation; pub mod permission; pub mod raw_device; pub mod remote_provisioning; +pub mod rkpd_client; pub mod security_level; pub mod service; pub mod shared_secret_negotiation; diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs index 1fca5d96..5efb798d 100644 --- a/keystore2/src/maintenance.rs +++ b/keystore2/src/maintenance.rs @@ -20,6 +20,7 @@ use crate::error::map_or_log_err; use crate::error::Error; use crate::globals::get_keymint_device; use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY}; +use crate::ks_err; use crate::permission::{KeyPerm, KeystorePerm}; use crate::super_key::{SuperKeyManager, UserState}; use crate::utils::{ @@ -71,8 +72,7 @@ impl Maintenance { fn on_user_password_changed(user_id: i32, password: Option<Password>) -> Result<()> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::ChangePassword) - .context("In on_user_password_changed.")?; + check_keystore_permission(KeystorePerm::ChangePassword).context(ks_err!())?; let mut skm = SUPER_KEY.write().unwrap(); @@ -80,7 +80,7 @@ impl Maintenance { DB.with(|db| { skm.unlock_screen_lock_bound_key(&mut db.borrow_mut(), user_id as u32, pw) }) - .context("In on_user_password_changed: unlock_screen_lock_bound_key failed")?; + .context(ks_err!("unlock_screen_lock_bound_key failed"))?; } match DB @@ -92,12 +92,11 @@ impl Maintenance { password.as_ref(), ) }) - .context("In on_user_password_changed.")? + .context(ks_err!())? { UserState::LskfLocked => { // Error - password can not be changed when the device is locked - Err(Error::Rc(ResponseCode::LOCKED)) - .context("In on_user_password_changed. Device is locked.") + Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")) } _ => { // LskfLocked is the only error case for password change @@ -109,7 +108,7 @@ impl Maintenance { fn add_or_remove_user(&self, user_id: i32) -> Result<()> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::ChangeUser).context("In add_or_remove_user.")?; + check_keystore_permission(KeystorePerm::ChangeUser).context(ks_err!())?; DB.with(|db| { SUPER_KEY.write().unwrap().reset_user( @@ -119,10 +118,10 @@ impl Maintenance { false, ) }) - .context("In add_or_remove_user: Trying to delete keys from db.")?; + .context(ks_err!("Trying to delete keys from db."))?; self.delete_listener .delete_user(user_id as u32) - .context("In add_or_remove_user: While invoking the delete listener.") + .context(ks_err!("While invoking the delete listener.")) } fn clear_namespace(&self, domain: Domain, nspace: i64) -> Result<()> { @@ -131,12 +130,12 @@ impl Maintenance { LEGACY_IMPORTER .bulk_delete_uid(domain, nspace) - .context("In clear_namespace: Trying to delete legacy keys.")?; + .context(ks_err!("Trying to delete legacy keys."))?; DB.with(|db| db.borrow_mut().unbind_keys_for_namespace(domain, nspace)) - .context("In clear_namespace: Trying to delete keys from db.")?; + .context(ks_err!("Trying to delete keys from db."))?; self.delete_listener .delete_namespace(domain, nspace) - .context("In clear_namespace: While invoking the delete listener.") + .context(ks_err!("While invoking the delete listener.")) } fn get_state(user_id: i32) -> Result<AidlUserState> { @@ -151,7 +150,7 @@ impl Maintenance { user_id as u32, ) }) - .context("In get_state. Trying to get UserState.")?; + .context(ks_err!("Trying to get UserState."))?; match state { UserState::Uninitialized => Ok(AidlUserState::UNINITIALIZED), @@ -164,13 +163,13 @@ impl Maintenance { where F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>, { - let (km_dev, _, _) = get_keymint_device(&sec_level) - .context("In call_with_watchdog: getting keymint device")?; + let (km_dev, _, _) = + get_keymint_device(&sec_level).context(ks_err!("getting keymint device"))?; let _wp = wd::watch_millis_with("In call_with_watchdog", 500, move || { format!("Seclevel: {:?} Op: {}", sec_level, name) }); - map_km_error(op(km_dev)).with_context(|| format!("In keymint device: calling {}", name))?; + map_km_error(op(km_dev)).with_context(|| ks_err!("calling {}", name))?; Ok(()) } @@ -203,7 +202,7 @@ impl Maintenance { fn early_boot_ended() -> Result<()> { check_keystore_permission(KeystorePerm::EarlyBootEnded) - .context("In early_boot_ended. Checking permission")?; + .context(ks_err!("Checking permission"))?; log::info!("In early_boot_ended."); if let Err(e) = @@ -216,7 +215,7 @@ impl Maintenance { fn on_device_off_body() -> Result<()> { // Security critical permission check. This statement must return on fail. - check_keystore_permission(KeystorePerm::ReportOffBody).context("In on_device_off_body.")?; + check_keystore_permission(KeystorePerm::ReportOffBody).context(ks_err!())?; DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now())); Ok(()) @@ -228,20 +227,16 @@ impl Maintenance { match source.domain { Domain::SELINUX | Domain::KEY_ID | Domain::APP => (), _ => { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In migrate_key_namespace: \ - Source domain must be one of APP, SELINUX, or KEY_ID.", - ) + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(ks_err!("Source domain must be one of APP, SELINUX, or KEY_ID.")); } }; match destination.domain { Domain::SELINUX | Domain::APP => (), _ => { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In migrate_key_namespace: \ - Destination domain must be one of APP or SELINUX.", - ) + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(ks_err!("Destination domain must be one of APP or SELINUX.")); } }; @@ -264,7 +259,7 @@ impl Maintenance { }, ) }) - .context("In migrate_key_namespace: Failed to load key blob.")?; + .context(ks_err!("Failed to load key blob."))?; { db.borrow_mut().migrate_key_namespace(key_id_guard, destination, calling_uid, |k| { check_key_permission(KeyPerm::Rebind, k, &None) @@ -276,7 +271,7 @@ impl Maintenance { fn delete_all_keys() -> Result<()> { // Security critical permission check. This statement must return on fail. check_keystore_permission(KeystorePerm::DeleteAllKeys) - .context("In delete_all_keys. Checking permission")?; + .context(ks_err!("Checking permission"))?; log::info!("In delete_all_keys."); Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys()) diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs index 3d8d6d3f..cd1cd75d 100644 --- a/keystore2/src/metrics.rs +++ b/keystore2/src/metrics.rs @@ -15,6 +15,7 @@ //! This module implements the IKeystoreMetrics AIDL interface, which exposes the API method for the //! proxy in the system server to pull the aggregated metrics in keystore. use crate::error::map_or_log_err; +use crate::ks_err; use crate::metrics_store::METRICS_STORE; use crate::permission::KeystorePerm; use crate::utils::{check_keystore_permission, watchdog as wd}; @@ -41,7 +42,7 @@ impl Metrics { fn pull_metrics(&self, atom_id: AtomID) -> Result<Vec<KeystoreAtom>> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::PullMetrics).context("In pull_metrics.")?; + check_keystore_permission(KeystorePerm::PullMetrics).context(ks_err!())?; METRICS_STORE.get_atoms(atom_id) } } diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs index 62a7d135..77cead8b 100644 --- a/keystore2/src/metrics_store.rs +++ b/keystore2/src/metrics_store.rs @@ -17,11 +17,11 @@ //! stores them in an in-memory store. //! 2. Returns the collected metrics when requested by the statsd proxy. -use crate::error::{get_error_code, Error}; +use crate::error::get_error_code; use crate::globals::DB; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; +use crate::ks_err; use crate::operation::Outcome; -use crate::remote_provisioning::get_pool_status; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, @@ -41,16 +41,13 @@ use android_security_metrics::aidl::android::security::metrics::{ KeystoreAtom::KeystoreAtom, KeystoreAtomPayload::KeystoreAtomPayload, Outcome::Outcome as MetricsOutcome, Purpose::Purpose as MetricsPurpose, RkpError::RkpError as MetricsRkpError, RkpErrorStats::RkpErrorStats, - RkpPoolStats::RkpPoolStats, SecurityLevel::SecurityLevel as MetricsSecurityLevel, - Storage::Storage as MetricsStorage, + SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage, }; -use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode; use anyhow::{Context, Result}; use lazy_static::lazy_static; use rustutils::system_properties::PropertyWatcherError; use std::collections::HashMap; use std::sync::Mutex; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; // Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only // gets restarted after a crash, during a boot cycle. @@ -94,11 +91,6 @@ impl MetricsStore { return pull_storage_stats(); } - // Process and return RKP pool stats. - if AtomID::RKP_POOL_STATS == atom_id { - return pull_attestation_pool_stats(); - } - // Process keystore crash stats. if AtomID::CRASH_STATS == atom_id { return Ok(vec![KeystoreAtom { @@ -559,49 +551,12 @@ fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> { Ok(atom_vec) } -fn pull_attestation_pool_stats() -> Result<Vec<KeystoreAtom>> { - let mut atoms = Vec::<KeystoreAtom>::new(); - for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] { - // set the expired_by date to be three days from now - let expired_by = SystemTime::now() - .checked_add(Duration::from_secs(60 * 60 * 24 * 3)) - .ok_or(Error::Rc(ResponseCode::SYSTEM_ERROR)) - .context("In pull_attestation_pool_stats: Failed to compute expired by system time.")? - .duration_since(UNIX_EPOCH) - .context("In pull_attestation_pool_stats: Failed to compute expired by duration.")? - .as_millis() as i64; - - let result = get_pool_status(expired_by, *sec_level); - - if let Ok(pool_status) = result { - let rkp_pool_stats = RkpPoolStats { - security_level: process_security_level(*sec_level), - expiring: pool_status.expiring, - unassigned: pool_status.unassigned, - attested: pool_status.attested, - total: pool_status.total, - }; - atoms.push(KeystoreAtom { - payload: KeystoreAtomPayload::RkpPoolStats(rkp_pool_stats), - ..Default::default() - }); - } else { - log::error!( - concat!( - "In pull_attestation_pool_stats: Failed to retrieve pool status", - " for security level: {:?}" - ), - sec_level - ); - } - } - Ok(atoms) -} - /// Log error events related to Remote Key Provisioning (RKP). pub fn log_rkp_error_stats(rkp_error: MetricsRkpError, sec_level: &SecurityLevel) { - let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats( - RkpErrorStats { rkpError: rkp_error, security_level: process_security_level(*sec_level) }); + let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats { + rkpError: rkp_error, + security_level: process_security_level(*sec_level), + }); METRICS_STORE.insert_atom(AtomID::RKP_ERROR_STATS, rkp_error_stats); } @@ -649,8 +604,8 @@ pub fn update_keystore_crash_sysprop() { /// Read the system property: keystore.crash_count. pub fn read_keystore_crash_count() -> Result<i32> { rustutils::system_properties::read("keystore.crash_count") - .context("In read_keystore_crash_count: Failed read property.")? - .context("In read_keystore_crash_count: Property not set.")? + .context(ks_err!("Failed read property."))? + .context(ks_err!("Property not set."))? .parse::<i32>() .map_err(std::convert::Into::into) } diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs index 5da3b326..2034a8a0 100644 --- a/keystore2/src/operation.rs +++ b/keystore2/src/operation.rs @@ -127,6 +127,7 @@ use crate::enforcements::AuthInfo; use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode}; +use crate::ks_err; use crate::metrics_store::log_key_operation_event_stats; use crate::utils::watchdog as wd; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ @@ -320,10 +321,8 @@ impl Operation { let guard = self.outcome.lock().expect("In check_active."); match *guard { Outcome::Unknown => Ok(guard), - _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)).context(format!( - "In check_active: Call on finalized operation with outcome: {:?}.", - *guard - )), + _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) + .context(ks_err!("Call on finalized operation with outcome: {:?}.", *guard)), } } @@ -358,13 +357,13 @@ impl Operation { .lock() .unwrap() .before_update() - .context("In update_aad: Trying to get auth tokens.")?; + .context(ks_err!("Trying to get auth tokens."))?; - self.update_outcome(&mut *outcome, { + self.update_outcome(&mut outcome, { let _wp = wd::watch_millis("Operation::update_aad: calling updateAad", 500); map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())) }) - .context("In update_aad: KeyMint::update failed.")?; + .context(ks_err!("Update failed."))?; Ok(()) } @@ -381,14 +380,14 @@ impl Operation { .lock() .unwrap() .before_update() - .context("In update: Trying to get auth tokens.")?; + .context(ks_err!("Trying to get auth tokens."))?; let output = self - .update_outcome(&mut *outcome, { + .update_outcome(&mut outcome, { let _wp = wd::watch_millis("Operation::update: calling update", 500); map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref())) }) - .context("In update: KeyMint::update failed.")?; + .context(ks_err!("Update failed."))?; if output.is_empty() { Ok(None) @@ -411,10 +410,10 @@ impl Operation { .lock() .unwrap() .before_finish() - .context("In finish: Trying to get auth tokens.")?; + .context(ks_err!("Trying to get auth tokens."))?; let output = self - .update_outcome(&mut *outcome, { + .update_outcome(&mut outcome, { let _wp = wd::watch_millis("Operation::finish: calling finish", 500); map_km_error(self.km_op.finish( input, @@ -424,7 +423,7 @@ impl Operation { confirmation_token.as_deref(), )) }) - .context("In finish: KeyMint::finish failed.")?; + .context(ks_err!("Finish failed."))?; self.auth_info.lock().unwrap().after_finish().context("In finish.")?; @@ -447,7 +446,7 @@ impl Operation { { let _wp = wd::watch_millis("Operation::abort: calling abort", 500); - map_km_error(self.km_op.abort()).context("In abort: KeyMint::abort failed.") + map_km_error(self.km_op.abort()).context(ks_err!("KeyMint::abort failed.")) } } } @@ -790,7 +789,7 @@ impl KeystoreOperation { Ok(mut mutex_guard) => { let result = match &*mutex_guard { Some(op) => { - let result = f(&*op); + let result = f(op); // Any error here means we can discard the operation. if result.is_err() { delete_op = true; @@ -798,7 +797,7 @@ impl KeystoreOperation { result } None => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) - .context("In KeystoreOperation::with_locked_operation"), + .context(ks_err!("KeystoreOperation::with_locked_operation")), }; if delete_op { @@ -811,7 +810,7 @@ impl KeystoreOperation { result } Err(_) => Err(Error::Rc(ResponseCode::OPERATION_BUSY)) - .context("In KeystoreOperation::with_locked_operation"), + .context(ks_err!("KeystoreOperation::with_locked_operation")), } } } @@ -823,7 +822,7 @@ impl IKeystoreOperation for KeystoreOperation { let _wp = wd::watch_millis("IKeystoreOperation::updateAad", 500); map_or_log_err( self.with_locked_operation( - |op| op.update_aad(aad_input).context("In KeystoreOperation::updateAad"), + |op| op.update_aad(aad_input).context(ks_err!("KeystoreOperation::updateAad")), false, ), Ok, @@ -834,7 +833,7 @@ impl IKeystoreOperation for KeystoreOperation { let _wp = wd::watch_millis("IKeystoreOperation::update", 500); map_or_log_err( self.with_locked_operation( - |op| op.update(input).context("In KeystoreOperation::update"), + |op| op.update(input).context(ks_err!("KeystoreOperation::update")), false, ), Ok, @@ -848,7 +847,7 @@ impl IKeystoreOperation for KeystoreOperation { let _wp = wd::watch_millis("IKeystoreOperation::finish", 500); map_or_log_err( self.with_locked_operation( - |op| op.finish(input, signature).context("In KeystoreOperation::finish"), + |op| op.finish(input, signature).context(ks_err!("KeystoreOperation::finish")), true, ), Ok, @@ -859,7 +858,7 @@ impl IKeystoreOperation for KeystoreOperation { let _wp = wd::watch_millis("IKeystoreOperation::abort", 500); map_err_with( self.with_locked_operation( - |op| op.abort(Outcome::Abort).context("In KeystoreOperation::abort"), + |op| op.abort(Outcome::Abort).context(ks_err!("KeystoreOperation::abort")), true, ), |e| { diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs index 22509c46..d9bdf791 100644 --- a/keystore2/src/permission.rs +++ b/keystore2/src/permission.rs @@ -19,6 +19,8 @@ //! defined by keystore2 and keystore2_key respectively. use crate::error::Error as KsError; +use crate::error::ResponseCode; +use crate::ks_err; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, }; @@ -53,7 +55,7 @@ implement_class!( /// the SELinux permissions. #[repr(i32)] #[selinux(class_name = keystore2_key)] - #[derive(Clone, Copy, Debug, PartialEq)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KeyPerm { /// Checked when convert_storage_key_to_ephemeral is called. #[selinux(name = convert_storage_key_to_ephemeral)] @@ -87,7 +89,9 @@ implement_class!( /// Checked when the caller attempts to use a private or public key. #[selinux(name = use)] Use = KeyPermission::USE.0, - /// Checked when the caller attempts to use device ids for attestation. + /// Does nothing, and is not checked. For use of device identifiers, + /// the caller must hold the READ_PRIVILEGED_PHONE_STATE Android + /// permission. #[selinux(name = use_dev_id)] UseDevId = KeyPermission::USE_DEV_ID.0, } @@ -97,7 +101,7 @@ implement_class!( /// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`. /// Using the implement_permission macro we get the same features as `KeyPerm`. #[selinux(class_name = keystore2)] - #[derive(Clone, Copy, Debug, PartialEq)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KeystorePerm { /// Checked when a new auth token is installed. #[selinux(name = add_auth)] @@ -299,8 +303,8 @@ pub fn check_grant_permission( } for p in access_vec.into_iter() { - selinux::check_permission(caller_ctx, &target_context, p).context(format!( - "check_grant_permission: check_permission failed. \ + selinux::check_permission(caller_ctx, &target_context, p).context(ks_err!( + "check_permission failed. \ The caller may have tried to grant a permission that they don't possess. {:?}", p ))? @@ -354,10 +358,10 @@ pub fn check_key_permission( return Err(selinux::Error::perm()) .context("Trying to access key without ownership."); } - getcon().context("check_key_permission: getcon failed.")? + getcon().context(ks_err!("getcon failed."))? } Domain::SELINUX => lookup_keystore2_key_context(key.nspace) - .context("check_key_permission: Domain::SELINUX: Failed to lookup namespace.")?, + .context(ks_err!("Domain::SELINUX: Failed to lookup namespace."))?, Domain::GRANT => { match access_vector { Some(_) => { @@ -366,9 +370,9 @@ pub fn check_key_permission( } None => { // If DOMAIN_GRANT was selected an access vector must be supplied. - return Err(KsError::sys()).context( + return Err(KsError::sys()).context(ks_err!( "Cannot check permission for Domain::GRANT without access vector.", - ); + )); } } } @@ -376,11 +380,12 @@ pub fn check_key_permission( // We should never be called with `Domain::KEY_ID. The database // lookup should have converted this into one of `Domain::APP` // or `Domain::SELINUX`. - return Err(KsError::sys()).context("Cannot check permission for Domain::KEY_ID."); + return Err(KsError::sys()) + .context(ks_err!("Cannot check permission for Domain::KEY_ID.",)); } Domain::BLOB => { let tctx = lookup_keystore2_key_context(key.nspace) - .context("Domain::BLOB: Failed to lookup namespace.")?; + .context(ks_err!("Domain::BLOB: Failed to lookup namespace."))?; // If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob" // permission in addition to the requested permission. selinux::check_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?; @@ -388,7 +393,7 @@ pub fn check_key_permission( tctx } _ => { - return Err(KsError::sys()) + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) .context(format!("Unknown domain value: \"{:?}\".", key.domain)) } }; diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs index 4ce9dceb..fa9872a7 100644 --- a/keystore2/src/raw_device.rs +++ b/keystore2/src/raw_device.rs @@ -22,6 +22,7 @@ use crate::{ }, error::{map_km_error, Error, ErrorCode}, globals::get_keymint_device, + ks_err, super_key::KeyBlob, utils::{key_characteristics_to_internal, watchdog as wd, AID_KEYSTORE}, }; @@ -62,11 +63,13 @@ impl KeyMintDevice { pub const KEY_MINT_V1: i32 = 100; /// Version number of KeyMintDevice@V2 pub const KEY_MINT_V2: i32 = 200; + /// Version number of KeyMintDevice@V3 + pub const KEY_MINT_V3: i32 = 300; /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> { - let (km_dev, hw_info, km_uuid) = get_keymint_device(&security_level) - .context("In KeyMintDevice::get: get_keymint_device failed")?; + let (km_dev, hw_info, km_uuid) = + get_keymint_device(&security_level).context(ks_err!("get_keymint_device failed"))?; Ok(KeyMintDevice { km_dev, @@ -110,12 +113,11 @@ impl KeyMintDevice { where F: FnOnce(&Strong<dyn IKeyMintDevice>) -> Result<KeyCreationResult, binder::Status>, { - let creation_result = map_km_error(creator(&self.km_dev)) - .context("In create_and_store_key: creator failed")?; + let creation_result = + map_km_error(creator(&self.km_dev)).context(ks_err!("creator failed"))?; let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics); - let creation_date = - DateTime::now().context("In create_and_store_key: DateTime::now() failed")?; + let creation_date = DateTime::now().context(ks_err!("DateTime::now() failed"))?; let mut key_metadata = KeyMetaData::new(); key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); @@ -131,7 +133,7 @@ impl KeyMintDevice { &key_metadata, &self.km_uuid, ) - .context("In create_and_store_key: store_new_key failed")?; + .context(ks_err!("store_new_key failed"))?; Ok(()) } @@ -152,7 +154,7 @@ impl KeyMintDevice { key_type: KeyType, ) -> Result<(KeyIdGuard, KeyEntry)> { db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(())) - .context("In lookup_from_desc: load_key_entry failed.") + .context(ks_err!("load_key_entry failed.")) } /// Look up the key in the database, and return None if it is absent. @@ -187,7 +189,7 @@ impl KeyMintDevice { // - because it avoids holding database locks during slow // KeyMint operations let lookup = Self::not_found_is_none(Self::lookup_from_desc(db, key_desc, key_type)) - .context("In lookup_or_generate_key: first lookup failed")?; + .context(ks_err!("first lookup failed"))?; if let Some((key_id_guard, mut key_entry)) = lookup { // If the key is associated with a different km instance @@ -220,7 +222,7 @@ impl KeyMintDevice { }) }, ) - .context("In lookup_or_generate_key: calling getKeyCharacteristics")?; + .context(ks_err!("calling getKeyCharacteristics"))?; if validate_characteristics(&key_characteristics) { return Ok((key_id_guard, key_blob)); @@ -234,7 +236,7 @@ impl KeyMintDevice { self.create_and_store_key(db, key_desc, key_type, |km_dev| { km_dev.generateKey(params, None) }) - .context("In lookup_or_generate_key: generate_and_store_key failed")?; + .context(ks_err!("generate_and_store_key failed"))?; Self::lookup_from_desc(db, key_desc, key_type) .and_then(|(key_id_guard, mut key_entry)| { Ok(( @@ -243,10 +245,10 @@ impl KeyMintDevice { .take_key_blob_info() .ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND)) .map(|(key_blob, _)| KeyBlob::NonSensitive(key_blob)) - .context("Missing key blob info.")?, + .context(ks_err!("Missing key blob info."))?, )) }) - .context("In lookup_or_generate_key: second lookup failed") + .context(ks_err!("second lookup failed")) } /// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and @@ -270,7 +272,7 @@ impl KeyMintDevice { ); self.km_dev.upgradeKey(&key_blob, &[]) }) - .context("In upgrade_keyblob_if_required_with: Upgrade failed")?; + .context(ks_err!("Upgrade failed"))?; let mut new_blob_metadata = BlobMetaData::new(); new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); @@ -281,22 +283,14 @@ impl KeyMintDevice { Some(&upgraded_blob), Some(&new_blob_metadata), ) - .context(concat!( - "In upgrade_keyblob_if_required_with: ", - "Failed to insert upgraded blob into the database" - ))?; + .context(ks_err!("Failed to insert upgraded blob into the database"))?; Ok(( - f(&upgraded_blob).context( - "In upgrade_keyblob_if_required_with: Closure failed after upgrade", - )?, + f(&upgraded_blob).context(ks_err!("Closure failed after upgrade"))?, KeyBlob::NonSensitive(upgraded_blob), )) } - result => Ok(( - result.context("In upgrade_keyblob_if_required_with: Closure failed")?, - key_blob, - )), + result => Ok((result.context(ks_err!("Closure failed"))?, key_blob)), } } @@ -322,15 +316,13 @@ impl KeyMintDevice { self.km_dev.begin(purpose, blob, operation_parameters, auth_token) }) }) - .context("In use_key_in_one_step: Failed to begin operation.")?; - let operation: Strong<dyn IKeyMintOperation> = begin_result - .operation - .ok_or_else(Error::sys) - .context("In use_key_in_one_step: Operation missing")?; + .context(ks_err!("Failed to begin operation."))?; + let operation: Strong<dyn IKeyMintOperation> = + begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?; map_km_error({ let _wp = wd::watch_millis("In use_key_in_one_step: calling: finish", 500); operation.finish(Some(input), None, None, None, None) }) - .context("In use_key_in_one_step: Failed to finish operation.") + .context(ks_err!("Failed to finish operation.")) } } diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs index ea2698f0..811ad98a 100644 --- a/keystore2/src/remote_provisioning.rs +++ b/keystore2/src/remote_provisioning.rs @@ -19,38 +19,21 @@ //! certificate chains signed by some root authority and stored in a keystore SQLite //! DB. -use std::collections::HashMap; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate, - DeviceInfo::DeviceInfo, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, - KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, - MacedPublicKey::MacedPublicKey, ProtectedData::ProtectedData, SecurityLevel::SecurityLevel, + KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, }; -use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{ - AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning, - IRemoteProvisioning::IRemoteProvisioning, - IRemotelyProvisionedKeyPool::BnRemotelyProvisionedKeyPool, - IRemotelyProvisionedKeyPool::IRemotelyProvisionedKeyPool, ImplInfo::ImplInfo, - RemotelyProvisionedKey::RemotelyProvisionedKey, -}; -use android_security_remoteprovisioning::binder::{BinderFeatures, Strong}; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, + Domain::Domain, KeyDescriptor::KeyDescriptor, }; use anyhow::{Context, Result}; use keystore2_crypto::parse_subject_from_certificate; -use serde_cbor::Value; -use std::collections::BTreeMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use crate::database::{CertificateChain, KeyIdGuard, KeystoreDB, Uuid}; -use crate::error::{self, map_or_log_err, map_rem_prov_error, Error}; -use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB}; +use crate::database::Uuid; +use crate::ks_err; use crate::metrics_store::log_rkp_error_stats; -use crate::permission::KeystorePerm; -use crate::utils::{check_keystore_permission, watchdog as wd}; +use crate::rkpd_client::get_rkpd_attestation_key; use android_security_metrics::aidl::android::security::metrics::RkpError::RkpError as MetricsRkpError; /// Contains helper functions to check if remote provisioning is enabled on the system and, if so, @@ -59,18 +42,12 @@ use android_security_metrics::aidl::android::security::metrics::RkpError::RkpErr pub struct RemProvState { security_level: SecurityLevel, km_uuid: Uuid, - is_hal_present: AtomicBool, } -static COSE_KEY_XCOORD: Value = Value::Integer(-2); -static COSE_KEY_YCOORD: Value = Value::Integer(-3); -static COSE_MAC0_LEN: usize = 4; -static COSE_MAC0_PAYLOAD: usize = 2; - impl RemProvState { /// Creates a RemProvState struct. pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Self { - Self { security_level, km_uuid, is_hal_present: AtomicBool::new(true) } + Self { security_level, km_uuid } } /// Returns the uuid for the KM instance attached to this RemProvState struct. @@ -91,30 +68,6 @@ impl RemProvState { .unwrap_or(default_value) } - /// Checks if remote provisioning is enabled and partially caches the result. On a hybrid system - /// remote provisioning can flip from being disabled to enabled depending on responses from the - /// server, so unfortunately caching the presence or absence of the HAL is not enough to fully - /// make decisions about the state of remote provisioning during runtime. - fn check_rem_prov_enabled(&self, db: &mut KeystoreDB) -> Result<bool> { - if self.is_rkp_only() { - return Ok(true); - } - if !self.is_hal_present.load(Ordering::Relaxed) - || get_remotely_provisioned_component(&self.security_level).is_err() - { - self.is_hal_present.store(false, Ordering::Relaxed); - return Ok(false); - } - // To check if remote provisioning is enabled on a system that supports both remote - // provisioning and factory provisioned keys, we only need to check if there are any - // keys at all generated to indicate if the app has gotten the signal to begin filling - // the key pool from the server. - let pool_status = db - .get_attestation_pool_status(0 /* date */, &self.km_uuid) - .context("In check_rem_prov_enabled: failed to get attestation pool status.")?; - Ok(pool_status.total != 0) - } - fn is_asymmetric_key(&self, params: &[KeyParameter]) -> bool { params.iter().any(|kp| { matches!( @@ -130,943 +83,42 @@ impl RemProvState { }) } - /// Checks to see (1) if the key in question should be attested to based on the algorithm and - /// (2) if remote provisioning is present and enabled on the system. If these conditions are - /// met, it makes an attempt to fetch the attestation key assigned to the `caller_uid`. - /// - /// It returns the ResponseCode `OUT_OF_KEYS` if there is not one key currently assigned to the - /// `caller_uid` and there are none available to assign. - pub fn get_remotely_provisioned_attestation_key_and_certs( + /// Fetches attestation key and corresponding certificates from RKPD. + pub fn get_rkpd_attestation_key_and_certs( &self, key: &KeyDescriptor, caller_uid: u32, params: &[KeyParameter], - db: &mut KeystoreDB, - ) -> Result<Option<(KeyIdGuard, AttestationKey, Certificate)>> { - if !self.is_asymmetric_key(params) || !self.check_rem_prov_enabled(db)? { - // There is no remote provisioning component for this security level on the - // device. Return None so the underlying KM instance knows to use its - // factory provisioned key instead. Alternatively, it's not an asymmetric key - // and therefore will not be attested. + ) -> Result<Option<(AttestationKey, Certificate)>> { + if !self.is_asymmetric_key(params) || key.domain != Domain::APP { Ok(None) } else { - match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) { + match get_rkpd_attestation_key(&self.security_level, caller_uid) { Err(e) => { - log::error!( - "In get_remote_provisioning_key_and_certs: Error occurred: {:?}", - e - ); if self.is_rkp_only() { + log::error!("Error occurred: {:?}", e); return Err(e); } - log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID, - &self.security_level); + log::warn!("Error occurred: {:?}", e); + log_rkp_error_stats( + MetricsRkpError::FALL_BACK_DURING_HYBRID, + &self.security_level, + ); Ok(None) } - Ok(v) => match v { - Some((guard, cert_chain)) => Ok(Some(( - guard, - AttestationKey { - keyBlob: cert_chain.private_key.to_vec(), - attestKeyParams: vec![], - issuerSubjectName: parse_subject_from_certificate( - &cert_chain.batch_cert, - ) - .context(concat!( - "In get_remote_provisioning_key_and_certs: Failed to ", - "parse subject." - ))?, - }, - Certificate { encodedCertificate: cert_chain.cert_chain }, - ))), - None => Ok(None), - }, - } - } - } -} -/// Implementation of the IRemoteProvisioning service. -#[derive(Default)] -pub struct RemoteProvisioningService { - device_by_sec_level: HashMap<SecurityLevel, Strong<dyn IRemotelyProvisionedComponent>>, - curve_by_sec_level: HashMap<SecurityLevel, i32>, -} - -impl RemoteProvisioningService { - fn get_dev_by_sec_level( - &self, - sec_level: &SecurityLevel, - ) -> Result<&dyn IRemotelyProvisionedComponent> { - if let Some(dev) = self.device_by_sec_level.get(sec_level) { - Ok(dev.as_ref()) - } else { - Err(error::Error::sys()).context(concat!( - "In get_dev_by_sec_level: Remote instance for requested security level", - " not found." - )) - } - } - - /// Creates a new instance of the remote provisioning service - pub fn new_native_binder() -> Result<Strong<dyn IRemoteProvisioning>> { - let mut result: Self = Default::default(); - let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT) - .context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?; - result.curve_by_sec_level.insert( - SecurityLevel::TRUSTED_ENVIRONMENT, - dev.getHardwareInfo() - .context("In new_native_binder: Failed to get hardware info for the TEE.")? - .supportedEekCurve, - ); - result.device_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, dev); - if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) { - result.curve_by_sec_level.insert( - SecurityLevel::STRONGBOX, - dev.getHardwareInfo() - .context("In new_native_binder: Failed to get hardware info for StrongBox.")? - .supportedEekCurve, - ); - result.device_by_sec_level.insert(SecurityLevel::STRONGBOX, dev); - } - Ok(BnRemoteProvisioning::new_binder(result, BinderFeatures::default())) - } - - fn extract_payload_from_cose_mac(data: &[u8]) -> Result<Value> { - let cose_mac0: Vec<Value> = serde_cbor::from_slice(data).context( - "In extract_payload_from_cose_mac: COSE_Mac0 returned from IRPC cannot be parsed", - )?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(format!( - "In extract_payload_from_cose_mac: COSE_Mac0 has improper length. \ - Expected: {}, Actual: {}", - COSE_MAC0_LEN, - cose_mac0.len(), - )); - } - match &cose_mac0[COSE_MAC0_PAYLOAD] { - Value::Bytes(key) => Ok(serde_cbor::from_slice(key) - .context("In extract_payload_from_cose_mac: COSE_Mac0 payload is malformed.")?), - _ => Err(error::Error::sys()).context( - "In extract_payload_from_cose_mac: COSE_Mac0 payload is the wrong type.", - )?, - } - } - - /// Generates a CBOR blob which will be assembled by the calling code into a larger - /// CBOR blob intended for delivery to a provisioning serever. This blob will contain - /// `num_csr` certificate signing requests for attestation keys generated in the TEE, - /// along with a server provided `eek` and `challenge`. The endpoint encryption key will - /// be used to encrypt the sensitive contents being transmitted to the server, and the - /// challenge will ensure freshness. A `test_mode` flag will instruct the remote provisioning - /// HAL if it is okay to accept EEKs that aren't signed by something that chains back to the - /// baked in root of trust in the underlying IRemotelyProvisionedComponent instance. - #[allow(clippy::too_many_arguments)] - pub fn generate_csr( - &self, - test_mode: bool, - num_csr: i32, - eek: &[u8], - challenge: &[u8], - sec_level: SecurityLevel, - protected_data: &mut ProtectedData, - device_info: &mut DeviceInfo, - ) -> Result<Vec<u8>> { - let dev = self.get_dev_by_sec_level(&sec_level)?; - let (_, _, uuid) = get_keymint_device(&sec_level)?; - let keys_to_sign = DB.with::<_, Result<Vec<MacedPublicKey>>>(|db| { - let mut db = db.borrow_mut(); - Ok(db - .fetch_unsigned_attestation_keys(num_csr, &uuid)? - .iter() - .map(|key| MacedPublicKey { macedKey: key.to_vec() }) - .collect()) - })?; - let mac = map_rem_prov_error(dev.generateCertificateRequest( - test_mode, - &keys_to_sign, - eek, - challenge, - device_info, - protected_data, - )) - .context("In generate_csr: Failed to generate csr")?; - let mut mac_and_keys: Vec<Value> = vec![Value::from(mac)]; - for maced_public_key in keys_to_sign { - mac_and_keys.push( - Self::extract_payload_from_cose_mac(&maced_public_key.macedKey) - .context("In generate_csr: Failed to get the payload from the COSE_Mac0")?, - ) - } - let cbor_array: Value = Value::Array(mac_and_keys); - serde_cbor::to_vec(&cbor_array) - .context("In generate_csr: Failed to serialize the mac and keys array") - } - - /// Provisions a certificate chain for a key whose CSR was included in generate_csr. The - /// `public_key` is used to index into the SQL database in order to insert the `certs` blob - /// which represents a PEM encoded X.509 certificate chain. The `expiration_date` is provided - /// as a convenience from the caller to avoid having to parse the certificates semantically - /// here. - pub fn provision_cert_chain( - &self, - db: &mut KeystoreDB, - public_key: &[u8], - batch_cert: &[u8], - certs: &[u8], - expiration_date: i64, - sec_level: SecurityLevel, - ) -> Result<()> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - db.store_signed_attestation_certificate_chain( - public_key, - batch_cert, - certs, /* DER encoded certificate chain */ - expiration_date, - &uuid, - ) - } - - fn parse_cose_mac0_for_coords(data: &[u8]) -> Result<Vec<u8>> { - let cose_mac0: Vec<Value> = serde_cbor::from_slice(data).context( - "In parse_cose_mac0_for_coords: COSE_Mac0 returned from IRPC cannot be parsed", - )?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(format!( - "In parse_cose_mac0_for_coords: COSE_Mac0 has improper length. \ - Expected: {}, Actual: {}", - COSE_MAC0_LEN, - cose_mac0.len(), - )); - } - let cose_key: BTreeMap<Value, Value> = match &cose_mac0[COSE_MAC0_PAYLOAD] { - Value::Bytes(key) => serde_cbor::from_slice(key) - .context("In parse_cose_mac0_for_coords: COSE_Key is malformed.")?, - _ => Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: COSE_Mac0 payload is the wrong type.")?, - }; - if !cose_key.contains_key(&COSE_KEY_XCOORD) || !cose_key.contains_key(&COSE_KEY_YCOORD) { - return Err(error::Error::sys()).context( - "In parse_cose_mac0_for_coords: \ - COSE_Key returned from IRPC is lacking required fields", - ); - } - let mut raw_key: Vec<u8> = vec![0; 64]; - match &cose_key[&COSE_KEY_XCOORD] { - Value::Bytes(x_coord) if x_coord.len() == 32 => { - raw_key[0..32].clone_from_slice(x_coord) - } - Value::Bytes(x_coord) => { - return Err(error::Error::sys()).context(format!( - "In parse_cose_mac0_for_coords: COSE_Key X-coordinate is not the right length. \ - Expected: 32; Actual: {}", - x_coord.len() - )) - } - _ => { - return Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: COSE_Key X-coordinate is not a bstr") - } - } - match &cose_key[&COSE_KEY_YCOORD] { - Value::Bytes(y_coord) if y_coord.len() == 32 => { - raw_key[32..64].clone_from_slice(y_coord) - } - Value::Bytes(y_coord) => { - return Err(error::Error::sys()).context(format!( - "In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not the right length. \ - Expected: 32; Actual: {}", - y_coord.len() - )) - } - _ => { - return Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not a bstr") - } - } - Ok(raw_key) - } - - /// Submits a request to the Remote Provisioner HAL to generate a signing key pair. - /// `is_test_mode` indicates whether or not the returned public key should be marked as being - /// for testing in order to differentiate them from private keys. If the call is successful, - /// the key pair is then added to the database. - pub fn generate_key_pair( - &self, - db: &mut KeystoreDB, - is_test_mode: bool, - sec_level: SecurityLevel, - ) -> Result<()> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - let dev = self.get_dev_by_sec_level(&sec_level).context(format!( - "In generate_key_pair: Failed to get device for security level {:?}", - sec_level - ))?; - let mut maced_key = MacedPublicKey { macedKey: Vec::new() }; - let priv_key = - map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key)) - .context("In generate_key_pair: Failed to generated ECDSA keypair.")?; - let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey) - .context("In generate_key_pair: Failed to parse raw key")?; - db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid) - .context("In generate_key_pair: Failed to insert attestation key entry") - } - - /// Checks the security level of each available IRemotelyProvisionedComponent hal and returns - /// all levels in an array to the caller. - pub fn get_implementation_info(&self) -> Result<Vec<ImplInfo>> { - Ok(self - .curve_by_sec_level - .iter() - .map(|(sec_level, curve)| ImplInfo { secLevel: *sec_level, supportedCurve: *curve }) - .collect()) - } - - /// Deletes all attestation keys generated by the IRemotelyProvisionedComponent from the device, - /// regardless of what state of the attestation key lifecycle they were in. - pub fn delete_all_keys(&self) -> Result<i64> { - DB.with::<_, Result<i64>>(|db| { - let mut db = db.borrow_mut(); - db.delete_all_attestation_keys() - }) - } -} - -/// Populates the AttestationPoolStatus parcelable with information about how many -/// certs will be expiring by the date provided in `expired_by` along with how many -/// keys have not yet been assigned. -pub fn get_pool_status(expired_by: i64, sec_level: SecurityLevel) -> Result<AttestationPoolStatus> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - DB.with::<_, Result<AttestationPoolStatus>>(|db| { - let mut db = db.borrow_mut(); - // delete_expired_attestation_keys is always safe to call, and will remove anything - // older than the date at the time of calling. No work should be done on the - // attestation keys unless the pool status is checked first, so this call should be - // enough to routinely clean out expired keys. - db.delete_expired_attestation_keys()?; - db.get_attestation_pool_status(expired_by, &uuid) - }) -} - -/// Fetches a remote provisioning attestation key and certificate chain inside of the -/// returned `CertificateChain` struct if one exists for the given caller_uid. If one has not -/// been assigned, this function will assign it. If there are no signed attestation keys -/// available to be assigned, it will return the ResponseCode `OUT_OF_KEYS` -fn get_rem_prov_attest_key( - domain: Domain, - caller_uid: u32, - db: &mut KeystoreDB, - km_uuid: &Uuid, -) -> Result<Option<(KeyIdGuard, CertificateChain)>> { - match domain { - Domain::APP => { - // Attempt to get an Attestation Key once. If it fails, then the app doesn't - // have a valid chain assigned to it. The helper function will return None after - // attempting to assign a key. An error will be thrown if the pool is simply out - // of usable keys. Then another attempt to fetch the just-assigned key will be - // made. If this fails too, something is very wrong. - get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid) - .context("In get_rem_prov_attest_key: Failed to get a key")? - .map_or_else( - || get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid), - |v| Ok(Some(v)), - ) - .context(concat!( - "In get_rem_prov_attest_key: Failed to get a key after", - "attempting to assign one." - ))? - .map_or_else( - || { - Err(Error::sys()).context(concat!( - "In get_rem_prov_attest_key: Attempted to assign a ", - "key and failed silently. Something is very wrong." - )) + Ok(rkpd_key) => Ok(Some(( + AttestationKey { + keyBlob: rkpd_key.keyBlob, + attestKeyParams: vec![], + // Batch certificate is at the beginning of the certificate chain. + issuerSubjectName: parse_subject_from_certificate( + &rkpd_key.encodedCertChain, + ) + .context(ks_err!("Failed to parse subject."))?, }, - |(guard, cert_chain)| Ok(Some((guard, cert_chain))), - ) - } - _ => Ok(None), - } -} - -/// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available. -fn get_rem_prov_attest_key_helper( - domain: Domain, - caller_uid: u32, - db: &mut KeystoreDB, - km_uuid: &Uuid, -) -> Result<Option<(KeyIdGuard, CertificateChain)>> { - let guard_and_chain = db - .retrieve_attestation_key_and_cert_chain(domain, caller_uid as i64, km_uuid) - .context("In get_rem_prov_attest_key_helper: Failed to retrieve a key + cert chain")?; - match guard_and_chain { - Some((guard, cert_chain)) => Ok(Some((guard, cert_chain))), - // Either this app needs to be assigned a key, or the pool is empty. An error will - // be thrown if there is no key available to assign. This will indicate that the app - // should be nudged to provision more keys so keystore can retry. - None => { - db.assign_attestation_key(domain, caller_uid as i64, km_uuid) - .context("In get_rem_prov_attest_key_helper: Failed to assign a key")?; - Ok(None) - } - } -} - -impl binder::Interface for RemoteProvisioningService {} - -// Implementation of IRemoteProvisioning. See AIDL spec at -// :aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl -impl IRemoteProvisioning for RemoteProvisioningService { - fn getPoolStatus( - &self, - expired_by: i64, - sec_level: SecurityLevel, - ) -> binder::Result<AttestationPoolStatus> { - let _wp = wd::watch_millis("IRemoteProvisioning::getPoolStatus", 500); - map_or_log_err(get_pool_status(expired_by, sec_level), Ok) - } - - fn generateCsr( - &self, - test_mode: bool, - num_csr: i32, - eek: &[u8], - challenge: &[u8], - sec_level: SecurityLevel, - protected_data: &mut ProtectedData, - device_info: &mut DeviceInfo, - ) -> binder::Result<Vec<u8>> { - let _wp = wd::watch_millis("IRemoteProvisioning::generateCsr", 500); - map_or_log_err( - self.generate_csr( - test_mode, - num_csr, - eek, - challenge, - sec_level, - protected_data, - device_info, - ), - Ok, - ) - } - - fn provisionCertChain( - &self, - public_key: &[u8], - batch_cert: &[u8], - certs: &[u8], - expiration_date: i64, - sec_level: SecurityLevel, - ) -> binder::Result<()> { - let _wp = wd::watch_millis("IRemoteProvisioning::provisionCertChain", 500); - DB.with::<_, binder::Result<()>>(|db| { - map_or_log_err( - self.provision_cert_chain( - &mut db.borrow_mut(), - public_key, - batch_cert, - certs, - expiration_date, - sec_level, - ), - Ok, - ) - }) - } - - fn generateKeyPair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> binder::Result<()> { - let _wp = wd::watch_millis("IRemoteProvisioning::generateKeyPair", 500); - DB.with::<_, binder::Result<()>>(|db| { - map_or_log_err( - self.generate_key_pair(&mut db.borrow_mut(), is_test_mode, sec_level), - Ok, - ) - }) - } - - fn getImplementationInfo(&self) -> binder::Result<Vec<ImplInfo>> { - let _wp = wd::watch_millis("IRemoteProvisioning::getSecurityLevels", 500); - map_or_log_err(self.get_implementation_info(), Ok) - } - - fn deleteAllKeys(&self) -> binder::Result<i64> { - let _wp = wd::watch_millis("IRemoteProvisioning::deleteAllKeys", 500); - map_or_log_err(self.delete_all_keys(), Ok) - } -} - -/// Implementation of the IRemotelyProvisionedKeyPool service. -#[derive(Default)] -pub struct RemotelyProvisionedKeyPoolService { - unique_id_to_sec_level: HashMap<String, SecurityLevel>, -} - -impl RemotelyProvisionedKeyPoolService { - /// Fetches a remotely provisioned certificate chain and key for the given client uid that - /// was provisioned using the IRemotelyProvisionedComponent with the given id. The same key - /// will be returned for a given caller_uid on every request. If there are no attestation keys - /// available, `OUT_OF_KEYS` is returned. - fn get_attestation_key( - &self, - db: &mut KeystoreDB, - caller_uid: i32, - irpc_id: &str, - ) -> Result<RemotelyProvisionedKey> { - log::info!("get_attestation_key(self, {}, {}", caller_uid, irpc_id); - - let sec_level = self - .unique_id_to_sec_level - .get(irpc_id) - .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT)) - .context(format!("In get_attestation_key: unknown irpc id '{}'", irpc_id))?; - let (_, _, km_uuid) = get_keymint_device(sec_level)?; - - let guard_and_cert_chain = - get_rem_prov_attest_key(Domain::APP, caller_uid as u32, db, &km_uuid) - .context("In get_attestation_key")?; - match guard_and_cert_chain { - Some((_, chain)) => Ok(RemotelyProvisionedKey { - keyBlob: chain.private_key.to_vec(), - encodedCertChain: chain.cert_chain, - }), - // It should be impossible to get `None`, but handle it just in case as a - // precaution against future behavioral changes in `get_rem_prov_attest_key`. - None => Err(error::Error::Rc(ResponseCode::OUT_OF_KEYS)) - .context("In get_attestation_key: No available attestation keys"), - } - } - - /// Creates a new instance of the remotely provisioned key pool service, used for fetching - /// remotely provisioned attestation keys. - pub fn new_native_binder() -> Result<Strong<dyn IRemotelyProvisionedKeyPool>> { - let mut result: Self = Default::default(); - - let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT) - .context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?; - if let Some(id) = dev.getHardwareInfo()?.uniqueId { - result.unique_id_to_sec_level.insert(id, SecurityLevel::TRUSTED_ENVIRONMENT); - } - - if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) { - if let Some(id) = dev.getHardwareInfo()?.uniqueId { - if result.unique_id_to_sec_level.contains_key(&id) { - anyhow::bail!("In new_native_binder: duplicate irpc id found: '{}'", id) - } - result.unique_id_to_sec_level.insert(id, SecurityLevel::STRONGBOX); + Certificate { encodedCertificate: rkpd_key.encodedCertChain }, + ))), } } - - // If none of the remotely provisioned components have unique ids, then we shouldn't - // bother publishing the service, as it's impossible to match keys with their backends. - if result.unique_id_to_sec_level.is_empty() { - anyhow::bail!( - "In new_native_binder: No remotely provisioned components have unique ids" - ) - } - - Ok(BnRemotelyProvisionedKeyPool::new_binder( - result, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - )) - } -} - -impl binder::Interface for RemotelyProvisionedKeyPoolService {} - -// Implementation of IRemotelyProvisionedKeyPool. See AIDL spec at -// :aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl -impl IRemotelyProvisionedKeyPool for RemotelyProvisionedKeyPoolService { - fn getAttestationKey( - &self, - caller_uid: i32, - irpc_id: &str, - ) -> binder::Result<RemotelyProvisionedKey> { - let _wp = wd::watch_millis("IRemotelyProvisionedKeyPool::getAttestationKey", 500); - map_or_log_err(check_keystore_permission(KeystorePerm::GetAttestationKey), Ok)?; - DB.with::<_, binder::Result<RemotelyProvisionedKey>>(|db| { - map_or_log_err(self.get_attestation_key(&mut db.borrow_mut(), caller_uid, irpc_id), Ok) - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_cbor::Value; - use std::collections::BTreeMap; - use std::sync::{Arc, Mutex}; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - RpcHardwareInfo::RpcHardwareInfo, - }; - - #[derive(Default)] - struct MockRemotelyProvisionedComponentValues { - hw_info: RpcHardwareInfo, - private_key: Vec<u8>, - maced_public_key: Vec<u8>, - } - - // binder::Interface requires the Send trait, so we have to use a Mutex even though the test - // is single threaded. - #[derive(Default)] - struct MockRemotelyProvisionedComponent(Arc<Mutex<MockRemotelyProvisionedComponentValues>>); - - impl binder::Interface for MockRemotelyProvisionedComponent {} - - impl IRemotelyProvisionedComponent for MockRemotelyProvisionedComponent { - fn getHardwareInfo(&self) -> binder::Result<RpcHardwareInfo> { - Ok(self.0.lock().unwrap().hw_info.clone()) - } - - fn generateEcdsaP256KeyPair( - &self, - test_mode: bool, - maced_public_key: &mut MacedPublicKey, - ) -> binder::Result<Vec<u8>> { - assert!(test_mode); - maced_public_key.macedKey = self.0.lock().unwrap().maced_public_key.clone(); - Ok(self.0.lock().unwrap().private_key.clone()) - } - - fn generateCertificateRequest( - &self, - _test_mode: bool, - _keys_to_sign: &[MacedPublicKey], - _eek: &[u8], - _challenge: &[u8], - _device_info: &mut DeviceInfo, - _protected_data: &mut ProtectedData, - ) -> binder::Result<Vec<u8>> { - Err(binder::StatusCode::INVALID_OPERATION.into()) - } - } - - // Hard coded cert that can be parsed -- the content doesn't matter for testing, only that it's valid. - fn get_fake_cert() -> Vec<u8> { - vec![ - 0x30, 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0x61, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, - 0x14, 0x3a, 0xd5, 0x67, 0xce, 0xfe, 0x93, 0xe1, 0xea, 0xb7, 0xe4, 0xbf, 0x64, 0x19, - 0xa4, 0x11, 0xe1, 0x87, 0x40, 0x20, 0x37, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, - 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x32, 0x31, 0x30, 0x32, 0x32, - 0x30, 0x38, 0x35, 0x32, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x30, 0x34, 0x32, 0x36, 0x32, - 0x32, 0x30, 0x38, 0x35, 0x32, 0x5a, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, - 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, - 0x00, 0x04, 0x1e, 0xac, 0x0c, 0xe0, 0x0d, 0xc5, 0x25, 0x84, 0x1b, 0xd2, 0x77, 0x2d, - 0xe7, 0xba, 0xf1, 0xde, 0xa7, 0xf6, 0x39, 0x7f, 0x38, 0x91, 0xbf, 0xa4, 0x58, 0xf5, - 0x62, 0x6b, 0xce, 0x06, 0xcf, 0xb9, 0x73, 0x91, 0x0d, 0x8a, 0x60, 0xa0, 0xc6, 0xa2, - 0x22, 0xe6, 0x51, 0x2e, 0x58, 0xd6, 0x43, 0x02, 0x80, 0x43, 0x44, 0x29, 0x38, 0x9a, - 0x99, 0xf3, 0xa4, 0xdd, 0xd0, 0xb4, 0x6f, 0x8b, 0x44, 0x2d, 0xa3, 0x53, 0x30, 0x51, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb, 0x13, 0x68, - 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, 0x42, 0x3c, 0xd9, 0x3f, 0x1a, - 0x33, 0xe9, 0xaa, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xdb, 0x13, 0x68, 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, - 0x42, 0x3c, 0xd9, 0x3f, 0x1a, 0x33, 0xe9, 0xaa, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, - 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, - 0x02, 0x20, 0x10, 0xdf, 0x40, 0xc3, 0x20, 0x54, 0x36, 0xb5, 0xc9, 0x3c, 0x70, 0xe3, - 0x55, 0x37, 0xd2, 0x04, 0x51, 0xeb, 0x0f, 0x18, 0x83, 0xd0, 0x58, 0xa1, 0x08, 0x77, - 0x8d, 0x4d, 0xa4, 0x20, 0xee, 0x33, 0x02, 0x21, 0x00, 0x8d, 0xe3, 0xa6, 0x6c, 0x0d, - 0x86, 0x25, 0xdc, 0x59, 0x0d, 0x21, 0x43, 0x22, 0x3a, 0xb9, 0xa1, 0x73, 0x28, 0xc9, - 0x16, 0x9e, 0x91, 0x15, 0xc4, 0xc3, 0xd7, 0xeb, 0xe5, 0xce, 0xdc, 0x1c, 0x1b, - ] - } - - // Generate a fake COSE_Mac0 with a key that's just `byte` repeated - fn generate_maced_pubkey(byte: u8) -> Vec<u8> { - vec![ - 0x84, 0x43, 0xA1, 0x01, 0x05, 0xA0, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, - 0x01, 0x21, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, 0x22, 0x58, 0x20, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, - ] - } - - #[test] - fn test_parse_cose_mac0_for_coords_raw_bytes() -> Result<()> { - let cose_mac0: Vec<u8> = vec![ - 0x84, 0x01, 0x02, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, - 0x20, 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, - 0x21, 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, - 0x43, 0x22, 0xC8, 0xEE, 0x03, 0x22, 0x58, 0x20, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, - 0x90, 0xA7, 0x5C, 0x5A, 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, - 0xA3, 0xB3, 0x1A, 0xB4, 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, 0x03, - ]; - let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&cose_mac0)?; - assert_eq!( - raw_key, - vec![ - 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, 0x21, - 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, 0x43, - 0x22, 0xC8, 0xEE, 0x03, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, 0x90, 0xA7, 0x5C, 0x5A, - 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, 0xA3, 0xB3, 0x1A, 0xB4, - 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, - ] - ); - Ok(()) - } - - #[test] - fn test_parse_cose_mac0_for_coords_constructed_mac() -> Result<()> { - let x_coord: Vec<u8> = vec![0; 32]; - let y_coord: Vec<u8> = vec![1; 32]; - let mut expected_key: Vec<u8> = Vec::new(); - expected_key.extend(&x_coord); - expected_key.extend(&y_coord); - let key_map: BTreeMap<Value, Value> = BTreeMap::from([ - (Value::Integer(1), Value::Integer(2)), - (Value::Integer(3), Value::Integer(-7)), - (Value::Integer(-1), Value::Integer(1)), - (Value::Integer(-2), Value::Bytes(x_coord)), - (Value::Integer(-3), Value::Bytes(y_coord)), - ]); - let cose_mac0: Vec<Value> = vec![ - Value::Integer(0), - Value::Integer(1), - Value::from(serde_cbor::to_vec(&key_map)?), - Value::Integer(2), - ]; - let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&serde_cbor::to_vec( - &Value::from(cose_mac0), - )?)?; - assert_eq!(expected_key, raw_key); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac() -> Result<()> { - let key_map = Value::Map(BTreeMap::from([(Value::Integer(1), Value::Integer(2))])); - let payload = Value::Bytes(serde_cbor::to_vec(&key_map)?); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_map = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - )?; - assert_eq!(key_map, extracted_map); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_malformed_payload() -> Result<()> { - let payload = Value::Bytes(vec![5; 10]); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_type() -> Result<()> { - let payload = Value::Integer(1); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_length() -> Result<()> { - let cose_mac0 = Value::Array(vec![Value::Integer(0), Value::Integer(1)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key_no_keys_provisioned() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - mock_rpc.0.lock().unwrap().hw_info.uniqueId = Some(String::from("mallory")); - - let mut service: RemotelyProvisionedKeyPoolService = Default::default(); - service - .unique_id_to_sec_level - .insert(String::from("mallory"), SecurityLevel::TRUSTED_ENVIRONMENT); - - assert_eq!( - service - .get_attestation_key(&mut db, 0, "mallory") - .unwrap_err() - .downcast::<error::Error>() - .unwrap(), - error::Error::Rc(ResponseCode::OUT_OF_KEYS) - ); - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; - let irpc_id = "paul"; - let caller_uid = 0; - - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - let mock_values = mock_rpc.0.clone(); - let mut remote_provisioning: RemoteProvisioningService = Default::default(); - remote_provisioning.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc)); - let mut key_pool: RemotelyProvisionedKeyPoolService = Default::default(); - key_pool.unique_id_to_sec_level.insert(String::from(irpc_id), sec_level); - - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![8, 6, 7, 5, 3, 0, 9]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11); - remote_provisioning.generate_key_pair(&mut db, true, sec_level).unwrap(); - - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - let batch_cert = get_fake_cert(); - let certs = &[5, 6, 7, 8]; - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - batch_cert.as_slice(), - certs, - 0, - sec_level - ) - .is_ok()); - - // ensure we got the key we expected - let first_key = key_pool - .get_attestation_key(&mut db, caller_uid, irpc_id) - .context("get first key") - .unwrap(); - assert_eq!(first_key.keyBlob, mock_values.lock().unwrap().private_key); - assert_eq!(first_key.encodedCertChain, certs); - - // ensure that multiple calls get the same key - assert_eq!( - first_key, - key_pool - .get_attestation_key(&mut db, caller_uid, irpc_id) - .context("get second key") - .unwrap() - ); - - // no more keys for new clients - assert_eq!( - key_pool - .get_attestation_key(&mut db, caller_uid + 1, irpc_id) - .unwrap_err() - .downcast::<error::Error>() - .unwrap(), - error::Error::Rc(ResponseCode::OUT_OF_KEYS) - ); - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key_gets_different_key_for_different_client() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; - let irpc_id = "ringo"; - let first_caller = 0; - let second_caller = first_caller + 1; - - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - let mock_values = mock_rpc.0.clone(); - let mut remote_provisioning: RemoteProvisioningService = Default::default(); - remote_provisioning.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc)); - let mut key_pool: RemotelyProvisionedKeyPoolService = Default::default(); - key_pool.unique_id_to_sec_level.insert(String::from(irpc_id), sec_level); - - // generate two distinct keys and provision them with certs - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![3, 1, 4, 1, 5]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11); - assert!(remote_provisioning.generate_key_pair(&mut db, true, sec_level).is_ok()); - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - get_fake_cert().as_slice(), - &[1], - 0, - sec_level - ) - .is_ok()); - - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![9, 0, 2, 1, 0]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x22); - assert!(remote_provisioning.generate_key_pair(&mut db, true, sec_level).is_ok()); - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - get_fake_cert().as_slice(), - &[2], - 0, - sec_level - ) - .is_ok()); - - // make sure each caller gets a distinct key - assert_ne!( - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("get first key") - .unwrap(), - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("get second key") - .unwrap() - ); - - // repeated calls should return the same key for a given caller - assert_eq!( - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("first caller a") - .unwrap(), - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("first caller b") - .unwrap(), - ); - - assert_eq!( - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("second caller a") - .unwrap(), - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("second caller b") - .unwrap() - ); } } diff --git a/keystore2/src/rkpd_client.rs b/keystore2/src/rkpd_client.rs new file mode 100644 index 00000000..0ea2d392 --- /dev/null +++ b/keystore2/src/rkpd_client.rs @@ -0,0 +1,739 @@ +// Copyright 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. + +//! Helper wrapper around RKPD interface. + +use crate::error::{map_binder_status_code, Error, ResponseCode}; +use crate::globals::get_remotely_provisioned_component_name; +use crate::ks_err; +use crate::utils::watchdog as wd; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; +use android_security_rkp_aidl::aidl::android::security::rkp::{ + IGetKeyCallback::BnGetKeyCallback, IGetKeyCallback::ErrorCode::ErrorCode as GetKeyErrorCode, + IGetKeyCallback::IGetKeyCallback, IGetRegistrationCallback::BnGetRegistrationCallback, + IGetRegistrationCallback::IGetRegistrationCallback, IRegistration::IRegistration, + IRemoteProvisioning::IRemoteProvisioning, + IStoreUpgradedKeyCallback::BnStoreUpgradedKeyCallback, + IStoreUpgradedKeyCallback::IStoreUpgradedKeyCallback, + RemotelyProvisionedKey::RemotelyProvisionedKey, +}; +use android_security_rkp_aidl::binder::{BinderFeatures, Interface, Strong}; +use anyhow::{Context, Result}; +use std::sync::Mutex; +use std::time::Duration; +use tokio::sync::oneshot; +use tokio::time::timeout; + +// Normally, we block indefinitely when making calls outside of keystore and rely on watchdog to +// report deadlocks. However, RKPD is mainline updatable. Also, calls to RKPD may wait on network +// for certificates. So, we err on the side of caution and timeout instead. +static RKPD_TIMEOUT: Duration = Duration::from_secs(10); + +fn tokio_rt() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap() +} + +/// Thread-safe channel for sending a value once and only once. If a value has +/// already been send, subsequent calls to send will noop. +struct SafeSender<T> { + inner: Mutex<Option<oneshot::Sender<T>>>, +} + +impl<T> SafeSender<T> { + fn new(sender: oneshot::Sender<T>) -> Self { + Self { inner: Mutex::new(Some(sender)) } + } + + fn send(&self, value: T) { + if let Some(inner) = self.inner.lock().unwrap().take() { + // It's possible for the corresponding receiver to time out and be dropped. In this + // case send() will fail. This error is not actionable though, so only log the error. + if inner.send(value).is_err() { + log::error!("SafeSender::send() failed"); + } + } + } +} + +struct GetRegistrationCallback { + registration_tx: SafeSender<Result<binder::Strong<dyn IRegistration>>>, +} + +impl GetRegistrationCallback { + pub fn new_native_binder( + registration_tx: oneshot::Sender<Result<binder::Strong<dyn IRegistration>>>, + ) -> Strong<dyn IGetRegistrationCallback> { + let result: Self = + GetRegistrationCallback { registration_tx: SafeSender::new(registration_tx) }; + BnGetRegistrationCallback::new_binder(result, BinderFeatures::default()) + } +} + +impl Interface for GetRegistrationCallback {} + +impl IGetRegistrationCallback for GetRegistrationCallback { + fn onSuccess(&self, registration: &Strong<dyn IRegistration>) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetRegistrationCallback::onSuccess", 500); + self.registration_tx.send(Ok(registration.clone())); + Ok(()) + } + fn onCancel(&self) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetRegistrationCallback::onCancel", 500); + log::warn!("IGetRegistrationCallback cancelled"); + self.registration_tx.send( + Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) + .context(ks_err!("GetRegistrationCallback cancelled.")), + ); + Ok(()) + } + fn onError(&self, description: &str) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetRegistrationCallback::onError", 500); + log::error!("IGetRegistrationCallback failed: '{description}'"); + self.registration_tx.send( + Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) + .context(ks_err!("GetRegistrationCallback failed: {:?}", description)), + ); + Ok(()) + } +} + +/// Make a new connection to a IRegistration service. +async fn get_rkpd_registration( + security_level: &SecurityLevel, +) -> Result<binder::Strong<dyn IRegistration>> { + let remote_provisioning: Strong<dyn IRemoteProvisioning> = + map_binder_status_code(binder::get_interface("remote_provisioning")) + .context(ks_err!("Trying to connect to IRemoteProvisioning service."))?; + + let rpc_name = get_remotely_provisioned_component_name(security_level) + .context(ks_err!("Trying to get IRPC name."))?; + + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + + remote_provisioning + .getRegistration(&rpc_name, &cb) + .context(ks_err!("Trying to get registration."))?; + + match timeout(RKPD_TIMEOUT, rx).await { + Err(e) => { + Err(Error::Rc(ResponseCode::SYSTEM_ERROR)).context(ks_err!("Waiting for RKPD: {:?}", e)) + } + Ok(v) => v.unwrap(), + } +} + +struct GetKeyCallback { + key_tx: SafeSender<Result<RemotelyProvisionedKey>>, +} + +impl GetKeyCallback { + pub fn new_native_binder( + key_tx: oneshot::Sender<Result<RemotelyProvisionedKey>>, + ) -> Strong<dyn IGetKeyCallback> { + let result: Self = GetKeyCallback { key_tx: SafeSender::new(key_tx) }; + BnGetKeyCallback::new_binder(result, BinderFeatures::default()) + } +} + +impl Interface for GetKeyCallback {} + +impl IGetKeyCallback for GetKeyCallback { + fn onSuccess(&self, key: &RemotelyProvisionedKey) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetKeyCallback::onSuccess", 500); + self.key_tx.send(Ok(RemotelyProvisionedKey { + keyBlob: key.keyBlob.clone(), + encodedCertChain: key.encodedCertChain.clone(), + })); + Ok(()) + } + fn onCancel(&self) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetKeyCallback::onCancel", 500); + log::warn!("IGetKeyCallback cancelled"); + self.key_tx.send( + Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) + .context(ks_err!("GetKeyCallback cancelled.")), + ); + Ok(()) + } + fn onError(&self, error: GetKeyErrorCode, description: &str) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetKeyCallback::onError", 500); + log::error!("IGetKeyCallback failed: {description}"); + let rc = match error { + GetKeyErrorCode::ERROR_UNKNOWN => ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, + GetKeyErrorCode::ERROR_PERMANENT => ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, + GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY => { + ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY + } + GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH => { + ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE + } + _ => { + log::error!("Unexpected error from rkpd: {error:?}"); + ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR + } + }; + self.key_tx.send(Err(Error::Rc(rc)).context(ks_err!( + "GetKeyCallback failed: {:?} {:?}", + error, + description + ))); + Ok(()) + } +} + +async fn get_rkpd_attestation_key_from_registration_async( + registration: &Strong<dyn IRegistration>, + caller_uid: u32, +) -> Result<RemotelyProvisionedKey> { + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + + registration + .getKey(caller_uid.try_into().unwrap(), &cb) + .context(ks_err!("Trying to get key."))?; + + match timeout(RKPD_TIMEOUT, rx).await { + Err(e) => { + // Make a best effort attempt to cancel the timed out request. + if let Err(e) = registration.cancelGetKey(&cb) { + log::error!("IRegistration::cancelGetKey failed: {:?}", e); + } + Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) + .context(ks_err!("Waiting for RKPD key timed out: {:?}", e)) + } + Ok(v) => v.unwrap(), + } +} + +async fn get_rkpd_attestation_key_async( + security_level: &SecurityLevel, + caller_uid: u32, +) -> Result<RemotelyProvisionedKey> { + let registration = get_rkpd_registration(security_level) + .await + .context(ks_err!("Trying to get to IRegistration service."))?; + get_rkpd_attestation_key_from_registration_async(®istration, caller_uid).await +} + +struct StoreUpgradedKeyCallback { + completer: SafeSender<Result<()>>, +} + +impl StoreUpgradedKeyCallback { + pub fn new_native_binder( + completer: oneshot::Sender<Result<()>>, + ) -> Strong<dyn IStoreUpgradedKeyCallback> { + let result: Self = StoreUpgradedKeyCallback { completer: SafeSender::new(completer) }; + BnStoreUpgradedKeyCallback::new_binder(result, BinderFeatures::default()) + } +} + +impl Interface for StoreUpgradedKeyCallback {} + +impl IStoreUpgradedKeyCallback for StoreUpgradedKeyCallback { + fn onSuccess(&self) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetRegistrationCallback::onSuccess", 500); + self.completer.send(Ok(())); + Ok(()) + } + + fn onError(&self, error: &str) -> binder::Result<()> { + let _wp = wd::watch_millis("IGetRegistrationCallback::onError", 500); + log::error!("IGetRegistrationCallback failed: {error}"); + self.completer.send( + Err(Error::Rc(ResponseCode::SYSTEM_ERROR)) + .context(ks_err!("Failed to store upgraded key: {:?}", error)), + ); + Ok(()) + } +} + +async fn store_rkpd_attestation_key_with_registration_async( + registration: &Strong<dyn IRegistration>, + key_blob: &[u8], + upgraded_blob: &[u8], +) -> Result<()> { + let (tx, rx) = oneshot::channel(); + let cb = StoreUpgradedKeyCallback::new_native_binder(tx); + + registration + .storeUpgradedKeyAsync(key_blob, upgraded_blob, &cb) + .context(ks_err!("Failed to store upgraded blob with RKPD."))?; + + match timeout(RKPD_TIMEOUT, rx).await { + Err(e) => Err(Error::Rc(ResponseCode::SYSTEM_ERROR)) + .context(ks_err!("Waiting for RKPD to complete storing key: {:?}", e)), + Ok(v) => v.unwrap(), + } +} + +async fn store_rkpd_attestation_key_async( + security_level: &SecurityLevel, + key_blob: &[u8], + upgraded_blob: &[u8], +) -> Result<()> { + let registration = get_rkpd_registration(security_level) + .await + .context(ks_err!("Trying to get to IRegistration service."))?; + store_rkpd_attestation_key_with_registration_async(®istration, key_blob, upgraded_blob).await +} + +/// Get attestation key from RKPD. +pub fn get_rkpd_attestation_key( + security_level: &SecurityLevel, + caller_uid: u32, +) -> Result<RemotelyProvisionedKey> { + let _wp = wd::watch_millis("Calling get_rkpd_attestation_key()", 500); + tokio_rt().block_on(get_rkpd_attestation_key_async(security_level, caller_uid)) +} + +/// Store attestation key in RKPD. +pub fn store_rkpd_attestation_key( + security_level: &SecurityLevel, + key_blob: &[u8], + upgraded_blob: &[u8], +) -> Result<()> { + let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); + tokio_rt().block_on(store_rkpd_attestation_key_async(security_level, key_blob, upgraded_blob)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::error::map_km_error; + use crate::globals::get_keymint_device; + use crate::utils::upgrade_keyblob_if_required_with; + use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, AttestationKey::AttestationKey, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue, Tag::Tag, + }; + use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration; + use keystore2_crypto::parse_subject_from_certificate; + use std::collections::HashMap; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::{Arc, Mutex}; + + struct MockRegistrationValues { + key: RemotelyProvisionedKey, + latency: Option<Duration>, + thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>, + } + + struct MockRegistration(Arc<Mutex<MockRegistrationValues>>); + + impl MockRegistration { + pub fn new_native_binder( + key: &RemotelyProvisionedKey, + latency: Option<Duration>, + ) -> Strong<dyn IRegistration> { + let result = Self(Arc::new(Mutex::new(MockRegistrationValues { + key: RemotelyProvisionedKey { + keyBlob: key.keyBlob.clone(), + encodedCertChain: key.encodedCertChain.clone(), + }, + latency, + thread_join_handles: Vec::new(), + }))); + BnRegistration::new_binder(result, BinderFeatures::default()) + } + } + + impl Drop for MockRegistration { + fn drop(&mut self) { + let mut values = self.0.lock().unwrap(); + for handle in values.thread_join_handles.iter_mut() { + // These are test threads. So, no need to worry too much about error handling. + handle.take().unwrap().join().unwrap(); + } + } + } + + impl Interface for MockRegistration {} + + impl IRegistration for MockRegistration { + fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { + let mut values = self.0.lock().unwrap(); + let key = RemotelyProvisionedKey { + keyBlob: values.key.keyBlob.clone(), + encodedCertChain: values.key.encodedCertChain.clone(), + }; + let latency = values.latency; + let get_key_cb = cb.clone(); + + // Need a separate thread to trigger timeout in the caller. + let join_handle = std::thread::spawn(move || { + if let Some(duration) = latency { + std::thread::sleep(duration); + } + get_key_cb.onSuccess(&key).unwrap(); + }); + values.thread_join_handles.push(Some(join_handle)); + Ok(()) + } + + fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { + Ok(()) + } + + fn storeUpgradedKeyAsync( + &self, + _: &[u8], + _: &[u8], + cb: &Strong<dyn IStoreUpgradedKeyCallback>, + ) -> binder::Result<()> { + // We are primarily concerned with timing out correctly. Storing the key in this mock + // registration isn't particularly interesting, so skip that part. + let values = self.0.lock().unwrap(); + let store_cb = cb.clone(); + let latency = values.latency; + + std::thread::spawn(move || { + if let Some(duration) = latency { + std::thread::sleep(duration); + } + store_cb.onSuccess().unwrap(); + }); + Ok(()) + } + } + + fn get_mock_registration( + key: &RemotelyProvisionedKey, + latency: Option<Duration>, + ) -> Result<binder::Strong<dyn IRegistration>> { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + let mock_registration = MockRegistration::new_native_binder(key, latency); + + assert!(cb.onSuccess(&mock_registration).is_ok()); + tokio_rt().block_on(rx).unwrap() + } + + // Using the same key ID makes test cases race with each other. So, we use separate key IDs for + // different test cases. + fn get_next_key_id() -> u32 { + static ID: AtomicU32 = AtomicU32::new(0); + ID.fetch_add(1, Ordering::Relaxed) + } + + #[test] + fn test_get_registration_cb_success() { + let key: RemotelyProvisionedKey = Default::default(); + let registration = get_mock_registration(&key, /*latency=*/ None); + assert!(registration.is_ok()); + } + + #[test] + fn test_get_registration_cb_cancel() { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + assert!(cb.onCancel().is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) + ); + } + + #[test] + fn test_get_registration_cb_error() { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + assert!(cb.onError("error").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) + ); + } + + #[test] + fn test_get_key_cb_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onSuccess(&mock_key).is_ok()); + + let key = tokio_rt().block_on(rx).unwrap().unwrap(); + assert_eq!(key, mock_key); + } + + #[test] + fn test_get_key_cb_cancel() { + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onCancel().is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) + ); + } + + #[test] + fn test_get_key_cb_error() { + let error_mapping = HashMap::from([ + (GetKeyErrorCode::ERROR_UNKNOWN, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + (GetKeyErrorCode::ERROR_PERMANENT, ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR), + ( + GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY, + ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY, + ), + ( + GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH, + ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE, + ), + ]); + + // Loop over the generated list of enum values to better ensure this test stays in + // sync with the AIDL. + for get_key_error in GetKeyErrorCode::enum_values() { + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onError(get_key_error, "error").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(error_mapping[&get_key_error]), + ); + } + } + + #[test] + fn test_store_upgraded_cb_success() { + let (tx, rx) = oneshot::channel(); + let cb = StoreUpgradedKeyCallback::new_native_binder(tx); + assert!(cb.onSuccess().is_ok()); + + tokio_rt().block_on(rx).unwrap().unwrap(); + } + + #[test] + fn test_store_upgraded_key_cb_error() { + let (tx, rx) = oneshot::channel(); + let cb = StoreUpgradedKeyCallback::new_native_binder(tx); + assert!(cb.onError("oh no! it failed").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::SYSTEM_ERROR) + ); + } + + #[test] + fn test_get_mock_key_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); + + let key = tokio_rt() + .block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)) + .unwrap(); + assert_eq!(key, mock_key); + } + + #[test] + fn test_get_mock_key_timeout() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let latency = RKPD_TIMEOUT + Duration::from_secs(1); + let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); + + let result = + tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) + ); + } + + #[test] + fn test_store_mock_key_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); + tokio_rt() + .block_on(store_rkpd_attestation_key_with_registration_async(®istration, &[], &[])) + .unwrap(); + } + + #[test] + fn test_store_mock_key_timeout() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let latency = RKPD_TIMEOUT + Duration::from_secs(1); + let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); + + let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async( + ®istration, + &[], + &[], + )); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::Rc(ResponseCode::SYSTEM_ERROR) + ); + } + + #[test] + fn test_get_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); + } + + #[test] + fn test_get_rkpd_attestation_key_same_caller() { + binder::ProcessState::start_thread_pool(); + let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; + let key_id = get_next_key_id(); + + // Multiple calls should return the same key. + let first_key = get_rkpd_attestation_key(&sec_level, key_id).unwrap(); + let second_key = get_rkpd_attestation_key(&sec_level, key_id).unwrap(); + + assert_eq!(first_key.keyBlob, second_key.keyBlob); + assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain); + } + + #[test] + fn test_get_rkpd_attestation_key_different_caller() { + binder::ProcessState::start_thread_pool(); + let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; + let first_key_id = get_next_key_id(); + let second_key_id = get_next_key_id(); + + // Different callers should be getting different keys. + let first_key = get_rkpd_attestation_key(&sec_level, first_key_id).unwrap(); + let second_key = get_rkpd_attestation_key(&sec_level, second_key_id).unwrap(); + + assert_ne!(first_key.keyBlob, second_key.keyBlob); + assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain); + } + + #[test] + // Couple of things to note: + // 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by + // keystore. + // 2. Storing and reading the stored key is prone to race condition. So, we only do this in one + // test case. + fn test_store_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; + let key_id = get_next_key_id(); + let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + let new_blob: [u8; 8] = rand::random(); + + assert!(store_rkpd_attestation_key(&sec_level, &key.keyBlob, &new_blob).is_ok()); + + let new_key = + get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + + // Restore original key so that we don't leave RKPD with invalid blobs. + assert!(store_rkpd_attestation_key(&sec_level, &new_blob, &key.keyBlob).is_ok()); + assert_eq!(new_key.keyBlob, new_blob); + } + + #[test] + // This is a helper for a manual test. We want to check that after a system upgrade RKPD + // attestation keys can also be upgraded and stored again with RKPD. The steps are: + // 1. Run this test and check in stdout that no key upgrade happened. + // 2. Perform a system upgrade. + // 3. Run this test and check in stdout that key upgrade did happen. + // + // Note that this test must be run with that same UID every time. Running as root, i.e. UID 0, + // should do the trick. Also, use "--nocapture" flag to get stdout. + fn test_rkpd_attestation_key_upgrade() { + binder::ProcessState::start_thread_pool(); + let security_level = SecurityLevel::TRUSTED_ENVIRONMENT; + let (keymint, _, _) = get_keymint_device(&security_level).unwrap(); + let key_id = get_next_key_id(); + let mut key_upgraded = false; + + let key = get_rkpd_attestation_key(&security_level, key_id).unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); + + upgrade_keyblob_if_required_with( + &*keymint, + &key.keyBlob, + /*upgrade_params=*/ &[], + /*km_op=*/ + |blob| { + let params = vec![ + KeyParameter { + tag: Tag::ALGORITHM, + value: KeyParameterValue::Algorithm(Algorithm::AES), + }, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) }, + ]; + let attestation_key = AttestationKey { + keyBlob: blob.to_vec(), + attestKeyParams: vec![], + issuerSubjectName: parse_subject_from_certificate(&key.encodedCertChain) + .unwrap(), + }; + + map_km_error(keymint.generateKey(¶ms, Some(&attestation_key))) + }, + /*new_blob_handler=*/ + |new_blob| { + // This handler is only executed if a key upgrade was performed. + key_upgraded = true; + store_rkpd_attestation_key(&security_level, &key.keyBlob, new_blob).unwrap(); + Ok(()) + }, + ) + .unwrap(); + + if key_upgraded { + println!("RKPD key was upgraded and stored with RKPD."); + } else { + println!("RKPD key was NOT upgraded."); + } + } + + #[test] + fn test_stress_get_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + let mut threads = vec![]; + const NTHREADS: u32 = 10; + const NCALLS: u32 = 1000; + + for _ in 0..NTHREADS { + threads.push(std::thread::spawn(move || { + for _ in 0..NCALLS { + let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id) + .unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); + } + })); + } + + for t in threads { + assert!(t.join().is_ok()); + } + } +} diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs index 28de1ec8..5eed37ce 100644 --- a/keystore2/src/security_level.rs +++ b/keystore2/src/security_level.rs @@ -23,8 +23,10 @@ use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode}; use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY}; use crate::key_parameter::KeyParameter as KsKeyParam; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; +use crate::ks_err; use crate::metrics_store::log_key_creation_event_stats; use crate::remote_provisioning::RemProvState; +use crate::rkpd_client::store_rkpd_attestation_key; use crate::super_key::{KeyBlob, SuperKeyManager}; use crate::utils::{ check_device_attestation_permissions, check_key_permission, @@ -89,7 +91,7 @@ impl KeystoreSecurityLevel { id_rotation_state: IdRotationState, ) -> Result<(Strong<dyn IKeystoreSecurityLevel>, Uuid)> { let (dev, hw_info, km_uuid) = get_keymint_device(&security_level) - .context("In KeystoreSecurityLevel::new_native_binder.")?; + .context(ks_err!("KeystoreSecurityLevel::new_native_binder."))?; let result = BnKeystoreSecurityLevel::new_binder( Self { security_level, @@ -147,7 +149,7 @@ impl KeystoreSecurityLevel { SecurityLevel::SOFTWARE, )); - let creation_date = DateTime::now().context("Trying to make creation time.")?; + let creation_date = DateTime::now().context(ks_err!("Trying to make creation time."))?; let key = match key.domain { Domain::BLOB => KeyDescriptor { @@ -171,7 +173,7 @@ impl KeystoreSecurityLevel { user_id, &key_blob, ) - .context("In store_new_key. Failed to handle super encryption.")?; + .context(ks_err!("Failed to handle super encryption."))?; let mut key_metadata = KeyMetaData::new(); key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); @@ -187,14 +189,14 @@ impl KeystoreSecurityLevel { &key_metadata, &self.km_uuid, ) - .context("In store_new_key.")?; + .context(ks_err!())?; Ok(KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id.id(), ..Default::default() }) }) - .context("In store_new_key.")?, + .context(ks_err!())?, }; Ok(KeyMetadata { @@ -221,20 +223,19 @@ impl KeystoreSecurityLevel { let (km_blob, key_properties, key_id_guard, blob_metadata) = match key.domain { Domain::BLOB => { check_key_permission(KeyPerm::Use, key, &None) - .context("In create_operation: checking use permission for Domain::BLOB.")?; + .context(ks_err!("checking use permission for Domain::BLOB."))?; if forced { - check_key_permission(KeyPerm::ReqForcedOp, key, &None).context( - "In create_operation: checking forced permission for Domain::BLOB.", - )?; + check_key_permission(KeyPerm::ReqForcedOp, key, &None) + .context(ks_err!("checking forced permission for Domain::BLOB."))?; } ( match &key.blob { Some(blob) => blob, None => { - return Err(Error::sys()).context(concat!( - "In create_operation: Key blob must be specified when", - " using Domain::BLOB." - )) + return Err(Error::sys()).context(ks_err!( + "Key blob must be specified when \ + using Domain::BLOB." + )); } }, None, @@ -265,12 +266,12 @@ impl KeystoreSecurityLevel { ) }) }) - .context("In create_operation: Failed to load key blob.")?; + .context(ks_err!("Failed to load key blob."))?; let (blob, blob_metadata) = - key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!( - "In create_operation: Successfully loaded key entry, ", - "but KM blob was missing." + key_entry.take_key_blob_info().ok_or_else(Error::sys).context(ks_err!( + "Successfully loaded key entry, \ + but KM blob was missing." ))?; scoping_blob = blob; @@ -285,11 +286,11 @@ impl KeystoreSecurityLevel { let purpose = operation_parameters.iter().find(|p| p.tag == Tag::PURPOSE).map_or( Err(Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In create_operation: No operation purpose specified."), + .context(ks_err!("No operation purpose specified.")), |kp| match kp.value { KeyParameterValue::KeyPurpose(p) => Ok(p), _ => Err(Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In create_operation: Malformed KeyParameter."), + .context(ks_err!("Malformed KeyParameter.")), }, )?; @@ -306,13 +307,13 @@ impl KeystoreSecurityLevel { operation_parameters.as_ref(), self.hw_info.timestampTokenRequired, ) - .context("In create_operation.")?; + .context(ks_err!())?; let km_blob = SUPER_KEY .read() .unwrap() .unwrap_key_if_required(&blob_metadata, km_blob) - .context("In create_operation. Failed to handle super encryption.")?; + .context(ks_err!("Failed to handle super encryption."))?; let (begin_result, upgraded_blob) = self .upgrade_keyblob_if_required_with( @@ -354,7 +355,7 @@ impl KeystoreSecurityLevel { } }, ) - .context("In create_operation: Failed to begin operation.")?; + .context(ks_err!("Failed to begin operation."))?; let operation_challenge = auth_info.finalize_create_authorization(begin_result.challenge); @@ -369,10 +370,10 @@ impl KeystoreSecurityLevel { LoggingInfo::new(self.security_level, purpose, op_params, upgraded_blob.is_some()), ), None => { - return Err(Error::sys()).context(concat!( - "In create_operation: Begin operation returned successfully, ", - "but did not return a valid operation." - )) + return Err(Error::sys()).context(ks_err!( + "Begin operation returned successfully, \ + but did not return a valid operation." + )); } }; @@ -380,7 +381,7 @@ impl KeystoreSecurityLevel { KeystoreOperation::new_native_binder(operation) .as_binder() .into_interface() - .context("In create_operation: Failed to create IKeystoreOperation.")?; + .context(ks_err!("Failed to create IKeystoreOperation."))?; Ok(CreateOperationResponse { iOperation: Some(op_binder), @@ -407,10 +408,10 @@ impl KeystoreSecurityLevel { // Unconditionally add the CREATION_DATETIME tag and prevent callers from // specifying it. if params.iter().any(|kp| kp.tag == Tag::CREATION_DATETIME) { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Specifying Tag::CREATION_DATETIME is not allowed.", - ); + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Specifying Tag::CREATION_DATETIME is not allowed." + )); } // Add CREATION_DATETIME only if the backend version Keymint V1 (100) or newer. @@ -420,16 +421,16 @@ impl KeystoreSecurityLevel { value: KeyParameterValue::DateTime( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Failed to get epoch time.", - )? + .context(ks_err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Failed to get epoch time." + ))? .as_millis() .try_into() - .context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Failed to convert epoch time.", - )?, + .context(ks_err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Failed to convert epoch time." + ))?, ), }); } @@ -441,9 +442,8 @@ impl KeystoreSecurityLevel { "In KeystoreSecurityLevel::add_required_parameters calling: get_aaid", 500, ); - keystore2_aaid::get_aaid(uid).map_err(|e| { - anyhow!(format!("In add_required_parameters: get_aaid returned status {}.", e)) - }) + keystore2_aaid::get_aaid(uid) + .map_err(|e| anyhow!(ks_err!("get_aaid returned status {}.", e))) }?; result.push(KeyParameter { @@ -456,14 +456,15 @@ impl KeystoreSecurityLevel { if check_key_permission(KeyPerm::GenUniqueId, key, &None).is_err() && check_unique_id_attestation_permissions().is_err() { - return Err(Error::perm()).context( - "In add_required_parameters: \ - Caller does not have the permission to generate a unique ID", - ); + return Err(Error::perm()).context(ks_err!( + "Caller does not have the permission to generate a unique ID" + )); } - if self.id_rotation_state.had_factory_reset_since_id_rotation().context( - "In add_required_parameters: Call to had_factory_reset_since_id_rotation failed.", - )? { + if self + .id_rotation_state + .had_factory_reset_since_id_rotation() + .context(ks_err!("Call to had_factory_reset_since_id_rotation failed."))? + { result.push(KeyParameter { tag: Tag::RESET_SINCE_ID_ROTATION, value: KeyParameterValue::BoolValue(true), @@ -474,8 +475,7 @@ impl KeystoreSecurityLevel { // If the caller requests any device identifier attestation tag, check that they hold the // correct Android permission. if params.iter().any(|kp| is_device_id_attestation_tag(kp.tag)) { - check_device_attestation_permissions().context(concat!( - "In add_required_parameters: ", + check_device_attestation_permissions().context(ks_err!( "Caller does not have the permission to attest device identifiers." ))?; } @@ -513,7 +513,7 @@ impl KeystoreSecurityLevel { ) -> Result<KeyMetadata> { if key.domain != Domain::BLOB && key.alias.is_none() { return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In generate_key: Alias must be specified"); + .context(ks_err!("Alias must be specified")); } let caller_uid = ThreadState::get_calling_uid(); @@ -529,7 +529,7 @@ impl KeystoreSecurityLevel { // generate_key requires the rebind permission. // Must return on error for security reasons. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In generate_key.")?; + check_key_permission(KeyPerm::Rebind, &key, &None).context(ks_err!())?; let attestation_key_info = match (key.domain, attest_key_descriptor) { (Domain::BLOB, _) => None, @@ -544,11 +544,11 @@ impl KeystoreSecurityLevel { &mut db.borrow_mut(), ) }) - .context("In generate_key: Trying to get an attestation key")?, + .context(ks_err!("Trying to get an attestation key"))?, }; let params = self .add_required_parameters(caller_uid, params, &key) - .context("In generate_key: Trying to get aaid.")?; + .context(ks_err!("Trying to get aaid."))?; let creation_result = match attestation_key_info { Some(AttestationKeyInfo::UserGenerated { @@ -581,42 +581,32 @@ impl KeystoreSecurityLevel { }) }, ) - .context("In generate_key: Using user generated attestation key.") + .context(ks_err!("Using user generated attestation key.")) .map(|(result, _)| result), - Some(AttestationKeyInfo::RemoteProvisioned { - key_id_guard, - attestation_key, - attestation_certs, - }) => self - .upgrade_keyblob_if_required_with( - &*self.keymint, - Some(key_id_guard), - &KeyBlob::Ref(&attestation_key.keyBlob), - Some(self.rem_prov_state.get_uuid()), - &[], - |blob| { - map_km_error({ - let _wp = self.watch_millis( - concat!( - "In KeystoreSecurityLevel::generate_key (RemoteProvisioned): ", - "calling generate_key.", - ), - 5000, // Generate can take a little longer. - ); - let dynamic_attest_key = Some(AttestationKey { - keyBlob: blob.to_vec(), - attestKeyParams: vec![], - issuerSubjectName: attestation_key.issuerSubjectName.clone(), - }); - self.keymint.generateKey(¶ms, dynamic_attest_key.as_ref()) - }) - }, - ) - .context("While generating Key with remote provisioned attestation key.") + Some(AttestationKeyInfo::RkpdProvisioned { attestation_key, attestation_certs }) => { + self.upgrade_rkpd_keyblob_if_required_with(&attestation_key.keyBlob, &[], |blob| { + map_km_error({ + let _wp = self.watch_millis( + concat!( + "In KeystoreSecurityLevel::generate_key (RkpdProvisioned): ", + "calling generate_key.", + ), + 5000, // Generate can take a little longer. + ); + let dynamic_attest_key = Some(AttestationKey { + keyBlob: blob.to_vec(), + attestKeyParams: vec![], + issuerSubjectName: attestation_key.issuerSubjectName.clone(), + }); + self.keymint.generateKey(¶ms, dynamic_attest_key.as_ref()) + }) + }) + .context(ks_err!("While generating Key with remote provisioned attestation key.")) .map(|(mut result, _)| { result.certificateChain.push(attestation_certs); result - }), + }) + } None => map_km_error({ let _wp = self.watch_millis( concat!( @@ -627,12 +617,12 @@ impl KeystoreSecurityLevel { ); self.keymint.generateKey(¶ms, None) }) - .context("While generating Key without explicit attestation key."), + .context(ks_err!("While generating Key without explicit attestation key.")), } - .context("In generate_key.")?; + .context(ks_err!())?; let user_id = uid_to_android_user(caller_uid); - self.store_new_key(key, creation_result, user_id, Some(flags)).context("In generate_key.") + self.store_new_key(key, creation_result, user_id, Some(flags)).context(ks_err!()) } fn import_key( @@ -645,7 +635,7 @@ impl KeystoreSecurityLevel { ) -> Result<KeyMetadata> { if key.domain != Domain::BLOB && key.alias.is_none() { return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In import_key: Alias must be specified"); + .context(ks_err!("Alias must be specified")); } let caller_uid = ThreadState::get_calling_uid(); @@ -660,17 +650,17 @@ impl KeystoreSecurityLevel { }; // import_key requires the rebind permission. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_key.")?; + check_key_permission(KeyPerm::Rebind, &key, &None).context(ks_err!("In import_key."))?; let params = self .add_required_parameters(caller_uid, params, &key) - .context("In import_key: Trying to get aaid.")?; + .context(ks_err!("Trying to get aaid."))?; let format = params .iter() .find(|p| p.tag == Tag::ALGORITHM) .ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("No KeyParameter 'Algorithm'.") + .context(ks_err!("No KeyParameter 'Algorithm'.")) .and_then(|p| match &p.value { KeyParameterValue::Algorithm(Algorithm::AES) | KeyParameterValue::Algorithm(Algorithm::HMAC) @@ -678,9 +668,9 @@ impl KeystoreSecurityLevel { KeyParameterValue::Algorithm(Algorithm::RSA) | KeyParameterValue::Algorithm(Algorithm::EC) => Ok(KeyFormat::PKCS8), v => Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context(format!("Unknown Algorithm {:?}.", v)), + .context(ks_err!("Unknown Algorithm {:?}.", v)), }) - .context("In import_key.")?; + .context(ks_err!())?; let km_dev = &self.keymint; let creation_result = map_km_error({ @@ -688,10 +678,10 @@ impl KeystoreSecurityLevel { self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500); km_dev.importKey(¶ms, format, key_data, None /* attestKey */) }) - .context("In import_key: Trying to call importKey")?; + .context(ks_err!("Trying to call importKey"))?; let user_id = uid_to_android_user(caller_uid); - self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.") + self.store_new_key(key, creation_result, user_id, Some(flags)).context(ks_err!()) } fn import_wrapped_key( @@ -708,20 +698,16 @@ impl KeystoreSecurityLevel { domain: Domain::SELINUX, blob: Some(ref blob), alias: Some(_), .. } => blob, _ => { - return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(format!( - concat!( - "In import_wrapped_key: Alias and blob must be specified ", - "and domain must be APP or SELINUX. {:?}" - ), + return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(ks_err!( + "Alias and blob must be specified and domain must be APP or SELINUX. {:?}", key - )) + )); } }; if wrapping_key.domain == Domain::BLOB { - return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context( - "In import_wrapped_key: Import wrapped key not supported for self managed blobs.", - ); + return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) + .context(ks_err!("Import wrapped key not supported for self managed blobs.")); } let caller_uid = ThreadState::get_calling_uid(); @@ -744,7 +730,7 @@ impl KeystoreSecurityLevel { }; // Import_wrapped_key requires the rebind permission for the new key. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_wrapped_key.")?; + check_key_permission(KeyPerm::Rebind, &key, &None).context(ks_err!())?; let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id); @@ -760,20 +746,18 @@ impl KeystoreSecurityLevel { ) }) }) - .context("Failed to load wrapping key.")?; + .context(ks_err!("Failed to load wrapping key."))?; - let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry - .take_key_blob_info() - .ok_or_else(error::Error::sys) - .context("No km_blob after successfully loading key. This should never happen.")?; + let (wrapping_key_blob, wrapping_blob_metadata) = + wrapping_key_entry.take_key_blob_info().ok_or_else(error::Error::sys).context( + ks_err!("No km_blob after successfully loading key. This should never happen."), + )?; let wrapping_key_blob = SUPER_KEY .read() .unwrap() .unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob) - .context( - "In import_wrapped_key. Failed to handle super encryption for wrapping key.", - )?; + .context(ks_err!("Failed to handle super encryption for wrapping key."))?; // km_dev.importWrappedKey does not return a certificate chain. // TODO Do we assume that all wrapped keys are symmetric? @@ -820,10 +804,10 @@ impl KeystoreSecurityLevel { Ok(creation_result) }, ) - .context("In import_wrapped_key.")?; + .context(ks_err!())?; self.store_new_key(key, creation_result, user_id, None) - .context("In import_wrapped_key: Trying to store the new key.") + .context(ks_err!("Trying to store the new key.")) } fn store_upgraded_keyblob( @@ -834,7 +818,7 @@ impl KeystoreSecurityLevel { ) -> Result<()> { let (upgraded_blob_to_be_stored, new_blob_metadata) = SuperKeyManager::reencrypt_if_required(key_blob, upgraded_blob) - .context("In store_upgraded_keyblob: Failed to handle super encryption.")?; + .context(ks_err!("Failed to handle super encryption."))?; let mut new_blob_metadata = new_blob_metadata.unwrap_or_default(); if let Some(uuid) = km_uuid { @@ -850,7 +834,7 @@ impl KeystoreSecurityLevel { Some(&new_blob_metadata), ) }) - .context("In store_upgraded_keyblob: Failed to insert upgraded blob into the database.") + .context(ks_err!("Failed to insert upgraded blob into the database.")) } fn upgrade_keyblob_if_required_with<T, F>( @@ -874,51 +858,66 @@ impl KeystoreSecurityLevel { if key_id_guard.is_some() { // Unwrap cannot panic, because the is_some was true. let kid = key_id_guard.take().unwrap(); - Self::store_upgraded_keyblob(kid, km_uuid, key_blob, upgraded_blob).context( - "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed", - ) + Self::store_upgraded_keyblob(kid, km_uuid, key_blob, upgraded_blob) + .context(ks_err!("store_upgraded_keyblob failed")) } else { Ok(()) } }, ) - .context("In KeystoreSecurityLevel::upgrade_keyblob_if_required_with.")?; + .context(ks_err!())?; // If no upgrade was needed, use the opportunity to reencrypt the blob if required // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no // upgrade was performed above and if one was given in the first place. if key_blob.force_reencrypt() { if let Some(kid) = key_id_guard { - Self::store_upgraded_keyblob(kid, km_uuid, key_blob, key_blob).context(concat!( - "In upgrade_keyblob_if_required_with: ", - "store_upgraded_keyblob failed in forced reencrypt" - ))?; + Self::store_upgraded_keyblob(kid, km_uuid, key_blob, key_blob) + .context(ks_err!("store_upgraded_keyblob failed in forced reencrypt"))?; } } Ok((v, upgraded_blob)) } + fn upgrade_rkpd_keyblob_if_required_with<T, F>( + &self, + key_blob: &[u8], + params: &[KeyParameter], + f: F, + ) -> Result<(T, Option<Vec<u8>>)> + where + F: Fn(&[u8]) -> Result<T, Error>, + { + crate::utils::upgrade_keyblob_if_required_with( + &*self.keymint, + key_blob, + params, + f, + |upgraded_blob| { + store_rkpd_attestation_key(&self.security_level, key_blob, upgraded_blob) + .context(ks_err!("Failed store_rkpd_attestation_key().")) + }, + ) + .context(ks_err!()) + } + fn convert_storage_key_to_ephemeral( &self, storage_key: &KeyDescriptor, ) -> Result<EphemeralStorageKeyResponse> { if storage_key.domain != Domain::BLOB { - return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(concat!( - "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: ", - "Key must be of Domain::BLOB" - )); + return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) + .context(ks_err!("Key must be of Domain::BLOB")); } let key_blob = storage_key .blob .as_ref() .ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context( - "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: No key blob specified", - )?; + .context(ks_err!("No key blob specified"))?; // convert_storage_key_to_ephemeral requires the associated permission check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None) - .context("In convert_storage_key_to_ephemeral: Check permission")?; + .context(ks_err!("Check permission"))?; let km_dev = &self.keymint; match { @@ -942,7 +941,7 @@ impl KeystoreSecurityLevel { ); map_km_error(km_dev.upgradeKey(key_blob, &[])) } - .context("In convert_storage_key_to_ephemeral: Failed to upgrade key blob.")?; + .context(ks_err!("Failed to upgrade key blob."))?; let ephemeral_key = { let _wp = self.watch_millis( "In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)", @@ -950,8 +949,7 @@ impl KeystoreSecurityLevel { ); map_km_error(km_dev.convertStorageKeyToEphemeral(&upgraded_blob)) } - .context(concat!( - "In convert_storage_key_to_ephemeral: ", + .context(ks_err!( "Failed to retrieve ephemeral key (after upgrade)." ))?; Ok(EphemeralStorageKeyResponse { @@ -959,31 +957,30 @@ impl KeystoreSecurityLevel { upgradedBlob: Some(upgraded_blob), }) } - Err(e) => Err(e) - .context("In convert_storage_key_to_ephemeral: Failed to retrieve ephemeral key."), + Err(e) => Err(e).context(ks_err!("Failed to retrieve ephemeral key.")), } } fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { if key.domain != Domain::BLOB { return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In IKeystoreSecurityLevel delete_key: Key must be of Domain::BLOB"); + .context(ks_err!("delete_key: Key must be of Domain::BLOB")); } let key_blob = key .blob .as_ref() .ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) - .context("In IKeystoreSecurityLevel delete_key: No key blob specified")?; + .context(ks_err!("delete_key: No key blob specified"))?; check_key_permission(KeyPerm::Delete, key, &None) - .context("In IKeystoreSecurityLevel delete_key: Checking delete permissions")?; + .context(ks_err!("delete_key: Checking delete permissions"))?; let km_dev = &self.keymint; { let _wp = self.watch_millis("In KeystoreSecuritylevel::delete_key: calling deleteKey", 500); - map_km_error(km_dev.deleteKey(key_blob)).context("In keymint device deleteKey") + map_km_error(km_dev.deleteKey(key_blob)).context(ks_err!("keymint device deleteKey")) } } } diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs index d634e0c0..7ba8cbc2 100644 --- a/keystore2/src/service.rs +++ b/keystore2/src/service.rs @@ -18,10 +18,11 @@ use std::collections::HashMap; use crate::audit_log::log_key_deleted; +use crate::ks_err; use crate::permission::{KeyPerm, KeystorePerm}; use crate::security_level::KeystoreSecurityLevel; use crate::utils::{ - check_grant_permission, check_key_permission, check_keystore_permission, + check_grant_permission, check_key_permission, check_keystore_permission, count_key_entries, key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd, }; use crate::{ @@ -65,10 +66,7 @@ impl KeystoreService { SecurityLevel::TRUSTED_ENVIRONMENT, id_rotation_state.clone(), ) - .context(concat!( - "In KeystoreService::new_native_binder: ", - "Trying to construct mandatory security level TEE." - ))?; + .context(ks_err!("Trying to construct mandatory security level TEE."))?; result.i_sec_level_by_uuid.insert(uuid, dev); result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); @@ -85,9 +83,7 @@ impl KeystoreService { .set_init(move || { (create_thread_local_db(), uuid_by_sec_level, LEGACY_BLOB_LOADER.clone()) }) - .context( - "In KeystoreService::new_native_binder: Trying to initialize the legacy migrator.", - )?; + .context(ks_err!("Trying to initialize the legacy migrator."))?; Ok(BnKeystoreService::new_binder( result, @@ -107,8 +103,7 @@ impl KeystoreService { if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) { Ok(dev.clone()) } else { - Err(error::Error::sys()) - .context("In get_i_sec_level_by_uuid: KeyMint instance for key not found.") + Err(error::Error::sys()).context(ks_err!("KeyMint instance for key not found.")) } } @@ -124,7 +119,7 @@ impl KeystoreService { Ok(dev.clone()) } else { Err(error::Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context("In get_security_level: No such security level.") + .context(ks_err!("No such security level.")) } } @@ -146,12 +141,12 @@ impl KeystoreService { ) }) }) - .context("In get_key_entry, while trying to load key info.")?; + .context(ks_err!("while trying to load key info."))?; let i_sec_level = if !key_entry.pure_cert() { Some( self.get_i_sec_level_by_uuid(key_entry.km_uuid()) - .context("In get_key_entry: Trying to get security level proxy.")?, + .context(ks_err!("Trying to get security level proxy."))?, ) } else { None @@ -173,7 +168,7 @@ impl KeystoreService { .creation_date() .map(|d| d.to_millis_epoch()) .ok_or(Error::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In get_key_entry: Trying to get creation date.")?, + .context(ks_err!("Trying to get creation date."))?, authorizations: key_parameters_to_authorizations(key_entry.into_key_parameters()), }, }) @@ -196,10 +191,7 @@ impl KeystoreService { KeyType::Client, KeyEntryLoadBits::NONE, caller_uid, - |k, av| { - check_key_permission(KeyPerm::Update, k, &av) - .context("In update_subcomponent.") - }, + |k, av| check_key_permission(KeyPerm::Update, k, &av).context(ks_err!()), ) }) { Err(e) => match e.root_cause().downcast_ref::<Error>() { @@ -208,22 +200,23 @@ impl KeystoreService { }, Ok(v) => Ok(Some(v)), } - .context("Failed to load key entry.")?; + .context(ks_err!("Failed to load key entry."))?; let mut db = db.borrow_mut(); if let Some((key_id_guard, _key_entry)) = entry { db.set_blob(&key_id_guard, SubComponentType::CERT, public_cert, None) - .context("Failed to update cert subcomponent.")?; + .context(ks_err!("Failed to update cert subcomponent."))?; db.set_blob(&key_id_guard, SubComponentType::CERT_CHAIN, certificate_chain, None) - .context("Failed to update cert chain subcomponent.")?; + .context(ks_err!("Failed to update cert chain subcomponent."))?; return Ok(()); } // If we reach this point we have to check the special condition where a certificate // entry may be made. if !(public_cert.is_none() && certificate_chain.is_some()) { - return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("No key to update."); + return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .context(ks_err!("No key to update.")); } // So we know that we have a certificate chain and no public cert. @@ -238,13 +231,13 @@ impl KeystoreService { (Domain::SELINUX, Some(_)) => key.clone(), _ => { return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)) - .context("Domain must be APP or SELINUX to insert a certificate.") + .context(ks_err!("Domain must be APP or SELINUX to insert a certificate.")) } }; // Security critical: This must return on failure. Do not remove the `?`; check_key_permission(KeyPerm::Rebind, &key, &None) - .context("Caller does not have permission to insert this certificate.")?; + .context(ks_err!("Caller does not have permission to insert this certificate."))?; db.store_new_certificate( &key, @@ -252,23 +245,29 @@ impl KeystoreService { certificate_chain.unwrap(), &KEYSTORE_UUID, ) - .context("Failed to insert new certificate.")?; + .context(ks_err!("Failed to insert new certificate."))?; Ok(()) }) - .context("In update_subcomponent.") + .context(ks_err!()) } - fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { + fn get_key_descriptor_for_lookup( + &self, + domain: Domain, + namespace: i64, + ) -> Result<KeyDescriptor> { let mut k = match domain { Domain::APP => KeyDescriptor { domain, nspace: ThreadState::get_calling_uid() as u64 as i64, ..Default::default() }, - Domain::SELINUX => KeyDescriptor{domain, nspace: namespace, ..Default::default()}, - _ => return Err(Error::perm()).context( - "In list_entries: List entries is only supported for Domain::APP and Domain::SELINUX." - ), + Domain::SELINUX => KeyDescriptor { domain, nspace: namespace, ..Default::default() }, + _ => { + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!( + "List entries is only supported for Domain::APP and Domain::SELINUX." + )) + } }; // First we check if the caller has the info permission for the selected domain/namespace. @@ -278,19 +277,40 @@ impl KeystoreService { // selected. if let Err(e) = check_key_permission(KeyPerm::GetInfo, &k, &None) { if let Some(selinux::Error::PermissionDenied) = - e.root_cause().downcast_ref::<selinux::Error>() { - + e.root_cause().downcast_ref::<selinux::Error>() + { check_keystore_permission(KeystorePerm::List) - .context("In list_entries: While checking keystore permission.")?; + .context(ks_err!("While checking keystore permission."))?; if namespace != -1 { k.nspace = namespace; } } else { - return Err(e).context("In list_entries: While checking key permission.")?; + return Err(e).context(ks_err!("While checking key permission."))?; } } + Ok(k) + } + + fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, None)) + } + + fn count_num_entries(&self, domain: Domain, namespace: i64) -> Result<i32> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; - DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + } + + fn list_entries_batched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> Result<Vec<KeyDescriptor>> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, start_past_alias)) } fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { @@ -301,11 +321,12 @@ impl KeystoreService { DB.with(|db| { LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid, |k, av| { - check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.") + check_key_permission(KeyPerm::Delete, k, &av) + .context(ks_err!("During delete_key.")) }) }) }) - .context("In delete_key: Trying to unbind the key.")?; + .context(ks_err!("Trying to unbind the key."))?; Ok(()) } @@ -330,7 +351,7 @@ impl KeystoreService { ) }) }) - .context("In KeystoreService::grant.") + .context(ks_err!("KeystoreService::grant.")) } fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> { @@ -339,7 +360,7 @@ impl KeystoreService { check_key_permission(KeyPerm::Grant, k, &None) }) }) - .context("In KeystoreService::ungrant.") + .context(ks_err!("KeystoreService::ungrant.")) } } @@ -393,4 +414,18 @@ impl IKeystoreService for KeystoreService { let _wp = wd::watch_millis("IKeystoreService::ungrant", 500); map_or_log_err(self.ungrant(key, grantee_uid), Ok) } + fn listEntriesBatched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> binder::Result<Vec<KeyDescriptor>> { + let _wp = wd::watch_millis("IKeystoreService::listEntriesBatched", 500); + map_or_log_err(self.list_entries_batched(domain, namespace, start_past_alias), Ok) + } + + fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> binder::Result<i32> { + let _wp = wd::watch_millis("IKeystoreService::getNumberOfEntries", 500); + map_or_log_err(self.count_num_entries(domain, namespace), Ok) + } } diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs index 42d38d29..739f4bab 100644 --- a/keystore2/src/shared_secret_negotiation.rs +++ b/keystore2/src/shared_secret_negotiation.rs @@ -23,7 +23,8 @@ use android_hardware_security_sharedsecret::aidl::android::hardware::security::s }; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::Result; -use keystore2_vintf::{get_aidl_instances, get_hidl_instances}; +use binder::get_declared_instances; +use keystore2_vintf::get_hidl_instances; use std::fmt::{self, Display, Formatter}; use std::time::Duration; @@ -111,6 +112,8 @@ 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 SHARED_SECRET_PACKAGE_AND_INTERFACE_NAME: &str = + "android.hardware.security.sharedsecret.ISharedSecret"; static COMPAT_PACKAGE_NAME: &str = "android.security.compat"; /// Lists participants. @@ -121,11 +124,11 @@ fn list_participants() -> Result<Vec<SharedSecretParticipant>> { let mut legacy_strongbox_found: bool = false; Ok([(4, 1), (4, 0)] .iter() - .map(|(ma, mi)| { + .flat_map(|(ma, mi)| { get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME) - .into_iter() + .iter() .filter_map(|name| { - filter_map_legacy_km_instances(name, (*ma, *mi)).and_then(|sp| { + filter_map_legacy_km_instances(name.to_string(), (*ma, *mi)).and_then(|sp| { if let SharedSecretParticipant::Hidl { is_strongbox: true, .. } = &sp { if !legacy_strongbox_found { legacy_strongbox_found = true; @@ -140,10 +143,9 @@ fn list_participants() -> Result<Vec<SharedSecretParticipant>> { }) .collect::<Vec<SharedSecretParticipant>>() }) - .into_iter() - .flatten() .chain({ - get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME) + get_declared_instances(SHARED_SECRET_PACKAGE_AND_INTERFACE_NAME) + .unwrap() .into_iter() .map(SharedSecretParticipant::Aidl) .collect::<Vec<_>>() diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs index 74e3e560..f0002131 100644 --- a/keystore2/src/super_key.rs +++ b/keystore2/src/super_key.rs @@ -25,6 +25,7 @@ use crate::{ error::Error, error::ResponseCode, key_parameter::{KeyParameter, KeyParameterValue}, + ks_err, legacy_blob::LegacyBlobLoader, legacy_importer::LegacyImporter, raw_device::KeyMintDevice, @@ -156,19 +157,17 @@ pub struct SuperKey { impl AesGcm for SuperKey { fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> { if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { - aes_gcm_decrypt(data, iv, tag, &self.key) - .context("In SuperKey::decrypt: Decryption failed.") + aes_gcm_decrypt(data, iv, tag, &self.key).context(ks_err!("Decryption failed.")) } else { - Err(Error::sys()).context("In SuperKey::decrypt: Key is not an AES key.") + Err(Error::sys()).context(ks_err!("Key is not an AES key.")) } } fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> { if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { - aes_gcm_encrypt(plaintext, &self.key) - .context("In SuperKey::encrypt: Encryption failed.") + aes_gcm_encrypt(plaintext, &self.key).context(ks_err!("Encryption failed.")) } else { - Err(Error::sys()).context("In SuperKey::encrypt: Key is not an AES key.") + Err(Error::sys()).context(ks_err!("Key is not an AES key.")) } } } @@ -203,7 +202,7 @@ impl LockedKey { .as_ref() .map(|(key_blob, _)| KeyBlob::Ref(key_blob)) .ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In LockedKey::decrypt: Missing key blob info.")?; + .context(ks_err!("Missing key blob info."))?; let key_params = vec![ KeyParameterValue::Algorithm(Algorithm::AES), KeyParameterValue::KeySize(256), @@ -270,10 +269,7 @@ impl SkmState { self.key_index.insert(id, Arc::downgrade(super_key)); Ok(()) } else { - Err(Error::sys()).context(format!( - "In add_key_to_key_index: cannot add key with ID {:?}", - super_key.id - )) + Err(Error::sys()).context(ks_err!("Cannot add key with ID {:?}", super_key.id)) } } } @@ -290,8 +286,8 @@ impl SuperKeyManager { log::info!("In set_up_boot_level_cache: called for a second time"); return Ok(()); } - let level_zero_key = get_level_zero_key(db) - .context("In set_up_boot_level_cache: get_level_zero_key failed")?; + let level_zero_key = + get_level_zero_key(db).context(ks_err!("get_level_zero_key failed"))?; skm_guard.data.boot_level_key_cache = Some(Mutex::new(BootLevelKeyCache::new(level_zero_key))); log::info!("Starting boot level watcher."); @@ -307,11 +303,11 @@ impl SuperKeyManager { /// Blocks waiting for system property changes, so must be run in its own thread. fn watch_boot_level(skm: Arc<RwLock<Self>>) -> Result<()> { let mut w = PropertyWatcher::new("keystore.boot_level") - .context("In watch_boot_level: PropertyWatcher::new failed")?; + .context(ks_err!("PropertyWatcher::new failed"))?; loop { let level = w .read(|_n, v| v.parse::<usize>().map_err(std::convert::Into::into)) - .context("In watch_boot_level: read of property failed")?; + .context(ks_err!("read of property failed"))?; // This scope limits the skm_guard life, so we don't hold the skm_guard while // waiting. @@ -322,14 +318,14 @@ impl SuperKeyManager { .boot_level_key_cache .as_mut() .ok_or_else(Error::sys) - .context("In watch_boot_level: Boot level cache not initialized")? + .context(ks_err!("Boot level cache not initialized"))? .get_mut() .unwrap(); if level < MAX_MAX_BOOT_LEVEL { log::info!("Read keystore.boot_level value {}", level); boot_level_key_cache .advance_boot_level(level) - .context("In watch_boot_level: advance_boot_level failed")?; + .context(ks_err!("advance_boot_level failed"))?; } else { log::info!( "keystore.boot_level {} hits maximum {}, finishing.", @@ -340,7 +336,7 @@ impl SuperKeyManager { break; } } - w.wait().context("In watch_boot_level: property wait failed")?; + w.wait().context(ks_err!("property wait failed"))?; } Ok(()) } @@ -363,7 +359,7 @@ impl SuperKeyManager { ) -> Result<()> { self.data .add_key_to_key_index(&super_key) - .context("In install_per_boot_key_for_user: add_key_to_key_index failed")?; + .context(ks_err!("add_key_to_key_index failed"))?; self.data.user_keys.entry(user).or_default().per_boot = Some(super_key); Ok(()) } @@ -379,7 +375,7 @@ impl SuperKeyManager { .as_ref() .map(|b| b.lock().unwrap().aes_key(*level as usize)) .transpose() - .context("In lookup_key: aes_key failed")? + .context(ks_err!("aes_key failed"))? .flatten() .map(|key| { Arc::new(SuperKey { @@ -425,12 +421,12 @@ impl SuperKeyManager { // For backward compatibility we need to check if there is a super key present. let super_key = legacy_blob_loader .load_super_key(user, pw) - .context("In create_new_key: Failed to load legacy key blob.")?; + .context(ks_err!("Failed to load legacy key blob."))?; let super_key = match super_key { None => { // No legacy file was found. So we generate a new key. generate_aes256_key() - .context("In create_new_key: Failed to generate AES 256 key.")? + .context(ks_err!("Failed to generate AES 256 key."))? } Some(key) => key, }; @@ -442,10 +438,10 @@ impl SuperKeyManager { Self::encrypt_with_password(&super_key, pw).context("In create_new_key.") }, ) - .context("In unlock_user_key: Failed to get key id.")?; + .context(ks_err!("Failed to get key id."))?; self.populate_cache_from_super_key_blob(user, USER_SUPER_KEY.algorithm, entry, pw) - .context("In unlock_user_key.")?; + .context(ks_err!())?; Ok(()) } @@ -459,12 +455,12 @@ impl SuperKeyManager { Ok(if let Some(key_id) = SuperKeyIdentifier::from_metadata(metadata) { let super_key = self .lookup_key(&key_id) - .context("In unwrap_key: lookup_key failed")? + .context(ks_err!("lookup_key failed"))? .ok_or(Error::Rc(ResponseCode::LOCKED)) - .context("In unwrap_key: Required super decryption key is not in memory.")?; + .context(ks_err!("Required super decryption key is not in memory."))?; KeyBlob::Sensitive { key: Self::unwrap_key_with_key(blob, metadata, &super_key) - .context("In unwrap_key: unwrap_key_with_key failed")?, + .context(ks_err!("unwrap_key_with_key failed"))?, reencrypt_with: super_key.reencrypt_with.as_ref().unwrap_or(&super_key).clone(), force_reencrypt: super_key.reencrypt_with.is_some(), } @@ -477,14 +473,11 @@ impl SuperKeyManager { fn unwrap_key_with_key(blob: &[u8], metadata: &BlobMetaData, key: &SuperKey) -> Result<ZVec> { match key.algorithm { SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) { - (Some(iv), Some(tag)) => key - .decrypt(blob, iv, tag) - .context("In unwrap_key_with_key: Failed to decrypt the key blob."), - (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!( - concat!( - "In unwrap_key_with_key: Key has incomplete metadata.", - "Present: iv: {}, aead_tag: {}." - ), + (Some(iv), Some(tag)) => { + key.decrypt(blob, iv, tag).context(ks_err!("Failed to decrypt the key blob.")) + } + (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( + "Key has incomplete metadata. Present: iv: {}, aead_tag: {}.", iv.is_some(), tag.is_some(), )), @@ -494,14 +487,12 @@ impl SuperKeyManager { (Some(public_key), Some(salt), Some(iv), Some(aead_tag)) => { ECDHPrivateKey::from_private_key(&key.key) .and_then(|k| k.decrypt_message(public_key, salt, iv, blob, aead_tag)) - .context( - "In unwrap_key_with_key: Failed to decrypt the key blob with ECDH.", - ) + .context(ks_err!("Failed to decrypt the key blob with ECDH.")) } (public_key, salt, iv, aead_tag) => { - Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!( + Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( concat!( - "In unwrap_key_with_key: Key has incomplete metadata.", + "Key has incomplete metadata. ", "Present: public_key: {}, salt: {}, iv: {}, aead_tag: {}." ), public_key.is_some(), @@ -526,14 +517,12 @@ impl SuperKeyManager { ) -> Result<bool> { let key_in_db = db .key_exists(Domain::APP, user_id as u64 as i64, USER_SUPER_KEY.alias, KeyType::Super) - .context("In super_key_exists_in_db_for_user.")?; + .context(ks_err!())?; if key_in_db { Ok(key_in_db) } else { - legacy_importer - .has_super_key(user_id) - .context("In super_key_exists_in_db_for_user: Trying to query legacy db.") + legacy_importer.has_super_key(user_id).context(ks_err!("Trying to query legacy db.")) } } @@ -550,13 +539,13 @@ impl SuperKeyManager { let alias = &USER_SUPER_KEY; let result = legacy_importer .with_try_import_super_key(user_id, pw, || db.load_super_key(alias, user_id)) - .context("In check_and_unlock_super_key. Failed to load super key")?; + .context(ks_err!("Failed to load super key"))?; match result { Some((_, entry)) => { let super_key = self .populate_cache_from_super_key_blob(user_id, alias.algorithm, entry, pw) - .context("In check_and_unlock_super_key.")?; + .context(ks_err!())?; Ok(UserState::LskfUnlocked(super_key)) } None => Ok(UserState::Uninitialized), @@ -578,17 +567,17 @@ impl SuperKeyManager { ) -> Result<UserState> { let super_key_exists_in_db = self .super_key_exists_in_db_for_user(db, legacy_importer, user_id) - .context("In check_and_initialize_super_key. Failed to check if super key exists.")?; + .context(ks_err!("Failed to check if super key exists."))?; if super_key_exists_in_db { Ok(UserState::LskfLocked) } else if let Some(pw) = pw { // Generate a new super key. - let super_key = generate_aes256_key() - .context("In check_and_initialize_super_key: Failed to generate AES 256 key.")?; + let super_key = + generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?; // Derive an AES256 key from the password and re-encrypt the super key // before we insert it in the database. - let (encrypted_super_key, blob_metadata) = Self::encrypt_with_password(&super_key, pw) - .context("In check_and_initialize_super_key.")?; + let (encrypted_super_key, blob_metadata) = + Self::encrypt_with_password(&super_key, pw).context(ks_err!())?; let key_entry = db .store_super_key( @@ -598,7 +587,7 @@ impl SuperKeyManager { &blob_metadata, &KeyMetaData::new(), ) - .context("In check_and_initialize_super_key. Failed to store super key.")?; + .context(ks_err!("Failed to store super key."))?; let super_key = self .populate_cache_from_super_key_blob( @@ -607,7 +596,7 @@ impl SuperKeyManager { key_entry, pw, ) - .context("In check_and_initialize_super_key.")?; + .context(ks_err!())?; Ok(UserState::LskfUnlocked(super_key)) } else { Ok(UserState::Uninitialized) @@ -623,9 +612,7 @@ impl SuperKeyManager { pw: &Password, ) -> Result<Arc<SuperKey>> { let super_key = Self::extract_super_key_from_key_entry(algorithm, entry, pw, None) - .context( - "In populate_cache_from_super_key_blob. Failed to extract super key from key entry", - )?; + .context(ks_err!("Failed to extract super key from key entry"))?; self.install_per_boot_key_for_user(user_id, super_key.clone())?; Ok(super_key) } @@ -646,20 +633,19 @@ impl SuperKeyManager { ) { (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => { // Note that password encryption is AES no matter the value of algorithm. - let key = pw.derive_key(Some(salt), AES_256_KEY_LENGTH).context( - "In extract_super_key_from_key_entry: Failed to generate key from password.", - )?; + let key = pw + .derive_key(salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to generate key from password."))?; - aes_gcm_decrypt(blob, iv, tag, &key).context( - "In extract_super_key_from_key_entry: Failed to decrypt key blob.", - )? + aes_gcm_decrypt(blob, iv, tag, &key) + .context(ks_err!("Failed to decrypt key blob."))? } (enc_by, salt, iv, tag) => { - return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!( + return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( concat!( - "In extract_super_key_from_key_entry: Super key has incomplete metadata.", - "encrypted_by: {:?}; Present: salt: {}, iv: {}, aead_tag: {}." - ), + "Super key has incomplete metadata.", + "encrypted_by: {:?}; Present: salt: {}, iv: {}, aead_tag: {}." + ), enc_by, salt.is_some(), iv.is_some(), @@ -674,8 +660,7 @@ impl SuperKeyManager { reencrypt_with, })) } else { - Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In extract_super_key_from_key_entry: No key blob info.") + Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!("No key blob info.")) } } @@ -686,13 +671,13 @@ impl SuperKeyManager { ) -> Result<(Vec<u8>, BlobMetaData)> { let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?; let derived_key = pw - .derive_key(Some(&salt), AES_256_KEY_LENGTH) - .context("In encrypt_with_password: Failed to derive password.")?; + .derive_key(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive password."))?; let mut metadata = BlobMetaData::new(); metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); metadata.add(BlobMetaEntry::Salt(salt)); let (encrypted_key, iv, tag) = aes_gcm_encrypt(super_key, &derived_key) - .context("In encrypt_with_password: Failed to encrypt new super key.")?; + .context(ks_err!("Failed to encrypt new super key."))?; metadata.add(BlobMetaEntry::Iv(iv)); metadata.add(BlobMetaEntry::AeadTag(tag)); Ok((encrypted_key, metadata)) @@ -711,17 +696,17 @@ impl SuperKeyManager { ) -> Result<(Vec<u8>, BlobMetaData)> { match self .get_user_state(db, legacy_importer, user_id) - .context("In super_encrypt. Failed to get user state.")? + .context(ks_err!("Failed to get user state."))? { UserState::LskfUnlocked(super_key) => { Self::encrypt_with_aes_super_key(key_blob, &super_key) - .context("In super_encrypt_on_key_init. Failed to encrypt the key.") + .context(ks_err!("Failed to encrypt the key.")) } UserState::LskfLocked => { - Err(Error::Rc(ResponseCode::LOCKED)).context("In super_encrypt. Device is locked.") + Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")) } UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED)) - .context("In super_encrypt. LSKF is not setup for the user."), + .context(ks_err!("LSKF is not setup for the user.")), } } @@ -733,12 +718,11 @@ impl SuperKeyManager { super_key: &SuperKey, ) -> Result<(Vec<u8>, BlobMetaData)> { if super_key.algorithm != SuperEncryptionAlgorithm::Aes256Gcm { - return Err(Error::sys()) - .context("In encrypt_with_aes_super_key: unexpected algorithm"); + return Err(Error::sys()).context(ks_err!("unexpected algorithm")); } let mut metadata = BlobMetaData::new(); let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key)) - .context("In encrypt_with_aes_super_key: Failed to encrypt new super key.")?; + .context(ks_err!("Failed to encrypt new super key."))?; metadata.add(BlobMetaEntry::Iv(iv)); metadata.add(BlobMetaEntry::AeadTag(tag)); super_key.id.add_to_metadata(&mut metadata); @@ -762,37 +746,29 @@ impl SuperKeyManager { SuperEncryptionType::None => Ok((key_blob.to_vec(), BlobMetaData::new())), SuperEncryptionType::LskfBound => self .super_encrypt_on_key_init(db, legacy_importer, user_id, key_blob) - .context(concat!( - "In handle_super_encryption_on_key_init. ", - "Failed to super encrypt with LskfBound key." - )), + .context(ks_err!("Failed to super encrypt with LskfBound key.")), SuperEncryptionType::ScreenLockBound => { let entry = self.data.user_keys.get(&user_id).and_then(|e| e.screen_lock_bound.as_ref()); if let Some(super_key) = entry { - Self::encrypt_with_aes_super_key(key_blob, super_key).context(concat!( - "In handle_super_encryption_on_key_init. ", - "Failed to encrypt with ScreenLockBound key." - )) + Self::encrypt_with_aes_super_key(key_blob, super_key) + .context(ks_err!("Failed to encrypt with ScreenLockBound key.")) } else { // Symmetric key is not available, use public key encryption - let loaded = - db.load_super_key(&USER_SCREEN_LOCK_BOUND_P521_KEY, user_id).context( - "In handle_super_encryption_on_key_init: load_super_key failed.", - )?; - let (key_id_guard, key_entry) = loaded.ok_or_else(Error::sys).context( - "In handle_super_encryption_on_key_init: User ECDH key missing.", - )?; - let public_key = - key_entry.metadata().sec1_public_key().ok_or_else(Error::sys).context( - "In handle_super_encryption_on_key_init: sec1_public_key missing.", - )?; + let loaded = db + .load_super_key(&USER_SCREEN_LOCK_BOUND_P521_KEY, user_id) + .context(ks_err!("load_super_key failed."))?; + let (key_id_guard, key_entry) = + loaded.ok_or_else(Error::sys).context(ks_err!("User ECDH key missing."))?; + let public_key = key_entry + .metadata() + .sec1_public_key() + .ok_or_else(Error::sys) + .context(ks_err!("sec1_public_key missing."))?; let mut metadata = BlobMetaData::new(); let (ephem_key, salt, iv, encrypted_key, aead_tag) = - ECDHPrivateKey::encrypt_message(public_key, key_blob).context(concat!( - "In handle_super_encryption_on_key_init: ", - "ECDHPrivateKey::encrypt_message failed." - ))?; + ECDHPrivateKey::encrypt_message(public_key, key_blob) + .context(ks_err!("ECDHPrivateKey::encrypt_message failed."))?; metadata.add(BlobMetaEntry::PublicKey(ephem_key)); metadata.add(BlobMetaEntry::Salt(salt)); metadata.add(BlobMetaEntry::Iv(iv)); @@ -806,13 +782,11 @@ impl SuperKeyManager { let key_id = SuperKeyIdentifier::BootLevel(level); let super_key = self .lookup_key(&key_id) - .context("In handle_super_encryption_on_key_init: lookup_key failed")? + .context(ks_err!("lookup_key failed"))? .ok_or(Error::Rc(ResponseCode::LOCKED)) - .context("In handle_super_encryption_on_key_init: Boot stage key absent")?; - Self::encrypt_with_aes_super_key(key_blob, &super_key).context(concat!( - "In handle_super_encryption_on_key_init: ", - "Failed to encrypt with BootLevel key." - )) + .context(ks_err!("Boot stage key absent"))?; + Self::encrypt_with_aes_super_key(key_blob, &super_key) + .context(ks_err!("Failed to encrypt with BootLevel key.")) } } } @@ -828,7 +802,7 @@ impl SuperKeyManager { KeyBlob::Sensitive { reencrypt_with: super_key, .. } => { let (key, metadata) = Self::encrypt_with_aes_super_key(key_after_upgrade, super_key) - .context("In reencrypt_if_required: Failed to re-super-encrypt key.")?; + .context(ks_err!("Failed to re-super-encrypt key."))?; Ok((KeyBlob::NonSensitive(key), Some(metadata))) } _ => Ok((KeyBlob::Ref(key_after_upgrade), None)), @@ -857,28 +831,22 @@ impl SuperKeyManager { } else { let (super_key, public_key) = match key_type.algorithm { SuperEncryptionAlgorithm::Aes256Gcm => ( - generate_aes256_key() - .context("In get_or_create_super_key: Failed to generate AES 256 key.")?, + generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?, None, ), SuperEncryptionAlgorithm::EcdhP521 => { let key = ECDHPrivateKey::generate() - .context("In get_or_create_super_key: Failed to generate ECDH key")?; + .context(ks_err!("Failed to generate ECDH key"))?; ( - key.private_key() - .context("In get_or_create_super_key: private_key failed")?, - Some( - key.public_key() - .context("In get_or_create_super_key: public_key failed")?, - ), + key.private_key().context(ks_err!("private_key failed"))?, + Some(key.public_key().context(ks_err!("public_key failed"))?), ) } }; // Derive an AES256 key from the password and re-encrypt the super key // before we insert it in the database. let (encrypted_super_key, blob_metadata) = - Self::encrypt_with_password(&super_key, password) - .context("In get_or_create_super_key.")?; + Self::encrypt_with_password(&super_key, password).context(ks_err!())?; let mut key_metadata = KeyMetaData::new(); if let Some(pk) = public_key { key_metadata.add(KeyMetaEntry::Sec1PublicKey(pk)); @@ -891,7 +859,7 @@ impl SuperKeyManager { &blob_metadata, &key_metadata, ) - .context("In get_or_create_super_key. Failed to store super key.")?; + .context(ks_err!("Failed to store super key."))?; Ok(Arc::new(SuperKey { algorithm: key_type.algorithm, key: super_key, @@ -926,7 +894,7 @@ impl SuperKeyManager { screen_lock_bound } else { self.get_or_create_super_key(db, user_id, &USER_SCREEN_LOCK_BOUND_KEY, password, None) - .context("In unlock_screen_lock_bound_key: Trying to get or create symmetric key.")? + .context(ks_err!("Trying to get or create symmetric key."))? }; let ecdh = if let Some(screen_lock_bound_private) = screen_lock_bound_private { @@ -941,7 +909,7 @@ impl SuperKeyManager { password, Some(aes.clone()), ) - .context("In unlock_screen_lock_bound_key: Trying to get or create asymmetric key.")? + .context(ks_err!("Trying to get or create asymmetric key."))? }; self.data.add_key_to_key_index(&aes)?; @@ -974,7 +942,7 @@ impl SuperKeyManager { let encrypting_key = generate_aes256_key()?; let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) - .context("In lock_screen_lock_bound_key: KeyMintDevice::get failed")?; + .context(ks_err!("KeyMintDevice::get failed"))?; let mut key_params = vec![ KeyParameterValue::Algorithm(Algorithm::AES), KeyParameterValue::KeySize(256), @@ -1046,9 +1014,9 @@ impl SuperKeyManager { AID_KEYSTORE, |_, _| Ok(()), ) - .context("In try_unlock_user_with_biometric: load_key_entry failed")?; + .context(ks_err!("load_key_entry failed"))?; let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) - .context("In try_unlock_user_with_biometric: KeyMintDevice::get failed")?; + .context(ks_err!("KeyMintDevice::get failed"))?; for sid in &biometric.sids { if let Some((auth_token_entry, _)) = db.find_auth_token_entry(|entry| { entry.auth_token().userId == *sid || entry.auth_token().authenticatorId == *sid @@ -1078,14 +1046,11 @@ impl SuperKeyManager { entry.screen_lock_bound_private = Some(slbp.clone()); self.data.add_key_to_key_index(&slb)?; self.data.add_key_to_key_index(&slbp)?; - log::info!(concat!( - "In try_unlock_user_with_biometric: ", - "Successfully unlocked with biometric" - )); + log::info!("Successfully unlocked with biometric"); return Ok(()); } Err(e) => { - log::warn!("In try_unlock_user_with_biometric: attempt failed: {:?}", e) + log::warn!("attempt failed: {:?}", e) } } } @@ -1110,7 +1075,7 @@ impl SuperKeyManager { // If so, return locked user state. if self .super_key_exists_in_db_for_user(db, legacy_importer, user_id) - .context("In get_user_state.")? + .context(ks_err!())? { Ok(UserState::LskfLocked) } else { @@ -1141,9 +1106,8 @@ impl SuperKeyManager { Some(_) if password.is_none() => { // Transitioning to swiping, delete only the super key in database and cache, // and super-encrypted keys in database (and in KM). - self.reset_user(db, legacy_importer, user_id, true).context( - "In reset_or_init_user_and_get_user_state: Trying to delete keys from the db.", - )?; + self.reset_user(db, legacy_importer, user_id, true) + .context(ks_err!("Trying to delete keys from the db."))?; // Lskf is now removed in Keystore. Ok(UserState::Uninitialized) } @@ -1174,7 +1138,7 @@ impl SuperKeyManager { ) -> Result<UserState> { match self.get_per_boot_key_by_user_id_internal(user_id) { Some(super_key) => { - log::info!("In unlock_and_get_user_state. Trying to unlock when already unlocked."); + log::info!("Trying to unlock when already unlocked."); Ok(UserState::LskfUnlocked(super_key)) } None => { @@ -1183,7 +1147,7 @@ impl SuperKeyManager { // Otherwise, try to unlock the super key and if successful, // return LskfUnlocked. self.check_and_unlock_super_key(db, legacy_importer, user_id, password) - .context("In unlock_and_get_user_state. Failed to unlock super key.") + .context(ks_err!("Failed to unlock super key.")) } } } @@ -1201,9 +1165,9 @@ impl SuperKeyManager { // Mark keys created on behalf of the user as unreferenced. legacy_importer .bulk_delete_user(user_id, keep_non_super_encrypted_keys) - .context("In reset_user: Trying to delete legacy keys.")?; + .context(ks_err!("Trying to delete legacy keys."))?; db.unbind_keys_for_user(user_id, keep_non_super_encrypted_keys) - .context("In reset user. Error in unbinding keys.")?; + .context(ks_err!("Error in unbinding keys."))?; // Delete super key in cache, if exists. self.forget_all_keys_for_user(user_id); diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs index 9db2eb9d..acac7ee6 100644 --- a/keystore2/src/utils.rs +++ b/keystore2/src/utils.rs @@ -17,6 +17,7 @@ use crate::error::{map_binder_status, map_km_error, Error, ErrorCode}; use crate::key_parameter::KeyParameter; +use crate::ks_err; use crate::permission; use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm}; use crate::{ @@ -51,9 +52,9 @@ use std::iter::IntoIterator; pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> { ThreadState::with_calling_sid(|calling_sid| { permission::check_keystore_permission( - calling_sid.ok_or_else(Error::sys).context( - "In check_keystore_permission: Cannot check permission without calling_sid.", - )?, + calling_sid + .ok_or_else(Error::sys) + .context(ks_err!("Cannot check permission without calling_sid."))?, perm, ) }) @@ -65,9 +66,9 @@ pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> { pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> { ThreadState::with_calling_sid(|calling_sid| { permission::check_grant_permission( - calling_sid.ok_or_else(Error::sys).context( - "In check_grant_permission: Cannot check permission without calling_sid.", - )?, + calling_sid + .ok_or_else(Error::sys) + .context(ks_err!("Cannot check permission without calling_sid."))?, access_vec, key, ) @@ -87,7 +88,7 @@ pub fn check_key_permission( ThreadState::get_calling_uid(), calling_sid .ok_or_else(Error::sys) - .context("In check_key_permission: Cannot check permission without calling_sid.")?, + .context(ks_err!("Cannot check permission without calling_sid."))?, perm, key, access_vector, @@ -103,6 +104,7 @@ pub fn is_device_id_attestation_tag(tag: Tag) -> bool { | Tag::ATTESTATION_ID_MEID | Tag::ATTESTATION_ID_SERIAL | Tag::DEVICE_UNIQUE_ATTESTATION + | Tag::ATTESTATION_ID_SECOND_IMEI ) } @@ -135,14 +137,12 @@ fn check_android_permission(permission: &str) -> anyhow::Result<()> { ThreadState::get_calling_uid() as i32, ) }; - let has_permissions = map_binder_status(binder_result) - .context("In check_device_attestation_permissions: checkPermission failed")?; + let has_permissions = + map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?; match has_permissions { true => Ok(()), - false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)).context(concat!( - "In check_device_attestation_permissions: ", - "caller does not have the permission to attest device IDs" - )), + false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) + .context(ks_err!("caller does not have the permission to attest device IDs")), } } @@ -189,18 +189,15 @@ where ); map_km_error(km_dev.upgradeKey(key_blob, upgrade_params)) } - .context("In utils::upgrade_keyblob_if_required_with: Upgrade failed.")?; + .context(ks_err!("Upgrade failed."))?; - new_blob_handler(&upgraded_blob) - .context("In utils::upgrade_keyblob_if_required_with: calling new_blob_handler.")?; + new_blob_handler(&upgraded_blob).context(ks_err!("calling new_blob_handler."))?; km_op(&upgraded_blob) .map(|v| (v, Some(upgraded_blob))) - .context("In utils::upgrade_keyblob_if_required_with: Calling km_op after upgrade.") + .context(ks_err!("Calling km_op after upgrade.")) } - r => r - .map(|v| (v, None)) - .context("In utils::upgrade_keyblob_if_required_with: Calling km_op."), + r => r.map(|v| (v, None)).context(ks_err!("Calling km_op.")), } } @@ -212,6 +209,7 @@ pub fn key_parameters_to_authorizations( parameters.into_iter().map(|p| p.into_authorization()).collect() } +#[allow(clippy::unnecessary_cast)] /// This returns the current time (in milliseconds) as an instance of a monotonic clock, /// by invoking the system call since Rust does not support getting monotonic time instance /// as an integer. @@ -260,26 +258,116 @@ pub fn uid_to_android_user(uid: u32) -> u32 { rustutils::users::multiuser_get_user_id(uid) } -/// List all key aliases for a given domain + namespace. +/// Merges and filters two lists of key descriptors. The first input list, legacy_descriptors, +/// is assumed to not be sorted or filtered. As such, all key descriptors in that list whose +/// alias is less than, or equal to, start_past_alias (if provided) will be removed. +/// This list will then be merged with the second list, db_descriptors. The db_descriptors list +/// is assumed to be sorted and filtered so the output list will be sorted prior to returning. +/// The returned value is a list of KeyDescriptor objects whose alias is greater than +/// start_past_alias, sorted and de-duplicated. +fn merge_and_filter_key_entry_lists( + legacy_descriptors: &[KeyDescriptor], + db_descriptors: &[KeyDescriptor], + start_past_alias: Option<&str>, +) -> Vec<KeyDescriptor> { + let mut result: Vec<KeyDescriptor> = + match start_past_alias { + Some(past_alias) => legacy_descriptors + .iter() + .filter(|kd| { + if let Some(alias) = &kd.alias { + alias.as_str() > past_alias + } else { + false + } + }) + .cloned() + .collect(), + None => legacy_descriptors.to_vec(), + }; + + result.extend_from_slice(db_descriptors); + result.sort_unstable(); + result.dedup(); + result +} + +fn estimate_safe_amount_to_return( + key_descriptors: &[KeyDescriptor], + response_size_limit: usize, +) -> usize { + let mut items_to_return = 0; + let mut returned_bytes: usize = 0; + // Estimate the transaction size to avoid returning more items than what + // could fit in a binder transaction. + for kd in key_descriptors.iter() { + // 4 bytes for the Domain enum + // 8 bytes for the Namespace long. + returned_bytes += 4 + 8; + // Size of the alias string. Includes 4 bytes for length encoding. + if let Some(alias) = &kd.alias { + returned_bytes += 4 + alias.len(); + } + // Size of the blob. Includes 4 bytes for length encoding. + if let Some(blob) = &kd.blob { + returned_bytes += 4 + blob.len(); + } + // The binder transaction size limit is 1M. Empirical measurements show + // that the binder overhead is 60% (to be confirmed). So break after + // 350KB and return a partial list. + if returned_bytes > response_size_limit { + log::warn!( + "Key descriptors list ({} items) may exceed binder \ + size, returning {} items est {} bytes.", + key_descriptors.len(), + items_to_return, + returned_bytes + ); + break; + } + items_to_return += 1; + } + items_to_return +} + +/// List all key aliases for a given domain + namespace. whose alias is greater +/// than start_past_alias (if provided). pub fn list_key_entries( db: &mut KeystoreDB, domain: Domain, namespace: i64, + start_past_alias: Option<&str>, ) -> Result<Vec<KeyDescriptor>> { - let mut result = Vec::new(); - result.append( - &mut LEGACY_IMPORTER - .list_uid(domain, namespace) - .context("In list_key_entries: Trying to list legacy keys.")?, - ); - result.append( - &mut db - .list(domain, namespace, KeyType::Client) - .context("In list_key_entries: Trying to list keystore database.")?, + let legacy_key_descriptors: Vec<KeyDescriptor> = LEGACY_IMPORTER + .list_uid(domain, namespace) + .context(ks_err!("Trying to list legacy keys."))?; + + // The results from the database will be sorted and unique + let db_key_descriptors: Vec<KeyDescriptor> = db + .list_past_alias(domain, namespace, KeyType::Client, start_past_alias) + .context(ks_err!("Trying to list keystore database past alias."))?; + + let merged_key_entries = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + start_past_alias, ); - result.sort_unstable(); - result.dedup(); - Ok(result) + + const RESPONSE_SIZE_LIMIT: usize = 358400; + let safe_amount_to_return = + estimate_safe_amount_to_return(&merged_key_entries, RESPONSE_SIZE_LIMIT); + Ok(merged_key_entries[..safe_amount_to_return].to_vec()) +} + +/// Count all key aliases for a given domain + namespace. +pub fn count_key_entries(db: &mut KeystoreDB, domain: Domain, namespace: i64) -> Result<i32> { + let legacy_keys = LEGACY_IMPORTER + .list_uid(domain, namespace) + .context(ks_err!("Trying to list legacy keys."))?; + + let num_keys_in_db = db.count_keys(domain, namespace, KeyType::Client)?; + + Ok((legacy_keys.len() + num_keys_in_db) as i32) } /// This module provides helpers for simplified use of the watchdog module. @@ -333,12 +421,11 @@ pub trait AesGcmKey { impl<T: AesGcmKey> AesGcm for T { fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> { - aes_gcm_decrypt(data, iv, tag, self.key()) - .context("In AesGcm<T>::decrypt: Decryption failed") + aes_gcm_decrypt(data, iv, tag, self.key()).context(ks_err!("Decryption failed")) } fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> { - aes_gcm_encrypt(plaintext, self.key()).context("In AesGcm<T>::encrypt: Encryption failed.") + aes_gcm_encrypt(plaintext, self.key()).context(ks_err!("Encryption failed.")) } } @@ -377,4 +464,84 @@ mod tests { } }) } + + fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> { + key_aliases + .iter() + .map(|key_alias| KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(key_alias.to_string()), + blob: None, + }) + .collect::<Vec<KeyDescriptor>>() + } + + fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> { + key_descriptors + .iter() + .map( + |kd| { + if let Some(alias) = &kd.alias { + String::from(alias) + } else { + String::from("") + } + }, + ) + .collect::<Vec<String>>() + } + + #[test] + fn test_safe_amount_to_return() -> Result<()> { + let key_aliases = vec!["key1", "key2", "key3"]; + let key_descriptors = create_key_descriptors_from_aliases(&key_aliases); + + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 20), 1); + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 50), 2); + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 100), 3); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_without_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_c", "key_a", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_a", "key_d"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = + merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_with_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_c", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_b"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_d", "key_e", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_c"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]); + Ok(()) + } } diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs index 89e18eba..5bb015fa 100644 --- a/keystore2/src/vintf/lib.rs +++ b/keystore2/src/vintf/lib.rs @@ -19,14 +19,6 @@ mod ffi { unsafe extern "C++" { include!("vintf.hpp"); - /// Gets all HAL names. - /// Note that this is not a zero-cost shim: it will make copies of the strings. - fn get_hal_names() -> Vec<String>; - - /// Gets all HAL names and versions. - /// Note that this is not a zero-cost shim: it will make copies of the strings. - fn get_hal_names_and_versions() -> Vec<String>; - /// 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. fn get_hidl_instances( @@ -35,28 +27,7 @@ mod ffi { minor_version: usize, interface_name: &str, ) -> Vec<String>; - - /// 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. - fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> Vec<String>; } } pub use ffi::*; - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test() { - let names = get_hal_names(); - assert_ne!(names.len(), 0); - - let names_and_versions = get_hal_names_and_versions(); - assert_ne!(names_and_versions.len(), 0); - - assert!(names_and_versions.len() >= names.len()); - } -} diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp index 00625bff..bf77f5e6 100644 --- a/keystore2/src/vintf/vintf.cpp +++ b/keystore2/src/vintf/vintf.cpp @@ -26,18 +26,6 @@ rust::Vec<rust::String> convert(const std::set<std::string>& names) { return result; } -rust::Vec<rust::String> get_hal_names() { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); - const auto names = manifest->getHalNames(); - return convert(names); -} - -rust::Vec<rust::String> get_hal_names_and_versions() { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); - const auto names = manifest->getHalNamesAndVersions(); - return convert(names); -} - rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, size_t minor_version, rust::Str interfaceName) { android::vintf::Version version(major_version, minor_version); @@ -46,11 +34,3 @@ rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_versi static_cast<std::string>(interfaceName)); return convert(names); } - -rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version, - rust::Str interfaceName) { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); - const auto names = manifest->getAidlInstances(static_cast<std::string>(package), version, - static_cast<std::string>(interfaceName)); - return convert(names); -} diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp index dbc88f0f..ef1e7882 100644 --- a/keystore2/src/vintf/vintf.hpp +++ b/keystore2/src/vintf/vintf.hpp @@ -18,9 +18,5 @@ #include "rust/cxx.h" -rust::Vec<rust::String> get_hal_names(); -rust::Vec<rust::String> get_hal_names_and_versions(); rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, size_t minor_version, rust::Str interfaceName); -rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version, - rust::Str interfaceName); diff --git a/keystore2/src/watchdog.rs b/keystore2/src/watchdog.rs index a26b632a..01043c55 100644 --- a/keystore2/src/watchdog.rs +++ b/keystore2/src/watchdog.rs @@ -141,7 +141,7 @@ impl WatchdogState { }, ); // Put the groups back into a vector. - let mut groups: Vec<Vec<(&Index, &Record)>> = groups.into_iter().map(|(_, v)| v).collect(); + let mut groups: Vec<Vec<(&Index, &Record)>> = groups.into_values().collect(); // Sort the groups by start time of the most recent (.last()) of each group. // It is panic safe to use unwrap() here because we never add empty vectors to // the map. diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs index 4fbe1241..4608bc5f 100644 --- a/keystore2/test_utils/authorizations.rs +++ b/keystore2/test_utils/authorizations.rs @@ -17,11 +17,13 @@ use std::ops::Deref; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, Tag::Tag, + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, Tag::Tag, }; /// Helper struct to create set of Authorizations. +#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct AuthSetBuilder(Vec<KeyParameter>); impl Default for AuthSetBuilder { @@ -69,14 +71,96 @@ impl AuthSetBuilder { self } - /// Add Attestation-ID. - pub fn attestation_app_id(mut self, b: Vec<u8>) -> Self { + /// Add No_auth_required. + pub fn no_auth_required(mut self) -> Self { self.0.push(KeyParameter { - tag: Tag::ATTESTATION_APPLICATION_ID, - value: KeyParameterValue::Blob(b), + tag: Tag::NO_AUTH_REQUIRED, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Add RSA_public_exponent. + pub fn rsa_public_exponent(mut self, e: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::RSA_PUBLIC_EXPONENT, + value: KeyParameterValue::LongInteger(e), + }); + self + } + + /// Add key size. + pub fn key_size(mut self, s: i32) -> Self { + self.0.push(KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(s) }); + self + } + + /// Add block mode. + pub fn block_mode(mut self, b: BlockMode) -> Self { + self.0.push(KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(b) }); + self + } + + /// Add certificate_not_before. + pub fn cert_not_before(mut self, b: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::CERTIFICATE_NOT_BEFORE, + value: KeyParameterValue::DateTime(b), + }); + self + } + + /// Add certificate_not_after. + pub fn cert_not_after(mut self, a: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::CERTIFICATE_NOT_AFTER, + value: KeyParameterValue::DateTime(a), }); self } + + /// Add padding mode. + pub fn padding_mode(mut self, p: PaddingMode) -> Self { + self.0.push(KeyParameter { tag: Tag::PADDING, value: KeyParameterValue::PaddingMode(p) }); + self + } + + /// Add mgf_digest. + pub fn mgf_digest(mut self, d: Digest) -> Self { + self.0.push(KeyParameter { + tag: Tag::RSA_OAEP_MGF_DIGEST, + value: KeyParameterValue::Digest(d), + }); + self + } + + /// Add nonce. + pub fn nonce(mut self, b: Vec<u8>) -> Self { + self.0.push(KeyParameter { tag: Tag::NONCE, value: KeyParameterValue::Blob(b) }); + self + } + + /// Add CALLER_NONCE. + pub fn caller_nonce(mut self) -> Self { + self.0.push(KeyParameter { + tag: Tag::CALLER_NONCE, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Add MAC length. + pub fn mac_length(mut self, l: i32) -> Self { + self.0.push(KeyParameter { tag: Tag::MAC_LENGTH, value: KeyParameterValue::Integer(l) }); + self + } + + /// Add min MAC length. + pub fn min_mac_length(mut self, l: i32) -> Self { + self.0 + .push(KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KeyParameterValue::Integer(l) }); + self + } } impl Deref for AuthSetBuilder { diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs index f49aa9ff..e4c4968f 100644 --- a/keystore2/test_utils/key_generations.rs +++ b/keystore2/test_utils/key_generations.rs @@ -14,52 +14,1068 @@ //! This module implements test utils to generate various types of keys. +use anyhow::Result; + use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, + KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, - KeyMetadata::KeyMetadata, + AuthenticatorSpec::AuthenticatorSpec, Authorization::Authorization, Domain::Domain, + IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, }; use crate::authorizations::AuthSetBuilder; +use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult}; + +/// Shell namespace. +pub const SELINUX_SHELL_NAMESPACE: i64 = 1; +/// Vold namespace. +pub const SELINUX_VOLD_NAMESPACE: i64 = 100; + +/// SU context. +pub const TARGET_SU_CTX: &str = "u:r:su:s0"; + +/// Vold context +pub const TARGET_VOLD_CTX: &str = "u:r:vold:s0"; + +/// Key parameters to generate a key. +pub struct KeyParams { + /// Key Size. + pub key_size: i32, + /// Key Purposes. + pub purpose: Vec<KeyPurpose>, + /// Padding Mode. + pub padding: Option<PaddingMode>, + /// Digest. + pub digest: Option<Digest>, + /// MFG Digest. + pub mgf_digest: Option<Digest>, + /// Block Mode. + pub block_mode: Option<BlockMode>, + /// Attestation challenge. + pub att_challenge: Option<Vec<u8>>, +} + +/// DER-encoded PKCS#8 format RSA key. Generated using: +/// openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -outform der | hexdump -e '30/1 "%02X" "\n"' +pub static RSA_2048_KEY: &[u8] = &[ + 0x30, 0x82, 0x04, 0xBD, 0x02, 0x01, 0x00, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x04, 0xA7, 0x30, 0x82, 0x04, 0xA3, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xE5, 0x14, 0xE3, 0xC2, 0x43, 0xF3, 0x0F, 0xCC, 0x22, 0x73, + 0x9C, 0x84, 0xCC, 0x1B, 0x6C, 0x97, 0x4B, 0xC9, 0xDF, 0x1F, 0xE2, 0xB8, 0x80, 0x85, 0xF9, 0x27, + 0xAB, 0x97, 0x94, 0x58, 0x4B, 0xC9, 0x40, 0x94, 0x5A, 0xB4, 0xD4, 0xF8, 0xD0, 0x36, 0xC4, 0x86, + 0x17, 0x7D, 0xA2, 0x48, 0x6D, 0x40, 0xF0, 0xB9, 0x61, 0x4F, 0xCE, 0x65, 0x80, 0x88, 0x81, 0x59, + 0x95, 0x11, 0x24, 0xF4, 0x36, 0xB7, 0xB7, 0x37, 0x44, 0xF4, 0x6C, 0x1C, 0xEB, 0x04, 0x19, 0x78, + 0xB2, 0x29, 0x4D, 0x21, 0x44, 0x16, 0x57, 0x58, 0x6D, 0x7D, 0x56, 0xB5, 0x99, 0xDD, 0xD2, 0xAD, + 0x02, 0x9A, 0x72, 0x16, 0x67, 0xD6, 0x00, 0x9F, 0x69, 0xE0, 0x25, 0xEE, 0x7C, 0x86, 0x54, 0x27, + 0x4B, 0x50, 0xEF, 0x60, 0x52, 0x60, 0x82, 0xAA, 0x09, 0x15, 0x72, 0xD2, 0xEB, 0x01, 0x52, 0x04, + 0x39, 0x60, 0xBC, 0x5E, 0x95, 0x07, 0xC8, 0xC2, 0x3A, 0x3A, 0xE2, 0xA4, 0x99, 0x6B, 0x27, 0xE3, + 0xA3, 0x55, 0x69, 0xC4, 0xB3, 0x2D, 0x19, 0xC4, 0x34, 0x76, 0xFC, 0x27, 0xDA, 0x22, 0xB2, 0x62, + 0x69, 0x25, 0xDE, 0x0D, 0xE7, 0x54, 0x3C, 0xBB, 0x61, 0xD2, 0x20, 0xDA, 0x7B, 0x6E, 0x63, 0xBD, + 0x9A, 0x4B, 0xCD, 0x75, 0xC6, 0xA1, 0x5E, 0x1C, 0x3E, 0xD5, 0x63, 0x59, 0x22, 0x7E, 0xE0, 0x6C, + 0x98, 0x25, 0x63, 0x97, 0x56, 0xDF, 0x71, 0xF5, 0x4C, 0x78, 0xE9, 0xE1, 0xD5, 0xFC, 0xF8, 0x5A, + 0x5B, 0xF6, 0x1D, 0xFA, 0x5A, 0x99, 0x4C, 0x99, 0x19, 0x21, 0x1D, 0xF5, 0x24, 0x07, 0xEF, 0x8A, + 0xC9, 0x9F, 0xE7, 0x3F, 0xBB, 0x46, 0x1A, 0x16, 0x96, 0xC6, 0xD6, 0x12, 0x7E, 0xDA, 0xCB, 0xEB, + 0x2F, 0x1D, 0x3B, 0x31, 0xCC, 0x55, 0x63, 0xA2, 0x6F, 0x8A, 0xDE, 0x35, 0x52, 0x40, 0x04, 0xBF, + 0xE0, 0x82, 0x32, 0xE1, 0x6D, 0x8B, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x2D, + 0x1F, 0x71, 0x41, 0x79, 0xBA, 0xED, 0xD8, 0xAA, 0xCC, 0x94, 0xFE, 0xFF, 0x69, 0x43, 0x79, 0x85, + 0xBF, 0x2C, 0xC9, 0x0E, 0x12, 0x83, 0x96, 0x60, 0x1E, 0x75, 0x49, 0x35, 0x3A, 0x33, 0x2B, 0x60, + 0x22, 0x18, 0xBF, 0xD7, 0xD7, 0x6E, 0xC3, 0xEA, 0xEF, 0xF2, 0xBE, 0x97, 0x71, 0xA6, 0xBB, 0x8C, + 0xEF, 0x27, 0x00, 0xDE, 0x49, 0xD6, 0x08, 0x8D, 0x5A, 0x04, 0xE7, 0xCC, 0x9C, 0xA2, 0x0E, 0x8B, + 0xF3, 0x42, 0x0C, 0xD7, 0x22, 0xD7, 0x14, 0x06, 0xA4, 0x64, 0x8B, 0x88, 0x1A, 0xCE, 0x5B, 0x8C, + 0x36, 0xE9, 0xD2, 0x2F, 0x7B, 0x33, 0xE4, 0xA2, 0xB3, 0xDB, 0x78, 0x6A, 0x92, 0x89, 0x3F, 0x78, + 0xFD, 0xED, 0x8F, 0xEE, 0x48, 0xCC, 0x94, 0x75, 0x0D, 0x0C, 0x63, 0xD3, 0xD2, 0xE8, 0x47, 0x04, + 0x55, 0xD3, 0xD6, 0x3A, 0xB8, 0xDA, 0xFB, 0x76, 0x99, 0x48, 0x68, 0x0A, 0x92, 0xA2, 0xCD, 0xF7, + 0x45, 0x8B, 0x50, 0xFE, 0xF9, 0x1A, 0x33, 0x24, 0x3C, 0x2E, 0xDE, 0x88, 0xAD, 0xB2, 0x5B, 0x9F, + 0x44, 0xEA, 0xD1, 0x9F, 0xC7, 0x9F, 0x02, 0x5E, 0x31, 0x61, 0xB3, 0xD6, 0xE2, 0xE1, 0xBC, 0xFB, + 0x1C, 0xDB, 0xBD, 0xB2, 0x9A, 0xE5, 0xEF, 0xDA, 0xCD, 0x29, 0xA5, 0x45, 0xCC, 0x67, 0x01, 0x8B, + 0x1C, 0x1D, 0x0E, 0x8F, 0x73, 0x69, 0x4D, 0x4D, 0xF6, 0x9D, 0xA6, 0x6C, 0x9A, 0x1C, 0xF4, 0x5C, + 0xE4, 0x83, 0x9A, 0x77, 0x12, 0x01, 0xBD, 0xCE, 0x66, 0x3A, 0x4B, 0x3D, 0x6E, 0xE0, 0x6E, 0x82, + 0x98, 0xDE, 0x74, 0x11, 0x47, 0xEC, 0x7A, 0x3A, 0xA9, 0xD8, 0x48, 0x00, 0x26, 0x64, 0x47, 0x7B, + 0xAE, 0x55, 0x9D, 0x29, 0x22, 0xB4, 0xB3, 0xB9, 0xB1, 0x64, 0xEA, 0x3B, 0x5A, 0xD3, 0x3F, 0x8D, + 0x0F, 0x14, 0x7E, 0x4E, 0xB8, 0x1B, 0x06, 0xFC, 0xB1, 0x7E, 0xCD, 0xB9, 0x1A, 0x4E, 0xA1, 0x02, + 0x81, 0x81, 0x00, 0xF9, 0xDE, 0xEE, 0xED, 0x13, 0x2F, 0xBB, 0xE7, 0xE2, 0xB3, 0x2D, 0x98, 0xD2, + 0xE8, 0x25, 0x07, 0x5A, 0x1E, 0x51, 0x0A, 0xC8, 0xAD, 0x50, 0x4B, 0x80, 0xC6, 0x22, 0xF5, 0x9B, + 0x08, 0xE6, 0x3D, 0x01, 0xC6, 0x3E, 0xC8, 0xD2, 0x54, 0x9F, 0x91, 0x77, 0x95, 0xCD, 0xCA, 0xC7, + 0xE7, 0x47, 0x94, 0xA9, 0x5F, 0x4E, 0xBE, 0x31, 0x3D, 0xB4, 0xAF, 0x43, 0x0F, 0xDC, 0x8D, 0x9C, + 0x1E, 0x52, 0x7B, 0x72, 0x21, 0x34, 0xB3, 0x96, 0x7C, 0x9C, 0xB8, 0x51, 0x65, 0x60, 0xAC, 0x3D, + 0x11, 0x32, 0xB8, 0xD6, 0x34, 0x35, 0x66, 0xD0, 0x30, 0xB9, 0xE9, 0x67, 0x2C, 0x87, 0x73, 0x43, + 0x9C, 0x12, 0x16, 0x7D, 0x4A, 0xD9, 0xA3, 0x4C, 0x24, 0x64, 0x6A, 0x32, 0x8E, 0xC3, 0xD8, 0x00, + 0x90, 0x5C, 0x4D, 0x65, 0x01, 0x53, 0x8A, 0xD0, 0x87, 0xCE, 0x96, 0xEF, 0xFA, 0x73, 0x03, 0xF1, + 0xDC, 0x1B, 0x9B, 0x02, 0x81, 0x81, 0x00, 0xEA, 0xB3, 0x69, 0x00, 0x11, 0x0E, 0x50, 0xAA, 0xD3, + 0x22, 0x51, 0x78, 0x9D, 0xFF, 0x05, 0x62, 0xBC, 0x9A, 0x67, 0x86, 0xE1, 0xC5, 0x02, 0x2D, 0x14, + 0x11, 0x29, 0x30, 0xE7, 0x90, 0x5D, 0x72, 0x6F, 0xC5, 0x62, 0xEB, 0xD4, 0xB0, 0x3F, 0x3D, 0xDC, + 0xB9, 0xFC, 0x2B, 0x5C, 0xBD, 0x9E, 0x71, 0x81, 0x5C, 0xC5, 0xFE, 0xDF, 0x69, 0x73, 0x12, 0x66, + 0x92, 0x06, 0xD4, 0xD5, 0x8F, 0xDF, 0x14, 0x2E, 0x9C, 0xD0, 0x4C, 0xC2, 0x4D, 0x31, 0x2E, 0x47, + 0xA5, 0xDC, 0x8A, 0x83, 0x7B, 0xE8, 0xA5, 0xC3, 0x03, 0x98, 0xD8, 0xBF, 0xF4, 0x7D, 0x6E, 0x87, + 0x55, 0xE4, 0x0F, 0x15, 0x10, 0xC8, 0x76, 0x4F, 0xAD, 0x1D, 0x1C, 0x95, 0x41, 0x9D, 0x88, 0xEC, + 0x8C, 0xDA, 0xBA, 0x90, 0x7F, 0x8D, 0xD9, 0x8B, 0x47, 0x6C, 0x0C, 0xFF, 0xBA, 0x73, 0x00, 0x20, + 0x1F, 0xF7, 0x7E, 0x5F, 0xF4, 0xEC, 0xD1, 0x02, 0x81, 0x80, 0x16, 0xB7, 0x43, 0xB5, 0x5D, 0xD7, + 0x2B, 0x18, 0x0B, 0xAE, 0x0A, 0x69, 0x28, 0x53, 0x5E, 0x7A, 0x6A, 0xA0, 0xF2, 0xF1, 0x2E, 0x09, + 0x43, 0x91, 0x79, 0xA5, 0x89, 0xAC, 0x16, 0x6A, 0x1A, 0xB4, 0x55, 0x22, 0xF6, 0xB6, 0x3F, 0x18, + 0xDE, 0x60, 0xD5, 0x24, 0x53, 0x4F, 0x2A, 0x19, 0x46, 0x92, 0xA7, 0x4B, 0x38, 0xD7, 0x65, 0x96, + 0x9C, 0x84, 0x8A, 0x6E, 0x38, 0xB8, 0xCF, 0x06, 0x9A, 0xAD, 0x0A, 0x55, 0x26, 0x7B, 0x65, 0x24, + 0xF3, 0x02, 0x76, 0xB3, 0xE6, 0xB4, 0x01, 0xE1, 0x3C, 0x61, 0x3D, 0x68, 0x05, 0xAA, 0xD1, 0x26, + 0x7C, 0xE0, 0x51, 0x36, 0xE5, 0x21, 0x7F, 0x76, 0x02, 0xD6, 0xF4, 0x91, 0x07, 0x74, 0x27, 0x09, + 0xEF, 0xEF, 0x0F, 0xA5, 0x96, 0xFC, 0x5E, 0x20, 0xC1, 0xA3, 0x6F, 0x99, 0x4D, 0x45, 0x03, 0x6C, + 0x35, 0x45, 0xD7, 0x8F, 0x47, 0x41, 0x86, 0x8D, 0x62, 0x1D, 0x02, 0x81, 0x81, 0x00, 0xC3, 0x93, + 0x85, 0xA7, 0xFC, 0x8E, 0x85, 0x42, 0x14, 0x76, 0xC0, 0x95, 0x56, 0x73, 0xB0, 0xB5, 0x3A, 0x9D, + 0x20, 0x30, 0x11, 0xEA, 0xED, 0x89, 0x4A, 0xF3, 0x91, 0xF3, 0xA2, 0xC3, 0x76, 0x5B, 0x6A, 0x30, + 0x7D, 0xE2, 0x2F, 0x76, 0x3E, 0xFC, 0xF9, 0xF6, 0x31, 0xE0, 0xA0, 0x83, 0x92, 0x88, 0xDB, 0x57, + 0xC7, 0xD6, 0x3F, 0xAD, 0xCB, 0xAA, 0x45, 0xB6, 0xE1, 0xE2, 0x71, 0xA4, 0x56, 0x2C, 0xA7, 0x3B, + 0x1D, 0x89, 0x19, 0x50, 0xE1, 0xEE, 0xC2, 0xDD, 0xC0, 0x0D, 0xDC, 0xCB, 0x60, 0x6E, 0xE1, 0x37, + 0x1A, 0x23, 0x64, 0xB2, 0x03, 0xE4, 0x1A, 0xFA, 0xC3, 0xF4, 0x9D, 0x85, 0x42, 0xC6, 0xF4, 0x56, + 0x39, 0xB0, 0x1B, 0xE0, 0x75, 0xBA, 0x28, 0x04, 0xA8, 0x30, 0x57, 0x41, 0x33, 0x9F, 0x58, 0xA4, + 0xC7, 0xB1, 0x7D, 0x58, 0x8D, 0x84, 0x49, 0x40, 0xDA, 0x28, 0x81, 0x25, 0xC4, 0x41, 0x02, 0x81, + 0x80, 0x13, 0x20, 0x65, 0xD5, 0x96, 0x98, 0x8D, 0x16, 0x73, 0xA1, 0x31, 0x73, 0x79, 0xBA, 0xEC, + 0xB0, 0xD9, 0x0C, 0xF6, 0xEF, 0x2F, 0xC2, 0xE7, 0x96, 0x9B, 0xA1, 0x2D, 0xE9, 0xFB, 0x45, 0xB9, + 0xD0, 0x30, 0xE2, 0xBD, 0x30, 0x4F, 0xB6, 0xFE, 0x24, 0x02, 0xCF, 0x8D, 0x51, 0x48, 0x45, 0xD9, + 0xF7, 0x20, 0x53, 0x1C, 0x0B, 0xA9, 0x7E, 0xC2, 0xA2, 0x65, 0xCC, 0x3E, 0x0E, 0x0D, 0xF1, 0x62, + 0xDD, 0x5F, 0xBC, 0x55, 0x9B, 0x58, 0x26, 0x40, 0x6A, 0xEE, 0x02, 0x55, 0x36, 0xE9, 0xBA, 0x82, + 0x5A, 0xFD, 0x3C, 0xDF, 0xA6, 0x26, 0x32, 0x81, 0xA9, 0x5E, 0x46, 0xBE, 0xBA, 0xDC, 0xD3, 0x2A, + 0x3A, 0x3B, 0xC1, 0x4E, 0xF7, 0x1A, 0xDC, 0x4B, 0xAF, 0x67, 0x1B, 0x3A, 0x83, 0x0D, 0x04, 0xDE, + 0x27, 0x47, 0xFC, 0xE6, 0x39, 0x89, 0x7B, 0x66, 0xF9, 0x50, 0x4D, 0xF1, 0xAC, 0x20, 0x43, 0x7E, + 0xEE, +]; + +/// DER-encoded PKCS#8 format EC key. Generated using: +/// openssl ecparam -name prime256v1 -genkey | openssl pkcs8 -topk8 -nocrypt -outform der | hexdump -e '30/1 "%02X" "\n"' +pub static EC_P_256_KEY: &[u8] = &[ + 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, + 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x04, 0x6D, 0x30, 0x6B, 0x02, + 0x01, 0x01, 0x04, 0x20, 0xB9, 0x1D, 0xAF, 0x50, 0xFD, 0xD8, 0x6A, 0x40, 0xAB, 0x2C, 0xCB, 0x54, + 0x4E, 0xED, 0xF1, 0x64, 0xBC, 0x30, 0x25, 0xFB, 0xC4, 0x69, 0x00, 0x34, 0x1A, 0x82, 0xA3, 0x72, + 0x5D, 0xC7, 0xA9, 0x85, 0xA1, 0x44, 0x03, 0x42, 0x00, 0x04, 0xE8, 0x53, 0x0A, 0xF2, 0xD3, 0x68, + 0x40, 0x48, 0x8C, 0xB4, 0x2F, 0x11, 0x34, 0xD7, 0xF4, 0x4A, 0x5C, 0x33, 0xFF, 0xF6, 0x2B, 0xF7, + 0x98, 0x0F, 0x02, 0xA5, 0xD7, 0x4F, 0xF9, 0xDE, 0x60, 0x9C, 0x6E, 0xB0, 0x45, 0xDA, 0x3F, 0xF4, + 0x34, 0x23, 0x9B, 0x4C, 0x3A, 0x09, 0x9C, 0x5E, 0x5D, 0x37, 0x96, 0xAC, 0x4A, 0xE7, 0x65, 0x2B, + 0xD6, 0x84, 0x98, 0xEA, 0x96, 0x91, 0xFB, 0x78, 0xED, 0x86, +]; -const SELINUX_SHELL_NAMESPACE: i64 = 1; +/// DER-encoded PKCS#8 format RSA key - +/// Size: 2048 +/// Public Exponent: 65537 +/// Purpose: WRAP_KEY, ENCRYPT, DECRYPT +/// Encryption scheme: RSAES-PKCS1-v1_5 +/// Digest: SHA_2_256 +/// Padding: RSA_OAEP +/// This sample wrapping_key is taken from KeyMint tests +/// (see hardware/interfaces/security/keymint/aidl/vts/functional/KeyMintTest.cpp). +/// Similarly more test keys can be generated with below command - +/// openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -outform der | hexdump -e '30/1 "%02X" "\n"' +pub static WRAPPING_KEY: &[u8] = &[ + 0x30, 0x82, 0x04, 0xbe, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x04, 0xa8, 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xae, 0xc3, 0x67, 0x93, 0x1d, 0x89, 0x00, 0xce, 0x56, 0xb0, + 0x06, 0x7f, 0x7d, 0x70, 0xe1, 0xfc, 0x65, 0x3f, 0x3f, 0x34, 0xd1, 0x94, 0xc1, 0xfe, 0xd5, 0x00, + 0x18, 0xfb, 0x43, 0xdb, 0x93, 0x7b, 0x06, 0xe6, 0x73, 0xa8, 0x37, 0x31, 0x3d, 0x56, 0xb1, 0xc7, + 0x25, 0x15, 0x0a, 0x3f, 0xef, 0x86, 0xac, 0xbd, 0xdc, 0x41, 0xbb, 0x75, 0x9c, 0x28, 0x54, 0xea, + 0xe3, 0x2d, 0x35, 0x84, 0x1e, 0xfb, 0x5c, 0x18, 0xd8, 0x2b, 0xc9, 0x0a, 0x1c, 0xb5, 0xc1, 0xd5, + 0x5a, 0xdf, 0x24, 0x5b, 0x02, 0x91, 0x1f, 0x0b, 0x7c, 0xda, 0x88, 0xc4, 0x21, 0xff, 0x0e, 0xba, + 0xfe, 0x7c, 0x0d, 0x23, 0xbe, 0x31, 0x2d, 0x7b, 0xd5, 0x92, 0x1f, 0xfa, 0xea, 0x13, 0x47, 0xc1, + 0x57, 0x40, 0x6f, 0xef, 0x71, 0x8f, 0x68, 0x26, 0x43, 0xe4, 0xe5, 0xd3, 0x3c, 0x67, 0x03, 0xd6, + 0x1c, 0x0c, 0xf7, 0xac, 0x0b, 0xf4, 0x64, 0x5c, 0x11, 0xf5, 0xc1, 0x37, 0x4c, 0x38, 0x86, 0x42, + 0x74, 0x11, 0xc4, 0x49, 0x79, 0x67, 0x92, 0xe0, 0xbe, 0xf7, 0x5d, 0xec, 0x85, 0x8a, 0x21, 0x23, + 0xc3, 0x67, 0x53, 0xe0, 0x2a, 0x95, 0xa9, 0x6d, 0x7c, 0x45, 0x4b, 0x50, 0x4d, 0xe3, 0x85, 0xa6, + 0x42, 0xe0, 0xdf, 0xc3, 0xe6, 0x0a, 0xc3, 0xa7, 0xee, 0x49, 0x91, 0xd0, 0xd4, 0x8b, 0x01, 0x72, + 0xa9, 0x5f, 0x95, 0x36, 0xf0, 0x2b, 0xa1, 0x3c, 0xec, 0xcc, 0xb9, 0x2b, 0x72, 0x7d, 0xb5, 0xc2, + 0x7e, 0x5b, 0x2f, 0x5c, 0xec, 0x09, 0x60, 0x0b, 0x28, 0x6a, 0xf5, 0xcf, 0x14, 0xc4, 0x20, 0x24, + 0xc6, 0x1d, 0xdf, 0xe7, 0x1c, 0x2a, 0x8d, 0x74, 0x58, 0xf1, 0x85, 0x23, 0x4c, 0xb0, 0x0e, 0x01, + 0xd2, 0x82, 0xf1, 0x0f, 0x8f, 0xc6, 0x72, 0x1d, 0x2a, 0xed, 0x3f, 0x48, 0x33, 0xcc, 0xa2, 0xbd, + 0x8f, 0xa6, 0x28, 0x21, 0xdd, 0x55, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x43, + 0x14, 0x47, 0xb6, 0x25, 0x19, 0x08, 0x11, 0x2b, 0x1e, 0xe7, 0x6f, 0x99, 0xf3, 0x71, 0x1a, 0x52, + 0xb6, 0x63, 0x09, 0x60, 0x04, 0x6c, 0x2d, 0xe7, 0x0d, 0xe1, 0x88, 0xd8, 0x33, 0xf8, 0xb8, 0xb9, + 0x1e, 0x4d, 0x78, 0x5c, 0xae, 0xee, 0xaf, 0x4f, 0x0f, 0x74, 0x41, 0x4e, 0x2c, 0xda, 0x40, 0x64, + 0x1f, 0x7f, 0xe2, 0x4f, 0x14, 0xc6, 0x7a, 0x88, 0x95, 0x9b, 0xdb, 0x27, 0x76, 0x6d, 0xf9, 0xe7, + 0x10, 0xb6, 0x30, 0xa0, 0x3a, 0xdc, 0x68, 0x3b, 0x5d, 0x2c, 0x43, 0x08, 0x0e, 0x52, 0xbe, 0xe7, + 0x1e, 0x9e, 0xae, 0xb6, 0xde, 0x29, 0x7a, 0x5f, 0xea, 0x10, 0x72, 0x07, 0x0d, 0x18, 0x1c, 0x82, + 0x2b, 0xcc, 0xff, 0x08, 0x7d, 0x63, 0xc9, 0x40, 0xba, 0x8a, 0x45, 0xf6, 0x70, 0xfe, 0xb2, 0x9f, + 0xb4, 0x48, 0x4d, 0x1c, 0x95, 0xe6, 0xd2, 0x57, 0x9b, 0xa0, 0x2a, 0xae, 0x0a, 0x00, 0x90, 0x0c, + 0x3e, 0xbf, 0x49, 0x0e, 0x3d, 0x2c, 0xd7, 0xee, 0x8d, 0x0e, 0x20, 0xc5, 0x36, 0xe4, 0xdc, 0x5a, + 0x50, 0x97, 0x27, 0x28, 0x88, 0xcd, 0xdd, 0x7e, 0x91, 0xf2, 0x28, 0xb1, 0xc4, 0xd7, 0x47, 0x4c, + 0x55, 0xb8, 0xfc, 0xd6, 0x18, 0xc4, 0xa9, 0x57, 0xbb, 0xdd, 0xd5, 0xad, 0x74, 0x07, 0xcc, 0x31, + 0x2d, 0x8d, 0x98, 0xa5, 0xca, 0xf7, 0xe0, 0x8f, 0x4a, 0x0d, 0x6b, 0x45, 0xbb, 0x41, 0xc6, 0x52, + 0x65, 0x9d, 0x5a, 0x5b, 0xa0, 0x5b, 0x66, 0x37, 0x37, 0xa8, 0x69, 0x62, 0x81, 0x86, 0x5b, 0xa2, + 0x0f, 0xbd, 0xd7, 0xf8, 0x51, 0xe6, 0xc5, 0x6e, 0x8c, 0xbe, 0x0d, 0xdb, 0xbf, 0x24, 0xdc, 0x03, + 0xb2, 0xd2, 0xcb, 0x4c, 0x3d, 0x54, 0x0f, 0xb0, 0xaf, 0x52, 0xe0, 0x34, 0xa2, 0xd0, 0x66, 0x98, + 0xb1, 0x28, 0xe5, 0xf1, 0x01, 0xe3, 0xb5, 0x1a, 0x34, 0xf8, 0xd8, 0xb4, 0xf8, 0x61, 0x81, 0x02, + 0x81, 0x81, 0x00, 0xde, 0x39, 0x2e, 0x18, 0xd6, 0x82, 0xc8, 0x29, 0x26, 0x6c, 0xc3, 0x45, 0x4e, + 0x1d, 0x61, 0x66, 0x24, 0x2f, 0x32, 0xd9, 0xa1, 0xd1, 0x05, 0x77, 0x75, 0x3e, 0x90, 0x4e, 0xa7, + 0xd0, 0x8b, 0xff, 0x84, 0x1b, 0xe5, 0xba, 0xc8, 0x2a, 0x16, 0x4c, 0x59, 0x70, 0x00, 0x70, 0x47, + 0xb8, 0xc5, 0x17, 0xdb, 0x8f, 0x8f, 0x84, 0xe3, 0x7b, 0xd5, 0x98, 0x85, 0x61, 0xbd, 0xf5, 0x03, + 0xd4, 0xdc, 0x2b, 0xdb, 0x38, 0xf8, 0x85, 0x43, 0x4a, 0xe4, 0x2c, 0x35, 0x5f, 0x72, 0x5c, 0x9a, + 0x60, 0xf9, 0x1f, 0x07, 0x88, 0xe1, 0xf1, 0xa9, 0x72, 0x23, 0xb5, 0x24, 0xb5, 0x35, 0x7f, 0xdf, + 0x72, 0xe2, 0xf6, 0x96, 0xba, 0xb7, 0xd7, 0x8e, 0x32, 0xbf, 0x92, 0xba, 0x8e, 0x18, 0x64, 0xea, + 0xb1, 0x22, 0x9e, 0x91, 0x34, 0x61, 0x30, 0x74, 0x8a, 0x6e, 0x3c, 0x12, 0x4f, 0x91, 0x49, 0xd7, + 0x1c, 0x74, 0x35, 0x02, 0x81, 0x81, 0x00, 0xc9, 0x53, 0x87, 0xc0, 0xf9, 0xd3, 0x5f, 0x13, 0x7b, + 0x57, 0xd0, 0xd6, 0x5c, 0x39, 0x7c, 0x5e, 0x21, 0xcc, 0x25, 0x1e, 0x47, 0x00, 0x8e, 0xd6, 0x2a, + 0x54, 0x24, 0x09, 0xc8, 0xb6, 0xb6, 0xac, 0x7f, 0x89, 0x67, 0xb3, 0x86, 0x3c, 0xa6, 0x45, 0xfc, + 0xce, 0x49, 0x58, 0x2a, 0x9a, 0xa1, 0x73, 0x49, 0xdb, 0x6c, 0x4a, 0x95, 0xaf, 0xfd, 0xae, 0x0d, + 0xae, 0x61, 0x2e, 0x1a, 0xfa, 0xc9, 0x9e, 0xd3, 0x9a, 0x2d, 0x93, 0x4c, 0x88, 0x04, 0x40, 0xae, + 0xd8, 0x83, 0x2f, 0x98, 0x43, 0x16, 0x3a, 0x47, 0xf2, 0x7f, 0x39, 0x21, 0x99, 0xdc, 0x12, 0x02, + 0xf9, 0xa0, 0xf9, 0xbd, 0x08, 0x30, 0x80, 0x07, 0xcb, 0x1e, 0x4e, 0x7f, 0x58, 0x30, 0x93, 0x66, + 0xa7, 0xde, 0x25, 0xf7, 0xc3, 0xc9, 0xb8, 0x80, 0x67, 0x7c, 0x06, 0x8e, 0x1b, 0xe9, 0x36, 0xe8, + 0x12, 0x88, 0x81, 0x52, 0x52, 0xa8, 0xa1, 0x02, 0x81, 0x80, 0x57, 0xff, 0x8c, 0xa1, 0x89, 0x50, + 0x80, 0xb2, 0xca, 0xe4, 0x86, 0xef, 0x0a, 0xdf, 0xd7, 0x91, 0xfb, 0x02, 0x35, 0xc0, 0xb8, 0xb3, + 0x6c, 0xd6, 0xc1, 0x36, 0xe5, 0x2e, 0x40, 0x85, 0xf4, 0xea, 0x5a, 0x06, 0x32, 0x12, 0xa4, 0xf1, + 0x05, 0xa3, 0x76, 0x47, 0x43, 0xe5, 0x32, 0x81, 0x98, 0x8a, 0xba, 0x07, 0x3f, 0x6e, 0x00, 0x27, + 0x29, 0x8e, 0x1c, 0x43, 0x78, 0x55, 0x6e, 0x0e, 0xfc, 0xa0, 0xe1, 0x4e, 0xce, 0x1a, 0xf7, 0x6a, + 0xd0, 0xb0, 0x30, 0xf2, 0x7a, 0xf6, 0xf0, 0xab, 0x35, 0xfb, 0x73, 0xa0, 0x60, 0xd8, 0xb1, 0xa0, + 0xe1, 0x42, 0xfa, 0x26, 0x47, 0xe9, 0x3b, 0x32, 0xe3, 0x6d, 0x82, 0x82, 0xae, 0x0a, 0x4d, 0xe5, + 0x0a, 0xb7, 0xaf, 0xe8, 0x55, 0x00, 0xa1, 0x6f, 0x43, 0xa6, 0x47, 0x19, 0xd6, 0xe2, 0xb9, 0x43, + 0x98, 0x23, 0x71, 0x9c, 0xd0, 0x8b, 0xcd, 0x03, 0x17, 0x81, 0x02, 0x81, 0x81, 0x00, 0xba, 0x73, + 0xb0, 0xbb, 0x28, 0xe3, 0xf8, 0x1e, 0x9b, 0xd1, 0xc5, 0x68, 0x71, 0x3b, 0x10, 0x12, 0x41, 0xac, + 0xc6, 0x07, 0x97, 0x6c, 0x4d, 0xdc, 0xcc, 0x90, 0xe6, 0x5b, 0x65, 0x56, 0xca, 0x31, 0x51, 0x60, + 0x58, 0xf9, 0x2b, 0x6e, 0x09, 0xf3, 0xb1, 0x60, 0xff, 0x0e, 0x37, 0x4e, 0xc4, 0x0d, 0x78, 0xae, + 0x4d, 0x49, 0x79, 0xfd, 0xe6, 0xac, 0x06, 0xa1, 0xa4, 0x00, 0xc6, 0x1d, 0xd3, 0x12, 0x54, 0x18, + 0x6a, 0xf3, 0x0b, 0x22, 0xc1, 0x05, 0x82, 0xa8, 0xa4, 0x3e, 0x34, 0xfe, 0x94, 0x9c, 0x5f, 0x3b, + 0x97, 0x55, 0xba, 0xe7, 0xba, 0xa7, 0xb7, 0xb7, 0xa6, 0xbd, 0x03, 0xb3, 0x8c, 0xef, 0x55, 0xc8, + 0x68, 0x85, 0xfc, 0x6c, 0x19, 0x78, 0xb9, 0xce, 0xe7, 0xef, 0x33, 0xda, 0x50, 0x7c, 0x9d, 0xf6, + 0xb9, 0x27, 0x7c, 0xff, 0x1e, 0x6a, 0xaa, 0x5d, 0x57, 0xac, 0xa5, 0x28, 0x46, 0x61, 0x02, 0x81, + 0x81, 0x00, 0xc9, 0x31, 0x61, 0x7c, 0x77, 0x82, 0x9d, 0xfb, 0x12, 0x70, 0x50, 0x2b, 0xe9, 0x19, + 0x5c, 0x8f, 0x28, 0x30, 0x88, 0x5f, 0x57, 0xdb, 0xa8, 0x69, 0x53, 0x68, 0x11, 0xe6, 0x86, 0x42, + 0x36, 0xd0, 0xc4, 0x73, 0x6a, 0x00, 0x08, 0xa1, 0x45, 0xaf, 0x36, 0xb8, 0x35, 0x7a, 0x7c, 0x3d, + 0x13, 0x99, 0x66, 0xd0, 0x4c, 0x4e, 0x00, 0x93, 0x4e, 0xa1, 0xae, 0xde, 0x3b, 0xb6, 0xb8, 0xec, + 0x84, 0x1d, 0xc9, 0x5e, 0x3f, 0x57, 0x97, 0x51, 0xe2, 0xbf, 0xdf, 0xe2, 0x7a, 0xe7, 0x78, 0x98, + 0x3f, 0x95, 0x93, 0x56, 0x21, 0x07, 0x23, 0x28, 0x7b, 0x0a, 0xff, 0xcc, 0x9f, 0x72, 0x70, 0x44, + 0xd4, 0x8c, 0x37, 0x3f, 0x1b, 0xab, 0xde, 0x07, 0x24, 0xfa, 0x17, 0xa4, 0xfd, 0x4d, 0xa0, 0x90, + 0x2c, 0x7c, 0x9b, 0x9b, 0xf2, 0x7b, 0xa6, 0x1b, 0xe6, 0xad, 0x02, 0xdf, 0xdd, 0xda, 0x8f, 0x4e, + 0x68, 0x22, +]; -/// Generate attested EC Key blob using given security level with below key parameters - +/// WrappedKeyData as ASN.1 DER-encoded data corresponding to the `SecureKeyWrapper` schema +/// specified in IKeyMintDevice.aidl. Wrapped key parameters are - +/// Algorithm: AES +/// Key size: 256 +/// Block mode: ECB +/// Padding mode: PKCS7 +/// This sample wrapped_key is taken from KeyMint tests (see KeyMintTest.cpp). +pub static WRAPPED_KEY: &[u8] = &[ + 0x30, 0x82, 0x01, 0x79, 0x02, 0x01, 0x00, 0x04, 0x82, 0x01, 0x00, 0x93, 0x4b, 0xf9, 0x4e, 0x2a, + 0xa2, 0x8a, 0x3f, 0x83, 0xc9, 0xf7, 0x92, 0x97, 0x25, 0x02, 0x62, 0xfb, 0xe3, 0x27, 0x6b, 0x5a, + 0x1c, 0x91, 0x15, 0x9b, 0xbf, 0xa3, 0xef, 0x89, 0x57, 0xaa, 0xc8, 0x4b, 0x59, 0xb3, 0x0b, 0x45, + 0x5a, 0x79, 0xc2, 0x97, 0x34, 0x80, 0x82, 0x3d, 0x8b, 0x38, 0x63, 0xc3, 0xde, 0xef, 0x4a, 0x8e, + 0x24, 0x35, 0x90, 0x26, 0x8d, 0x80, 0xe1, 0x87, 0x51, 0xa0, 0xe1, 0x30, 0xf6, 0x7c, 0xe6, 0xa1, + 0xac, 0xe9, 0xf7, 0x9b, 0x95, 0xe0, 0x97, 0x47, 0x4f, 0xeb, 0xc9, 0x81, 0x19, 0x5b, 0x1d, 0x13, + 0xa6, 0x90, 0x86, 0xc0, 0x86, 0x3f, 0x66, 0xa7, 0xb7, 0xfd, 0xb4, 0x87, 0x92, 0x22, 0x7b, 0x1a, + 0xc5, 0xe2, 0x48, 0x9f, 0xeb, 0xdf, 0x08, 0x7a, 0xb5, 0x48, 0x64, 0x83, 0x03, 0x3a, 0x6f, 0x00, + 0x1c, 0xa5, 0xd1, 0xec, 0x1e, 0x27, 0xf5, 0xc3, 0x0f, 0x4c, 0xec, 0x26, 0x42, 0x07, 0x4a, 0x39, + 0xae, 0x68, 0xae, 0xe5, 0x52, 0xe1, 0x96, 0x62, 0x7a, 0x8e, 0x3d, 0x86, 0x7e, 0x67, 0xa8, 0xc0, + 0x1b, 0x11, 0xe7, 0x5f, 0x13, 0xcc, 0xa0, 0xa9, 0x7a, 0xb6, 0x68, 0xb5, 0x0c, 0xda, 0x07, 0xa8, + 0xec, 0xb7, 0xcd, 0x8e, 0x3d, 0xd7, 0x00, 0x9c, 0x96, 0x36, 0x53, 0x4f, 0x6f, 0x23, 0x9c, 0xff, + 0xe1, 0xfc, 0x8d, 0xaa, 0x46, 0x6f, 0x78, 0xb6, 0x76, 0xc7, 0x11, 0x9e, 0xfb, 0x96, 0xbc, 0xe4, + 0xe6, 0x9c, 0xa2, 0xa2, 0x5d, 0x0b, 0x34, 0xed, 0x9c, 0x3f, 0xf9, 0x99, 0xb8, 0x01, 0x59, 0x7d, + 0x52, 0x20, 0xe3, 0x07, 0xea, 0xa5, 0xbe, 0xe5, 0x07, 0xfb, 0x94, 0xd1, 0xfa, 0x69, 0xf9, 0xe5, + 0x19, 0xb2, 0xde, 0x31, 0x5b, 0xac, 0x92, 0xc3, 0x6f, 0x2e, 0xa1, 0xfa, 0x1d, 0xf4, 0x47, 0x8c, + 0x0d, 0xde, 0xde, 0xae, 0x8c, 0x70, 0xe0, 0x23, 0x3c, 0xd0, 0x98, 0x04, 0x0c, 0xd7, 0x96, 0xb0, + 0x2c, 0x37, 0x0f, 0x1f, 0xa4, 0xcc, 0x01, 0x24, 0xf1, 0x30, 0x2e, 0x02, 0x01, 0x03, 0x30, 0x29, + 0xa1, 0x08, 0x31, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0xa2, 0x03, 0x02, 0x01, 0x20, 0xa3, + 0x04, 0x02, 0x02, 0x01, 0x00, 0xa4, 0x05, 0x31, 0x03, 0x02, 0x01, 0x01, 0xa6, 0x05, 0x31, 0x03, + 0x02, 0x01, 0x40, 0xbf, 0x83, 0x77, 0x02, 0x05, 0x00, 0x04, 0x20, 0xcc, 0xd5, 0x40, 0x85, 0x5f, + 0x83, 0x3a, 0x5e, 0x14, 0x80, 0xbf, 0xd2, 0xd3, 0x6f, 0xaf, 0x3a, 0xee, 0xe1, 0x5d, 0xf5, 0xbe, + 0xab, 0xe2, 0x69, 0x1b, 0xc8, 0x2d, 0xde, 0x2a, 0x7a, 0xa9, 0x10, 0x04, 0x10, 0x64, 0xc9, 0xf6, + 0x89, 0xc6, 0x0f, 0xf6, 0x22, 0x3a, 0xb6, 0xe6, 0x99, 0x9e, 0x0e, 0xb6, 0xe5, +]; + +/// To map Keystore errors. +#[derive(thiserror::Error, Debug, Eq, PartialEq)] +pub enum Error { + /// Keystore2 error code + #[error("ResponseCode {0:?}")] + Rc(ResponseCode), + /// Keymint error code + #[error("ErrorCode {0:?}")] + Km(ErrorCode), + /// Exception + #[error("Binder exception {0:?}")] + Binder(ExceptionCode), + /// This is returned if the C implementation of extractSubjectFromCertificate failed. + #[error("Failed to validate certificate chain.")] + ValidateCertChainFailed, + /// Error code to indicate error in ASN.1 DER-encoded data creation. + #[error("Failed to create and encode ASN.1 data.")] + DerEncodeFailed, +} + +/// Keystore2 error mapping. +pub fn map_ks_error<T>(r: BinderResult<T>) -> Result<T, Error> { + r.map_err(|s| { + match s.exception_code() { + ExceptionCode::SERVICE_SPECIFIC => { + match s.service_specific_error() { + se if se < 0 => { + // Negative service specific errors are KM error codes. + Error::Km(ErrorCode(se)) + } + se => { + // Positive service specific errors are KS response codes. + Error::Rc(ResponseCode(se)) + } + } + } + // We create `Error::Binder` to preserve the exception code + // for logging. + e_code => Error::Binder(e_code), + } + }) +} + +/// Generate EC Key using given security level and domain with below key parameters and +/// optionally allow the generated key to be attested with factory provisioned attest key using +/// given challenge and application id - /// Purposes: SIGN and VERIFY /// Digest: SHA_2_256 /// Curve: P_256 -pub fn generate_ec_p256_signing_key_with_attestation( +pub fn generate_ec_p256_signing_key( sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + att_challenge: Option<&[u8]>, ) -> binder::Result<KeyMetadata> { - let att_challenge: &[u8] = b"foo"; - let att_app_id: &[u8] = b"bar"; - let gen_params = AuthSetBuilder::new() + let mut key_attest = false; + let mut gen_params = AuthSetBuilder::new() + .no_auth_required() .algorithm(Algorithm::EC) .purpose(KeyPurpose::SIGN) .purpose(KeyPurpose::VERIFY) .digest(Digest::SHA_2_256) - .ec_curve(EcCurve::P_256) - .attestation_challenge(att_challenge.to_vec()) - .attestation_app_id(att_app_id.to_vec()); + .ec_curve(EcCurve::P_256); + + if let Some(challenge) = att_challenge { + key_attest = true; + gen_params = gen_params.clone().attestation_challenge(challenge.to_vec()); + } match sec_level.generateKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &gen_params, + 0, + b"entropy", + ) { + Ok(key_metadata) => { + assert!(key_metadata.certificate.is_some()); + if key_attest { + assert!(key_metadata.certificateChain.is_some()); + } + if domain == Domain::BLOB { + assert!(key_metadata.key.blob.is_some()); + } + + Ok(key_metadata) + } + Err(e) => Err(e), + } +} + +/// Generate EC signing key. +pub fn generate_ec_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + ec_curve: EcCurve, + digest: Digest, +) -> binder::Result<KeyMetadata> { + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(digest) + .ec_curve(ec_curve); + + let key_metadata = sec_level.generateKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &gen_params, + 0, + b"entropy", + )?; + + // Must have a public key. + assert!(key_metadata.certificate.is_some()); + + // Should not have an attestation record. + assert!(key_metadata.certificateChain.is_none()); + + if domain == Domain::BLOB { + assert!(key_metadata.key.blob.is_some()); + } else { + assert!(key_metadata.key.blob.is_none()); + } + Ok(key_metadata) +} + +/// Generate a RSA key with the given key parameters, alias, domain and namespace. +pub fn generate_rsa_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + key_params: &KeyParams, + attest_key: Option<&KeyDescriptor>, +) -> binder::Result<KeyMetadata> { + let mut gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .rsa_public_exponent(65537) + .key_size(key_params.key_size); + + for purpose in &key_params.purpose { + gen_params = gen_params.purpose(*purpose); + } + if let Some(value) = key_params.digest { + gen_params = gen_params.digest(value) + } + if let Some(value) = key_params.padding { + gen_params = gen_params.padding_mode(value); + } + if let Some(value) = key_params.mgf_digest { + gen_params = gen_params.mgf_digest(value); + } + if let Some(value) = key_params.block_mode { + gen_params = gen_params.block_mode(value) + } + if let Some(value) = &key_params.att_challenge { + gen_params = gen_params.attestation_challenge(value.to_vec()) + } + + let key_metadata = sec_level.generateKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + attest_key, + &gen_params, + 0, + b"entropy", + )?; + + // Must have a public key. + assert!(key_metadata.certificate.is_some()); + + if attest_key.is_none() && key_params.att_challenge.is_some() { + // Should have an attestation record. + assert!(key_metadata.certificateChain.is_some()); + } else { + // Should not have an attestation record. + assert!(key_metadata.certificateChain.is_none()); + } + + assert!( + (domain == Domain::BLOB && key_metadata.key.blob.is_some()) + || key_metadata.key.blob.is_none() + ); + + Ok(key_metadata) +} + +/// Generate AES/3DES key. +pub fn generate_sym_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + algorithm: Algorithm, + size: i32, + alias: &str, + padding_mode: &PaddingMode, + block_mode: &BlockMode, + min_mac_len: Option<i32>, +) -> binder::Result<KeyMetadata> { + let mut gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(algorithm) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(size) + .padding_mode(*padding_mode) + .block_mode(*block_mode); + + if let Some(val) = min_mac_len { + gen_params = gen_params.min_mac_length(val); + } + + let key_metadata = sec_level.generateKey( &KeyDescriptor { - domain: Domain::BLOB, - nspace: SELINUX_SHELL_NAMESPACE, - alias: None, + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), blob: None, }, None, &gen_params, 0, b"entropy", + )?; + + // Should not have public certificate. + assert!(key_metadata.certificate.is_none()); + + // Should not have an attestation record. + assert!(key_metadata.certificateChain.is_none()); + Ok(key_metadata) +} + +/// Generate HMAC key. +pub fn generate_hmac_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, + key_size: i32, + min_mac_len: i32, + digest: Digest, +) -> binder::Result<KeyMetadata> { + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .key_size(key_size) + .min_mac_length(min_mac_len) + .digest(digest); + + let key_metadata = sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )?; + + // Should not have public certificate. + assert!(key_metadata.certificate.is_none()); + + // Should not have an attestation record. + assert!(key_metadata.certificateChain.is_none()); + + Ok(key_metadata) +} + +/// Generate RSA or EC attestation keys using below parameters - +/// Purpose: ATTEST_KEY +/// Digest: Digest::SHA_2_256 +/// Padding: PaddingMode::RSA_PKCS1_1_5_SIGN +/// RSA-Key-Size: 2048 +/// EC-Curve: EcCurve::P_256 +pub fn generate_attestation_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + algorithm: Algorithm, + att_challenge: &[u8], +) -> binder::Result<KeyMetadata> { + assert!(algorithm == Algorithm::RSA || algorithm == Algorithm::EC); + + if algorithm == Algorithm::RSA { + let alias = "ks_rsa_attest_test_key"; + let metadata = generate_rsa_key( + sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ATTEST_KEY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + None, + ) + .unwrap(); + Ok(metadata) + } else { + let metadata = generate_ec_attestation_key( + sec_level, + att_challenge, + Digest::SHA_2_256, + EcCurve::P_256, + ) + .unwrap(); + + Ok(metadata) + } +} + +/// Generate EC attestation key with the given +/// curve, attestation-challenge and attestation-app-id. +pub fn generate_ec_attestation_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + att_challenge: &[u8], + digest: Digest, + ec_curve: EcCurve, +) -> binder::Result<KeyMetadata> { + let alias = "ks_attest_ec_test_key"; + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::ATTEST_KEY) + .ec_curve(ec_curve) + .digest(digest) + .attestation_challenge(att_challenge.to_vec()); + + let attestation_key_metadata = sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )?; + + // Should have public certificate. + assert!(attestation_key_metadata.certificate.is_some()); + // Should have an attestation record. + assert!(attestation_key_metadata.certificateChain.is_some()); + + Ok(attestation_key_metadata) +} + +/// Generate EC-P-256 key and attest it with given attestation key. +pub fn generate_ec_256_attested_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: Option<String>, + att_challenge: &[u8], + attest_key: &KeyDescriptor, +) -> binder::Result<KeyMetadata> { + let ec_gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(att_challenge.to_vec()); + + let ec_key_metadata = sec_level + .generateKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: None }, + Some(attest_key), + &ec_gen_params, + 0, + b"entropy", + ) + .unwrap(); + + // Should have public certificate. + assert!(ec_key_metadata.certificate.is_some()); + // Shouldn't have an attestation record. + assert!(ec_key_metadata.certificateChain.is_none()); + + Ok(ec_key_metadata) +} + +/// Verify that given key param is listed in given authorizations list. +pub fn check_key_param(authorizations: &[Authorization], key_param: KeyParameter) -> bool { + for authrization in authorizations { + if authrization.keyParameter == key_param { + return true; + } + } + + false +} + +/// Imports above defined RSA key - `RSA_2048_KEY` and validates imported key parameters. +pub fn import_rsa_2048_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + import_params: AuthSetBuilder, +) -> binder::Result<KeyMetadata> { + let key_metadata = sec_level + .importKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &import_params, + 0, + RSA_2048_KEY, + ) + .unwrap(); + + assert!(key_metadata.certificate.is_some()); + assert!(key_metadata.certificateChain.is_none()); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { + tag: Tag::RSA_PUBLIC_EXPONENT, + value: KeyParameterValue::LongInteger(65537) + } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { + tag: Tag::PADDING, + value: KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS) + } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + )); + + Ok(key_metadata) +} + +/// Imports above defined EC key - `EC_P_256_KEY` and validates imported key parameters. +pub fn import_ec_p_256_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + import_params: AuthSetBuilder, +) -> binder::Result<KeyMetadata> { + let key_metadata = sec_level + .importKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &import_params, + 0, + EC_P_256_KEY, + ) + .unwrap(); + + assert!(key_metadata.certificate.is_some()); + assert!(key_metadata.certificateChain.is_none()); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::EC) } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(EcCurve::P_256) } + )); + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + )); + + Ok(key_metadata) +} + +/// Import sample AES key and validate its key parameters. +pub fn import_aes_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, +) -> binder::Result<KeyMetadata> { + static AES_KEY: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let key_size = AES_KEY.len() * 8; + + let import_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .block_mode(BlockMode::ECB) + .key_size(key_size.try_into().unwrap()) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .padding_mode(PaddingMode::PKCS7); + + let key_metadata = sec_level.importKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &import_params, + 0, + AES_KEY, + )?; + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::AES) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { + tag: Tag::PADDING, + value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7) + } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + )); + + Ok(key_metadata) +} + +/// Import sample 3DES key and validate its key parameters. +pub fn import_3des_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, +) -> binder::Result<KeyMetadata> { + static TRIPLE_DES_KEY: &[u8] = &[ + 0xa4, 0x9d, 0x75, 0x64, 0x19, 0x9e, 0x97, 0xcb, 0x52, 0x9d, 0x2c, 0x9d, 0x97, 0xbf, 0x2f, + 0x98, 0xd3, 0x5e, 0xdf, 0x57, 0xba, 0x1f, 0x73, 0x58, + ]; + + let import_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::TRIPLE_DES) + .block_mode(BlockMode::ECB) + .key_size(168) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .padding_mode(PaddingMode::PKCS7); + + let key_metadata = sec_level.importKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &import_params, + 0, + TRIPLE_DES_KEY, + )?; + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { + tag: Tag::ALGORITHM, + value: KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES) + } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(168) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { + tag: Tag::PADDING, + value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7) + } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + )); + + Ok(key_metadata) +} + +/// Import sample HMAC key and validate its key parameters. +pub fn import_hmac_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, +) -> binder::Result<KeyMetadata> { + static HMAC_KEY: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let key_size = HMAC_KEY.len() * 8; + + let import_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .key_size(key_size.try_into().unwrap()) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .min_mac_length(256); + + let key_metadata = sec_level.importKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &import_params, + 0, + HMAC_KEY, + )?; + + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::HMAC) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + )); + assert!(check_key_param( + &key_metadata.authorizations, + KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + )); + + Ok(key_metadata) +} + +/// Imports RSA encryption key with WRAP_KEY purpose. +pub fn import_wrapping_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + wrapping_key_data: &[u8], + wrapping_key_alias: Option<String>, +) -> binder::Result<KeyMetadata> { + let wrapping_key_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .purpose(KeyPurpose::WRAP_KEY) + .padding_mode(PaddingMode::RSA_OAEP) + .key_size(2048) + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: wrapping_key_alias, blob: None }, + None, + &wrapping_key_params, + 0, + wrapping_key_data, + ) +} + +/// Import wrapped key using given wrapping key. +pub fn import_wrapped_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: Option<String>, + wrapping_key_metadata: &KeyMetadata, + wrapped_key: Option<Vec<u8>>, +) -> binder::Result<KeyMetadata> { + let unwrap_params = + AuthSetBuilder::new().digest(Digest::SHA_2_256).padding_mode(PaddingMode::RSA_OAEP); + + let authenticator_spec: &[AuthenticatorSpec] = &[AuthenticatorSpec { + authenticatorType: HardwareAuthenticatorType::NONE, + authenticatorId: 0, + }]; + + let key_metadata = sec_level.importWrappedKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: wrapped_key }, + &wrapping_key_metadata.key, + None, + &unwrap_params, + authenticator_spec, + )?; + + Ok(key_metadata) +} + +/// Import wrapping key and then import wrapped key using wrapping key. +pub fn import_wrapping_key_and_wrapped_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + wrapping_key_alias: Option<String>, + wrapping_key_params: AuthSetBuilder, +) -> binder::Result<KeyMetadata> { + let wrapping_key_metadata = sec_level.importKey( + &KeyDescriptor { domain, nspace, alias: wrapping_key_alias, blob: None }, + None, + &wrapping_key_params, + 0, + WRAPPING_KEY, + )?; + + import_wrapped_key(sec_level, alias, &wrapping_key_metadata, Some(WRAPPED_KEY.to_vec())) +} + +/// Import given key material as AES-256-GCM-NONE transport key. +pub fn import_transport_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + transport_key_alias: Option<String>, + transport_key: &[u8], +) -> binder::Result<KeyMetadata> { + let transport_key_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .block_mode(BlockMode::GCM) + .padding_mode(PaddingMode::NONE) + .key_size(256) + .caller_nonce() + .min_mac_length(128) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT); + + sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: transport_key_alias, blob: None }, + None, + &transport_key_params, + 0, + transport_key, + ) +} + +/// Generate EC key with purpose AGREE_KEY. +pub fn generate_ec_agree_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + ec_curve: EcCurve, + digest: Digest, + domain: Domain, + nspace: i64, + alias: Option<String>, +) -> binder::Result<KeyMetadata> { + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::AGREE_KEY) + .digest(digest) + .ec_curve(ec_curve); + + match sec_level.generateKey( + &KeyDescriptor { domain, nspace, alias, blob: None }, + None, + &gen_params, + 0, + b"entropy", ) { Ok(key_metadata) => { assert!(key_metadata.certificate.is_some()); - assert!(key_metadata.certificateChain.is_some()); - assert!(key_metadata.key.blob.is_some()); + if domain == Domain::BLOB { + assert!(key_metadata.key.blob.is_some()); + } Ok(key_metadata) } diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp new file mode 100644 index 00000000..78dd2d72 --- /dev/null +++ b/keystore2/tests/Android.bp @@ -0,0 +1,100 @@ +// Copyright 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_security_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +rust_test { + name: "keystore2_client_tests", + defaults: [ + "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", + ], + srcs: ["keystore2_client_tests.rs"], + test_suites: [ + "general-tests", + ], + test_config: "AndroidTest.xml", + + rustlibs: [ + "librustutils", + "libkeystore2_test_utils", + "packagemanager_aidl-rust", + "libnix", + "libanyhow", + "libbinder_rs", + "liblazy_static", + "liblibc", + "libserde", + "libthiserror", + "libcxx", + "libopenssl", + ], + static_libs: [ + "libkeystore2_ffi_test_utils", + "libgtest", + "libkeymint_vts_test_utils", + ], + shared_libs: [ + "libcrypto", + "libkeymaster_portable", + "libkeymaster_messages", + "libcppbor_external", + ], + require_root: true, +} + +cc_library_static { + name: "libkeystore2_ffi_test_utils", + srcs: ["ffi_test_utils.cpp"], + defaults: [ + "keymint_vts_defaults", + "hidl_defaults", + ], + generated_headers: [ + "cxx-bridge-header", + "libkeystore2_ffi_test_utils_bridge_header", + ], + generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"], + static_libs: [ + "libkeymint_vts_test_utils", + ], + shared_libs: [ + "libkeymaster_portable", + "libkeymaster_messages", + "libcppbor_external", + ], +} + +genrule { + name: "libkeystore2_ffi_test_utils_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) >> $(out)", + srcs: ["ffi_test_utils.rs"], + out: ["libkeystore2_test_utils_cxx_generated.cc"], +} + +genrule { + name: "libkeystore2_ffi_test_utils_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header >> $(out)", + srcs: ["ffi_test_utils.rs"], + out: ["ffi_test_utils.rs.h"], +} diff --git a/keystore2/tests/AndroidTest.xml b/keystore2/tests/AndroidTest.xml new file mode 100644 index 00000000..7db36f7e --- /dev/null +++ b/keystore2/tests/AndroidTest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Config to run keystore2_client_tests device tests."> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option + name="push" + value="keystore2_client_tests->/data/local/tmp/keystore2_client_tests" + /> + </target_preparer> + + <test class="com.android.tradefed.testtype.rust.RustBinaryTest" > + <option name="test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="keystore2_client_tests" /> + <!-- When we run run multiple tests by default they run in parallel. + This will create issue as we create various child/user contexts + in a test leading to issues with IPC. + Serializing tests with below configuration to avoid IPC issues. + --> + <option name="native-test-flag" value="--test-threads=1" /> + </test> +</configuration> diff --git a/keystore2/tests/ffi_test_utils.cpp b/keystore2/tests/ffi_test_utils.cpp new file mode 100644 index 00000000..de20d838 --- /dev/null +++ b/keystore2/tests/ffi_test_utils.cpp @@ -0,0 +1,366 @@ +#include "ffi_test_utils.hpp" + +#include <iostream> + +#include <KeyMintAidlTestBase.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <keymaster/UniquePtr.h> + +#include <memory> +#include <vector> + +#include <hardware/keymaster_defs.h> +#include <keymaster/android_keymaster_utils.h> +#include <keymaster/keymaster_tags.h> + +#include <keymaster/km_openssl/attestation_record.h> +#include <keymaster/km_openssl/openssl_err.h> +#include <keymaster/km_openssl/openssl_utils.h> +#include <openssl/asn1t.h> + +using aidl::android::hardware::security::keymint::ErrorCode; + +#define TAG_SEQUENCE 0x30 +#define LENGTH_MASK 0x80 +#define LENGTH_VALUE_MASK 0x7F + +/** + * ASN.1 structure for `KeyDescription` Schema. + * See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. + * KeyDescription ::= SEQUENCE( + * keyFormat INTEGER, # Values from KeyFormat enum. + * keyParams AuthorizationList, + * ) + */ +typedef struct key_description { + ASN1_INTEGER* key_format; + keymaster::KM_AUTH_LIST* key_params; +} TEST_KEY_DESCRIPTION; + +ASN1_SEQUENCE(TEST_KEY_DESCRIPTION) = { + ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_format, ASN1_INTEGER), + ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_params, keymaster::KM_AUTH_LIST), +} ASN1_SEQUENCE_END(TEST_KEY_DESCRIPTION); +DECLARE_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); + +/** + * ASN.1 structure for `SecureKeyWrapper` Schema. + * See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` schema. + * SecureKeyWrapper ::= SEQUENCE( + * version INTEGER, # Contains value 0 + * encryptedTransportKey OCTET_STRING, + * initializationVector OCTET_STRING, + * keyDescription KeyDescription, + * encryptedKey OCTET_STRING, + * tag OCTET_STRING + * ) + */ +typedef struct secure_key_wrapper { + ASN1_INTEGER* version; + ASN1_OCTET_STRING* encrypted_transport_key; + ASN1_OCTET_STRING* initialization_vector; + TEST_KEY_DESCRIPTION* key_desc; + ASN1_OCTET_STRING* encrypted_key; + ASN1_OCTET_STRING* tag; +} TEST_SECURE_KEY_WRAPPER; + +ASN1_SEQUENCE(TEST_SECURE_KEY_WRAPPER) = { + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, version, ASN1_INTEGER), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_transport_key, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, initialization_vector, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, key_desc, TEST_KEY_DESCRIPTION), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_key, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, tag, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(TEST_SECURE_KEY_WRAPPER); +DECLARE_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); + +IMPLEMENT_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); +IMPLEMENT_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); + +struct TEST_KEY_DESCRIPTION_Delete { + void operator()(TEST_KEY_DESCRIPTION* p) { TEST_KEY_DESCRIPTION_free(p); } +}; +struct TEST_SECURE_KEY_WRAPPER_Delete { + void operator()(TEST_SECURE_KEY_WRAPPER* p) { TEST_SECURE_KEY_WRAPPER_free(p); } +}; + +/* This function extracts a certificate from the certs_chain_buffer at the given + * offset. Each DER encoded certificate starts with TAG_SEQUENCE followed by the + * total length of the certificate. The length of the certificate is determined + * as per ASN.1 encoding rules for the length octets. + * + * @param certs_chain_buffer: buffer containing DER encoded X.509 certificates + * arranged sequentially. + * @data_size: Length of the DER encoded X.509 certificates buffer. + * @index: DER encoded X.509 certificates buffer offset. + * @cert: Encoded certificate to be extracted from buffer as outcome. + * @return: ErrorCode::OK on success, otherwise ErrorCode::UNKNOWN_ERROR. + */ +ErrorCode +extractCertFromCertChainBuffer(uint8_t* certs_chain_buffer, int certs_chain_buffer_size, int& index, + aidl::android::hardware::security::keymint::Certificate& cert) { + if (index >= certs_chain_buffer_size) { + return ErrorCode::UNKNOWN_ERROR; + } + + uint32_t length = 0; + std::vector<uint8_t> cert_bytes; + if (certs_chain_buffer[index] == TAG_SEQUENCE) { + // Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length. + if (0 == (certs_chain_buffer[index + 1] & LENGTH_MASK)) { + length = (uint32_t)certs_chain_buffer[index]; + // Add SEQ and Length fields + length += 2; + } else { + // Long form. Two to 127 octets. Bit 8 of first octet has value "1" and + // bits 7-1 give the number of additional length octets. Second and following + // octets give the actual length. + int additionalBytes = certs_chain_buffer[index + 1] & LENGTH_VALUE_MASK; + if (additionalBytes == 0x01) { + length = certs_chain_buffer[index + 2]; + // Add SEQ and Length fields + length += 3; + } else if (additionalBytes == 0x02) { + length = (certs_chain_buffer[index + 2] << 8 | certs_chain_buffer[index + 3]); + // Add SEQ and Length fields + length += 4; + } else if (additionalBytes == 0x04) { + length = certs_chain_buffer[index + 2] << 24; + length |= certs_chain_buffer[index + 3] << 16; + length |= certs_chain_buffer[index + 4] << 8; + length |= certs_chain_buffer[index + 5]; + // Add SEQ and Length fields + length += 6; + } else { + // Length is larger than uint32_t max limit. + return ErrorCode::UNKNOWN_ERROR; + } + } + cert_bytes.insert(cert_bytes.end(), (certs_chain_buffer + index), + (certs_chain_buffer + index + length)); + index += length; + + for (int i = 0; i < cert_bytes.size(); i++) { + cert.encodedCertificate = std::move(cert_bytes); + } + } else { + // SEQUENCE TAG MISSING. + return ErrorCode::UNKNOWN_ERROR; + } + + return ErrorCode::OK; +} + +ErrorCode getCertificateChain( + rust::Vec<rust::u8>& chainBuffer, + std::vector<aidl::android::hardware::security::keymint::Certificate>& certChain) { + uint8_t* data = chainBuffer.data(); + int index = 0; + int data_size = chainBuffer.size(); + + while (index < data_size) { + aidl::android::hardware::security::keymint::Certificate cert = + aidl::android::hardware::security::keymint::Certificate(); + if (extractCertFromCertChainBuffer(data, data_size, index, cert) != ErrorCode::OK) { + return ErrorCode::UNKNOWN_ERROR; + } + certChain.push_back(std::move(cert)); + } + return ErrorCode::OK; +} + +bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check) { + std::vector<aidl::android::hardware::security::keymint::Certificate> cert_chain = + std::vector<aidl::android::hardware::security::keymint::Certificate>(); + if (cert_len <= 0) { + return false; + } + if (getCertificateChain(cert_buf, cert_chain) != ErrorCode::OK) { + return false; + } + + for (int i = 0; i < cert_chain.size(); i++) { + std::cout << cert_chain[i].toString() << "\n"; + } + auto result = aidl::android::hardware::security::keymint::test::ChainSignaturesAreValid( + cert_chain, strict_issuer_check); + + if (result == testing::AssertionSuccess()) return true; + + return false; +} + +/** + * Below mentioned key parameters are used to create authorization list of + * secure key. + * Algorithm: AES-256 + * Padding: PKCS7 + * Blockmode: ECB + * Purpose: Encrypt, Decrypt + */ +keymaster::AuthorizationSet build_wrapped_key_auth_list() { + return keymaster::AuthorizationSet(keymaster::AuthorizationSetBuilder() + .AesEncryptionKey(256) + .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_ECB) + .Authorization(keymaster::TAG_PADDING, KM_PAD_PKCS7) + .Authorization(keymaster::TAG_NO_AUTH_REQUIRED)); +} + +/** + * Creates ASN.1 DER-encoded data corresponding to `KeyDescription` schema as + * AAD. See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. + */ +CxxResult buildAsn1DerEncodedWrappedKeyDescription() { + CxxResult cxx_result{}; + keymaster_error_t error; + cxx_result.error = KM_ERROR_OK; + + keymaster::UniquePtr<TEST_KEY_DESCRIPTION, TEST_KEY_DESCRIPTION_Delete> key_description( + TEST_KEY_DESCRIPTION_new()); + if (!key_description.get()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + // Fill secure key authorizations. + keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); + error = build_auth_list(auth_list, key_description->key_params); + if (error != KM_ERROR_OK) { + cxx_result.error = error; + return cxx_result; + } + + // Fill secure key format. + if (!ASN1_INTEGER_set(key_description->key_format, KM_KEY_FORMAT_RAW)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Perform ASN.1 DER encoding of KeyDescription. + int asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), nullptr); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + std::vector<uint8_t> asn1_data(asn1_data_len, 0); + + if (!asn1_data.data()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + uint8_t* p = asn1_data.data(); + asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), &p); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + std::move(asn1_data.begin(), asn1_data.end(), std::back_inserter(cxx_result.data)); + + return cxx_result; +} + +/** + * Creates wrapped key material to import in ASN.1 DER-encoded data corresponding to + * `SecureKeyWrapper` schema. See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` + * schema. + */ +CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, + rust::Vec<rust::u8> encrypted_transport_key, rust::Vec<rust::u8> iv, + rust::Vec<rust::u8> tag) { + CxxResult cxx_result{}; + keymaster_error_t error; + cxx_result.error = KM_ERROR_OK; + + uint8_t* enc_secure_key_data = encrypted_secure_key.data(); + int enc_secure_key_size = encrypted_secure_key.size(); + + uint8_t* iv_data = iv.data(); + int iv_size = iv.size(); + + uint8_t* tag_data = tag.data(); + int tag_size = tag.size(); + + uint8_t* enc_transport_key_data = encrypted_transport_key.data(); + int enc_transport_key_size = encrypted_transport_key.size(); + + keymaster::UniquePtr<TEST_SECURE_KEY_WRAPPER, TEST_SECURE_KEY_WRAPPER_Delete> sec_key_wrapper( + TEST_SECURE_KEY_WRAPPER_new()); + if (!sec_key_wrapper.get()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + // Fill version = 0 + if (!ASN1_INTEGER_set(sec_key_wrapper->version, 0)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Fill encrypted transport key. + if (enc_transport_key_size && + !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_transport_key, enc_transport_key_data, + enc_transport_key_size)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Fill encrypted secure key. + if (enc_secure_key_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_key, + enc_secure_key_data, enc_secure_key_size)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Fill secure key authorization list. + keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); + error = build_auth_list(auth_list, sec_key_wrapper->key_desc->key_params); + if (error != KM_ERROR_OK) { + cxx_result.error = error; + return cxx_result; + } + + // Fill secure key format. + if (!ASN1_INTEGER_set(sec_key_wrapper->key_desc->key_format, KM_KEY_FORMAT_RAW)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Fill initialization vector used for encrypting secure key. + if (iv_size && + !ASN1_OCTET_STRING_set(sec_key_wrapper->initialization_vector, iv_data, iv_size)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Fill GCM-tag, extracted during secure key encryption. + if (tag_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->tag, tag_data, tag_size)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // ASN.1 DER-encoding of secure key wrapper. + int asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), nullptr); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + std::vector<uint8_t> asn1_data(asn1_data_len, 0); + + if (!asn1_data.data()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + uint8_t* p = asn1_data.data(); + asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), &p); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + std::move(asn1_data.begin(), asn1_data.end(), std::back_inserter(cxx_result.data)); + + return cxx_result; +} diff --git a/keystore2/tests/ffi_test_utils.hpp b/keystore2/tests/ffi_test_utils.hpp new file mode 100644 index 00000000..b8c7c483 --- /dev/null +++ b/keystore2/tests/ffi_test_utils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "rust/cxx.h" +#include "ffi_test_utils.rs.h" + +bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check); +CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, + rust::Vec<rust::u8> encrypted_transport_key, + rust::Vec<rust::u8> iv, + rust::Vec<rust::u8> tag); +CxxResult buildAsn1DerEncodedWrappedKeyDescription(); diff --git a/keystore2/tests/ffi_test_utils.rs b/keystore2/tests/ffi_test_utils.rs new file mode 100644 index 00000000..066d4a1e --- /dev/null +++ b/keystore2/tests/ffi_test_utils.rs @@ -0,0 +1,80 @@ +// Copyright 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. + +use keystore2_test_utils::key_generations::Error; + +#[cxx::bridge] +mod ffi { + struct CxxResult { + data: Vec<u8>, + error: i32, + } + + unsafe extern "C++" { + include!("ffi_test_utils.hpp"); + fn validateCertChain(cert_buf: Vec<u8>, cert_len: u32, strict_issuer_check: bool) -> bool; + fn createWrappedKey( + encrypted_secure_key: Vec<u8>, + encrypted_transport_key: Vec<u8>, + iv: Vec<u8>, + tag: Vec<u8>, + ) -> CxxResult; + fn buildAsn1DerEncodedWrappedKeyDescription() -> CxxResult; + } +} + +/// Validate given certificate chain. +pub fn validate_certchain(cert_buf: &[u8]) -> Result<bool, Error> { + if ffi::validateCertChain(cert_buf.to_vec(), cert_buf.len().try_into().unwrap(), true) { + return Ok(true); + } + + Err(Error::ValidateCertChainFailed) +} + +fn get_result(result: ffi::CxxResult) -> Result<Vec<u8>, Error> { + if result.error == 0 && !result.data.is_empty() { + Ok(result.data) + } else { + Err(Error::DerEncodeFailed) + } +} + +/// Creates wrapped key material to import in ASN.1 DER-encoded data corresponding to +/// `SecureKeyWrapper`. See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` +/// schema. +pub fn create_wrapped_key( + encrypted_secure_key: &[u8], + encrypted_transport_key: &[u8], + iv: &[u8], + tag: &[u8], +) -> Result<Vec<u8>, Error> { + get_result(ffi::createWrappedKey( + encrypted_secure_key.to_vec(), + encrypted_transport_key.to_vec(), + iv.to_vec(), + tag.to_vec(), + )) +} + +/// Creates ASN.1 DER-encoded data corresponding to `KeyDescription` schema. +/// See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. +/// Below mentioned key parameters are used - +/// Algorithm: AES-256 +/// Padding: PKCS7 +/// Blockmode: ECB +/// Purpose: Encrypt, Decrypt +pub fn create_wrapped_key_additional_auth_data() -> Result<Vec<u8>, Error> { + get_result(ffi::buildAsn1DerEncodedWrappedKeyDescription()) +} diff --git a/keystore2/tests/keystore2_client_3des_key_tests.rs b/keystore2/tests/keystore2_client_3des_key_tests.rs new file mode 100644 index 00000000..eda24db0 --- /dev/null +++ b/keystore2/tests/keystore2_client_3des_key_tests.rs @@ -0,0 +1,218 @@ +// Copyright 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. + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, +}; + +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::keystore2_client_test_utils::{ + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; + +/// Generate a 3DES key. Create encryption and decryption operations using the generated key. +fn create_3des_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + padding_mode: PaddingMode, + block_mode: BlockMode, + nonce: &mut Option<Vec<u8>>, +) -> Result<(), binder::Status> { + let alias = format!("ks_3des_test_key_{}{}", block_mode.0, padding_mode.0); + + let key_metadata = key_generations::generate_sym_key( + sec_level, + Algorithm::TRIPLE_DES, + 168, + &alias, + &padding_mode, + &block_mode, + None, + )?; + + // Encrypts `SAMPLE_PLAIN_TEXT` whose length is multiple of DES block size. + let cipher_text = perform_sample_sym_key_encrypt_op( + sec_level, + padding_mode, + block_mode, + nonce, + None, + &key_metadata.key, + )?; + assert!(cipher_text.is_some()); + + let plain_text = perform_sample_sym_key_decrypt_op( + sec_level, + &cipher_text.unwrap(), + padding_mode, + block_mode, + nonce, + None, + &key_metadata.key, + ) + .unwrap(); + assert!(plain_text.is_some()); + assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec()); + Ok(()) +} + +/// Generate 3DES keys with various block modes and paddings. +/// - Block Modes: ECB, CBC +/// - Padding Modes: NONE, PKCS7 +/// Test should generate keys and perform operation successfully. +#[test] +fn keystore2_3des_ecb_cbc_generate_key_success() { + let keystore2 = get_keystore_service(); + let block_modes = [BlockMode::ECB, BlockMode::CBC]; + let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE]; + + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + for block_mode in block_modes { + for padding_mode in padding_modes { + assert_eq!( + Ok(()), + create_3des_key_and_operation(&sec_level, padding_mode, block_mode, &mut None) + ); + } + } +} + +/// Try to generate 3DES key with invalid key size. Test should fail to generate a key with +/// an error code `UNSUPPORTED_KEY_SIZE`. +#[test] +fn keystore2_3des_key_fails_unsupported_key_size() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "3des_key_test_invalid_1"; + let invalid_key_size = 128; + + let result = key_generations::map_ks_error(key_generations::generate_sym_key( + &sec_level, + Algorithm::TRIPLE_DES, + invalid_key_size, + alias, + &PaddingMode::PKCS7, + &BlockMode::CBC, + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err()); +} + +/// Generate a 3DES key without providing padding mode and try to use the generated key to create +/// an operation. Test should fail to create an operation with an error code +/// `UNSUPPORTED_PADDING_MODE`. +#[test] +fn keystore2_3des_key_fails_missing_padding() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "3des_key_test_missing_padding"; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::TRIPLE_DES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(168) + .block_mode(BlockMode::ECB); + + let key_metadata = sec_level + .generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + ) + .unwrap(); + + let op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .block_mode(BlockMode::ECB); + + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &op_params, + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err()); +} + +/// Generate a 3DES key with padding mode NONE. Try to encrypt a text whose length isn't a +/// multiple of the DES block size. +#[test] +fn keystore2_3des_key_encrypt_fails_invalid_input_length() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "3des_key_test_invalid_input_len"; + + let key_metadata = key_generations::generate_sym_key( + &sec_level, + Algorithm::TRIPLE_DES, + 168, + alias, + &PaddingMode::NONE, + &BlockMode::ECB, + None, + ) + .unwrap(); + + let op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .padding_mode(PaddingMode::NONE) + .block_mode(BlockMode::ECB); + + let op_response = sec_level + .createOperation(&key_metadata.key, &op_params, false) + .expect("Error in creation of operation using rebound key."); + assert!(op_response.iOperation.is_some()); + + let op = op_response.iOperation.unwrap(); + // 3DES expects input should be multiple of DES block size (64-bits) length. Try with invalid + // length of input. + let invalid_block_size_msg = b"my message 111"; + let result = key_generations::map_ks_error(op.finish(Some(invalid_block_size_msg), None)); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_INPUT_LENGTH), result.unwrap_err()); +} + +/// Try to generate 3DES key with BlockMode::CTR. Test should fail to create an operation with an +/// error code `UNSUPPORTED_BLOCK_MODE`. +#[test] +fn keystore2_3des_key_fails_unsupported_block_mode() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = key_generations::map_ks_error(create_3des_key_and_operation( + &sec_level, + PaddingMode::NONE, + BlockMode::CTR, + &mut None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs new file mode 100644 index 00000000..313f596f --- /dev/null +++ b/keystore2/tests/keystore2_client_aes_key_tests.rs @@ -0,0 +1,477 @@ +// Copyright 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. + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, +}; + +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::keystore2_client_test_utils::{ + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; + +/// Generate a AES key. Create encrypt and decrypt operations using the generated key. +fn create_aes_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + key_size: i32, + padding_mode: PaddingMode, + block_mode: BlockMode, + mac_len: Option<i32>, + min_mac_len: Option<i32>, + nonce: &mut Option<Vec<u8>>, +) -> Result<(), binder::Status> { + let alias = format!("ks_aes_test_key_{}{}{}", key_size, block_mode.0, padding_mode.0); + + let key_metadata = key_generations::generate_sym_key( + sec_level, + Algorithm::AES, + key_size, + &alias, + &padding_mode, + &block_mode, + min_mac_len, + )?; + + let cipher_text = perform_sample_sym_key_encrypt_op( + sec_level, + padding_mode, + block_mode, + nonce, + mac_len, + &key_metadata.key, + )?; + + assert!(cipher_text.is_some()); + + let plain_text = perform_sample_sym_key_decrypt_op( + sec_level, + &cipher_text.unwrap(), + padding_mode, + block_mode, + nonce, + mac_len, + &key_metadata.key, + ) + .unwrap(); + assert!(plain_text.is_some()); + assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec()); + Ok(()) +} + +/// Generate AES keys with various block modes and paddings. +/// - Block Modes: ECB, CBC +/// - Padding Modes: NONE, PKCS7 +/// Test should generate keys and perform operation successfully. +#[test] +fn keystore2_aes_ecb_cbc_generate_key() { + let keystore2 = get_keystore_service(); + let key_sizes = [128, 256]; + let block_modes = [BlockMode::ECB, BlockMode::CBC]; + let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE]; + + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + for key_size in key_sizes { + for block_mode in block_modes { + for padding_mode in padding_modes { + assert_eq!( + Ok(()), + create_aes_key_and_operation( + &sec_level, + key_size, + padding_mode, + block_mode, + None, + None, + &mut None, + ) + ); + } + } + } +} + +/// Generate AES keys with - +/// - Block Modes: `CTR, GCM` +/// - Padding Modes: `NONE` +/// Test should generate keys and perform operation successfully. +#[test] +fn keystore2_aes_ctr_gcm_generate_key_success() { + let keystore2 = get_keystore_service(); + let key_sizes = [128, 256]; + let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))]; + + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for key_size in key_sizes { + for (block_mode, mac_len, min_mac_len) in key_params { + let result = key_generations::map_ks_error(create_aes_key_and_operation( + &sec_level, + key_size, + PaddingMode::NONE, + block_mode, + mac_len, + min_mac_len, + &mut None, + )); + + assert_eq!(Ok(()), result); + } // End of block mode. + } // End of key size. +} + +/// Generate AES keys with - +/// - Block Modes: `CTR, GCM` +/// - Padding Modes: `PKCS7` +/// Try to create an operation using generated keys, test should fail to create an operation +/// with an error code `INCOMPATIBLE_PADDING_MODE`. +#[test] +fn keystore2_aes_ctr_gcm_generate_key_fails_incompatible() { + let keystore2 = get_keystore_service(); + let key_sizes = [128, 256]; + let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))]; + + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for key_size in key_sizes { + for (block_mode, mac_len, min_mac_len) in key_params { + let result = key_generations::map_ks_error(create_aes_key_and_operation( + &sec_level, + key_size, + PaddingMode::PKCS7, + block_mode, + mac_len, + min_mac_len, + &mut None, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE), result.unwrap_err()); + } // End of block mode. + } // End of key size. +} + +/// Try to generate AES key with invalid key size. Test should fail to generate a key with +/// an error code `UNSUPPORTED_KEY_SIZE`. +#[test] +fn keystore2_aes_key_fails_unsupported_key_size() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let result = key_generations::map_ks_error(key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 1024, + alias, + &PaddingMode::NONE, + &BlockMode::ECB, + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err()); +} + +/// Try to generate AES key with GCM block mode without providing `MIN_MAC_LENGTH`. +/// Test should fail to generate a key with an error code `MISSING_MIN_MAC_LENGTH`. +#[test] +fn keystore2_aes_gcm_key_fails_missing_min_mac_len() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let result = key_generations::map_ks_error(key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 128, + alias, + &PaddingMode::NONE, + &BlockMode::GCM, + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::MISSING_MIN_MAC_LENGTH), result.unwrap_err()); +} + +/// Try to create an operation using AES key with multiple block modes. Test should fail to create +/// an operation with `UNSUPPORTED_BLOCK_MODE` error code. +#[test] +fn keystore2_aes_key_op_fails_multi_block_modes() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(128) + .block_mode(BlockMode::ECB) + .block_mode(BlockMode::CBC) + .padding_mode(PaddingMode::NONE); + + let key_metadata = sec_level + .generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + ) + .unwrap(); + + let op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .block_mode(BlockMode::ECB) + .block_mode(BlockMode::CBC) + .padding_mode(PaddingMode::NONE); + + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &op_params, + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err()); +} + +/// Try to create an operation using AES key with multiple padding modes. Test should fail to create +/// an operation with `UNSUPPORTED_PADDING_MODE` error code. +#[test] +fn keystore2_aes_key_op_fails_multi_padding_modes() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(128) + .block_mode(BlockMode::ECB) + .padding_mode(PaddingMode::PKCS7) + .padding_mode(PaddingMode::NONE); + + let key_metadata = sec_level + .generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + ) + .unwrap(); + + let op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .block_mode(BlockMode::ECB) + .padding_mode(PaddingMode::PKCS7) + .padding_mode(PaddingMode::NONE); + + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &op_params, + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err()); +} + +/// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key +/// with PKCS7 padding mode. Test should fail to create an Operation with +/// `INCOMPATIBLE_PADDING_MODE` error code. +#[test] +fn keystore2_aes_key_op_fails_incompatible_padding() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let key_metadata = key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 128, + alias, + &PaddingMode::NONE, + &BlockMode::ECB, + None, + ) + .unwrap(); + + let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( + &sec_level, + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE), result.unwrap_err()); +} + +/// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key +/// with CBC block mode. Test should fail to create an Operation with +/// `INCOMPATIBLE_BLOCK_MODE` error code. +#[test] +fn keystore2_aes_key_op_fails_incompatible_blockmode() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_invalid_1"; + + let key_metadata = key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 128, + alias, + &PaddingMode::NONE, + &BlockMode::ECB, + None, + ) + .unwrap(); + + let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( + &sec_level, + PaddingMode::NONE, + BlockMode::CBC, + &mut None, + None, + &key_metadata.key, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_BLOCK_MODE), result.unwrap_err()); +} + +/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this +/// generated key without providing `MAC_LENGTH`. Test should fail to create an operation with +/// `MISSING_MAC_LENGTH` error code. +#[test] +fn keystore2_aes_gcm_op_fails_missing_mac_len() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let mac_len = None; + let min_mac_len = Some(128); + + let result = key_generations::map_ks_error(create_aes_key_and_operation( + &sec_level, + 128, + PaddingMode::NONE, + BlockMode::GCM, + mac_len, + min_mac_len, + &mut None, + )); + assert!(result.is_err()); + + let e = result.unwrap_err(); + assert!( + e == Error::Km(ErrorCode::MISSING_MAC_LENGTH) + || e == Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH) + ); +} + +/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this +/// generated key and provide `MAC_LENGTH` < key's `MIN_MAC_LENGTH`. Test should fail to create +/// an operation with `INVALID_MAC_LENGTH` error code. +#[test] +fn keystore2_aes_gcm_op_fails_invalid_mac_len() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let mac_len = Some(96); + let min_mac_len = Some(104); + + let result = key_generations::map_ks_error(create_aes_key_and_operation( + &sec_level, + 128, + PaddingMode::NONE, + BlockMode::GCM, + mac_len, + min_mac_len, + &mut None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_MAC_LENGTH), result.unwrap_err()); +} + +/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this +/// generated key and provide `MAC_LENGTH` > 128. Test should fail to create an operation with +/// `UNSUPPORTED_MAC_LENGTH` error code. +#[test] +fn keystore2_aes_gcm_op_fails_unsupported_mac_len() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = key_generations::map_ks_error(create_aes_key_and_operation( + &sec_level, + 128, + PaddingMode::NONE, + BlockMode::GCM, + Some(256), + Some(128), + &mut None, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH), result.unwrap_err()); +} + +/// Generate a AES-CBC-PKCS7 key without `CALLER_NONCE` authorization. Try to set nonce while +/// creating an operation using this generated key. Test should fail to create an operation with +/// `CALLER_NONCE_PROHIBITED` error code. +#[test] +fn keystore2_aes_key_op_fails_nonce_prohibited() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "aes_key_test_nonce_1"; + let mut nonce = Some(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + let key_metadata = key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 128, + alias, + &PaddingMode::PKCS7, + &BlockMode::CBC, + None, + ) + .unwrap(); + + let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( + &sec_level, + PaddingMode::NONE, + BlockMode::CBC, + &mut nonce, + None, + &key_metadata.key, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::CALLER_NONCE_PROHIBITED), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs new file mode 100644 index 00000000..4febd9b5 --- /dev/null +++ b/keystore2/tests/keystore2_client_attest_key_tests.rs @@ -0,0 +1,482 @@ +// Copyright 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. + +use nix::unistd::getuid; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::ffi_test_utils::validate_certchain; + +use crate::{ + keystore2_client_test_utils::app_attest_key_feature_exists, + skip_test_if_no_app_attest_key_feature, +}; + +/// Generate RSA and EC attestation keys and use them for signing RSA-signing keys. +/// Test should be able to generate attestation keys and use them successfully. +#[test] +fn keystore2_attest_rsa_signing_key_success() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + for algo in [Algorithm::RSA, Algorithm::EC] { + // Create attestation key. + let attestation_key_metadata = + key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Create RSA signing key and use attestation key to sign it. + let sign_key_alias = format!("ks_attest_rsa_signing_key_{}", getuid()); + let sign_key_metadata = key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(sign_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + Some(&attestation_key_metadata.key), + ) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain"); + } +} + +/// Generate RSA and EC attestation keys and use them for signing RSA encrypt/decrypt keys. +/// Test should be able to generate attestation keys and use them successfully. +#[test] +fn keystore2_attest_rsa_encrypt_key_success() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + for algo in [Algorithm::RSA, Algorithm::EC] { + // Create attestation key. + let attestation_key_metadata = + key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Create RSA encrypt/decrypt key and use attestation key to sign it. + let decrypt_key_alias = format!("ks_attest_rsa_encrypt_key_{}", getuid()); + let decrypt_key_metadata = key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(decrypt_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + Some(&attestation_key_metadata.key), + ) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(decrypt_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + } +} + +/// Generate RSA and EC attestation keys and use them for signing EC keys. +/// Test should be able to generate attestation keys and use them successfully. +#[test] +fn keystore2_attest_ec_key_success() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + for algo in [Algorithm::RSA, Algorithm::EC] { + // Create attestation key. + let attestation_key_metadata = + key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Create EC key and use attestation key to sign it. + let ec_key_alias = format!("ks_ec_attested_test_key_{}", getuid()); + let ec_key_metadata = key_generations::generate_ec_256_attested_key( + &sec_level, + Some(ec_key_alias), + att_challenge, + &attestation_key_metadata.key, + ) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(ec_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + } +} + +/// Generate EC-CURVE_25519 attestation key and use it for signing RSA-signing keys. +/// Test should be able to generate RSA signing key with EC-CURVE_25519 as attestation key +/// successfully. +#[test] +fn keystore2_attest_rsa_signing_key_with_ec_25519_key_success() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + // Create EcCurve::CURVE_25519 attestation key. + let attestation_key_metadata = key_generations::generate_ec_attestation_key( + &sec_level, + att_challenge, + Digest::NONE, + EcCurve::CURVE_25519, + ) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Create RSA signing key and use attestation key to sign it. + let sign_key_alias = format!("ksrsa_attested_sign_test_key_{}", getuid()); + let sign_key_metadata = key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(sign_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + Some(&attestation_key_metadata.key), + ) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain"); +} + +/// Try to generate RSA attestation key with multiple purposes. Test should fail with error code +/// `INCOMPATIBLE_PURPOSE` to generate an attestation key. +#[test] +fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let digest = Digest::SHA_2_256; + let padding = PaddingMode::RSA_PKCS1_1_5_SIGN; + let key_size = 2048; + + let attest_key_alias = + format!("ksrsa_attest_multipurpose_key_{}{}{}", getuid(), key_size, digest.0); + + let attest_gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .purpose(KeyPurpose::ATTEST_KEY) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(digest) + .key_size(key_size) + .rsa_public_exponent(65537) + .padding_mode(padding); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(attest_key_alias), + blob: None, + }, + None, + &attest_gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Try to generate EC attestation key with multiple purposes. Test should fail with error code +/// `INCOMPATIBLE_PURPOSE` to generate an attestation key. +#[test] +fn keystore2_ec_attest_key_with_multi_purpose_fail() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let attest_key_alias = format!("ks_ec_attest_multipurpose_key_{}", getuid()); + + let attest_gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::ATTEST_KEY) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(attest_key_alias), + blob: None, + }, + None, + &attest_gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate RSA attestation key and try to use it for signing RSA key without providing +/// attestation challenge. Test should fail to generate a key with error code +/// `ATTESTATION_CHALLENGE_MISSING`. +#[test] +fn keystore2_attest_key_fails_missing_challenge() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + // Create RSA attestation key. + let attestation_key_metadata = + key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Try to attest RSA signing key without providing attestation challenge. + let sign_key_alias = format!("ksrsa_attested_test_key_missing_challenge{}", getuid()); + let result = key_generations::map_ks_error(key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(sign_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + Some(&attestation_key_metadata.key), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::ATTESTATION_CHALLENGE_MISSING), result.unwrap_err()); +} + +/// Generate an asymmetric key which doesn't possess ATTEST_KEY purpose. Try to use this key as +/// attestation key while generating RSA key. Test should fail to generate a key with error +/// code `INCOMPATIBLE_PURPOSE`. +#[test] +fn keystore2_attest_rsa_key_with_non_attest_key_fails_incompat_purpose_error() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + let alias = format!("non_attest_key_{}", getuid()); + let non_attest_key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + None, + ) + .unwrap(); + + // Try to generate RSA signing key with non-attestation key to sign it. + let sign_key_alias = format!("ksrsa_attested_sign_test_key_non_attest_{}", getuid()); + let result = key_generations::map_ks_error(key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(sign_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + Some(&non_attest_key_metadata.key), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate a symmetric key. Try to use this symmetric key as attestation key while generating RSA +/// key. Test should fail to generate a key with response code `INVALID_ARGUMENT`. +#[test] +fn keystore2_attest_rsa_key_with_symmetric_key_fails_sys_error() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + let alias = "aes_attest_key"; + let sym_key_metadata = key_generations::generate_sym_key( + &sec_level, + Algorithm::AES, + 128, + alias, + &PaddingMode::NONE, + &BlockMode::ECB, + None, + ) + .unwrap(); + + // Try to generate RSA signing key with symmetric key as attestation key. + let sign_key_alias = format!("ksrsa_attested_sign_test_key_sym_attest_{}", getuid()); + let result = key_generations::map_ks_error(key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(sign_key_alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + Some(&sym_key_metadata.key), + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Generate RSA attestation key and try to use it as attestation key while generating symmetric +/// key. Test should generate symmetric key successfully. Verify that generated symmetric key +/// should not have attestation record or certificate. +#[test] +fn keystore2_attest_symmetric_key_fail_sys_error() { + skip_test_if_no_app_attest_key_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let att_challenge: &[u8] = b"foo"; + + // Create attestation key. + let attestation_key_metadata = + key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) + .unwrap(); + + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap()); + validate_certchain(&cert_chain).expect("Error while validating cert chain."); + + // Generate symmetric key with above generated key as attestation key. + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(128) + .padding_mode(PaddingMode::NONE) + .block_mode(BlockMode::ECB) + .attestation_challenge(att_challenge.to_vec()); + + let alias = format!("ks_test_sym_key_attest_{}", getuid()); + let aes_key_metadata = sec_level + .generateKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, + Some(&attestation_key_metadata.key), + &gen_params, + 0, + b"entropy", + ) + .unwrap(); + + // Should not have public certificate. + assert!(aes_key_metadata.certificate.is_none()); + + // Should not have an attestation record. + assert!(aes_key_metadata.certificateChain.is_none()); +} diff --git a/keystore2/tests/keystore2_client_delete_key_tests.rs b/keystore2/tests/keystore2_client_delete_key_tests.rs new file mode 100644 index 00000000..2a06edbc --- /dev/null +++ b/keystore2/tests/keystore2_client_delete_key_tests.rs @@ -0,0 +1,150 @@ +// Copyright 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. + +use nix::unistd::getuid; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + ErrorCode::ErrorCode, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error}; + +/// Generate a key and delete it using keystore2 service `deleteKey` API. Test should successfully +/// delete the generated key. +#[test] +fn keystore2_delete_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "delete_key_success_key"; + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + None, + ) + .unwrap(); + + keystore2.deleteKey(&key_metadata.key).expect("Failed to delete a key."); + + // Check wehther deleted key is removed from keystore. + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key)); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Try to delete non-existing key with domain other than BLOB using keystore2 service `deleteKey` +/// API. Test should fail with an error code `KEY_NOT_FOUND`. +#[test] +fn keystore2_delete_key_fail() { + let test_alias = "delete_key_failure_key"; + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.deleteKey(&KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(test_alias.to_string()), + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Generate a key with `Domain::BLOB`. Try to delete a key with `Domain::BLOB` using keystore2 +/// service `deleteKey` API. Test should fail to delete a key with domain BLOB with an error code +/// `INVALID_ARGUMENT`. +#[test] +fn keystore2_delete_key_with_blob_domain_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "delete_key_blob_fail_key"; + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::BLOB, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + None, + ) + .unwrap(); + + let result = key_generations::map_ks_error(keystore2.deleteKey(&key_metadata.key)); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Generate a key with `Domain::BLOB`. Delete generated key with `Domain::BLOB` using underlying +/// security level `deleteKey` API. Test should delete the key successfully. +#[test] +fn keystore2_delete_key_blob_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "delete_key_blob_success_key"; + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::BLOB, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + None, + ) + .unwrap(); + + let result = sec_level.deleteKey(&key_metadata.key); + assert!(result.is_ok()); +} + +/// Try to delete a key with `Domain::BLOB` without providing key-blob. Test should fail to delete a +/// key with error code `INVALID_ARGUMENT`. +#[test] +fn keystore2_delete_key_fails_with_missing_key_blob() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = key_generations::map_ks_error(sec_level.deleteKey(&KeyDescriptor { + domain: Domain::BLOB, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Try to delete a key with domain other than `Domain::BLOB` using underlying security-level +/// `deleteKey` API. Test should fail to delete a key-blob from underlying security-level backend +/// with error code `INVALID_ARGUMENT`. +#[test] +fn keystore2_delete_key_blob_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_delete_keyblob_test_key_{}", getuid()); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + None, + ) + .unwrap(); + + let result = key_generations::map_ks_error(sec_level.deleteKey(&key_metadata.key)); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs new file mode 100644 index 00000000..c2034ded --- /dev/null +++ b/keystore2/tests/keystore2_client_ec_key_tests.rs @@ -0,0 +1,517 @@ +// Copyright 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. + +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, + KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + CreateOperationResponse::CreateOperationResponse, Domain::Domain, + IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, +}; + +use crate::keystore2_client_test_utils::{ + delete_app_key, execute_op_run_as_child, perform_sample_sign_operation, BarrierReached, + ForcedOp, TestOutcome, +}; + +macro_rules! test_ec_sign_key_op_success { + ( $test_name:ident, $digest:expr, $ec_curve:expr ) => { + #[test] + fn $test_name() { + perform_ec_sign_key_op_success(stringify!($test_name), $digest, $ec_curve); + } + }; +} + +macro_rules! test_ec_sign_key_op_with_none_or_md5_digest { + ( $test_name:ident, $digest:expr, $ec_curve:expr ) => { + #[test] + fn $test_name() { + perform_ec_sign_key_op_with_none_or_md5_digest( + stringify!($test_name), + $digest, + $ec_curve, + ); + } + }; +} + +fn create_ec_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + digest: Digest, + ec_curve: EcCurve, +) -> binder::Result<CreateOperationResponse> { + let key_metadata = + key_generations::generate_ec_key(sec_level, domain, nspace, alias, ec_curve, digest)?; + + sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), + false, + ) +} + +fn perform_ec_sign_key_op_success(alias: &str, digest: Digest, ec_curve: EcCurve) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let op_response = create_ec_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + digest, + ec_curve, + ) + .unwrap(); + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + delete_app_key(&keystore2, alias).unwrap(); +} + +fn perform_ec_sign_key_op_with_none_or_md5_digest(alias: &str, digest: Digest, ec_curve: EcCurve) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + match key_generations::map_ks_error(create_ec_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + digest, + ec_curve, + )) { + Ok(op_response) => { + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_DIGEST)); + assert!(digest == Digest::NONE || digest == Digest::MD5); + } + } + + delete_app_key(&keystore2, alias).unwrap(); +} + +// Below macros generate tests for generating EC keys with curves EcCurve::P_224, EcCurve::P_256, +// EcCurve::P_384, EcCurve::P_521 and various digest modes. Tests tries to create operations using +// the generated keys. Operations with digest modes `SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and +// SHA-2 512` should be created successfully. Creation of operations with digest modes NONE and +// MD5 should fail with an error code `UNSUPPORTED_DIGEST`. +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_none_ec_p224, + Digest::NONE, + EcCurve::P_224 +); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_md5_ec_p224, + Digest::MD5, + EcCurve::P_224 +); +test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p224, Digest::SHA1, EcCurve::P_224); +test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p224, Digest::SHA_2_224, EcCurve::P_224); +test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p224, Digest::SHA_2_256, EcCurve::P_224); +test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p224, Digest::SHA_2_384, EcCurve::P_224); +test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p224, Digest::SHA_2_512, EcCurve::P_224); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_none_ec_p256, + Digest::NONE, + EcCurve::P_256 +); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_md5_ec_p256, + Digest::MD5, + EcCurve::P_256 +); +test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p256, Digest::SHA1, EcCurve::P_256); +test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p256, Digest::SHA_2_224, EcCurve::P_256); +test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p256, Digest::SHA_2_256, EcCurve::P_256); +test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p256, Digest::SHA_2_384, EcCurve::P_256); +test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p256, Digest::SHA_2_512, EcCurve::P_256); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_none_ec_p384, + Digest::NONE, + EcCurve::P_384 +); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_md5_ec_p384, + Digest::MD5, + EcCurve::P_384 +); +test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p384, Digest::SHA1, EcCurve::P_384); +test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p384, Digest::SHA_2_224, EcCurve::P_384); +test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p384, Digest::SHA_2_256, EcCurve::P_384); +test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p384, Digest::SHA_2_384, EcCurve::P_384); +test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p384, Digest::SHA_2_512, EcCurve::P_384); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_none_ec_p521, + Digest::NONE, + EcCurve::P_521 +); +test_ec_sign_key_op_with_none_or_md5_digest!( + sign_ec_key_op_md5_ec_p521, + Digest::MD5, + EcCurve::P_521 +); +test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p521, Digest::SHA1, EcCurve::P_521); +test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p521, Digest::SHA_2_224, EcCurve::P_521); +test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p521, Digest::SHA_2_256, EcCurve::P_521); +test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p521, Digest::SHA_2_384, EcCurve::P_521); +test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p521, Digest::SHA_2_512, EcCurve::P_521); + +/// This test will try to load the key with Domain::BLOB. +/// INVALID_ARGUMENT error is expected. +#[test] +fn keystore2_get_key_entry_blob_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Generate a key with domain as BLOB. + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::BLOB, + key_generations::SELINUX_SHELL_NAMESPACE, + None, + None, + ) + .unwrap(); + + // Try to load the key using above generated KeyDescriptor. + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key)); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); + + // Delete the generated key blob. + sec_level.deleteKey(&key_metadata.key).unwrap(); +} + +/// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected. +#[test] +fn keystore2_generate_key_invalid_domain() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_invalid_test_key_{}", getuid()); + + let result = key_generations::map_ks_error(key_generations::generate_ec_key( + &sec_level, + Domain(99), // Invalid domain. + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + EcCurve::P_256, + Digest::SHA_2_256, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Try to generate a EC key without providing the curve. +/// `UNSUPPORTED_EC_CURVE or UNSUPPORTED_KEY_SIZE` error response is expected. +#[test] +fn keystore2_generate_ec_key_missing_curve() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_ec_no_curve_test_key_{}", getuid()); + + // Don't provide EC curve. + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!( + err, + Error::Km(ErrorCode::UNSUPPORTED_EC_CURVE) | Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE) + )); +} + +/// Try to generate a EC key with curve `CURVE_25519` having `SIGN and AGREE_KEY` purposes. +/// `INCOMPATIBLE_PURPOSE` error response is expected. +#[test] +fn keystore2_generate_ec_key_25519_multi_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_ec_no_curve_test_key_{}", getuid()); + + // Specify `SIGN and AGREE_KEY` purposes. + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .ec_curve(EcCurve::CURVE_25519) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::AGREE_KEY) + .digest(Digest::SHA_2_256); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate EC key with curve `CURVE_25519` and digest mode NONE. Try to create an operation using +/// generated key. `CURVE_25519` key should support `Digest::NONE` digest mode and test should be +/// able to create an operation successfully. +#[test] +fn keystore2_ec_25519_generate_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_ec_25519_none_test_key_gen_{}", getuid()); + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + EcCurve::CURVE_25519, + Digest::NONE, + ) + .unwrap(); + + let op_response = sec_level + .createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::NONE), + false, + ) + .unwrap(); + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); +} + +/// Generate EC keys with curve `CURVE_25519` and digest modes `MD5, SHA1, SHA-2 224, SHA-2 256, +/// SHA-2 384 and SHA-2 512`. Try to create operations using generated keys. `CURVE_25519` keys +/// shouldn't support these digest modes. Test should fail to create operations with an error +/// `UNSUPPORTED_DIGEST`. +#[test] +fn keystore2_ec_25519_generate_key_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let digests = [ + Digest::MD5, + Digest::SHA1, + Digest::SHA_2_224, + Digest::SHA_2_256, + Digest::SHA_2_384, + Digest::SHA_2_512, + ]; + + for digest in digests { + let alias = format!("ks_ec_25519_test_key_gen_{}{}", getuid(), digest.0); + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + EcCurve::CURVE_25519, + digest, + ) + .unwrap(); + + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); + } +} + +/// Generate a EC key with `SHA_2_256` digest mode. Try to create an operation with digest mode +/// other than `SHA_2_256`. Creation of an operation with generated key should fail with +/// `INCOMPATIBLE_DIGEST` error as there is a mismatch of digest mode in key authorizations. +#[test] +fn keystore2_create_op_with_incompatible_key_digest() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_ec_test_incomp_key_digest"; + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .unwrap(); + + let digests = + [Digest::NONE, Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_384, Digest::SHA_2_512]; + + for digest in digests { + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err()); + } +} + +/// Generate a key in client#1 and try to use it in other client#2. +/// Client#2 should fail to load the key as the it doesn't own the client#1 generated key. +#[test] +fn keystore2_key_owner_validation() { + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID_1: u32 = 10601; + + let uid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1; + let gid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1; + let alias = "ks_owner_check_test_key"; + + // Client#1: Generate a key and create an operation using generated key. + // Wait until the parent notifies to continue. Once the parent notifies, this operation + // is expected to be completed successfully. + let mut child_handle = execute_op_run_as_child( + TARGET_CTX, + Domain::APP, + -1, + Some(alias.to_string()), + Uid::from_raw(uid1), + Gid::from_raw(gid1), + ForcedOp(false), + ); + + // Wait until (client#1) child process notifies us to continue, so that there will be a key + // generated by client#1. + child_handle.recv(); + + // Client#2: This child will try to load the key generated by client#1. + const APPLICATION_ID_2: u32 = 10602; + let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2; + let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2; + unsafe { + run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || { + let keystore2_inst = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }); + }; + + // Notify the child process (client#1) to resume and finish. + child_handle.send(&BarrierReached {}); + assert!( + (child_handle.get_result() == TestOutcome::Ok), + "Client#1 failed to complete the operation." + ); +} + +/// Generate EC key with BLOB as domain. Generated key should be returned to caller as key blob. +/// Verify that `blob` field in the `KeyDescriptor` is not empty and should have the key blob. +/// Try to use this key for performing a sample operation and the operation should complete +/// successfully. +#[test] +fn keystore2_generate_key_with_blob_domain() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::BLOB, + key_generations::SELINUX_SHELL_NAMESPACE, + None, + EcCurve::P_256, + Digest::SHA_2_256, + ) + .unwrap(); + + assert!(key_metadata.certificate.is_some()); + assert!(key_metadata.certificateChain.is_none()); + + // Must have the key blob. + assert!(key_metadata.key.blob.is_some()); + + let op_response = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )) + .unwrap(); + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + // Delete the generated key blob. + sec_level.deleteKey(&key_metadata.key).unwrap(); +} diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs new file mode 100644 index 00000000..bde872d0 --- /dev/null +++ b/keystore2/tests/keystore2_client_grant_key_tests.rs @@ -0,0 +1,755 @@ +// Copyright 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. + +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, + IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, + ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, +}; + +use crate::keystore2_client_test_utils::{ + generate_ec_key_and_grant_to_users, perform_sample_sign_operation, +}; + +/// Generate an EC signing key and grant it to the user with given access vector. +fn generate_ec_key_and_grant_to_user( + grantee_uid: i32, + access_vector: i32, +) -> binder::Result<KeyDescriptor> { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("{}{}", "ks_grant_test_key_1", getuid()); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + None, + ) + .unwrap(); + + keystore2.grant(&key_metadata.key, grantee_uid, access_vector) +} + +fn load_grant_key_and_perform_sign_operation( + keystore2: &binder::Strong<dyn IKeystoreService>, + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + grant_key_nspace: i64, +) -> Result<(), binder::Status> { + let key_entry_response = keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })?; + + // Perform sample crypto operation using granted key. + let op_response = sec_level.createOperation( + &key_entry_response.metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )?; + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + Ok(()) +} + +/// Try to grant a key with permission that does not map to any of the `KeyPermission` values. +/// An error is expected with values that does not map to set of permissions listed in +/// `KeyPermission`. +#[test] +fn keystore2_grant_key_with_invalid_perm_expecting_syserror() { + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + let grantee_uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + let invalid_access_vector = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0 << 19; + + let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_user( + grantee_uid.try_into().unwrap(), + invalid_access_vector, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err()); +} + +/// Try to grant a key with empty access vector `KeyPermission::NONE`, should be able to grant a +/// key with empty access vector successfully. In grantee context try to use the granted key, it +/// should fail to load the key with permission denied error. +#[test] +fn keystore2_grant_key_with_perm_none() { + static TARGET_SU_CTX: &str = "u:r:su:s0"; + + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + let grant_key_nspace = unsafe { + run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let empty_access_vector = KeyPermission::NONE.0; + + let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user( + GRANTEE_UID.try_into().unwrap(), + empty_access_vector, + )) + .unwrap(); + + assert_eq!(grant_key.domain, Domain::GRANT); + + grant_key.nspace + }) + }; + + // In grantee context try to load the key, it should fail to load the granted key as it is + // granted with empty access vector. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }, + ) + }; +} + +/// Grant a key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether grantee +/// can succeed in loading the granted key and try to perform simple operation using this granted +/// key. Grantee should be able to load the key and use the key to perform crypto operation +/// successfully. Try to delete the granted key in grantee context where it is expected to fail to +/// delete it as `DELETE` permission is not granted. +#[test] +fn keystore2_grant_get_info_use_key_perm() { + static TARGET_SU_CTX: &str = "u:r:su:s0"; + + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + // Generate a key and grant it to a user with GET_INFO|USE key permissions. + let grant_key_nspace = unsafe { + run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0; + let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user( + GRANTEE_UID.try_into().unwrap(), + access_vector, + )) + .unwrap(); + + assert_eq!(grant_key.domain, Domain::GRANT); + + grant_key.nspace + }) + }; + + // In grantee context load the key and try to perform crypto operation. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + let sec_level = + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Load the granted key. + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + }) + .unwrap(); + + // Perform sample crypto operation using granted key. + let op_response = sec_level + .createOperation( + &key_entry_response.metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ) + .unwrap(); + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + // Try to delete the key, it is expected to be fail with permission denied error. + let result = key_generations::map_ks_error(keystore2.deleteKey(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }, + ) + }; +} + +/// Grant a key to the user with DELETE access. In grantee context load the key and delete it. +/// Verify that grantee should succeed in deleting the granted key and in grantor context test +/// should fail to find the key with error response `KEY_NOT_FOUND`. +#[test] +fn keystore2_grant_delete_key_success() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + static ALIAS: &str = "ks_grant_key_delete_success"; + + // Generate a key and grant it to a user with DELETE permission. + let grant_key_nspace = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let access_vector = KeyPermission::DELETE.0; + let mut grant_keys = generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(ALIAS.to_string()), + vec![GRANTEE_UID.try_into().unwrap()], + access_vector, + ) + .unwrap(); + + grant_keys.remove(0) + }) + }; + + // Grantee context, delete the key. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + keystore2 + .deleteKey(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + }) + .unwrap(); + }, + ) + }; + + // Verify whether key got deleted in grantor's context. + unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), move || { + let keystore2_inst = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS.to_string()), + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }) + }; +} + +/// Grant a key to the user. In grantee context load the granted key and try to grant it to second +/// user. Test should fail with a response code `PERMISSION_DENIED` to grant a key to second user +/// from grantee context. Test should make sure second grantee should not have a access to granted +/// key. +#[test] +fn keystore2_grant_key_fails_with_permission_denied() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + const SEC_USER_ID: u32 = 98; + const SEC_APPLICATION_ID: u32 = 10001; + static SEC_GRANTEE_UID: u32 = SEC_USER_ID * AID_USER_OFFSET + SEC_APPLICATION_ID; + static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID; + + // Generate a key and grant it to a user with GET_INFO permission. + let grant_key_nspace = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let access_vector = KeyPermission::GET_INFO.0; + let alias = format!("ks_grant_perm_denied_key_{}", getuid()); + let mut grant_keys = generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(alias), + vec![GRANTEE_UID.try_into().unwrap()], + access_vector, + ) + .unwrap(); + + grant_keys.remove(0) + }) + }; + + // Grantee context, load the granted key and try to grant it to `SEC_GRANTEE_UID` grantee. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + let access_vector = KeyPermission::GET_INFO.0; + + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + }) + .unwrap(); + + let result = key_generations::map_ks_error(keystore2.grant( + &key_entry_response.metadata.key, + SEC_GRANTEE_UID.try_into().unwrap(), + access_vector, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }, + ) + }; + + // Make sure second grantee shouldn't have access to the above granted key. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(SEC_GRANTEE_UID), + Gid::from_raw(SEC_GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); + + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }, + ) + }; +} + +/// Try to grant a key with `GRANT` access. Keystore2 system shouldn't allow to grant a key with +/// `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response code. +#[test] +fn keystore2_grant_key_fails_with_grant_perm_expect_perm_denied() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let access_vector = KeyPermission::GRANT.0; + let alias = format!("ks_grant_access_vec_key_{}", getuid()); + let user_id = 98; + let application_id = 10001; + let grantee_uid = user_id * AID_USER_OFFSET + application_id; + + let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(alias), + vec![grantee_uid.try_into().unwrap()], + access_vector, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); +} + +/// Try to grant a non-existing key to the user. Test should fail with `KEY_NOT_FOUND` error +/// response. +#[test] +fn keystore2_grant_fails_with_non_existing_key_expect_key_not_found_err() { + let keystore2 = get_keystore_service(); + let alias = format!("ks_grant_test_non_existing_key_5_{}", getuid()); + let user_id = 98; + let application_id = 10001; + let grantee_uid = user_id * AID_USER_OFFSET + application_id; + let access_vector = KeyPermission::GET_INFO.0; + + let result = key_generations::map_ks_error(keystore2.grant( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias), + blob: None, + }, + grantee_uid.try_into().unwrap(), + access_vector, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Grant a key to the user and immediately ungrant the granted key. In grantee context try to load +/// the key. Grantee should fail to load the ungranted key with `KEY_NOT_FOUND` error response. +#[test] +fn keystore2_ungrant_key_success() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + // Generate a key and grant it to a user with GET_INFO permission. + let grant_key_nspace = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_ungrant_test_key_1{}", getuid()); + let access_vector = KeyPermission::GET_INFO.0; + let mut grant_keys = generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(alias.to_string()), + vec![GRANTEE_UID.try_into().unwrap()], + access_vector, + ) + .unwrap(); + + let grant_key_nspace = grant_keys.remove(0); + + //Ungrant above granted key. + keystore2 + .ungrant( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias), + blob: None, + }, + GRANTEE_UID.try_into().unwrap(), + ) + .unwrap(); + + grant_key_nspace + }) + }; + + // Grantee context, try to load the ungranted key. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }, + ) + }; +} + +/// Generate a key, grant it to the user and then delete the granted key. Try to ungrant +/// a deleted key. Test should fail to ungrant a non-existing key with `KEY_NOT_FOUND` error +/// response. Generate a new key with the same alias and try to access the previously granted +/// key in grantee context. Test should fail to load the granted key in grantee context as the +/// associated key is deleted from grantor context. +#[test] +fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const APPLICATION_ID: u32 = 10001; + const USER_ID: u32 = 99; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + let grant_key_nspace = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid()); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + None, + ) + .unwrap(); + + let access_vector = KeyPermission::GET_INFO.0; + let grant_key = keystore2 + .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector) + .unwrap(); + assert_eq!(grant_key.domain, Domain::GRANT); + + // Delete above granted key. + keystore2.deleteKey(&key_metadata.key).unwrap(); + + // Try to ungrant above granted key. + let result = key_generations::map_ks_error( + keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()), + ); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + + // Generate a new key with the same alias and try to access the earlier granted key + // in grantee context. + let result = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + None, + ); + assert!(result.is_ok()); + + grant_key.nspace + }) + }; + + // Make sure grant did not persist, try to access the earlier granted key in grantee context. + // Grantee context should fail to load the granted key as its associated key is deleted in + // grantor context. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }, + ) + }; +} + +/// Grant a key to multiple users. Verify that all grantees should succeed in loading the key and +/// use it for performing an operation successfully. +#[test] +fn keystore2_grant_key_to_multi_users_success() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const APPLICATION_ID: u32 = 10001; + const USER_ID_1: u32 = 99; + static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_1_GID: u32 = GRANTEE_1_UID; + + const USER_ID_2: u32 = 98; + static GRANTEE_2_UID: u32 = USER_ID_2 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_2_GID: u32 = GRANTEE_2_UID; + + // Generate a key and grant it to multiple users with GET_INFO|USE permissions. + let mut grant_keys = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_grant_test_key_2{}", getuid()); + let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0; + + generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(alias), + vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()], + access_vector, + ) + .unwrap() + }) + }; + + for (grantee_uid, grantee_gid) in + &[(GRANTEE_1_UID, GRANTEE_1_GID), (GRANTEE_2_UID, GRANTEE_2_GID)] + { + let grant_key_nspace = grant_keys.remove(0); + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(*grantee_uid), + Gid::from_raw(*grantee_gid), + move || { + let keystore2 = get_keystore_service(); + let sec_level = + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + assert_eq!( + Ok(()), + key_generations::map_ks_error(load_grant_key_and_perform_sign_operation( + &keystore2, + &sec_level, + grant_key_nspace + )) + ); + }, + ) + }; + } +} + +/// Grant a key to multiple users with GET_INFO|DELETE permissions. In one of the grantee context +/// use the key and delete it. Try to load the granted key in another grantee context. Test should +/// fail to load the granted key with `KEY_NOT_FOUND` error response. +#[test] +fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID_1: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_1_GID: u32 = GRANTEE_1_UID; + + const USER_ID_2: u32 = 98; + static GRANTEE_2_UID: u32 = USER_ID_2 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_2_GID: u32 = GRANTEE_2_UID; + + // Generate a key and grant it to multiple users with GET_INFO permission. + let mut grant_keys = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_grant_test_key_2{}", getuid()); + let access_vector = + KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0; + + generate_ec_key_and_grant_to_users( + &keystore2, + &sec_level, + Some(alias), + vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()], + access_vector, + ) + .unwrap() + }) + }; + + // Grantee #1 context + let grant_key1_nspace = grant_keys.remove(0); + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_1_UID), + Gid::from_raw(GRANTEE_1_GID), + move || { + let keystore2 = get_keystore_service(); + let sec_level = + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + assert_eq!( + Ok(()), + key_generations::map_ks_error(load_grant_key_and_perform_sign_operation( + &keystore2, + &sec_level, + grant_key1_nspace + )) + ); + + // Delete the granted key. + keystore2 + .deleteKey(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key1_nspace, + alias: None, + blob: None, + }) + .unwrap(); + }, + ) + }; + + // Grantee #2 context + let grant_key2_nspace = grant_keys.remove(0); + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_2_UID), + Gid::from_raw(GRANTEE_2_GID), + move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key2_nspace, + alias: None, + blob: None, + })); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + }, + ) + }; +} diff --git a/keystore2/tests/keystore2_client_hmac_key_tests.rs b/keystore2/tests/keystore2_client_hmac_key_tests.rs new file mode 100644 index 00000000..6bb80017 --- /dev/null +++ b/keystore2/tests/keystore2_client_hmac_key_tests.rs @@ -0,0 +1,305 @@ +// Copyright 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. + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, + SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::keystore2_client_test_utils::perform_sample_sign_operation; + +/// Generate HMAC key with given parameters and perform a sample operation using generated key. +fn create_hmac_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, + key_size: i32, + mac_len: i32, + min_mac_len: i32, + digest: Digest, +) -> Result<(), binder::Status> { + let key_metadata = + key_generations::generate_hmac_key(sec_level, alias, key_size, min_mac_len, digest)?; + + let op_response = sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(digest) + .mac_length(mac_len), + false, + )?; + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + Ok(()) +} + +/// Generate HMAC keys with various digest modes [SHA1, SHA_2_224, SHA_2_256, SHA_2_384, +/// SHA_2_512]. Create an operation using generated keys. Test should create operations +/// successfully. +#[test] +fn keystore2_hmac_key_op_success() { + let digests = + [Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_256, Digest::SHA_2_384, Digest::SHA_2_512]; + let min_mac_len = 128; + let mac_len = 128; + let key_size = 128; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for digest in digests { + let alias = format!("ks_hmac_test_key_{}", digest.0); + + assert_eq!( + Ok(()), + create_hmac_key_and_operation( + &sec_level, + &alias, + key_size, + mac_len, + min_mac_len, + digest, + ) + ); + } +} + +/// Generate HMAC keys with various key lengths. For invalid key sizes, key generation +/// should fail with an error code `UNSUPPORTED_KEY_SIZE`. +#[test] +fn keystore2_hmac_gen_keys_fails_expect_unsupported_key_size() { + let min_mac_len = 256; + let digest = Digest::SHA_2_256; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for key_size in 0..513 { + let alias = format!("ks_hmac_test_key_{}", key_size); + let result = key_generations::map_ks_error(key_generations::generate_hmac_key( + &sec_level, + &alias, + key_size, + min_mac_len, + digest, + )); + + match result { + Ok(_) => { + assert!((key_size >= 64 && key_size % 8 == 0)); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE)); + assert!((key_size < 64 || key_size % 8 != 0), "Unsupported KeySize: {}", key_size); + } + } + } +} + +/// Generate HMAC keys with various min-mac-lengths. For invalid min-mac-length, key generation +/// should fail with an error code `UNSUPPORTED_MIN_MAC_LENGTH`. +#[test] +fn keystore2_hmac_gen_keys_fails_expect_unsupported_min_mac_length() { + let digest = Digest::SHA_2_256; + let key_size = 128; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for min_mac_len in 0..257 { + let alias = format!("ks_hmac_test_key_mml_{}", min_mac_len); + match key_generations::map_ks_error(key_generations::generate_hmac_key( + &sec_level, + &alias, + key_size, + min_mac_len, + digest, + )) { + Ok(_) => { + assert!((min_mac_len >= 64 && min_mac_len % 8 == 0)); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_MIN_MAC_LENGTH)); + assert!( + (min_mac_len < 64 || min_mac_len % 8 != 0), + "Unsupported MinMacLength: {}", + min_mac_len + ); + } + } + } +} + +/// Try to generate HMAC key with multiple digests in key authorizations list. +/// Test fails to generate a key with multiple digests with an error code `UNSUPPORTED_DIGEST`. +#[test] +fn keystore2_hmac_gen_key_multi_digests_fails_expect_unsupported_digest() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_hmac_test_key_multi_dig"; + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .key_size(128) + .min_mac_length(128) + .digest(Digest::SHA1) + .digest(Digest::SHA_2_256); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); +} + +/// Try to generate HMAC key without providing digest mode. HMAC key generation with +/// no digest should fail with an error code `UNSUPPORTED_DIGEST`. +#[test] +fn keystore2_hmac_gen_key_no_digests_fails_expect_unsupported_digest() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_hmac_test_key_no_dig"; + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .key_size(128) + .min_mac_length(128); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); +} + +/// Try to generate a HMAC key with NONE digest mode, it should fail with `UNSUPPORTED_DIGEST` +/// error code. +#[test] +fn keystore2_hmac_gen_key_with_none_digest_fails_expect_unsupported_digest() { + let min_mac_len = 128; + let key_size = 128; + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_hmac_test_key_fail"; + let result = key_generations::map_ks_error(key_generations::generate_hmac_key( + &sec_level, + alias, + key_size, + min_mac_len, + Digest::NONE, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); +} + +/// Generate HMAC key with min-mac-len of 128 bits for the digest modes Digest::SHA1 and +/// Digest::SHA_2_224. Try to create an operation with generated key and mac-length greater than +/// digest length. Test should fail to create an operation with an error code +/// `UNSUPPORTED_MAC_LENGTH`. +#[test] +fn keystore2_hmac_key_op_with_mac_len_greater_than_digest_len_fail() { + let digests = [Digest::SHA1, Digest::SHA_2_224]; + let min_mac_len = 128; + let mac_len = 256; + let key_size = 128; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for digest in digests { + let alias = format!("ks_hmac_test_key_{}", digest.0); + + let result = key_generations::map_ks_error(create_hmac_key_and_operation( + &sec_level, + &alias, + key_size, + mac_len, + min_mac_len, + digest, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH), result.unwrap_err()); + } +} + +/// Generate HMAC key with min-mac-len of 128 bits for the digest modes Digest::SHA1 and +/// Digest::SHA_2_224. Try to create an operation with generated key and mac-length less than +/// min-mac-length. Test should fail to create an operation with an error code +/// `INVALID_MAC_LENGTH`. +#[test] +fn keystore2_hmac_key_op_with_mac_len_less_than_min_mac_len_fail() { + let digests = [Digest::SHA1, Digest::SHA_2_224]; + let min_mac_len = 128; + let mac_len = 64; + let key_size = 128; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + for digest in digests { + let alias = format!("ks_hmac_test_key_{}", digest.0); + + let result = key_generations::map_ks_error(create_hmac_key_and_operation( + &sec_level, + &alias, + key_size, + mac_len, + min_mac_len, + digest, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_MAC_LENGTH), result.unwrap_err()); + } +} diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs new file mode 100644 index 00000000..ecba402a --- /dev/null +++ b/keystore2/tests/keystore2_client_import_keys_tests.rs @@ -0,0 +1,635 @@ +// Copyright 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. + +use nix::unistd::getuid; + +use openssl::rand::rand_bytes; +use openssl::x509::X509; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + AuthenticatorSpec::AuthenticatorSpec, Domain::Domain, + IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::ffi_test_utils::{create_wrapped_key, create_wrapped_key_additional_auth_data}; + +use crate::keystore2_client_test_utils::{ + encrypt_secure_key, encrypt_transport_key, has_default_keymint, + perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op, + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; + +pub fn import_rsa_sign_key_and_perform_sample_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + import_params: authorizations::AuthSetBuilder, +) { + let key_metadata = + key_generations::import_rsa_2048_key(sec_level, domain, nspace, alias, import_params) + .unwrap(); + + perform_sample_asym_sign_verify_op( + sec_level, + &key_metadata, + Some(PaddingMode::RSA_PSS), + Some(Digest::SHA_2_256), + ); +} + +fn perform_sym_key_encrypt_decrypt_op( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + key_metadata: &KeyMetadata, +) { + let cipher_text = perform_sample_sym_key_encrypt_op( + sec_level, + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + ) + .unwrap(); + + assert!(cipher_text.is_some()); + + let plain_text = perform_sample_sym_key_decrypt_op( + sec_level, + &cipher_text.unwrap(), + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + ) + .unwrap(); + + assert!(plain_text.is_some()); + assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec()); +} + +fn build_secure_key_wrapper( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + secure_key: &[u8], + transport_key: &[u8], + nonce: &[u8], + aad: &[u8], + wrapping_key_metadata: &KeyMetadata, +) -> Result<Vec<u8>, Error> { + // Encrypt secure key with transport key. + let transport_key_alias = format!("ks_transport_key_aes_256_key_test_{}", getuid()); + let transport_key_metadata = + key_generations::import_transport_key(sec_level, Some(transport_key_alias), transport_key) + .unwrap(); + let encrypted_secure_key = encrypt_secure_key( + sec_level, + secure_key, + aad, + nonce.to_vec(), + 128, + &transport_key_metadata.key, + ) + .unwrap(); + + // Extract GCM-tag and encrypted secure key data. + let encrypted_secure_key = encrypted_secure_key.unwrap(); + let gcm_tag: Vec<u8> = + encrypted_secure_key[secure_key.len()..(encrypted_secure_key.len())].to_vec(); + let encrypted_secure_key: Vec<u8> = encrypted_secure_key[0..secure_key.len()].to_vec(); + + // Get wrapping key puplic part and encrypt the transport key. + let cert_bytes = wrapping_key_metadata.certificate.as_ref().unwrap(); + let cert = X509::from_der(cert_bytes.as_ref()).unwrap(); + let public_key = cert.public_key().unwrap(); + let encrypted_transport_key = encrypt_transport_key(transport_key, &public_key).unwrap(); + + // Create `SecureKeyWrapper` ASN.1 DER-encoded data. + create_wrapped_key(&encrypted_secure_key, &encrypted_transport_key, nonce, &gcm_tag) +} + +/// Import RSA key and verify imported key parameters. Try to create an operation using the +/// imported key. Test should be able to create an operation successfully. +#[test] +fn keystore2_rsa_import_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_rsa_key_test_import_1_{}{}", getuid(), 2048); + + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::RSA_PSS) + .key_size(2048) + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + import_rsa_sign_key_and_perform_sample_operation( + &sec_level, + Domain::APP, + -1, + Some(alias), + import_params, + ); +} + +/// Import RSA key without providing key-size and public exponent in import key parameters list. +/// Let Key-size and public-exponent to be determined from the imported key material. Verify +/// imported key parameters. Try to create an operation using the imported key. Test should be +/// able to create an operation successfully. +#[test] +fn keystore2_rsa_import_key_determine_key_size_and_pub_exponent() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_rsa_key_test_import_2_{}{}", getuid(), 2048); + + // key-size and public-exponent shouldn't be specified in import key parameters list. + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::RSA_PSS) + .cert_not_before(0) + .cert_not_after(253402300799000); + + import_rsa_sign_key_and_perform_sample_operation( + &sec_level, + Domain::APP, + -1, + Some(alias), + import_params, + ); +} + +/// Try to import RSA key with wrong key size as import-key-parameter. Test should fail to import +/// a key with `IMPORT_PARAMETER_MISMATCH` error code. +#[test] +fn keystore2_rsa_import_key_fails_with_keysize_param_mismatch_error() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_rsa_key_test_import_3_{}{}", getuid(), 2048); + + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::RSA_PSS) + .key_size(1024) // Wrong key size is specified, (actual key-size is 2048). + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let result = key_generations::map_ks_error(sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, + None, + &import_params, + 0, + key_generations::RSA_2048_KEY, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err()); +} + +/// Try to import RSA key with wrong public-exponent as import-key-parameter. +/// Test should fail to import a key with `IMPORT_PARAMETER_MISMATCH` error code. +#[test] +fn keystore2_rsa_import_key_fails_with_public_exponent_param_mismatch_error() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_rsa_key_test_import_4_{}{}", getuid(), 2048); + + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::RSA_PSS) + .key_size(2048) + .rsa_public_exponent(3) // This doesn't match the key. + .cert_not_before(0) + .cert_not_after(253402300799000); + + let result = key_generations::map_ks_error(sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, + None, + &import_params, + 0, + key_generations::RSA_2048_KEY, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err()); +} + +/// Try to import a key with multiple purposes. Test should fail to import a key with +/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` then `importKey` shall be +/// successful. +#[test] +fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_rsa_key_test_import_5_{}{}", getuid(), 2048); + + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::ATTEST_KEY) + .padding_mode(PaddingMode::RSA_PSS) + .key_size(2048) + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let result = key_generations::map_ks_error(sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, + None, + &import_params, + 0, + key_generations::RSA_2048_KEY, + )); + + if has_default_keymint() { + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); + } else { + assert!(result.is_ok()); + } +} + +/// Import EC key and verify imported key parameters. Let ec-curve to be determined from the +/// imported key material. Try to create an operation using the imported key. Test should be +/// able to create an operation successfully. +#[test] +fn keystore2_import_ec_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256); + + // Don't specify ec-curve. + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let key_metadata = key_generations::import_ec_p_256_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + import_params, + ) + .expect("Failed to import EC key."); + + perform_sample_asym_sign_verify_op(&sec_level, &key_metadata, None, Some(Digest::SHA_2_256)); +} + +/// Try to import EC key with wrong ec-curve as import-key-parameter. Test should fail to import a +/// key with `IMPORT_PARAMETER_MISMATCH` error code. +#[test] +fn keystore2_ec_import_key_fails_with_mismatch_curve_error() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256); + + let import_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_224) // It doesn't match with key material. + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let result = key_generations::map_ks_error(sec_level.importKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, + None, + &import_params, + 0, + key_generations::EC_P_256_KEY, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err()); +} + +/// Import AES key and verify key parameters. Try to create an operation using the imported key. +/// Test should be able to create an operation successfully. +#[test] +fn keystore2_import_aes_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_aes_key_test_import_1_{}{}", getuid(), 256); + let key_metadata = key_generations::import_aes_key(&sec_level, Domain::APP, -1, Some(alias)) + .expect("Failed to import AES key."); + + perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); +} + +/// Import 3DES key and verify key parameters. Try to create an operation using the imported key. +/// Test should be able to create an operation successfully. +#[test] +fn keystore2_import_3des_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = key_generations::map_ks_error( + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT), + ) + .unwrap(); + + let alias = format!("ks_3des_key_test_import_1_{}{}", getuid(), 168); + + let key_metadata = key_generations::import_3des_key(&sec_level, Domain::APP, -1, Some(alias)) + .expect("Failed to import 3DES key."); + + perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); +} + +/// Import HMAC key and verify key parameters. Try to create an operation using the imported key. +/// Test should be able to create an operation successfully. +#[test] +fn keystore2_import_hmac_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_hmac_key_test_import_1_{}", getuid()); + + let key_metadata = key_generations::import_hmac_key(&sec_level, Domain::APP, -1, Some(alias)) + .expect("Failed to import HMAC key."); + + perform_sample_hmac_sign_verify_op(&sec_level, &key_metadata.key); +} + +/// This test creates a wrapped key data and imports it. Validates the imported wrapped key. +/// 1. Create a wrapped key material to import, as ASN.1 DER-encoded data corresponding to the +/// `SecureKeyWrapper` schema defined in IKeyMintDevice.aidl. +/// 2. Import wrapped key and use it for crypto operations. +/// Test should successfully import the wrapped key and perform crypto operations. +#[test] +fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let mut secure_key = [0; 32]; + rand_bytes(&mut secure_key).unwrap(); + + let mut transport_key = [0; 32]; + rand_bytes(&mut transport_key).unwrap(); + + let mut nonce = [0; 12]; + rand_bytes(&mut nonce).unwrap(); + + // Import wrapping key. + let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); + let wrapping_key_metadata = key_generations::import_wrapping_key( + &sec_level, + key_generations::RSA_2048_KEY, + Some(wrapping_key_alias), + ) + .unwrap(); + + // Create the DER-encoded representation of `KeyDescription` schema defined in + // `IKeyMintDevice.aidl` and use it as additional authenticated data. + let aad = create_wrapped_key_additional_auth_data().unwrap(); + + // Build ASN.1 DER-encoded wrapped key material as described in `SecureKeyWrapper` schema. + let wrapped_key_data = build_secure_key_wrapper( + &sec_level, + &secure_key, + &transport_key, + &nonce, + &aad, + &wrapping_key_metadata, + ) + .unwrap(); + + // Unwrap the key. Import wrapped key. + let secured_key_alias = format!("ks_wrapped_aes_key_{}", getuid()); + let secured_key_metadata = key_generations::import_wrapped_key( + &sec_level, + Some(secured_key_alias), + &wrapping_key_metadata, + Some(wrapped_key_data.to_vec()), + ) + .unwrap(); + + perform_sym_key_encrypt_decrypt_op(&sec_level, &secured_key_metadata); +} + +/// Create a wrapped key data with invalid Additional Authenticated Data (AAD) and +/// try to import wrapped key. +/// 1. Create a wrapped key material with invalid AAD to import, as ASN.1 DER-encoded +/// data corresponding to the `SecureKeyWrapper` schema defined in IKeyMintDevice.aidl. +/// 2. Import wrapped key and use it for crypto operations. +/// Test should fail to import the wrapped key with error code `VERIFICATION_FAILED`. +#[test] +fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let mut secure_key = [0; 32]; + rand_bytes(&mut secure_key).unwrap(); + + let mut transport_key = [0; 32]; + rand_bytes(&mut transport_key).unwrap(); + + let mut nonce = [0; 12]; + rand_bytes(&mut nonce).unwrap(); + + // Import wrapping key. + let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); + let wrapping_key_metadata = key_generations::import_wrapping_key( + &sec_level, + key_generations::RSA_2048_KEY, + Some(wrapping_key_alias), + ) + .unwrap(); + + // Use invalid value as the additional authenticated data. + let aad = b"foo"; + + // Build ASN.1 DER-encoded wrapped key material as described in `SecureKeyWrapper` schema. + let wrapped_key_data = build_secure_key_wrapper( + &sec_level, + &secure_key, + &transport_key, + &nonce, + aad, + &wrapping_key_metadata, + ) + .unwrap(); + + // Unwrap the key. Import wrapped key. + let secured_key_alias = format!("ks_wrapped_aes_key_{}", getuid()); + let result = key_generations::map_ks_error(key_generations::import_wrapped_key( + &sec_level, + Some(secured_key_alias), + &wrapping_key_metadata, + Some(wrapped_key_data.to_vec()), + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::VERIFICATION_FAILED), result.unwrap_err()); +} + +/// Import wrapped AES key and use it for crypto operations. Test should import wrapped key and +/// perform crypto operations successfully. +#[test] +fn keystore2_import_wrapped_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_wrapped_key_test_import_1_{}_256", getuid()); + let wrapping_key_alias = format!("ks_wrapping_key_test_import_1_{}_2048", getuid()); + + let wrapping_key_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .purpose(KeyPurpose::WRAP_KEY) + .padding_mode(PaddingMode::RSA_OAEP) + .key_size(2048) + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let key_metadata = key_generations::import_wrapping_key_and_wrapped_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + Some(wrapping_key_alias), + wrapping_key_params, + ) + .expect("Failed to import wrapped key."); + + // Try to perform operations using wrapped key. + perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); +} + +/// Import wrapping-key without specifying KeyPurpose::WRAP_KEY in import key parameters. Try to +/// use this as wrapping-key for importing wrapped-key. Test should fail with an error code +/// `INCOMPATIBLE_PURPOSE` to import wrapped-key using a wrapping-key which doesn't possess +/// `WRAP_KEY` purpose. +#[test] +fn keystore2_import_wrapped_key_fails_with_wrong_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); + let alias = format!("ks_wrapped_key_test_import_2_{}_256", getuid()); + + // In this KeyPurpose::WRAP_KEY is missing. + let wrapping_key_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .digest(Digest::SHA_2_256) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::RSA_OAEP) + .key_size(2048) + .rsa_public_exponent(65537) + .cert_not_before(0) + .cert_not_after(253402300799000); + + let result = + key_generations::map_ks_error(key_generations::import_wrapping_key_and_wrapped_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + Some(wrapping_key_alias), + wrapping_key_params, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Try to import wrapped key whose wrapping key is missing in Android Keystore. +/// Test should fail to import wrapped key with `ResponseCode::KEY_NOT_FOUND`. +#[test] +fn keystore2_import_wrapped_key_fails_with_missing_wrapping_key() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let unwrap_params = authorizations::AuthSetBuilder::new() + .digest(Digest::SHA_2_256) + .padding_mode(PaddingMode::RSA_OAEP); + + let authenticator_spec: &[AuthenticatorSpec] = &[AuthenticatorSpec { + authenticatorType: HardwareAuthenticatorType::NONE, + authenticatorId: 0, + }]; + + let alias = format!("ks_wrapped_key_test_import_3_{}_256", getuid()); + + // Wrapping key with this alias doesn't exist. + let wrapping_key_alias = format!("ks_wrapping_key_not_exist_{}_2048", getuid()); + + let result = key_generations::map_ks_error(sec_level.importWrappedKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias), + blob: Some(key_generations::WRAPPED_KEY.to_vec()), + }, + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(wrapping_key_alias), + blob: None, + }, + None, + &unwrap_params, + authenticator_spec, + )); + + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_key_agreement_tests.rs b/keystore2/tests/keystore2_client_key_agreement_tests.rs new file mode 100644 index 00000000..6b2e3c2d --- /dev/null +++ b/keystore2/tests/keystore2_client_key_agreement_tests.rs @@ -0,0 +1,179 @@ +// Copyright 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. + +use nix::unistd::getuid; + +use openssl::ec::{EcGroup, EcKey}; +use openssl::error::ErrorStack; +use openssl::nid::Nid; +use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use openssl::pkey_ctx::PkeyCtx; +use openssl::x509::X509; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, + SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + KeyMetadata::KeyMetadata, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +/// This macro is used to verify that the key agreement works for the given curve. +macro_rules! test_ec_key_agree { + ( $test_name:ident, $ec_curve:expr ) => { + #[test] + fn $test_name() { + perform_ec_key_agreement($ec_curve); + } + }; +} + +// Get the KeyMint key's public part. +fn get_keymint_public_key(keymint_key: &KeyMetadata) -> Result<PKey<Public>, ErrorStack> { + let cert_bytes = keymint_key.certificate.as_ref().unwrap(); + let cert = X509::from_der(cert_bytes.as_ref()).unwrap(); + cert.public_key() +} + +// Perform local ECDH between the two keys and check the derived secrets are the same. +fn check_agreement( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + keymint_key: &KeyDescriptor, + keymint_pub_key: &PKey<Public>, + local_key: &PKeyRef<Private>, + local_pub_key: &[u8], +) { + let authorizations = authorizations::AuthSetBuilder::new().purpose(KeyPurpose::AGREE_KEY); + let key_agree_op = sec_level.createOperation(keymint_key, &authorizations, false).unwrap(); + assert!(key_agree_op.iOperation.is_some()); + + let op = key_agree_op.iOperation.unwrap(); + let secret = op.finish(Some(local_pub_key), None).unwrap(); + assert!(secret.is_some()); + + let mut ctx = PkeyCtx::new(local_key).unwrap(); + ctx.derive_init().unwrap(); + ctx.derive_set_peer(keymint_pub_key).unwrap(); + let mut peer_secret = vec![]; + ctx.derive_to_vec(&mut peer_secret).unwrap(); + + assert_eq!(secret.unwrap(), peer_secret); +} + +fn ec_curve_to_openrssl_curve_name(ec_curve: &EcCurve) -> Nid { + match *ec_curve { + EcCurve::P_224 => Nid::SECP224R1, + EcCurve::P_256 => Nid::X9_62_PRIME256V1, + EcCurve::P_384 => Nid::SECP384R1, + EcCurve::P_521 => Nid::SECP521R1, + _ => Nid::UNDEF, + } +} + +/// Generate two EC keys with given curve from KeyMint and OpeanSSL. Perform local ECDH between +/// them and verify that the derived secrets are the same. +fn perform_ec_key_agreement(ec_curve: EcCurve) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let openssl_ec_curve = ec_curve_to_openrssl_curve_name(&ec_curve); + + let alias = format!("ks_ec_test_key_agree_{}", getuid()); + let keymint_key = key_generations::generate_ec_agree_key( + &sec_level, + ec_curve, + Digest::SHA_2_256, + Domain::APP, + -1, + Some(alias), + ) + .unwrap(); + + let keymint_pub_key = get_keymint_public_key(&keymint_key).unwrap(); + + let group = EcGroup::from_curve_name(openssl_ec_curve).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let local_key = PKey::from_ec_key(ec_key).unwrap(); + let local_pub_key = local_key.public_key_to_der().unwrap(); + + check_agreement(&sec_level, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); +} + +test_ec_key_agree!(test_ec_p224_key_agreement, EcCurve::P_224); +test_ec_key_agree!(test_ec_p256_key_agreement, EcCurve::P_256); +test_ec_key_agree!(test_ec_p384_key_agreement, EcCurve::P_384); +test_ec_key_agree!(test_ec_p521_key_agreement, EcCurve::P_521); + +/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpeanSSL. +/// Perform local ECDH between them and verify that the derived secrets are the same. +#[test] +fn keystore2_ec_25519_agree_key_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_ec_25519_test_key_agree_{}", getuid()); + let keymint_key = key_generations::generate_ec_agree_key( + &sec_level, + EcCurve::CURVE_25519, + Digest::NONE, + Domain::APP, + -1, + Some(alias), + ) + .unwrap(); + + let keymint_pub_key = get_keymint_public_key(&keymint_key).unwrap(); + + let local_key = PKey::generate_x25519().unwrap(); + let local_pub_key = local_key.public_key_to_der().unwrap(); + + check_agreement(&sec_level, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); +} + +/// Generate two EC keys with different curves and try to perform local ECDH. Since keys are using +/// different curves operation should fail with `ErrorCode:INVALID_ARGUMENT`. +#[test] +fn keystore2_ec_agree_key_with_different_curves_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("ks_test_key_agree_fail{}", getuid()); + let keymint_key = key_generations::generate_ec_agree_key( + &sec_level, + EcCurve::P_256, + Digest::SHA_2_256, + Domain::APP, + -1, + Some(alias), + ) + .unwrap(); + + let local_key = PKey::generate_x25519().unwrap(); + let local_pub_key = local_key.public_key_to_der().unwrap(); + + // If the keys are using different curves KeyMint should fail with + // ErrorCode:INVALID_ARGUMENT. + let authorizations = authorizations::AuthSetBuilder::new().purpose(KeyPurpose::AGREE_KEY); + let key_agree_op = sec_level.createOperation(&keymint_key.key, &authorizations, false).unwrap(); + assert!(key_agree_op.iOperation.is_some()); + + let op = key_agree_op.iOperation.unwrap(); + let result = key_generations::map_ks_error(op.finish(Some(&local_pub_key), None)); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_key_id_domain_tests.rs b/keystore2/tests/keystore2_client_key_id_domain_tests.rs new file mode 100644 index 00000000..09b13784 --- /dev/null +++ b/keystore2/tests/keystore2_client_key_id_domain_tests.rs @@ -0,0 +1,257 @@ +// Copyright 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. + +use nix::unistd::getuid; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::keystore2_client_test_utils::perform_sample_sign_operation; + +/// Try to generate a key with `Domain::KEY_ID`, test should fail with an error code +/// `SYSTEM_ERROR`. `Domain::KEY_ID` is not allowed to use for generating a key. Key id is returned +/// by Keystore2 after a key has been mapped from an alias. +#[test] +fn keystore2_generate_key_with_key_id_domain_expect_sys_error() { + let alias = "ks_gen_key_id_test_key"; + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = key_generations::map_ks_error(key_generations::generate_ec_key( + &sec_level, + Domain::KEY_ID, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + EcCurve::P_256, + Digest::SHA_2_256, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err()); +} + +/// Generate a key and try to load the generated key using KEY_ID as domain. Create an +/// operation using key which is loaded with domain as KEY_ID. Test should create an operation +/// successfully. +#[test] +fn keystore2_find_key_with_key_id_as_domain() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "ks_key_id_test_key"; + + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .expect("Failed to generate a EC key."); + + // Try to load the above generated key with KEY_ID as domain. + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::KEY_ID, + nspace: key_metadata.key.nspace, + alias: Some(alias.to_string()), + blob: None, + }) + .expect("Error in getKeyEntry to load a key with domain KEY_ID."); + + // Verify above found key is same the one generated. + assert_eq!(key_metadata.key, key_entry_response.metadata.key); + assert_eq!(key_metadata.certificate, key_entry_response.metadata.certificate); + assert_eq!(key_metadata.certificateChain, key_entry_response.metadata.certificateChain); + assert_eq!(key_metadata.key.nspace, key_entry_response.metadata.key.nspace); + + // Try to create an operation using above loaded key, operation should be created + // successfully. + let op_response = sec_level + .createOperation( + &key_entry_response.metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ) + .expect("Error in creation of operation."); + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); +} + +/// Generate a key with an alias. Generate another key and bind it to the same alias. +/// Try to create an operation using previously generated key. Creation of an operation should +/// fail because previously generated key material is no longer accessible. Test should successfully +/// create an operation using the rebound key. +#[test] +fn keystore2_key_id_alias_rebind_verify_by_alias() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_key_id_test_alias_rebind_1_{}", getuid()); + + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .expect("Failed to generate a EC key."); + + // Generate a key with same alias as above generated key, so that alias will be rebound + // to this key. + let new_key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .expect("Failed to generate a rebound EC key."); + + assert_ne!(key_metadata.key, new_key_metadata.key); + assert_ne!(key_metadata.certificate, new_key_metadata.certificate); + assert_ne!(key_metadata.key.nspace, new_key_metadata.key.nspace); + + // Try to create an operation using previously generated key_metadata. + // It should fail as previously generated key material is no longer remains valid. + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + + // Try to create an operation using rebound key, operation should be created + // successfully. + let op_response = sec_level + .createOperation( + &new_key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ) + .expect("Error in creation of operation using rebound key."); + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); +} + +/// Generate a key with an alias. Load the generated key with `Domain::KEY_ID`. Generate another +/// key and bind it to the same alias. Try to create an operation using the key loaded with domain +/// `KEY_ID`. Creation of an operation should fail because originally loaded key no longer exists. +/// Test should successfully create an operation using the rebound key. +#[test] +fn keystore2_key_id_alias_rebind_verify_by_key_id() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_key_id_test_alias_rebind_2_{}", getuid()); + + let key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .expect("Failed to generate a EC key."); + + // Load the above generated key with KEY_ID as domain. + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::KEY_ID, + nspace: key_metadata.key.nspace, + alias: Some(alias.to_string()), + blob: None, + }) + .expect("Error in getKeyEntry to load a key with domain KEY_ID."); + + // Verify above found key is same the one generated. + assert_eq!(key_metadata.key, key_entry_response.metadata.key); + assert_eq!(key_metadata.certificate, key_entry_response.metadata.certificate); + assert_eq!(key_metadata.certificateChain, key_entry_response.metadata.certificateChain); + assert_eq!(key_metadata.key.nspace, key_entry_response.metadata.key.nspace); + + // Generate another key with same alias as above generated key, so that alias will be rebound + // to this key. + let new_key_metadata = key_generations::generate_ec_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + EcCurve::P_256, + Digest::SHA_2_256, + ) + .expect("Failed to generate a rebound EC key."); + + // Verify that an alias is rebound to a new key. + assert_eq!(key_metadata.key.alias, new_key_metadata.key.alias); + assert_ne!(key_metadata.key, new_key_metadata.key); + assert_ne!(key_metadata.certificate, new_key_metadata.certificate); + assert_ne!(key_metadata.key.nspace, new_key_metadata.key.nspace); + + // Try to create an operation using previously loaded key_entry_response. + // It should fail as previously generated key material is no longer valid. + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_entry_response.metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); + + // Try to create an operation using rebound key, operation should be created + // successfully. + let op_response = sec_level + .createOperation( + &new_key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ) + .expect("Error in creation of operation using rebound key."); + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); +} diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs new file mode 100644 index 00000000..3b656c3d --- /dev/null +++ b/keystore2/tests/keystore2_client_list_entries_tests.rs @@ -0,0 +1,253 @@ +// Copyright 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. + +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; +use std::collections::HashSet; +use std::fmt::Write; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, + KeyPermission::KeyPermission, ResponseCode::ResponseCode, +}; + +use crate::keystore2_client_test_utils::delete_app_key; +use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as}; + +/// Try to find a key with given key parameters using `listEntries` API. +fn key_alias_exists( + keystore2: &binder::Strong<dyn IKeystoreService>, + domain: Domain, + nspace: i64, + alias: String, +) -> bool { + let key_descriptors = keystore2.listEntries(domain, nspace).unwrap(); + let alias_count = key_descriptors + .into_iter() + .map(|key| key.alias.unwrap()) + .filter(|key_alias| *key_alias == alias) + .count(); + + alias_count != 0 +} + +/// List key entries with domain as SELINUX and APP. +/// 1. Generate a key with domain as SELINUX and find this key entry in list of keys retrieved from +/// `listEntries` with domain SELINUX. Test should be able find this key entry successfully. +/// 2. Grant above generated Key to a user. +/// 3. In a user context, generate a new key with domain as APP. Try to list the key entries with +/// domain APP. Test should find only one key entry that should be the key generated in user +/// context. GRANT keys shouldn't be part of this list. +#[test] +fn keystore2_list_entries_success() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 91; + const APPLICATION_ID: u32 = 10006; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = format!("list_entries_grant_key1_{}", getuid()); + + // Make sure there is no key exist with this `alias` in `SELINUX` domain and + // `SELINUX_SHELL_NAMESPACE` namespace. + if key_alias_exists( + &keystore2, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + alias.to_string(), + ) { + keystore2 + .deleteKey(&KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias.to_string()), + blob: None, + }) + .unwrap(); + } + + // Generate a key with above defined `alias`. + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + None, + ) + .unwrap(); + + // Verify that above generated key entry is listed with domain SELINUX and + // namespace SELINUX_SHELL_NAMESPACE + assert!(key_alias_exists( + &keystore2, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + alias, + )); + + // Grant a key with GET_INFO permission. + let access_vector = KeyPermission::GET_INFO.0; + keystore2 + .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector) + .unwrap(); + }) + }; + + // In user context validate list of key entries associated with it. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + let sec_level = + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("list_entries_success_key{}", getuid()); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + None, + ) + .unwrap(); + + // Make sure there is only one key entry exist and that should be the same key + // generated in this user context. Granted key shouldn't be included in this list. + let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + assert_eq!(1, key_descriptors.len()); + + let key = key_descriptors.get(0).unwrap(); + assert_eq!(key.alias, Some(alias)); + assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap()); + assert_eq!(key.domain, Domain::APP); + + keystore2.deleteKey(&key_metadata.key).unwrap(); + + let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + assert_eq!(0, key_descriptors.len()); + }, + ) + }; +} + +/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses +/// `GET_INFO` permission for specified namespace. Test should fail to list key entries with error +/// response code `PERMISSION_DENIED`. +#[test] +fn keystore2_list_entries_fails_perm_denied() { + let auid = 91 * AID_USER_OFFSET + 10001; + let agid = 91 * AID_USER_OFFSET + 10001; + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + unsafe { + run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error( + keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE), + ); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }) + }; +} + +/// Try to list key entries with domain BLOB. Test should fail with error repose code +/// `INVALID_ARGUMENT`. +#[test] +fn keystore2_list_entries_fails_invalid_arg() { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error( + keystore2.listEntries(Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE), + ); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Import large number of Keystore entries with long aliases and try to list aliases +/// of all the entries in the keystore. +#[test] +fn keystore2_list_entries_with_long_aliases_success() { + static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 92; + const APPLICATION_ID: u32 = 10002; + static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static CLIENT_GID: u32 = CLIENT_UID; + + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Make sure there are no keystore entries exist before adding new entries. + let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + if !key_descriptors.is_empty() { + key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { + delete_app_key(&keystore2, &alias).unwrap(); + }); + } + + let mut imported_key_aliases = HashSet::new(); + + // Import 100 keys with aliases of length 6000. + for count in 1..101 { + let mut alias = String::new(); + write!(alias, "{}_{}", "X".repeat(6000), count).unwrap(); + imported_key_aliases.insert(alias.clone()); + + let result = + key_generations::import_aes_key(&sec_level, Domain::APP, -1, Some(alias)); + assert!(result.is_ok()); + } + + // b/222287335 Limiting Keystore `listEntries` API to return subset of the Keystore + // entries to avoid running out of binder buffer space. + // To verify that all the imported key aliases are present in Keystore, + // - get the list of entries from Keystore + // - check whether the retrieved key entries list is a subset of imported key aliases + // - delete this subset of keystore entries from Keystore as well as from imported + // list of key aliases + // - continue above steps till it cleanup all the imported keystore entries. + while !imported_key_aliases.is_empty() { + let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + + // Check retrieved key entries list is a subset of imported keys list. + assert!(key_descriptors + .iter() + .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); + + // Delete the listed key entries from Keystore as well as from imported keys list. + key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { + delete_app_key(&keystore2, &alias).unwrap(); + assert!(imported_key_aliases.remove(&alias)); + }); + } + + assert!(imported_key_aliases.is_empty()); + }) + }; +} diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs new file mode 100644 index 00000000..19175ddd --- /dev/null +++ b/keystore2/tests/keystore2_client_operation_tests.rs @@ -0,0 +1,449 @@ +// Copyright 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. + +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; +use std::thread; +use std::thread::JoinHandle; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + CreateOperationResponse::CreateOperationResponse, Domain::Domain, + IKeystoreOperation::IKeystoreOperation, ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, +}; + +use crate::keystore2_client_test_utils::{ + create_signing_operation, execute_op_run_as_child, perform_sample_sign_operation, + BarrierReached, ForcedOp, TestOutcome, +}; + +/// Create `max_ops` number child processes with the given context and perform an operation under each +/// child process. +pub fn create_operations( + target_ctx: &'static str, + forced_op: ForcedOp, + max_ops: i32, +) -> Vec<run_as::ChildHandle<TestOutcome, BarrierReached>> { + let alias = format!("ks_op_test_key_{}", getuid()); + let base_gid = 99 * AID_USER_OFFSET + 10001; + let base_uid = 99 * AID_USER_OFFSET + 10001; + (0..max_ops) + .map(|i| { + execute_op_run_as_child( + target_ctx, + Domain::APP, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + Uid::from_raw(base_uid + (i as u32)), + Gid::from_raw(base_gid + (i as u32)), + forced_op, + ) + }) + .collect() +} + +/// Executes an operation in a thread. Expect an `OPERATION_BUSY` error in case of operation +/// failure. Returns True if `OPERATION_BUSY` error is encountered otherwise returns false. +fn perform_op_busy_in_thread(op: binder::Strong<dyn IKeystoreOperation>) -> JoinHandle<bool> { + thread::spawn(move || { + for _n in 1..1000 { + match key_generations::map_ks_error(op.update(b"my message")) { + Ok(_) => continue, + Err(e) => { + assert_eq!(Error::Rc(ResponseCode::OPERATION_BUSY), e); + return true; + } + } + } + let sig = op.finish(None, None).unwrap(); + assert!(sig.is_some()); + false + }) +} + +/// This test verifies that backend service throws BACKEND_BUSY error when all +/// operations slots are full. This test creates operations in child processes and +/// collects the status of operations performed in each child proc and determines +/// whether any child proc exited with error status. +#[test] +fn keystore2_backend_busy_test() { + const MAX_OPS: i32 = 100; + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS); + + // Wait until all child procs notifies us to continue, + // so that there are definitely enough operations outstanding to trigger a BACKEND_BUSY. + for ch in child_handles.iter_mut() { + ch.recv(); + } + // Notify each child to resume and finish. + for ch in child_handles.iter_mut() { + ch.send(&BarrierReached {}); + } + + // Collect the result and validate whether backend busy has occurred. + let mut busy_count = 0; + for ch in child_handles.into_iter() { + if ch.get_result() == TestOutcome::BackendBusy { + busy_count += 1; + } + } + assert!(busy_count > 0) +} + +/// This test confirms that forced operation is having high pruning power. +/// 1. Initially create regular operations such that there are enough operations outstanding +/// to trigger BACKEND_BUSY. +/// 2. Then, create a forced operation. System should be able to prune one of the regular +/// operations and create a slot for forced operation successfully. +#[test] +fn keystore2_forced_op_after_backendbusy_test() { + const MAX_OPS: i32 = 100; + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + // Create regular operations. + let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS); + + // Wait until all child procs notifies us to continue, so that there are enough + // operations outstanding to trigger a BACKEND_BUSY. + for ch in child_handles.iter_mut() { + ch.recv(); + } + + // Create a forced operation. + let auid = 99 * AID_USER_OFFSET + 10604; + let agid = 99 * AID_USER_OFFSET + 10604; + unsafe { + run_as::run_as( + key_generations::TARGET_VOLD_CTX, + Uid::from_raw(auid), + Gid::from_raw(agid), + move || { + let alias = format!("ks_prune_forced_op_key_{}", getuid()); + + // To make room for this forced op, system should be able to prune one of the + // above created regular operations and create a slot for this forced operation + // successfully. + create_signing_operation( + ForcedOp(true), + KeyPurpose::SIGN, + Digest::SHA_2_256, + Domain::SELINUX, + 100, + Some(alias), + ) + .expect("Client failed to create forced operation after BACKEND_BUSY state."); + }, + ); + }; + + // Notify each child to resume and finish. + for ch in child_handles.iter_mut() { + ch.send(&BarrierReached {}); + } + + // Collect the results of above created regular operations. + let mut pruned_count = 0; + let mut busy_count = 0; + let mut _other_err = 0; + for ch in child_handles.into_iter() { + match ch.get_result() { + TestOutcome::BackendBusy => { + busy_count += 1; + } + TestOutcome::InvalidHandle => { + pruned_count += 1; + } + _ => { + _other_err += 1; + } + } + } + // Verify that there should be at least one backend busy has occurred while creating + // above regular operations. + assert!(busy_count > 0); + + // Verify that there should be at least one pruned operation which should have failed while + // performing operation. + assert!(pruned_count > 0); +} + +/// This test confirms that forced operations can't be pruned. +/// 1. Creates an initial forced operation and tries to complete the operation after BACKEND_BUSY +/// error is triggered. +/// 2. Create MAX_OPS number of forced operations so that definitely enough number of operations +/// outstanding to trigger a BACKEND_BUSY. +/// 3. Try to use initially created forced operation (in step #1) and able to perform the +/// operation successfully. This confirms that none of the later forced operations evicted the +/// initial forced operation. +#[test] +fn keystore2_max_forced_ops_test() { + const MAX_OPS: i32 = 100; + let auid = 99 * AID_USER_OFFSET + 10205; + let agid = 99 * AID_USER_OFFSET + 10205; + + // Create initial forced operation in a child process + // and wait for the parent to notify to perform operation. + let alias = format!("ks_forced_op_key_{}", getuid()); + let mut first_op_handle = execute_op_run_as_child( + key_generations::TARGET_SU_CTX, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + Uid::from_raw(auid), + Gid::from_raw(agid), + ForcedOp(true), + ); + + // Wait until above child proc notifies us to continue, so that there is definitely a forced + // operation outstanding to perform a operation. + first_op_handle.recv(); + + // Create MAX_OPS number of forced operations. + let mut child_handles = + create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS); + + // Wait until all child procs notifies us to continue, so that there are enough operations + // outstanding to trigger a BACKEND_BUSY. + for ch in child_handles.iter_mut() { + ch.recv(); + } + + // Notify initial created forced operation to continue performing the operations. + first_op_handle.send(&BarrierReached {}); + + // Collect initially created forced operation result and is expected to complete operation + // successfully. + let first_op_result = first_op_handle.get_result(); + assert_eq!(first_op_result, TestOutcome::Ok); + + // Notify each child to resume and finish. + for ch in child_handles.iter_mut() { + ch.send(&BarrierReached {}); + } + + // Collect the result and validate whether backend busy has occurred with MAX_OPS number + // of forced operations. + let busy_count = child_handles + .into_iter() + .map(|ch| ch.get_result()) + .filter(|r| *r == TestOutcome::BackendBusy) + .count(); + assert!(busy_count > 0); +} + +/// This test will verify the use case with the same owner(UID) requesting `n` number of operations. +/// This test confirms that when all operation slots are full and a new operation is requested, +/// an operation which is least recently used and lived longest will be pruned to make a room +/// for a new operation. Pruning strategy should prevent the operations of the other owners(UID) +/// from being pruned. +/// +/// 1. Create an operation in a child process with `untrusted_app` context and wait for parent +/// notification to complete the operation. +/// 2. Let parent process create `n` number of operations such that there are enough operations +/// outstanding to trigger cannibalizing their own sibling operations. +/// 3. Sequentially try to use above created `n` number of operations and also add a new operation, +/// so that it should trigger cannibalizing one of their own sibling operations. +/// 3.1 While trying to use these pruned operations an `INVALID_OPERATION_HANDLE` error is +/// expected as they are already pruned. +/// 4. Notify the child process to resume and complete the operation. It is expected to complete the +/// operation successfully. +/// 5. Try to use the latest operation of parent. It is expected to complete the operation +/// successfully. +#[test] +fn keystore2_ops_prune_test() { + const MAX_OPS: usize = 40; // This should be at least 32 with sec_level TEE. + + static TARGET_CTX: &str = "u:r:untrusted_app:s0"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10601; + + let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + + // Create an operation in an untrusted_app context. Wait until the parent notifies to continue. + // Once the parent notifies, this operation is expected to be completed successfully. + let alias = format!("ks_reg_op_key_{}", getuid()); + let mut child_handle = execute_op_run_as_child( + TARGET_CTX, + Domain::APP, + -1, + Some(alias), + Uid::from_raw(uid), + Gid::from_raw(gid), + ForcedOp(false), + ); + + // Wait until child process notifies us to continue, so that an operation from child process is + // outstanding to complete the operation. + child_handle.recv(); + + // Generate a key to use in below operations. + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_prune_op_test_key_{}", getuid()); + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + None, + ) + .unwrap(); + + // Create multiple operations in this process to trigger cannibalizing sibling operations. + let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS) + .map(|_| { + sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ) + }) + .collect(); + + // Sequentially try to use operation handles created above and also add a new operation. + for vec_index in 0..MAX_OPS { + match &ops[vec_index] { + Ok(CreateOperationResponse { iOperation: Some(op), .. }) => { + // Older operation handle is pruned, if we try to use that an error is expected. + assert_eq!( + Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)), + key_generations::map_ks_error(op.update(b"my message")) + ); + } + _ => panic!("Operation should have created successfully."), + } + + // Create a new operation, it should trigger to cannibalize one of their own sibling + // operations. + ops.push( + sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256), + false, + ), + ); + } + + // Notify child process to continue the operation. + child_handle.send(&BarrierReached {}); + assert!((child_handle.get_result() == TestOutcome::Ok), "Failed to perform an operation"); + + // Try to use the latest operation created by parent, should be able to use it successfully. + match ops.last() { + Some(Ok(CreateOperationResponse { iOperation: Some(op), .. })) => { + assert_eq!(Ok(()), key_generations::map_ks_error(perform_sample_sign_operation(op))); + } + _ => panic!("Operation should have created successfully."), + } +} + +/// Try to create forced operations with various contexts - +/// - untrusted_app +/// - system_server +/// - priv_app +/// `PERMISSION_DENIED` error response is expected. +#[test] +fn keystore2_forced_op_perm_denied_test() { + static TARGET_CTXS: &[&str] = + &["u:r:untrusted_app:s0", "u:r:system_server:s0", "u:r:priv_app:s0"]; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10601; + + let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + + for context in TARGET_CTXS.iter() { + unsafe { + run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || { + let alias = format!("ks_app_forced_op_test_key_{}", getuid()); + let result = key_generations::map_ks_error(create_signing_operation( + ForcedOp(true), + KeyPurpose::SIGN, + Digest::SHA_2_256, + Domain::APP, + -1, + Some(alias), + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }); + } + } +} + +/// Try to create a forced operation with `vold` context. +/// Should be able to create forced operation with `vold` context successfully. +#[test] +fn keystore2_forced_op_success_test() { + static TARGET_CTX: &str = "u:r:vold:s0"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10601; + + let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + + unsafe { + run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || { + let alias = format!("ks_vold_forced_op_key_{}", getuid()); + create_signing_operation( + ForcedOp(true), + KeyPurpose::SIGN, + Digest::SHA_2_256, + Domain::SELINUX, + key_generations::SELINUX_VOLD_NAMESPACE, + Some(alias), + ) + .expect("Client with vold context failed to create forced operation."); + }); + } +} + +/// Create an operation and try to use this operation handle in multiple threads to perform +/// operations. Test should fail to perform an operation with an error response `OPERATION_BUSY` +/// when multiple threads try to access the operation handle at same time. +#[test] +fn keystore2_op_fails_operation_busy() { + let op_response = create_signing_operation( + ForcedOp(false), + KeyPurpose::SIGN, + Digest::SHA_2_256, + Domain::APP, + -1, + Some("op_busy_alias_test_key".to_string()), + ) + .unwrap(); + + let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap(); + + let th_handle_1 = perform_op_busy_in_thread(op.clone()); + let th_handle_2 = perform_op_busy_in_thread(op); + + let result1 = th_handle_1.join().unwrap(); + let result2 = th_handle_2.join().unwrap(); + + assert!(result1 || result2); +} diff --git a/keystore2/tests/keystore2_client_rsa_key_tests.rs b/keystore2/tests/keystore2_client_rsa_key_tests.rs new file mode 100644 index 00000000..ad176a48 --- /dev/null +++ b/keystore2/tests/keystore2_client_rsa_key_tests.rs @@ -0,0 +1,1895 @@ +// Copyright 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. + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + CreateOperationResponse::CreateOperationResponse, Domain::Domain, + IKeystoreSecurityLevel::IKeystoreSecurityLevel, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use crate::keystore2_client_test_utils::{delete_app_key, perform_sample_sign_operation, ForcedOp}; + +/// This macro is used for creating signing key operation tests using digests and paddings +/// for various key sizes. +macro_rules! test_rsa_sign_key_op { + ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr ) => { + #[test] + fn $test_name() { + perform_rsa_sign_key_op_success($digest, $key_size, stringify!($test_name), $padding); + } + }; + + ( $test_name:ident, $digest:expr, $padding:expr ) => { + #[test] + fn $test_name() { + perform_rsa_sign_key_op_failure($digest, stringify!($test_name), $padding); + } + }; +} + +/// This macro is used for creating encrypt/decrypt key operation tests using digests, mgf-digests +/// and paddings for various key sizes. +macro_rules! test_rsa_encrypt_key_op { + ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr ) => { + #[test] + fn $test_name() { + create_rsa_encrypt_decrypt_key_op_success( + $digest, + $key_size, + stringify!($test_name), + $padding, + None, + ); + } + }; + + ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr, $mgf_digest:expr ) => { + #[test] + fn $test_name() { + create_rsa_encrypt_decrypt_key_op_success( + $digest, + $key_size, + stringify!($test_name), + $padding, + $mgf_digest, + ); + } + }; +} + +/// Generate a RSA key and create an operation using the generated key. +fn create_rsa_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + domain: Domain, + nspace: i64, + alias: Option<String>, + key_params: &key_generations::KeyParams, + op_purpose: KeyPurpose, + forced_op: ForcedOp, +) -> binder::Result<CreateOperationResponse> { + let key_metadata = + key_generations::generate_rsa_key(sec_level, domain, nspace, alias, key_params, None)?; + + let mut op_params = authorizations::AuthSetBuilder::new().purpose(op_purpose); + + if let Some(value) = key_params.digest { + op_params = op_params.digest(value) + } + if let Some(value) = key_params.padding { + op_params = op_params.padding_mode(value); + } + if let Some(value) = key_params.mgf_digest { + op_params = op_params.mgf_digest(value); + } + if let Some(value) = key_params.block_mode { + op_params = op_params.block_mode(value) + } + + sec_level.createOperation(&key_metadata.key, &op_params, forced_op.0) +} + +/// Generate RSA signing key with given parameters and perform signing operation. +fn perform_rsa_sign_key_op_success( + digest: Digest, + key_size: i32, + alias: &str, + padding: PaddingMode, +) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let op_response = create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(padding), + digest: Some(digest), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::SIGN, + ForcedOp(false), + ) + .expect("Failed to create an operation."); + + assert!(op_response.iOperation.is_some()); + assert_eq!( + Ok(()), + key_generations::map_ks_error(perform_sample_sign_operation( + &op_response.iOperation.unwrap() + )) + ); + + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate RSA signing key with given parameters and try to perform signing operation. +/// Error `INCOMPATIBLE_DIGEST | UNKNOWN_ERROR` is expected while creating an opearation. +fn perform_rsa_sign_key_op_failure(digest: Digest, alias: &str, padding: PaddingMode) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(padding), + digest: Some(digest), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::SIGN, + ForcedOp(false), + )); + assert!(result.is_err()); + + let e = result.unwrap_err(); + assert!( + e == Error::Km(ErrorCode::UNKNOWN_ERROR) || e == Error::Km(ErrorCode::INCOMPATIBLE_DIGEST) + ); + + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate RSA encrypt/decrypt key with given parameters and perform decrypt operation. +fn create_rsa_encrypt_decrypt_key_op_success( + digest: Option<Digest>, + key_size: i32, + alias: &str, + padding: PaddingMode, + mgf_digest: Option<Digest>, +) { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let result = create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(padding), + digest, + mgf_digest, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + ); + + assert!(result.is_ok()); + + delete_app_key(&keystore2, alias).unwrap(); +} + +// Below macros generate tests for generating RSA signing keys with - +// Padding mode: RSA_PKCS1_1_5_SIGN +// Digest modes: `NONE, MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// and create operations with generated keys. Tests should create operations successfully. +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_none_2048, + Digest::NONE, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_md5_2048, + Digest::MD5, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha1_2048, + Digest::SHA1, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha224_2048, + Digest::SHA_2_224, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha256_2048, + Digest::SHA_2_256, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha384_2048, + Digest::SHA_2_384, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha512_2048, + Digest::SHA_2_512, + 2048, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_none_3072, + Digest::NONE, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_md5_3072, + Digest::MD5, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha1_3072, + Digest::SHA1, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha224_3072, + Digest::SHA_2_224, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha256_3072, + Digest::SHA_2_256, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha384_3072, + Digest::SHA_2_384, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha512_3072, + Digest::SHA_2_512, + 3072, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_none_4096, + Digest::NONE, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_md5_4096, + Digest::MD5, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha1_4096, + Digest::SHA1, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha224_4096, + Digest::SHA_2_224, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha256_4096, + Digest::SHA_2_256, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha384_4096, + Digest::SHA_2_384, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); +test_rsa_sign_key_op!( + sign_key_pkcs1_1_5_sha512_4096, + Digest::SHA_2_512, + 4096, + PaddingMode::RSA_PKCS1_1_5_SIGN +); + +// Below macros generate tests for generating RSA signing keys with - +// Padding mode: RSA_PSS +// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// and create operations with generated keys. Tests should create operations +// successfully. +test_rsa_sign_key_op!(sign_key_pss_md5_2048, Digest::MD5, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha1_2048, Digest::SHA1, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha224_2048, Digest::SHA_2_224, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha256_2048, Digest::SHA_2_256, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha384_2048, Digest::SHA_2_384, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha512_2048, Digest::SHA_2_512, 2048, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_md5_3072, Digest::MD5, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha1_3072, Digest::SHA1, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha224_3072, Digest::SHA_2_224, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha256_3072, Digest::SHA_2_256, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha384_3072, Digest::SHA_2_384, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha512_3072, Digest::SHA_2_512, 3072, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_md5_4096, Digest::MD5, 4096, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha1_4096, Digest::SHA1, 4096, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha224_4096, Digest::SHA_2_224, 4096, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha256_4096, Digest::SHA_2_256, 4096, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha384_4096, Digest::SHA_2_384, 4096, PaddingMode::RSA_PSS); +test_rsa_sign_key_op!(sign_key_pss_sha512_4096, Digest::SHA_2_512, 4096, PaddingMode::RSA_PSS); + +// Below macros generate tests for generating RSA signing keys with - +// Padding mode: `NONE` +// Digest mode `NONE` +// and try to create operations with generated keys. Tests should create operations +// successfully. +test_rsa_sign_key_op!(sign_key_none_none_2048, Digest::NONE, 2048, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_none_3072, Digest::NONE, 3072, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_none_4096, Digest::NONE, 4096, PaddingMode::NONE); + +// Below macros generate tests for generating RSA signing keys with - +// Padding mode: `NONE` +// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// and create operations with generated keys. Tests should fail to create operations with +// an error code `UNKNOWN_ERROR | INCOMPATIBLE_DIGEST`. +test_rsa_sign_key_op!(sign_key_none_md5_2048, Digest::MD5, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_sha1_2048, Digest::SHA1, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_sha224_2048, Digest::SHA_2_224, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_sha256_2048, Digest::SHA_2_256, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_sha384_2048, Digest::SHA_2_384, PaddingMode::NONE); +test_rsa_sign_key_op!(sign_key_none_sha512_2048, Digest::SHA_2_512, PaddingMode::NONE); + +// Below macros generate tests for generating RSA encryption keys with various digest mode +// and padding mode combinations. +// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// Padding modes: `NONE, RSA_PKCS1_1_5_ENCRYPT` +// and try to create operations using generated keys, tests should create operations successfully. +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_none_2048, + Some(Digest::NONE), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_md5_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha1_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha224_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha256_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha384_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha512_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_none_3072, + Some(Digest::NONE), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_md5_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha1_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha224_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha256_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha384_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha512_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_none_4096, + Some(Digest::NONE), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_md5_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha1_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha224_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha256_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha384_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_sha512_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT +); +test_rsa_encrypt_key_op!(encrypt_key_none_none_2048, Some(Digest::NONE), 2048, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_md5_2048, Some(Digest::MD5), 2048, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_sha1_2048, Some(Digest::SHA1), 2048, PaddingMode::NONE); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha224_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha256_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha384_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha512_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!(encrypt_key_none_none_3072, Some(Digest::NONE), 3072, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_md5_3072, Some(Digest::MD5), 3072, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_sha1_3072, Some(Digest::SHA1), 3072, PaddingMode::NONE); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha224_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha256_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha384_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha512_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!(encrypt_key_none_none_4096, Some(Digest::NONE), 4096, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_md5_4096, Some(Digest::MD5), 4096, PaddingMode::NONE); +test_rsa_encrypt_key_op!(encrypt_key_none_sha1_4096, Some(Digest::SHA1), 4096, PaddingMode::NONE); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha224_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha256_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha384_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::NONE +); +test_rsa_encrypt_key_op!( + encrypt_key_none_sha512_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::NONE +); + +// Below macros generate tests for generating RSA keys with - +// Padding Mode: `RSA_OAEP` +// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// mgf-digests: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// and create a decrypt operations using generated keys. Tests should create operations +// successfully. +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_md5_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha1_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha224_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha256_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha384_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha512_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_md5_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha1_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha224_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha256_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha384_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha512_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_md5_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha1_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha224_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha256_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha384_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha512_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_md5_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha1_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha224_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha256_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha384_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha512_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_md5_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha1_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha224_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha256_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha384_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha512_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_md5_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha1_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha224_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha256_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha384_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha512_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_md5_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha1_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha224_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha256_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha384_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha512_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_md5_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha1_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha224_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha256_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha384_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha512_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_md5_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha1_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha224_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha256_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha384_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha512_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_md5_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha1_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha224_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha256_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha384_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha512_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_md5_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha1_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha224_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha256_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha384_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha512_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_md5_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha1_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha224_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha256_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha384_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha512_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_md5_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha1_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha224_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha256_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha384_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_sha512_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_md5_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha1_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha224_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha256_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha384_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_sha512_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_md5_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha1_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha224_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha256_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha384_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_sha512_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_md5_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha1_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha224_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha256_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha384_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_sha512_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_md5_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha1_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha224_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha256_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha384_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_sha512_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_md5_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::MD5) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha1_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA1) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha224_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_224) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha256_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_256) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha384_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_384) +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_sha512_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + Some(Digest::SHA_2_512) +); + +// Below macros generate tests for generating RSA keys with - +// Padding mode: `RSA_OAEP` +// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512` +// and create a decrypt operations using generated keys. Tests should create operations +// successfully. +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_no_mgf_2048, + Some(Digest::MD5), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_no_mgf_2048, + Some(Digest::SHA1), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_no_mgf_2048, + Some(Digest::SHA_2_224), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_no_mgf_2048, + Some(Digest::SHA_2_256), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_no_mgf_2048, + Some(Digest::SHA_2_384), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_no_mgf_2048, + Some(Digest::SHA_2_512), + 2048, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_no_mgf_3072, + Some(Digest::MD5), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_no_mgf_3072, + Some(Digest::SHA1), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_no_mgf_3072, + Some(Digest::SHA_2_224), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_no_mgf_3072, + Some(Digest::SHA_2_256), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_no_mgf_3072, + Some(Digest::SHA_2_384), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_no_mgf_3072, + Some(Digest::SHA_2_512), + 3072, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_md5_no_mgf_4096, + Some(Digest::MD5), + 4096, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha1_no_mgf_4096, + Some(Digest::SHA1), + 4096, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha224_no_mgf_4096, + Some(Digest::SHA_2_224), + 4096, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha256_no_mgf_4096, + Some(Digest::SHA_2_256), + 4096, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha384_no_mgf_4096, + Some(Digest::SHA_2_384), + 4096, + PaddingMode::RSA_OAEP, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_oaep_sha512_no_mgf_4096, + Some(Digest::SHA_2_512), + 4096, + PaddingMode::RSA_OAEP, + None +); + +// Below macros generate tests for generating RSA encryption keys with only padding modes. +// Padding modes: `NONE, RSA_PKCS1_1_5_ENCRYPT` +// and try to create operations using generated keys, tests should create operations +// successfully. +test_rsa_encrypt_key_op!(encrypt_key_none_pad_2048, None, 2048, PaddingMode::NONE, None); +test_rsa_encrypt_key_op!(encrypt_key_none_pad_3072, None, 3072, PaddingMode::NONE, None); +test_rsa_encrypt_key_op!(encrypt_key_none_pad_4096, None, 4096, PaddingMode::NONE, None); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_pad_2048, + None, + 2048, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_pad_3072, + None, + 3072, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT, + None +); +test_rsa_encrypt_key_op!( + encrypt_key_pkcs1_1_5_pad_4096, + None, + 4096, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT, + None +); + +/// Generate RSA signing key with - +/// Padding mode: RSA_PSS +/// Digest mode: `NONE`. +/// Try to create an operation with this generated key. Test should fail to create an operation with +/// `INCOMPATIBLE_DIGEST` error code. +#[test] +fn keystore2_rsa_generate_signing_key_padding_pss_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_pss_none_key_op_test"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PSS), + digest: Some(Digest::NONE), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::SIGN, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err()); +} + +/// Generate RSA encryption key with - +/// Digest mode: `NONE` +/// Padding mode: `RSA_OAEP` +/// Try to create an operation using generated key. Test should fail to create an operation +/// with an error code `INCOMPATIBLE_DIGEST`. +#[test] +fn keystore2_rsa_generate_key_with_oaep_padding_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_oaep_padding_fail_test"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(PaddingMode::RSA_OAEP), + digest: Some(Digest::NONE), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err()); +} + +/// Generate RSA keys without padding and digest modes. Try to create decrypt operation without +/// digest and padding. Creation of an operation should fail with an error code +/// `UNSUPPORTED_PADDING_MODE`. +#[test] +fn keystore2_rsa_generate_keys() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_unsupport_padding_test"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: None, + digest: None, + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err()); +} + +/// Generate a RSA encryption key. Try to create a signing operation with it, an error +/// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support sign operation. +#[test] +fn keystore2_rsa_encrypt_key_op_invalid_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_test_key_1"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::SIGN, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate a RSA signing key. Try to create a decrypt operation with it, an error +/// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support decrypt operation. +#[test] +fn keystore2_rsa_sign_key_op_invalid_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_test_key_2"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate a RSA key with SIGN and AGREE_KEY purposes. Try to perform an operation using the +/// generated key, an error `UNSUPPORTED_PURPOSE` is expected as RSA doesn't support AGREE_KEY. +#[test] +fn keystore2_rsa_key_unsupported_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_test_3"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::AGREE_KEY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::AGREE_KEY, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PURPOSE), result.unwrap_err()); +} + +/// Generate a RSA encrypt key with padding mode supported for signing. Try to create an operation +/// using generated key, an error `UNSUPPORTED_PADDING_MODE` is expected with unsupported padding +/// mode. +#[test] +fn keystore2_rsa_encrypt_key_unsupported_padding() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let paddings = [PaddingMode::RSA_PKCS1_1_5_SIGN, PaddingMode::RSA_PSS]; + + for padding in paddings { + let alias = format!("ks_rsa_encrypt_key_unsupported_pad_test{}", padding.0); + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(padding), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err()); + } +} + +/// Generate a RSA signing key with padding mode supported for encryption. Try to create an +/// operation using generated key, an error `UNSUPPORTED_PADDING_MODE` is expected with +/// unsupported padding mode. +#[test] +fn keystore2_rsa_signing_key_unsupported_padding() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let paddings = [PaddingMode::RSA_PKCS1_1_5_ENCRYPT, PaddingMode::RSA_OAEP]; + + for padding in paddings { + let alias = format!("ks_rsa_sign_key_unsupported_pad_test_4_{}", padding.0); + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(padding), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::SIGN, + ForcedOp(false), + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err()); + } +} + +/// Generate a RSA encryption key. Try to perform encrypt operation using the generated +/// key, an error `UNSUPPORTED_PURPOSE` is expected as encrypt operation is not supported +/// with RSA key. +#[test] +fn keystore2_rsa_key_unsupported_op() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_test_5"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::ENCRYPT, + ForcedOp(false), + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PURPOSE), result.unwrap_err()); +} + +/// Generate a RSA key with encrypt, sign and verify purpose. Try to perform decrypt operation +/// using the generated key, an error `INCOMPATIBLE_PURPOSE` is expected as the key is not +/// generated with decrypt purpose. +#[test] +fn keystore2_rsa_key_missing_purpose() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_test_6"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); +} + +/// Generate RSA encryption keys with OAEP padding mode and without digest mode. Try to create an +/// operation with generated key, unsupported digest error is expected. +#[test] +fn keystore2_rsa_gen_keys_with_oaep_paddings_without_digest() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_padding_fail"; + let result = key_generations::map_ks_error(create_rsa_key_and_operation( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT], + padding: Some(PaddingMode::RSA_OAEP), + digest: None, + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + KeyPurpose::DECRYPT, + ForcedOp(false), + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); +} + +/// Generate RSA keys with unsupported key size, an error `UNSUPPORTED_KEY_SIZE` is expected. +#[test] +fn keystore2_rsa_gen_keys_unsupported_size() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "ks_rsa_key_padding_fail"; + let result = key_generations::map_ks_error(key_generations::generate_rsa_key( + &sec_level, + Domain::APP, + -1, + Some(alias.to_string()), + &key_generations::KeyParams { + key_size: 5120, + purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: None, + }, + None, + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs new file mode 100644 index 00000000..58e6b7d3 --- /dev/null +++ b/keystore2/tests/keystore2_client_test_utils.rs @@ -0,0 +1,419 @@ +// Copyright 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. + +use nix::unistd::{Gid, Uid}; +use serde::{Deserialize, Serialize}; + +use openssl::encrypt::Encrypter; +use openssl::error::ErrorStack; +use openssl::hash::MessageDigest; +use openssl::pkey::PKey; +use openssl::pkey::Public; +use openssl::rsa::Padding; +use openssl::sign::Verifier; +use openssl::x509::X509; + +use binder::wait_for_interface; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + BlockMode::BlockMode, Digest::Digest, ErrorCode::ErrorCode, + KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + SecurityLevel::SecurityLevel, Tag::Tag, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + CreateOperationResponse::CreateOperationResponse, Domain::Domain, + IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::IKeystoreSecurityLevel, + IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, + KeyParameters::KeyParameters, ResponseCode::ResponseCode, +}; + +use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, +}; + +/// This enum is used to communicate between parent and child processes. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum TestOutcome { + Ok, + BackendBusy, + InvalidHandle, + OtherErr, +} + +/// This is used to notify the child or parent process that the expected state is reched. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BarrierReached; + +/// Forced operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ForcedOp(pub bool); + +/// Sample plain text input for encrypt operation. +pub const SAMPLE_PLAIN_TEXT: &[u8] = b"my message 11111"; + +pub const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native"; +pub const APP_ATTEST_KEY_FEATURE: &str = "android.hardware.keystore.app_attest_key"; + +/// Determines whether app_attest_key_feature is supported or not. +pub fn app_attest_key_feature_exists() -> bool { + let pm = wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE) + .expect("Failed to get package manager native service."); + + pm.hasSystemFeature(APP_ATTEST_KEY_FEATURE, 0).expect("hasSystemFeature failed.") +} + +#[macro_export] +macro_rules! skip_test_if_no_app_attest_key_feature { + () => { + if !app_attest_key_feature_exists() { + return; + } + }; +} + +/// Indicate whether the default device is KeyMint (rather than Keymaster). +pub fn has_default_keymint() -> bool { + binder::is_declared("android.hardware.security.keymint.IKeyMintDevice/default") + .expect("Could not check for declared keymint interface") +} + +/// Generate EC key and grant it to the list of users with given access vector. +/// Returns the list of granted keys `nspace` values in the order of given grantee uids. +pub fn generate_ec_key_and_grant_to_users( + keystore2: &binder::Strong<dyn IKeystoreService>, + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: Option<String>, + grantee_uids: Vec<i32>, + access_vector: i32, +) -> Result<Vec<i64>, binder::Status> { + let key_metadata = + key_generations::generate_ec_p256_signing_key(sec_level, Domain::APP, -1, alias, None)?; + + let mut granted_keys = Vec::new(); + + for uid in grantee_uids { + let granted_key = keystore2.grant(&key_metadata.key, uid, access_vector)?; + assert_eq!(granted_key.domain, Domain::GRANT); + granted_keys.push(granted_key.nspace); + } + + Ok(granted_keys) +} + +/// Generate a EC_P256 key using given domain, namespace and alias. +/// Create an operation using the generated key and perform sample signing operation. +pub fn create_signing_operation( + forced_op: ForcedOp, + op_purpose: KeyPurpose, + op_digest: Digest, + domain: Domain, + nspace: i64, + alias: Option<String>, +) -> binder::Result<CreateOperationResponse> { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let key_metadata = + key_generations::generate_ec_p256_signing_key(&sec_level, domain, nspace, alias, None) + .unwrap(); + + sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(op_purpose).digest(op_digest), + forced_op.0, + ) +} + +/// Performs sample signing operation. +pub fn perform_sample_sign_operation( + op: &binder::Strong<dyn IKeystoreOperation>, +) -> Result<(), binder::Status> { + op.update(b"my message")?; + let sig = op.finish(None, None)?; + assert!(sig.is_some()); + Ok(()) +} + +/// Perform sample HMAC sign and verify operations. +pub fn perform_sample_hmac_sign_verify_op( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + key: &KeyDescriptor, +) { + let sign_op = sec_level + .createOperation( + key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256) + .mac_length(256), + false, + ) + .unwrap(); + assert!(sign_op.iOperation.is_some()); + + let op = sign_op.iOperation.unwrap(); + op.update(b"my message").unwrap(); + let sig = op.finish(None, None).unwrap(); + assert!(sig.is_some()); + + let sig = sig.unwrap(); + let verify_op = sec_level + .createOperation( + key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256), + false, + ) + .unwrap(); + assert!(verify_op.iOperation.is_some()); + + let op = verify_op.iOperation.unwrap(); + let result = op.finish(Some(b"my message"), Some(&sig)).unwrap(); + assert!(result.is_none()); +} + +/// Map KeyMint Digest values to OpenSSL MessageDigest. +pub fn get_openssl_digest_mode(digest: Option<Digest>) -> MessageDigest { + match digest { + Some(Digest::MD5) => MessageDigest::md5(), + Some(Digest::SHA1) => MessageDigest::sha1(), + Some(Digest::SHA_2_224) => MessageDigest::sha224(), + Some(Digest::SHA_2_256) => MessageDigest::sha256(), + Some(Digest::SHA_2_384) => MessageDigest::sha384(), + Some(Digest::SHA_2_512) => MessageDigest::sha512(), + _ => MessageDigest::sha256(), + } +} + +/// Map KeyMint PaddingMode values to OpenSSL Padding. +pub fn get_openssl_padding_mode(padding: PaddingMode) -> Padding { + match padding { + PaddingMode::RSA_OAEP => Padding::PKCS1_OAEP, + PaddingMode::RSA_PSS => Padding::PKCS1_PSS, + PaddingMode::RSA_PKCS1_1_5_SIGN => Padding::PKCS1, + PaddingMode::RSA_PKCS1_1_5_ENCRYPT => Padding::PKCS1, + _ => Padding::NONE, + } +} + +/// Perform sample sign and verify operations using RSA or EC key. +pub fn perform_sample_asym_sign_verify_op( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + key_metadata: &KeyMetadata, + padding: Option<PaddingMode>, + digest: Option<Digest>, +) { + let mut authorizations = authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN); + if let Some(value) = padding { + authorizations = authorizations.padding_mode(value); + } + if let Some(value) = digest { + authorizations = authorizations.digest(value); + } + + let sign_op = sec_level.createOperation(&key_metadata.key, &authorizations, false).unwrap(); + assert!(sign_op.iOperation.is_some()); + + let op = sign_op.iOperation.unwrap(); + op.update(b"my message").unwrap(); + let sig = op.finish(None, None).unwrap(); + assert!(sig.is_some()); + + let sig = sig.unwrap(); + let cert_bytes = key_metadata.certificate.as_ref().unwrap(); + let cert = X509::from_der(cert_bytes.as_ref()).unwrap(); + let pub_key = cert.public_key().unwrap(); + let mut verifier = Verifier::new(get_openssl_digest_mode(digest), pub_key.as_ref()).unwrap(); + if let Some(value) = padding { + verifier.set_rsa_padding(get_openssl_padding_mode(value)).unwrap(); + } + verifier.update(b"my message").unwrap(); + assert!(verifier.verify(&sig).unwrap()); +} + +/// Create new operation on child proc and perform simple operation after parent notification. +pub fn execute_op_run_as_child( + target_ctx: &'static str, + domain: Domain, + nspace: i64, + alias: Option<String>, + auid: Uid, + agid: Gid, + forced_op: ForcedOp, +) -> run_as::ChildHandle<TestOutcome, BarrierReached> { + unsafe { + run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| { + let result = key_generations::map_ks_error(create_signing_operation( + forced_op, + KeyPurpose::SIGN, + Digest::SHA_2_256, + domain, + nspace, + alias, + )); + + // Let the parent know that an operation has been started, then + // wait until the parent notifies us to continue, so the operation + // remains open. + writer.send(&BarrierReached {}); + reader.recv(); + + // Continue performing the operation after parent notifies. + match &result { + Ok(CreateOperationResponse { iOperation: Some(op), .. }) => { + match key_generations::map_ks_error(perform_sample_sign_operation(op)) { + Ok(()) => TestOutcome::Ok, + Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => { + TestOutcome::InvalidHandle + } + Err(e) => panic!("Error in performing op: {:#?}", e), + } + } + Ok(_) => TestOutcome::OtherErr, + Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy, + _ => TestOutcome::OtherErr, + } + }) + .expect("Failed to create an operation.") + } +} + +/// Get NONCE value from given key parameters list. +pub fn get_op_nonce(parameters: &KeyParameters) -> Option<Vec<u8>> { + for key_param in ¶meters.keyParameter { + if key_param.tag == Tag::NONCE { + if let KeyParameterValue::Blob(val) = &key_param.value { + return Some(val.clone()); + } + } + } + None +} + +/// This performs sample encryption operation with given symmetric key (AES/3DES). +/// It encrypts `SAMPLE_PLAIN_TEXT` of length 128-bits. +pub fn perform_sample_sym_key_encrypt_op( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + padding_mode: PaddingMode, + block_mode: BlockMode, + nonce: &mut Option<Vec<u8>>, + mac_len: Option<i32>, + key: &KeyDescriptor, +) -> binder::Result<Option<Vec<u8>>> { + let mut op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .padding_mode(padding_mode) + .block_mode(block_mode); + if let Some(value) = nonce { + op_params = op_params.nonce(value.to_vec()); + } + + if let Some(val) = mac_len { + op_params = op_params.mac_length(val); + } + + let op_response = sec_level.createOperation(key, &op_params, false)?; + assert!(op_response.iOperation.is_some()); + let op = op_response.iOperation.unwrap(); + if op_response.parameters.is_some() && nonce.is_none() { + *nonce = get_op_nonce(&op_response.parameters.unwrap()); + } + op.finish(Some(SAMPLE_PLAIN_TEXT), None) +} + +/// This performs sample decryption operation with given symmetric key (AES/3DES). +pub fn perform_sample_sym_key_decrypt_op( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + input: &[u8], + padding_mode: PaddingMode, + block_mode: BlockMode, + nonce: &mut Option<Vec<u8>>, + mac_len: Option<i32>, + key: &KeyDescriptor, +) -> binder::Result<Option<Vec<u8>>> { + let mut op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::DECRYPT) + .padding_mode(padding_mode) + .block_mode(block_mode); + if let Some(value) = nonce { + op_params = op_params.nonce(value.to_vec()); + } + + if let Some(val) = mac_len { + op_params = op_params.mac_length(val); + } + + let op_response = sec_level.createOperation(key, &op_params, false)?; + assert!(op_response.iOperation.is_some()); + let op = op_response.iOperation.unwrap(); + op.finish(Some(input), None) +} + +/// Delete a key with domain APP. +pub fn delete_app_key( + keystore2: &binder::Strong<dyn IKeystoreService>, + alias: &str, +) -> binder::Result<()> { + keystore2.deleteKey(&KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }) +} + +/// Encrypt the secure key with given transport key. +pub fn encrypt_secure_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + secure_key: &[u8], + aad: &[u8], + nonce: Vec<u8>, + mac_len: i32, + key: &KeyDescriptor, +) -> binder::Result<Option<Vec<u8>>> { + let op_params = authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::ENCRYPT) + .padding_mode(PaddingMode::NONE) + .block_mode(BlockMode::GCM) + .nonce(nonce) + .mac_length(mac_len); + + let op_response = sec_level.createOperation(key, &op_params, false)?; + + let op = op_response.iOperation.unwrap(); + op.updateAad(aad)?; + op.finish(Some(secure_key), None) +} + +/// Encrypt the transport key with given RSA wrapping key. +pub fn encrypt_transport_key( + transport_key: &[u8], + pkey: &PKey<Public>, +) -> Result<Vec<u8>, ErrorStack> { + let mut encrypter = Encrypter::new(pkey).unwrap(); + encrypter.set_rsa_padding(Padding::PKCS1_OAEP).unwrap(); + encrypter.set_rsa_oaep_md(MessageDigest::sha256()).unwrap(); + encrypter.set_rsa_mgf1_md(MessageDigest::sha1()).unwrap(); + + let input = transport_key.to_vec(); + let buffer_len = encrypter.encrypt_len(&input).unwrap(); + let mut encoded = vec![0u8; buffer_len]; + let encoded_len = encrypter.encrypt(&input, &mut encoded).unwrap(); + let encoded = &encoded[..encoded_len]; + + Ok(encoded.to_vec()) +} diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs new file mode 100644 index 00000000..07a298a8 --- /dev/null +++ b/keystore2/tests/keystore2_client_tests.rs @@ -0,0 +1,30 @@ +// Copyright 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. + +pub mod ffi_test_utils; +pub mod keystore2_client_3des_key_tests; +pub mod keystore2_client_aes_key_tests; +pub mod keystore2_client_attest_key_tests; +pub mod keystore2_client_delete_key_tests; +pub mod keystore2_client_ec_key_tests; +pub mod keystore2_client_grant_key_tests; +pub mod keystore2_client_hmac_key_tests; +pub mod keystore2_client_import_keys_tests; +pub mod keystore2_client_key_agreement_tests; +pub mod keystore2_client_key_id_domain_tests; +pub mod keystore2_client_list_entries_tests; +pub mod keystore2_client_operation_tests; +pub mod keystore2_client_rsa_key_tests; +pub mod keystore2_client_test_utils; +pub mod keystore2_client_update_subcomponent_tests; diff --git a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs new file mode 100644 index 00000000..0be092f8 --- /dev/null +++ b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs @@ -0,0 +1,295 @@ +// Copyright 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. + +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + ErrorCode::ErrorCode, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, + ResponseCode::ResponseCode, +}; + +use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as}; + +/// Generate a key and update its public certificate and certificate chain. Test should be able to +/// load the key and able to verify whether its certificate and cert-chain are updated successfully. +#[test] +fn keystore2_update_subcomponent_success() { + let alias = "update_subcomponent_success_key"; + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + None, + ) + .unwrap(); + + let other_cert: [u8; 32] = [123; 32]; + let other_cert_chain: [u8; 32] = [12; 32]; + + keystore2 + .updateSubcomponent(&key_metadata.key, Some(&other_cert), Some(&other_cert_chain)) + .expect("updateSubcomponent should have succeeded."); + + let key_entry_response = keystore2.getKeyEntry(&key_metadata.key).unwrap(); + assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate); + assert_eq!(Some(other_cert_chain.to_vec()), key_entry_response.metadata.certificateChain); +} + +/// Try to update non-existing asymmetric key public cert and certificate chain. Test should fail +/// to update with error response code `KEY_NOT_FOUND`. +#[test] +fn keystore2_update_subcomponent_fail() { + let alias = "update_component_failure_key"; + + let keystore2 = get_keystore_service(); + + let other_cert: [u8; 32] = [123; 32]; + let other_cert_chain: [u8; 32] = [12; 32]; + + let result = key_generations::map_ks_error(keystore2.updateSubcomponent( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias.to_string()), + blob: None, + }, + Some(&other_cert), + Some(&other_cert_chain), + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Try to update non-existing asymmetric key public cert only. Test should fail +/// to update with error response code `KEY_NOT_FOUND`. +#[test] +fn keystore2_update_subcomponent_no_key_entry_cert_fail() { + let alias = "update_no_key_entry_cert_only_component_fail_key"; + let keystore2 = get_keystore_service(); + let other_cert: [u8; 32] = [123; 32]; + + let result = key_generations::map_ks_error(keystore2.updateSubcomponent( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + Some(&other_cert), + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Try to update non existing key with the only given certificate-chain, test should succeed +/// in creating a new keystore entry with the given certificate-chain. +#[test] +fn keystore2_update_subcomponent_no_key_entry_cert_chain_success() { + let alias = "update_no_key_entry_cert_chain_only_component_success"; + let keystore2 = get_keystore_service(); + let cert_entries = + vec![(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE), (Domain::APP, -1)]; + let other_cert_chain: [u8; 32] = [12; 32]; + + for (domain, nspace) in cert_entries { + keystore2 + .updateSubcomponent( + &KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None }, + None, + Some(&other_cert_chain), + ) + .expect("updateSubcomponent should have succeeded."); + + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain, + nspace, + alias: Some(alias.to_string()), + blob: None, + }) + .unwrap(); + assert_eq!(Some(other_cert_chain.to_vec()), key_entry_response.metadata.certificateChain); + assert!(key_entry_response.metadata.certificate.is_none(), "Unexpected certificate entry"); + assert!(key_entry_response.metadata.authorizations.is_empty(), "Unexpected authorizations"); + assert_eq!(key_entry_response.metadata.keySecurityLevel, SecurityLevel::SOFTWARE); + + keystore2 + .deleteKey(&KeyDescriptor { + domain, + nspace, + alias: Some(alias.to_string()), + blob: None, + }) + .unwrap(); + } +} + +/// Generate a key and grant it to two users. For one user grant it with only `GET_INFO` access +/// permission and for another user grant it with GET_INFO and UPDATE access permissions. In a +/// grantee context where key is granted with only GET_INFO access permission, try to update +/// key's public certificate and certificate chain. Test should fail to update with error response +/// code `PERMISSION_DENIED` because grantee does not possess UPDATE access permission for the +/// specified key. In a grantee context where key is granted with UPDATE and GET_INFO access +/// permissions, test should be able to update public certificate and cert-chain successfully. +#[test] +fn keystore2_update_subcomponent_fails_permission_denied() { + static GRANTOR_SU_CTX: &str = "u:r:su:s0"; + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID_1: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_1_GID: u32 = GRANTEE_1_UID; + + const USER_ID_2: u32 = 98; + static GRANTEE_2_UID: u32 = USER_ID_2 * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_2_GID: u32 = GRANTEE_2_UID; + + // Generate a key and grant it to multiple users with different access permissions. + let mut granted_keys = unsafe { + run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = format!("ks_update_subcompo_test_1_{}", getuid()); + let mut granted_keys = Vec::new(); + + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::APP, + -1, + Some(alias), + None, + ) + .unwrap(); + + // Grant a key without update permission. + let access_vector = KeyPermission::GET_INFO.0; + let granted_key = keystore2 + .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector) + .unwrap(); + assert_eq!(granted_key.domain, Domain::GRANT); + granted_keys.push(granted_key.nspace); + + // Grant a key with update permission. + let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0; + let granted_key = keystore2 + .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector) + .unwrap(); + assert_eq!(granted_key.domain, Domain::GRANT); + granted_keys.push(granted_key.nspace); + + granted_keys + }) + }; + + // Grantee context, try to update the key public certs, permission denied error is expected. + let granted_key1_nspace = granted_keys.remove(0); + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_1_UID), + Gid::from_raw(GRANTEE_1_GID), + move || { + let keystore2 = get_keystore_service(); + + let other_cert: [u8; 32] = [123; 32]; + let other_cert_chain: [u8; 32] = [12; 32]; + + let result = key_generations::map_ks_error(keystore2.updateSubcomponent( + &KeyDescriptor { + domain: Domain::GRANT, + nspace: granted_key1_nspace, + alias: None, + blob: None, + }, + Some(&other_cert), + Some(&other_cert_chain), + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }, + ) + }; + + // Grantee context, update granted key public certs. Update should happen successfully. + let granted_key2_nspace = granted_keys.remove(0); + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_2_UID), + Gid::from_raw(GRANTEE_2_GID), + move || { + let keystore2 = get_keystore_service(); + + let other_cert: [u8; 32] = [124; 32]; + let other_cert_chain: [u8; 32] = [13; 32]; + + keystore2 + .updateSubcomponent( + &KeyDescriptor { + domain: Domain::GRANT, + nspace: granted_key2_nspace, + alias: None, + blob: None, + }, + Some(&other_cert), + Some(&other_cert_chain), + ) + .expect("updateSubcomponent should have succeeded."); + + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::GRANT, + nspace: granted_key2_nspace, + alias: None, + blob: None, + }) + .unwrap(); + assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate); + assert_eq!( + Some(other_cert_chain.to_vec()), + key_entry_response.metadata.certificateChain + ); + }, + ) + }; +} + +#[test] +fn keystore2_get_security_level_success() { + let keystore2 = get_keystore_service(); + assert!( + keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).is_ok(), + "getSecurityLevel with SecurityLevel::TRUSTED_ENVIRONMENT should have succeeded." + ); +} + +#[test] +fn keystore2_get_security_level_failure() { + let keystore2 = get_keystore_service(); + let result = key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::SOFTWARE)); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); +} diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp index 9322a411..92f2cc34 100644 --- a/keystore2/tests/legacy_blobs/Android.bp +++ b/keystore2/tests/legacy_blobs/Android.bp @@ -33,8 +33,6 @@ rust_test { rustlibs: [ "libkeystore2_with_test_utils", "libkeystore2_crypto_rust", - "android.system.keystore2-V2-rust", - "android.hardware.security.keymint-V2-rust", "android.security.maintenance-rust", "android.security.authorization-rust", "librustutils", @@ -47,5 +45,9 @@ rust_test { "libserde", "libthiserror", ], + defaults: [ + "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", + ], require_root: true, } diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs index 6def39e2..63122fe0 100644 --- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs +++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs @@ -85,7 +85,7 @@ fn keystore2_restart_service() { .expect("failed to execute pidof keystore2"); let id = String::from_utf8(output.stdout).unwrap(); - let id: String = id.chars().filter(|c| c.is_digit(10)).collect(); + let id: String = id.chars().filter(|c| c.is_ascii_digit()).collect(); let _status = std::process::Command::new("kill").arg("-9").arg(id).status().unwrap(); @@ -164,13 +164,19 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) .unwrap(); // Generate Key BLOB and prepare legacy keystore blob files. - let key_metadata = - key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level) - .expect("Failed to generate key blob"); + let att_challenge: &[u8] = b"foo"; + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::BLOB, + SELINUX_SHELL_NAMESPACE, + None, + Some(att_challenge), + ) + .expect("Failed to generate key blob"); // Create keystore file layout for user_99. let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); + let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); let super_key = TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); @@ -415,13 +421,19 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) .unwrap(); // Generate Key BLOB and prepare legacy keystore blob files. - let key_metadata = - key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level) - .expect("Failed to generate key blob"); + let att_challenge: &[u8] = b"foo"; + let key_metadata = key_generations::generate_ec_p256_signing_key( + &sec_level, + Domain::BLOB, + SELINUX_SHELL_NAMESPACE, + None, + Some(att_challenge), + ) + .expect("Failed to generate key blob"); // Create keystore file layout for user_98. let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); + let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); let super_key = TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp index d73f8fe5..f56cfab3 100644 --- a/ondevice-signing/Android.bp +++ b/ondevice-signing/Android.bp @@ -101,6 +101,15 @@ cc_library { recovery_available: true, } +genrule { + name: "statslog_odsign.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_odsign.h --module art --namespace art,metrics,statsd", + out: [ + "statslog_odsign.h", + ], +} + cc_binary { name: "odsign", defaults: [ @@ -114,6 +123,7 @@ cc_binary { "odsign_main.cpp", "StatsReporter.cpp", ], + generated_headers: ["statslog_odsign.h"], header_libs: ["odrefresh_headers"], diff --git a/ondevice-signing/StatsReporter.cpp b/ondevice-signing/StatsReporter.cpp index 65e645a3..e4e4a035 100644 --- a/ondevice-signing/StatsReporter.cpp +++ b/ondevice-signing/StatsReporter.cpp @@ -20,12 +20,13 @@ #include <string> #include <sys/stat.h> -// Keep these constant in sync with COMPOS_METRIC_NAME & METRICS_FILE in OdsignStatsLogger.java. +// Keep these constants in sync with those in OdsignStatsLogger.java. constexpr const char* kOdsignMetricsFile = "/data/misc/odsign/metrics/odsign-metrics.txt"; constexpr const char* kComposMetricName = "comp_os_artifacts_check_record"; +constexpr const char* kOdsignMetricName = "odsign_record"; StatsReporter::~StatsReporter() { - if (comp_os_artifacts_check_record_ == nullptr) { + if (comp_os_artifacts_check_record_ == nullptr && !odsign_record_enabled_) { LOG(INFO) << "Metrics report is empty"; // Remove the metrics file if any old version of the file already exists @@ -42,24 +43,31 @@ StatsReporter::~StatsReporter() { PLOG(ERROR) << "Could not open file: " << kOdsignMetricsFile; return; } - - odsign_metrics_file_ << kComposMetricName << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->current_artifacts_ok << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->comp_os_pending_artifacts_exists - << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->use_comp_os_generated_artifacts - << '\n'; if (chmod(kOdsignMetricsFile, 0644) != 0) { PLOG(ERROR) << "Could not set correct file permissions for " << kOdsignMetricsFile; return; } + + if (comp_os_artifacts_check_record_ != nullptr) { + odsign_metrics_file_ << kComposMetricName << ' ' + << comp_os_artifacts_check_record_->current_artifacts_ok << ' ' + << comp_os_artifacts_check_record_->comp_os_pending_artifacts_exists + << ' ' + << comp_os_artifacts_check_record_->use_comp_os_generated_artifacts + << '\n'; + } + + if (odsign_record_enabled_) { + odsign_metrics_file_ << kOdsignMetricName << ' ' << odsign_record_.status << '\n'; + } + odsign_metrics_file_.close(); if (!odsign_metrics_file_) { PLOG(ERROR) << "Failed to close the file"; } } -StatsReporter::CompOsArtifactsCheckRecord* StatsReporter::GetComposArtifactsCheckRecord() { +StatsReporter::CompOsArtifactsCheckRecord* StatsReporter::GetOrCreateComposArtifactsCheckRecord() { if (comp_os_artifacts_check_record_ == nullptr) { comp_os_artifacts_check_record_ = std::make_unique<CompOsArtifactsCheckRecord>(); } diff --git a/ondevice-signing/StatsReporter.h b/ondevice-signing/StatsReporter.h index 2682b963..add7a110 100644 --- a/ondevice-signing/StatsReporter.h +++ b/ondevice-signing/StatsReporter.h @@ -18,27 +18,44 @@ #include <fstream> +#include "statslog_odsign.h" + // Class to store CompOsArtifactsCheck related metrics. // These are flushed to a file kOdsignMetricsFile and consumed by // System Server (in class OdsignStatsLogger) & sent to statsd. class StatsReporter { public: - // Keep sync with EarlyBootCompOsArtifactsCheckReported - // definition in proto_logging/stats/atoms.proto. + // Keep in sync with the EarlyBootCompOsArtifactsCheckReported definition in + // proto_logging/stats/atoms.proto. struct CompOsArtifactsCheckRecord { bool current_artifacts_ok = false; bool comp_os_pending_artifacts_exists = false; bool use_comp_os_generated_artifacts = false; }; + // Keep in sync with the OdsignReported definition in proto_logging/stats/atoms.proto. + struct OdsignRecord { + int32_t status = art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_UNSPECIFIED; + }; + // The report is flushed (from buffer) into a file by the destructor. ~StatsReporter(); - // Get pointer to comp_os_artifacts_check_record, caller can then modify it. - // Note: pointer remains valid for the lifetime of this StatsReporter. - CompOsArtifactsCheckRecord* GetComposArtifactsCheckRecord(); + // Returns a mutable CompOS record. The pointer remains valid for the lifetime of this + // StatsReporter. If this function is not called, no CompOS record will be logged. + CompOsArtifactsCheckRecord* GetOrCreateComposArtifactsCheckRecord(); + + // Returns a mutable odsign record. The pointer remains valid for the lifetime of this + // StatsReporter. + OdsignRecord* GetOdsignRecord() { return &odsign_record_; } + + // Enables/disables odsign metrics. + void SetOdsignRecordEnabled(bool value) { odsign_record_enabled_ = value; } private: // Temporary buffer which stores the metrics. std::unique_ptr<CompOsArtifactsCheckRecord> comp_os_artifacts_check_record_; + + OdsignRecord odsign_record_; + bool odsign_record_enabled_ = true; }; diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp index cd9a1ea6..0b631daa 100644 --- a/ondevice-signing/VerityUtils.cpp +++ b/ondevice-signing/VerityUtils.cpp @@ -26,16 +26,15 @@ #include <sys/types.h> #include <sys/wait.h> +#include "android-base/errors.h" #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/result.h> #include <android-base/unique_fd.h> #include <asm/byteorder.h> #include <libfsverity.h> #include <linux/fsverity.h> -#include "CertUtils.h" -#include "SigningKey.h" - #define FS_VERITY_MAX_DIGEST_SIZE 64 using android::base::ErrnoError; @@ -58,23 +57,6 @@ static std::string toHex(std::span<const uint8_t> data) { return ss.str(); } -static std::vector<uint8_t> fromHex(std::string_view hex) { - if (hex.size() % 2 != 0) { - return {}; - } - std::vector<uint8_t> result; - result.reserve(hex.size() / 2); - for (size_t i = 0; i < hex.size(); i += 2) { - uint8_t byte; - auto conversion_result = std::from_chars(&hex[i], &hex[i + 2], byte, 16); - if (conversion_result.ptr != &hex[i + 2] || conversion_result.ec != std::errc()) { - return {}; - } - result.push_back(byte); - } - return result; -} - static int read_callback(void* file, void* buf, size_t count) { int* fd = (int*)file; if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO; @@ -127,20 +109,6 @@ template <typename T> struct DeleteAsPODArray { } }; -static Result<void> measureFsVerity(int fd, const fsverity_digest* digest) { - if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) != 0) { - if (errno == ENODATA) { - return Error() << "File is not in fs-verity"; - } else { - return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY"; - } - } - - return {}; -} - -} // namespace - template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>; template <typename T> @@ -150,28 +118,35 @@ static trailing_unique_ptr<T> makeUniqueWithTrailingData(size_t trailing_data_si return trailing_unique_ptr<T>{ptr}; } -static Result<std::vector<uint8_t>> signDigest(const SigningKey& key, - const std::vector<uint8_t>& digest) { - auto d = makeUniqueWithTrailingData<fsverity_formatted_digest>(digest.size()); +static Result<std::string> measureFsVerity(int fd) { + auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); + d->digest_size = FS_VERITY_MAX_DIGEST_SIZE; + + if (ioctl(fd, FS_IOC_MEASURE_VERITY, d.get()) != 0) { + if (errno == ENODATA) { + return Error() << "File is not in fs-verity"; + } else { + return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY"; + } + } - memcpy(d->magic, "FSVerity", 8); - d->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256); - d->digest_size = __cpu_to_le16(digest.size()); - memcpy(d->digest, digest.data(), digest.size()); + return toHex({&d->digest[0], &d->digest[d->digest_size]}); +} - auto signed_digest = key.sign(std::string((char*)d.get(), sizeof(*d) + digest.size())); - if (!signed_digest.ok()) { - return signed_digest.error(); +static Result<std::string> measureFsVerity(const std::string& path) { + unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); + if (!fd.ok()) { + return ErrnoError() << "Failed to open " << path; } - return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end()); + return measureFsVerity(fd.get()); } -static Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) { +} // namespace + +static Result<void> enableFsVerity(int fd) { struct fsverity_enable_arg arg = {.version = 1}; - arg.sig_ptr = reinterpret_cast<uint64_t>(pkcs7.data()); - arg.sig_size = pkcs7.size(); arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; arg.block_size = 4096; @@ -184,59 +159,24 @@ static Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) { return {}; } -Result<std::string> enableFsVerity(int fd, const SigningKey& key) { - auto digest = createDigest(fd); - if (!digest.ok()) { - return Error() << digest.error(); - } - - auto signed_digest = signDigest(key, digest.value()); - if (!signed_digest.ok()) { - return signed_digest.error(); - } - - auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject); - if (!pkcs7_data.ok()) { - return pkcs7_data.error(); - } - - auto enabled = enableFsVerity(fd, pkcs7_data.value()); - if (!enabled.ok()) { - return Error() << enabled.error(); - } - - // Return the root hash as a hex string - return toHex(digest.value()); -} - -static Result<std::string> isFileInVerity(int fd) { - auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); - d->digest_size = FS_VERITY_MAX_DIGEST_SIZE; - - const auto& status = measureFsVerity(fd, d.get()); - if (!status.ok()) { - return status.error(); - } - - return toHex({&d->digest[0], &d->digest[d->digest_size]}); -} - -static Result<std::string> isFileInVerity(const std::string& path) { +Result<void> enableFsVerity(const std::string& path) { unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); if (!fd.ok()) { - return ErrnoError() << "Failed to open " << path; + return Error() << "Can't open " << path; } - auto digest = isFileInVerity(fd.get()); - if (!digest.ok()) { - return Error() << digest.error() << ": " << path; - } + return enableFsVerity(fd.get()); +} - return digest; +static Result<bool> isFileInVerity(int fd) { + unsigned int flags; + if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0) { + return ErrnoError() << "ioctl(FS_IOC_GETFLAGS) failed"; + } + return (flags & FS_VERITY_FL) != 0; } -Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path, - const SigningKey& key) { +Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path) { std::map<std::string, std::string> digests; std::error_code ec; @@ -247,18 +187,15 @@ Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std:: if (!fd.ok()) { return ErrnoError() << "Failed to open " << path; } - auto digest = isFileInVerity(fd); - if (!digest.ok()) { + auto enabled = OR_RETURN(isFileInVerity(fd)); + if (!enabled) { LOG(INFO) << "Adding " << it->path() << " to fs-verity..."; - auto result = enableFsVerity(fd, key); - if (!result.ok()) { - return result.error(); - } - digests[it->path()] = *result; + OR_RETURN(enableFsVerity(fd)); } else { LOG(INFO) << it->path() << " was already in fs-verity."; - digests[it->path()] = *digest; } + auto digest = OR_RETURN(measureFsVerity(fd)); + digests[it->path()] = digest; } } if (ec) { @@ -268,31 +205,6 @@ Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std:: return digests; } -Result<void> enableFsVerity(const std::string& path, const std::string& signature_path) { - unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return Error() << "Can't open " << path; - } - - std::string signature; - android::base::ReadFileToString(signature_path, &signature); - std::vector<uint8_t> span = std::vector<uint8_t>(signature.begin(), signature.end()); - - const auto& enable = enableFsVerity(fd.get(), span); - if (!enable.ok()) { - return enable.error(); - } - - auto digest = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); - digest->digest_size = FS_VERITY_MAX_DIGEST_SIZE; - const auto& measure = measureFsVerity(fd.get(), digest.get()); - if (!measure.ok()) { - return measure.error(); - } - - return {}; -} - Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) { std::map<std::string, std::string> digests; std::error_code ec; @@ -303,11 +215,8 @@ Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::str while (!ec && it != end) { if (it->is_regular_file()) { // Verify the file is in fs-verity - auto result = isFileInVerity(it->path()); - if (!result.ok()) { - return result.error(); - } - digests[it->path()] = *result; + auto result = OR_RETURN(measureFsVerity(it->path())); + digests[it->path()] = result; } else if (it->is_directory()) { // These are fine to ignore } else if (it->is_symlink()) { @@ -325,8 +234,7 @@ Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::str } Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, - const std::map<std::string, std::string>& digests, - const SigningKey& signing_key) { + const std::map<std::string, std::string>& digests) { std::error_code ec; size_t verified_count = 0; auto it = std::filesystem::recursive_directory_iterator(directory_path, ec); @@ -344,41 +252,18 @@ Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, return ErrnoError() << "Can't open " << path; } - auto verity_digest = isFileInVerity(fd); - if (verity_digest.ok()) { - // The file is already in fs-verity. We need to make sure it was signed - // by CompOS, so we just check that it has the digest we expect. - if (verity_digest.value() == compos_digest) { - ++verified_count; - } else { - return Error() << "fs-verity digest does not match CompOS digest: " << path; - } - } else { - // Not in fs-verity yet. We know the digest CompOS provided; If - // it's not the correct digest for the file then enabling - // fs-verity will fail, so we don't need to check it explicitly - // ourselves. Otherwise we should be good. - LOG(INFO) << "Adding " << path << " to fs-verity..."; - - auto digest_bytes = fromHex(compos_digest); - if (digest_bytes.empty()) { - return Error() << "Invalid digest " << compos_digest; - } - auto signed_digest = signDigest(signing_key, digest_bytes); - if (!signed_digest.ok()) { - return signed_digest.error(); - } - - auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject); - if (!pkcs7_data.ok()) { - return pkcs7_data.error(); - } - - auto enabled = enableFsVerity(fd, pkcs7_data.value()); - if (!enabled.ok()) { - return Error() << enabled.error(); - } + bool enabled = OR_RETURN(isFileInVerity(fd)); + if (!enabled) { + LOG(INFO) << "Enabling fs-verity for " << path; + OR_RETURN(enableFsVerity(fd)); + } + + auto actual_digest = OR_RETURN(measureFsVerity(fd)); + // Make sure the file's fs-verity digest matches the known value. + if (actual_digest == compos_digest) { ++verified_count; + } else { + return Error() << "fs-verity digest does not match CompOS digest: " << path; } } else if (it->is_directory()) { // These are fine to ignore diff --git a/ondevice-signing/include/VerityUtils.h b/ondevice-signing/include/VerityUtils.h index e6e49c7d..626bbdb4 100644 --- a/ondevice-signing/include/VerityUtils.h +++ b/ondevice-signing/include/VerityUtils.h @@ -22,11 +22,9 @@ #include <string> #include <vector> -#include "SigningKey.h" - android::base::Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName); android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path); -android::base::Result<std::string> enableFsVerity(int fd, const SigningKey& key); +android::base::Result<std::string> enableFsVerity(int fd); bool SupportsFsVerity(); android::base::Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path); @@ -34,13 +32,11 @@ verifyAllFilesInVerity(const std::string& path); // Note that this function will skip files that are already in fs-verity, and // for those files it will return the existing digest. android::base::Result<std::map<std::string, std::string>> -addFilesToVerityRecursive(const std::string& path, const SigningKey& key); +addFilesToVerityRecursive(const std::string& path); // Enable verity on the provided file, using the given PKCS7 signature. -android::base::Result<void> enableFsVerity(const std::string& path, - const std::string& signature_path); +android::base::Result<void> enableFsVerity(const std::string& path); android::base::Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, - const std::map<std::string, std::string>& digests, - const SigningKey& signing_key); + const std::map<std::string, std::string>& digests); diff --git a/ondevice-signing/odsign.rc b/ondevice-signing/odsign.rc index de09fc0e..b96c62ff 100644 --- a/ondevice-signing/odsign.rc +++ b/ondevice-signing/odsign.rc @@ -3,6 +3,13 @@ service odsign /system/bin/odsign user root group system disabled # does not start with the core class + # Explicitly specify empty capabilities, otherwise odsign will inherit all + # the capabilities from init. + # Note: whether a process can use capabilities is controlled by SELinux, so + # inheriting all the capabilities from init is not a security issue. + # However, for defense-in-depth and just for the sake of bookkeeping it's + # better to explicitly state that odsign doesn't need any capabilities. + capabilities # Note that odsign is not oneshot, but stopped manually when it exits. This # ensures that if odsign crashes during a module update, apexd will detect diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp index c45e3085..a688ead6 100644 --- a/ondevice-signing/odsign_main.cpp +++ b/ondevice-signing/odsign_main.cpp @@ -35,6 +35,7 @@ #include "KeystoreKey.h" #include "StatsReporter.h" #include "VerityUtils.h" +#include "statslog_odsign.h" #include "odsign_info.pb.h" @@ -370,7 +371,7 @@ art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_k bool* digests_verified, StatsReporter* stats_reporter) { StatsReporter::CompOsArtifactsCheckRecord* compos_check_record = - stats_reporter->GetComposArtifactsCheckRecord(); + stats_reporter->GetOrCreateComposArtifactsCheckRecord(); if (!directoryHasContent(kCompOsPendingArtifactsDir)) { // No pending CompOS artifacts, all that matters is the current ones. @@ -420,7 +421,7 @@ art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_k std::map<std::string, std::string> compos_digests(compos_info->file_hashes().begin(), compos_info->file_hashes().end()); - auto status = verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_digests, signing_key); + auto status = verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_digests); if (!status.ok()) { LOG(WARNING) << "Faild to verify CompOS artifacts: " << status.error(); } else { @@ -468,12 +469,9 @@ art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_k } // namespace int main(int /* argc */, char** argv) { - // stats_reporter is a pointer so that we can explicitly delete it - // instead of waiting for the program to die & its destrcutor be called - auto stats_reporter = std::make_unique<StatsReporter>(); android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM)); - auto errorScopeGuard = []() { + auto scope_guard = android::base::make_scope_guard([]() { // In case we hit any error, remove the artifacts and tell Zygote not to use // anything removeDirectory(kArtArtifactsDir); @@ -485,17 +483,24 @@ int main(int /* argc */, char** argv) { SetProperty(kOdsignVerificationDoneProp, "1"); // Tell init it shouldn't try to restart us - see odsign.rc SetProperty(kStopServiceProp, "odsign"); - }; - auto scope_guard = android::base::make_scope_guard(errorScopeGuard); + }); + + // `stats_reporter` must come after `scope_guard` so that its destructor is called before + // `scope_guard`. + auto stats_reporter = std::make_unique<StatsReporter>(); + StatsReporter::OdsignRecord* odsign_record = stats_reporter->GetOdsignRecord(); if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { LOG(INFO) << "Device doesn't support updatable APEX, exiting."; + stats_reporter->SetOdsignRecordEnabled(false); return 0; } auto keystoreResult = KeystoreKey::getInstance(kPublicKeySignature, kKeyAlias, kKeyNspace, kKeyBootLevel); if (!keystoreResult.ok()) { LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error(); + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_KEYSTORE_FAILED; return -1; } SigningKey* key = keystoreResult.value(); @@ -517,17 +522,13 @@ int main(int /* argc */, char** argv) { if (!new_cert.ok()) { LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error(); // TODO apparently the key become invalid - delete the blob / cert + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_CERT_FAILED; return -1; } } else { LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert; } - auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert, "fsv_ods"); - if (!cert_add_result.ok()) { - LOG(ERROR) << "Failed to add certificate to fs-verity keyring: " - << cert_add_result.error(); - return -1; - } } bool digests_verified = false; @@ -535,12 +536,6 @@ int main(int /* argc */, char** argv) { useCompOs ? CheckCompOsPendingArtifacts(*key, &digests_verified, stats_reporter.get()) : checkArtifacts(); - // Explicitly reset the pointer - We rely on stats_reporter's - // destructor for actually writing the buffered metrics. This will otherwise not be called - // if the program doesn't exit normally (for ex, killed by init, which actually happens - // because odsign (after it finishes) sets kStopServiceProp instructing init to kill it). - stats_reporter.reset(); - // The artifacts dir doesn't necessarily need to exist; if the existing // artifacts on the system partition are valid, those can be used. int err = access(kArtArtifactsDir.c_str(), F_OK); @@ -578,6 +573,8 @@ int main(int /* argc */, char** argv) { // instead prevent Zygote from using them (which is taken care of // in the exit handler). LOG(ERROR) << "Failed to remove unknown artifacts."; + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_CLEANUP_FAILED; return -1; } } @@ -591,14 +588,19 @@ int main(int /* argc */, char** argv) { if (odrefresh_status == art::odrefresh::ExitCode::kOkay) { // No new artifacts generated, and we verified existing ones above, nothing left to do. LOG(INFO) << "odrefresh said artifacts are VALID"; + stats_reporter->SetOdsignRecordEnabled(false); } 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; + // This value may be overwritten later. + odsign_record->status = + compiled_all ? art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ALL_OK + : art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_PARTIAL_OK; Result<std::map<std::string, std::string>> digests; if (supportsFsVerity) { - digests = addFilesToVerityRecursive(kArtArtifactsDir, *key); + digests = addFilesToVerityRecursive(kArtArtifactsDir); } else { // If we can't use verity, just compute the root hashes and store // those, so we can reverify them at the next boot. @@ -606,24 +608,39 @@ int main(int /* argc */, char** argv) { } if (!digests.ok()) { LOG(ERROR) << digests.error(); + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_SIGNING_FAILED; return -1; } auto persistStatus = persistDigests(*digests, *key); if (!persistStatus.ok()) { LOG(ERROR) << persistStatus.error(); + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_SIGNING_FAILED; return -1; } } else if (odrefresh_status == art::odrefresh::ExitCode::kCleanupFailed) { LOG(ERROR) << "odrefresh failed cleaning up existing artifacts"; + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ODREFRESH_FAILED; return -1; } else { LOG(ERROR) << "odrefresh exited unexpectedly, returned " << odrefresh_status; + odsign_record->status = + art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ODREFRESH_FAILED; return -1; } LOG(INFO) << "On-device signing done."; scope_guard.Disable(); + + // Explicitly reset the pointer - We rely on stats_reporter's + // destructor for actually writing the buffered metrics. This will otherwise not be called + // if the program doesn't exit normally (for ex, killed by init, which actually happens + // because odsign (after it finishes) sets kStopServiceProp instructing init to kill it). + stats_reporter.reset(); + // At this point, we're done with the key for sure SetProperty(kOdsignKeyDoneProp, "1"); // And we did a successful verification diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp new file mode 100644 index 00000000..763aaa01 --- /dev/null +++ b/prng_seeder/Android.bp @@ -0,0 +1,82 @@ +// 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 { + // See: http://go/android-license-faq + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +rust_bindgen { + name: "libcutils_socket_bindgen", + crate_name: "cutils_socket_bindgen", + wrapper_src: "cutils_wrapper.h", + source_stem: "bindings", + bindgen_flags: [ + "--allowlist-function=android_get_control_socket", + ], + shared_libs: [ + "libcutils", + ], +} + +rust_defaults { + name: "prng_seeder_defaults", + edition: "2021", + rustlibs: [ + "libanyhow", + "libbssl_ffi", + "libclap", + "libcutils_socket_bindgen", + "liblogger", + "liblog_rust", + "libnix", + "libtokio", + ], + + init_rc: ["prng_seeder.rc"], +} + +rust_binary { + name: "prng_seeder", + defaults: ["prng_seeder_defaults"], + srcs: ["src/main.rs"], +} + +rust_binary { + name: "prng_seeder_microdroid", + defaults: ["prng_seeder_defaults"], + srcs: ["src/main.rs"], + stem: "prng_seeder", + bootstrap: true, + installable: false, + prefer_rlib: true, +} + +rust_test { + name: "prng_seeder.test", + edition: "2021", + srcs: ["src/main.rs"], + rustlibs: [ + "libanyhow", + "libbssl_ffi", + "libclap", + "libcutils_socket_bindgen", + "liblogger", + "liblog_rust", + "libnix", + "libtokio", + ], + test_suites: ["general-tests"], +} diff --git a/prng_seeder/OWNERS b/prng_seeder/OWNERS new file mode 100644 index 00000000..51b7f38c --- /dev/null +++ b/prng_seeder/OWNERS @@ -0,0 +1,2 @@ +paulcrowley@google.com +prb@google.com diff --git a/diced/src/lib_vendor.rs b/prng_seeder/cutils_wrapper.h index 01c804b7..9c1fe565 100644 --- a/diced/src/lib_vendor.rs +++ b/prng_seeder/cutils_wrapper.h @@ -1,4 +1,4 @@ -// Copyright 2021, The Android Open Source Project +// 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. @@ -12,9 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! This crate implements the android.hardware.security.dice.IDiceDevice interface -//! and provides support for implementing a DICE HAL service. - -mod error_vendor; -pub mod hal_node; -pub use diced_open_dice_cbor as dice; +#include <cutils/sockets.h> diff --git a/prng_seeder/prng_seeder.rc b/prng_seeder/prng_seeder.rc new file mode 100644 index 00000000..9825583a --- /dev/null +++ b/prng_seeder/prng_seeder.rc @@ -0,0 +1,12 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Start PRNG seeder daemon from early-init + +on early-init + start prng_seeder + +service prng_seeder /system/bin/prng_seeder + user prng_seeder + group prng_seeder + stdio_to_kmsg + socket prng_seeder stream+listen 0666 prng_seeder prng_seeder diff --git a/prng_seeder/src/conditioner.rs b/prng_seeder/src/conditioner.rs new file mode 100644 index 00000000..ec1181bd --- /dev/null +++ b/prng_seeder/src/conditioner.rs @@ -0,0 +1,73 @@ +// 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. + +use std::{fs::File, io::Read}; + +use anyhow::{ensure, Context, Result}; +use log::debug; +use tokio::io::AsyncReadExt; + +use crate::drbg; + +const SEED_FOR_CLIENT_LEN: usize = 496; +const NUM_REQUESTS_PER_RESEED: u32 = 256; + +pub struct ConditionerBuilder { + hwrng: File, + rg: drbg::Drbg, +} + +impl ConditionerBuilder { + pub fn new(mut hwrng: File) -> Result<ConditionerBuilder> { + let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN]; + hwrng.read_exact(&mut et).context("hwrng.read_exact in new")?; + let rg = drbg::Drbg::new(&et)?; + Ok(ConditionerBuilder { hwrng, rg }) + } + + pub fn build(self) -> Conditioner { + Conditioner { + hwrng: tokio::fs::File::from_std(self.hwrng), + rg: self.rg, + requests_since_reseed: 0, + } + } +} + +pub struct Conditioner { + hwrng: tokio::fs::File, + rg: drbg::Drbg, + requests_since_reseed: u32, +} + +impl Conditioner { + pub async fn reseed_if_necessary(&mut self) -> Result<()> { + if self.requests_since_reseed >= NUM_REQUESTS_PER_RESEED { + debug!("Reseeding DRBG"); + let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN]; + self.hwrng.read_exact(&mut et).await.context("hwrng.read_exact in reseed")?; + self.rg.reseed(&et)?; + self.requests_since_reseed = 0; + } + Ok(()) + } + + pub fn request(&mut self) -> Result<[u8; SEED_FOR_CLIENT_LEN]> { + ensure!(self.requests_since_reseed < NUM_REQUESTS_PER_RESEED, "Not enough reseeds"); + let mut seed_for_client = [0u8; SEED_FOR_CLIENT_LEN]; + self.rg.generate(&mut seed_for_client)?; + self.requests_since_reseed += 1; + Ok(seed_for_client) + } +} diff --git a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs b/prng_seeder/src/cutils_socket.rs index 7e3e848c..ab2c8698 100644 --- a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs +++ b/prng_seeder/src/cutils_socket.rs @@ -1,4 +1,4 @@ -// Copyright 2021, The Android Open Source Project +// 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. @@ -12,15 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(missing_docs)] -#![no_main] -#[macro_use] -extern crate libfuzzer_sys; -use keystore2::legacy_blob::LegacyBlobLoader; +use std::ffi::CString; +use std::os::unix::{net::UnixListener, prelude::FromRawFd}; -fuzz_target!(|data: &[u8]| { - if !data.is_empty() { - let string = data.iter().filter_map(|c| std::char::from_u32(*c as u32)).collect::<String>(); - let _res = LegacyBlobLoader::decode_alias(&string); - } -}); +use anyhow::{ensure, Result}; + +pub fn android_get_control_socket(name: &str) -> Result<UnixListener> { + let name = CString::new(name)?; + let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) }; + ensure!(fd >= 0, "android_get_control_socket failed"); + Ok(unsafe { UnixListener::from_raw_fd(fd) }) +} diff --git a/prng_seeder/src/drbg.rs b/prng_seeder/src/drbg.rs new file mode 100644 index 00000000..89c5a888 --- /dev/null +++ b/prng_seeder/src/drbg.rs @@ -0,0 +1,65 @@ +// 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. + +use anyhow::{ensure, Result}; +use bssl_ffi as bssl_sys; + +pub const ENTROPY_LEN: usize = bssl_sys::CTR_DRBG_ENTROPY_LEN as usize; + +pub type Entropy = [u8; ENTROPY_LEN]; + +pub struct Drbg(*mut bssl_sys::CTR_DRBG_STATE); + +impl Drbg { + pub fn new(entropy: &Entropy) -> Result<Drbg> { + let p = unsafe { bssl_sys::CTR_DRBG_new(entropy.as_ptr(), std::ptr::null(), 0) }; + ensure!(!p.is_null(), "CTR_DRBG_new failed"); + Ok(Drbg(p)) + } + + pub fn reseed(&mut self, entropy: &Entropy) -> Result<()> { + ensure!( + unsafe { bssl_sys::CTR_DRBG_reseed(self.0, entropy.as_ptr(), std::ptr::null(), 0) } + == 1, + "CTR_DRBG_reseed failed" + ); + Ok(()) + } + + pub fn generate(&mut self, buf: &mut [u8]) -> Result<()> { + ensure!( + unsafe { + bssl_sys::CTR_DRBG_generate( + self.0, + buf.as_mut_ptr(), + buf.len(), + std::ptr::null(), + 0, + ) + } == 1, + "CTR_DRBG_generate failed" + ); + Ok(()) + } +} + +impl Drop for Drbg { + fn drop(&mut self) { + unsafe { + bssl_sys::CTR_DRBG_free(self.0); + } + } +} + +unsafe impl Send for Drbg {} diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs new file mode 100644 index 00000000..924481ac --- /dev/null +++ b/prng_seeder/src/main.rs @@ -0,0 +1,148 @@ +// 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. + +//! FIPS compliant random number conditioner. Reads from /dev/hw_random +//! and applies the NIST SP 800-90A CTR DRBG strategy to provide +//! pseudorandom bytes to clients which connect to a socket provided +//! by init. + +mod conditioner; +mod cutils_socket; +mod drbg; + +use std::{ + convert::Infallible, + fs::remove_file, + io::ErrorKind, + os::unix::net::UnixListener, + path::{Path, PathBuf}, +}; + +use anyhow::{ensure, Context, Result}; +use clap::Parser; +use log::{error, info, Level}; +use nix::sys::signal; +use tokio::{io::AsyncWriteExt, net::UnixListener as TokioUnixListener}; + +use crate::conditioner::ConditionerBuilder; + +#[derive(Debug, Parser)] +struct Cli { + #[clap(long, default_value = "/dev/hw_random")] + source: PathBuf, + #[clap(long)] + socket: Option<PathBuf>, +} + +fn configure_logging() -> Result<()> { + ensure!( + logger::init( + logger::Config::default().with_tag_on_device("prng_seeder").with_min_level(Level::Info) + ), + "log configuration failed" + ); + Ok(()) +} + +fn get_socket(path: &Path) -> Result<UnixListener> { + if let Err(e) = remove_file(path) { + if e.kind() != ErrorKind::NotFound { + return Err(e).context(format!("Removing old socket: {}", path.display())); + } + } else { + info!("Deleted old {}", path.display()); + } + UnixListener::bind(path) + .with_context(|| format!("In get_socket: binding socket to {}", path.display())) +} + +fn setup() -> Result<(ConditionerBuilder, UnixListener)> { + configure_logging()?; + let cli = Cli::try_parse()?; + unsafe { signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigIgn) } + .context("In setup, setting SIGPIPE to SIG_IGN")?; + + let listener = match cli.socket { + Some(path) => get_socket(path.as_path())?, + None => cutils_socket::android_get_control_socket("prng_seeder") + .context("In setup, calling android_get_control_socket")?, + }; + let hwrng = std::fs::File::open(&cli.source) + .with_context(|| format!("Unable to open hwrng {}", cli.source.display()))?; + let cb = ConditionerBuilder::new(hwrng)?; + Ok((cb, listener)) +} + +async fn listen_loop(cb: ConditionerBuilder, listener: UnixListener) -> Result<Infallible> { + let mut conditioner = cb.build(); + listener.set_nonblocking(true).context("In listen_loop, on set_nonblocking")?; + let listener = TokioUnixListener::from_std(listener).context("In listen_loop, on from_std")?; + info!("Starting listen loop"); + loop { + match listener.accept().await { + Ok((mut stream, _)) => { + let new_bytes = conditioner.request()?; + tokio::spawn(async move { + if let Err(e) = stream.write_all(&new_bytes).await { + error!("Request failed: {}", e); + } + }); + conditioner.reseed_if_necessary().await?; + } + Err(e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e).context("accept on socket failed"), + } + } +} + +fn run() -> Result<Infallible> { + let (cb, listener) = match setup() { + Ok(t) => t, + Err(e) => { + // If setup fails, just hang forever. That way init doesn't respawn us. + error!("Hanging forever because setup failed: {:?}", e); + // Logs are sometimes mysteriously not being logged, so print too + println!("prng_seeder: Hanging forever because setup failed: {:?}", e); + loop { + std::thread::park(); + error!("std::thread::park() finished unexpectedly, re-parking thread"); + } + } + }; + + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .context("In run, building reactor")? + .block_on(async { listen_loop(cb, listener).await }) +} + +fn main() { + let e = run(); + error!("Launch terminated: {:?}", e); + // Logs are sometimes mysteriously not being logged, so print too + println!("prng_seeder: launch terminated: {:?}", e); + std::process::exit(-1); +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/provisioner/Android.bp b/provisioner/Android.bp index 665a9e71..b5489738 100644 --- a/provisioner/Android.bp +++ b/provisioner/Android.bp @@ -43,12 +43,10 @@ aidl_interface { }, } -cc_binary { - name: "rkp_factory_extraction_tool", - vendor: true, - srcs: ["rkp_factory_extraction_tool.cpp"], +cc_defaults { + name: "rkp_factory_extraction_defaults", defaults: [ - "keymint_use_latest_hal_aidl_ndk_shared", + "keymint_use_latest_hal_aidl_ndk_static", ], shared_libs: [ "libbinder", @@ -57,11 +55,46 @@ cc_binary { "liblog", ], static_libs: [ + "android.hardware.security.rkp-V3-ndk", "libbase", "libcppbor_external", "libcppcose_rkp", - "libgflags", "libjsoncpp", "libkeymint_remote_prov_support", ], } + +cc_library_static { + name: "librkp_factory_extraction", + defaults: [ + "rkp_factory_extraction_defaults", + ], + srcs: ["rkp_factory_extraction_lib.cpp"], + vendor_available: true, +} + +cc_test { + name: "librkp_factory_extraction_test", + defaults: [ + "rkp_factory_extraction_defaults", + ], + srcs: ["rkp_factory_extraction_lib_test.cpp"], + test_suites: ["device-tests"], + static_libs: [ + "libgmock", + "librkp_factory_extraction", + ], +} + +cc_binary { + name: "rkp_factory_extraction_tool", + vendor: true, + srcs: ["rkp_factory_extraction_tool.cpp"], + defaults: [ + "rkp_factory_extraction_defaults", + ], + static_libs: [ + "libgflags", + "librkp_factory_extraction", + ], +} diff --git a/provisioner/TEST_MAPPING b/provisioner/TEST_MAPPING new file mode 100644 index 00000000..de3f1650 --- /dev/null +++ b/provisioner/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "librkp_factory_extraction_test" + } + ] +} diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp new file mode 100644 index 00000000..ab7d17c9 --- /dev/null +++ b/provisioner/rkp_factory_extraction_lib.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 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. + */ + +#include "rkp_factory_extraction_lib.h" + +#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <android-base/properties.h> +#include <android/binder_manager.h> +#include <cppbor.h> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <iterator> +#include <keymaster/cppcose/cppcose.h> +#include <openssl/base64.h> +#include <remote_prov/remote_prov_utils.h> +#include <sys/random.h> + +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <vector> + +#include "cppbor_parse.h" + +using aidl::android::hardware::security::keymint::DeviceInfo; +using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent; +using aidl::android::hardware::security::keymint::MacedPublicKey; +using aidl::android::hardware::security::keymint::ProtectedData; +using aidl::android::hardware::security::keymint::RpcHardwareInfo; +using aidl::android::hardware::security::keymint::remote_prov::EekChain; +using aidl::android::hardware::security::keymint::remote_prov::generateEekChain; +using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain; +using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild; +using aidl::android::hardware::security::keymint::remote_prov::parseAndValidateFactoryDeviceInfo; +using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryCsr; +using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryProtectedData; + +using namespace cppbor; +using namespace cppcose; + +constexpr size_t kVersionWithoutSuperencryption = 3; + +std::string toBase64(const std::vector<uint8_t>& buffer) { + size_t base64Length; + int rc = EVP_EncodedLength(&base64Length, buffer.size()); + if (!rc) { + std::cerr << "Error getting base64 length. Size overflow?" << std::endl; + exit(-1); + } + + std::string base64(base64Length, ' '); + rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size()); + ++rc; // Account for NUL, which BoringSSL does not for some reason. + if (rc != base64Length) { + std::cerr << "Error writing base64. Expected " << base64Length + << " bytes to be written, but " << rc << " bytes were actually written." + << std::endl; + exit(-1); + } + + // BoringSSL automatically adds a NUL -- remove it from the string data + base64.pop_back(); + + return base64; +} + +std::vector<uint8_t> generateChallenge() { + std::vector<uint8_t> challenge(kChallengeSize); + + ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size()); + uint8_t* writePtr = challenge.data(); + while (bytesRemaining > 0) { + int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0); + if (bytesRead < 0) { + if (errno == EINTR) { + continue; + } else { + std::cerr << errno << ": " << strerror(errno) << std::endl; + exit(-1); + } + } + bytesRemaining -= bytesRead; + writePtr += bytesRead; + } + + return challenge; +} + +CborResult<Array> composeCertificateRequestV1(const ProtectedData& protectedData, + const DeviceInfo& verifiedDeviceInfo, + const std::vector<uint8_t>& challenge, + const std::vector<uint8_t>& keysToSignMac, + IRemotelyProvisionedComponent* provisionable) { + Array macedKeysToSign = Array() + .add(Map().add(1, 5).encode()) // alg: hmac-sha256 + .add(Map()) // empty unprotected headers + .add(Null()) // nil for the payload + .add(keysToSignMac); // MAC as returned from the HAL + + ErrMsgOr<std::unique_ptr<Map>> parsedVerifiedDeviceInfo = + parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, provisionable); + if (!parsedVerifiedDeviceInfo) { + return {nullptr, parsedVerifiedDeviceInfo.moveMessage()}; + } + + auto [parsedProtectedData, ignore2, errMsg] = parse(protectedData.protectedData); + if (!parsedProtectedData) { + std::cerr << "Error parsing protected data: '" << errMsg << "'" << std::endl; + return {nullptr, errMsg}; + } + + Array deviceInfo = Array().add(parsedVerifiedDeviceInfo.moveValue()).add(Map()); + + auto certificateRequest = std::make_unique<Array>(); + (*certificateRequest) + .add(std::move(deviceInfo)) + .add(challenge) + .add(std::move(parsedProtectedData)) + .add(std::move(macedKeysToSign)); + return {std::move(certificateRequest), ""}; +} + +CborResult<Array> getCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) { + std::vector<uint8_t> keysToSignMac; + std::vector<MacedPublicKey> emptyKeys; + DeviceInfo verifiedDeviceInfo; + ProtectedData protectedData; + RpcHardwareInfo hwInfo; + ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo); + if (!status.isOk()) { + std::cerr << "Failed to get hardware info for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + + const std::vector<uint8_t> eek = getProdEekChain(hwInfo.supportedEekCurve); + const std::vector<uint8_t> challenge = generateChallenge(); + status = irpc->generateCertificateRequest( + /*test_mode=*/false, emptyKeys, eek, challenge, &verifiedDeviceInfo, &protectedData, + &keysToSignMac); + if (!status.isOk()) { + std::cerr << "Bundle extraction failed for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + return composeCertificateRequestV1(protectedData, verifiedDeviceInfo, challenge, keysToSignMac, + irpc); +} + +void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) { + std::vector<uint8_t> keysToSignMac; + std::vector<MacedPublicKey> emptyKeys; + DeviceInfo verifiedDeviceInfo; + ProtectedData protectedData; + RpcHardwareInfo hwInfo; + ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo); + if (!status.isOk()) { + std::cerr << "Failed to get hardware info for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + + const std::vector<uint8_t> eekId = {0, 1, 2, 3, 4, 5, 6, 7}; + ErrMsgOr<EekChain> eekChain = generateEekChain(hwInfo.supportedEekCurve, /*length=*/3, eekId); + if (!eekChain) { + std::cerr << "Error generating test EEK certificate chain: " << eekChain.message(); + exit(-1); + } + const std::vector<uint8_t> challenge = generateChallenge(); + status = irpc->generateCertificateRequest( + /*test_mode=*/true, emptyKeys, eekChain->chain, challenge, &verifiedDeviceInfo, + &protectedData, &keysToSignMac); + if (!status.isOk()) { + std::cerr << "Error generating test cert chain for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + + auto result = verifyFactoryProtectedData(verifiedDeviceInfo, /*keysToSign=*/{}, keysToSignMac, + protectedData, *eekChain, eekId, + hwInfo.supportedEekCurve, irpc, challenge); + + if (!result) { + std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName + << "'. Error message: '" << result.message() << "'." << std::endl; + exit(-1); + } +} + +CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) { + const std::string kFingerprintProp = "ro.build.fingerprint"; + + auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr); + if (!parsedCsr) { + return {nullptr, csrErrMsg}; + } + if (!parsedCsr->asArray()) { + return {nullptr, "CSR is not a CBOR array."}; + } + + if (!::android::base::WaitForPropertyCreation(kFingerprintProp)) { + return {nullptr, "Unable to read build fingerprint"}; + } + + Map unverifiedDeviceInfo = + Map().add("fingerprint", ::android::base::GetProperty(kFingerprintProp, /*default=*/"")); + parsedCsr->asArray()->add(std::move(unverifiedDeviceInfo)); + return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""}; +} + +CborResult<cppbor::Array> getCsrV3(std::string_view componentName, + IRemotelyProvisionedComponent* irpc, bool selfTest) { + std::vector<uint8_t> csr; + std::vector<MacedPublicKey> emptyKeys; + const std::vector<uint8_t> challenge = generateChallenge(); + + auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr); + if (!status.isOk()) { + std::cerr << "Bundle extraction failed for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + + if (selfTest) { + auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge); + if (!result) { + std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName + << "'. Error message: '" << result.message() << "'." << std::endl; + exit(-1); + } + } + + return composeCertificateRequestV3(csr); +} + +CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc, + bool selfTest) { + RpcHardwareInfo hwInfo; + auto status = irpc->getHardwareInfo(&hwInfo); + if (!status.isOk()) { + std::cerr << "Failed to get hardware info for '" << componentName + << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + exit(-1); + } + + if (hwInfo.versionNumber < kVersionWithoutSuperencryption) { + if (selfTest) { + selfTestGetCsrV1(componentName, irpc); + } + return getCsrV1(componentName, irpc); + } else { + return getCsrV3(componentName, irpc, selfTest); + } +} diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h new file mode 100644 index 00000000..ae8ea6b6 --- /dev/null +++ b/provisioner/rkp_factory_extraction_lib.h @@ -0,0 +1,55 @@ +/* + * Copyright 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. + */ + +#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <android/binder_manager.h> +#include <cppbor.h> +#include <keymaster/cppcose/cppcose.h> + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +// Challenge size must be between 32 and 64 bytes inclusive. +constexpr size_t kChallengeSize = 64; + +// Contains a the result of an operation that should return cborData on success. +// Returns an an error message and null cborData on error. +template <typename T> struct CborResult { + std::unique_ptr<T> cborData; + std::string errMsg; +}; + +// Return `buffer` encoded as a base64 string. +std::string toBase64(const std::vector<uint8_t>& buffer); + +// Generate a random challenge containing `kChallengeSize` bytes. +std::vector<uint8_t> generateChallenge(); + +// Get a certificate signing request for the given IRemotelyProvisionedComponent. +// On error, the csr Array is null, and the string field contains a description of +// what went wrong. +CborResult<cppbor::Array> +getCsr(std::string_view componentName, + aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc, + bool selfTest); + +// Generates a test certificate chain and validates it, exiting the process on error. +void selfTestGetCsr( + std::string_view componentName, + aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc); diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp new file mode 100644 index 00000000..3fe88da8 --- /dev/null +++ b/provisioner/rkp_factory_extraction_lib_test.cpp @@ -0,0 +1,268 @@ +/* + * 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. + */ + +#include "rkp_factory_extraction_lib.h" + +#include "gmock/gmock-matchers.h" +#include "gmock/gmock-more-matchers.h" +#include <aidl/android/hardware/security/keymint/DeviceInfo.h> +#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <aidl/android/hardware/security/keymint/MacedPublicKey.h> +#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h> +#include <android-base/properties.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <cstdint> +#include <memory> +#include <ostream> +#include <set> +#include <vector> + +#include "aidl/android/hardware/security/keymint/ProtectedData.h" +#include "android/binder_auto_utils.h" +#include "android/binder_interface_utils.h" +#include "cppbor.h" + +using ::ndk::ScopedAStatus; +using ::ndk::SharedRefBase; + +using namespace ::aidl::android::hardware::security::keymint; +using namespace ::cppbor; +using namespace ::testing; + +namespace cppbor { + +std::ostream& operator<<(std::ostream& os, const Item& item) { + return os << prettyPrint(&item); +} + +std::ostream& operator<<(std::ostream& os, const std::unique_ptr<Item>& item) { + return os << *item; +} + +std::ostream& operator<<(std::ostream& os, const Item* item) { + return os << *item; +} + +} // namespace cppbor + +class MockIRemotelyProvisionedComponent : public IRemotelyProvisionedComponentDefault { + public: + MOCK_METHOD(ScopedAStatus, getHardwareInfo, (RpcHardwareInfo * _aidl_return), (override)); + MOCK_METHOD(ScopedAStatus, generateEcdsaP256KeyPair, + (bool in_testMode, MacedPublicKey* out_macedPublicKey, + std::vector<uint8_t>* _aidl_return), + (override)); + MOCK_METHOD(ScopedAStatus, generateCertificateRequest, + (bool in_testMode, const std::vector<MacedPublicKey>& in_keysToSign, + const std::vector<uint8_t>& in_endpointEncryptionCertChain, + const std::vector<uint8_t>& in_challenge, DeviceInfo* out_deviceInfo, + ProtectedData* out_protectedData, std::vector<uint8_t>* _aidl_return), + (override)); + MOCK_METHOD(ScopedAStatus, generateCertificateRequestV2, + (const std::vector<MacedPublicKey>& in_keysToSign, + const std::vector<uint8_t>& in_challenge, std::vector<uint8_t>* _aidl_return), + (override)); + MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override)); + MOCK_METHOD(ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override)); +}; + +TEST(LibRkpFactoryExtractionTests, ToBase64) { + std::vector<uint8_t> input(UINT8_MAX + 1); + for (int i = 0; i < input.size(); ++i) { + input[i] = i; + } + + // Test three lengths so we get all the different paddding options + EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4" + "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV" + "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj" + "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8" + "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv" + "s7e7v8PHy8/T19vf4+fr7/P3+/w==", + toBase64(input)); + + input.push_back(42); + EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4" + "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV" + "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj" + "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8" + "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv" + "s7e7v8PHy8/T19vf4+fr7/P3+/yo=", + toBase64(input)); + + input.push_back(42); + EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4" + "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV" + "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj" + "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8" + "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv" + "s7e7v8PHy8/T19vf4+fr7/P3+/yoq", + toBase64(input)); +} + +TEST(LibRkpFactoryExtractionTests, UniqueChallengeSmokeTest) { + // This will at least catch VERY broken implementations. + constexpr size_t NUM_CHALLENGES = 32; + std::set<std::vector<uint8_t>> challenges; + for (size_t i = 0; i < NUM_CHALLENGES; ++i) { + const std::vector<uint8_t> challenge = generateChallenge(); + const auto [_, wasInserted] = challenges.insert(generateChallenge()); + EXPECT_TRUE(wasInserted) << "Duplicate challenge: " << toBase64(challenge); + } +} + +TEST(LibRkpFactoryExtractionTests, GetCsrWithV2Hal) { + ASSERT_TRUE(true); + + const std::vector<uint8_t> kFakeMac = {1, 2, 3, 4}; + + Map cborDeviceInfo; + cborDeviceInfo.add("product", "gShoe"); + cborDeviceInfo.add("version", 2); + cborDeviceInfo.add("brand", "Fake Brand"); + cborDeviceInfo.add("manufacturer", "Fake Mfr"); + cborDeviceInfo.add("model", "Fake Model"); + cborDeviceInfo.add("device", "Fake Device"); + cborDeviceInfo.add("vb_state", "orange"); + cborDeviceInfo.add("bootloader_state", "unlocked"); + cborDeviceInfo.add("vbmeta_digest", std::vector<uint8_t>{1, 2, 3, 4}); + cborDeviceInfo.add("system_patch_level", 42); + cborDeviceInfo.add("boot_patch_level", 31415); + cborDeviceInfo.add("vendor_patch_level", 0); + cborDeviceInfo.add("fused", 0); + cborDeviceInfo.add("security_level", "tee"); + cborDeviceInfo.add("os_version", "the best version"); + const DeviceInfo kVerifiedDeviceInfo = {cborDeviceInfo.canonicalize().encode()}; + + Array cborProtectedData; + cborProtectedData.add(Bstr()); // protected + cborProtectedData.add(Map()); // unprotected + cborProtectedData.add(Bstr()); // ciphertext + cborProtectedData.add(Array()); // recipients + const ProtectedData kProtectedData = {cborProtectedData.encode()}; + + std::vector<uint8_t> eekChain; + std::vector<uint8_t> challenge; + + // Set up mock, then call getSCsr + auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>(); + EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) { + hwInfo->versionNumber = 2; + return ScopedAStatus::ok(); + }); + EXPECT_CALL(*mockRpc, + generateCertificateRequest(false, // testMode + IsEmpty(), // keysToSign + _, // endpointEncryptionCertChain + _, // challenge + NotNull(), // deviceInfo + NotNull(), // protectedData + NotNull())) // _aidl_return + .WillOnce(DoAll(SaveArg<2>(&eekChain), // + SaveArg<3>(&challenge), // + SetArgPointee<4>(kVerifiedDeviceInfo), // + SetArgPointee<5>(kProtectedData), // + SetArgPointee<6>(kFakeMac), // + Return(ByMove(ScopedAStatus::ok())))); // + + auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(), + /*selfTest=*/false); + ASSERT_THAT(csr, NotNull()) << csrErrMsg; + ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4)))); + + // Verify the input parameters that we received + auto [parsedEek, ignore1, eekParseError] = parse(eekChain); + ASSERT_THAT(parsedEek, NotNull()) << eekParseError; + EXPECT_THAT(parsedEek->asArray(), Pointee(Property(&Array::size, Gt(1)))); + EXPECT_THAT(challenge, Property(&std::vector<uint8_t>::size, Eq(kChallengeSize))); + + // Device info consists of (verified info, unverified info) + const Array* deviceInfoArray = csr->get(0)->asArray(); + EXPECT_THAT(deviceInfoArray, Pointee(Property(&Array::size, 2))); + + // Verified device info must match our mock value + const Map* actualVerifiedDeviceInfo = deviceInfoArray->get(0)->asMap(); + EXPECT_THAT(actualVerifiedDeviceInfo, Pointee(Property(&Map::size, Eq(cborDeviceInfo.size())))); + EXPECT_THAT(actualVerifiedDeviceInfo->get("product"), Pointee(Eq(Tstr("gShoe")))); + EXPECT_THAT(actualVerifiedDeviceInfo->get("version"), Pointee(Eq(Uint(2)))); + + // Empty unverified device info + const Map* actualUnverifiedDeviceInfo = deviceInfoArray->get(1)->asMap(); + EXPECT_THAT(actualUnverifiedDeviceInfo, Pointee(Property(&Map::size, Eq(0)))); + + // Challenge must match the call to generateCertificateRequest + const Bstr* actualChallenge = csr->get(1)->asBstr(); + EXPECT_THAT(actualChallenge, Pointee(Property(&Bstr::value, Eq(challenge)))); + + // Protected data must match the mock value + const Array* actualProtectedData = csr->get(2)->asArray(); + EXPECT_THAT(actualProtectedData, Pointee(Eq(ByRef(cborProtectedData)))); + + // Ensure the maced public key matches the expected COSE_mac0 + const Array* actualMacedKeys = csr->get(3)->asArray(); + ASSERT_THAT(actualMacedKeys, Pointee(Property(&Array::size, Eq(4)))); + ASSERT_THAT(actualMacedKeys->get(0)->asBstr(), NotNull()); + auto [macProtectedParams, ignore2, macParamParseError] = + parse(actualMacedKeys->get(0)->asBstr()); + ASSERT_THAT(macProtectedParams, NotNull()) << macParamParseError; + Map expectedMacProtectedParams; + expectedMacProtectedParams.add(1, 5); + EXPECT_THAT(macProtectedParams, Pointee(Eq(ByRef(expectedMacProtectedParams)))); + EXPECT_THAT(actualMacedKeys->get(1)->asMap(), Pointee(Property(&Map::size, Eq(0)))); + EXPECT_THAT(actualMacedKeys->get(2)->asNull(), NotNull()); + EXPECT_THAT(actualMacedKeys->get(3)->asBstr(), Pointee(Eq(Bstr(kFakeMac)))); +} + +TEST(LibRkpFactoryExtractionTests, GetCsrWithV3Hal) { + const std::vector<uint8_t> kCsr = Array() + .add(3 /* version */) + .add(Map() /* UdsCerts */) + .add(Array() /* DiceCertChain */) + .add(Array() /* SignedData */) + .encode(); + std::vector<uint8_t> challenge; + + // Set up mock, then call getCsr + auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>(); + EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) { + hwInfo->versionNumber = 3; + return ScopedAStatus::ok(); + }); + EXPECT_CALL(*mockRpc, + generateCertificateRequestV2(IsEmpty(), // keysToSign + _, // challenge + NotNull())) // _aidl_return + .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr), + Return(ByMove(ScopedAStatus::ok())))); + + auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(), + /*selfTest=*/false); + ASSERT_THAT(csr, NotNull()) << csrErrMsg; + ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(5)))); + + EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(3)))); + EXPECT_THAT(csr->get(1)->asMap(), NotNull()); + EXPECT_THAT(csr->get(2)->asArray(), NotNull()); + EXPECT_THAT(csr->get(3)->asArray(), NotNull()); + + const Map* unverifedDeviceInfo = csr->get(4)->asMap(); + ASSERT_THAT(unverifedDeviceInfo, NotNull()); + EXPECT_THAT(unverifedDeviceInfo->get("fingerprint"), NotNull()); + const Tstr fingerprint(android::base::GetProperty("ro.build.fingerprint", "")); + EXPECT_THAT(*unverifedDeviceInfo->get("fingerprint")->asTstr(), Eq(fingerprint)); +} diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp index 0f455310..5ba777e8 100644 --- a/provisioner/rkp_factory_extraction_tool.cpp +++ b/provisioner/rkp_factory_extraction_tool.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#include <string> -#include <vector> - #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> #include <android/binder_manager.h> #include <cppbor.h> @@ -26,21 +23,22 @@ #include <remote_prov/remote_prov_utils.h> #include <sys/random.h> -using aidl::android::hardware::security::keymint::DeviceInfo; +#include <string> +#include <vector> + +#include "rkp_factory_extraction_lib.h" + using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent; -using aidl::android::hardware::security::keymint::MacedPublicKey; -using aidl::android::hardware::security::keymint::ProtectedData; -using aidl::android::hardware::security::keymint::RpcHardwareInfo; -using aidl::android::hardware::security::keymint::remote_prov::generateEekChain; -using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain; using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild; using namespace cppbor; using namespace cppcose; -DEFINE_bool(test_mode, false, "If enabled, a fake EEK key/cert are used."); - -DEFINE_string(output_format, "csr", "How to format the output. Defaults to 'csr'."); +DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'."); +DEFINE_bool(self_test, true, + "If true, this tool performs a self-test, validating the payload for correctness. " + "This checks that the device on the factory line is producing valid output " + "before attempting to upload the output to the device info service."); namespace { @@ -49,89 +47,6 @@ constexpr std::string_view kBinaryCsrOutput = "csr"; // Just the raw csr as constexpr std::string_view kBuildPlusCsr = "build+csr"; // Text-encoded (JSON) build // fingerprint plus CSR. -constexpr size_t kChallengeSize = 16; - -std::string toBase64(const std::vector<uint8_t>& buffer) { - size_t base64Length; - int rc = EVP_EncodedLength(&base64Length, buffer.size()); - if (!rc) { - std::cerr << "Error getting base64 length. Size overflow?" << std::endl; - exit(-1); - } - - std::string base64(base64Length, ' '); - rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size()); - ++rc; // Account for NUL, which BoringSSL does not for some reason. - if (rc != base64Length) { - std::cerr << "Error writing base64. Expected " << base64Length - << " bytes to be written, but " << rc << " bytes were actually written." - << std::endl; - exit(-1); - } - return base64; -} - -std::vector<uint8_t> generateChallenge() { - std::vector<uint8_t> challenge(kChallengeSize); - - ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size()); - uint8_t* writePtr = challenge.data(); - while (bytesRemaining > 0) { - int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0); - if (bytesRead < 0) { - if (errno == EINTR) { - continue; - } else { - std::cerr << errno << ": " << strerror(errno) << std::endl; - exit(-1); - } - } - bytesRemaining -= bytesRead; - writePtr += bytesRead; - } - - return challenge; -} - -Array composeCertificateRequest(const ProtectedData& protectedData, - const DeviceInfo& verifiedDeviceInfo, - const std::vector<uint8_t>& challenge, - const std::vector<uint8_t>& keysToSignMac) { - Array macedKeysToSign = Array() - .add(std::vector<uint8_t>(0)) // empty protected headers as bstr - .add(Map()) // empty unprotected headers - .add(Null()) // nil for the payload - .add(keysToSignMac); // MAC as returned from the HAL - - Array deviceInfo = - Array().add(EncodedItem(verifiedDeviceInfo.deviceInfo)).add(Map()); // Empty device info - - Array certificateRequest = Array() - .add(std::move(deviceInfo)) - .add(challenge) - .add(EncodedItem(protectedData.protectedData)) - .add(std::move(macedKeysToSign)); - return certificateRequest; -} - -std::vector<uint8_t> getEekChain(uint32_t curve) { - if (FLAGS_test_mode) { - const std::vector<uint8_t> kFakeEekId = {'f', 'a', 'k', 'e', 0}; - auto eekOrErr = generateEekChain(curve, 3 /* chainlength */, kFakeEekId); - if (!eekOrErr) { - std::cerr << "Failed to generate test EEK somehow: " << eekOrErr.message() << std::endl; - exit(-1); - } - auto [eek, pubkey, privkey] = eekOrErr.moveValue(); - std::cout << "EEK raw keypair:" << std::endl; - std::cout << " pub: " << toBase64(pubkey) << std::endl; - std::cout << " priv: " << toBase64(privkey) << std::endl; - return eek; - } - - return getProdEekChain(curve); -} - void writeOutput(const std::string instance_name, const Array& csr) { if (FLAGS_output_format == kBinaryCsrOutput) { auto bytes = csr.encode(); @@ -166,28 +81,13 @@ void getCsrForInstance(const char* name, void* /*context*/) { exit(-1); } - std::vector<uint8_t> keysToSignMac; - std::vector<MacedPublicKey> emptyKeys; - DeviceInfo verifiedDeviceInfo; - ProtectedData protectedData; - RpcHardwareInfo hwInfo; - ::ndk::ScopedAStatus status = rkp_service->getHardwareInfo(&hwInfo); - if (!status.isOk()) { - std::cerr << "Failed to get hardware info for '" << fullName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + auto [request, errMsg] = getCsr(name, rkp_service.get(), FLAGS_self_test); + if (!request) { + std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl; exit(-1); } - status = rkp_service->generateCertificateRequest( - FLAGS_test_mode, emptyKeys, getEekChain(hwInfo.supportedEekCurve), challenge, - &verifiedDeviceInfo, &protectedData, &keysToSignMac); - if (!status.isOk()) { - std::cerr << "Bundle extraction failed for '" << fullName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; - exit(-1); - } - auto request = - composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac); - writeOutput(std::string(name), request); + + writeOutput(std::string(name), *request); } } // namespace diff --git a/provisioner/support/Android.bp b/provisioner/support/Android.bp new file mode 100644 index 00000000..778b1e0f --- /dev/null +++ b/provisioner/support/Android.bp @@ -0,0 +1,64 @@ +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +cc_defaults { + name: "librkp_support_defaults", + static_libs: [ + "android.hardware.security.rkp-V3-cpp", + "android.security.rkp_aidl-cpp", + ], + shared_libs: [ + "libbase", + "libbinder", + "libutils", + "libvintf", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} + +cc_library { + name: "librkp_support", + defaults: ["librkp_support_defaults"], + srcs: [ + "rkpd_client.cpp", + ], + export_include_dirs: ["include"], +} + +cc_test { + name: "librkp_support_test", + defaults: [ + "librkp_support_defaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: ["test.cpp"], + static_libs: [ + "librkp_support", + ], + test_suites: ["general-tests"], + require_root: true, +} diff --git a/provisioner/support/TEST_MAPPING b/provisioner/support/TEST_MAPPING new file mode 100644 index 00000000..fc301043 --- /dev/null +++ b/provisioner/support/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "librkp_support_test" + } + ] +} diff --git a/provisioner/support/include/rkp/support/rkpd_client.h b/provisioner/support/include/rkp/support/rkpd_client.h new file mode 100644 index 00000000..5a7fe6e4 --- /dev/null +++ b/provisioner/support/include/rkp/support/rkpd_client.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +#include <future> +#include <optional> + +#include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <android/security/rkp/RemotelyProvisionedKey.h> + +namespace android::security::rkp::support { + +using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; +using ::android::security::rkp::RemotelyProvisionedKey; + +// Callers of getRpcKeyFuture() and getRpcKey() need at least two threads to +// retrieve the key, one to asynchronously handle binder callbacks and one to +// wait on the future. +std::optional<std::future<std::optional<RemotelyProvisionedKey>>> +getRpcKeyFuture(const sp<IRemotelyProvisionedComponent>& rpc, int32_t keyId); + +std::optional<RemotelyProvisionedKey> getRpcKey(const sp<IRemotelyProvisionedComponent>& rpc, + int32_t keyId, int32_t timeout_sec = 10); + +} // namespace android::security::rkp::support diff --git a/provisioner/support/rkpd_client.cpp b/provisioner/support/rkpd_client.cpp new file mode 100644 index 00000000..06434573 --- /dev/null +++ b/provisioner/support/rkpd_client.cpp @@ -0,0 +1,219 @@ +/* + * 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. + */ + +#define LOG_TAG "rkpd_client" + +#include <atomic> + +#include <android-base/logging.h> +#include <android/security/rkp/BnGetKeyCallback.h> +#include <android/security/rkp/BnGetRegistrationCallback.h> +#include <android/security/rkp/IGetKeyCallback.h> +#include <android/security/rkp/IRemoteProvisioning.h> +#include <binder/IServiceManager.h> +#include <binder/Status.h> +#include <rkp/support/rkpd_client.h> +#include <vintf/VintfObject.h> + +namespace android::security::rkp::support { +namespace { + +using ::android::binder::Status; +using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; +using ::android::hardware::security::keymint::RpcHardwareInfo; +using ::android::security::rkp::BnGetKeyCallback; +using ::android::security::rkp::BnGetRegistrationCallback; +using ::android::security::rkp::IGetKeyCallback; +using ::android::security::rkp::IRegistration; +using ::android::security::rkp::IRemoteProvisioning; +using ::android::security::rkp::RemotelyProvisionedKey; + +constexpr const char* kRemoteProvisioningServiceName = "remote_provisioning"; + +std::optional<std::string> getRpcId(const sp<IRemotelyProvisionedComponent>& rpc) { + RpcHardwareInfo rpcHwInfo; + Status status = rpc->getHardwareInfo(&rpcHwInfo); + if (!status.isOk()) { + LOG(ERROR) << "Error getting remotely provisioned component hardware info: " << status; + return std::nullopt; + } + + if (!rpcHwInfo.uniqueId) { + LOG(ERROR) << "Remotely provisioned component is missing a unique id. " + << "This is a bug in the vendor implementation."; + return std::nullopt; + } + + return *rpcHwInfo.uniqueId; +} + +std::optional<String16> findRpcNameById(std::string_view targetRpcId) { + auto deviceManifest = vintf::VintfObject::GetDeviceHalManifest(); + auto instances = deviceManifest->getAidlInstances("android.hardware.security.keymint", + "IRemotelyProvisionedComponent"); + for (const std::string& instance : instances) { + auto rpcName = + IRemotelyProvisionedComponent::descriptor + String16("/") + String16(instance.c_str()); + sp<IRemotelyProvisionedComponent> rpc = + android::waitForService<IRemotelyProvisionedComponent>(rpcName); + + auto rpcId = getRpcId(rpc); + if (!rpcId) { + continue; + } + if (*rpcId == targetRpcId) { + return rpcName; + } + } + + LOG(ERROR) << "Remotely provisioned component with given unique ID: " << targetRpcId + << " not found"; + return std::nullopt; +} + +std::optional<String16> getRpcName(const sp<IRemotelyProvisionedComponent>& rpc) { + std::optional<std::string> targetRpcId = getRpcId(rpc); + if (!targetRpcId) { + return std::nullopt; + } + return findRpcNameById(*targetRpcId); +} + +class GetKeyCallback : public BnGetKeyCallback { + public: + GetKeyCallback(std::promise<std::optional<RemotelyProvisionedKey>> keyPromise) + : keyPromise_(std::move(keyPromise)), called_() {} + + Status onSuccess(const RemotelyProvisionedKey& key) override { + if (called_.test_and_set()) { + return Status::ok(); + } + keyPromise_.set_value(key); + return Status::ok(); + } + Status onCancel() override { + if (called_.test_and_set()) { + return Status::ok(); + } + LOG(ERROR) << "GetKeyCallback cancelled"; + keyPromise_.set_value(std::nullopt); + return Status::ok(); + } + Status onError(IGetKeyCallback::ErrorCode error, const String16& description) override { + if (called_.test_and_set()) { + return Status::ok(); + } + LOG(ERROR) << "GetKeyCallback failed: " << static_cast<int>(error) << ", " << description; + keyPromise_.set_value(std::nullopt); + return Status::ok(); + } + + private: + std::promise<std::optional<RemotelyProvisionedKey>> keyPromise_; + // This callback can only be called into once + std::atomic_flag called_; +}; + +class GetRegistrationCallback : public BnGetRegistrationCallback { + public: + GetRegistrationCallback(std::promise<std::optional<RemotelyProvisionedKey>> keyPromise, + uint32_t keyId) + : keyPromise_(std::move(keyPromise)), keyId_(keyId), called_() {} + + Status onSuccess(const sp<IRegistration>& registration) override { + if (called_.test_and_set()) { + return Status::ok(); + } + auto cb = sp<GetKeyCallback>::make(std::move(keyPromise_)); + auto status = registration->getKey(keyId_, cb); + if (!status.isOk()) { + cb->onError(IGetKeyCallback::ErrorCode::ERROR_UNKNOWN, + String16("Failed to register GetKeyCallback")); + } + return Status::ok(); + } + Status onCancel() override { + if (called_.test_and_set()) { + return Status::ok(); + } + LOG(ERROR) << "GetRegistrationCallback cancelled"; + keyPromise_.set_value(std::nullopt); + return Status::ok(); + } + Status onError(const String16& error) override { + if (called_.test_and_set()) { + return Status::ok(); + } + LOG(ERROR) << "GetRegistrationCallback failed: " << error; + keyPromise_.set_value(std::nullopt); + return Status::ok(); + } + + private: + std::promise<std::optional<RemotelyProvisionedKey>> keyPromise_; + int32_t keyId_; + // This callback can only be called into once + std::atomic_flag called_; +}; + +} // namespace + +std::optional<std::future<std::optional<RemotelyProvisionedKey>>> +getRpcKeyFuture(const sp<IRemotelyProvisionedComponent>& rpc, int32_t keyId) { + std::promise<std::optional<RemotelyProvisionedKey>> keyPromise; + auto keyFuture = keyPromise.get_future(); + + auto rpcName = getRpcName(rpc); + if (!rpcName) { + LOG(ERROR) << "Failed to get IRemotelyProvisionedComponent name"; + return std::nullopt; + } + + sp<IRemoteProvisioning> remoteProvisioning = + android::waitForService<IRemoteProvisioning>(String16(kRemoteProvisioningServiceName)); + if (!remoteProvisioning) { + LOG(ERROR) << "Failed to get IRemoteProvisioning HAL"; + return std::nullopt; + } + + auto cb = sp<GetRegistrationCallback>::make(std::move(keyPromise), keyId); + Status status = remoteProvisioning->getRegistration(*rpcName, cb); + if (!status.isOk()) { + LOG(ERROR) << "Failed getRegistration()"; + return std::nullopt; + } + + return keyFuture; +} + +std::optional<RemotelyProvisionedKey> getRpcKey(const sp<IRemotelyProvisionedComponent>& rpc, + int32_t keyId, int32_t timeout_sec) { + auto rpcKeyFuture = getRpcKeyFuture(rpc, keyId); + if (!rpcKeyFuture) { + LOG(ERROR) << "Failed getRpcKeyFuture()"; + return std::nullopt; + } + + auto timeout = std::chrono::seconds(timeout_sec); + if (rpcKeyFuture->wait_for(timeout) != std::future_status::ready) { + LOG(ERROR) << "Waiting for remotely provisioned attestation key timed out"; + return std::nullopt; + } + + return rpcKeyFuture->get(); +} + +} // namespace android::security::rkp::support diff --git a/provisioner/support/test.cpp b/provisioner/support/test.cpp new file mode 100644 index 00000000..418eab9d --- /dev/null +++ b/provisioner/support/test.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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. + */ + +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <gtest/gtest.h> +#include <rkp/support/rkpd_client.h> + +using ::android::getAidlHalInstanceNames; +using ::android::sp; +using ::android::String16; +using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; +using ::android::security::rkp::RemotelyProvisionedKey; +using ::android::security::rkp::support::getRpcKey; + +// TODO(b/272600606): Add tests for error cases +class RkpdClientTest : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + auto rpcName = String16(GetParam().c_str()); + rpc_ = android::waitForService<IRemotelyProvisionedComponent>(rpcName); + ASSERT_NE(rpc_, nullptr); + } + + sp<IRemotelyProvisionedComponent> rpc_; +}; + +TEST_P(RkpdClientTest, getRpcKey) { + std::optional<RemotelyProvisionedKey> key = getRpcKey(rpc_, /*keyId=*/0); + + ASSERT_TRUE(key.has_value()) << "Failed to get remotely provisioned attestation key"; + ASSERT_FALSE(key->keyBlob.empty()) << "Key blob is empty"; + ASSERT_FALSE(key->encodedCertChain.empty()) << "Certificate is empty"; +} + +INSTANTIATE_TEST_SUITE_P( + PerInstance, RkpdClientTest, + testing::ValuesIn(getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor)), + ::android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + + // We need one thread to issue requests to RKPD and one to handle + // asynchronous responses from RKPD. + android::ProcessState::self()->setThreadPoolMaxThreadCount(2); + android::ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +} |