RSA/ECB/PKCS1Padding iOS encryption - ios

I am currently stuck at a problem which involves encryption in iOS.
My client has given me the public key,
"-----BEGIN PUBLIC KEY-----
xxxx
-----END PUBLIC KEY-----"
The padding strategy that needs to be used is RSA/ECB/PKCS1Padding.
With android, it seems pretty straight forward
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
return encryptedBytes;
I dont see any direct methods to do this in iOS. Any of the common pods used like Commoncrypto doesnt allow me to force PKCS1 padding scheme. Being a pretty inexperienced guy with RSA and encryption, it would be very much appreciated if you could help me understand on how to approach this and guide me through this.

Using standard Security Framework - SecKeyEncrypt with kSecPaddingPKCS1 parameter

My issue was solved using non padding :
kSecPaddingNone

-(SecKeyRef)getPublicKeyForEncryption
{
NSString *thePath = [MAuthBundle pathForResource:#"certificate" ofType:#"der"];
//2. Get the contents of the certificate and load to NSData
NSData *certData = [[NSData alloc]
initWithContentsOfFile:thePath];
//3. Get CFDataRef of the certificate data
CFDataRef myCertData = (__bridge CFDataRef)certData;
SecCertificateRef myCert;
SecKeyRef aPublicKeyRef = NULL;
SecTrustRef aTrustRef = NULL;
//4. Create certificate with the data
myCert = SecCertificateCreateWithData(NULL, myCertData);
//5. Returns a policy object for the default X.509 policy
SecPolicyRef aPolicyRef = SecPolicyCreateBasicX509();
if (aPolicyRef) {
if (SecTrustCreateWithCertificates((CFTypeRef)myCert, aPolicyRef, &aTrustRef) == noErr) {
SecTrustResultType result;
if (SecTrustEvaluate(aTrustRef, &result) == noErr) {
//6. Returns the public key for a leaf certificate after it has been evaluated.
aPublicKeyRef = SecTrustCopyPublicKey(aTrustRef);
}
}
}
return aPublicKeyRef;
}
-(NSString*) rsaEncryptString:(NSString*) string
{
SecKeyRef publicKey = [self getPublicKeyForEncryption];
NSData* strData = [string dataUsingEncoding:NSUTF8StringEncoding];
CFErrorRef err ;
NSData * data = CFBridgingRelease(SecKeyCreateEncryptedData(publicKey, kSecKeyAlgorithmRSAEncryptionPKCS1, ( __bridge CFDataRef)strData, &err));
NSString *base64EncodedString = [data base64EncodedStringWithOptions:0];
return base64EncodedString;
}

Related

Objective C- RSA digital signature from objective c sent to JAVA server can't be verified

Im generating and signing data with RSA private key from objective C,
and sending signed data with string RSA public key to JAVA server,
then the server must be verifying the signed data with the public key I provided,
but it always fails.
Can anyone tell me what is wrong with the codes on Objective C side.
Here's how I'm generating RSA key pairs
KeyChainWrapper* keychainTemp = [[KeyChainWrapper alloc] initWithService:[NSString stringWithFormat:#"SS%#TEMP", KEYCHAINSERVICEID] withGroup:nil];
NSDictionary* attributes =
#{ (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: #2048
};
CFErrorRef cfError = NULL;
SecKeyRef privateKeySec = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &cfError);
if (!privateKeySec) {
CFBridgingRelease(cfError);
return;
}
SecKeyRef publicKeySec = SecKeyCopyPublicKey(privateKeySec);
NSData* dataPublicKey = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(publicKeySec, &cfError);
NSData* dataPrivateKey = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(privateKeySec, &cfError);
if (dataPublicKey && dataPrivateKey) {
[keychainTemp insert:[NSString stringWithFormat:#"%#RSAPUBKEYTEMP", strType] DATA:dataPublicKey];
[keychainTemp insert:[NSString stringWithFormat:#"%#RSAPRIKEYTEMP", strType] DATA:dataPrivateKey];
}
if (publicKeySec) { CFRelease(publicKeySec); }
if (privateKeySec) { CFRelease(privateKeySec); }
then convert dataPublicKey to strPublicKey to send to the server,
and here's how I sign the data.
strRsaSign = [self sha256HashFor:[NSString stringWithFormat:#"%#%#%#%#", userId, systemId, [self SSGET_UNIQUE_DEVICE], strPublicKey]];
NSData* dataStrRSASign = [strRsaSign dataUsingEncoding:NSUTF8StringEncoding];
size_t signatureBytesSize = SecKeyGetBlockSize(privateKey);
uint8_t *signatureBytes = (uint8_t *)malloc(signatureBytesSize * sizeof(uint8_t));
memset((void *)signatureBytes, 0x0, signatureBytesSize);
OSStatus status = SecKeyRawSign(privateKey,
kSecPaddingPKCS1,
(unsigned char *)dataStrRSASign.bytes,
dataStrRSASign.length,
signatureBytes,
&signatureBytesSize
);
base64StrRSASign = [[NSData dataWithBytes:signatureBytes length:signatureBytesSize] base64EncodedStringWithOptions:0];
And then, I send the base64StrRSASign and strPublicKey to the server,
to let server verifies the base64StrRSASign with strPublicKey.
What can I possibly be missing here if anyone can tell me...

Get public/private key from certificate

I try to get public or private key from certificate saved on device.
I'm using this method:
- (SecKeyRef)publicKeyFromFile:(NSString *)path
{
NSData * certificateData = [[NSData alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path]];
if (certificateData != nil && certificateData.bytes != 0) {
CFDataRef cfDataPath = CFDataCreate(NULL, [certificateData bytes], [certificateData length]);
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, cfDataPath);
if (certificateFromFile) {
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
SecTrustResultType resultType;
SecTrustEvaluate(trust, &resultType);
SecKeyRef publicKeyObj = SecTrustCopyPublicKey(trust);
return publicKeyObj;
}
}
return nil;
}
There is data in cfDataPath, but certificateFromFile is always nil...
Does anyone know where's the problem?
Apple doc refers:
Obtaining a SecKeyRef Object for Public Key Cryptography
Extracting Keys from the Keychain If you are using existing public and private keys from your keychain, read Certificate, Key, and Trust Services Programming Guide to learn how to retrieve a SecKeychainItemRef object for that key.
Once you have obtained a SecKeychainItemRef, you can cast it to a SecKeyRef for use with this API.
Importing Existing Public and Private Keys Importing and exporting public and private key pairs is somewhat more complicated than generating new keys because of the number of different key formats in common use.
This example describes how to import and export a key pair in PEM (Privacy Enhanced Mail) format.
Read more : https://developer.apple.com/library/mac/documentation/Security/Conceptual/SecTransformPG/SigningandVerifying/SigningandVerifying.html and https://developer.apple.com/library/mac/documentation/Security/Conceptual/CertKeyTrustProgGuide/01introduction/introduction.html#//apple_ref/doc/uid/TP40001358
Try with this:
-(BOOL)trustCertFromChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustResultType trustResult;
SecTrustRef trust = challenge.protectionSpace.serverTrust;
OSStatus status = SecTrustEvaluate(trust, &trustResult);
//DLog(#"Failed: %#",error.localizedDescription);
//DLog(#"Status: %li | Trust: %# - %li",(long)status,trust,(long)trustResult);
if (status == 0 && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
SecKeyRef serverKey = SecTrustCopyPublicKey(trust);
NSString *certPath = [[NSBundle mainBundle] pathForResource:#"MYCert" ofType:#"der"];
NSData *certData = [NSData dataWithContentsOfFile:certPath];
SecCertificateRef localCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
SecKeyRef localKey = NULL;
SecTrustRef localTrust = NULL;
SecCertificateRef certRefs[1] = {localCertificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *)certRefs, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &localTrust);
if (status == errSecSuccess)
localKey = SecTrustCopyPublicKey(localTrust);
CFRelease(localTrust);
CFRelease(policy);
CFRelease(certArray);
if (serverKey != NULL && localKey != NULL && [(__bridge id)serverKey isEqual:(__bridge id)localKey])
return YES;
else
return NO;
}
//DLog(#"Failed: %#",error.localizedDescription);
return NO;
}
Follow the accepted answer for more details: Objective-C / C pulling private key (modulus) from SecKeyRef

X.509 RSA Encryption/Decryption iOS

I need to implement encryption / decryption using a X.509 RSA public/private key pair.
So far, I have something which I think will work for encryption, but I have no way of decrypting to check. Everything I try has issues reading the private key.
Generating the key pairs (returns a .der and a .pem)
openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem -days 3650
Encryption (Not sure if this works, but looks like it does)
+ (NSData *) RSAEncryptData:(NSData *)content {
SecKeyRef publicKey;
SecCertificateRef certificate;
SecPolicyRef policy;
SecTrustRef trust;
size_t maxPlainLen;
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:#"public_key" ofType:#"der"];
NSData *base64KeyData = [NSData dataWithContentsOfFile:publicKeyPath];
certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef) base64KeyData);
if (certificate == nil) {
NSLog(#"Can not read certificate from data");
return nil;
}
policy = SecPolicyCreateBasicX509();
OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);
if (returnCode != 0) {
NSLog(#"SecTrustCreateWithCertificates fail. Error Code: %d", (int)returnCode);
return nil;
}
SecTrustResultType trustResultType;
returnCode = SecTrustEvaluate(trust, &trustResultType);
if (returnCode != 0) {
return nil;
}
publicKey = SecTrustCopyPublicKey(trust);
if (publicKey == nil) {
NSLog(#"SecTrustCopyPublicKey fail");
return nil;
}
maxPlainLen = SecKeyGetBlockSize(publicKey) - 12;
size_t plainLen = [content length];
if (plainLen > maxPlainLen) {
NSLog(#"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
return nil;
}
void *plain = malloc(plainLen);
[content getBytes:plain
length:plainLen];
size_t cipherLen = 128; // currently RSA key length is set to 128 bytes
void *cipher = malloc(cipherLen);
OSStatus encryptReturnCode = SecKeyEncrypt(publicKey, kSecPaddingPKCS1, plain,
plainLen, cipher, &cipherLen);
NSData *result = nil;
if (encryptReturnCode != 0) {
NSLog(#"SecKeyEncrypt fail. Error Code: %d", (int)returnCode);
}
else {
result = [NSData dataWithBytes:cipher
length:cipherLen];
}
free(plain);
free(cipher);
return result;
}
Decryption
I have tried using OpenSSL's PEM_read_X509 also PEM_read_RSAPrivateKey, but both fail to read the cert. I have not even gotten past that. If I could do this without having a dependency on the OpenSSL library, that would be even better.
+(void)readTest{
FILE *fp;
X509 *x;
NSString *path =[[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"pem"];
fp=fopen([path UTF8String],"r");
x=NULL;
PEM_read_X509(fp,&x,NULL,NULL); // I have also tried PEM_read_RSAPrivateKey
if (x == NULL) {
NSLog(#"Cant Read File"); // This ALWAYS fires
}
fclose(fp);
X509_free(x);
}
If someone could help me out with encryption/decryption using X.509 RSA pairs, I'd appreciate it. Thanks.
Where you are stuck
It seems your private key is encrypted (openssl asked you for a password on the command line), yet you do not decrypt it when you try to open it. Besides, private_key.pem is an RSA key, not a certificate, so you should use PEM_read_RSAPrivateKey.
The following decoding code should work:
int pass_cb(char *buf, int size, int rwflag, void* password) {
snprintf(buf, size, "%s", (char*) password);
return strlen(buf);
}
+(void)readTest{
FILE *fp;
RSA *x;
NSString *path =[[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"pem"];
fp=fopen([path UTF8String],"r");
x = PEM_read_RSAPrivateKey(fp,&x,pass_cb,"key password");
if (x == NULL) {
NSLog(#"Cant Read File"); // This ALWAYS fires
}
fclose(fp);
X509_free(x);
}
Alternatively, you could generate a non-encrypted key. Pass -nodes to openssl when creating the keys and the certificate.
Please note that you might need to make sure OpenSSL is properly initialized with:
SSL_library_init();
OpenSSL_add_all_algorithms();
Besides, OpenSSL generates error messages that could help you through development. You load the error strings with:
SSL_load_error_strings();
And you could call:
ERR_print_errors_fp(stderr);
RSA encryption and decryption on iOS
OpenSSL is not the only solution as Security framework on iOS contains everything you need. I guess you turned to OpenSSL because you did not know how to convert your private key file to valid parameters for SecKeyDecrypt.
The trick is to produce a PKCS#12 file and to call SecPKCS12Import.
You can produce this file with OpenSSL:
openssl x509 -inform der -outform pem -in public_key.der -out public_key.pem
openssl pkcs12 -export -in public_key.pem -inkey private_key.pem -out private_key.p12
This will ask you for an export password. This password should be passed to SecPKCS12Import ("key password" below).
NSString *privateKeyPath = [[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"p12"];
NSData *pkcs12key = [NSData dataWithContentsOfFile:privateKeyPath];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys: #"key password", kSecImportExportPassphrase, nil];
CFArrayRef importedItems = NULL;
OSStatus returnCode = SecPKCS12Import(
(__bridge CFDataRef) pkcs12key,
(__bridge CFDictionaryRef) options,
&importedItems
);
importedItems is an array containing all imported PKCS12 items, and basically, the "identity" (private key + certificate).
NSDictionary* item = (NSDictionary*) CFArrayGetValueAtIndex(importedItems, 0);
SecIdentityRef identity = (__bridge SecIdentityRef) [item objectForKey:(__bridge NSString *) kSecImportItemIdentity];
SecKeyRef privateKeyRef;
SecIdentityCopyPrivateKey(identity, &privateKeyRef);
Then you can use privateKeyRef to perform the decryption with SecKeyDecrypt. To match your encryption routine:
size_t cipherLen = [content length];
void *cipher = malloc(cipherLen);
[content getBytes:cipher length:cipherLen];
size_t plainLen = SecKeyGetBlockSize(privateKeyRef) - 12;
void *plain = malloc(plainLen);
OSStatus decryptReturnCode = SecKeyDecrypt(privateKeyRef, kSecPaddingPKCS1, cipher, cipherLen, plain, &plainLen);

encrypt or sign string with public key in iOS with Objective C

i have a private key. Text File that begins like "--- begin private key..."
i want to use that key to encrypt an NSString. since its the private key, better call it sign an NSString.
can this be done without any external frameworks?
the result should be equivalent to the php openssl_sign function.
The iOS SDK framework you will need to use is called CommonCrypto. Here's a very good article that describes the right way to go about it.
Edit: I missed the part about compatibility with the PHP function openssl_sign. The solution below resolves that.
The way to do this so that it's compatible with the PHP function openssl_sign is to use the OpenSSL library. The openssl_sign function uses OpenSSL's EVP API internally to encrypt the input string using the private key and compute the SHA-1 hash digest of that encrypted string. It's common then to convert this hash digest into a Base64-encoded string.
Unfortunately, the iOS SDK does not include OpenSSL, but it's easy to build it. The following instructions for building OpenSSL for iOS are taken from this blog post and are reproduced here to provide a complete solution to the question.
In Terminal, follow those steps to build the OpenSSL library for iOS:
# Make a directory in which to run the build
mkdir ~/openssl-ios
cd ~/openssl-ios
# Download the openssl source (verify the file before using it in production!)
curl -O http://www.openssl.org/source/openssl-1.0.1e.tar.gz
# Download the openssl iOS build script
curl -O https://raw.github.com/Raphaelios/raphaelios-scripts/master/openssl/build-openssl.sh
# Make the build script executable
chmod +x build-openssl.sh
# Run the script (takes about 3min on an Intel Core i5)
./build-openssl.sh
This will take a few minutes but once it's complete you can verify that the build library is a universal library that you can use on iOS devices and in the iOS Simulator using the following command:
lipo -info ~/openssl-ios/lib/*.a
Now that the OpenSSL library has been built, let's got on with writing the code to sign a string.
First, we need to setup the Xcode project to link against the OpenSSL library. Drag & drop both libcrypto.a and libssl.a to the Frameworks group in the Project Navigator of your iOS project. In your project's Build Settings, add the following to the Header Search Paths setting:
~/openssl-ios/include/include
Next, create a new Objective-C Category file called openssl_sign on the NSString class. In NSString+openssl_sign.h, define the following interface:
#interface NSString (openssl_sign)
- (NSString *)signStringWithPrivateKey:(NSData *)privateKey;
#end
In NSString+openssl_sign.m, add the following header imports:
#import <openssl/evp.h>
#import <openssl/pem.h>
And add the following implementation of signStringWithPrivateKey::
#implementation NSString (openssl_sign)
- (NSString *)signStringWithPrivateKey:(NSData *)privateKeyData
{
BIO *publicBIO = NULL;
EVP_PKEY *privateKey = NULL;
if ((publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length])) == NO) {
NSLog(#"BIO_new_mem_buf() failed!");
return nil;
}
if (PEM_read_bio_PrivateKey(publicBIO, &privateKey, NULL, NULL) == NO) {
NSLog(#"PEM_read_bio_PrivateKey() failed!");
return nil;
}
const char * cString = [self cStringUsingEncoding:NSUTF8StringEncoding];
unsigned int stringLength = [self length];
unsigned char * signatureBuffer[EVP_MAX_MD_SIZE];
int signatureLength;
EVP_MD_CTX msgDigestContext;
const EVP_MD * msgDigest = EVP_sha1();
EVP_MD_CTX_init(&msgDigestContext);
EVP_SignInit(&msgDigestContext, msgDigest);
EVP_SignUpdate(&msgDigestContext, cString, stringLength);
if (EVP_SignFinal(&msgDigestContext, (unsigned char *)signatureBuffer, (unsigned int *)&signatureLength, privateKey) == NO) {
NSLog(#"Failed to sign string.");
return nil;
}
EVP_MD_CTX_cleanup(&msgDigestContext);
EVP_PKEY_free(privateKey);
NSData *signatureData = [NSData dataWithBytes:signatureBuffer length:signatureLength];
NSString *signature = [signatureData base64EncodedStringWithOptions:0];
return signature;
}
#end
In the class that will be signing the string, you can now import NSString+openssl_sign.h and sign the string like so:
NSData *privateKey = ...; // Read the .pem file into a NSData variable
NSString *helloSignature = [#"hello" signStringWithPrivateKey:privateKey];
You can verify that the signatures are the same using the following command in Terminal:
echo -n "hello" | openssl dgst -sha1 -sign priv.pem | openssl enc -base64 | tr -d '\n'
You can solve this much easier with no external sources or components.
I found out how and wanted to share it so i may help others.
You need to load the key file a SecKeyRef and safe the maxPlainLen as well
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:privateKeyResourceName ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
//change to the actual password you used here
[options setObject:#"_YOURPASSWORDHERE__" forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef) p12Data,
(__bridge CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
CFRelease(items);
privateKey = privateKeyRef;
maxPlainLen = SecKeyGetBlockSize(privateKey) - 12;
You can convert NSString with a category method to SHA1
- (NSData*)toSha1AsData {
// PHP uses ASCII encoding, not UTF
const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
// This is the destination
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
// This one function does an unkeyed SHA1 hash of your hash data
CC_SHA1(keyData.bytes, keyData.length, digest);
// Now convert to NSData structure to make it usable again
NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]
return out;
}
Now you can sign your SHA1 with this method
(NSData *)signSha1Data:(NSData *)data {
size_t plainLen = [data length];
if (plainLen > maxPlainLen)
{
NSLog(#"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
return nil;
}
void *plain = malloc(plainLen);
[data getBytes:plain
length:plainLen];
size_t cipherLen = 128; // currently RSA key length is set to 128 bytes
void *cipher = malloc(cipherLen);
OSStatus returnCode = SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
plain, plainLen, cipher, &cipherLen);
NSData *result = nil;
if (returnCode != 0) {
NSLog(#"SecKeyEncrypt fail. Error Code: %ld", returnCode);
}
else {
result = [NSData dataWithBytes:cipher
length:cipherLen];
}
free(plain);
free(cipher);
return result;
}
It works very well and without any external libs. There is no need to compile some wierd openssl stuff.

How to recover RSA from file?

I want to recover a public key from a file. Here is the Java code that works:
PublicKey readPubKeyFromFile(AssetFileDescriptor cle) throws IOException {
// read RSA public key
byte[] encodedKey = new byte[(int) cle.getDeclaredLength()];
cle.createInputStream().read(encodedKey);
// create public key
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedKey);
PublicKey pk = null;
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePublic(publicKeySpec);
} catch(Exception e) {
Logger.getInstance().logError("KeyUtils", e.toString());
}
return pk;
}
And here is the iOS code that doesn't work:
-(SecKeyRef)readPublicKeyFromFile:(NSString*)filename andExtension:(NSString*)extension {
NSString* filePath = [[NSBundle mainBundle] pathForResource:filename ofType:extension];
NSData* encodedKey = [NSData dataWithContentsOfFile:filePath];
CFDataRef myCertData = (CFDataRef)encodedKey;
SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorSystemDefault, myCertData);
CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus check = SecTrustCreateWithCertificates(certs, policy, &trust);
if (check != noErr)
{
NSLog(#"Problem extracting public key from file: %#", filename);
return nil;
}
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
return pub_key_leaf;
}
Any idea of what is wrong in my iOS code?
I've tested your code and there is nothing wrong with it. The problem seems to be related with the format of the certificates that you are trying to get the public key.
The function SecCertificateCreateWithData() assumes that the certificate that you are providing is in DER format. Most certificates that you find are encoded in base64 like the famous .pem format. I've tested your code with a correctly formatted DER certificate (the certificate form developer.apple.com converted to DER with openssl) and the public key is correctly extracted.
To convert a .pem certificate to DER simply use openssl in terminal:
openssl x509 -in developer.apple.com.pem -outform der -out cert.der
After that the output certificate file should work with no problems with your code.
But you can convert the certificate on the application itself, you only need to grab de x509 base64 encoded certificate (assuming that you are using .pem encoded certificates) and convert it to binary.
There is an example how you can do it:
This code will assume that the certificate is encoded in the following standard:
-----BEGIN CERTIFICATE-----
< your base64 encoded certificate goes here >
-----END CERTIFICATE-----
The code to convert this certificate to binary DER is:
-(NSData *)getBinaryCertificateFromPemEncodedFile:(NSString *)filename andExtension:(NSString *)extension
{
NSString* filePath = [[NSBundle mainBundle] pathForResource:filename ofType:extension];
NSString *pemCert = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
//The header and footer conforms to .pem specificatio
NSString *header = #"-----BEGIN CERTIFICATE-----";
NSString *footer = #"-----END CERTIFICATE-----";
NSString *base64Cert;
NSScanner *scanner = [NSScanner scannerWithString:pemCert];
//First we ignore the header part
[scanner scanString:header intoString:nil];
//Then we copy the base64 string excluding the footer
[scanner scanUpToString:footer intoString:&base64Cert];
//The reason I'm using NSDataBase64DecodingIgnoreUnknownCharacters is to exclude possible line breaks in the encoding
NSData *binaryCertificate = [[NSData alloc] initWithBase64EncodedString:base64Cert options:NSDataBase64DecodingIgnoreUnknownCharacters];
return binaryCertificate;
}
Then a small adaptation in your perfectly functional code does the trick:
-(SecKeyRef)readPublicKeyFromCertificate:(NSData *)binaryCertificate {
NSData *encodedKey = binaryCertificate;
CFDataRef myCertData = (CFDataRef)CFBridgingRetain(encodedKey);
SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorSystemDefault, myCertData);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
//If you only have one certificate you don't need to put it inside an array
OSStatus check = SecTrustCreateWithCertificates(cert, policy, &trust);
if (check != noErr)
{
NSLog(#"Problem extracting public key from certificate");
return nil;
}
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
return pub_key_leaf;
}
Then just call it:
NSData *data = [self getBinaryCertificateFromPemEncodedFile:#"developer" andExtension:#"pem"];
SecKeyRef key = [self readPublicKeyFromCertificate:data];
NSLog(#"%#", key);
And if your certificate is "valid" you should see:
2014-09-15 21:52:13.275 cert[15813:60b] <SecKeyRef algorithm id: 1,
key type: RSAPublicKey, version: 2, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537},
modulus: BE19E30F47F2D31F27D576CF007B3E615F986D14AFD0D52B825E01E90BA3E1CBB6F3A472E6AECDC28BC13D0B6E58FC497ACF61D80F274E4799602DA4F819E54ADDE2FBFA89FC4EB2172501DDED8DE0FBDDBC5550CC018C73E1FD8152C905DE850862B8D57596025DE1908D8337E95637AF0F52C4A11DA178FF737DCE09471BC0A49DAD7DB39F1BA1B693D3A12F9CA50EF388B50292C73076BF1EEE412A5CFA940E99D4CF07F17FAC87F0D0E2FC8FA3ACDDEEFCCE8AFEC407B94536FCB1E4ACF34773728D189F85EAE4347E0BF868D25C7CE89F8A29B4E6865C68F4F915DFA540549EE9333007145D367FE2852622AAD776F3E5D505A02E5155CC8646A01C1031,
addr: 0x9a48200>
For testing I've used the certificate from developer.apple.com, you can check the public key in the log and compare it.

Resources