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
이 계산한 다이제스트와 일치하는지 확인합니다. 이렇게 하면 아티팩트가 조작되지 않았음을 확인할 수 있습니다.
파일 다이제스트가 일치하지 않는 경우 등 오류 조건에서 odrefresh
와 odsign
은 /data
에 있는 기존의 모든 아티팩트를 버리고 재생성을 시도합니다. 이 작업에 실패하면 시스템은 JIT 모드로 돌아갑니다.
odrefresh
와 odsign
은 dm-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는 다음과 같이 구현을 처리합니다.
- 첫 부팅 시 키 저장소는
MAX_USES_PER_BOOT
태그가1
로 설정된 대칭 키 K0을 만듭니다. 즉, 키는 부팅당 한 번만 사용할 수 있습니다. - 부팅 중에 부팅 수준이 높아지면 HKDF 함수(
Ki+i=HKDF(Ki, "some_fixed_string")
)를 사용하여 K0에서 해당 부팅 수준의 새 키를 생성할 수 있습니다. 예를 들어 부팅 수준 0에서 부팅 수준 10으로 이동하면 HKDF가 10번 호출되어 K0에서 K10을 얻습니다. 부팅 수준이 변경되면 이전 부팅 수준의 키는 메모리에서 삭제되고 이전 부팅 수준과 연결된 키를 더 이상 사용할 수 없습니다.
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 키는 초기 부팅 수준에서만 사용할 수 있으므로 공격자가 만들 수 없기 때문입니다.