Armazenamento de chaves com suporte de hardware

A disponibilidade de um ambiente de execução confiável em um sistema em um chip (SoC) oferece uma oportunidade para os dispositivos Android fornecerem serviços de segurança robustos e apoiados por hardware para o sistema operacional Android, serviços de plataforma e até aplicativos de terceiros. Os desenvolvedores que procuram as extensões específicas do Android devem acessar android.security.keystore .

Antes do Android 6.0, o Android já tinha uma API de serviços de criptografia baseada em hardware simples, fornecida pelas versões 0.2 e 0.3 do Keymaster Hardware Abstraction Layer (HAL). O Keystore forneceu assinatura digital e operações de verificação, além de geração e importação de pares de chaves de assinatura assimétrica. Isso já está implementado em muitos dispositivos, mas há muitos objetivos de segurança que não podem ser alcançados facilmente apenas com uma API de assinatura. O Keystore no Android 6.0 estendeu a API Keystore para fornecer uma gama mais ampla de recursos.

No Android 6.0, o Keystore adicionou primitivas criptográficas simétricas , AES e HMAC e um sistema de controle de acesso para chaves com suporte de hardware. Os controles de acesso são especificados durante a geração da chave e aplicados durante a vida útil da chave. As chaves podem ser restritas para serem usadas somente depois que o usuário for autenticado e apenas para fins específicos ou com parâmetros criptográficos especificados. Para obter mais informações, consulte as páginas Tags e funções de autorização.

Além de expandir a variedade de primitivos criptográficos, o Keystore no Android 6.0 adicionou o seguinte:

  • Um esquema de controle de uso para permitir que o uso de chaves seja limitado, para mitigar o risco de comprometimento da segurança devido ao uso indevido de chaves
  • Um esquema de controle de acesso para permitir a restrição de chaves para usuários e clientes especificados e um intervalo de tempo definido

No Android 7.0, o Keymaster 2 adicionou suporte para atestado de chave e vinculação de versão. O atestado de chave fornece certificados de chave pública que contêm uma descrição detalhada da chave e seus controles de acesso, para tornar a existência da chave em hardware seguro e sua configuração verificável remotamente.

A vinculação de versão vincula as chaves ao sistema operacional e à versão de nível de patch. Isso garante que um invasor que descubra uma fraqueza em uma versão antiga do sistema ou do software TEE não possa reverter um dispositivo para a versão vulnerável e usar as 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. À medida que o dispositivo é atualizado, as chaves "catraca" 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.

No Android 8.0, o Keymaster 3 fez a transição da camada de abstração de hardware (HAL) de estrutura C antiga para a interface HAL C++ gerada a partir de uma definição na nova linguagem de definição de interface de hardware (HIDL). Como parte da mudança, muitos dos tipos de argumento foram alterados, embora tipos e métodos tenham uma correspondência um-para-um com os tipos antigos e os métodos de estrutura HAL. Consulte a página Funções para obter mais detalhes.

Além dessa revisão de interface, o Android 8.0 estendeu o recurso de atestado do Keymaster 2 para oferecer suporte ao atestado de ID . O atestado de ID fornece um mecanismo limitado e opcional para atestar fortemente os identificadores de hardware, como número de série do dispositivo, nome do produto e ID do telefone (IMEI/MEID). Para implementar essa adição, o Android 8.0 alterou o esquema de atestado ASN.1 para adicionar atestado de ID. As implementações do Keymaster precisam encontrar uma maneira segura de recuperar os itens de dados relevantes, bem como definir um mecanismo para desabilitar o recurso de forma segura e permanente.

No Android 9, as atualizações incluíam:

  • Atualização para Keymaster 4
  • Suporte para elementos seguros incorporados
  • Suporte para importação de chave segura
  • Suporte para criptografia 3DES
  • Alterações na vinculação de versão para que boot.img e system.img tenham versões definidas separadamente para permitir atualizações independentes

Glossário

Aqui está uma visão geral rápida dos componentes do Keystore e seus relacionamentos.

