12

In my static Library I have a licence file. Which I want to make sure has been generated by myself (and has not been altered). So the idea was to use an RSA Signature from what I've read.

I've looked on the internet and this is what I came up with:

First: Generating the private keys and self signed certificates with the information I found here.

// Generate private key
openssl genrsa -out private_key.pem 2048 -sha256

// Generate certificate request
openssl req -new -key private_key.pem -out certificate_request.pem -sha256

// Generate public certificate
openssl x509 -req -days 2000 -in certificate_request.pem -signkey private_key.pem -out certificate.pem -sha256

// Convert it to cer format so iOS kan work with it
openssl x509 -outform der -in certificate.pem -out certificate.cer -sha256

After that, I create a licence file (with a date and app identifier as contents) and generate a signature for that file like so based on the information found here:

// Store the sha256 of the licence in a file
openssl dgst -sha256 licence.txt > hash

// And generate a signature file for that hash with the private key generated earlier
openssl rsautl -sign -inkey private_key.pem -keyform PEM -in hash > signature.sig

Which I think all works fine. I don't get any errors and get the keys and certificates and other files as expected.

Next I copy certificate.cer, signature.sig and license.txt to my application.

Now I want to check if the signature has been signed by me and is valid for the license.txt. I found it fairly hard to find any good examples but this is what I have currently:

The Seucyrity.Framework I found out uses a SecKeyRef to reference an RSA key / certificate and SecKeyRawVerify to verify a signature.

I have the following method to load the public key from a file.

- (SecKeyRef)publicKeyFromFile:(NSString *) path
{
    NSData *myCertData = [[NSFileManager defaultManager] contentsAtPath:path];
    CFDataRef myCertDataRef = (__bridge CFDataRef) myCertData;

    SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorDefault, myCertDataRef);
    CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    SecTrustRef trust;
    SecTrustCreateWithCertificates(certs, policy, &trust);
    SecTrustResultType trustResult;
    SecTrustEvaluate(trust, &trustResult);
    SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);

    if (trustResult == kSecTrustResultRecoverableTrustFailure)
    {
        NSLog(@"I think this is the problem");
    }
    return pub_key_leaf;
}

Which is based on this SO post.

For the signature validation I found the following function

BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
    size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
    const void* signedHashBytes = [signature bytes];

    size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
    uint8_t* hashBytes = malloc(hashBytesSize);
    if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) {
        return nil;
    }

    OSStatus status = SecKeyRawVerify(publicKey,
                                      kSecPaddingPKCS1SHA256,
                                      hashBytes,
                                      hashBytesSize,
                                      signedHashBytes,
                                      signedHashBytesSize);

    return status == errSecSuccess;
}

Which is taken from here

In my project I call the code like so:

// Get the licence data
NSString *licencePath = [[NSBundle mainBundle] pathForResource:@"licence" ofType:@"txt"];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:licencePath];

// Get the signature data
NSString *signaturePath = [[NSBundle mainBundle] pathForResource:@"signature" ofType:@"sig"];
NSData *signature = [[NSFileManager defaultManager] contentsAtPath:signaturePath];

// Get the public key
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"];
SecKeyRef publicKey = [self publicKeyFromFile:publicKeyPath];

// Check if the signature is valid with this public key for this data
BOOL result = PKCSVerifyBytesSHA256withRSA(data, signature, publicKey);

if (result)
{
    NSLog(@"Alright All good!");
}
else
{
    NSLog(@"Something went wrong!");
}

Currently it always says: "Something went wrong!" though I am not sure what. I found out that trust result in the method that fetches the public key equals kSecTrustResultRecoverableTrustFailure which I think is the problem. In the Apple documentation I found that could be the result of a certificate that has been expired. Though that does not seem to be the case here. But maybe there is something wrong with the way I generate my certificate?

My question boils down to, what am I doing wrong, and how could I fix that? I find the documentation on this quite sparse and hard to read.

I have uploaded an iOS project with generated certificates and the code referenced here. Maybe that could come in handy.

Community
  • 1
  • 1
