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 antecipada (AOT) de jars 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. Ele é responsável por gerar os artefatos de tempo de execução. Ele verifica os artefatos atuais em relação à versão instalada do módulo ART, aos jars do bootclasspath e aos jars do servidor do sistema para determinar se eles estão atualizados ou precisam ser regenerados. Se precisarem ser regenerados, oodrefresh
vai fazer isso e armazená-los.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 é invocarodrefresh
para verificar se algum artefato precisa ser gerado ou atualizado. Para todos os artefatos novos ou atualizados que oodrefresh
gera, oodsign
calcula uma função de hash. O resultado desse cálculo é chamado de resumo do arquivo. Para artefatos que já existem, oodsign
verifica se os resumos dos artefatos atuais correspondem aos resumos que oodsign
havia calculado anteriormente. Isso garante que os artefatos não foram violados.
Em condições de erro, como quando o resumo de um arquivo não corresponde, odrefresh
e odsign
descartam todos os artefatos existentes em /data
e tentam regenerá-los. Se isso falhar, o sistema vai voltar 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 em árvores de Merkle. Ao ativar o fs-verity em um arquivo, o sistema de arquivos cria uma árvore de Merkle com os dados do arquivo usando hashes SHA-256, armazena em um local oculto ao lado do arquivo e marca o arquivo como somente leitura. O fs-verity verifica automaticamente os dados do arquivo em relação à á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 de resumo do arquivo fs-verity, e garante que todos os dados lidos do arquivo sejam consistentes com esse resumo.
O odsign
usa o fs-verity para melhorar o desempenho da 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 hash de todos os dados do
artefato na inicialização. Em vez disso, os dados de artefato são hashados sob demanda pelo fs-verity
à medida que são usados, bloco por bloco.
Em dispositivos cujo kernel não é compatível com 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. Portanto, 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
O odsign
armazena os resumos dos 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 pode ser gerada e usada apenas durante a inicialização antecipada, 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 o odrefresh
determinar que os artefatos atuais estão atualizados, o odsign
vai garantir que os arquivos não foram adulterados desde que foram gerados. O odsign
faz isso verificando os resumos dos arquivos. Primeiro, ele verifica a assinatura de odsign.info
. Se a assinatura for válida, o
odsign
vai verificar se o resumo de cada arquivo corresponde ao resumo
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 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 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 um 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 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 já aumentou além de 30, e o Keystore se recusa a realizar operações que usam a chave.
- Os invasores não podem criar uma nova chave porque, quando têm a chance de executar um código malicioso, o nível de inicialização já aumentou 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
odsign
vai rejeitá-la.
O keystore garante que o nível de inicialização seja aplicado corretamente. As seções a seguir detalham como isso é feito em diferentes versões do KeyMint (antes Keymaster).
Implementação do Keymaster 4.0
Versões diferentes do Keymaster processam a implementação de chaves de estágio de inicialização de maneiras diferentes. Em dispositivos com um TEE/StrongBox 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_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 do 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 anterior é apagada da memória, e as chaves associadas a 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 sempre ocorre pelo menos uma transição de nível de inicialização (para o nível de inicialização final).
Quando um cliente do Keystore, como odsign
, solicita a criação de uma chave no
nível de inicialização i
, o blob é 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 etapas
posteriores de inicialização.
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 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 adicional
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 sua própria
chave MAX_USES_PER_BOOT=1
para assinar artefatos. Esse tipo de 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 comprometer o sistema de arquivos poderá 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 controle dele.
Para evitar esse ataque, o odsign
cria outra chave HMAC 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 em
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 elas forem iguais, a chave pública será confiável, porque a chave HMAC só pode ser usada em níveis de inicialização antecipada e, portanto, não pode ter sido criada por um invasor.