Keychain API is looking only into system keychain not in login keychain Mac OS Swift - swift

I need some help in keychain API. I am searching a certificate in keychain using keychain API in a mac os application. However, it is searching only in system.keychain not in login.keychain. I didn't find any document where i can put or specify keychain in keychain search query. The current code is as below.
func isKeychainhasCert(for sNumber:Data) -> Bool {
var query = [String: AnyObject]()
query[kSecClass as String] = kSecClassCertificate
query[kSecAttrSerialNumber as String] = sNumber as CFData
query[kSecReturnRef as String] = true as CFBoolean
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult, {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
})
guard status != errSecItemNotFound else {
return false
}
return true
}
Any help or suggestion. Based on above code the method return only true if cert is present system.keychain. If present in login.keychain but not in system.keychain return false.

This is not an answer, but might shed some light on how to get the certs:
import Foundation
import Security
var result: OSStatus
// Usually, we operate in the "user" domain, check this:
var domain: SecPreferencesDomain = .system
result = SecKeychainGetPreferenceDomain(&domain)
if result == errSecSuccess {
assert(domain == .user)
}
// Get the list of searched keychains of the current domain:
// (it should include the "default" keychain for the "user" domain)
var searchList: CFArray? = nil
result = SecKeychainCopyDomainSearchList(domain, &searchList)
guard result == errSecSuccess, let searchedKeyChains = searchList as? [SecKeychain] else {
fatalError("SecKeychainCopyDomainSearchList failed")
}
print("Keychain search list:")
searchedKeyChains.forEach { keyChain in
print(keyChain)
}
print("")
// Get the default keychain for the current domain:
var defaultKeyChain: SecKeychain? = nil
result = SecKeychainCopyDefault(&defaultKeyChain)
guard result == errSecSuccess, defaultKeyChain != nil else {
fatalError("SecKeychainCopyDefault failed")
}
print("Default Keychain: \(defaultKeyChain!)")
Up until here, we only did some checks and tests.
Now, search the all certificates in the keychain(s):
// Here, search all "searchable" keychains, by not specifying a
// kSecMatchSearchList value.
var copyResult: CFTypeRef? = nil
result = SecItemCopyMatching([
kSecClass: kSecClassCertificate,
kSecMatchLimit: kSecMatchLimitAll,
kSecReturnRef: true
] as NSDictionary, &copyResult)
guard result == errSecSuccess, let certificates1 = copyResult as? [SecCertificate] else {
fatalError("unexpected result type: \(copyResult)")
}
Print all found certificates:
certificates1.forEach { cert in
print(cert)
}
Search only in the "default" keychain for the current domain (which should be .user). We do this, by specifying the kSecMatchSearchList parameter:
// Here, search only the default keychain:
result = SecItemCopyMatching([
kSecClass: kSecClassCertificate,
kSecMatchLimit: kSecMatchLimitAll,
kSecMatchSearchList: [defaultKeyChain] as CFArray,
kSecReturnRef: true
] as NSDictionary, &copyResult)
guard result == errSecSuccess, let certificates2 = copyResult as? [SecCertificate] else {
fatalError("unexpected result type: \(copyResult)")
}
Now, lets see what additional certs have been found in any other keychain than the default one:
let diff = Array(Set(certificates1).symmetricDifference(Set(certificates2)))
print("Difference:")
diff.forEach { cert in
print(cert)
}
print("")

Related

Data conflicts - Swift

I am new to Swift, and I am fetching a VPN certificate from a database to create a tunnel.
The problem is that the variable that should contain the certificate (as Data type) is calling a function that is fetching the certificate, but the certificate takes time to appear, so the variable is empty and sent to the VPN configuration, so the VPN won't work.
The certificate appears later on.
I am not really good at solving these kinds of issues,
I tried to make a delay in the function that is fetching my certificate, but the problem is still present (I know that it's a bad method because network speed may vary and I cannot control the time)
Can you please help me?
func getCertificate() -> Data? {
self.loading.onNext(true)
var configData:String = ""
Services.getCert(authToken: UserManager.currentUser?.token ?? "", method: vpnMethod, userID: String(UserManager.currentUser?.user_id ?? 0)) { (error, VpnModel, Int) in
self.loading.onNext(false)
if error == nil{
if VpnModel?.cert_body == nil{
configData = ""
}else{
configData = VpnModel?.cert_body ?? ""
}
}
}
let configurationData = Data(configData.utf8)
print(String(decoding: configurationData, as: UTF8.self))
return configurationData
}
You need a completion as Services.getCert(...... is asynchronous
func getCertificate(completion:#escaping (Data? ->())) {
self.loading.onNext(true)
var configData:String = ""
Services.getCert(authToken: UserManager.currentUser?.token ?? "", method: vpnMethod, userID: String(UserManager.currentUser?.user_id ?? 0)) { (error, VpnModel, Int) in
self.loading.onNext(false)
if error == nil{
if VpnModel?.cert_body == nil{
configData = ""
}else{
configData = VpnModel?.cert_body ?? ""
}
let configurationData = Data(configData.utf8)
print(String(decoding: configurationData, as: UTF8.self))
completion(configurationData)
}
}
}
To call
getCertificate { res in
print(res)
}

Where does SecKeyCreateSignature get the key name for Keychain signing authorization dialog?

I have noticed a difference between certain keys in the Keychain with respect to how they appear in the Keychain signing dialog, and I cannot figure out why some are displayed a certain way while others are not.
Here is some test code to use identities in the Keychain to sign a sample bit of data.
func testCreateSignature() throws {
let query: [String: Any] = [kSecClass as String: kSecClassIdentity,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: false,
kSecReturnRef as String: true,
kSecReturnData as String: true]
var resultsRef: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &resultsRef)
guard status == errSecSuccess else { throw SecurityError.unhandledError(status: status) }
guard let results = resultsRef as? [[String:Any]] else {
throw SecurityError.unexpectedCertificateData
}
let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
var privateKey: SecKey!
for result in results {
let secIdentity = result[kSecValueRef as String] as! SecIdentity
try SecIdentityCopyPrivateKey(secIdentity, &privateKey).check()
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA1, data as CFData, &error)!
if let error = error {
throw error.takeRetainedValue()
}
print(signature)
}
}
When the code attempts to use one of the keys that Xcode installed for code signing, the resulting dialog looks like the following:
However, when the code attempts to use a key that I've installed, no matter what the label on the key in the Keychain is, it always looks like this:
When my app attempts to use a key to sign, I would like the user to see the name of the key the app wants to use, instead of just generic "privateKey", but I cannot find where this information might be stored on the key.
I have checked the kSecAttrLabel and kSecAttrApplicationLabel attributes of both identities and the private keys and cannot find the text that appears in the dialogs.
I found it. It is a property of the Access Control List of a Keychain item. See 'descriptor' param for SecAccessCreate.
If you do not specify a custom ACL when importing a key, it will default to "privateKey".
I was using SecPKCS12Import to import a .pfx file. I attempted to set the kSecImportExportAccess key in the options parameter to a custom SecAccess object, but it would always import with a default ACL.
I ended up refactoring the code to use SecItemImport instead to import the .pfx file and supplied a custom SecAccess instance:
static func importIdentity(contentsOf url: URL, password: String) throws {
let data = try Data.init(contentsOf: url)
var access: SecAccess!
try SecAccessCreate("License Key" as CFString, nil, &access).check()
var keychain: SecKeychain!
var outItems: CFArray?
let filename: CFString? = url.isFileURL ? url.lastPathComponent as CFString : nil
var inputFormat: SecExternalFormat = .formatPKCS12
var itemType: SecExternalItemType = .itemTypeAggregate
let unmanagedPassword = Unmanaged<AnyObject>.passRetained(password as AnyObject)
let unmanagedAccess = Unmanaged<SecAccess>.passRetained(access)
var params: SecItemImportExportKeyParameters = SecItemImportExportKeyParameters(version: UInt32(SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION),
flags: .importOnlyOne,
passphrase: unmanagedPassword,
alertTitle: nil,
alertPrompt: nil,
accessRef: unmanagedAccess,
keyUsage: nil,
keyAttributes: nil)
try SecKeychainCopyDefault(&keychain).check()
try SecItemImport(data as CFData, filename, &inputFormat, &itemType, [], &params, keychain, &outItems).check()
}
Importing the identity as above will result in "License Key" being shown in the signing dialog rather than "privateKey".

How to get the realy fixed Device-ID in swift?

