시작하며

[이전 글]

최근에 네트워크 통신 관련 규제가 심해짐에 따라, 우리나라에서도 ZTNA (Zero Trust Network Access)를 도입하고자 하는 이야기가 나온다. ZTNA란 네트워크 통신 구간에 있어서 그 어떤 통신도 허용하지 않는다는 이야기이다. 이는 두 네트워크 노드 사이를 신뢰할 수 있는 개인키와 공개키를 가지고 통신한다는 말로도 이해할 수 있다. 이는 즉, 기존 우리가 사용하던 공인인증서 방식을 사용하거나 또는 FIDO Aliance의 국제 산업 표준 기준을 더욱 활발하게 사용한다는 뜻으로도 이해할 수 있다. 우리나라 또한 FIDO의 사용을 더욱 추진하지 않을까라는 전망을 해본다.

문제점1

하지만 진짜 문제는 FIDO Aliance의 연동 문제이다. 앞으로 FIDO가 더욱 본격적으로 사용될 수 있을거라 예상할 수 있는데,(특히 모바일) FIDO Aliance에 나와있는 문서를 보면 여러가지 문제들이 발견된다.

첫 번째로 마지막 업데이트 날짜가 2017년이라는 점이다. 그리고 Android앱을 통해서 FIDO UAF를 구현하기 위해선, Android Framework의 전용 API인 StartActivityForResult를 사용하라고 나와있다는 것이다. 이 부분에 있어, 필자는 FIDO Aliance가 왜 StartActivityForResult에 의존하라고 했는지 많은 고민을 했으며, 어느정도 이해가 가긴 했다. 하지만 현재 상황으로 본다면 위 API는 Deprecated되었으며, 더 이상 위 API를 통해 FIDO UAF의 안드로이드 부분을 구현할 수 없다는 점이다.


가이드 원문 : https://fidoalliance.org/specs/fido-uaf-v1.2-ps-20201020/FIDO-UAF-COMPLETE-v1.2-ps-20201020.pdf

해결책?

StartActivityForResult라는 API는 안드로이드에서 워낙 범용적으로 오래 사용되어오고 있었고, 이로써 바뀌지 않을거란 생각을 했을것이다. 그래서 과감하게 위 API에 의존하라고 MUST라는 키워드까지 붙이며 권장하고 있었을 것이다.

하지만 생각해봐야 할 부분은 Vendor사 입장에서 위 API를 그대로 사용하는 것은, 고객사로 하여금 StartActivityForResult를 그대로 사용하라고 강요하는 것과 같다. 고객은 위 API가 아닌, ActivityResultLauncher를 사용하고 있을터이다. 하지만 Fido벤더사로 인하여 자신들의 소스코드에 적용되어 있는 모든 코드를 StartActivityForResult로 바꾸는 것은 말이 안된다.

이로 인해, Fido Aliance의 Vendor사들은 아래의 선택지에 직면하게 된다.

  1. 인터페이스 변경 시도를 해본다(StartActivityForResult -> ActivityResultLauncher)
  2. 2번이 불가능 하다면, 어댑터 패턴을 적용하여, 기존 UAF의 SDK를 래핑한다.
  3. Fido Aliance UAF의 Android부분을 다시 구축한다.

문제점1의 해결책

필자의 경우, 위 1번과 2번으로써는 도저히 해결되지 않았다. 위 사진을 보면 알겠지만, 결국 FIDO CLIENT의 인터페이스가 바뀐다는 것은 이전 ASM모듈이 내려주는 데이터에 대한 모든 처리를 해주어야 한다는 것이고, 이를 위의 1번과 2번으로 시도하기엔 불가능했다. 따라서 다시 만들게 되었다.

문제점2

문서의 2017년 마지막 업데이트로 안드로이드 생태계를 반영 못하는 또 다른 문제가 있다. 바로, FacetID를 발급받을때의 로직이다.

FacetID는 앱을 패키징하며 사용된 서명 키(=.jks파일)를 통하여 해시값을 뽑아내고 이를 통해 FacetID가 생성된다.

