How to move SecKey , from app keychain to shared keychain group - ios

I'm generating the private key in my iOS app for secure communication between server and app.
The private key is being stored in the keychain. In the new version of the app, I want to use the shared keychain group because of notification extensions. How do is transfer the private key that was stored in the app keychain to the shared group keychain. Below is the code I m using to generate the private key
func createPrivateKey(withLabel label: String) throws -> SecKey {
let privateKeyAttrs: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationLabel as String: label,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
]
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyAttrs,
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
// swiftlint:disable:next force_unwrapping
throw error!.takeRetainedValue() as Error
}
return privateKey
}

You'll have to specify the access group on creation, or update the existing keys with the new access groups. After you've done that, you should setup the entitlements correctly, so that apps and extensions you create can access the correct keychain access group. Read with care, as there is only a thin line between app groups and keychain sharing group. Make sure you set up the correct one (documentation here).
As for your query
let accessGroup = "<# Your Team ID #>.com.example.SharedItems"
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecAttrAccessGroup as String: accessGroup,
kSecPrivateKeyAttrs as String: privateKeyAttrs
]
That should create the query for creating keys for the access group you specify. I suppose you can figure out the update query yourself.

Related

kSecAttrAccount and kSecAttrGeneric

I would like to save within an account marta#domain.com two values. Example of the dictionaries:
Value 1.
kSecAttrAccount = marta#domain.com
kSecAttrGeneric = value1Key
kSecValueData = value1
Value 2.
kSecAttrAccount = marta#domain.com
kSecAttrGeneric = value2Key
kSecValueData = value2
Is it possible? I'm getting an error to do the query when I try to keep the second value.
I attach a snippet of my code:
func addCredentials(_ credentials: KeychainCredentials) throws {
let account = credentials.account
let password = credentials.password.data(using: String.Encoding.utf8)!
let generic = credentials.generic
let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.userPresence,
nil)
// Build the query
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: account,
kSecAttrGeneric as String: generic,
kSecAttrAccessControl as String: accessControl as Any,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError(status: status) }
}
For kSecClassGenericPassword, the combination of service and account must be unique. For the list of which attributes are part of the primary key, see the documentation for errSecDuplicateItem.
The system considers an item to be a duplicate for a given keychain when that keychain already has an item of the same class with the same set of composite primary keys. Each class of keychain item has a different set of primary keys, although a few attributes are used in common across all classes. In particular, where applicable, kSecAttrSynchronizable and kSecAttrAccessGroup are part of the set of primary keys. The additional per-class primary keys are listed below:
For generic passwords, the primary keys include kSecAttrAccount and kSecAttrService.
For internet passwords, the primary keys include kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol, kSecAttrAuthenticationType, kSecAttrPort, and kSecAttrPath.
For certificates, the primary keys include kSecAttrCertificateType, kSecAttrIssuer, and kSecAttrSerialNumber.
For key items, the primary keys include kSecAttrKeyClass, kSecAttrKeyType, kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeySizeInBits, and kSecAttrEffectiveKeySize.
For identity items, which are a certificate and a private key bundled together, the primary keys are the same as for a certificate. Because a private key may be certified more than once, the uniqueness of the certificate determines that of the identity.
If I were building this, I would likely put all the key/values into a dictionary, and serialize that to a single kSecAttrAccount record.
Alternately, you could merge your account and service values together into the account (if you have a service), and put value1Key in the service attribute. Then account+service would be unique to a given key.

Secure Enclave: update SecAccessControlCreateFlags after key creation

I am wondering if anyone knows whether its possible to update the flags after the key creation inside the Secure Enclave or not?
Here's how I am creating the key:
let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[SecAccessControlCreateFlags.userPresence,
SecAccessControlCreateFlags.privateKeyUsage],
nil)!
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "stacksometimesoverflow",
kSecAttrAccessControl as String: access
]
]
var error: Unmanaged<CFError>?
guard SecKeyCreateRandomKey(attributes as CFDictionary, &error) != nil else {
throw error!.takeRetainedValue() as Error
}
As you can see, the key is created with
SecAccessControlCreateFlags.userPresence, SecAccessControlCreateFlags.privateKeyUsage
My question is, is it possible to update the access flag of the key (same key), say I want to remove SecAccessControlCreateFlags.userPresence
All the best!
Johnny
I don't think that's possible. According to Apple's documentation:
... because its backing storage is physically part of the Secure Enclave, you can never inspect the key’s data.
I think the best way is to delete your key with SecItemDelete(_:) and then create new key without the .userPresence flag.

