I am using NSStream/CFStream Sockets to connect to my server and now want to use SSL encryption.
I have a self-signed p12 file but absolutely now idea how I can now use this with my stream.
I imported the p12 file in XCode, read it, converted it to NSData and read the certificate with this code
let path = NSBundle.mainBundle().pathForResource("certificate", ofType: "p12")
let certData = NSData(contentsOfFile: path!)
let passDictionary:NSMutableDictionary = NSMutableDictionary()
passDictionary.setValue("passphrase", forKey: kSecImportExportPassphrase as String)
var items: CFArray?
let error = SecPKCS12Import(certData!, passDictionary, &items)
But I don't know what to do with this now.
I tried using it with my streams like this
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey:NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
inputStream!.setProperty(items, forKey: kCFStreamSSLCertificates as String)
outputStream!.setProperty(items, forKey: kCFStreamSSLCertificates as String)
But then I get ErrorCode 9807 when trying to connect. How can I correctly extract the certificates and tell my App to trust them?
UPDATE:
I changed above code to this:
let path = NSBundle.mainBundle().pathForResource("CERTNAME", ofType: "p12")
let certData = NSData(contentsOfFile: path!)
let passDictionary:NSMutableDictionary = NSMutableDictionary()
passDictionary.setValue("meetsapp", forKey: kSecImportExportPassphrase as String)
var items: CFArray?
let error = SecPKCS12Import(certData!, passDictionary, &items)
let unwrappedItems = items! as [AnyObject]
let certDict = unwrappedItems[0] as! [String:AnyObject]
var certs = [certDict["identity"]!]
for c in certDict["chain"]! as! [AnyObject]
{
certs.append(c as! SecCertificateRef)
}
items = certs
If I print "items" it looks like this:
[<SecIdentityRef: 0x7faa834d9930>, <cert(0x7faa834d4860) s: CERTNAME i: CA-NAME>, <cert(0x7faa8585ea00) s: CA-NAME i: CA-NAME>]
Which is the required format for the property kCFStreamSSLCertificates if I am not wrong but I still get the same error code. I am assuming my app has trust issues, but how can I resolve them?
Related
I have a certificate "cert.p12" that I use to sign some data to send it to s SAS server
I read the certificate this way
let data = try Data.init(contentsOf: _certURL)
let password = "somepassword"
let options = [ kSecImportExportPassphrase as String: password ]
var rawItems: CFArray?
let status = SecPKCS12Import(data as CFData,
options as CFDictionary,
&rawItems)
guard status == errSecSuccess else {
print("Error[36]")
return
}
let items = rawItems! as! Array<Dictionary<String, Any>>
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
let trust = firstItem[kSecImportItemTrust as String] as! SecTrust
let publicKey = SecTrustCopyKey(trust)
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, nil)
let publicKeyString = (publicKeyData! as Data).base64EncodedString()
I get the public key like this
"MInsdfknsiufiwefwef ...... etc"
While in android I get
"[Ghhsdfwe89fwenfnwekfjwef]MInsdfknsiufiwefwef ......etc"
So the key is same except for the first like 40 bytes or so
See the image below where I use text comparison tool
The key at the bottom is the correct one accepted by the SAS server
What could be going wrong or missing in iOS
I am trying to write to a plist in xcode. What I've written works, except the plist file doesn't change. I've tried a few different implementations of this, and reaching the same result.
Here is the code:
func saveGameData() {
let BedroomFloorKey = "BedroomFloor"
let BedroomWallKey = "BedroomWall"
var bedroomFloorID: AnyObject = 101 as AnyObject
var bedroomWallID: AnyObject = 101 as AnyObject
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDirectory = paths.object(at: 0) as! NSString
let path = documentsDirectory.appendingPathComponent("room.plist")
print("PATH", path)
let dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(bedroomFloorID, forKey: BedroomFloorKey as NSCopying)
dict.setObject(bedroomWallID, forKey: BedroomWallKey as NSCopying)
//...
//writing to GameData.plist
dict.write(toFile: path, atomically: false)
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("Saved GameData.plist file is --> \(resultDictionary?.description ?? "")")
}
My plist is in the main directory of my xcode project, same folder as were the ViewController is.
Thanks
You cannot write into the application bundle, for obvious reasons the bundle is read-only.
Your code writes the plist into the Documents directory in the container of the application. If you have a default Property List file in the application bundle copy it on the first launch of the app into the Documents directory.
However the code looks like a ugly literal translation of Objective-C code. This is a native Swift version
func saveGameData() throws {
let bedroomFloorKey = "BedroomFloor"
let bedroomWallKey = "BedroomWall"
let bedroomFloorID = 101
let bedroomWallID = 101
let documentsDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let url = documentsDirectory.appendingPathComponent("room.plist")
print("PATH", url)
var dict : [String:Any] = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict[bedroomFloorKey] = bedroomFloorID
dict[bedroomWallKey] = bedroomWallID
//...
//writing to GameData.plist
let data = try PropertyListSerialization.data(fromPropertyList: dict, format: .xml, options: 0)
try data.write(to: url)
print("Saved GameData.plist file is --> \(dict)")
}
There is no need to reread the data. If no error is thrown the plist has been written successfully.
You create a plist file inside documents
let path = documentsDirectory.appendingPathComponent("room.plist")
which isn't same as the file located in your main bundle ( same level as ViewController.swift ) which won't accept any write as bundle is signed with the app and can't accept any change
I have a use case where the user gives a PKCS#12 file and its password as input, and I need both the certificate's and the private key's PEM strings.
I have managed to create a SecIdentity using the p12, and its documentation says:
A SecIdentity object contains a SecKey object and an associated SecCertificate object.
So, I believe I am in the right path, but I couldn't find a way of extracting the SecKey and the SecCertificate from this SecIdentity.
I have also not found a way to get a PEM string from a SecKey or SecCertificate, but that would only be the last step.
This is the code I've used to create the identity:
let key: NSString = kSecImportExportPassphrase as NSString
let options: NSDictionary = [key : p12Password]
var rawItems: CFArray?
let p12Data = try! Data(contentsOf: p12FileUrl)
let data = p12Data! as NSData
let cfdata = CFDataCreate(kCFAllocatorDefault, data.bytes.assumingMemoryBound(to: UInt8.self), data.length)!
let result: OSStatus = SecPKCS12Import(cfdata, options, &rawItems)
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
}
-- UPDATE --
I made some more progress, and managed to extract the certificate in a DER format (which I haven't tried to convert to PEM so far - I believe it should be easy), but I still have no idea how to get the private key.
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
var cert: SecCertificate?
SecIdentityCopyCertificate(identity, &cert)
var certDer = SecCertificateCopyData(cert!) //DER format
var key: SecKey?
SecIdentityCopyPrivateKey(identity, &key)
let keyDict = SecKeyCopyAttributes(key!) //Not sure what we can find here
}
I am following the Apple documentation for adding a password to the keychain located here -> https://developer.apple.com/documentation/security/keychain_services/keychain_items/adding_a_password_to_the_keychain
When I run the following code it works as expected and the status comes back as 0.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
let account = credentials.username
let password = credentials.password.data(using: String.Encoding.utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
print(status)
When I modify the code with hardcoded strings it fails with a status of -50.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
//let account = credentials.username
//let password = credentials.password.data(using: String.Encoding.utf8)!
let account = "testUserName"
let password = "testPassword"
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
print("Keychain Save Status: \(status)")
Can someone explain this to me? I also tried explicitly setting the strings to utf8 format with let account = "testUsername".utf8. It doesn't make sense to me that this would fail if the value is a valid string.
Also does anyone have the link to the status code descriptions? I found the descriptions but it doesn't give the associated number code https://developer.apple.com/documentation/security/1542001-security_framework_result_codes
YES, I know my answer on the other track. As per my own experience, I'm using below keychain wrapper in Swift and Objective - C. Happy and Lazy Coding! :)
Swift - Locksmith - https://github.com/matthewpalmer/Locksmith
Objective-C - SSKeychain - https://github.com/samsoffes/sskeychain
//Generate Device UUID
func CreateApplicationDeviceUUID() -> String{
let DeviceUUID = NSUUID().uuidString
print("DeviceUUD==\(DeviceUUID)")
return DeviceUUID
}
//Retrive Device Unique UUID
let keyChainID = Locksmith.loadDataForUserAccount(userAccount: Bundle.main.object(forInfoDictionaryKey:"CFBundleName") as! String)
let retriveuuid = keyChainID?[RDGlobalFunction.deviceAppUUID] //RDGlobalFunction.deviceAppUUID is a Key of KeyChain Value Storage
if(retriveuuid == nil){
let uuid = CreateApplicationDeviceUUID()
do{
try Locksmith.saveData(data: [RDGlobalFunction.deviceAppUUID : uuid], forUserAccount: Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String) //Locksmith - SSkeyChain Thirdparty KeyChain Wrapper
}catch{
//Catch Error
}
}
Other Reference Link:
https://www.raywenderlich.com/179924/secure-ios-user-data-keychain-biometrics-face-id-touch-id
https://medium.com/ios-os-x-development/securing-user-data-with-keychain-for-ios-e720e0f9a8e2
https://code.tutsplus.com/tutorials/securing-ios-data-at-rest--cms-28528
I figured out that if I change
let password = "testPassword"
to
let password = "testPassword".data(using: String.Encoding.utf8)!
then it will work as expected. It seems that the kSecValueData parameter must be a utf8 encoded string.
I need help to read and write data to a remote plist file in my iOS application with Swift.
I can read and save data in local but not with a remote server.
Here, my code to read in local.
Variables
var VintiInizialiID: AnyObject!
var PersiInizialiID: AnyObject!
var CampionatoID: AnyObject!
var coefficientetorneoID: AnyObject!
loadPlistData()
func loadPlistData() {
var VintiInizialiKey = "VintiIniziali"
var PersiInizialiKey = "PersiIniziali"
var TutorialKey = "Tutorial"
var coefficientetorneoKey = "CoefficienteTorneo"
var CampionatoKey = "Campionato"
// getting path to database.plist
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths[0] as! String
let path = documentsDirectory.stringByAppendingPathComponent("database.plist")
let fileManager = NSFileManager.defaultManager()
//check if file exists
if(!fileManager.fileExistsAtPath(path)) {
// If it doesn't, copy it from the default file in the Bundle
if let bundlePath = NSBundle.mainBundle().pathForResource("database", ofType: "plist") {
let resultDictionary = NSMutableDictionary(contentsOfFile: bundlePath)
println("Bundle database.plist file is --> \(resultDictionary?.description)")
fileManager.copyItemAtPath(bundlePath, toPath: path, error: nil)
println("copy")
} else {
println("database.plist not found. Please, make sure it is part of the bundle.")
}
} else {
println("database.plist already exits at path.")
// use this to delete file from documents directory
//fileManager.removeItemAtPath(path, error: nil)
}
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
println("Loaded database.plist file is --> \(resultDictionary?.description)")
var myDict = NSDictionary(contentsOfFile: path)
if let dict = myDict {
//loading values
VintiInizialiID = dict.objectForKey(VintiInizialiKey)!
PersiInizialiID = dict.objectForKey(PersiInizialiKey)!
CampionatoID = dict.objectForKey(CampionatoKey)!
coefficientetorneoID = dict.objectForKey(coefficientetorneoKey)!
//...
} else {
println("WARNING: Couldn't create dictionary from GameData.plist! Default values will be used!")
}
}
And Finally SavePlistData()
func Saveplistdata() {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0)as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("database.plist")
var dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(VintiInizialiID, forKey: "VintiIniziali")
dict.setObject(PersiInizialiID, forKey: "PersiIniziali")
dict.setObject(CampionatoID, forKey: "Campionato")
dict.setObject(coefficientetorneoID, forKey: "CoefficienteTorneo")
//...
//writing to database.plist
dict.writeToFile(path, atomically: false)
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
// println("Saved database.plist file is --> \(resultDictionary?.description)")
}
No, there isn't a native way to read and write to an external .plist using just Swift without downloading the file, making changes and re-uploading it. Alternatively, you'd need to set up your own API on a server in order to carry out the read / write actions for you.
As #Scott H stated in the comments, theres a better way to do this:
If you want to go this route, download the file locally, change it
locally, and then upload to the server. However, there are many
alternatives available to you for remote configuration like CloudKit,
Parse, or similar.
Learn more about 3rd party options:
CloudKit
Parse
NSMutableDictionary(contentsOfFile: bundlePath)
Use contentsOfURL instead.