본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android 보안] 패키지 관리 #1

by 돼지왕 왕돼지 2018. 4. 18.
반응형

[android 보안] 패키지 관리 #1


출처 : Android Security Internals 3장

/system/etc/security/otacerts.zip, /system/vendor/app/, abstract syntax notation one, apk, application/vnd.android.package-archive, asn.1, attached signature, Authenticity, BLOB, Ca, cert.sf, CMS, cms format, data/app, detached signature, dex 파일 위치, Digest, Digital Signature, DSA, EC, ECDSA, Integrity, jar, jar 파일 검증, jar 파일 서명, Jarsigner, keytool, manifest.mf, META_INF/, MIME Type, OpenSSL, OTA, otacerts.zip, over the air, pem, pkcs, PKI, Private Key, public key, public key infrastructure, releasekey.x509.pem, resources.arsc, RSA, SF, SHA-1, SHA-256, SHA-512, sha1digest-manifest, SHA1withRSA, sha256withrsa, side loading, signapk, signature block file, signature file, SignedData, userdata 파티션, verify, Web of Trust, x.509 인증, x.509 포맷, zip, [android 보안] 패키지 관리, 공개 키 기반구조, 구글 플레이 스토어, 다이제스트, 다이제스트 검증, 다이제스트 알고리즘, 디지털 서명, 디지털 서명 체계, 매니페스트, 무결성, 무결성 검증, 분리된 서명, 사이드 로딩, 서명 검증, 서명 블록 파일, 서명 알고리즘, 서명 인증서, 서명 키, 서명 파일, 서명값, 서명자 정보 보기, 신뢰망, 안드로이드 코드 서명, 안드로이드 코드 서명 도구, 앱 패키지 구성, 인증서, 인증서 추출, 자바 코드 서명, 자바7, 전자서명, 전체 아카이브 서명, 전체 파일 서명, 제조사 앱, 첨부된 서명, 코드 서명, 키 별명, 키 저장소 파일명

개요 목차


3. 패키지 관리

     3.1. 안드로이드 앱 패키지 구성


     3.2. 코드 서명

          3.2.1. 자바 코드 서명

          3.2.2. 안드로이드 코드 서명


     3.3. APK 설치 과정

          3.3.1. 앱 패키지와 데이터의 위치

          3.3.2. 활성화된 컴포넌트

          3.3.3. 지역 패키지의 설치

          3.3.4. 패키지 업데이트

          3.3.5. 암호화된 APK 의 설치

          3.3.6. 포워드 락

          3.3.7. 암호화된 앱과 구글 플레이


     3.4. 패키지 검증

          3.4.1. 패키지 검증을 위한 안드로이드 지원 기능

          3.4.2. 구글 플레이 구현


     3.5. 요약





3.1. 안드로이드 앱 패키지 구성


-

APK 는 자바 JAR 를 확장한 포맷이며, JAR 파일은 널리 사용되는 ZIP 압축 파일 포맷을 확장한 것이다.



-

APK 파일은 보통 .apk 확장자를 가지며, application/vnd.android.package-archive MIME 형식에 연결되어 있다.



-

apk 파일 안에는 resources.arsc 가 있는데, 이 녀석은 문자열과 스타일 등 앱의 컴파일된 리소스가 모두 들어 있다.



-

JAR 파일과 마찬가지로 APK 파일에도 META_INF/ 디렉터리를 포함하는데,

이 디렉터리에는 패키지 매니페스트 파일과 코드 서명이 들어간다.





3.2. 코드 서명


-

일반적으로 무결성( integrity ) 과 생성자를 확인 ( authenticity ) 하기 위해 서명한다.

서드파티 프로그램을 실행하기 전에 이 프로그램이 변경되지 않았는지(무결성), 실제로 그 생성자에 의해 만들어졌는지(생성자 확인)에 대해 확인할 필요가 있다.

이런 기능은 일반적으로 서명 키를 소유한 개체만 정당한 코드 서명을 생성할 수 있도록 보장하는 디지털 서명 체계를 이용해 구현된다.



-

서명 검증 절차는 코드가 변경되지 않았는지, 서명이 올바른 키로 생성되었는지를 확인한다.

그러나 이 코드 서명 체계만으로는 코드 서명자(소프트웨어 개발자)를 신뢰할 수 있다고 직접적으로 보장하지는 못한다.

일반적으로 신뢰를 확보하려면 코드 서명자가 자신의 디지털 인증서를 보관하고 서명된 코드에 첨부해야 한다.

검증자는 PKI 나 신뢰망(web of trust)등의 신뢰 모델에 기반을 두거나 건별로 인증서를 신뢰할지 결정한다.


게다가 코드 서명만으로는 서명된 코드가 실행하기에 안전한 코드인지를 전혀 판단할 수가 없다.

