iOS - share key from keychain with service extension - ios

I need to share the private key that was generated by the main iOS app with the push notification service extension. Getting the key works fine within the app with SecItemCopyMatching(). But if I try to get the private key from within the service extension I get a "errSecItemNotFound".
Here is what I'm doing:
In the main app I generate a key pair and store it on the keychain with SecItemAdd(). I don't use the "kSecAttrIsPermanent: true" attribute while generating the key pair, because it doesn't allow to specify an "kSecAttrAccessGroup". As far as I understand the keychain sharing I need to specify an access group to share keys between apps, or between an extension and an app.
kSecAttrAccessGroup is a string that I specify which looks something like this: "MyAppIdentifierPrefix" + "com.example.app.my-keychain"
keyTag is also a string that i specify which looks something like this: "com.example.app.my-key"
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrApplicationTag as String: keyTag as AnyObject
]
// public key parameters
let publicKeyParams: [String: AnyObject] = [
kSecAttrApplicationTag as String: keyTag as AnyObject
]
// global parameters for key generation
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: myKeyType,
kSecAttrKeySizeInBits as String: myKeySize as AnyObject,
kSecPublicKeyAttrs as String: publicKeyParams as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject
]
// generate the key pair
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)
if status == errSecSuccess {
// parameters for public key
let pubKeyParameters: [String: AnyObject] = [
kSecClass as String: kSecClassKey,
kSecAttrAccessible as String: kSecAttrAccessibleAlways,
kSecAttrAccessGroup as String: keychainGroupName as AnyObject,
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrApplicationTag as String: keyTag as AnyObject,
kSecAttrKeyType as String: myKeyType,
kSecAttrKeySizeInBits as String: myKeySize as AnyObject,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecValueRef as String: pubKey!
]
// add public key to keychain
let statusPubKey = SecItemAdd(pubKeyParameters as CFDictionary, nil)
// parameters for private key
let privKeyParameters: [String: AnyObject] = [
kSecClass as String: kSecClassKey,
kSecAttrAccessible as String: kSecAttrAccessibleAlways,
kSecAttrAccessGroup as String: keychainGroupName as AnyObject,
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrApplicationTag as String: keyTag as AnyObject,
kSecAttrKeyType as String: myKeyType,
kSecAttrKeySizeInBits as String: myKeySize as AnyObject,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecValueRef as String: privKey!
]
// add private key to keychain
let statusPrivKey = SecItemAdd(privKeyParameters as CFDictionary, nil)
}
I enabled keychain sharing in the capabilities tab and specified the same identifier "com.example.app.my-keychain" for both the app and the push notification service extension.
My function to get the key bytes is as following:
func getPrivateKeyData(keyTag: String) -> Data? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: keyTag,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrAccessGroup as String: keychainGroupName as AnyObject,
kSecReturnData as String: true
] as [String : Any]
var data: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &data)
if status == errSecSuccess {
return data as? Data
} else { return nil }
}
This does not work so far...

Related

Creating shared secret using public and private keys using swift

