Bug: 310885032

Clone this repo:
  1. aec45dd Treat failure same as absence by David Drysdale · 3 days ago main master
  2. f13cfb7 Skip updating policy if existing one is same by Shikha Panwar · 13 days ago
  3. 4df3b8f Secretkeeper TA: Nothing to validate by Shikha Panwar · 10 weeks ago
  4. 08cc8f1 Add the DICE policy tests to TEST_MAPPING by Alan Stokes · 3 weeks ago
  5. 528f54a Secretkeeper: Reduce max parallel session to 4 by Shikha Panwar · 7 weeks ago

Secretkeeper

Secretkeeper provides secure storage of secrets on behalf of other components in Android. It is specified as a HAL and must be implemented in an environment with privilege higher than any of its clients. Typically this will be a trusted execution environment such as ARM TrustZone.

The core SecretManagement API is a CBOR based protocol and can be used to store (& get) 32 bytes of secret data. Secretkeeper supports establishing a secure channel with clients as well as deletion of some or all data.

AuthGraph key exchange

The requests (from the client) & responses (from Secretkeeper) must be encrypted using symmetric keys agreed between the client & service. For this, Secretkeeper (& client) must implement the AuthGraph key exchange protocol to establish a secure channel between them.

In the key exchange protocol, the client acts as P1 (source) and Secretkeeper as P2 (sink). The interface returned by getAuthGraphKe() can be used to invoke methods on the sink.

Policy Gated Storage

The storage layer of Secretkeeper, in addition to conventional storage, provides DICE policy based access control. A client can restrict the access to its stored entry.

Storage

The underlying storage of Secretkeeper should offer the following security guarantees:

  1. Confidentiality: No entity (of security privilege lower than Secretkeeper) should be able to get a client's data in the clear.
  2. Integrity: The data is protected against malicious Android OS tampering with database. i.e., if Android (userspace & kernel) tampers with the client's secret, the Secretkeeper service must be able to detect it & return an error when clients requests for their secrets. The integrity requirements also include rollback protection i.e., reverting the database into an old state should be detected.
  3. Persistence: The data is persistent across device boot. Note: Denial of service is not in scope. A malicious Android may be able to delete data. In ideal situation, the data should be persistent.

Access control

Secretkeeper uses DICE policy based access control. Each secret is associated with a sealing policy, which is a DICE policy. This is a required input while storing a secret. Further access to this secret is restricted to clients whose DICE chain adheres to the corresponding sealing policy.

Reference Implementation

Android provides a reference implementation of Secretkeeper as well as the required AuthGraph Key exchange HAL. The implementation is modular and easily configurable. For example, partners can plug in their implementation of AES-GCM, RNG instead of using the BoringSSL implementations.

Navigating this project:

  1. ./core/: Contains the reference implementation of Secretkeeper TA.
  2. ./hal/: Contains the reference implementation of Secretkeeper HAL.
  3. ./client/: A client library for Secretkeeper, which can be used for managing sessions with Secretkeeper, sending requests etc.
  4. ./comm/: Secretkeeper is a CBOR heavy protocol, the Rust definition of Requests/Response/Arguments/Errors is contained in this directory. Additionally, Rust types for supporting the CBOR based communication between the TA and HAL is also exported here. This is used by secretkeeper_core, secretkeeper_hal and secretkeeper_client.
  5. ./dice_policy/: Contains code for building dice_policies as well as well as matching them against Dice chain. As explained here, this forms the foundation of Policy Gated Storage in Secretkeeper.

Outside this directory:

  1. Secretkeeper HAL Spec: hardware/interfaces/security/secretkeeper/aidl/android/hardware/security/secretkeeper/ISecretkeeper.aidl
  2. Vendor Test Suite for Secretkeeper
  3. HAL Integration for Trusty
  4. TA Integration for Trusty
  5. Command line test tool for interacting with Secretkeeper
  6. AuthGraph HAL Spec & Test Suite
  7. AuthGraph Reference implementation: system/authgraph/

Porting to a Device

To use the Rust reference implementation on an Android device, device-specific implementations of various abstractions must be provided. This section describes the different areas of functionality that are required.

Rust Toolchain and Heap Allocator

Using the reference implementation requires a Rust toolchain that can target the secure environment. This toolchain (and any associated system libraries) must also support heap allocation (or an approximation thereof) via the alloc sysroot crate.

If the BoringSSL-based implementation of cryptographic functionality is used (see below), then some parts of the Rust std library must also be provided, in order to support the compilation of the openssl wrapper crate.

Checklist:

  • [ ] Rust toolchain that targets secure environment.
  • [ ] Heap allocation support via alloc.

HAL Service

Secretkeeper appears as a HAL service in userspace, and so an executable that registers for and services the Secretkeeper HAL must be provided.

The implementation of this service is mostly provided by the secretkeeper_hal crate (and by the associated authgraph_hal crate), but a driver program must be provided that:

  • Performs start-of-day administration (e.g. logging setup, panic handler setup).
  • Creates communication channels to the Secretkeeper TA.
  • Registers for the Secretkeeper HAL service.
  • Starts a thread pool to service requests.