하지만 2017년땐, apk파일을 통해서만 구글 콘솔에 앱을 업로드헀다. 하지만 지금은 aab파일을 통해서만 구글 콘솔에 앱을 업로드 할 수 있다.

이는 아주 큰 문제인게, apk파일을 통해서만 앱을 업로드 할 시, 키가 1개만 필요하다는 점이다. 현재는 앱을 서명할 때 '업로드 키'(개발자가 관리)와 '앱 서명 키'(구글이 관리)를 통하여 2번 서명이 진행되는데 이는 반영 못한다는 점이다.

현, FIDO Aliance UAF에 나와있는 방식대로만 따른다고 가정했을 시, 개발자가 가지고 있는 '업로드 키'만 사용한 FacetID를 추출하게 된다. 그럼 어떤일이 벌어질까?

구글에 앱을 업로드했을 때, 구글이 보유한 '앱 서명 키'를 사용하여 한번 더 앱이 서명될 것이고 이로 인해 FacetID가 예상했던것과 달라진다는 문제가 있다. 이는 즉, UAF인증을 사용하는 사용자들로 하여금 인증이 안된다는 말이다.

아래는 기존의 FacetID를 추출하던 Android의 코드이다.


가이드 원문 : https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#obtaining-facetid-of-android-native-app

위의 방식을 통해 FacetID를 추출하면 고객사들은 반드시 문제에 직면할 것이다. 왜냐? 개발자가 보유한 '업로드 키'를 사용한 FacetID를 추출할 것이기 때문이다.

문제점2의 해결책

앱을 최종 패키징(=구글의 앱 서명키 사용)했을 때, 그에 맞는 해시값을 뽑아내는 코드가 새로 필요하다. 아래는 필자가 알아낸 FacetID추출 코드이며 테스트 또한 완료헀다.

fun getFacetID(): String {
    val packageManager = context.packageManager
    val uid = packageManager.getActivityInfo(
        packageManager.getLaunchIntentForPackage(context.packageName)?.component ?: throw NullPointerException("FacetID Error. generating `componentName` is null."),
        PackageManager.GET_META_DATA
    ).applicationInfo.uid

    val packageNames = packageManager.getPackagesForUid(uid) ?: throw NullPointerException("FacetID Error. generating `packageName` is null.")
    val packageInfo = context.packageManager.getPackageInfo(
        packageNames[0],
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            PackageManager.GET_SIGNING_CERTIFICATES
        } else {
            PackageManager.GET_SIGNATURES
        }
    )

    val signatures =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            packageInfo.signingInfo?.signingCertificateHistory ?: throw NullPointerException("FacetID Error. generating `signatures` is null.")
        } else {
            packageInfo.signatures
        }
    val md = MessageDigest.getInstance("SHA1")

    val certBytes = signatures[0].toByteArray()
    md.update(certBytes)

    return "android:apk-key-hash:" + Base64.encodeToString(md.digest(), Base64.NO_PADDING or Base64.NO_WRAP)
}

위의 코드를 사용하면 문제없이 '앱 서명 키'에 대한 FacetID를 얻어올 수 있다. 다만 주의할 점이 있다. 위 메서드를 호출했을 때, 개발자 개인 노트북을 사용한 디버깅 키로 서명하여 앱을 실행하거나 동일 방식의 apk파일을 쓰면 안된다는 것이다. 앞으로 사용할 jks파일을 정해두고, 그 파일을 통해 로그를 뽑아서 육안으로 확인해야 한다. 그리고 메모해두고 쓰면 끝이다.

FIDO Aliance가 개선하면 좋을거 같은 점

Android Framework의 API에 의존하는 것이 아닌, 단순 java나 kotlin언어 기능에 의존하게 하면 어땟을까라는 생각을 한다. 옵저버 패턴을 사용하여 콜백함수를 사용하는 방식처럼 말이다. 특히, 코틀린의 경우는 고차함수 기능이 deprecated될 확률이 거의 없다. 이유는 kotlin의 경우, 함수형 프로그래밍을 지원하는 언어이기에 해당 기능이 빠질 수 없기 때문이다.

또한 FacetID를 추출하는 코드를 개선할 필요가 있다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN