Updating this code to comply with TN3138: Handling App Store receipt signing certificate changes

My Mac app fails to open for some users with the error:

"ABC.app does not support the latest receipt validation requirements."

I assume this is due to the update of the App Store receipt signing intermediate certificate with one that uses the SHA-256 algorithm.

I cannot reproduce this myself and I have trouble figuring out how to address this issue.

Below is the code that decrypts the receipt and verifies its signature.

How does this code need to be updated to support the new signing certificate?

Thanks a lot in advance!


inline static void CheckBundleSignature(void)
{
    NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
    
    SecStaticCodeRef staticCode = NULL;
    OSStatus status = SecStaticCodeCreateWithPath((__bridge CFURLRef)bundleURL, kSecCSDefaultFlags, &staticCode);
    if (status != errSecSuccess) {
        [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Create a static code", nil];
    }
    
    NSString *requirementText = @"anchor apple generic";
    
    SecRequirementRef requirement = NULL;
    status = SecRequirementCreateWithString((__bridge CFStringRef)requirementText, kSecCSDefaultFlags, &requirement);
    if (status != errSecSuccess) {
        if (staticCode) CFRelease(staticCode);
        [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Create a requirement", nil];
    }
    
    status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirement);
    if (status != errSecSuccess) {
        if (staticCode) CFRelease(staticCode);
        if (requirement) CFRelease(requirement);
        [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Check the static code validity", nil];
    }
    
    if (staticCode) CFRelease(staticCode);
    if (requirement) CFRelease(requirement);
}

static NSData *DecodeReceiptData(NSData *receiptData)
{
    CMSDecoderRef decoder = NULL;
    SecPolicyRef policyRef = NULL;
    SecTrustRef trustRef = NULL;
    
    @try {
        OSStatus status = CMSDecoderCreate(&decoder);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Create a decoder", nil];
        }
        
        status = CMSDecoderUpdateMessage(decoder, receiptData.bytes, receiptData.length);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Update message", nil];
        }
        
        status = CMSDecoderFinalizeMessage(decoder);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Finalize message", nil];
        }
        
        NSData *ret = nil;
        CFDataRef dataRef = NULL;
        status = CMSDecoderCopyContent(decoder, &dataRef);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Get decrypted content", nil];
        }
        ret = [NSData dataWithData:(__bridge NSData *)dataRef];
        CFRelease(dataRef);
        
        size_t numSigners;
        status = CMSDecoderGetNumSigners(decoder, &numSigners);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: Get singer count", nil];
        }
        if (numSigners == 0) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: No signer found", nil];
        }
        
        policyRef = SecPolicyCreateBasicX509();
        
        CMSSignerStatus signerStatus;
        OSStatus certVerifyResult;
        status = CMSDecoderCopySignerStatus(decoder, 0, policyRef, TRUE, &signerStatus, &trustRef, &certVerifyResult);
        if (status) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: Get signer status", nil];
        }
        if (signerStatus != kCMSSignerValid) {
            [NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: No valid signer", nil];
        }
        
        return ret;
    } @catch (NSException *e) {
        @throw e;
    } @finally {
        if (policyRef) CFRelease(policyRef);
        if (trustRef) CFRelease(trustRef);
        if (decoder) CFRelease(decoder);
    }
}

From Update your app to support SHA-256 certificates:

Follow these guidelines to update your app to support certificates that use the SHA-256 algorithm for on-device receipt validation:

  1. If your app follows the instructions in Validating receipts on the device, the new certificate affects step 2, which involves verifying the certificate chain. Be sure your app uses the latest certificates from Apple PKI.

  2. Use cryptography code that supports SHA-256 algorithm. If you wrote your own code to verify receipts, update that code to use the SHA-256 algorithm. If your app uses a cryptography library, update the library to the latest version that supports SHA-256 algorithm.

  3. Test your app in the sandbox environment to ensure that your on-device receipt validation succeeds.

You need to debug and test your code to ensure that it follows the above guidelines.

Updating this code to comply with TN3138: Handling App Store receipt signing certificate changes
 
 
Q