기기 내 서명 아키텍처

Android 12부터 Android 런타임 (ART) 모듈은 메인라인 모듈입니다. 이 모듈을 업데이트하려면 bootclasspath jar 및 시스템 서버의 AOT(Ahead-Of-Time) 컴파일 아티팩트를 다시 빌드해야 할 수도 있습니다. 이러한 아티팩트는 보안에 민감하므로 Android 12에서는 기기 내 서명 기능을 사용하여 이러한 아티팩트가 조작되지 않도록 합니다. 이 페이지에서는 기기 내 서명 아키텍처와 다른 Android 보안 기능과의 상호작용을 다룹니다.

대략적인 구성

기기 내 서명의 핵심 구성요소 두 가지는 다음과 같습니다.

  • odrefresh: ART Mainline 모듈의 일부입니다. 런타임 아티팩트 생성을 담당합니다. 기존 아티팩트를 설치된 ART 모듈 버전과 bootclasspath jar, 시스템 서버 jar와 비교하여 최신 상태인지 재생성해야 하는지 확인합니다. 재생성해야 하는 경우 odrefresh가 생성하여 저장합니다.

  • odsign: Android 플랫폼의 일부인 바이너리입니다. /data 파티션이 마운트된 직후 초기 부팅 중에 실행됩니다. 기본적으로 odrefresh를 호출하여 아티팩트를 생성하거나 업데이트해야 하는지 확인하는 작업을 담당합니다. odrefresh가 생성하는 새 아티팩트나 업데이트된 아티팩트의 경우 odsign이 해시 함수를 계산합니다. 이러한 해시 계산의 결과를 파일 다이제스트라고 합니다. 이미 존재하는 아티팩트의 경우 odsign은 기존 아티팩트의 다이제스트가 이전에 odsign이 계산한 다이제스트와 일치하는지 확인합니다. 이렇게 하면 아티팩트가 조작되지 않았음을 확인할 수 있습니다.

파일 다이제스트가 일치하지 않는 경우 등 오류 조건에서 odrefreshodsign/data에 있는 기존의 모든 아티팩트를 버리고 재생성을 시도합니다. 이 작업에 실패하면 시스템은 JIT 모드로 돌아갑니다.

odrefreshodsigndm-verity로 보호되며 Android의 자체 검사 부팅 체인의 일부입니다.

fs-verity를 사용하여 파일 다이제스트 계산

fs-verity는 파일 데이터의 머클 트리 기반 확인을 실행하는 Linux 커널의 기능입니다. 파일에서 fs-verity를 사용 설정하면 파일 시스템이 SHA-256 해시를 사용하여 파일의 데이터에 대한 머클 트리를 빌드하여 파일과 함께 숨겨진 위치에 저장하고 파일을 읽기 전용으로 표시합니다. fs-verity는 파일을 읽을 때 필요에 따라 머클 트리와 비교하여 파일의 데이터를 자동으로 확인합니다. fs-verity는 머클 트리의 루트 해시를 fs-verity 파일 다이제스트라는 값으로 사용할 수 있게 하며 fs-verity는 파일에서 읽은 모든 데이터가 이 파일 다이제스트와 일치하는지 확인합니다.

odsign은 fs-verity를 사용하여 부팅 시간에 기기 내 컴파일된 아티팩트의 암호화 인증을 최적화함으로써 부팅 성능을 개선합니다. 아티팩트가 생성되면 odsign은 해당 아티팩트에 관해 fs-verity를 사용 설정합니다. odsign은 아티팩트를 확인할 때 전체 파일 해시가 아닌 fs-verity 파일 다이제스트를 확인합니다. 이렇게 하면 부팅 시간에 아티팩트의 전체 데이터를 읽고 해싱하지 않아도 됩니다. 대신 아티팩트 데이터는 사용될 때 필요에 따라 블록 단위로 fs-verity에 의해 해싱됩니다.

커널이 fs-verity를 지원하지 않는 기기에서는 odsign이 사용자 공간의 컴퓨팅 파일 다이제스트로 대체됩니다. odsign은 fs-verity와 동일한 머클 트리 기반 해시 알고리즘을 사용하므로 어느 경우든 다이제스트는 동일합니다. fs-verity는 Android 11 이상으로 출시된 모든 기기에 필요합니다.

파일 다이제스트 저장

odsign은 아티팩트의 파일 다이제스트를 별도 파일인 odsign.info에 저장합니다. odsign.info가 조작되지 않도록 odsign.info는 중요한 보안 속성이 있는 서명 키로 서명됩니다. 특히, 키는 신뢰할 수 있는 코드만 실행되는 초기 부팅 시에만 생성 및 사용될 수 있습니다. 자세한 내용은 신뢰할 수 있는 서명 키를 참고하세요.

파일 다이제스트 확인

부팅 시마다 odrefresh가 기존 아티팩트가 최신 상태라고 판단하면 odsign은 파일이 생성된 이후 조작되지 않았는지 확인합니다. odsign은 파일 다이제스트를 확인하여 이 작업을 합니다. 먼저 odsign.info의 서명을 확인합니다. 서명이 유효하면 odsign은 각 파일의 다이제스트가 odsign.info의 해당 다이제스트와 일치하는지 확인합니다.

신뢰할 수 있는 서명 키

Android 12에서는 부팅 단계 키라는 새로운 키 저장소 기능을 도입하며 다음과 같은 보안 문제를 해결합니다.

  • 공격자가 서명 키를 사용하여 자체 버전의 odsign.info에 서명하는 것을 방지하는 방법은 무엇인가요?
  • 공격자가 자체 서명 키를 생성하고 이를 사용하여 자체 버전의 odsign.info에 서명하는 것을 방지하는 방법은 무엇인가요?

부팅 단계 키는 Android의 부팅 주기를 여러 수준으로 분할하고 키의 생성 및 사용을 지정된 수준에 암호화 방식으로 연결합니다. odsign은 신뢰할 수 있는 코드만 실행되고 dm-verity를 통해 보호되는 초기 수준에서 서명 키를 만듭니다.

부팅 단계 수준은 0부터 매직 번호 1000000000까지 번호가 매겨집니다. Android 부팅 프로세스 중에 init.rc에서 시스템 속성을 설정하여 부팅 수준을 높일 수 있습니다. 예를 들어 다음 코드는 부팅 수준을 10으로 설정합니다.

setprop keystore.boot_level 10

키 저장소의 클라이언트는 특정 부팅 수준에 연결된 키를 만들 수 있습니다. 예를 들어 부팅 수준 10의 키를 만들면 이 키는 기기가 부팅 수준 10인 경우에만 사용할 수 있습니다.

odsign은 부팅 수준 30을 사용하며, 만드는 서명 키는 해당 부팅 수준과 연결됩니다. 키를 사용하여 아티팩트에 서명하기 전에 odsign은 키가 부팅 수준 30에 연결되어 있는지 확인합니다.

이렇게 하면 이 섹션의 앞부분에서 설명한 두 가지 공격을 방지할 수 있습니다.

  • 공격자는 생성된 키를 사용할 수 없습니다. 공격자가 악성 코드를 실행할 수 있을 때면 부팅 수준이 30을 초과하고 키 저장소는 이 키를 사용하는 작업을 거부하기 때문입니다.
  • 공격자는 새 키를 만들 수 없습니다. 공격자가 악성 코드를 실행할 수 있을 때면 부팅 수준이 30을 초과하고 키 저장소는 해당 부팅 수준으로 새 키를 생성하지 않기 때문입니다. 공격자가 부팅 수준 30에 연결되지 않은 새 키를 만들면 odsign에서 이를 거부합니다.

키 저장소는 부팅 수준이 올바르게 적용되도록 보장합니다. 다음 섹션에서는 다양한 Keymaster 버전에서 이 작업이 실행되는 방식을 자세히 설명합니다.

Keymaster 4.0 구현

