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.
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):So
signature.sig
is based on that and not onlicense.txt
By using your sample and creating the signing file with:
The hashing & signing step gets correct, and the sample outputs:
Alright All good!
The final state of my file, just in case
PS: the
malloc
was a leakEdit:
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 toSecKeyRawVerify
withkSecPaddingPKCS1
and notkSecPaddingPKCS1SHA256
: