No Android 12, o módulo Android Runtime (ART) é um módulo Mainline. A atualização do módulo pode exigir a recriação dos artefatos de compilação ahead-of-time (AOT) de arquivos JAR do bootclasspath e do servidor do sistema. Como esses artefatos são sensíveis à segurança, o Android 12 emprega um recurso chamado assinatura no dispositivo para evitar que eles sejam adulterados. Esta página aborda a arquitetura de assinatura no dispositivo e as interações dela com outros recursos de segurança do Android.
Design de alto nível
A assinatura no dispositivo tem dois componentes principais:
O
odrefreshfaz parte do módulo Mainline do ART. Ele é responsável por gerar os artefatos do ambiente de execução. Ele verifica os artefatos atuais em relação à versão instalada do módulo ART, arquivos JAR do bootclasspath e arquivos JAR do servidor do sistema para determinar se eles estão atualizados ou precisam ser regenerados. Se eles precisarem ser regenerados, oodrefreshos gera e armazena.O
odsigné um binário que faz parte da plataforma Android. Ele é executado durante a inicialização antecipada, logo após a montagem da partição/data. A principal responsabilidade dele é invocar oodrefreshpara verificar se algum artefato precisa ser gerado ou atualizado. Para todos os artefatos novos ou atualizados que oodrefreshgera, oodsigncalcula uma função de hash. O resultado desse cálculo de hash é chamado de resumo do arquivo. Para todos os artefatos que já existem, oodsignverifica se os resumos dos artefatos atuais correspondem aos resumos que oodsignhavia calculado anteriormente. Isso garante que os artefatos não foram adulterados.
Em condições de erro, como quando o resumo de um arquivo não corresponde, o odrefresh e o odsign descartam todos os artefatos atuais em /data e tentam regenerá-los. Se isso falhar, o sistema volta para o modo JIT.
O odrefresh e o odsign são protegidos pelo dm-verity e fazem parte da cadeia de inicialização verificada do Android.
Cálculo de resumos de arquivos com fs-verity
O fs-verity é um recurso do kernel do Linux que faz a verificação de dados de arquivos com base na árvore de Merkle. A ativação do fs-verity em um arquivo faz com que o sistema de arquivos crie uma árvore de Merkle nos dados do arquivo usando hashes SHA-256, armazene-a em um local oculto ao lado do arquivo e marque o arquivo como somente leitura. O fs-verity verifica automaticamente os dados do arquivo na árvore de Merkle sob demanda à medida que são lidos. O fs-verity disponibiliza o hash raiz da árvore de Merkle como um valor chamado o resumo do arquivo fs-verity, e o fs-verity garante que todos os dados lidos do arquivo sejam consistentes com esse resumo.
O odsign usa o fs-verity para melhorar a performance de inicialização, otimizando a autenticação criptográfica de artefatos compilados no dispositivo durante a inicialização. Quando um artefato é gerado, o odsign ativa o fs-verity nele. Quando o odsign verifica um artefato, ele verifica o resumo do arquivo fs-verity em vez do hash completo do arquivo. Isso elimina a necessidade de ler e fazer o hash dos dados completos do artefato no momento da inicialização. Em vez disso, os dados do artefato são hash sob demanda pelo fs-verity à medida que são usados, bloco a bloco.
Em dispositivos cujo kernel não oferece suporte ao fs-verity, o odsign volta a calcular resumos de arquivos no espaço do usuário. O odsign usa o mesmo algoritmo de hash baseado em árvore de Merkle que o fs-verity, então os resumos são os mesmos em ambos os casos. O fs-verity é obrigatório em todos os dispositivos lançados com o Android 11 e versões mais recentes.
Armazenamento dos resumos de arquivos
odsign armazena os resumos de arquivos dos artefatos em um arquivo separado chamado
odsign.info. Para garantir que o odsign.info não seja adulterado, ele é assinado com uma chave de assinatura que tem propriedades de segurança importantes.odsign.info Em
particular, a chave só pode ser gerada e usada durante a inicialização antecipada, momento em
que apenas o código confiável está em execução. Consulte Chaves de assinatura confiáveis para mais detalhes.
Verificação de resumos de arquivos
Em cada inicialização, se o odrefresh determinar que os artefatos atuais estão atualizados, o odsign garante que os arquivos não foram adulterados desde que foram gerados. O odsign faz isso verificando os resumos de arquivos. Primeiro, ele verifica a assinatura do odsign.info. Se a assinatura for válida, então
odsign verifica se o resumo de cada arquivo corresponde ao resumo correspondente
em odsign.info.
Chaves de assinatura confiáveis
O Android 12 apresenta um novo recurso do Keystore chamado chaves de estágio de inicialização, que aborda as seguintes preocupações de segurança:
- O que impede um invasor de usar nossa chave de assinatura para assinar a própria versão do
odsign.info? - O que impede um invasor de gerar a própria chave de assinatura e usá-la para assinar a própria versão do
odsign.info?
As chaves de estágio de inicialização dividem o ciclo de inicialização do Android em níveis e vinculam criptograficamente a criação e o uso de uma chave a um nível especificado. O odsign cria a chave de assinatura em um nível inicial, quando apenas o código confiável está em execução, protegido pelo dm-verity.
Os níveis de estágio de inicialização são numerados de 0 ao número mágico 1000000000. Durante o processo de inicialização do Android, é possível aumentar o nível de inicialização definindo uma propriedade do sistema em init.rc. Por exemplo, o código a seguir define o nível de inicialização como 10:
setprop keystore.boot_level 10
Os clientes do Keystore podem criar chaves vinculadas a um determinado nível de inicialização. Por exemplo, se você criar uma chave para o nível de inicialização 10, ela só poderá ser usada quando o dispositivo estiver no nível de inicialização 10.
O odsign usa o nível de inicialização 30, e a chave de assinatura criada por ele está vinculada a esse nível de inicialização. Antes de usar uma chave para assinar artefatos, o odsign verifica se a chave está vinculada ao nível de inicialização 30.
Isso impede os dois ataques descritos anteriormente nesta seção:
- Os invasores não podem usar a chave gerada porque, quando um invasor tem a chance de executar um código malicioso, o nível de inicialização aumenta além de 30, e o Keystore recusa operações que usam a chave.
- Os invasores não podem criar uma nova chave porque, quando um invasor tem a chance de executar um código malicioso, o nível de inicialização aumenta além de 30, e o Keystore se recusa a criar uma nova chave com esse nível de inicialização. Se um invasor criar uma nova chave que não esteja vinculada ao nível de inicialização 30, o
odsigna rejeitará.
O Keystore garante que o nível de inicialização seja aplicado corretamente. As seções a seguir detalham como isso é feito para diferentes versões do KeyMint (anteriormente Keymaster).
Implementação do Keymaster 4.0
Diferentes versões do Keymaster processam a implementação de chaves de estágio de inicialização de maneira diferente. Em dispositivos com um TEE/StrongBox do Keymaster 4.0, o Keymaster processa a implementação da seguinte maneira:
- Na primeira inicialização, o Keystore cria uma chave simétrica K0 com a tag
MAX_USES_PER_BOOTdefinida como1. Isso significa que a chave só pode ser usada uma vez por inicialização. - Durante a inicialização, se o nível de inicialização for aumentado, uma nova chave para esse nível de inicialização
poderá ser gerada a partir de K0 usando uma função HKDF:
Ki+i=HKDF(Ki, "some_fixed_string"). Por exemplo, se você passar do nível de inicialização 0 para o nível de inicialização 10, o HKDF será invocado 10 vezes para derivar K10 de K0. Quando o nível de inicialização muda, a chave do nível de inicialização anterior é apagada da memória, e as chaves associadas aos níveis de inicialização anteriores não ficam mais disponíveis.
A chave K0 é uma chave
MAX_USES_PER_BOOT=1. Isso significa que também é impossível usar essa chave mais tarde na inicialização, porque pelo menos uma transição de nível de inicialização (para o nível de inicialização final) sempre ocorre.
Quando um cliente do Keystore, como o odsign, solicita a criação de uma chave no nível de inicialização i, o blob dela é criptografado com a chave Ki. Como Ki não está disponível
após o nível de inicialização i, essa chave não pode ser criada ou descriptografada em estágios de inicialização posteriores.
Implementação do Keymaster 4.1 e do KeyMint 1.0
As implementações do Keymaster 4.1 e do KeyMint 1.0 são praticamente as mesmas da implementação do Keymaster 4.0. A principal diferença é que K0 não é uma chave MAX_USES_PER_BOOT, mas uma chave EARLY_BOOT_ONLY, que foi introduzida no Keymaster 4.1. Uma chave EARLY_BOOT_ONLY só pode ser usada durante as fases iniciais da inicialização, quando nenhum código não confiável está em execução. Isso oferece um nível extra de proteção: na implementação do Keymaster 4.0, um invasor que compromete o sistema de arquivos e o SELinux pode modificar o banco de dados do Keystore para criar a própria chave MAX_USES_PER_BOOT=1 para assinar artefatos. Esse ataque é impossível com as implementações do Keymaster 4.1 e do KeyMint 1.0, porque as chaves EARLY_BOOT_ONLY só podem ser criadas durante a inicialização antecipada.
Componente público de chaves de assinatura confiáveis
O odsign recupera o componente de chave pública da chave de assinatura do Keystore.
No entanto, o Keystore não recupera essa chave pública do TEE/SE que contém a chave privada correspondente. Em vez disso, ele recupera a chave pública do próprio banco de dados no disco. Isso significa que um invasor que compromete o sistema de arquivos pode modificar o banco de dados do Keystore para conter uma chave pública que faz parte de um par de chaves pública/privada sob o controle dele.
Para evitar esse ataque, o odsign cria uma chave HMAC extra com o mesmo nível de inicialização da chave de assinatura. Em seguida, ao criar a chave de assinatura, o odsign usa essa chave HMAC para criar uma assinatura da chave pública e a armazena no disco. Nas inicializações subsequentes, ao recuperar a chave pública da chave de assinatura, ele usa a chave HMAC para verificar se a assinatura no disco corresponde à assinatura da chave pública recuperada. Se elas corresponderem, a chave pública será confiável, porque a chave HMAC só poderá ser usada em níveis de inicialização antecipados e, portanto, não poderá ter sido criada por um invasor.