I use the below code since long time. I have thought it is unique
But I have deleted my app and reinstalled it, I get new different Device-ID.
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
print(uuid)
}
every new reinstall, I get a new ID.
How do I get something which stays the same?
Since the value returned from identifierForVendor can be cleared when deleting the app or reset if the user resets it in the Settings app, you have to manage persisting it yourself.
There are a few ways to accomplish this. You can setup a server that assigns a uuid which is then persisted and fetched server side via user login, or you can create and store it locally in the keychain.
Items stored in the keychain will not be deleted when the app is deleted. This allows you to check if a uuid was previously stored, if so you can retrieve it, if not you can generate a new uuid and persist it.
Here's a way you could do it locally:
/// Creates a new unique user identifier or retrieves the last one created
func getUUID() -> String? {
// create a keychain helper instance
let keychain = KeychainAccess()
// this is the key we'll use to store the uuid in the keychain
let uuidKey = "com.myorg.myappid.unique_uuid"
// check if we already have a uuid stored, if so return it
if let uuid = try? keychain.queryKeychainData(itemKey: uuidKey), uuid != nil {
return uuid
}
// generate a new id
guard let newId = UIDevice.current.identifierForVendor?.uuidString else {
return nil
}
// store new identifier in keychain
try? keychain.addKeychainData(itemKey: uuidKey, itemValue: newId)
// return new id
return newId
}
And here's the class for storing/retrieving from the keychain:
import Foundation
class KeychainAccess {
func addKeychainData(itemKey: String, itemValue: String) throws {
guard let valueData = itemValue.data(using: .utf8) else {
print("Keychain: Unable to store data, invalid input - key: \(itemKey), value: \(itemValue)")
return
}
//delete old value if stored first
do {
try deleteKeychainData(itemKey: itemKey)
} catch {
print("Keychain: nothing to delete...")
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: valueData as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
let resultCode: OSStatus = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != 0 {
print("Keychain: value not added - Error: \(resultCode)")
} else {
print("Keychain: value added successfully")
}
}
func deleteKeychainData(itemKey: String) throws {
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)
if resultCodeDelete != 0 {
print("Keychain: unable to delete from keychain: \(resultCodeDelete)")
} else {
print("Keychain: successfully deleted item")
}
}
func queryKeychainData (itemKey: String) throws -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad != 0 {
print("Keychain: unable to load data - \(resultCodeLoad)")
return nil
}
guard let resultVal = result as? NSData, let keyValue = NSString(data: resultVal as Data, encoding: String.Encoding.utf8.rawValue) as String? else {
print("Keychain: error parsing keychain result - \(resultCodeLoad)")
return nil
}
return keyValue
}
}
Then you can just have a user class where you get the identifier:
let uuid = getUUID()
print("UUID: \(uuid)")
If you put this in a test app in viewDidLoad, launch the app and note the uuid printed in the console, delete the app and relaunch and you'll have the same uuid.
You can also create your own completely custom uuid in the app if you like by doing something like this:
// convenience extension for creating an MD5 hash from a string
extension String {
func MD5() -> Data? {
guard let messageData = data(using: .utf8) else { return nil }
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes { digestBytes in
messageData.withUnsafeBytes { messageBytes in
CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
}
}
return digestData
}
}
// extension on UUID to generate your own custom UUID
extension UUID {
static func custom() -> String? {
guard let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
return nil
}
let unique = bundleID + NSUUID().uuidString
let hashData = unique.MD5()
let md5String = hashData?.map { String(format: "%02hhx", $0) }.joined()
return md5String
}
}
Note that to use the MD5 function you will have to add the following import to an Objective-C bridging header in your app: (if you're building with Xcode < 10. In Xcode 10+ CommonCrypto is included so you can skip this step)
#import <CommonCrypto/CommonCrypto.h>
If your app does not have a bridging header, add one to your project and make sure to set it in build settings:
Once it's setup you can generate your own custom uuid like this:
let otherUuid = UUID.custom()
print("Other: \(otherUuid)")
Running the app and logging both outputs generates uuids something like this:
// uuid from first example
UUID: Optional("8A2496F0-EFD0-4723-8C6D-8E18431A49D2")
// uuid from second custom example
Other: Optional("63674d91f08ec3aaa710f3448dd87818")
Unique Id in iPhone is UDID, which is not accessible in current version of OS, because it can be misused. So Apple has given the other option for the unique key but it change every time you install the app.
--Cannot access UDID
But there is another way to implement this feature.
First you have to generate the Unique ID :
func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
After getting the this which is unique but changes after the app install and reinstall.
Save this id to the Key-Chain on any key let say "uniqueID".
To save the key in keyChain :
func getDataFromKeyChainFunction() {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
}
}
func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Next when you required to perform any task based on uniqueID ,first check if any data is saved in Key-Chain on key "uniqueID" .
Even if you uninstall the app , the key-chain data is still persisted, it will delete by OS.
func checkUniqueID() {
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
} else {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
print("status: ", status)
}
}
In this way you can generate the uniqueID once and use this id every time.
NOTE: But when you upload next version of your app, upload it with the same Provisioning Profile otherwise you cannot access the key-chain store of your last installed app.
Key-Chain Store is associated with the Provisioning profile.
Check Here
Access to the unique device id (UDID) has been disallowed for ages now. identifierForVendor is its replacement, and its behaviour has always been documented.

Keychain references in Swift used in NEVPNManager

