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 fortes e apoiados por hardware para o sistema operacional Android, para serviços de plataforma e até mesmo para aplicativos de terceiros. Os desenvolvedores que buscam extensões específicas do Android devem acessar android.security.keystore .
Antes do Android 6.0, o Android já tinha uma API simples de serviços de criptografia baseada em hardware, fornecida pelas versões 0.2 e 0.3 do Keymaster Hardware Abstraction Layer (HAL). O Keystore forneceu operações de assinatura e verificação digital, 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 facilmente alcançados 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 apoiadas por 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 utilizáveis somente após o usuário ter sido 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 gama de primitivas criptográficas, 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 chaves ao sistema operacional e à versão em nível de patch. Isso garante que um invasor que descubra uma vulnerabilidade em uma versão antiga do sistema ou no software 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. À 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.
No Android 8.0, o Keymaster 3 fez a transição da antiga camada de abstração de hardware (HAL) de estrutura C para a interface C++ HAL 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 argumentos foram alterados, embora os tipos e métodos tenham uma correspondência individual com os tipos antigos e os métodos de estrutura HAL. Consulte a página Funções para obter mais detalhes.
Além desta revisão da 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 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 o atestado de ID. As implementações do Keymaster precisam encontrar alguma 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íram:
- Atualização para Keymaster 4
- Suporte para Elementos Seguros incorporados
- Suporte para importação segura de chaves
- Suporte para criptografia 3DES
- Mudanças 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 rápida visão geral dos componentes do Keystore e seus relacionamentos.
AndroidKeystore é a API e o componente do Android Framework usado por aplicativos para acessar a funcionalidade do Keystore. Ele é implementado como uma extensão das APIs padrão da Java Cryptography Architecture 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 comportamento do Keystore, encaminhando-as para o daemon do keystore.
O daemon keystore é um daemon do sistema Android que fornece acesso a todas as funcionalidades do Keystore por meio de uma API Binder . É responsável por armazenar "blobs de chaves", que contêm o material real da chave secreta, 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, geralmente em TrustZone em um ARM SoC, que fornece todas as operações seguras de 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 ele fornece ao daemon de keystore e que são finalmente 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 (aplicativo confiável) é 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 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 com suporte de hardware e controladas por acesso.
O Keymaster HAL é uma biblioteca carregável dinamicamente fornecida pelo OEM, usada pelo serviço Keystore para fornecer serviços criptográficos apoiados por hardware. Para manter tudo seguro, as implementações 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 acessado por meio de alguma interface do kernel. A arquitetura resultante fica assim:
Dentro de um dispositivo Android, o "cliente" do Keymaster HAL consiste em múltiplas camadas (por exemplo, aplicativo, estrutura, daemon Keystore), mas isso pode ser ignorado para os fins 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 está descrita no site do desenvolvedor Android .
O objetivo do Keymaster HAL não é implementar algoritmos sensíveis à segurança, mas apenas empacotar e desempacotar solicitações para o mundo seguro. O formato de ligação é 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 Android 5.0 e versões anteriores lançadas com os HALs Keymaster mais antigos, o Keystore fornece um adaptador que implementa o HAL Keymaster 1 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.
Keymaster 2 simplificou ainda mais a interface HAL removendo os métodos get_supported_*
e permitindo que o método finish()
aceitasse entradas. Isto 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 do antigo HAL de estrutura C 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 HAL de novo estilo é criada subclassificando a classe IKeymasterDevice
gerada e implementando os métodos virtuais puros. Como parte da mudança, muitos dos tipos de argumentos foram alterados, embora os tipos e métodos tenham uma correspondência individual 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 Trusted Execution Environment (TEE) considerem 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 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 é equivalente ao seguinte do HAL keymaster2:
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 está implícito. O argumento params
não é mais uma estrutura contendo um ponteiro que faz referência a uma matriz de objetos key_parameter_t
, mas um vec
(vetor) contendo objetos KeyParameter
. Os valores de retorno estão listados na cláusula " generates
", incluindo um vetor de valores uint8_t
para o blob de chave.
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 HALS keymaster0, keymaster1 ou keymaster2 de estilo antigo.
Controle de acesso
A regra mais básica do controle de acesso do Keystore é que cada aplicativo tenha seu próprio namespace. Mas para cada regra há uma exceção. O Keystore possui alguns mapas codificados que permitem que determinados componentes do sistema acessem outros namespaces. Este é um instrumento muito contundente, pois dá a um componente controle total sobre outro namespace. E há também a questão dos componentes do fornecedor como clientes do 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 introduz domínios e namespaces SELinux.
Domínios de armazenamento de chaves
Com domínios Keystore, podemos dissociar 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 nesta tupla e na identidade do chamador, podemos determinar qual chave o chamador deseja acessar e se possui as permissões apropriadas.
Introduzimos 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 é executado.
-
DOMAIN_APP
: o domínio do aplicativo cobre o comportamento legado. O Java Keystore SPI usa esse domínio por padrão. Quando este domínio é usado, o argumento do namespace é ignorado e o UID do chamador é usado em seu lugar. O acesso a este domínio é controlado pelo rótulo Keystore para a classekeystore_key
na política SELinux. -
DOMAIN_SELINUX
: Este domínio indica que o namespace possui 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 classekeystore_key
. Quando a permissão for estabelecida para uma determinada operação, a tupla completa será usada para a pesquisa de chave. -
DOMAIN_GRANT
: O domínio de concessão indica que o parâmetro namespace é um identificador de concessão. O parâmetro alias é ignorado. As verificações do SELinux são realizadas quando a concessão é criada. O controle de acesso adicional verifica apenas se o UID do chamador corresponde ao UID do beneficiário da concessão solicitada. -
DOMAIN_KEY_ID
: Este domínio indica que o parâmetro namespace é um ID de chave exclusivo. A própria chave pode ter sido criada comDOMAIN_APP
ouDOMAIN_SELINUX
. A verificação de permissão é realizada após odomain
e onamespace
terem sido carregados do banco de dados de chaves da mesma maneira como se o blob fosse carregado pelo domínio, pelo namespace e pela tupla de alias. A justificativa para o domínio de identificação 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, entretanto, nunca muda. Portanto, ao usar uma chave por ID de chave após ela ter sido carregada do banco de dados Keystore usando o alias uma vez, pode-se ter certeza de que é a mesma chave, desde que o ID da chave ainda exista. Esta 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 da partição de dados ser montada. O blob de chave está incluído no campoblob
do descritor de chave.
Usando o domínio SELinux, podemos dar aos componentes do fornecedor acesso a namespaces 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 forma, podemos conceder 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 normalmente. 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 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 namespaces SELinux do Keystore 2.0. Cada partição possui um intervalo reservado diferente de 10.000 IDs de namespace para evitar colisões.
Partição | Faixa | 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"
, pelo seu id numérico.
Acima disso, os seguintes namespaces foram definidos. Se substituirem regras especiais, a tabela a seguir indica o UID ao qual correspondiam.
ID do namespace | Rótulo da política SE | UID | Descrição |
---|---|---|---|
0 | su_key | N / D | Chave de superusuário. Usado apenas para testes em compilações userdebug e eng. Não é relevante em compilações de usuários. |
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 Android para dar suporte à retomada na reinicialização. |
Acessar vetores
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 si só. Isto é especificamente útil para vold. |
rebind | Esta permissão controla se um alias pode ser religado a uma nova chave. Isto é necessário para inserção e implica que a chave anteriormente vinculada será excluída. É basicamente uma permissão de inserção, mas captura melhor a semântica do keystore. |
req_forced_op | Os clientes com esta permissão podem criar operações não remediáveis, e a criação de operações nunca falha, a menos que todos os slots de operação sejam ocupados por operações não remediáveis. |
update | Necessário para atualizar o subcomponente de uma chave. |
use | Verificado ao criar uma operação Keymint que utiliza o material da chave, por exemplo, para assinatura, en/descriptografia. |
use_dev_id | Obrigató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 SELinux keystore2
:
Permissão | Significado |
---|---|
add_auth | Exigido pelo provedor de autenticação, como Gatekeeper ou BiometricsManager, para adicionar tokens de autenticação. |
clear_ns | Anteriormente clear_uid, esta permissão permite que um não proprietário de um namespace exclua todas as chaves desse namespace. |
list | Exigido 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. |