tree: 6d2e0b5b4749ceca901f5094c556133feb9b76d3 [path history] [tgz]
  1. card/
  2. include/
  3. tests/
  4. Android.bp
  5. boot.c
  6. boot_private.h
  7. ese_boot_tool.cpp
  8. README.md
apps/boot/README.md

Verified Boot Storage Applet for AVB 2.0

  • Status: Draft as of April 6, 2017

Introduction

The application and support libraries in this directory provide a mechanism for a device's bootloader, using AVB, to store sensitive information. For a bootloader, sensitive information includes whether the device is unlocked or locked, whether it is unlockable, and what the minimum version of the OS/kernel is allowed to be booted. It may also store other sensitive flags.

The verified boot storage applet provides a mechanism to store this data in a way that enforceѕ the expected policies even if the higher level operating system is compromised or operates in an unexpected fashion.

Design Overview

The Verified Boot Storage Applet, VBSA, provides three purpose-built interfaces:

  • Lock storage and policy enforcement
  • Rollback index storage
  • Applet state

Each will be detailed below.

Locks

There are four supported lock types:

  • LOCK_CARRIER
  • LOCK_DEVICE
  • LOCK_BOOT
  • LOCK_OWNER

Each lock has a single byte of "lock" storage. If that byte is 0x0, then it is unlocked, or cleared. If it is non-zero, then the lock is locked. Any non-zero value is valid and may be used by the bootloader if any additional internal flagging is necessary.

In addition, a lock may have associated metadata which must be supplied during lock or unlock, or both.

See ese_boot_lock_* in include/ese/app/boot.h for the specific interfaces.

LOCK_CARRIER

The Carrier Lock implements a lock which can only be set when the device is not in production and can only be unlocked if provided a cryptographic signature.

This lock is available for use to implement "sim locking" or "phone locking" such that the carrier can determine if the device is allowed to boot an unsigned or unknown system image.

To provision this lock, device-specific data must be provided in an exact format. An example of this can be found in 'ese-boot-tool.cpp':collect_device_data(). This data is then provided to the applet using ese_boot_lock_xset().

Metadata format for locking/provisioning

The following system attributes must be collected in the given order:

  • ro.product.brand
  • ro.product.device
  • ro.build.product
  • ro.serialno
  • [Modem ID: MEID or IMEI]
  • ro.product.manufacturer
  • ro.product.model

The data is serialized as follows:

\[length||string\]\[...\]

The length is a uint8_t and the value is appended as a stream of uint8_t values.

This data is then prefixed with the desired non-zero lock value before being submitted to the applet. If successful, the applet will have stored a SHA256 hash of the device data

Note, LOCK_CARRIER can only be locked (non-zero lock value) when the applet is not in 'production' mode.

Clearing/unlocking

If LOCK_CARRIER is set to a non-zero value and the applet is in production mode, then clearing the lock value requires authorization.

Authorization comes in the form of a RSA_SHA256-PKCS#1 signature over the provisioned device data SHA256 hash and a supplied montonically increasing "nonce".

The nonce value must be higher than the last seen nonce value and the signature must validate using public key internally stored in the applet (CarrierLock.java:PK_MOD).

To perform a clear, ese_boot_lock_xset() must be called with lock data that begins with 0x0, to clear the lock, and then contains data of the following format:

    unlockToken = VERSION || NONCE || SIGNATURE

    SIGNATURE = RSA_Sign(SHA256(deviceData))
  • The version is a little endian uint64_t (8 bytes).
  • The nonce is a little endian uint64_t (8 bytes).
  • The signature is a RSA 2048-bit with SHA-256 PKCS#1 v1.5 (256 bytes).

On unlock, the device data hash is cleared.

Testing

It is possible to test the key with a valid signature but a fake internal nonce and fake internal device data using ese_boot_carrier_lock_test(). When using this interface, it expects the same unlock token as in the prior but prefixes with the fake data:

    testVector = LAST_NONCE || DEVICE_DATA || unlockToken
  • The last nonce is the value the nonce is compared against (8 bytes).
  • Device data is a replacement for the internally stored SHA-256 hash of the deviec data. (32 bytes).

LOCK_DEVICE

The device lock is one of the setting used by the bootloader to determine if the boot lock can be changed. It may only be set by the operating system and is meant to protect the device from being reflashed by someone that cannot unlock or access the OS. This may also be used by an enterprise administrator to control lock policy for managed devices.