Delete Private Key from Keychain

For the past few days, I have been working on trying to delete a generated private key from the keychain. I have been doing my best to follow the documentation Apple provides to delete keys, which you can find here, but I haven't been able to figure out where I am going wrong. Below is my current code for it:
func deleteKey() {
var secret: AnyObject?
// Retrieve Private Key
let tag = "tag".data(using: .utf8)!
print(secret)
let getQuery: [String: Any] = [kSecClass as String: kSecClassKey, // This is a query we make to find the Private key in the key chain based of the input we put inside of it
kSecAttrApplicationTag as String: tag, // We use the tag to search for our saved Private key in the KeyChain as it is unique
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // This says the keychain will be of type RSA
kSecReturnRef as String: kCFBooleanTrue,// This says to return a reference to the key, and not the key data itself
]
let status = SecItemCopyMatching(getQuery as CFDictionary, &secret)
print(secret!)
let delQuery: [String: Any] = [
kSecMatchItemList as String: secret,// Key to delete
]
let delStatus = SecItemDelete(delQuery as CFDictionary)
guard delStatus == errSecSuccess || delStatus == errSecItemNotFound else {
print(delStatus)
print("Error")
return
}
print ("success")
My delStatus variable keeps on returning "-50", which I know means there is something wrong with my search parameters in the query, but I honestly can not figure it out. While I wait for an answer, I will keep trying to figure it out, but any help would be greatly appreciated. Thanks in advance.

SecKeyRef to base64 and back in swift

I am trying to generate a public/private pair of keys on the device and store them in the keychain.
Since i am using swift i will use a library to interact with the keychain. This is the one i found for that https://github.com/matthewpalmer/Locksmith.
What i need to do after i generate the keys is to convert them both in base64 and then store them in the key chain and afterwards recreate both keys using the base64 string from the keychain.
Using the Locksmith library this should be something like this.
Locksmith.saveData(["publicKeyKey": "publicKeyBase64data"], forUserAccount: "myUserAccount")
To generate the keys i use the following code
public func GenerateKeys() -> [SecKeyRef]{
let keySize = 2048;
var publicKeyPtr, privateKeyPtr: Unmanaged<SecKeyRef>?
let publicKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "com.site.key.public"
]
let privateKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "com.site.key.private"
]
let parameters: [String: AnyObject] = [
kSecAttrKeyType as! String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as! String: keySize,
kSecPublicKeyAttrs.takeUnretainedValue() as! String: publicKeyParameters,
kSecPrivateKeyAttrs.takeUnretainedValue() as! String: privateKeyParameters
]
let result = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)
let publicKey = publicKeyPtr!.takeRetainedValue()
let privateKey = privateKeyPtr!.takeRetainedValue()
let blockSize = SecKeyGetBlockSize(publicKey)
return [publicKey, privateKey];
}
I am able to generate the keys successfully but i can't figure out how to convert them to base64 and back. So i have the SecKeyRef objects but don't really know to continue.
Most of the code i have found already is in objective-c which i am not so familiar with.
Any kind of help is appreciated.
Thanks
You don't really need another keychain provider. You have set the kSecAttrIsPermanent parameter to true, so according to Apple doc the keyPair is already stored in default keychain.

How to generate an RSA asymmetric Key Pair in Swift for IOS?