플레임 같은 코드 서명된 악성 코드에서 알 수 있듯이, 신뢰할 수 있는 서드 파티에서 서명한 것처럼 보이는 코드도 안전하지 않을 수 있다.


cf) PKI ( public key infrastructure )

공개 키 기반구조라 불리며, 공개 키 암호 방식을 바탕으로 한 디지털 인증서를 활용하는 소프트웨어, 하드웨어, 사용자, 정책 및 제도 등을 총칭.



* 3.2.1. 자바 코드 서명


-

자바 코드는 JAR 파일 수준에서 서명한다.

자바 코드 서명은 코드 서명을 JAR 아카이브에 추가하기 위해 JAR 매니페스트 파일을 재사용하고 확장한다.

핵심 JAR 매니페스트 파일(MANIFEST.MF) 은 아카이브 안에 있는 각 파일의 파일명과 서명값 항목을 갖고 있다.

( 한국어 번역본 책 90p 에 MANIFEST.MF 파일의 일부가 있다. )



** 구현


-

자바 코드 서명은 서명 파일(확장자는 .SF, Signature File)이라는 또 다른 매니페스트 파일을 추가해 구현한다.

서명 파일에는 서명할 데이터와 이 데이터에 대한 디지털 인증값이 들어 있다.

( 한국어 번연복 책 90p 에는 서명 파일(SF, Signature File)의 일부가 있다. )



-

디지털 서명은 “서명 블록 파일(Signature Block File)” 이라고 하며, 2진 형태의 아카이브 파일에 저장된다.

서명 파일은 사용한 서명 알고리즘에 따라 .RSA, .DSA, .EC 확장자 중 하나를 사용한다.



-

서명 파일에는 전체 매니페스트 파일(SHA1Digest-Manifest)뿐만 아니라 MANIFEST.MF 에 있는 각 항목에 대한 다이제스트(Digest)가 들어 있다. ( Manifest.MF 에 있는 값을 기반으로 생성한단다. )

자바 6까지는 SHA-1 이 기본 다이제스트 알고리즘이었지만, 자바 7에서부터는 SHA-256 과 SHA-512 해시 알고리즘으로 매니페스트 파일의 다이제스트를 생성할 수 있게 되었다.

SHA-256 이나 SHA-512 를 사용하면 다이제스트 속성이 각각 SHA-256-Digest, SHA-512-Digest 가 된다.

안드로이드 버전 4.3(JellyBean MR2) 부터 SHA-256 과 SHA-512 다이제스트를 지원한다.



-

openssl 명령을 사용하면 서명 파일에 들어 있는 다이제스트를 간단히 검증할 수 있다.

$ openssl sha1 -binary MANIFEST.MF | openssl base 64

// 이 명령은 매니페스트 파일 전체의 SHA-1 다이제스트를 생성하고 Base64 로 인코딩해 SHA1-Digest-Manifest 값을 만든다.


$echo -en “Name: [filePath]\r\n SHA1-Digest: \[SHA1-Digest]\r\n\n\n” | openssl sha1 -binary | openssl base64

// 이 명령은 매니페스트 항목의 다이제스트를 계산하는 방법을 보여준다.



-

실제 디지털 서명은 2진 PKCS#7 (더 간단히 CMS 라고도 부른다.) 포맷으로 되어 있으며, 서명값과 서명 인증서를 담고 있다.

RSA 알고리즘으로 생성한 서명 블록은 .RSA 확장자 파일에 저장되고, DSA 알고리즘으로 생성한 서명 블록은 .DSA, EC 알고리즘으로 생성한 서명 블록은 .EC 파일에 저장된다.

다중 서명을 하는 경우, 여러 .SF 및 .RSA/DSA/EC 파일이 JAR 파일의 META-INF/ 디렉터리에 들어간다.



-

다양한 알고리즘과 파라미터로 서명 및 암호화할 수 있으므로 CMS 포맷은 다소 복잡하다.



-

JAR 파일을 서명할 때 CMS 구조에는 기본적으로 다이제스트 알고리즘, 서명 인증서(public key 를 포함, for verify), 서명값(private key로 만들어진 digital signature, 전자서명)이 들어간다고 알아두자.

CMS 명세서에서는 CMS 의 SingedData 구조체에 서명된 데이터를 포함할 수 있게 허용하지만(첨부된 서명(Attached Signature) 이라고 불리는 포맷 변형), JAR 서명에는 서명된 데이터를 포함시키지 않는다.

CMS 구조체에 서명된 데이터가 포함되지 않으면 그 서명을 분리된 서명(Detached Signatue)이라고 하며, 서명을 검증하려면 원래 데이터가 필요하다.



-

서명 데이터는 ASN.1 로 파싱 가능하다.

