How to add external public key to iOS keychain

We are developing an i-phone app in Swift 5 that needs a SSH connection to a remote Windows server. Every night, data collected in SQLite database should be pushed to the Windows server. The approved authentication method is to use SSH server URL in port 22 and my user ID.


With a new firewall rule, I am able to connect using URL, port 22 and my user name, but I am not authenticated. From this point on, I need public keys exchanged and added at both ends. Obviouly, data encrption will need to be done using public keys and dedcryption with private keys.


I need to accomplish two tasks:


1. Create a keypair in iOS and send the public key to Windows Admin to add my user ID.

2. Add a public key sent by server admin to iOS keychain in my app for later use in data encryption


1. Creation of keypair in Swift 5


I used SecKeyGeneratePair to create key pair.


Code:


func createKeys1() {

var statusCode: OSStatus?

var publicKey: SecKey?

var privateKey: SecKey?

var error: Unmanaged<CFError>?


let publicKeyAttr: [NSObject: NSObject] = [

kSecAttrIsPermanent:true as NSObject,

kSecAttrApplicationTag:"com.mycompany.mailtracking1.public".data(using: String.Encoding.utf8)! as NSObject,

kSecClass: kSecClassKey,

kSecReturnData: kCFBooleanTrue]

let privateKeyAttr: [NSObject: NSObject] = [

kSecAttrIsPermanent:true as NSObject,

kSecAttrApplicationTag:"com.mycompany.mailtracking1.private".data(using: String.Encoding.utf8)! as NSObject,

kSecClass: kSecClassKey,

kSecReturnData: kCFBooleanTrue]


var keyPairAttr = [NSObject: NSObject]()

keyPairAttr[kSecAttrKeyType] = kSecAttrKeyTypeRSA

keyPairAttr[kSecAttrKeySizeInBits] = 2048 as NSObject

keyPairAttr[kSecPublicKeyAttrs] = publicKeyAttr as NSObject

keyPairAttr[kSecPrivateKeyAttrs] = privateKeyAttr as NSObject


statusCode = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)


if statusCode == noErr && publicKey != nil && privateKey != nil {

print("Key pair generated OK")

}


// I read that the key should be converted to base64 encoded format for external use, so the code below was added


// Generate exportable public key

let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, &error)

// The above public key was sent to Windows server admin but he couldn't load it in server against my user name. He reported server locks and errors with multiple tries.


2. Add key sent by server admin to iOS keychain


var pKeyContents: String = ""

var publicKey: SecKey?

var privateKey: SecKey?

var privateKeyAsData: Data?

var stringFromData: String = ""


func addKeys1() {

// convert private key to PKCS#8 format

// This command worked in terminal

// openssl pkcs8 -topk8 -in yixtest.goldlnk.rootlnka.net.key -out yixtest.goldlnk.rootlnka.net.keypkcs8.key

// It will prompt to enter PW twice

// -nocrypt was removed since it kept giving errors

// Refer stackoverflow search secitemadd-keep-return-50-error-in-swift

// secItemAdd keep return -50 error in swift

// The code below is to add a test key (a private key)

// I removed "BEGIN.....KEY" and "END ... KEY" manually to file "PkeyWOHeader" and added it to project bundle

if let path = Bundle.main.path(forResource: "PKeyWOHeader", ofType: "txt")

{

do {

let contents = try String(contentsOfFile: path)

print("Printing Contents -- \(contents)")

pKeyContents = contents

}

catch {

print("Contents could not be loaded")

return

}

} else {

print("File not found")

return

}

// Now add the key to keychain

print("Start adding key")

let key: Data = pKeyContents.data(using: .utf8)!

let tag: Data = "com.mycompany.mailtracking2.private".data(using: .utf8)!

let addQuery: [String: Any] = [kSecClass as String: kSecClassKey,

kSecAttrApplicationTag as String: tag,

kSecValueData as String: key]

var result: CFTypeRef? = nil

let status = SecItemAdd(addQuery as CFDictionary, &result)

if status == errSecSuccess {

print("Key successfully added")

} else {

if let error: String = SecCopyErrorMessageString(status, nil) as String? {

print(error)

print("Error adding key")

}


return

}

}

I have been trying secItemAdd to add the Windows server key without success. secItemAdd completes without errors, but returns secKey as Nil. It appears, from several research in internet, that the iOS keychain has very unique requirements and is different from several other applications that use OpenSSL to do these kinds of things in minutes.


Site below suggests that keys generated in iOS is not in ASCII and needs a base64 decoder. In addition uses PKCS# 1 but is missing ASN1 preambles. So, accepting an external key would mean removing these preambles. Being new to iOS in general and cryptography in particular, I am not sure what this meant. I just added the Objective C code in Swift with a header bridge, etc. I still get a nil value for SecKey output


http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/


The site below suggested using a GITHUB utility called CryptoExportImportManager(), but that also didn't help

https://digitalleaves.com/blog/2015/10/sharing-public-keys-between-ios-and-the-rest-of-the-world/


If I were in Apple support, I would have ignored these third party solutions as "not supported". I get that. But, I am trying find a way to fix above two issues.


Any help is appreciated


Dharma

Replies

After weeks of trying both objectives mentioned above, I could never retrieve a key I uploaded to keychain (always got a Nil value). The best I could accomplish was to load an outside generated key to keychain. Similarly, the key I generated from iOS keychain could never be added to our remote Windows server due to repeated server locks and error messages reported by Windows server admin. Not sure if these keys should be "treated" in any way before handing it to Windows admin. One can only spend so much time trying these things (50 + hours already) in XCODE, which is typically done in minutes in command prompt by the rest of the world in other operating systems. They are many youtube videos and Google article for these things. The biggest challenge was to try and fail with multiple combinations of dictionary query parameters hoping to get my key back for use.


Finally I was able to leverage NMSSH (GIT) and use its method (in-memory public key, private key, PW option) to get authenticated to my user name in SSH server. The important point here is that we should be setting public key parameter as nil and allow the code to generate public key using private key and PW parameters.


I would still like to hear from anyone that succeeded in in retrieving an externally generated key stored in keychain. I will be glad to provide any log file or other means to diagnose the issue.