SecPKCS12Import leak under arc? - iphone

I was found memory leaks by iOS instruments on all SecPKCS12Import lines of that code under arc:
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trustRef, 0);
CFStringRef certSummary = SecCertificateCopySubjectSummary(certRef);
NSData *data = (__bridge_transfer NSData *) SecCertificateCopyData(certRef);
NSURL *indexURL = [[NSBundle mainBundle] URLForResource:#"cert1" withExtension:#"p12"];
NSData *localP12 = [NSData dataWithContentsOfURL:indexURL];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
NSString *password = ///
[options setObject:password forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef) localP12,(__bridge CFDictionaryRef)options, &items);
if (securityError == noErr) { };/// good } else { //bad }
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
CFArrayRef certificates =
(CFArrayRef)CFDictionaryGetValue(identityDict,kSecImportItemCertChain);
SecCertificateRef localCert = (SecCertificateRef)CFArrayGetValueAtIndex(certificates,0);
CFDataRef dataLocal = SecCertificateCopyData(localCert);
NSData *local = (__bridge NSData *)dataLocal;
//NSLog(#"local:%#",local);
NSURL *indexURLmac3 = [[NSBundle mainBundle] URLForResource:#"cert2" withExtension:#"p12"];
NSData *localP12mac3 = [NSData dataWithContentsOfURL:indexURLmac3];
NSMutableDictionary * optionsMac3 = [[NSMutableDictionary alloc] init];
NSString *passwordMac3 = //
[optionsMac3 setObject:passwordMac3 forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef itemsMac3 = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((__bridge CFDataRef) localP12mac3, (__bridge CFDictionaryRef)optionsMac3, &itemsMac3);
if (securityError == noErr) { };/// good } else { //bad }
CFDictionaryRef identityDictMac3 = CFArrayGetValueAtIndex(itemsMac3, 0);
CFArrayRef certificatesMac3 =
(CFArrayRef)CFDictionaryGetValue(identityDictMac3, kSecImportItemCertChain);
SecCertificateRef localCertMac3 = (SecCertificateRef)CFArrayGetValueAtIndex(certificatesMac3,0);
CFDataRef dataLocalMac3 = SecCertificateCopyData(localCertMac3);
NSData *localMac3 = (__bridge NSData *)dataLocalMac3;
NSURL *indexURLwebcob3 = [[NSBundle mainBundle] URLForResource:#"cert3" withExtension:#"p12"];
NSData *localP12wwebcob3 = [NSData dataWithContentsOfURL:indexURLwebcob3];
NSMutableDictionary * optionsWebcob3 = [[NSMutableDictionary alloc] init];
NSString *passwordWebcob3 = //
[optionsWebcob3 setObject:passwordWebcob3 forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef itemsWebcob3 = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((__bridge CFDataRef) localP12wwebcob3, (__bridge CFDictionaryRef)optionsWebcob3, &itemsWebcob3);
if (securityError == noErr) { };/// good } else { //bad }
CFDictionaryRef identityDictWebcob3 = CFArrayGetValueAtIndex(itemsWebcob3, 0);
CFArrayRef certificatesWebcob3 =
(CFArrayRef)CFDictionaryGetValue(identityDictWebcob3,
kSecImportItemCertChain);
SecCertificateRef localCertWebcob3 = (SecCertificateRef)CFArrayGetValueAtIndex(certificatesWebcob3,0);
CFDataRef dataLocalWebcob3 = SecCertificateCopyData(localCertWebcob3);
NSData *localWebcob3 = (__bridge NSData *)dataLocalWebcob3;
if ([data isEqualToData:local] || [data isEqualToData:localMac3] || [data isEqualToData:localWebcob3]) trust = YES;
CFRelease(certSummary);
CFRelease((CFDataRef) dataLocal);
CFRelease((CFDataRef) dataLocalMac3);
CFRelease((CFDataRef) dataLocalWebcob3);
where i'm wrong?

Wow. That code is really hard to follow. You seem to be doing three different PKCS12 imports, you may want to make that a single method that's called three times. Just saying.
Anyway, without even following your code I know what the issue may be - because I have seen this before. The Security methods you are using follow the CoreFoundation memory management patterns defined here. More than once I have found the PKCS12 identity import process leaking because someone did not realize that, or thought that bridge casts to ARC would just make it work.
But here is what you should look at - in addition to whatever Instruments is trying to tell you, of course:
You need to release the items passed as the last argument to SecPKCS12Import (the CFArrayRef in the documentation). Look at Apple's example for guidance.
I see something even more obvious - you are calling CFArrayCreate without a corresponding release.

Related

Authenticate certificate chaining with server in iOS

I have to authenticate certificate chain with my root certificate against server. I get the server certificate's NSData.
We don't have any authenticationChallenge mechanism and need to verify using the NSData.
How do I achieve this, because SecTrustEvaluate always returns kSecTrustResultRecoverableTrustFailure.
Following is the code I am using: -
NSData *aServerCertificateData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ABC" ofType:#"cer"]]; // This will actually come from the server
NSString *aRootPath = [[NSBundle mainBundle] pathForResource:#"XYZ" ofType:#"pem"];
CFDataRef aRootCertData = (__bridge CFDataRef)[NSData dataWithContentsOfFile:aRootPath];
CFDataRef myCertData = (__bridge CFDataRef)aServerCertificateData;
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecCertificateRef certArray[1] = {myCert};
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(myCerts, myPolicy, &myTrust);
SecCertificateRef rootCertRef[1] = {rootCert};
CFArrayRef rootCerts = CFArrayCreate(NULL, (void *)rootCertRef, 1, NULL);
status = SecTrustSetAnchorCertificates(myTrust, rootCerts);
SecTrustResultType trustResult;
if (status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult);
if (status == noErr) {
if (trustResult == kSecTrustResultRecoverableTrustFailure) {// 2
NSData *exceptions = (__bridge NSData *)(SecTrustCopyExceptions(myTrust)); // This always gets called
NSLog(#"Exceptions: %#", [NSPropertyListSerialization propertyListWithData:exceptions options:kNilOptions format:nil error:nil]);
}
publicKeyRef = SecTrustCopyPublicKey(myTrust);
}
}

OAuth signature with RSA-SHA1 on iOS

I need help creating an RSA-SHA1 signature to be used in a 3-legged OAuth implementation on iOS.
I was able to do this using HMAC-SHA1 using CommonCrypto.h, but this library doesn't seem to support RSA-SHA1.
Have any of you implemented OAuth signatures with RSA? Could you point me to some resources where I can find more information?
Thanks.
The answer by Erik Villegas was also the solution for me. But there is a bug in the posted code which I encountered when using this solution: secretFile was opened with fopen(), so it must be closed with fclose()
- (NSString *)RSASHA1HashForString:(NSString *)source {
NSLog(#"encrypting %#", source);
if (source == nil) return nil;
OpenSSL_add_all_algorithms();
NSString *signature = nil;
// make a SHA-1 digest of the source string
const char* sourceChars = [source UTF8String];
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1((const unsigned char *)sourceChars, strlen(sourceChars), digest);
NSString *path = [[NSBundle mainBundle] pathForResource:#"privatekey" ofType:#"pem"];
const char *pathCString = [path cStringUsingEncoding:NSUTF8StringEncoding];
FILE *secretFile = fopen(pathCString, "r");
RSA *rsa = NULL;
PEM_read_RSAPrivateKey(secretFile, &rsa, NULL, NULL);
if (rsa != NULL) {
unsigned int sigLen = 0;
unsigned char *sigBuff = malloc(RSA_size(rsa));
int result = RSA_sign(NID_sha1, digest, (unsigned int) sizeof(digest),
sigBuff, &sigLen, rsa);
if (result != 0) {
NSData *sigData = [NSData dataWithBytes:sigBuff length:sigLen];
signature = [self base64forData:sigData];
}
free(sigBuff);
RSA_free(rsa);
}
fclose(secretFile);
NSLog(#"generated signature: %#", signature);
return signature;
}
I finally found the solution. Here is a method that will look for a privatekey.pem file in your bundle and create a RSA-SHA1 signature using the string that is passed in. You will need to add the openssl library. You can use this project as a reference: https://github.com/x2on/OpenSSL-for-iPhone
- (NSString *)RSASHA1HashForString:(NSString *)source {
NSLog(#"encrypting %#", source);
if (source == nil) return nil;
OpenSSL_add_all_algorithms();
NSString *signature = nil;
// make a SHA-1 digest of the source string
const char* sourceChars = [source UTF8String];
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1((const unsigned char *)sourceChars, strlen(sourceChars), digest);
NSString *path = [[NSBundle mainBundle] pathForResource:#"privatekey" ofType:#"pem"];
const char *pathCString = [path cStringUsingEncoding:NSUTF8StringEncoding];
FILE *secretFile = fopen(pathCString, "r");
RSA *rsa = NULL;
PEM_read_RSAPrivateKey(secretFile, &rsa, NULL, NULL);
if (rsa != NULL) {
unsigned int sigLen = 0;
unsigned char *sigBuff = malloc(RSA_size(rsa));
int result = RSA_sign(NID_sha1, digest, (unsigned int) sizeof(digest),
sigBuff, &sigLen, rsa);
if (result != 0) {
NSData *sigData = [NSData dataWithBytes:sigBuff length:sigLen];
signature = [self base64forData:sigData];
}
free(sigBuff);
RSA_free(rsa);
}
NSLog(#"generated signature: %#", signature);
return signature;
}
If you are implementing OAuth you'll need to pass the signature base into this method. More info can be found here: http://oauth.net/core/1.0a/#anchor13
Here is implementation using core security iOS framework.
Key should be converted to pkcs12 format.
Algorythm name is "kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1"
- (NSString *)getStringSHA1RSASignature:(NSString *)str
{
NSString *path = [[NSBundle bundleForClass:[self class]]
pathForResource:#"rsaPrivate.pfx" ofType:#"pkcs12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
[options setObject:#" your password " forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef)p12data, (CFDictionaryRef)options, &items);
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
assert(securityError == noErr);
SecKeyRef privateKeyRef;
SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
/// have a key
NSData *input = [str dataUsingEncoding:NSUTF8StringEncoding];
CFErrorRef error = nil;
///////////////////////////////// "SHA1withRSA" java
CFDataRef signature = SecKeyCreateSignature(privateKeyRef, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1, (CFDataRef)input, &error);
NSAssert(signature != nil, #"");
NSString *result = [(__bridge NSData *)signature base64EncodedStringWithOptions:0];
CFRelease(signature);
NSAssert(result != nil, #"");
NSLog(#"generated signature: %#", result);
return result;
}

Get Exif data from UIImage - UIImagePickerController [duplicate]

This question already has answers here:
UIImagePickerController and extracting EXIF data from existing photos
(18 answers)
Closed 7 years ago.
How can we get Exif information from UIImage selected from UIImagePickerController?
I had done much R&D for this and got many replies but still failed to implement this.
I had gone through this this and this link
Please help me to solve this problem.
Thanks in advance..
Interesting question! I came up with the following solution working for images picked from your photo library (note my code is using ARC):
Import AssetsLibrary.framework and ImageIO.framework.
Then include the needed classes inside your .h-file:
#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <ImageIO/CGImageSource.h>
#import <ImageIO/CGImageProperties.h>
And put this inside your imagePickerController:didFinishPickingMediaWithInfo: delegate method:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:[info objectForKey:UIImagePickerControllerReferenceURL]
resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *image_representation = [asset defaultRepresentation];
// create a buffer to hold image data
uint8_t *buffer = (Byte*)malloc(image_representation.size);
NSUInteger length = [image_representation getBytes:buffer fromOffset: 0.0 length:image_representation.size error:nil];
if (length != 0) {
// buffer -> NSData object; free buffer afterwards
NSData *adata = [[NSData alloc] initWithBytesNoCopy:buffer length:image_representation.size freeWhenDone:YES];
// identify image type (jpeg, png, RAW file, ...) using UTI hint
NSDictionary* sourceOptionsDict = [NSDictionary dictionaryWithObjectsAndKeys:(id)[image_representation UTI] ,kCGImageSourceTypeIdentifierHint,nil];
// create CGImageSource with NSData
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef) adata, (__bridge CFDictionaryRef) sourceOptionsDict);
// get imagePropertiesDictionary
CFDictionaryRef imagePropertiesDictionary;
imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(sourceRef,0, NULL);
// get exif data
CFDictionaryRef exif = (CFDictionaryRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyExifDictionary);
NSDictionary *exif_dict = (__bridge NSDictionary*)exif;
NSLog(#"exif_dict: %#",exif_dict);
// save image WITH meta data
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURL *fileURL = nil;
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, imagePropertiesDictionary);
if (![[sourceOptionsDict objectForKey:#"kCGImageSourceTypeIdentifierHint"] isEqualToString:#"public.tiff"])
{
fileURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#.%#",
documentsDirectory,
#"myimage",
[[[sourceOptionsDict objectForKey:#"kCGImageSourceTypeIdentifierHint"] componentsSeparatedByString:#"."] objectAtIndex:1]
]];
CGImageDestinationRef dr = CGImageDestinationCreateWithURL ((__bridge CFURLRef)fileURL,
(__bridge CFStringRef)[sourceOptionsDict objectForKey:#"kCGImageSourceTypeIdentifierHint"],
1,
NULL
);
CGImageDestinationAddImage(dr, imageRef, imagePropertiesDictionary);
CGImageDestinationFinalize(dr);
CFRelease(dr);
}
else
{
NSLog(#"no valid kCGImageSourceTypeIdentifierHint found …");
}
// clean up
CFRelease(imageRef);
CFRelease(imagePropertiesDictionary);
CFRelease(sourceRef);
}
else {
NSLog(#"image_representation buffer length == 0");
}
}
failureBlock:^(NSError *error) {
NSLog(#"couldn't get asset: %#", error);
}
];
One thing I noticed is, that iOS will ask the user to allow location services – if he denies, you won't be abled to get the image data …
EDIT
Added code to save the image including its meta data. It's a quick approach, so maybe there is a better way, but it works!
These answers all seem extremely complex. If the image has been saved to the Camera Roll, and you have the ALAsset (either from UIImagePicker or ALAssetLibrary) you can get the metadata like so:
asset.defaultRepresentation.metadata;
If you want to save that image from camera roll to another location (say in Sandbox/Documents) simply do:
CGImageDestinationRef imageDestinationRef = CGImageDestinationCreateWithURL((__bridge CFURLRef)urlToSaveTo, kUTTypeJPEG, 1, NULL);
CFDictionaryRef imagePropertiesRef = (__bridge CFDictionaryRef)asset.defaultRepresentation.metadata;
CGImageDestinationAddImage(imageDestinationRef, asset.defaultRepresentation.fullResolutionImage, imagePropertiesRef);
if (!CGImageDestinationFinalize(imageDestinationRef)) NSLog(#"Failed to copy photo on save to %#", urlToSaveTo);
CFRelease(imageDestinationRef);
I had found solution and got answer from here
From here We can get GPS info as well..
Amazing and thanks all for helping me to solve this problem.
UPDATE
This is another function that I had created myself, also return Exif data as well as GPS data and in this function we doesn't need any third party library.. but you have to turn on location services for this. and use current latitude and longitude for that. so have to use CoreLocation.framework
//FOR CAMERA IMAGE
-(NSMutableData *)getImageWithMetaData:(UIImage *)pImage
{
NSData* pngData = UIImagePNGRepresentation(pImage);
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
[metadata release];
//For GPS Dictionary
NSMutableDictionary *GPSDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy]autorelease];
if(!GPSDictionary)
GPSDictionary = [NSMutableDictionary dictionary];
[GPSDictionary setValue:[NSNumber numberWithDouble:currentLatitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithDouble:currentLongitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
NSString* ref;
if (currentLatitude <0.0)
ref = #"S";
else
ref =#"N";
[GPSDictionary setValue:ref forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
if (currentLongitude <0.0)
ref = #"W";
else
ref =#"E";
[GPSDictionary setValue:ref forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:location.altitude] forKey:(NSString*)kCGImagePropertyGPSAltitude];
//For EXIF Dictionary
NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
if(!EXIFDictionary)
EXIFDictionary = [NSMutableDictionary dictionary];
[EXIFDictionary setObject:[NSDate date] forKey:(NSString*)kCGImagePropertyExifDateTimeOriginal];
[EXIFDictionary setObject:[NSDate date] forKey:(NSString*)kCGImagePropertyExifDateTimeDigitized];
//add our modified EXIF data back into the image’s metadata
[metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
[metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data, UTI, 1, NULL);
if(!destination)
dest_data = [[pngData mutableCopy] autorelease];
else
{
CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable);
BOOL success = CGImageDestinationFinalize(destination);
if(!success)
dest_data = [[pngData mutableCopy] autorelease];
}
if(destination)
CFRelease(destination);
CFRelease(source);
return dest_data;
}
//FOR PHOTO LIBRARY IMAGE
-(NSMutableData *)getImagedataPhotoLibrary:(NSDictionary *)pImgDictionary andImage:(UIImage *)pImage
{
NSData* data = UIImagePNGRepresentation(pImage);
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
NSMutableDictionary *metadataAsMutable = [[pImgDictionary mutableCopy]autorelease];
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *dest_data = [NSMutableData data];
//For Mutabledata
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data, UTI, 1, NULL);
if(!destination)
dest_data = [[data mutableCopy] autorelease];
else
{
CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable);
BOOL success = CGImageDestinationFinalize(destination);
if(!success)
dest_data = [[data mutableCopy] autorelease];
}
if(destination)
CFRelease(destination);
CFRelease(source);
return dest_data;
}
and We will retrieve that data like this
//FOR CAMERA IMAGE
NSData *originalImgData = [self getImageWithMetaData:imgOriginal];
//FOR PHOTO LIBRARY IMAGE
[self getImagedataPhotoLibrary:[[myasset defaultRepresentation] metadata] andImage:imgOriginal];
For all of this you should have to Import AssetsLibrary.framework and ImageIO.framework.
I have used this method for getting the exifdata dictionary from image , I hope this will also work for you
-(void)getExifDataFromImage:(UIImage *)currentImage
{
NSData* pngData = UIImageJPEGRepresentation(currentImage, 1.0);
CGImageSourceRef mySourceRef = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);
//CGImageSourceRef mySourceRef = CGImageSourceCreateWithURL((__bridge CFURLRef)myURL, NULL);
if (mySourceRef != NULL)
{
NSDictionary *myMetadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(mySourceRef,0,NULL);
NSDictionary *exifDic = [myMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary];
NSDictionary *tiffDic = [myMetadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
NSLog(#"exifDic properties: %#", myMetadata); //all data
float rawShutterSpeed = [[exifDic objectForKey:(NSString *)kCGImagePropertyExifExposureTime] floatValue];
int decShutterSpeed = (1 / rawShutterSpeed);
NSLog(#"Camera %#",[tiffDic objectForKey:(NSString *)kCGImagePropertyTIFFModel]);
NSLog(#"Focal Length %#mm",[exifDic objectForKey:(NSString *)kCGImagePropertyExifFocalLength]);
NSLog(#"Shutter Speed %#", [NSString stringWithFormat:#"1/%d", decShutterSpeed]);
NSLog(#"Aperture f/%#",[exifDic objectForKey:(NSString *)kCGImagePropertyExifFNumber]);
NSNumber *ExifISOSpeed = [[exifDic objectForKey:(NSString*)kCGImagePropertyExifISOSpeedRatings] objectAtIndex:0];
NSLog(#"ISO %ld",[ExifISOSpeed integerValue]);
NSLog(#"Taken %#",[exifDic objectForKey:(NSString*)kCGImagePropertyExifDateTimeDigitized]);
}
}
You need ALAssetsLibrary to actually retrieve the EXIF info from an image. The EXIF is added to an image only when it is saved to the Photo Library. Even if you use ALAssetLibrary to get an image asset from the library, it will lose all EXIF info if you set it to a UIImage.
I have tried to insert GPS coordinates into image metadata picked by iPad Camera as it was suggested by Mehul.
It Works, Thank you for your post.
P.S.
Who intends to use that code, just substitude the two geolocations at the top of the function -(NSMutableData *)getImageWithMetaData:(UIImage *)pImage {
double currentLatitude = [locationManager location].coordinate.latitude;
double currentLongitude = [locationManager location].coordinate.longitude;
...
By supposing that you have already initializied somewhere locationManager in your code, like this:
locationManager = [[CLLocationManager alloc] init];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[locationManager setDelegate:self]; // Not necessary in this case
[locationManager startUpdatingLocation]; // Not neccessary in this case
and by importing CoreLocation/CoreLocation.h and ImageIO/ImageIO.h headers with associated frameworks.

How to write exif metadata to an image (not the camera roll, just a UIImage or JPEG)

I am aware of how to save metadata using ALAssets. But, I want to save an image, or upload it somewhere, with exif intact. I have exif data as an NSDictionary. But how can I inject it properly into a UIImage (or probably an NSData JPEG representation)?
I am using UIImagePickerController to get the image from the camera and my flow is a bit different than the one described by Chiquis. Here it is:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *image = info[#"UIImagePickerControllerOriginalImage"];
NSString *fullPhotoFilename = ...; // generate the photo name and path here
NSData *photoData = [UIImage taggedImageData:image.jpegData metadata:info[#"UIImagePickerControllerMediaMetadata"] orientation:image.imageOrientation];
[photoData writeToFile:fullPhotoFilename atomically:YES];
}
And using a UIImage category to put combine the image data with its metadata:
#import <ImageIO/ImageIO.h>
#import "UIImage+Tagging.h"
#import "LocationHelper.h"
#implementation UIImage (Tagging)
+ (NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSMutableDictionary *)metadata {
// create an imagesourceref
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
// this is the type of image (e.g., public.jpeg)
CFStringRef UTI = CGImageSourceGetType(source);
// create a new data object and write the new image into it
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
if (!destination) {
NSLog(#"Error: Could not create image destination");
}
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadata);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(#"Error: Could not create data from image destination");
}
CFRelease(destination);
CFRelease(source);
return dest_data;
}
+ (NSData *)taggedImageData:(NSData *)imageData metadata:(NSDictionary *)metadata orientation:(UIImageOrientation)orientation {
CLLocationManager *locationManager = [CLLocationManager new];
CLLocation *location = [locationManager location];
NSMutableDictionary *newMetadata = [NSMutableDictionary dictionaryWithDictionary:metadata];
if (!newMetadata[(NSString *)kCGImagePropertyGPSDictionary] && location) {
newMetadata[(NSString *)kCGImagePropertyGPSDictionary] = [LocationHelper gpsDictionaryForLocation:location];
}
// Reference: http://sylvana.net/jpegcrop/exif_orientation.html
int newOrientation;
switch (orientation) {
case UIImageOrientationUp:
newOrientation = 1;
break;
case UIImageOrientationDown:
newOrientation = 3;
break;
case UIImageOrientationLeft:
newOrientation = 8;
break;
case UIImageOrientationRight:
newOrientation = 6;
break;
case UIImageOrientationUpMirrored:
newOrientation = 2;
break;
case UIImageOrientationDownMirrored:
newOrientation = 4;
break;
case UIImageOrientationLeftMirrored:
newOrientation = 5;
break;
case UIImageOrientationRightMirrored:
newOrientation = 7;
break;
default:
newOrientation = -1;
}
if (newOrientation != -1) {
newMetadata[(NSString *)kCGImagePropertyOrientation] = #(newOrientation);
}
NSData *newImageData = [self writeMetadataIntoImageData:imageData metadata:newMetadata];
return newImageData;
}
And finally, here is the method I am using to generate the needed GPS dictionary:
+ (NSDictionary *)gpsDictionaryForLocation:(CLLocation *)location {
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:#"HH:mm:ss.SS"];
NSDictionary *gpsDict = #{(NSString *)kCGImagePropertyGPSLatitude: #(fabs(location.coordinate.latitude)),
(NSString *)kCGImagePropertyGPSLatitudeRef: ((location.coordinate.latitude >= 0) ? #"N" : #"S"),
(NSString *)kCGImagePropertyGPSLongitude: #(fabs(location.coordinate.longitude)),
(NSString *)kCGImagePropertyGPSLongitudeRef: ((location.coordinate.longitude >= 0) ? #"E" : #"W"),
(NSString *)kCGImagePropertyGPSTimeStamp: [formatter stringFromDate:[location timestamp]],
(NSString *)kCGImagePropertyGPSAltitude: #(fabs(location.altitude)),
};
return gpsDict;
}
Hope it helps someone. Thanks to Gustavo Ambrozio, Chiquis and several others SO members I was able to piece it together and use it in my project.
UIImage does not contain metadata information (it is stripped). So if you want to save it without using the imagepicker method (not in camera roll):
Follow the answer here to write to a file with the metadata intact:
Problem setting exif data for an image
no idea why would this be downvoted but here is the method:
In this case im getting the image through AVFoundation and this is what goes in the
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
// code here
}
block code:
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
// Create formatted date
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:#"HH:mm:ss.SS"];
// Create GPS Dictionary
NSDictionary *gpsDict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:fabs(loc.coordinate.latitude)], kCGImagePropertyGPSLatitude
, ((loc.coordinate.latitude >= 0) ? #"N" : #"S"), kCGImagePropertyGPSLatitudeRef
, [NSNumber numberWithFloat:fabs(loc.coordinate.longitude)], kCGImagePropertyGPSLongitude
, ((loc.coordinate.longitude >= 0) ? #"E" : #"W"), kCGImagePropertyGPSLongitudeRef
, [formatter stringFromDate:[loc timestamp]], kCGImagePropertyGPSTimeStamp
, [NSNumber numberWithFloat:fabs(loc.altitude)], kCGImagePropertyGPSAltitude
, nil];
// The gps info goes into the gps metadata part
CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, (__bridge void *)gpsDict);
// Here just as an example im adding the attitude matrix in the exif comment metadata
CMRotationMatrix m = att.rotationMatrix;
GLKMatrix4 attMat = GLKMatrix4Make(m.m11, m.m12, m.m13, 0, m.m21, m.m22, m.m23, 0, m.m31, m.m32, m.m33, 0, 0, 0, 0, 1);
NSMutableDictionary *EXIFDictionary = (__bridge NSMutableDictionary*)CFDictionaryGetValue(mutable, kCGImagePropertyExifDictionary);
[EXIFDictionary setValue:NSStringFromGLKMatrix4(attMat) forKey:(NSString *)kCGImagePropertyExifUserComment];
CFDictionarySetValue(mutable, kCGImagePropertyExifDictionary, (__bridge void *)EXIFDictionary);
NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer] ;
After this code you will have your image in the jpeg nsdata and the correspoding dictionary for that image in the mutable cfdictionary.
All you have to do now is:
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL);
CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data,UTI,1,NULL);
if(!destination) {
NSLog(#"***Could not create image destination ***");
}
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) mutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = CGImageDestinationFinalize(destination);
if(!success) {
NSLog(#"***Could not create data from image destination ***");
}
//now we have the data ready to go, so do whatever you want with it
//here we just write it to disk at the same path we were passed
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"ImagesFolder"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder
// NSString *imageName = #"ImageName";
NSString *fullPath = [dataPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.jpg", name]]; //add our image to the path
[dest_data writeToFile:fullPath atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
Note how I'm not saving using the ALAssets but directly into a folder of my choice.
Btw most of this code can be found in the link I posted at first.
There is easier way. If you need to save some exif, you can use SimpleExif pod
First create a ExifContainer:
ExifContainer *container = [[ExifContainer alloc] init];
and populate it with all requred data:
[container addUserComment:#"A long time ago, in a galaxy far, far away"];
[container addCreationDate:[NSDate dateWithTimeIntervalSinceNow:-10000000]];
[container addLocation:locations[0]];
Then you can add this data to image:
NSData *imageData = [[UIImage imageNamed:#"DemoImage"] addExif:container];
Then you just save this data as a JPEG
I faced the same problem, now I can upload files with EXIF data, also you can compress photo if need it, this solved the issue for me:
// Get your image.
UIImage *loImgPhoto = [self getImageFromAsset:loPHAsset];
// Get your metadata (includes the EXIF data).
CGImageSourceRef loImageOriginalSource = CGImageSourceCreateWithData(( CFDataRef) loDataFotoOriginal, NULL);
NSDictionary *loDicMetadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(loImageOriginalSource, 0, NULL);
// Set your compression quality (0.0 to 1.0).
NSMutableDictionary *loDicMutableMetadata = [loDicMetadata mutableCopy];
[loDicMutableMetadata setObject:#(lfCompressionQualityValue) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];
// Create an image destination.
NSMutableData *loNewImageDataWithExif = [NSMutableData data];
CGImageDestinationRef loImgDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)loNewImageDataWithExif, CGImageSourceGetType(loImageOriginalSource), 1, NULL);
// Add your image to the destination.
CGImageDestinationAddImage(loImgDestination, loImgPhoto.CGImage, (__bridge CFDictionaryRef) loDicMutableMetadata);
// Finalize the destination.
if (CGImageDestinationFinalize(loImgDestination))
{
NSLog(#"Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
//Upload photo with EXIF metadata
[self myUploadMethod:loNewImageDataWithExif];
}
else
{
NSLog(#"Error -> failed to finalize the image.");
}
CFRelease(loImageOriginalSource);
CFRelease(loImgDestination);
getImageFromAsset method:
-(UIImage *)getImageFromAsset:(PHAsset *)aPHAsset
{
__block UIImage *limgImageResult;
PHImageRequestOptions *lPHImageRequestOptions = [PHImageRequestOptions new];
lPHImageRequestOptions.synchronous = YES;
[self.imageManager requestImageForAsset:aPHAsset
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeDefault//PHImageContentModeAspectFit
options:lPHImageRequestOptions
resultHandler:^(UIImage *limgImage, NSDictionary *info) {
limgImageResult = limgImage;
}];
return limgImageResult;
}
Here's the basics of setting Make and Model metadata on a .jpg file in Swift 3 https://gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7 The higher level versions, like using ExifContainer pod, did not work for me.

Why does Keychain Services return the wrong keychain content?

I've been trying to use persistent keychain references in an iPhone application. I found that if I created two different keychain items, I would get a different persistent reference each time (they look like 'genp.......1', 'genp.......2', …). However, attempts to look up the items by persistent reference always returned the content of the first item. Why should this be? I confirmed that my keychain-saving code was definitely creating new items in each case (rather than updating existing items), and was not getting any errors. And as I say, Keychain Services is giving a different persistent reference for each item.
I've managed to solve my immediate problem by searching for keychain items by attribute rather than persistent references, but it would be easier to use persistent references so I'd appreciate solving this problem.
Here's my code:
- (NSString *)keychainItemWithName: (NSString *)name {
NSString *path = [GLApplicationSupportFolder()
stringByAppendingPathComponent: name];
NSData *persistentRef = [NSData dataWithContentsOfFile: path];
if (!persistentRef) {
NSLog(#"no persistent reference for name: %#", name);
return nil;
}
NSArray *refs = [NSArray arrayWithObject: persistentRef];
//get the data
CFMutableDictionaryRef params = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(params, kSecMatchItemList, refs);
CFDictionaryAddValue(params, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(params, kSecReturnData, kCFBooleanTrue);
CFDataRef item = NULL;
OSStatus result = SecItemCopyMatching(params, (CFTypeRef *)&item);
CFRelease(params);
if (result != errSecSuccess) {
NSLog(#"error %d retrieving keychain reference for name: %#", result, name);
return nil;
}
NSString *token = [[NSString alloc] initWithData: (NSData *)item
encoding: NSUTF8StringEncoding];
CFRelease(item);
return [token autorelease];
}
- (void)setKeychainItem: (NSString *)newToken forName: (NSString *)name {
NSData *tokenData = [newToken dataUsingEncoding: NSUTF8StringEncoding];
//firstly, find out whether the item already exists
NSDictionary *searchAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
name, kSecAttrAccount,
kCFBooleanTrue, kSecReturnAttributes,
nil];
NSDictionary *foundAttrs = nil;
OSStatus searchResult = SecItemCopyMatching((CFDictionaryRef)searchAttributes,
(CFTypeRef *)&foundAttrs);
if (noErr == searchResult) {
NSMutableDictionary *toStore = [foundAttrs mutableCopy];
[toStore setObject: tokenData forKey: (id)kSecValueData];
OSStatus result = SecItemUpdate((CFDictionaryRef)foundAttrs,
(CFDictionaryRef)toStore);
if (result != errSecSuccess) {
NSLog(#"error %d updating keychain", result);
}
[toStore release];
return;
}
//need to create the item.
CFMutableDictionaryRef params = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(params, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(params, kSecAttrAccount, name);
CFDictionaryAddValue(params, kSecReturnPersistentRef, kCFBooleanTrue);
CFDictionaryAddValue(params, kSecValueData, tokenData);
NSData *persistentRef = nil;
OSStatus result = SecItemAdd(params, (CFTypeRef *)&persistentRef);
CFRelease(params);
if (result != errSecSuccess) {
NSLog(#"error %d from keychain services", result);
return;
}
NSString *path = [GLApplicationSupportFolder()
stringByAppendingPathComponent: name];
[persistentRef writeToFile: path atomically: NO];
[persistentRef release];
}
Turns out that using the kSecMatchItemList doesn't appear to work at all.
I did mine like this:
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword, kSecClass,
persistentRef, (id)kSecValuePersistentRef,
(id)kCFBooleanTrue, kSecReturnAttributes,
(id)kCFBooleanTrue, kSecReturnData,
nil];
NSDictionary *result = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query,
(CFTypeRef*)&result);
which returned the attributes and data for the persistent reference. The documentation in the header about converting a "persistent reference" into a "standard reference" makes no sense at all. Hope this helps.