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.
RSA Digital Signatures To sign a message m, just apply the RSA function with the private key to produce a signature s; to verify, apply the RSA function with the public key to the signature, and check that the result equals the expected message.
Some software updates are automatically verified When you download and install an update from Apple, Apple's digital signature is automatically verified before installation.
Open the Preferences dialog box. Under Categories, select Signatures. For Verification, click More. To automatically validate all signatures in a PDF when you open the document, select Verify Signatures When The Document Is Opened.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With