(ASN.1 은 추상 구문 표기법이라 불리는 Abstract Syntax Notation One 으로, 통신과 네트워크에서 데이터를 인코딩하는 규칙과 구조를 기술하는 표준 표기법. 암호 표준에서 암호 객체의 구조를 정의하기 위해 널리 사용된다.)



-

서명 블록에는 ASN.1 객체 데이터(SignedData)의 형을 나타내는 객체 식별자와 데이터 자체가 들어간다.

SignedData 객체에는 버전, 사용된 일련의 해시 알고리즘 식별자, 서명한 데이터의 형, 일련의 서명 인증서, 그리고 서명값을 포함하고 있는 SignerInfo 구조체가(서명자마다 하나씩) 들어간다.


SignerInfo 구조체에는 버전, SignerIdentifier객체, 사용된 다이제스트 알고리즘, 서명값을 생성하기 위해 사용한 다이제스트 암호화 알고리즘, 암호화된 다이제스트(서명값)자체가 들어간다.



-

JAR 와 APK 서명에 관련해 SignedData 구조체 안에서 가장 중요한 요소는 일련의 서명 인증서와 서명값이다.

JAR 파일의 내용을 압축해제하면 서명 파일이나 서명 데이터를 지정해 OpenSSL 의 smime 명령으로 서명을 검증할 수 있다.






** JAR 파일 서명


-

JAR 서명과 검증에 사용하는 공식 JDK 도구에는 jarsigner 와 keytool 명령이 있다.

자바 5.0 jarsigner 는 타임 스탬프 발급 기관(Time Stamp Authority, TSA)에 의해 서명 시각도 기록할 수 있게 해주므로, 서명 인증서의 유효 기간이 끝나기 전에 혹은 끝난 후에 서명이 만들어졌는지 확인해야 할 경우 상당히 유용하다.

그렇지만 이 기능은 널리 사용되지도 않고, 안드로이드에서 지원하지도 않는다.



-

키 저장소 파일명, 서명에 사용할 키 별명(-sigfile 옵션을 지정하지 않으면 키 별명의 앞에서부터 여덟 글자가 서명 블록 파일의 기본명이 된다.) 그리고 선택적으로 서명 알고리즘을 지정해 jarsigner 명령을 호출하면 JAR 파일을 서명할 수 있다.


cf) 자바 7부터 기본 알고리즘이 SHA256withRSA 로 바뀌었으므로, 하위 호환성을 위해 SHA-1 알고리즘을 사용하려면 명시적으로 지정해야 한다.

안드로이드 4.3(JellyBean MR2)부터 SHA-256 과 SHA-512 에 기반을 둔 서명이 지원된다.




** JAR 파일 검증


-

jarsigner 명령에 -verify 옵션을 지정하면 JAR 파일을 검증할 수 있다.

이 검증은 서명 블록과 서명 인증서를 검증해 서명 파일이 조작되지 않았음을 보장한다.

다음으로 서명 파일(CERT.SF)에 들어 있는 각각의 다이제스트가 매니페스트 파일(MANIFEST.MF)의 해당 섹션과 일치하는지 확인한다.

마지막으로 jarsigner 는 각 매니페스트 항목을 읽고 파일 다이제스트가 실제 파일의 내용과 일치하는지 확인한다.

-keystore 옵션으로 키 저장소를 지정하면 jarsigner 는 서명 인증서가 지정된 키 저장소에 존재하는지 검사한다.



-

자바 7부터 추가된 -strict 옵션은 시간 유효성 검사 및 인증서 체인 검증 등을 추가적으로 수행한다.




** 서명자 정보 보기


-

$keytool -list -printcert -jarfile test.apk

// 이 명령을 통해 APK 서명자 정보를 볼 수 있다.



-

아카이브 내용을 나열해 일단 서명 블록 파일명을 알아내면 openssl 과 unzip 명령을 이용해 서명 인증서를 간단히 하나의 파일로 추출할 수 있다. ( 만약 SignedData 구조체에 두 개 이상의 인증서가 들어 있으면 인증서가 모두 추출된다. 인증서가 여러개 있을 때에는 SignedInfo 구조체를 분석해 실제 서명 인증서의 식별자를 알아내야 한다. )

$ unzip -q -c test.apk META-INF/CERT.RSA | openssl pkcs7 -inform DER -print_certs -out \cert.pem




* 3.2.2. 안드로이드 코드 서명


-

안드로이드 코드 서명이 자바 JAR 서명에 기반을 두고 있으므로 다른 코드 서명 체계와 비슷하게 공개 키 암호와 X.509 인증서를 사용하지만, 유사점은 이게 전부다.



-

자바 ME 와 윈도우 폰 등 코드 서명을 사용하는 다른 거의 모든 플랫폼에서 코드 서명 인증서는 플랫폼이 신뢰하는 CA 에 의해 발급되어야 한다.