I am trying to convert an ephemeralKey which is a series of bytes to an Elliptic Curve public key and then use it to create a shared key using my private key.
I know how to do this in python (code below). But I cannot find a way to do this in Swift. My Swift code is also copied below but it is not correct. Do you see the problem with my Swift code?
My python code:
from cryptography.hazmat.primitives.asymmetric import ec
privateKey = ec.generate_private_key(ec.SECP256R1())
devicePublicKey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), ephemeralKey)
sharedKey = privateKey.exchange(ec.ECDH(), devicePublicKey)
My Swift code:
let privateKeyParams: [String: Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256
]
var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(privateKeyParams as CFDictionary, &error)
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
]
let devicePublicKey = SecKeyCreateWithData(ephemeralKey as CFData, attributes as CFDictionary, nil)!
let sharedKey = ecdhSecretCalculation(publicKey: devicePublicKey, privateKey: privateKey)
func ecdhSecretCalculation(publicKey: SecKey, privateKey: SecKey) -> Data? {
var error: Unmanaged<CFError>?
let keyPairAttr:[String : Any] = [
kSecAttrKeySizeInBits as String: 256,
SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 32,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false]
]
let algorithm:SecKeyAlgorithm = .ecdhKeyExchangeStandardX963SHA256
let shared = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey, keyPairAttr as CFDictionary, &error) as Data?
return shared
}
The reason for the different shared secrets is that ecdhKeyExchangeStandardX963SHA256 is applied in the posted Swift code. Instead, ecdhKeyExchangeStandard must be applied.
Also, bear in mind that SecKeyCreateWithData() in the Swift code expects the private key to be the concatenation of the uncompressed public key (0x04|x|y) and the raw private key: 0x04|x|y|private, while in the Python code only the raw private key is specified (this is only for the sake of completeness, since the posted codes do not show how the private key was imported).
If this is taken into account, both codes provide the same shared secret.
Test:
Python:
from cryptography.hazmat.primitives.asymmetric import ec
import base64
ephemeralKey = base64.b64decode("BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/M=")
devicePublicKey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), ephemeralKey)
privateKeyRaw = base64.b64decode("v5PriRLFXqqILYFZkX2LWEbUQ/y/NCZXD6il5S6KPus=")
privateKey = ec.derive_private_key(int.from_bytes(privateKeyRaw, 'big'), ec.SECP256R1())
sharedKey = privateKey.exchange(ec.ECDH(), devicePublicKey)
print(base64.b64encode(sharedKey)) # 8bOrCLEe1eCciEDbq710xbSXQnKzvSHnVjnXnhpJImE=
Swift:
import Foundation
let keyStringPub = "BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/M="
let ephemeralKey = Data(base64Encoded: keyStringPub)!
let devicePublicKey = SecKeyCreateWithData(ephemeralKey as CFData, [kSecAttrKeyClass as String: kSecAttrKeyClassPublic, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256] as CFDictionary, nil)!
let keyStringPriv = "BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/O/k+uJEsVeqogtgVmRfYtYRtRD/L80JlcPqKXlLoo+6w=="
let privateKeyRaw = Data(base64Encoded: keyStringPriv)!
let privateKey = SecKeyCreateWithData(privateKeyRaw as CFData, [kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256,] as CFDictionary, nil)!
let sharedKey = ecdhSecretCalculation(publicKey: devicePublicKey, privateKey: privateKey)
let sharedKeyB64 = sharedKey!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
print(sharedKeyB64) // 8bOrCLEe1eCciEDbq710xbSXQnKzvSHnVjnXnhpJImE=
func ecdhSecretCalculation(publicKey: SecKey, privateKey: SecKey) -> Data? {
var error: Unmanaged<CFError>?
let keyPairAttr:[String : Any] = [
kSecAttrKeySizeInBits as String: 256,
SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 32,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false]
]
let algorithm:SecKeyAlgorithm = .ecdhKeyExchangeStandard // Fix!
let shared = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey, keyPairAttr as CFDictionary, &error) as Data?
return shared
}
Both codes provide the same shared secret.
If the ecdhKeyExchangeStandardX963SHA256 algorithm is used in the Swift code, the shared secret is additionally processed with ANSI-X9.63-KDF. The Cryptography library also provides this X963KDF:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF
import base64
xkdf = X963KDF(
algorithm=hashes.SHA256(),
length=32,
sharedinfo=None,
)
key = xkdf.derive(sharedKey)
print(base64.b64encode(key)) # ULQCW4bsStNQGg/avnlqaNALpAy2Z42SBZN97Qlnqa0=
For the reasons why a KDF is generally used afterwards, see e.g. here. Note that the X9.63-KDF is only one possibility, there are others, see e.g. here.

iOS Swift: SecureEnclave encryption app crash on SecKeyGeneratePair