AndroidKeystore é a API do Android Framework e o componente usado por aplicativos para acessar a funcionalidade do Keystore. Ele é implementado como uma extensão das APIs padrão da Arquitetura de Criptografia Java e consiste em código Java que é executado no próprio espaço de processo do aplicativo. AndroidKeystore atende às solicitações do aplicativo para o comportamento do Keystore, encaminhando-as para o daemon do keystore.

O daemon do keystore é um daemon do sistema Android que fornece acesso a todas as funcionalidades do Keystore por meio de uma API do Binder . Ele é responsável por armazenar "blobs de chaves", que contêm o material da chave secreta real, criptografado para que o Keystore possa armazená-los, mas não usá-los ou revelá-los.

keymasterd é um servidor HIDL que fornece acesso ao Keymaster TA. (Este nome não é padronizado e é para fins conceituais.)

Keymaster TA (aplicativo confiável) é o software executado em um contexto seguro, na maioria das vezes em TrustZone em um ARM SoC, que fornece todas as operações seguras do Keystore, tem acesso ao material bruto da chave, valida todas as condições de controle de acesso nas chaves , etc

LockSettingsService é o componente do sistema Android responsável pela autenticação do usuário, tanto por senha quanto por impressão digital. Não faz parte do Keystore, mas é relevante porque muitas operações de chave do Keystore exigem autenticação do usuário. LockSettingsService interage com o Gatekeeper TA e o Fingerprint TA para obter tokens de autenticação, que são fornecidos ao daemon de armazenamento de chaves e que são consumidos pelo aplicativo Keymaster TA.

Gatekeeper TA (aplicativo confiável) é outro componente executado no contexto seguro, responsável por autenticar senhas de usuários e gerar tokens de autenticação usados ​​para provar ao Keymaster TA que uma autenticação foi feita para um determinado usuário em um determinado momento.

Fingerprint TA (trusted application) é outro componente executado no contexto seguro que é responsável por autenticar as impressões digitais do usuário e gerar tokens de autenticação usados ​​para provar ao Keymaster TA que uma autenticação foi feita para um determinado usuário em um determinado momento.

Arquitetura

A API do Android Keystore e o Keymaster HAL subjacente fornecem um conjunto básico, mas adequado, de primitivas criptográficas para permitir a implementação de protocolos usando chaves suportadas por hardware e com controle de acesso.

O Keymaster HAL é uma biblioteca carregável dinamicamente fornecida pelo OEM usada pelo serviço Keystore para fornecer serviços criptográficos com suporte de hardware. Para manter as coisas seguras, as implementações de HAL não executam nenhuma operação sensível no espaço do usuário ou mesmo no espaço do kernel. As operações confidenciais são delegadas a um processador seguro alcançado por meio de alguma interface do kernel. A arquitetura resultante se parece com isso:

Acesso ao Keymaster

Figura 1. Acesso ao Keymaster

Dentro de um dispositivo Android, o "cliente" do Keymaster HAL consiste em várias camadas (por exemplo, aplicativo, framework, daemon Keystore), mas isso pode ser ignorado para os propósitos deste documento. Isso significa que a API Keymaster HAL descrita é de baixo nível, usada por componentes internos da plataforma e não exposta a desenvolvedores de aplicativos. A API de nível superior é descrita no site do desenvolvedor Android .

O objetivo do Keymaster HAL não é implementar os algoritmos sensíveis à segurança, mas apenas empacotar e desempacotar solicitações para o mundo seguro. O formato do fio é definido pela implementação.

Compatibilidade com versões anteriores

O Keymaster 1 HAL é completamente incompatível com os HALs lançados anteriormente, por exemplo, Keymaster 0.2 e 0.3. Para facilitar a interoperabilidade em dispositivos que executam o Android 5.0 e versões anteriores que foram lançados com os Keymaster HALs mais antigos, o Keystore fornece um adaptador que implementa o Keymaster 1 HAL com chamadas para a biblioteca de hardware existente. O resultado não pode fornecer toda a gama de funcionalidades do Keymaster 1 HAL. Em particular, ele suporta apenas algoritmos RSA e ECDSA, e toda a imposição de autorização de chave é executada pelo adaptador, no mundo não seguro.

O Keymaster 2 simplificou ainda mais a interface HAL removendo os métodos get_supported_* e permitindo que o método finish() aceitasse entrada. Isso reduz o número de viagens de ida e volta ao TEE nos casos em que a entrada está disponível de uma só vez e simplifica a implementação da descriptografia AEAD.

No Android 8.0, o Keymaster 3 fez a transição da estrutura C de estilo antigo HAL para a interface C++ HAL gerada a partir de uma definição na nova linguagem de definição de interface de hardware (HIDL). Uma implementação de HAL de novo estilo é criada pela subclasse da classe IKeymasterDevice gerada e implementando os métodos virtuais puros. Como parte da mudança, muitos dos tipos de argumento foram alterados, embora tipos e métodos tenham uma correspondência um-para-um com os tipos antigos e os métodos de estrutura HAL.

Visão geral do HIDL

A linguagem de definição de interface de hardware (HIDL) fornece um mecanismo independente de linguagem de implementação para especificar interfaces de hardware. As ferramentas HIDL atualmente suportam a geração de interfaces C++ e Java. Espera-se que a maioria dos implementadores do TEE (Trusted Execution Environment) ache as ferramentas C++ mais convenientes, portanto, este documento discute apenas a representação C++.

As interfaces HIDL consistem em um conjunto de métodos, expressos como:

  methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);

Existem vários tipos predefinidos, e as HALs podem definir novos tipos enumerados e de estrutura. Para obter mais detalhes sobre HIDL, consulte a seção Referência .

Um método de exemplo do Keymaster 3 IKeymasterDevice.hal é:

generateKey(vec<KeyParameter> keyParams)
        generates(ErrorCode error, vec<uint8_t> keyBlob,
                  KeyCharacteristics keyCharacteristics);

Isso é o equivalente ao seguinte do keymaster2 HAL:

keymaster_error_t (*generate_key)(
        const struct keymaster2_device* dev,
        const keymaster_key_param_set_t* params,
        keymaster_key_blob_t* key_blob,
        keymaster_key_characteristics_t* characteristics);

Na versão HIDL, o argumento dev é removido porque é implícito. O argumento params não é mais uma estrutura contendo um ponteiro referenciando uma matriz de objetos key_parameter_t , mas um vec (vetor) contendo objetos KeyParameter . Os valores de retorno são listados na cláusula " generates ", incluindo um vetor de valores uint8_t para o blob de chaves.

O método virtual C++ gerado pelo compilador HIDL é:

Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams,
                         generateKey_cb _hidl_cb) override;

Onde generateKey_cb é um ponteiro de função definido como:

std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
                   const KeyCharacteristics& keyCharacteristics)>

Ou seja, generateKey_cb é uma função que recebe os valores de retorno listados na cláusula generate. A classe de implementação HAL substitui esse método generateKey e chama o ponteiro de função generateKey_cb para retornar o resultado da operação ao chamador. Observe que a chamada do ponteiro de função é síncrona . O chamador chama generateKey e generateKey chama o ponteiro de função fornecido, que é executado até a conclusão, retornando o controle para a implementação generateKey , que então retorna ao chamador.

Para obter um exemplo detalhado, consulte a implementação padrão em hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp . A implementação padrão fornece compatibilidade com versões anteriores para dispositivos com keymaster0, keymaster1 ou keymaster2 HALS de estilo antigo.

Controle de acesso

A regra mais básica do controle de acesso ao Keystore é que cada aplicativo tenha seu próprio namespace. Mas para toda regra existe uma exceção. O keystore tem alguns mapas codificados que permitem que determinados componentes do sistema acessem determinados outros namespaces. Este é um instrumento muito contundente, pois dá a um componente controle total sobre outro namespace. E depois há a questão dos componentes do fornecedor como clientes para o Keystore. Atualmente, não temos como estabelecer um namespace para componentes de fornecedores, por exemplo, suplicante WPA.

Para acomodar os componentes do fornecedor e generalizar o controle de acesso sem exceções codificadas, o Keystore 2.0 apresenta domínios e namespaces SELinux.

Domínios de armazenamento de chaves