The Secretkeeper HAL service (which runs in userspace) must communicate with the Secretkeeper TA (which runs in the secure environment). The reference implementation assumes the existence of two reliable, message-oriented, bi-directional communication channels for this (one for Secretkeeper, one for AuthGraph), as encapsulated in the authgraph_hal::channel::SerializedChannel trait.

This trait has a single method execute(), which takes as input a request message (as bytes), and returns a response message (as bytes) or an error.

Two instances of this trait must be provided to the secretkeeper_hal::SecretkeeperService type, which allows it to service Binder requests by forwarding the requests to the TA as request/response pairs.

Checklist:

  • [ ] Implementation of HAL service, which registers the Secretkeeper HAL service with Binder.
  • [ ] SELinux policy for the HAL service.
  • [ ] init.rc configuration for the HAL service.
  • [ ] Implementation of SerializedChannel trait, for reliable HAL <-> TA communication.

The Trusty implementation of the Secretkeeper HAL provides an example of all of the above.

TA Driver

The secretkeeper_core::ta module provides the majority of the implementation of the Secretkeeper TA, but needs a driver program that:

  • Performs start-of-day administration (e.g. logging setup).
  • Creates an authgraph_core::ta::AuthGraphTa instance.
  • Creates a secretkeeper_core_ta::SecretkeeperTa instance.
  • Configures the pair of communication channels that communicate with the HAL service (one for Secretkeeper, one for AuthGraph).
  • Configures the communication channel with the bootloader, which is required so that the current Secretkeeper identity information can be retrieved at start-of-day.
  • Holds the main loop that:
    • reads request messages from the channel(s)
    • passes request messages to SecretkeeperTa::process() or AuthGraphTa::process(), receiving a response
    • writes response messages back to the relevant channel.

Checklist:

  • [ ] Implementation of main equivalent for TA, handling scheduling of incoming requests.
  • [ ] Implementation of communication channels between HAL service and TA.
  • [ ] Implementation of communication channel between bootloader and TA.

The Trusty implementation of the Secretkeeper TA provides an example of all of the above.

Bootloader

If Secretkeeper is used to store secrets on behalf of protected virtual machines (pVMs), then the bootloader is required to retrieve the identity of Secretkeeper (expressed as a public key) at boot time so that the identity can be (securely) provided to pVM instances, as described below. The bootloader should use the secretkeeper_core::ta::bootloader::GetIdentityKey message to do this.

Checklist:

  • [ ] Implementation of communication channel from bootloader to TA.
  • [ ] Send GetIdentityKey request at boot time.
  • [ ] Populate the relevant device tree property.

Cryptographic Abstractions

The Secretkeeper TA requires implementations for low-level cryptographic primitives to be provided, in the form of implementations of the various Rust traits held in authgraph_core::traits.

Note that some of these traits include methods that have default implementations, which means that an external implementation is not required (but can be provided if desired).

Checklist:

  • [ ] RNG implementation.
  • [ ] AES-GCM implementation.
  • [ ] HMAC implementation.
  • [ ] ECDH implementation with P-256.
  • [ ] ECDSA implementation (P-256, P-384, Ed25519).

BoringSSL-based implementations are available for all of the above.

Device-Specific Abstractions

The Secretkeeper requires an implementation of the secretkeeper_core::store::KeyValueStore trait that abstracts away access to secure storage.

The Trusty implementation of the Secretkeeper TA includes an example implementation.

Checklist:

  • [ ] KeyValueStore implementation.

Example usage: Rollback protected secrets of Microdroid based pVMs

Microdroid instances use Secretkeeper to store their secrets while protecting against the Rollback attacks on the boot images & packages. Such secrets (and data protected by the secrets) are accessible on updates but not on downgrades of boot images and apks.

Secretkeeper public key

As described above, Microdroid as a Secretkeeper client establishes a secure channel with the Secretkeeper implementation using the AuthGraph key exchange protocol. As part of this Microdroid needs to verify that it is communicating with the real Secretkeeper.

To achieve this the Secretkeeper implementation should generate a per-boot key pair and use that as its identity in the AuthGraph protocol. The public key from the pair then needs to be securely communicated to the Microdroid VM which uses it to verify the identity of Secretkeeper.

The public key is transported as a CBOR-encoded COSE_key, as a PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384 as defined in generateCertificateRequestV2.cddl.

Microdroid expects the public key to be present in the Linux device tree as the value of the secretkeeper_public_key property of the /avf node - exposed to userspace at /proc/device-tree/avf/secretkeeper_public_key.

When a protected VM is started, AVF populates this property in the VM DT /avf node from the corresponding property in the /avf/reference/avf node in the host DT. pvmfw verifies that the value is correct using the VM reference DT that is included in the pvmfw configuration data.

The Android bootloader should request the public key from the Secretkeeper implementation at boot time and populate it in both the host Android DT and the VM Reference DT for pvmfw.

The reference code for Secretkeeper defines a protocol that can be used by the bootloader to retrieve the public key; see core/src/ta/bootloader.rs.