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.
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
Im trying to generate a privatekey that only is accessible when either devicecode or current set of biometrics( that is already registered on device) is used.
It works when i have a finger registered on device, then its all good. But if i delete my registered "touchid-finger", and try to generate a new key, then it returns nil
Errorcode is -25293
Code example:
func generateKey() -> SecKey?{
var error: Unmanaged<CFError>?
let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.devicePasscode,.or,.biometryCurrentSet],
nil)
let attributes:[String : Any] = [kSecAttrType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String:4096,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent:true,
kSecAttrCanSign: true,
kSecAttrApplicationTag: "yes.its.my.tag",
kSecAttrAccessControl:accessControl!]]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
if(error != nil || privateKey == nil) {
fatalError("explode Kittens")
}
return privateKey
}
fyi.Its actually works on simulators but not on real devices.
Am i doing something wrong? Is this working as intended? is it a bug? (sooo many questions :D )
I'm generating a private key, you need to add in ACL object parameter kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. This will allow you to get a private key using a passcode or biometric data.
guard let aclObject = SecAccessControlCreateWithFlags(
kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage], nil) else {
return ""
}
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrAccessControl as String: aclObject as AnyObject, //protect with touch id
kSecAttrIsPermanent as String: true as AnyObject
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecAttrKeyType as String: kSecMessECCKeyType,
kSecAttrKeySizeInBits as String: kSecMessECCKeySize as AnyObject,
kSecAttrLabel as String: kSecMessECCSignLabel as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject
]
guard let eCCPrivKey = SecKeyCreateRandomKey(parameters as CFDictionary, nil) else {
print("ECC KeyGen Error!")
return ""
}
guard let eCCPubKey = SecKeyCopyPublicKey(eCCPrivKey) else {
print("ECC Pub KeyGen Error")
return ""
}
"The problem relates to how the item is /created/, and that’s not what these flags control.
Indeed, for .biometryCurrentSet to have any meaning at the time of use, there must actually be a current set of biometrics at the time of creation, and I think that’s the source of the errSecAuthFailed.
My recommendation is manually fall back to using just .devicePasscode if there’s no biometrics available. Two ways: A. Catch the error and retry B. Preflight the request using the LocalAuthentication framework."
Apple says no can do :D
I have an application where I generate shared secret between iOS / android / nodejs using ECDH over secp521r1 curve. To ensure that it works I wrote a test that uses data generated with nodejs and that worked until iOS 13.
The source code of the test was:
let publicKey = "BABBvZ56c4bj1Zo73LIt/bBVa3jvGTA1fceoOG/M9TeXHx5ffCggRteEVS+bwrgQWPOwJPHhevNenaVn32ZnhztS0QFBqKGZTF1pKNSvuj+PDKQ625TauNroq+LQdeS+Pn6GVHL0iW5pp84NZ06L97VZ9HYm+g2lMnlUFV8hco2CmwBqHQ=="
let privateKey = "AXn994UN59QCEqmCmXmmNZ3hVZPlMwzTIeBupJGG4CqDWfWLuCTui7qiBfQtCFcQ1ks4NNB/tHEZUJ+bB97+pkJ3"
let otherBase64 = "BAAzWyzdh2e+ZNUCFt4oDADURb8+m9WA7gbWtTo57ZP3U23VuvMnRHf+12GpTSV8A5pt+vZfaR2cT02P+LPRc/kGzgAT2IYIgDz/cKbzMi520ZLa0GYk1xzCuNqFhdBZmrB5w0ymsPLdJzIG1QZ3xu7OufEipm5D41abphLLnbH+OyTX6w=="
let expectedShared = "AQkTOOHPcvlXufR2dm1FHaIJRlTgmxTJMI+h0kJ+nMVNopIP+opSqUNmflsgnJzT8JTodd/eehaaq5vvYdDVciIQ"
// iOS secKey is reconstructed by concatenating public and private key
let otherDataKey = Data.init(base64Encoded: otherBase64)!
var concatenatedKey = Data.init(base64Encoded: publicKey)!
concatenatedKey.append(Data.init(base64Encoded: privateKey)!)
// generate private key
var attributes: [String:Any] =
[
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 521,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(concatenatedKey as CFData, attributes as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate other public key
attributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic
guard let otherKey = SecKeyCreateWithData(otherDataKey as CFData, attributes as CFDictionary, nil) else {
XCTAssertTrue(false)
return
}
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate shared secret
XCTAssertEqual((shared as Data).base64EncodedString(), expectedShared);
With iOS 13 I was forced to modify the content of my exchangeOptions dictionary as discussed here (SecKeyCopyKeyExchangeResult() function return an error, "kSecKeyKeyExchangeParameterRequestedSize is missing")
let exchangeOptions: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 66]
The problem is that with this option, the result of SecKeyCopyKeyExchangeResult does not match anymorewith nodejs one (which is also true on iOS 12)
I finally found a solution... In iOS <= 12, leaving exchange parameters empty when trying to use ecdhKeyExchangeStandardX963SHA256 algorithm was falling back to using SecKeyAlgorithm.ecdhKeyExchangeCofactor.
Therefore the fix to reproduce previous behavior is to modify the SecKeyCopyKeyExchangeResult with
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeCofactor, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
This works at least for iOS 10 to 13
Does anyone know how to validate x509certificate in swift? Or maybe anyone have link with examples of it?
I got certificate from keychain, now I need to validate it.
let keychainQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
kSecAttrLabel as String: "cert",
kSecReturnRef as String: kCFBooleanTrue]
var result: CFTypeRef?
let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result)
guard status == errSecSuccess else { return }
entry = result as! SecCertificate
I found solution for resolving those problem, but still doesn't check it.
We need to create SecTrust class from our certificate.
let policy: SecPolicy = SecPolicyCreateBasicX509()
let trust: SecTrust?
let status = SecTrustCreateWithCertificates(entry as CFTypeRef, policy, &trust)
Then we can check this for example on date
let trustCertStatus = SecTrustSetVerifyDate(trust!, CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent()))
Apple has provided Generic Keychain sample which is written in Swift, I want to go ahead with Objective-C.
I have enabled keychain sharing in both the apps and on canOpenUrl I am able to invoke application B from A, now I want to share username and password from app A to app B. App ID is same for both the applications.
I have looked at various tutorials also don't want to use any third party project.
Could not came to know how to pass the parameter from app A to app B.
Enable Keychain sharing:
Turn on the Keychain Sharing capability.
Select developer team
Specify Keychain group name to something meaningful (for example testKeychainG1)
Open .entitlements file and replace $(AppIdentifierPrefix) with your APP ID (for example AB123CDE45.testKeychainG1)
Accessing Keychain (Retrieve shared items):
let itemKey = "Item Key"
let keychainAccessGroupName = "AB123CDE45.testKeychainG1"
let query:[String:Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: keychainAccessGroupName
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad == noErr {
if let result = result as? Data,
let keyValue = NSString(data: result,
encoding: String.Encoding.utf8.rawValue) as? String {
// Found successfully
print(keyValue)
}
} else {
print("Error: \(resultCodeLoad)")
}
step 1:
Set URL Schemes and add the AppA's URL Scheme to the AppB's info.plist like this:<key>LSApplicationQueriesSchemes</key>
<array>
<string>Aapp_Scheme</string>
</array>
step 2:
In app A:
let url = URL.init(string: "B_Scheme://name=Obama&password=Trump");
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: ["":""], completionHandler: nil);
}
step 3:
In app B's AppDelegate.swift add the code:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
var name = "", password = "";
let param = url.absoluteString.replacingOccurrences(of: "B_Scheme://", with: "");
let paramArray = param.components(separatedBy: "&");
for temp in paramArray {
if (temp.range(of: "name=") != nil){
name = temp.replacingOccurrences(of: "name=", with: "");
}
else if (temp.range(of: "password=") != nil){
password = temp.replacingOccurrences(of: "password=", with: "");
}
}
if name == "Obama" && password == "Trump" {
print("get param success!");
}
return true;
}