Com domínios Keystore, podemos desacoplar namespaces de UIDs. Os clientes que acessam uma chave no Keystore precisam especificar o domínio, o namespace e o alias que desejam acessar. Com base nessa tupla e na identidade do chamador, podemos determinar qual chave o chamador deseja acessar e se possui permissões apropriadas.

Apresentamos cinco parâmetros de domínio que controlam como as chaves podem ser acessadas. Eles controlam a semântica do parâmetro namespace do descritor de chave e como o controle de acesso é realizado.

  • DOMAIN_APP : o domínio do aplicativo abrange o comportamento legado. O Java Keystore SPI usa esse domínio por padrão. Quando esse domínio é usado, o argumento namespace é ignorado e o UID do chamador é usado em seu lugar. O acesso a este domínio é controlado pelo rótulo Keystore para a classe keystore_key na política do SELinux.
  • DOMAIN_SELINUX : Este domínio indica que o namespace tem um rótulo na política SELinux. O parâmetro namespace é consultado e traduzido em um contexto de destino, e uma verificação de permissão é executada para o contexto SELinux de chamada para a classe keystore_key . Quando a permissão for estabelecida para a operação especificada, a tupla completa será usada para a pesquisa de chave.
  • DOMAIN_GRANT : O domínio de concessão indica que o parâmetro de namespace é um identificador de concessão. O parâmetro alias é ignorado. As verificações do SELinux são executadas quando a concessão é criada. O controle de acesso adicional apenas verifica se o UID do chamador corresponde ao UID dos beneficiários da concessão solicitada.
  • DOMAIN_KEY_ID : Este domínio indica que o parâmetro de namespace é um ID de chave exclusivo. A própria chave pode ter sido criada com DOMAIN_APP ou DOMAIN_SELINUX . A verificação de permissão é executada após o domain e o namespace serem carregados do banco de dados de chaves da mesma forma como se o blob fosse carregado pelo domínio, namespace e tupla de alias. A justificativa para o domínio de ID de chave é a continuidade. Ao acessar uma chave por alias, as chamadas subsequentes podem operar em chaves diferentes, pois uma nova chave pode ter sido gerada ou importada e vinculada a esse alias. O ID da chave, no entanto, nunca muda. Portanto, ao usar uma chave por id de chave depois que ela foi carregada do banco de dados Keystore usando o alias uma vez, pode-se ter certeza de que é a mesma chave, desde que o id de chave ainda exista. Essa funcionalidade não é exposta aos desenvolvedores de aplicativos. Em vez disso, ele é usado no Android Keystore SPI para fornecer uma experiência mais consistente, mesmo quando usado simultaneamente de maneira insegura.
  • DOMAIN_BLOB : O domínio do blob indica que o chamador gerencia o blob sozinho. Isso é usado para clientes que precisam acessar o Keystore antes que a partição de dados seja montada. O blob de chave é incluído no campo blob do descritor de chave.

Usando o domínio SELinux, podemos dar aos componentes do fornecedor acesso a namespaces de Keystore muito específicos que podem ser compartilhados por componentes do sistema, como a caixa de diálogo de configurações.

Política SELinux para keystore_key

Os rótulos de namespace são configurados usando o arquivo keystore2_key_context .
Cada linha nesses arquivos mapeia um id de namespace numérico para um rótulo SELinux. Por exemplo,

# wifi_key is a keystore2_key namespace intended to be used by wpa supplicant and
# Settings to share keystore keys.
102            u:object_r:wifi_key:s0

Depois de configurar um novo namespace de chave dessa maneira, podemos dar acesso a ele adicionando uma política apropriada. Por exemplo, para permitir que wpa_supplicant obtenha e use chaves no novo namespace, adicionaríamos a seguinte linha a hal_wifi_supplicant.te :

allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };

Depois de configurar o novo namespace, o AndroidKeyStore pode ser usado quase como de costume. A única diferença é que o ID do namespace deve ser especificado. Para carregar e importar chaves de e para o Keystore, o ID do namespace é especificado usando o AndroidKeyStoreLoadStoreParameter . Por exemplo,

import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import java.security.KeyStore;

KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(new AndroidKeyStoreLoadStoreParameter(102));

Para gerar uma chave em um determinado namespace, o ID do namespace deve ser fornecido usando KeyGenParameterSpec.Builder#setNamespace():

