6

I have been stuck since two days translating a piece of code from Objective-C to Swift:

CFArrayRef keyref = NULL;
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0);
SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict,      kSecImportItemIdentity);

Yes, that's all! I simply can't satisfy the compiler with those pointers. Please help :-)

This is the whole objective c code to translate:

// Read .p12 file
NSString *path = [[NSBundle mainBundle] pathForResource:@"SSLKeyStoreClient" ofType:@"p12"];
NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:path];

// Import .p12 data
CFArrayRef keyref = NULL;
OSStatus sanityChesk = SecPKCS12Import((__bridge CFDataRef)pkcs12data,
                                       (__bridge CFDictionaryRef)[NSDictionary
                                                                  dictionaryWithObject:@"wed-zzz"
                                                                  forKey:(__bridge id)kSecImportExportPassphrase],
                                       &keyref);
if (sanityChesk != noErr) {
    NSLog(@"Error while importing pkcs12 [%d]", (int)sanityChesk);
} else
    NSLog(@"Success opening p12 certificate.");

// Identity
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0);
SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                                                  kSecImportItemIdentity);

// Cert
SecCertificateRef cert = NULL;
OSStatus status = SecIdentityCopyCertificate(identityRef, &cert);
if (status)
    NSLog(@"SecIdentityCopyCertificate failed.");

// the certificates array, containing the identity then the root certificate
NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)cert, nil];

NSMutableDictionary *SSLOptions;
[SSLOptions setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];

NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
                          [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
                          kCFNull,kCFStreamSSLPeerName,
                          myCerts,kCFStreamSSLCertificates,
                          nil];


CFReadStreamSetProperty((CFReadStreamRef)self.inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty((CFWriteStreamRef)self.outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);

And this is what i got so far:

// Read .p12 file
var path = NSBundle.mainBundle().pathForResource("SSLKeyStoreClient", ofType: "p12")
var pkcs12data: NSData = NSData.dataWithContentsOfFile(path, options: nil, error: nil)

// Import .p12 data
var keyref: Unmanaged<CFArray>?


var optionDict: NSMutableDictionary = NSMutableDictionary()
optionDict.setValue("wed-zzz", forKey: kSecImportExportPassphrase!.takeRetainedValue())

var sanityChesk = SecPKCS12Import(pkcs12data,optionDict,&keyref)
if sanityChesk != 0{ //noErr
   println("Error while importing pkcs12 \(sanityChesk)")
} else {
   println("Success opening p12 certificate.")
}


// Identity
var key = keyref!
var identityDict: CFDictionary = CFArrayGetValueAtIndex(keyref, 0)
var identityRef:COpaquePointer = CFDictionaryGetValue(identityDict,nil)


// Cert
var cert: Unmanaged<SecCertificate>?
var status: OSStatus  = SecIdentityCopyCertificate(identityRef, &cert)
if status == 0{
    println("SecIdentityCopyCertificate failed.")
}

// the certificates array, containing the identity then the root certificate
var sslOptions = Dictionary<NSObject, NSObject>()

sslOptions[kCFStreamSSLAllowsExpiredRoots] = NSNumber.numberWithBool(true)

var settings = Dictionary<NSObject, NSObject>()
settings[kCFStreamSSLAllowsExpiredCertificates] = NSNumber.numberWithBool(true)
settings[kCFStreamSSLAllowsAnyRoot] = NSNumber.numberWithBool(true)
settings[kCFStreamSSLValidatesCertificateChain] = NSNumber.numberWithBool(false)
settings[kCFStreamSSLPeerName] = kCFNull
//settings[kCFStreamSSLCertificates] = myCerts



    CFReadStreamSetProperty(self.inputStream, kCFStreamPropertySSLSettings, settings)
    CFReadStreamSetProperty(self.inputStream, kCFStreamPropertySSLSettings, settings)

The Problem starts at:

var identityDict: CFDictionary = CFArrayGetValueAtIndex(keyref, 0)

Error: "unmanaged CFArray not convertible to CFArray".

This is my best try so far.

Vincenzo
  • 1,158
  • 11
  • 18
  • 1
    No `[ ]` means it's not Objective-C. ;) And please post the code that you have tried. (Or at least some of the stuff you've tried in those two days) – Matthias Bauch Jun 08 '14 at 21:42
  • 2
    What is the real value of `keyref`? Obviously it's not actually NULL. – Abhi Beckert Jun 08 '14 at 21:44
  • this is C code, no ObjC code – Bryan Chen Jun 08 '14 at 21:51
  • @user3211074 Any update on this? Do you have a working code sample to share? – Adrian Nov 10 '14 at 16:32
  • I do have a related question. I'm using a similar code fragment for my SSL Connection mit Certificate Pinning. Unfortunately the .p12 File requires my private key in order to have at least one element in the 'keyref' array. Without the private key in the .p12 Import works fine, but no objects are in the 'keyref'. As the Keyfile is part of the app and distributed to the store, I don't want to have the private key included. How can I handle this issue? – geri-m Jan 05 '15 at 19:33

