Vinculação de versão

No Keymaster 1, todas as chaves do Keymaster eram vinculadas criptograficamente à raiz de confiança do dispositivo ou à chave de inicialização verificada. No Keymaster 2 e 3, todas as chaves também são vinculadas ao sistema operacional e ao nível de patch da imagem do sistema. Isso garante que um invasor que descobrir uma vulnerabilidade em uma versão antiga do software do sistema ou da TEE não possa reverter um dispositivo para a versão vulnerável e usar chaves criadas com a versão mais recente. Além disso, quando uma chave com uma determinada versão e nível de patch é usada em um dispositivo que foi atualizado para uma versão ou nível de patch mais recente, a chave é atualizada antes de poder ser usada, e a versão anterior da chave é invalidada. Dessa forma, à medida que o dispositivo é atualizado, as chaves *avançam* junto com o dispositivo, mas qualquer reversão do dispositivo para uma versão anterior faz com que as chaves fiquem inutilizáveis.

Para ser compatível com a estrutura modular do Treble e romper a vinculação de system.img com boot.img, o Keymaster 4 alterou o modelo de vinculação de versão de chave para ter níveis de patch separados para cada partição. Isso permite que cada partição seja atualizada de maneira independente, fornecendo ainda proteção contra reversão.

Para implementar essa vinculação de versão, o app confiável (TA, na sigla em inglês) do KeyMint precisa de uma maneira de receber com segurança a versão atual do SO e os níveis de patch, além de garantir que as informações recebidas correspondam a todas as informações sobre o sistema em execução.

  • Os dispositivos com a Inicialização verificada do Android (AVB, na sigla em inglês) podem colocar todos os níveis de patch e a versão do sistema no vbmeta, para que o carregador de inicialização possa fornecê-los ao Keymaster. Para partições encadeadas, as informações de versão da partição estão no vbmeta encadeado. Em geral, as informações de versão precisam estar na vbmeta struct que contém os dados de confirmação de identidade (hash ou hashtree) de uma determinada partição.
  • Em dispositivos sem AVB:
    • As implementações da Inicialização verificada precisam fornecer um hash dos metadados da versão ao carregador de inicialização, para que ele possa fornecer o hash ao Keymaster.
    • boot.img pode continuar armazenando o nível de patch no cabeçalho.
    • system.img pode continuar armazenando o nível de patch e a versão do SO em propriedades somente leitura .
    • vendor.img armazena o nível de patch na propriedade somente leitura ro.vendor.build.version.security_patch.
    • O carregador de inicialização pode fornecer um hash de todos os dados validados pela Inicialização verificada ao Keymaster.
  • No Android 9, use as seguintes tags para fornecer informações de versão para as seguintes partições:
    • VENDOR_PATCH_LEVEL: partição vendor
    • BOOT_PATCH_LEVEL: partição boot
    • OS_PATCH_LEVEL e OS_VERSION: system partição. OS_VERSION é removido do o boot.img cabeçalho.
  • As implementações do Keymaster precisam tratar todos os níveis de patch de forma independente. As chaves podem ser usadas se todas as informações de versão corresponderem aos valores associados a uma chave, e IKeymaster::upgradeDevice() será revertido para um nível de patch mais alto, se necessário.

Mudanças na HAL

Para oferecer suporte à vinculação e à atestação de versão, o Android 7.1 adicionou as tags Tag::OS_VERSION e Tag::OS_PATCHLEVEL e os métodos configure e upgradeKey. As tags de versão são adicionadas automaticamente pelas implementações do Keymaster 2 e mais recentes a todas as chaves recém-geradas (ou atualizadas). Além disso, qualquer tentativa de usar uma chave que não tenha uma versão do SO ou um nível de patch correspondente à versão ou nível de patch do sistema atual, respectivamente, é rejeitada com ErrorCode::KEY_REQUIRES_UPGRADE.

Tag::OS_VERSION é um valor UINT que representa as partes principal, secundária e sub-secundária de uma versão do sistema Android como MMmmss, em que MM é a versão principal, mm é a versão secundária e ss é a versão sub-secundária. Por exemplo, 6.1.2 seria representado como 060102.

Tag::OS_PATCHLEVEL é um valor UINT que representa o ano e o mês da última atualização do sistema como YYYYMM, em que YYYY é o ano de quatro dígitos e MM é o mês de dois dígitos. Por exemplo, março de 2016 seria representado como 201603.

UpgradeKey

Para permitir que as chaves sejam atualizadas para a nova versão do SO e o nível de patch da imagem do sistema, o Android 7.1 adicionou o método upgradeKey à HAL:

Keymaster 3

    upgradeKey(vec keyBlobToUpgrade, vec upgradeParams)
        generates(ErrorCode error, vec upgradedKeyBlob);