import android.security.keystore.KeyGenParameterSpec;
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder();
specBuilder.setNamespace(102);

Os arquivos de contexto a seguir podem ser usados ​​para configurar os namespaces SELinux do Keystore 2.0. Cada partição tem um intervalo reservado diferente de 10.000 IDs de namespace para evitar colisões.

Partição Variar Arquivos de configuração
Sistema 0 ... 9.999
/system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts
Sistema Estendido 10.000 ... 19.999
/system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts
produtos 20.000 ... 29.999
/product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts
Fornecedor 30.000 ... 39.999
/vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts

O cliente solicita a chave solicitando o domínio SELinux e o namespace virtual desejado, neste caso "wifi_key" , por seu id numérico.

Acima disso, os seguintes namespaces foram definidos. Se eles substituirem regras especiais, a tabela a seguir indica o UID ao qual eles costumavam corresponder.

ID do namespace Etiqueta SEPolicy UID Descrição
0 su_key N / D Chave de superusuário. Usado apenas para testar em builds userdebug e eng. Não é relevante nas compilações do usuário.
1 shell_key N / D Namespace disponível para shell. Usado principalmente para testes, mas também pode ser usado em compilações de usuários a partir da linha de comando.
100 vold_key N / D Destinado ao uso por vold.
101 odsing_key N / D Usado pelo daemon de assinatura no dispositivo.
102 chave_wifi AID_WIFI(1010) Usado pelo sistema Wifi do Android, incluindo wpa_supplicant.
120 resume_on_reboot_key AID_SYSTEM(1000) Usado pelo servidor do sistema do Android para dar suporte à retomada na reinicialização.

Vetores de acesso

A classe keystore_key do SELinux envelheceu bastante e algumas das permissões, como verify ou sign , perderam o significado. Aqui está o novo conjunto de permissões, keystore2_key , que o Keystore 2.0 aplicará.

Permissão Significado
delete Verificado ao remover chaves do Keystore.
get_info Verificado quando os metadados de uma chave são solicitados.
grant O chamador precisa dessa permissão para criar uma concessão para a chave no contexto de destino.
manage_blob O chamador pode usar DOMAIN_BLOB no namespace SELinux fornecido, gerenciando assim os blobs por conta própria. Isso é útil especificamente para vold.
rebind Essa permissão controla se um alias pode ser religado para uma nova chave. Isso é necessário para inserção e implica que a chave vinculada anteriormente será excluída. É basicamente uma permissão de inserção, mas captura melhor a semântica do keystore.
req_forced_op Os clientes com essa permissão podem criar operações que não podem ser removidas e a criação da operação nunca falha, a menos que todos os slots de operação sejam ocupados por operações que não podem ser removidas.
update Necessário para atualizar o subcomponente de uma chave.
use Verificado ao criar uma operação Keymint que usa o material da chave, por exemplo, para assinatura, en/decriptação.
use_dev_id Necessário ao gerar informações de identificação do dispositivo, como atestado de identificação do dispositivo.

Além disso, dividimos um conjunto de permissões de keystore não específicas de chave na classe de segurança do SELinux keystore2 :

Permissão Significado
add_auth Requerido pelo provedor de autenticação, como Gatekeeper ou BiometricsManager, para adicionar tokens de autenticação.
clear_ns Anteriormente clear_uid, essa permissão permite que um não proprietário de um namespace exclua todas as chaves desse namespace.
list Requerido pelo sistema para enumerar chaves por várias propriedades, como propriedade ou limite de autenticação. Essa permissão não é exigida pelos chamadores que enumeram seus próprios namespaces. Isso é coberto pela permissão get_info .
lock Essa permissão permite bloquear o Keystore, ou seja, despejar a chave mestra, de modo que as chaves vinculadas à autenticação se tornem inutilizáveis ​​e incriáveis.
reset Esta permissão permite redefinir o Keystore para o padrão de fábrica, excluindo todas as chaves que não são vitais para o funcionamento do sistema operacional Android.
unlock Essa permissão é necessária para tentar desbloquear a chave mestra para chaves vinculadas à autenticação.