I need a way to generate an RSA asymmetrical key pair in Swift. I don't need to store it in the keychain or anything. I just need to generate a key pair and shove both keys into String variables.
The keys do need to be compatible with PHP on the other end. I will use symmetrical encryption to secure the private key and store it on the phone. I'll be sending the public key to a web service that is implemented in PHP, and the web service will store the public key in a database.
That public key will be used later by the web service to encrypt values like one-time passwords and other sensitive values destined for the IOS app. I'll implement a similar scheme for small pieces of data flowing from the IOS app to the web service.
The only documented API declaration I could find for generating a key pair in Swift is shown on developer.apple.com:
func SecKeyGeneratePairAsync(_ parameters: CFDictionary!,
_ deliveryQueue: dispatch_queue_t!,
_ result: SecKeyGeneratePairBlock!)
I tried to figure out how to use this, but XCode doesn't like the underscores, and I'm not sure what I am actually supposed to do with this or how to use it.
Even if XCode would accept it, I am not sure how I would call the function and what values to pass it, etc. I included "import Security" at the top, so that's not the problem.
It's ridiculous that this should be so hard. All I want to do is generate an asymmetric key pair.
It's a piece of cake to do this in PHP or .NET or Java or any other language, but I can't find any clear documentation on it for Swift. I have to use Swift for this app. I don't want to use OpenSSL because it's deprecated.
I am using Swift because I bloody hate objective C. Swift is the reason I am finally jumping into IOS development.
I don't know how to integrate an Objective C class with Swift, and I'd really rather have a pure Swift solution if there is one. If not, I'd appreciate some pointers on how to integrate an Objective C solution and make it work.
The above snippet is the only function call Apple provides, and naturally it's incomplete, doesn't make sense, and doesn't work.
There is a good example of how to do this in Swift in the CertificateSigningRequestSwift_Test project in GitHub. Using a single call to SecKeyCreateRandomKey() one can generate the public/private key pair both at the same time and they will be saved in the keychain.
let tagPublic = "com.example.public"
let tagPrivate = "com.example.private"
let publicKeyParameters: [String: AnyObject] = [
String(kSecAttrIsPermanent): kCFBooleanTrue,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecAttrAccessible): kSecAttrAccessibleAlways
]
var privateKeyParameters: [String: AnyObject] = [
String(kSecAttrIsPermanent): kCFBooleanTrue,
String(kSecAttrApplicationTag): tagPrivate as AnyObject,
String(kSecAttrAccessible): kSecAttrAccessibleAlways
]
//Define what type of keys to be generated here
var parameters: [String: AnyObject] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 2048,
String(kSecReturnRef): kCFBooleanTrue,
kSecPublicKeyAttrs as String: publicKeyParameters as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParameters as AnyObject,
]
//Use Apple Security Framework to generate keys, save them to application keychain
var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error)
if privateKey == nil{
print("Error creating keys occurred: \(error!.takeRetainedValue() as Error), keys weren't created")
return
}
//Get generated public key
let query: [String: AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecReturnRef): kCFBooleanTrue
]
var publicKeyReturn:AnyObject?
let result = SecItemCopyMatching(query as CFDictionary, &publicKeyReturn)
if result != errSecSuccess{
print("Error getting publicKey from keychain occurred: \(result)")
return
}
let publicKey = publicKeyReturn as! SecKey?
//Set block size
let keyBlockSize = SecKeyGetBlockSize(self.publicKey!)
//Ask keychain to provide the publicKey in bits
let query: [String: AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrKeyType): keyAlgorithm.secKeyAttrType,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecReturnData): kCFBooleanTrue
]
var tempPublicKeyBits:AnyObject?
_ = SecItemCopyMatching(query as CFDictionary, &tempPublicKeyBits)
guard let publicKeyBits = tempPublicKeyBits as? Data else {
return
}
Heimdall seems to be what you're looking for. It's easy to use, can create RSA keypairs, encrypt, decrypt, sign and verify.
It uses the iOS/OS X keychain for storing the keys, so the keys are stored in a secure way.
From the GitHub Readme:
if let heimdall = Heimdall(tagPrefix: "com.example") {
let testString = "This is a test string"
// Encryption/Decryption
if let encryptedString = heimdall.encrypt(testString) {
println(encryptedString) // "cQzaQCQLhAWqkDyPoHnPrpsVh..."
if let decryptedString = heimdall.decrypt(encryptedString) {
println(decryptedString) // "This is a test string"
}
}
// Signatures/Verification
if let signature = heimdall.sign(testString) {
println(signature) // "fMVOFj6SQ7h+cZTEXZxkpgaDsMrki..."
var verified = heimdall.verify(testString, signatureBase64: signature)
println(verified) // True
// If someone meddles with the message and the signature becomes invalid
verified = heimdall.verify(testString + "injected false message",
signatureBase64: signature)
println(verified) // False
}
}

Resources