I am facing an issue with SecureEnclave encryption in SecKeyGeneratePair method.
App crash on SecKeyGeneratePair method, I don't know what's missing or something wrong. I am referring https://github.com/trailofbits/SecureEnclaveCrypto
See the following code snippet.
func generateKeyPair(accessControl: SecAccessControl) throws -> (`public`: SecureEnclaveKeyReference, `private`: SecureEnclaveKeyReference) {
let privateKeyParams: [String: Any] = [
kSecAttrLabel as String: privateLabel,
kSecAttrIsPermanent as String: true,
kSecAttrAccessControl as String: accessControl,
]
let params: [String: Any] = [
kSecAttrKeyType as String: attrKeyTypeEllipticCurve,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: privateKeyParams
]
var publicKey, privateKey: SecKey?
let status = SecKeyGeneratePair(params as CFDictionary, &publicKey, &privateKey)
guard status == errSecSuccess else {
throw SecureEnclaveHelperError(message: "Could not generate keypair", osStatus: status)
}
return (public: SecureEnclaveKeyReference(publicKey!), private: SecureEnclaveKeyReference(privateKey!))
}

Can I update kSecAttrApplicationTag using SecItemUpdate?

Here is my code:
var query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: currentTag
]
var attributesToUpdate: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: archiveTag
]
osStatus = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
The error that I got back was One or more parameters passed to a function were not valid.
I removed kSecClass as String: kSecClassKey in attributesToUpdate and it worked

How to check if Key was store in KeyChain in Swift

I was trying to store two keys private and public in KeyChain and when I try to do so my result valuable return 0 which I am assuming that mean it was store ,but when I try to get back to decrypt a message I am getting it back as nil so if there is a way to check if the keys were store based SecItemCopyMatching? but I am not getting any error while creating them
let tagName = "PrivateKeyTag"
let privkey = "key"
let privkeyData = Data(privkey!.utf8)
let privateFilter: [String : Any] = [
(kSecClass as String) : kSecClassKey,
(kSecAttrKeyType as String) : kSecAttrKeyTypeRSA,
(kSecAttrApplicationTag as String) : tagName,
(kSecValueData as String) : privkeyData,
(kSecAttrKeyClass as String) : kSecAttrKeyClassPrivate,
// kSecAttrKeySizeInBits as String: 2048,
(kSecReturnPersistentRef as String): true,
] as [String : Any]
let result = SecItemAdd(privateFilter as CFDictionary, nil)
if ((result != noErr) && (result != errSecDuplicateItem)) {
NSLog("Cannot add key to keychain, status \(result).")
}
let getquery: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
print("key not found")
return
}
let key = item as! SecKey
When you create a cryptographic key, you can set the parameter kSecAttrIsPermanent to true which will automatically store the key in the default keychain. This will clean your code a bit so you no longer have to deal with the SecItemAdd() and all the error handling with that. So here is a simpler way to do what you're trying to do.
To create a key and query a key
let tag = "com.example.keys.mykey".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil)
let query: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
var item: CFTypeRef?
let status2 = SecItemCopyMatching(query as CFDictionary, &item)
guard status2 == errSecSuccess else { print("error1"); return }
let key = item as! SecKey
Running this code I believe will accomplish what you are trying to do in the code you provided in the question. I tried running the code you gave and the version of Xcode and swift I am using gives compiler errors.
If you run the code in this answer you will notice that no errors are printed to the console, indicating that the key was successfully found in the default keychain.

How to store public and private keys of kSecAttrKeyTypeRSA type with size 4096 in coredata instead of keychain

I have generated Public and Private keys using RSA4096 and not stored into keychain by setting kSecAttrIsPermanent to false.
Now I am trying to store SecKey into core data and I am not able to convert SecKey to NSData. Please suggest
let privateKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String:"com.RSAKey.MyApp" as AnyObject
]
// public key parameters
let publicKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String:"com.RSAKey.MyApp" as AnyObject
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 4096 as AnyObject,
kSecPublicKeyAttrs as String: publicKeyParams as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject,
]
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)
You can use SecKeyCopyExternalRepresentation() to convert SecKey into Data.

Resources