As LOCK_DEVICE has not metadata, it can be set and retrieved using ese_boot_lock_set() and ese_boot_lock_get().

LOCK_BOOT

The boot lock is used by the bootloader to control whether it should only boot verified system software or not. When the lock value is cleared (0x0), the bootloader will boot anything. When the lock value is non-zero, it should only boot software that is signed by a key stored in the bootloader except if LOCK_OWNER is set. Discussion of LOCK_OWNER will follow.

LOCK_BOOT can only be toggled when in the bootloader/fastboot and if both LOCK_CARRIER and LOCK_DEVICE are cleared/unlocked.

As with LOCK_DEVICE, LOCK_BOOT has no metadata so it does not need the extended accessors.

LOCK_OWNER

The owner lock is used by the bootloader to support an alternative OS signing key provided by the device owner. LOCK_OWNER can only be toggled if LOCK_BOOT is cleared. LOCK_OWNER does not require any metadata to unlock, but to lock, it requires a blob of up to 2048 bytes be provided. This is just secure storage for use by the bootloader. LOCK_OWNER may be toggled in the bootloader or the operating system. This allows an unlocked device (LOCK_BOOT=0x0) to install an owner key using fastboot or using software on the operating system itself.

Before LOCK_OWNER's key should be honored by the bootloader, LOCK_BOOT should be set (in the bootloader) to enforce use of the key and to keep malicious software in the operating system from changing it.

(Note, that the owner key should not be treated as equivalent to the bootloader's internally stored key in that the device user should have a means of knowing if an owner key is in use, but the requirement for the device to be unlocked implies both physical access the LOCK_DEVICE being cleared.)

Rollback storage

Verifying an operating system kernel and image is useful both for system reliability and for ensuring that the software has not been modified by a malicious party. However, if the system software is updated, malicious software may attempt to "roll" a device back to an older version in order to take advantage of mistakes in the older, verified code.

Rollback index values, or versions, may be stored securely by the bootloader to prevent these problems. The Verified Boot Storage applet provides eight 64-bit slots for storing a value. They may be read at any time, but they may only be written to when the device is in the bootloader (or fastboot).

Rollback storage is written to using ese_boot_rollback_index_write() and read using ese_boot_rollback_index_read().

Applet state

The applet supports two operational states:

  • production=true
  • production=false

On initial installation, production is false. When the applet is not in production mode, it does not enforce a number of security boundaries, such as requiring bootloader or hlos mode for lock toggling or CarrierLock verification. This allows the factory tools to run in any mode and properly configure a default lock state.

To transition to "production", a call to ese_boot_set_production(true) is necessary.

To check the state and collect debugging information, the call ese_boot_get_state() will return the current bootloader value, the production state, any errors codes from lock initialization, and the contents of lock storage.

Example applet provisioning

After the applet is installed, a flow as follows would prepare the applet for use:

  • ese_boot_session_init();
  • ese_boot_session_open();
  • ese_boot_session_lock_xset(LOCK_OWNER, {0, ...});
  • ese_boot_session_lock_set(LOCK_BOOT, 0x1);
  • ese_boot_session_lock_set(LOCK_DEVICE, 0x1);
  • [collect device data]
  • ese_boot_session_lock_set(LOCK_CARRIER, {1, deviceData});
  • ese_boot_session_set_production(true);
  • ese_boot_session_close();

Additional details

Bootloader mode

Bootloader mode detection depends on hardware support to signal the applet that the application processor has been reset. Additionally, there is a mechanism for the bootloader to indicate that is loading the main OS where it flips the value. This signal provides the assurances around when storage is mutable or not during the time a device is powered on.

Error results

EseAppResult is an enum that all ese_boot_* functions return. The enum only covers the lower 16-bits. The upper 16-bits are reserved for passing applet and secure element OS status for debugging and analysis. When the lower 16-bits are ESE_APP_RESULT_ERROR_APPLET, then the upper bytes will be the applet code. That code can then be cross-referenced in the applet by function and code. If the lower bytes are ESE_APP_RESULT_ERROR_OS, then the status code are the ISO7816 code from an uncaught exception or OS-level error.

Cooldown

ESE_APP_RESULT_ERROR_COOLDOWN indicates that the secure element needs to stay powered on for a period of time -- either at the end of use or before the requested command can be serviced. As the behavior is implementation specific, the only effective option is to keep the secure element powered for the number of seconds specified by the response uint32_t.

For chips that support it, like the one this applet is being tested on, the cooldown time can be requested with a special APDU to ese_transceive():

    FFE10000

In response, a 6 byte response will contain a uint32_t and a successful status code (90 00). The unsigned little-endian integer indicates how many seconds the chip needs to stay powered and unused to cooldown. If this happens before the locks or rollback storage can be read, the bootloader will need to determine a safe delay or recovery path until boot can proceed securely.

Examples

There are many ways to integrate this library and the associated applet. Below are some concrete examples to guide standard approach.

Configuration in factory

  • Install configure the secure element and install the applets
    (outside of the scope of this document).
  • Boot to an environment to run the ese-boot-tool.
  • Leave the inBootloader() signal asserted (recommended but not required).
  • Configure the desired lock states:
    • # ese-boot-tool lock set carrier 1 modem-imei-string
    • # ese-boot-tool lock set device 1
    • # ese-boot-tool lock set boot 1
    • # ese-boot-tool lock set owner 0
  • To move from factory mode to production mode call:
    • # ese-boot-tool production set true

Configuration during repair

  • Boot to an environment to run the ese-boot-tool.
  • Leave inBootloader() signal asserted or implement the steps below in
    the bootloader.
  • Transition out of production mode:
    • # ese-boot-tool production set false
  • If a LOCK_CARRIER problem is being repaired, it is possible to reset the
    internal nonce counter and all lock state with the command below. A full
    lock reset is not expected in most cases.
    • # ese-boot-tool lock reset
  • Reconfigure the lock states:
    • # ese-boot-tool lock set carrier 1 modem-imei-string
    • # ese-boot-tool lock set device 1
    • # ese-boot-tool lock set boot 1
    • # ese-boot-tool lock set owner 0 (To clear data from the owner lock, set owner 1 must be called with
      4096 00s.)
  • Then move back to production mode:
    • # ese-boot-tool production set true

