Generating a signature for promotional offers in Objective-c

My requirements are to generate a signature for a promotional offer in the iOS app code (Objective-C), but I'm getting nil every time with the error: 'The operation couldn’t be completed. (OSStatus error -50 - EC private key creation from data failed).' Please refer to my code below and suggest how I can get the signature. The privateKeyPath is a .p8 file saved in the Xcode project's main folder:

+ (NSString *)generateSignatureWithKeyId:(NSString *)keyId
                                bundleId:(NSString *)bundleId
                               productId:(NSString *)productId
                                 offerId:(NSString *)offerId
                                   nonce:(NSUUID *)nonce
                               timestamp:(NSTimeInterval)timestamp
                          privateKeyPath:(NSString *)privateKeyPath {
    
    // Load the private key
    NSString *privateKeyString = [NSString stringWithContentsOfFile:privateKeyPath encoding:NSUTF8StringEncoding error:nil];
    
    if (!privateKeyString) {
        NSLog(@"Failed to load private key");
        return nil;
    }
    
    // Remove the header and footer
    privateKeyString = [privateKeyString stringByReplacingOccurrencesOfString:@"-----BEGIN PRIVATE KEY-----" withString:@""];
    privateKeyString = [privateKeyString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    privateKeyString = [privateKeyString stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    privateKeyString = [privateKeyString stringByReplacingOccurrencesOfString:@"-----END PRIVATE KEY-----" withString:@""];
   
    // Convert to NSData
    NSData *privateKeyData = [[NSData alloc] initWithBase64EncodedString:privateKeyString options:NSDataBase64DecodingIgnoreUnknownCharacters];
    
    NSLog(@"NSDATA private key: %@", [privateKeyData base64EncodedStringWithOptions:0]);
    
    if (!privateKeyData) {
        
        NSLog(@"Failed to decode private key");
        
        return nil;
        
    }
    
    // Create the payload string
    NSString *payloadString = [NSString stringWithFormat:@"%@\u2063%@\u2063%@\u2063%@\u2063%@\u2063%@\u2063%.0f",
                               bundleId,
                               keyId,
                               productId,
                               offerId,
                               @"", // appAccountToken is optional, leave empty if not used
                               [nonce UUIDString],
                               timestamp];
    
    NSData *payloadData = [payloadString dataUsingEncoding:NSUTF8StringEncoding];
    
    // Sign the payload
    NSData *signature = [self signData:payloadData withPrivateKey:privateKeyData];
    
    if (!signature) {
        NSLog(@"Failed to sign data");
        return nil;
    }
    
    // Base64 encode the signature
    NSString *signatureBase64 = [self base64UrlEncode:signature];
    
    return signatureBase64;
}

+ (NSData *)signData:(NSData *)data withPrivateKey:(NSData *)privateKey {
    
    SecKeyRef keyRef = [self createSecKeyRefFromPrivateKeyData:privateKey];
    
    if (!keyRef) {
        NSLog(@"Failed to create private key reference.");
        return nil;
    }

    size_t sigLen = SecKeyGetBlockSize(keyRef);
    uint8_t *sig = malloc(sigLen);
    if (sig == NULL) {
        NSLog(@"Failed to allocate memory for signature.");
        return nil;
    }

    OSStatus status = SecKeyRawSign(keyRef, kSecPaddingPKCS1, data.bytes, data.length, sig, &sigLen);
    if (status != errSecSuccess) {
        NSLog(@"Failed to sign data: %d", (int)status);
        free(sig);
        return nil;
    }

    return [NSData dataWithBytes:sig length:sigLen];
}

+ (SecKeyRef)createSecKeyRefFromPrivateKeyData:(NSData *)privateKeyData {
    
    NSString *base64EncodedString = [privateKeyData base64EncodedStringWithOptions:0];
    
    SecKeyRef privateKeyRef = NULL;
   
    NSDictionary *privateKeyAttr = @{
        
        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
        (id)kSecAttrKeySizeInBits: @256,
        (id)kSecAttrIsPermanent: @NO
        
    };

    CFErrorRef error = NULL;
    privateKeyRef = SecKeyCreateWithData((__bridge CFDataRef)privateKeyData,
                                         (__bridge CFDictionaryRef)privateKeyAttr,
                                         &error);

    if (error != NULL) {
        
        NSString *errorDescription = CFBridgingRelease(CFErrorCopyDescription(error));
        NSLog(@"Error creating private key reference: %@", errorDescription);

        CFRelease(error);
        privateKeyRef = NULL;
        
    }

    return privateKeyRef;
}

So, I’m presuming that the call that actually fails is SecKeyCreateWithData. If so, the most likely reason is that you’re key byte aren’t in the expected format. I talked about this in depth in On Cryptographic Key Formats and the related Importing Cryptographic Keys.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the quick and detailed response. I'm still going through your explanation, but due to time constraints, I would like to know a more straightforward solution. After downloading the .p8 file from Apple Developer, how can I proceed in Objective-C code to generate a signature?

I am requesting Apple to make subscription offers presentation easier. For example, by creating a method with the main subscription product ID and the offer ID, the offer can be presented to the user without signature generation.

Have a great day.

Generating a signature for promotional offers in Objective-c
 
 
Q