Keymaster 2

keymaster_error_t (*upgrade_key)(const struct keymaster2_device* dev,
    const keymaster_key_blob_t* key_to_upgrade,
    const keymaster_key_param_set_t* upgrade_params,
    keymaster_key_blob_t* upgraded_key);
  • dev é a estrutura do dispositivo.
  • keyBlobToUpgrade é a chave que precisa ser atualizada.
  • upgradeParams são os parâmetros necessários para atualizar a chave. Isso inclui Tag::APPLICATION_ID e Tag::APPLICATION_DATA, que são necessários para descriptografar o blob de chave se fornecidos durante a geração.
  • upgradedKeyBlob é o parâmetro de saída, usado para retornar o novo blob de chave.

Se upgradeKey for chamado com um blob de chave que não pode ser analisado ou que é inválido, ele retornará ErrorCode::INVALID_KEY_BLOB. Se for chamado com uma chave cujo nível de patch seja maior que o valor do sistema atual, ele retornará ErrorCode::INVALID_ARGUMENT. Se for chamado com uma chave cuja versão do SO seja maior que o valor do sistema atual e o valor do sistema não seja zero, ele retornará ErrorCode::INVALID_ARGUMENT. As atualizações de versão do SO de não zero para zero são permitidas. Em caso de erros de comunicação com o mundo seguro, ele retorna um valor de erro apropriado (por exemplo, ErrorCode::SECURE_HW_ACCESS_DENIED, ErrorCode::SECURE_HW_BUSY). Caso contrário, ele retorna ErrorCode::OK e um novo blob de chave em upgradedKeyBlob.

keyBlobToUpgrade permanece válido após a chamada upgradeKey e, teoricamente, pode ser usado novamente se o dispositivo for rebaixado. Na prática, o keystore geralmente chama deleteKey no blob keyBlobToUpgrade logo após a chamada para upgradeKey. Se keyBlobToUpgrade tiver a tag Tag::ROLLBACK_RESISTANT, upgradedKeyBlob também precisará ter essa tag (e ser resistente à reversão).

Configuração segura

Para implementar a vinculação de versão, o TA do Keymaster precisa de uma maneira de receber com segurança a versão atual do SO e o nível de patch (informações de versão), além de garantir que as informações recebidas correspondam às informações sobre o sistema em execução.

Para oferecer suporte à entrega segura de informações de versão ao TA, um OS_VERSION campo foi adicionado ao cabeçalho da imagem de inicialização. O script de build da imagem de inicialização preenche esse campo automaticamente. Os OEMs e os implementadores do TA do Keymaster precisam trabalhar juntos para modificar os carregadores de inicialização do dispositivo para extrair as informações de versão da imagem de inicialização e transmiti-las ao TA antes que o sistema não seguro seja inicializado. Isso garante que os invasores não possam interferir no provisionamento de informações de versão para o TA.

Também é necessário garantir que a imagem do sistema tenha as mesmas informações de versão que a imagem de inicialização. Para isso, o método de configuração foi adicionado à HAL do Keymaster:

keymaster_error_t (*configure)(const struct keymaster2_device* dev,
  const keymaster_key_param_set_t* params);

O argumento params contém Tag::OS_VERSION e Tag::OS_PATCHLEVEL. Esse método é chamado pelos clientes do Keymaster 2 depois de abrir a HAL, mas antes de chamar qualquer outro método. Se qualquer outro método for chamado antes da configuração, o TA retornará ErrorCode::KEYMASTER_NOT_CONFIGURED.

A primeira vez que configure é chamado após a inicialização do dispositivo, ele precisa verificar se as informações de versão fornecidas correspondem ao que foi fornecido pelo carregador de inicialização. Se as informações de versão não corresponderem, configure retornará ErrorCode::INVALID_ARGUMENT, e todos os outros métodos do Keymaster continuarão retornando ErrorCode::KEYMASTER_NOT_CONFIGURED. Se as informações corresponderem, configure retornará ErrorCode::OK, e outros métodos do Keymaster começarão a funcionar normalmente.

As chamadas subsequentes para configure retornam o mesmo valor retornado pela primeira chamada e não mudam o estado do Keymaster.

Como configure é chamado pelo sistema cujo conteúdo ele pretende validar, há uma pequena janela de oportunidade para um invasor comprometer a imagem do sistema e forçá-la a fornecer informações de versão que correspondam à imagem de inicialização, mas que não sejam a versão real do sistema. A combinação da verificação da imagem de inicialização, da validação dm-verity do conteúdo da imagem do sistema e do fato de que configure é chamado muito cedo na inicialização do sistema deve dificultar a exploração dessa janela de oportunidade.