Use during boot

Do not load any non-repair or non-factory OS without clearing the inBootloader signal as the applet may be transitioned out of production mode and/or the rollback state may be changed.

Checking rollback values

  • Read and write rollback values as per libavb using the API
    • ese_boot_rollback_index_write()
    • ese_boot_rollback_index_read()
  • Prior to leaving the bootloader, clear the inBootloader signal.

As rollback index values can only be written when inBootloader signal is set, it is critical to clear it when leaving the bootloader.

Checking locks

The pseudo-code and comments below should outline the basic algorithm, but it does not include integration into libavb or include use of rollback index value checking:

// Read LOCK_BOOT
ese_boot_lock_get(session, kEseBootLockIdBoot, &lockBoot);

if (lockBoot != 0x0) {  // Boot is LOCKED.
    // Read the LOCK_OWNER
    ese_boot_lock_xget(session, kEseBootLockIdOwner, &lockOwner);
    if (lockOwner != 0x0) {  // Owner is LOCKED
      // Get the lock owner value with metadata.
      // This is done as a second stage to avoid wasted copying when it
      // is not locked.
      uint8_t ownerData[kEseBootOwnerKeyMax + 1];
      ese_boot_lock_xget(session, kEseBootLockIdOwner, ownerData
                         sizeof(ownerData), &ownerDataUsed);
      // lockOwner == ownerData[0]
      // Parse the stored metadata into a key as per your bootloader
      // design.
      SomeBootKey key;
      parseDeviceOwnerKeyForBooting(ownerData + 1, ownerDataUsed, &key);
      // Boot using the supplied owner key
      // (E.g., as part of avb_validate_vbmeta_public_key())
      setDeviceOwnerKeyForBooting(&key);
      continueBootFlow();
} else {  // Boot is UNLOCKED (0x0)
    // Perform the boot flow.
    setBootIsUnverified();
    continueBootFlow();
}

In fastboot

  • LOCK_BOOT may be toggled by a fastboot command. If the conditions of
    unlock are not allowed by applet policy, it will fail.
  • LOCK_OWNERmay be toggled and set a boot key from a fastboot command
    or from an unlocked OS image.
  • If the verified boot design dictates that rollback indices are clear on
    lock/unlock, this can be done by calling
    • ese_boot_rollback_index_write() on each slot with the value of 0.

Note, LOCK_DEVICE and LOCK_CARRIER should not need to be used by fastboot.

For debugging and support, it may be desirable to connect the ese_boot_get_state() to allow fastboot to return the current value of production, inbootloader, and the lock metadata.