다양한 버전의 Keymaster가 부팅 단계 키 구현을 다르게 처리합니다. Keymaster 4.0 TEE/Strongbox가 적용된 기기에서 Keymaster는 다음과 같이 구현을 처리합니다.

  1. 첫 부팅 시 키 저장소는 MAX_USES_PER_BOOT 태그가 1로 설정된 대칭 키 K0을 만듭니다. 즉, 키는 부팅당 한 번만 사용할 수 있습니다.
  2. 부팅 중에 부팅 수준이 높아지면 HKDF 함수(Ki+i=HKDF(Ki, "some_fixed_string"))를 사용하여 K0에서 해당 부팅 수준의 새 키를 생성할 수 있습니다. 예를 들어 부팅 수준 0에서 부팅 수준 10으로 이동하면 HKDF가 10번 호출되어 K0에서 K10을 얻습니다.
  3. 부팅 수준이 변경되면 이전 부팅 수준의 키는 메모리에서 삭제되고 이전 부팅 수준과 연결된 키를 더 이상 사용할 수 없습니다.

    K0 키는 MAX_USES_PER_BOOT=1 키입니다. 즉, 최소 한 번의 부팅 수준 전환(최종 부팅 수준까지)이 항상 발생하므로 나중에 부팅할 때 이 키를 사용할 수 없습니다.

odsign과 같은 키 저장소 클라이언트가 부팅 수준 i에서 키가 생성되도록 요청하면 blob은 Ki 키로 암호화됩니다. Ki를 부팅 수준 i 후에는 사용할 수 없으므로 이 키는 이후 부팅 단계에서 만들거나 복호화할 수 없습니다.

Keymaster 4.1 및 KeyMint 1.0 구현

Keymaster 4.1 및 KeyMint 1.0 구현은 Keymaster 4.0 구현과 대체로 동일합니다. 주요 차이점은 K0이 MAX_USES_PER_BOOT 키가 아니라 Keymaster 4.1에서 도입된 EARLY_BOOT_ONLY 키라는 점입니다. EARLY_BOOT_ONLY 키는 신뢰할 수 없는 코드가 실행되지 않는 초기 부팅 단계에서만 사용할 수 있습니다. 이를 통해 추가 보호 수준이 제공됩니다. Keymaster 4.0 구현에서는 파일 시스템과 SELinux를 손상시키는 공격자가 키 저장소 데이터베이스를 수정하여 아티팩트에 서명할 자체 MAX_USES_PER_BOOT=1 키를 만들 수 있습니다. 이러한 공격은 Keymaster 4.1 및 KeyMint 1.0 구현에서는 불가능합니다. EARLY_BOOT_ONLY 키가 초기 부팅 중에만 생성될 수 있기 때문입니다.

신뢰할 수 있는 서명 키의 공개 구성요소

odsign은 키 저장소에서 서명 키의 공개 키 구성요소를 검색합니다. 그러나 키 저장소는 상응하는 비공개 키를 보유하는 TEE/SE에서 해당 공개 키를 검색하지 않습니다. 대신 자체 디스크 데이터베이스에서 공개 키를 검색합니다. 즉, 파일 시스템을 손상시키는 공격자가 키 저장소 데이터베이스를 수정하여 제어된 공개/비공개 키 쌍의 일부인 공개 키를 포함할 수 있습니다.

이 공격을 방지하기 위해 odsign에서는 서명 키와 동일한 부팅 수준으로 HMAC 키를 추가로 만듭니다. 그런 다음 서명 키를 만들 때 odsign은 이 HMAC 키를 사용하여 공개 키의 서명을 만들고 디스크에 저장합니다. 후속 부팅에서는 서명 키의 공개 키를 검색할 때 HMAC 키를 사용하여 디스크 상의 서명이 검색된 공개 키의 서명과 일치하는지 확인합니다. 일치하는 경우 공개 키를 신뢰할 수 있습니다. HMAC 키는 초기 부팅 수준에서만 사용할 수 있으므로 공격자가 만들 수 없기 때문입니다.