코드 서명 인증서를 발급하는 CA 가 많이 있지만 모든 타깃 디바이스가 신뢰하는 인증서를 획득하기는 상당히 어렵다.

그렇지만 안드로이드는 이 문제를 상당히 간단히 해결한다.


코드 서명 인증서의 내용이나 서명자에 대해 전혀 신경쓰지 않아도 된다.


CA 가 인증서를 발급할 필요가 없으며, 사실상 안드로이드에서 사용하는 거의 모든 코드 서명 인증서는 본인이 직접 서명한 것이다.

게다가 여러분의 신원을 입증할 필요가 없다.

주체명으로 어떤 문자열이든 사용할 수 있다. ( 구글 플레이 스토어에서는 일반적인 이름을 걸러내기 위해 몇 가지 검사를 실행하지만, 안드로이드 자체에서는 검사하지 않는다. )


안드로이드는 서명 인증서를 단순히 하나의 블랍(Blob)으로 처리하며, 단지 인증서가 JAR 포맷을 사용하기 때문에 X.509  포맷으로 되어 있을 뿐이다.



-

안드로이드에서는 유효 기간이 지난 인증서로 서명된 앱도 설치한다.



-

안드로이드에서는 APK 마다 일련의 서명자들을 할당하지만 (여러 서명자를 지원하지만 일반적으로 단 한 명이 서명한다 ), APK 파일 안에 있는 항목들을 서로 다른 서명자가 서명하도록 허용하지는 않는다.



-

안드로이드는 패키지 검사 루틴에 서명 인증서 검사 기능을 추가함으로써 APK 의 무결성을 검증하고 모든 APK 파일 항목이 동일 인증서로 서명되어 있는지 보장하기는 하지만, 안드로이드 코드 서명에 JAR 파일 포맷이 가장 좋은 선택은 아니라는 점은 명백하다.



** 안드로이드 코드 서명 도구


-

AOSP 의 build/ 디렉터리에는 signapk 라는 안드로이드 고유의 도구가 들어 있다.

몇 가지 두드러진 차이점을 제외하면 서명 모드에서는 jarsigner 와 거의 동일하게 작동한다.

특히 jarsigner 에서는 키들이 호환성 있는 키 저장소 파일에 들어 있어야 하지만, signapk 에서는 입력에 사용하는 서명 키(DER 인코딩된 PKCS#8 포맷)와 인증서(DER 인코딩된 X.509 포맷)가 별도의 파일에 존재해야 한다.



-

안드로이드 4.4(Kitkat)부터 signapk 는 SHA1withRSA 나(안드로이드4.3 플랫폼에 추가된) SHA256withRSA 매커니즘으로만 서명할 수 있지만, AOSP 마스터 브랜치에 있는 signapk 는 ECDSA 서명을 지원하도록 확장되었다.




** OTA 파일 코드 서명


-

APK 서명 모드가 기본이지만, signapk 도구는 -w 옵션으로 활성화할 수 있는 “전체 파일 서명” 모드도 갖고 있다.

전체 파일 서명 모드에서는 각각의 JAR 항목뿐만 아니라 전체 아카이브에 대한 서명도 생성한다.

이 모드는 jarsigner 에서는 지원되지 않고 안드로이드에서만 지원된다.



-

각각의 파일이 서명되어 있는데 전체 아카이브를 서명하는 이유는 무엇일까?

바로 무선 업데이트(Over the Air, OTA) 업데이트를 지원하기 위해서다.

OTA 패키지는 업데이트할 파일과 적용할 스크립트를 담고 있으며, JAR 파일과 비슷한 포맷으로 되어 있다.

패키지에는 META-INF/ 디렉터리, 매니페스트, 서명 블록, 이외 PEM 포맷으로 되어 있는 업데이트 인증서인 META-INF/com/android/otacert 등 몇 가지 파일이 들어 있다.



-

업데이트 하기 위해 복구 모드로 부팅하기 전에 안드로이드는 패키지 서명을 검증하고 업데이트를 서명한 인증서를 신뢰할 수 있는지 확인한다.

OTA 할 때 신뢰하는 인증서는 일반적인 시스템 신뢰 저장소와는 다른 파일에 존재하며, 보통 /system/etc/security/otacerts.zip 파일로 저장되는 ZIP 파일 안에 들어간다.

상용 디바이스에서는 일반적으로 이 ZIP 파일에 releasekey.x509.pem 파일 하나만 들어 있다.



-

디바이스를 재부팅한 후 복구 OS 는 시스템을 업데이트하기 전에 OTA 패키지 서명을 한번 더 검증함으로써 재부팅하는 동안 OTA 패키지가 변경되지 않았는지를 검증한다.





반응형

댓글