A partir do Android 12, o módulo do Android Runtime (ART) é um módulo Mainline. A atualização do módulo pode exigir que ele reconstrua os artefatos de compilação antecipada (AOT) dos jars de bootclasspath e do servidor do sistema. Como esses artefatos são sensíveis à segurança, o Android 12 usa um recurso chamado assinatura no dispositivo para impedir 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:
odrefresh
faz parte do módulo Mainline do ART. Ela é responsável por gerar os artefatos de execução. Ele verifica os artefatos existentes em relação à versão instalada do módulo do ART, dos jars do bootclasspath e dos jars do servidor do sistema para determinar se eles estão atualizados ou precisam ser regenerados. Se eles precisarem ser regenerados, oodrefresh
os gera e os armazena.odsign
é um binário que faz parte da plataforma Android. Ele é executado durante a inicialização inicial, logo após a montagem da partição/data
. A principal responsabilidade dele é invocarodrefresh
para verificar se algum artefato precisa ser gerado ou atualizado. Para qualquer artefato novo ou atualizado que oodrefresh
gera, oodsign
calcula uma função hash. O resultado dessa computação é chamado de digesto de arquivo. Para artefatos que já existem,odsign
verifica se os resumos dos artefatos correspondem aos resumos queodsign
calculou 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,
odrefresh
e odsign
descartam todos os artefatos existentes no /data
e
tentam regenerá-los. Se isso falhar, o sistema voltará ao modo JIT.
odrefresh
e odsign
são protegidos por 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 Merkle. Ativar o fs-verity em um arquivo faz com que o sistema de arquivos crie uma árvore Merkle sobre os dados do arquivo usando hashes SHA-256, os armazene em um local oculto com o arquivo e marque o arquivo como somente leitura. O fs-verity verifica automaticamente os dados do arquivo em relação à árvore Merkle sob demanda conforme ele é lido. O fs-verity disponibiliza o hash raiz da árvore Merkle como um valor chamado digest de arquivo fs-verity, e ele garante que todos os dados lidos do arquivo sejam consistentes com esse resumo de arquivo.
O odsign
usa o fs-verity para melhorar a performance de inicialização, otimizando a
autenticação criptográfica de artefatos compilados no dispositivo no momento da inicialização. Quando
um artefato é gerado, o odsign
ativa o fs-verity nele. Quando o odsign
verifica um artefato, ele verifica o resumo de arquivo fs-verity em vez do hash
completo do arquivo. Isso elimina a necessidade de ler e gerar um hash dos dados completos do
artefato no momento da inicialização. Em vez disso, os dados de artefato são gerados em hash sob demanda pelo fs-verity
conforme são usados, bloco por bloco.
Em dispositivos com um kernel que não oferece suporte ao fs-verity, o odsign
volta a
calcular resumos de arquivos no espaço do usuário. odsign
usa o mesmo algoritmo de hash
baseado em árvore Merkle que o fs-verity. Portanto, os resumos são os mesmos em ambos os casos.
O fs-verity é necessário em todos os dispositivos lançados com o Android 11 e versões mais recentes.
Armazenamento dos resumos de arquivos
O odsign
armazena os resumos de arquivos dos artefatos em um arquivo separado chamado
odsign.info
. Para garantir que odsign.info
não seja adulterado, odsign.info
é assinado com uma chave de assinatura que tem propriedades de segurança importantes. Em
particular, a chave só pode ser gerada e usada durante a inicialização inicial, quando
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 odrefresh
determinar que os artefatos existentes estão
atualizados, odsign
garante que os arquivos não foram adulterados desde
que foram gerados. O odsign
faz isso verificando os resumos de arquivo. Primeiro, ela
verifica a assinatura de odsign.info
. Se a assinatura for válida, odsign
vai verificar 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 de Keystore chamado chaves de fase de inicialização que aborda as seguintes questões de segurança:
- O que impede um invasor de usar nossa chave de assinatura para assinar a
própria versão de
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 de
odsign.info
?
As chaves de estágio de inicialização dividem o ciclo de inicialização do Android em níveis e
associam 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
por dm-verity
.
Os níveis da fase 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 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 evita 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 para mais 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
odsign
a 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 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 Keymaster 4.0 TEE/Strongbox, 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_BOOT
definida 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
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 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 estão 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 de Keystore, como odsign
, solicita que uma chave seja criada 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 em grande parte iguais à
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 de
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 inicial.
Componente público de chaves de assinatura confiáveis
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 comprometa 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 adicional com o mesmo
nível de inicialização da chave de assinatura. Em seguida, ao criar a chave de assinatura, odsign
usa essa chave HMAC para criar uma assinatura da chave pública e armazenar essa assinatura no
disco. Em 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 eles corresponderem, a chave pública será confiável, porque
a chave HMAC só pode ser usada nos níveis iniciais de inicialização e, portanto, não pode ter sido
criada por um invasor.