iOS In-App-Purchase Verifying receipts locally in Swift

79874cedccd1cf5baa8cb264b5091ee6?s=47 codelynx
September 14, 2019
4

iOS In-App-Purchase Verifying receipts locally in Swift

There aren't so much living example how to verify receipts of In-App-Purchase locally preferably in Swift. Some how, reusing the same verification code are not recommended for security reason. Therefore, it is hard to implement whole set of receipt verification code in your hand. This slide is not intended for encouraging write less secure code, but guide you how to implement a whole set of verification code in Swift. You may have to consider how to make your code to become more secure before your releasing day.

79874cedccd1cf5baa8cb264b5091ee6?s=128

codelynx

September 14, 2019
Tweet

Transcript

  1. Verifying In-App- Purchase Receipts Locally in Swift kaz.yoshikawa@gmail.com iOS Tokyo

    Meetup September 2019
  2. About me

  3. Kaz Yoshikawa • Electricwoods LLC / Digital Lynx Systems Inc.

    • e-mail: kyoshikawa@electricwoods.com • twitter: @codelynx1 • Working History • Adobe Systems (Tokyo) • Lionbridge (Tokyo) • Quark (Tokyo / Denver) • Hummingbird Communications (Mt. View, USA) • Fact International (Vancouver, Canada) • Perle Systems (Toronto, Canada), etc.
  4. How to verify In-App-Purchase Receipts “Locally”? Show me the code.

  5. Verifying receipts via server • There are many articles about

    how to verify receipt through your trusted server • iOSͷֹ݄՝ۚϨγʔτݕূΛαʔόʔαΠυͰߦ͏ͱ͖ͷTips·ͱΊ • iOS In-App Purchase࣮૷Ͱඞͣ஌͓͖͍ͬͯͨӅΕͨ᠘ • Apple App Store Receipt Validation with Swift and Go • Validating in-app purchases in your iOS app • ϨγʔτͷverifyͱSandbox • ࣗಈߪಡ՝ۚʹ͍ͭͯʲiOSฤʳ
  6. Verifying receipts locally • Very few articles available: • Suggestions

    like: • Use “ASN1C” to access ASN.1 format • Verify the receipt is signed by Apple • Use OpenSSL • They do not show any working code. • Hey ! show me the code.
  7. Disclaimer • This presentation material is not for: • How

    to write secure code • How to prevent from code cracking • Discouraging to write less secure code
  8. Steps to verify • Obtain Apple’s root certificate • Your

    app receive a receipt • Verify if the receipt is signed by Apple • Extract purchases from the receipt • Verify if a purchase is expired or cancelled • Unlock contents if necessary
  9. Obtain Apple’s root certificate Apple Root Certificates Apple Inc. Root

    Certificate Apple Computer, Inc. Root Certificate Apple Root CA - G2 Root Certificate Apple Root CA - G3 Root Certificate Apple Intermediate Certificates Apple IST CA 2 - G1 Certficate Apple IST CA 4 - G1 Certficate Apple IST CA 5 - G1 Certficate Apple IST CA 8 - G1 Certficate Application Integration Certificate Application Integration 2 Certificate Application Integration - G3 Certificate Developer Authentication Certificate Developer ID Certificate Software Update Certificate Timestamp Certificate WWDR Certificate (Expiring 02/07/23) WWDR Certificate (Expiring 02/14/16) Worldwide Developer Relations - G2 Certificate Apple PKI Apple established the Apple PKI in support of the generation, issuance, distribution, revocation, administration, and management of public/private cryptographic keys that are contained in CA-signed X.509 Certificates. https://www.apple.com/certificateauthority/
  10. Find hash (sha256) of the “AppleIncRootCertificate.cer” $ shasum -a 256

    AppleIncRootCertificate.cer b0b1730ecbc7ff4505142c49f1295e6eda6bcaed7e2c68c5be91b5a11001f024 AppleIncRootCertificate.cer Use this to ensure AppleIncRootCertificate.cer is genuine
  11. Load the Root Certificate var appleIncRootCertificate: Data { if let

    url = Bundle.main.url(forResource: "AppleIncRootCertificate", withExtension: "cer"), let data = try? Data(contentsOf: url) { // make sure the certificate is not fake one let sha256 = Data(base64Encoded: "sLFzDsvH/0UFFCxJ8Slebtpryu1+LGjFvpG1oRAB8CQ=") if data.sha256 == sha256 { return data } } fatalError("error: failed to read the certificate.") } Check hash against hardcoded base64 hash value
  12. Receiving receipts Bundle.main.appStoreReceiptURL if let url = Bundle.main.appStoreReceiptURL { do

    { let receiptData = try Data(contentsOf: url) // ... } catch { } }
  13. Digging ASN1 Format

  14. Check if it is signed by Apple let pkcs7 =

    try PKCS7(data: reciptData) let appleX509cert = try X509Certificate(data: self.appleIncRootCertificate) guard let appleKey = appleX509cert.publicKey?.key else { print("x509 public key not found."); return } print(appleKey as NSData) // check if one of these certificates is signed by apple let signedByApple: Bool = { print("certificates:") for certificate in pkcs7.certificates { if let signedKey = certificate.publicKey?.key { print(signedKey as NSData) if signedKey == appleKey { return true } } } return false }()
  15. Check all purchases if let inAppPurchases = receipt.inAppPurchases { for

    purchase in inAppPurchases { guard let productIdentifier = purchase.productId else { continue } print(productIdentifier) print(purchase.purchaseDate ?? "n/a") print(purchase.originalPurchaseDate ?? "n/a") print(purchase.cancellationDate ?? "n/a") print(purchase.expiresDate ?? "n/a") // ... } } What to check is not covered by this presentation.
  16. Recap

  17. Qiita https://qiita.com/codelynx/items/a88805b47b7e40ef3782

  18. Thank you kyoshikawa@electricwoods.com