I'm trying to connect to a VPN using Swift in Xcode. I'm using KeychainSwift to keep keychain references. My code looks like this:
private func connectVPN(completion: #escaping () -> Void) {
let keychain = KeychainSwift()
keychain.set("<mypassword>", forKey: "passref")
keychain.set("<sharedsecretpassword>", forKey: "secretref")
NEVPNManager.shared().loadFromPreferences { error in
let vpnhost = "<11.11.11.11>"
let username = "<myusername>"
let p = NEVPNProtocolIPSec()
p.username = username
p.localIdentifier = username
p.serverAddress = vpnhost
p.remoteIdentifier = vpnhost
p.authenticationMethod = .sharedSecret
p.disconnectOnSleep = false
p.sharedSecretReference = keychain.getData("secretref")
p.passwordReference = keychain.getData("passref")
var rules = [NEOnDemandRule]()
let rule = NEOnDemandRuleConnect()
rule.interfaceTypeMatch = .any
rules.append(rule)
NEVPNManager.shared().localizedDescription = "My VPN"
NEVPNManager.shared().protocolConfiguration = p
NEVPNManager.shared().onDemandRules = rules
NEVPNManager.shared().isOnDemandEnabled = true
NEVPNManager.shared().isEnabled = true
NEVPNManager.shared().saveToPreferences { error in
if (error != nil) {
print(error!)
} else {
do {
try NEVPNManager.shared().connection.startVPNTunnel()
completion()
} catch {
print("can't connect VPN'")
}
}
}
}
}
I'm using keychain.getData("secretref"), because this field needs
A persistent keychain reference to a keychain item containing the IKE
shared secret.
What's more,
The persistent keychain reference must refer to a keychain item of
class kSecClassGenericPassword.
I'm not really sure, if I'm doing it right. I didn't subclass kSecClassGenericPassword or use it in any way.
When I'm using this function in code, a window shows with information, that there is no shared secret for this VPN. I think it means that this keychain doesn't work as it's supposed to.
In iPhone settings, it tries to connect, moves switch towards green and instantly the switch goes back to "off" state. When I put the same data as in code manually, the connection works.
What am I doing wrong? What should I correct?
Okay, I have the answer. In the query for the SecItemCopyMatching, I had to choose kSecReturnPersistentRef with kCFBooleanTrue - not kSecReturnData.

How do I encode an unmanaged<SecKey> to base64 to send to another server?

I'm trying to use key pair encryption to validate identity between my app and my PHP server. To do this I need to send the public key over to the server after I generate it in my app.
if let pubKey = NSData(base64EncodedData: publicKey, options: NSDataBase64DecodingOptions.allZeros)! {
println(pubKey)
}
publicKey is of type Unmanaged<SecKey>.
The error I'm getting in the above code is: Extra argument 'base64EncodedData' in call
How would I do this? Is there a better way?
Edit: This is how the keypair is generated:
var publicKeyPtr, privateKeyPtr: Unmanaged<SecKey>?
let parameters = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 2048
]
let result = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)
let publicKey = publicKeyPtr!.takeRetainedValue()
let privateKey = privateKeyPtr!.takeRetainedValue()
let blockSize = SecKeyGetBlockSize(publicKey)
Edit 2: So the issue is that SecKey is not NSData, so my question here should be: How do I convert a publicKey:SecKey to NSData?
It seems that you can temporary store the key to keychain and then get it back and convert it to data:
func convertSecKeyToBase64(inputKey: SecKey) ->String? {
// First Temp add to keychain
let tempTag = "de.a-bundle-id.temp"
let addParameters :[String:AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): tempTag,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecValueRef): inputKey,
String(kSecReturnData):kCFBooleanTrue
]
var keyPtr: Unmanaged<AnyObject>?
let result = SecItemAdd(addParameters, &keyPtr)
switch result {
case noErr:
let data = keyPtr!.takeRetainedValue() as! NSData
// Remove from Keychain again:
SecItemDelete(addParameters)
let encodingParameter = NSDataBase64EncodingOptions(rawValue: 0)
return data.base64EncodedStringWithOptions(encodingParameter)
case errSecDuplicateItem:
println("Duplicate Item")
SecItemDelete(addParameters)
return nil
case errSecItemNotFound:
println("Not found!")
return nil
default:
println("Error: \(result)")
return nil
}
}
While the fact is barely documented, you can pull out everything you need (that is, modulus and exponent) from the SecKey using SecKeyCopyAttributes.
See here for the details.
Swift 4 method to get base64 string from SecKey, publicKey :)
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, nil) else {
NSLog("\tError obtaining export of public key.")
return ""
}
let publicKeyNSData = NSData(data: publicKeyData as Data)
let publicKeyBase64Str = publicKeyNSData.base64EncodedString()