Matthijn
  • 3,126
  • 9
  • 46
  • 69
  • 1
    If you look at Listing 3-5 in the documentation you pointed to, you'll see it listing "`AllStatusBits`". Can you figure out what the status bits are when you encounter that error? – Michael Dautermann Oct 16 '15 at 14:51
  • Hi, when I try to incorporate that code one of the first errors is that the type `CSSM_TP_APPLE_CERT_STATUS` of `AllStatusBits` is unknown and I can't seem to find a working header file to include to get that type. On the internet I found that it might be `#import ` but that one does not exist (anymore?) on iOS. - I have [uploaded](http://up.indev.nl/RTR4y0Ou0L.zip) my project with the code and certificates, maybe that helps. – Matthijn Oct 16 '15 at 15:23
  • Yes, I just double checked, but I did [already](http://up.indev.nl/C5OHOFziPO.png) add the Security framework. Maybe that has been moved somewhere else in an iOS version? – Matthijn Oct 16 '15 at 16:34
  • Sorry you are right, it is not in the iOS Security.framework, I was looking at an OS X project. – zaph Oct 16 '15 at 16:43
  • Hi, how strict is the security requirement? I'm wondering why you don't want to use something simple like a md5/sha hash and just hardcode the hash value into your app? – user3334059 Oct 19 '15 at 22:29
  • I want the licence system so that I can every time ship the same SDK but with another licence file for other vendors. That's why I wanted to go for this system. – Matthijn Oct 20 '15 at 11:40

1 Answers1

8

The problem is lying on the way you create the signature file; following the same step I was able to produce the binary equivalent signature.sig file.

By looking inside the hash file we can see openssl add some prefix (and hex encode the hash):

$ cat hash
SHA256(licence.txt)= 652b23d424dd7106b66f14c49bac5013c74724c055bc2711521a1ddf23441724

So signature.sig is based on that and not on license.txt

By using your sample and creating the signing file with:

openssl dgst -sha256 -sign certificates/private_key.pem licence.txt > signature.sig

The hashing & signing step gets correct, and the sample outputs: Alright All good!


The final state of my file, just in case

- (SecKeyRef)publicKeyFromFile:(NSString *) path
{
    NSData * certificateData = [[NSFileManager defaultManager] contentsAtPath:path];
    SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData);
    SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
    SecTrustRef trust;
    SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
    SecTrustResultType resultType;
    SecTrustEvaluate(trust, &resultType);
    SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
    return publicKey;
}

BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
    uint8_t digest[CC_SHA256_DIGEST_LENGTH];
    if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], digest))
        return NO;

    OSStatus status = SecKeyRawVerify(publicKey,
                                      kSecPaddingPKCS1SHA256,
                                      digest,
                                      CC_SHA256_DIGEST_LENGTH,
                                      [signature bytes],
                                      [signature length]);

    return status == errSecSuccess;
}

PS: the malloc was a leak


Edit:

To make your current signature.sig file work as-is, you have to produce the same step as openssl (add prefix, hex-hash, and a newline \n), then pass this data to SecKeyRawVerify with kSecPaddingPKCS1 and not kSecPaddingPKCS1SHA256:

BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
    uint8_t digest[CC_SHA256_DIGEST_LENGTH];
    if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], digest))
        return NO;

    NSMutableString *hashFile = [NSMutableString stringWithFormat:@"SHA256(licence.txt)= "];
    for (NSUInteger index = 0; index < sizeof(digest); ++index)
        [hashFile appendFormat:@"%02x", digest[index]];

    [hashFile appendString:@"\n"];
    NSData *hashFileData = [hashFile dataUsingEncoding:NSNonLossyASCIIStringEncoding];

    OSStatus status = SecKeyRawVerify(publicKey,
                                      kSecPaddingPKCS1,
                                      [hashFileData bytes],
                                      [hashFileData length],
                                      [signature bytes],
                                      [signature length]);

    return status == errSecSuccess;
}
blld
  • 1,030
  • 10
  • 19
  • You still though get a `kSecTrustResultRecoverableTrustFailure` in the `SecTrustEvaluate`, but yes it is successfully verifying the signature – Lefteris Oct 21 '15 at 13:49
  • I think the kSecTrustResultRecoverableTrustFailure issue is that your certificate is self signed, if you want it to properly succeed all the trust chain have to be valid; you have to get a certificate from an (Apple) trusted authority. (ver*s*gn or the like) – blld Oct 22 '15 at 01:56
  • or install your certificate on the host iOS device (which doesn't seems to be an option) – blld Oct 22 '15 at 01:58
  • 1
    @AurélienBouilland: It's possible; email the certificate to the device, open the attachment and process it. – l'L'l Oct 25 '15 at 19:17