서론

    졸업 프로젝트를 하다 React Native 환경의 클라이언트에서 앱 무결성 검증을 해야 하는 상황이 되었다.
    이 기록은 앱 무결성 검증을 하며 헤맨 부분의 기록이다.

    본론

    iOS AppAttest도 앱의 변조 여부를 파악하는 역할을 한다. 하지만 iOS의 경우 키 복호화 등 절차가 너무 복잡해 라이브러리를 사용하여 문제를 해결하였다.

    // implementation("ch.veehait.devicecheck:devicecheck-appattest:0.9.6")
    
    lateinit var attest: AppleAppAttest
    
    init {
        attest = AppleAppAttest(
            app = App(teamId, packageName),
            appleAppAttestEnvironment = AppleAppAttestEnvironment.PRODUCTION
        )
    }
    
    fun verifyIosAppAttest(
        attestation: String,
        keyId: String?,
        challenge: String
    ): IntegrityVerificationResponse {
        try {
            val validator = attest.createAttestationValidator()
    
            val result = validator.validate(
                Base64.getDecoder().decode(attestation),
                keyId ?: "",
                Base64.getDecoder().decode(challenge)
            )
            // 라이브러리의 validate 메소드는 검증 실패 시 AttestationException을 throw 합니다.
            // 여기까지 도달했다는 것은 검증에 성공했다는 의미입니다.
            return IntegrityVerificationResponse(
                isValid = true,
                message = "iOS app attestation verified successfully"
                // 필요한 경우 'result' 객체에서 추가 정보를 추출하여 details에 포함
            )
        } catch (e: AttestationException) { // <-- 라이브러리의 특정 예외를 캐치
            // Attestation 검증 자체에서 발생한 오류
            logger.error("App attestation validation failed: ${e.message}", e)
            return IntegrityVerificationResponse(
                isValid = false,
                message = "App attestation validation failed: ${e.message}",
                details = mapOf("errorType" to e.javaClass.simpleName, "validationError" to (e.message ?: "unknown")) // 예외 메시지 포함
            )
        } catch (e: Exception) { // 그 외 예상치 못한 다른 오류
            logger.error("Unexpected error during iOS app attestation verification", e)
            return IntegrityVerificationResponse(
                isValid = false,
                message = "Unexpected error verifying iOS attestation: ${e.localizedMessage ?: e.message}",
                details = mapOf("errorType" to e.javaClass.simpleName)
            )
        }
    }

    iOS의 경우도 똑같이 검사 결과를 Decode하는 절차가 필요하다. 하지만 애플의 경우 키 교환 등.... 직접 구현하기엔 너무 많은 보안절차가 필요하다.
    그렇다고 전용 엔드포인트를 제공하지도 않는다... 그래서 라이브러리를 사용하는 상황이 되었다;

    검증결과, 프론트엔드에서 생성된 KeyId, 그리고 Challenge code(Nonce)를 비교해 검증을 실시한다.

    결론

    앱의 무결성 검사는 무척 중요한 파트이다. 하지만 국내에서는 잘 다뤄지지 않은 파트로 보인다.
    그래서 미래에 똑같은 일을 하게 될 상황을 대비해 기록을 남긴다.

    Posted by dalbodeule