ios.netjwtin-app-purchase

How to verify JWS in dotnet?


I encountered an issue that has troubled me for a long time, and I found a lot of information, but most of it is only in Java and PHP. I have tried to write corresponding C# code based on the Java and PHP code, but it seems that C# lacks a similar API. This task is beyond my abilities.

Requirements

I’m developing an iOS app that needs to integrate with the Apple In-App Purchase (IAP) system. After a user successfully makes a purchase, the Apple server generates a callback and sends a string of information to me, which I need to verify for authenticity.But I don't known how to do it. (Although I would like to share the content of this JWS, it contains sensitive information and cannot be publicly disclosed.)

Current Progress

  1. Callback Information Verification:

    • The information sent in the callback is a JSON string, where the payload is a JWS. This JWS can be verified directly at https://jwt.io, confirming that the JWS is valid and can self-verify.
  2. Certificate Chain Verification:

    • I can use the X509Chain class to verify the certificate chain in the x5c part of the JWS. Here’s my validation code:
    // AppleRootCA-G3.cer is downloaded from apple.com manually 
    var AppleRootCA = new X509Certificate2("AppleRootCA-G3.cer");
    var handler = new JwtSecurityTokenHandler();
    var jsonToken = handler.ReadToken(dto.signedPayload) as JwtSecurityToken; // dto is the json that the apple sent to me, dto.signedPayload is a jws.
    var itor = jsonToken.Header["x5c"] as IReadOnlyList<object>;
    
    // certificate2.Thumbprint is same as AppleRootCA.Thumbprint
    X509Certificate2 certificate2 = new X509Certificate2(Convert.FromBase64String((string)itor[2]));
    X509Certificate2 certificate1 = new X509Certificate2(Convert.FromBase64String((string)itor[1]));
    X509Certificate2 certificate0 = new X509Certificate2(Convert.FromBase64String((string)itor[0]));
    
    // I have trusted the AppleRootCA-G3.cer before, so I don't need to call them again.
    // X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
    // store.Open(OpenFlags.ReadWrite);
    // store.Add(AppleRootCA);
    
    using var chain = new X509Chain();
    // chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    // chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
    chain.ChainPolicy.ExtraStore.Add(certificate2);
    chain.ChainPolicy.ExtraStore.Add(certificate1);
    chain.ChainPolicy.ExtraStore.Add(certificate0);
    var isValid = chain.Build(AppleRootCA);
    Console.WriteLine(isValid); // true
    
  3. Token Validation Attempt:

    • I attempted to use the following code to validate the token:
    var validationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new X509SecurityKey(AppleRootCA),
        ValidateIssuer = false,
        ValidateAudience = false,
        // RequireExpirationTime = true,
        ValidateLifetime = true,
        // TryAllIssuerSigningKeys = true
    };
    try
    {
        handler.ValidateToken(dto.signedPayload, validationParameters, out var st);
        // await handler.ValidateTokenAsync(jsonToken, validationParameters);
        Console.WriteLine("success");
        // return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"false: {ex.Message}");
        // return false;
    }
    
    • However, I keep encountering the following error:
    IDX10517: Signature validation failed. The token's kid is missing. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: 'B52CB02FD567E0359FE8FA4D4C41037970FE01B0', InternalId: 'tSywL9Vn4DWf6PpNTEEDeXD-AbA'. , KeyId: B52CB02FD567E0359FE8FA4D4C41037970FE01B0
    '. Number of keys in TokenValidationParameters: '1'.
    Number of keys in Configuration: '0'.
    Exceptions caught:
    '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
    token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. See https://aka.ms/IDX10503 for details.
    
    • ChatGPT suggested that I pass in the correct IssuerSigningKey. I tried changing the input parameter AppleRootCA to certificate0, certificate1, and certificate2, but the same error persists. I’m currently at a loss and don’t know how to ensure this JWS is valid. I’m very eager to get some help!

Some information may be helpful:AppleRootCA-G3.cer is downloaded from apple.com/certificateauthority/AppleRootCA-G3.cer


Solution

  • Finally,I have found out the real solution.Replace

    IssuerSigningKey = new X509SecurityKey(AppleRootCA),
    

    to

    IssuerSigningKey = new ECDsaSecurityKey(certificate0.GetECDsaPublicKey()),
    

    All things will work well.

    This solution helps me: https://github.com/naveenjangra2/AppStoreServerNotifications

    Also,here is an example token from App Store Server Notifications I found out in Github.com:https://github.com/alexalok/AppStoreServerNotificationsV2/blob/main/AppStoreServerNotificationsV2/Converters/Sandbox_Resubscription.json

    Thanks naveenjangra2 and alexalok works!