1 Answers1

4

Presumably the first line is a placeholder for an actual array? If you're actually working with a NULL array pointer, the rest of your code does nothing.

Assuming you're starting from a real CFArrayRef, you can take advantage of bridging: CoreFoundation types are automatically treated like Swift objects, so you don't need to work with CFArrayRef and CFDictionaryRef pointers. The same goes for any other C API that uses the CF type system, so it should also apply to SecIdentity.

There seems to be some weirdness with automatic bridging of CF collections — you can implicitly bridge a CFArray to an NSArray and an NSArray to a Swift Array<T>, you can't just subscript a CFArray.

So your conversion looks something like this (wrapped in a function that handles your assumed array):

func getIdentity(keychainArray: NSArray) -> SecIdentity? {
    let dict = keychainArray[0] as Dictionary<String,AnyObject>
    let key = kSecImportItemIdentity.takeRetainedValue()
    return dict[key] as SecIdentity?
}

If you have a CFArray you can pass it to this function and it'll automatically bridge/cast to NSArray, which then automatically casts to a Swift array for subscripting. Treat item 0 as a Swift dictionary, and you can subscript the dictionary to get the identity out. For the key, you'll need to pull it out of an Unmanaged<CFString> because the Security framework isn't set up for implicit bridging of that constant's declaration.

I've left this function returning an optional, since I don't know wether the array+dictionary you're passing in actually contains an identity. If you're sure it does, you could take out the two question marks.

(This compiles in playground, but I don't have an array containing a dictionary containing an identity handy for testing with, so caveat emptor.)

rickster
  • 124,678
  • 26
  • 272
  • 326
  • Thank you, i have used your hints with the typecasting. But now i am at another problem (where i somehow already was yesterday) Compiler Error: Undefined symbols for architecture i386: "_OBJC_CLASS_$_SecIdentity", referenced from: __TFC9messenger10Connection11getIdentityfS0_FCSo7NSArrayGSqCSo11SecIdentity_ in Connection.o ld: symbol(s) not found for architecture i386 clang: error: linker command failed with exit code 1 (use -v to see invocation) – Vincenzo Jun 08 '14 at 22:28
  • It seems to compile in playground, but not in a empty project. Do you have any idea? – Vincenzo Jun 08 '14 at 23:07
  • I guess it must be a beta problem, can't explain myself – Vincenzo Jun 08 '14 at 23:21
  • Your code does not compile in a normal project. Still having the above mentioned error. Any idea? – Vincenzo Jun 09 '14 at 14:24
  • new question open:http://stackoverflow.com/questions/24122645/swift-undefined-symbols-for-architecture-i386-objc-class-secidentity – Vincenzo Jun 09 '14 at 14:40
  • Did you `import Security`? – rickster Jun 09 '14 at 16:34
  • yes, i did also link the security framework. No success, another day passed away for this stupid little problem. – Vincenzo Jun 09 '14 at 17:05
  • Might be a bug with whether/how `Security.framework` is set up for implicit bridging. [Have you filed one?](http://bugreport.apple.com) – rickster Jun 09 '14 at 17:17
  • I have created a bug report. I will keep you informed. – Vincenzo Jun 09 '14 at 18:00
  • 1
    i have modified your code and am now able to compile, however it ends in a runtime error. Maybe you could help me with that: `let dict = keychainArray[0] as Dictionary let key:NSString = kSecImportItemIdentity.takeRetainedValue() return dict[key]!` This ends in: "fatal error: value failed to bridge from Objective-C type to a Swift type" It seems that SecIdentity is a Swift type?! but where would the Objective C type be? – Vincenzo Jun 10 '14 at 13:37