SW개발/개발이야기

앱 스토어 인앱결제 서버 알림 Signature 검증 구현기 (feat. X.509 인증서란?)

안녕하세요, 오늘은 앱 스토어 인앱결제 시스템 구현을 위해 사용되어지는 App Store Server Notification에서 수신 받는 데이터를 검증하는 방법에 대해서 이야기 해보려고 합니다. 

 

https://developer.apple.com/documentation/appstoreservernotifications

 

Apple Developer Documentation

 

developer.apple.com

 

App Store Server Notification 란?

앱 스토어에서 인앱결제에 대한 상태 변화들을 실시간으로 알려주는 Server to Server 서비스입니다. 구매, 취소, 환불, 갱신, 만료 등 인앱결제 상품에서 발생하는 이벤트들을 수신할 수 있습니다.

 

수신받는 서버는 수신된 데이터를 가지고 본인의 서비스에 알맞게 비즈니스 로직을 작성하면 됩니다.

 

다루고자 하는 범위

이번 포스팅에서는 Server Notification 중 알림 데이터에 대한 검증 부분만을 집중적으로 다룰 것 입니다. 이 외에 내용들은 위의 애플 공식 문서를 참고하시면 좋을 것 같습니다.

 

Server Notification의 Signature 검증 처리

검증이 필요한 이유

JWT의 개념과 유사합니다. JWT 역시 header, signature를 통해서 해당 토큰이 변조 되었는지 아닌지에 대한 검증이 필요합니다. Server Notification 역시 마찬가지로 수신받은 데이터가 변조되었는지 아닌지에 대한 검증을 해야합니다.

 

검증을 하지 않고 단순히 payload 부분만을 디코딩해서 이용한다면 악의적인 의도를 가진 사람이 데이터를 변조해, 서버 주소로 요청을 보내면 보안 사고가 발생할 가능성이 높아집니다. (물론, request를 보고 애플에서 보낸 것인지 아닌지 판별할 수는 있지만 이 역시도 변조될 수 있습니다)

 

Signature 검증 구현

기본적인 방법은 공식문서에 나와있고, 제가 정리한 내용은 다음과 같습니다.

  1. 수신된 알림을 "."(콤마) 구분자로 하여 header, payload, signature로 분리하고 base64url 디코딩 합니다.
  2. header 부분에 담긴 alg, x5c 데이터를 확인합니다.
  3. alg, x5c 값을 이용해 signature를 복호화 합니다. 
  4. 복호화가 성공적이라면 검증에 통과한 것입니다.

 

이제는 실제로 구현하는 Python 코드를 살펴봅니다. 라이브러리의 기능을 사용하기에 위의 내용과 다를 수 있습니다.

import jwt
from cryptography.x509 import load_pem_x509_certificate

encoded_jws = "애플로부터 수신 받은 인코딩 되어 있는 알림데이터(E.g eysdaf.ey32tgsdg.ifsdgh)"

header = jwt.get_unverified_header(encoded_jws)

# x5c의 첫번째 값인 공개키로, cert 인증서 오브젝트를 생성함
# BEGIN, END는 PEM 파일의 형태
cert_str = f"-----BEGIN CERTIFICATE-----{header['x5c'][0]}-----END CERTIFICATE-----".encode('utf-8')
cert_obj = load_pem_x509_certificate(cert_str)
# cert 인증서로부터 공개키를 가져옴
public_key = cert_obj.public_key()

# jwt 라이브러리로 알림데이터를 공개키와 알고리즘을 포함해 복호화, signature 검증 옵션 True
decoded_jws = jwt.decode(
    encoded_jws, key=public_key, algorithms=[header['alg']], options={"verify_signature": True}
)

PyJWT 라이브러리를 활용하여 보다 간단하게 구현할 수 있습니다. 이렇게 한다면 decoded_jws에는 signature 검증을 마친 payload가 딕셔너리 형태로 저장됩니다. 복호화에 실패한 경우에는 에러가 발생합니다.

 

(위 코드는 샘플 코드이므로 추가적인 에러처리나 추상화된 로직으로의 리팩토링이 필요합니다.)

 

x5c의 모든 요소들을 인증서로 만들어서 각각의 cert_obj에는 무엇이 담겨있는지도 확인해봅니다.

print(cert_obj_1.subject)
<Name(CN=Prod ECC Mac App Store and iTunes Store Receipt Signing,
OU=Apple Worldwide Developer Relations,O=Apple Inc.,C=US)>

print(cert_obj_2.subject)
<Name(CN=Apple Worldwide Developer Relations Certification Authority, OU=G6,O=Apple Inc.,C=US)>

print(cert_obj_3.subject)
<Name(CN=Apple Root CA - G3,OU=Apple Certification Authority,O=Apple Inc.,C=US)>

이름만 보고 유추하자면 앱 스토어 영수증, 개발자 인증 기관, 애플 인증기관 과 같은 정보들이 담겨져 있는 것을 확인할 수 있습니다. 이 내용들은 뒤에서 부가 설명합니다.

 

실제로 구현된 코드만 본다면 정말 간단합니다. 하지만, 이를 제대로 이해하고 구현하는데에는 굉장히 많은 지식들을 필요로 합니다. 이어서 그 내용들을 소개합니다.

 

X.509 (x5c) 인증서란?

X.509는 ITU-T가 만든 PKI(공개키 기반 구조)의 표준입니다. 인증서에는 다음과 같은 정보들이 배열의 형태로 담겨 있습니다.

  1. 첫번째 요소 [고정] : 공개키 값
  2. 두번째 요소 : 첫번째 요소에 대한 Certificate(인증서), 주로 미들 CA 인증서
  3. 세번째 요소 : 두번째 요소에 대한 Certificate, 주로 Root CA 인증서 

조금 더 풀어서 설명해보겠습니다. 

  1. 단어 그대로 "공개키" 입니다. 해당 키 값을 이용해 복호화에 이용할 수 있습니다.
  2. 첫번째 공개키를 발급한 기관에 대한 인증이 담겨 있는 인증서 입니다. 중간급 인증 기관입니다.
  3. 두번째 인증서를 발급한 기관에 대한 인증이 담겨 있는 인증서 입니다. 최상위 인증 기관입니다.

 

해당 인증서의 스펙은 RFC5280 으로 정의되어 있으며 아래의 문서에서 자세한 내용을 확인할 수 있습니다.

https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6

 

RFC ft-ietf-jose-json-web-signature: JSON Web Signature (JWS)

JSON Web Signature (JWS) represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON

datatracker.ietf.org

 

해당 스펙에 따라서 위의 샘플 코드에서는 첫번째 요소인 공개키를 가지고 복호화를 시도한 것입니다. 사실상 두, 세번째 요소들은 Signature를 검증하는데 이용되지 않습니다.

 

X.509를 이해하려고 하다보니 CA라는 용어가 나옵니다. 이것도 무엇인지 알아봅니다.

 

CA (Certificate Authority)란?

CA는 디지털 인증서를 발급하는 하나의 단위입니다. 인증 기관(CA)은 공개키 인증서를 발급하고, 인증서에 대한 정보를 사용자에게 확인시켜주는 의무를 가지고 있습니다.

 

또한, 애플은 인증서에 대한 관리를 담당하기 위해 루트 CA를 설립해서 독자적으로 운영하고 있다고 합니다.

https://www.apple.com/pki/

 

Public Key Infrastructure - Apple

 

www.apple.com

 

위에서 cert_obj 에 담긴 값을 확인해본 내용들이 이제는 이해가 되시나요? 저 역시도 검증 과정에 담긴 용어들과 값들을 이해하기 위해서는 꽤나 많은 시간과 지식들이 필요했습니다.

 

마치며

여태까지 JWT를 다룰 때는 x5c 라는 필드를 본적이 없었습니다. 처음에 검색했을때는 그저 공개키 값이 들어있는 데이터인가? 라고 생각했습니다.

하지만 값도 3개이고 검증과 함께 decode를 하려고하면 무언가 예외가 발생하면서 잘 되지 않았습니다.

 

그래서 스펙에 대해서 자세히 공부해보고, Cert 인증서를 만들어서 복호화에 이용한다는 것을 깨닫고 구현할 수 있었던 것 같습니다.

 

결론만 보면 간단한 코드지만 구현을 위해서 많은 지식들을 알게된 것 같아서 재미있었던 경험이었습니다. 다른분들에게도 위 내용들이 도움이 되기를 바라면서 마무리 하겠습니다.

 

읽어주셔서 감사합니다 :)

 

728x90