I encountered an issue with creating SecKey from NSData. Basically my client-server communication is based on signature created with private key and verified on the server with public key.
I am implementing session transfer between two devices and in order to continue communication I need those keys to be transferred as well. I am converting SecKey to NSData and sending it via bluetooth, but on other side I cannot convert NSData to SecKey back to use encryption.
Could you help please?
More complete example (swift 4, iOS 10+) - assuming you have a Base64 encoded string. Note that the other side of the connection needs to also be creating key payloads using the same format (i.e. RSA - PKCS #1, also verify key size ). This function handles public or private keys (security caveats omitted for brevity).
// Extract secKey from encoded string - defaults to extracting public keys
func decodeSecKeyFromBase64(encodedKey: String, isPrivate: Bool = false) -> SecKey? {
var keyClass = kSecAttrKeyClassPublic
if isPrivate {
keyClass = kSecAttrKeyClassPrivate
}
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: keyClass,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
guard let secKeyData = Data.init(base64Encoded: encodedKey) else {
print("Error: invalid encodedKey, cannot extract data")
return nil
}
guard let secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else {
print("Error: Problem in SecKeyCreateWithData()")
return nil
}
return secKey
}
Starting from iOS 10 you can use the following code:
let attributes: [String:Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: blockSize,
]
secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil)
what I use with success ...
func obtainKeyData(tag: String) -> NSData? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnData): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let result: NSData?
switch SecItemCopyMatching(query, &keyRef) {
case noErr:
result = keyRef as? NSData
default:
result = nil
}
return result
}
func insertPublicKey(publicTag: String, data: NSData) -> SecKeyRef? {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): publicTag as CFStringRef,
String(kSecValueData): data as CFDataRef,
String(kSecReturnPersistentRef): true as CFBooleanRef]
var persistentRef: AnyObject?
let status = SecItemAdd(query, &persistentRef)
if status != noErr && status != errSecDuplicateItem {
return nil
}
return obtainKey(publicTag)
}
func obtainKey(tag: String) -> SecKey? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnRef): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let status = SecItemCopyMatching(query, &keyRef)
switch status {
case noErr:
if let ref = keyRef {
return (ref as! SecKeyRef)
}
default:
break
}
return nil
}
There is no easy way to transfer private part of the key pair( it is possible, but try to avoid it )
Related
I am looking for a way to share account credentials like userID and accessToken between my iOS app and my WatchOS app. As i understand i can use the WatchConnectivity framework but that does not seem reliable. For example if my iOS app is killed i could not find a way to force wake the app to fetch the data.
The other think that i tried was keychain sharing which also does not seem to work and gives
keyStore.retrieve SecItemCopyMatching error -25300
error
Below is the code that i am using, which i got from this tutorial.
class KeyStore {
let account = "accessToken"
let group = "[TeamID].[BundleID]" //Setup same as in keychain sharing options in capabilities
func store(token : String) {
let data = token.data(using: .utf8)!
let addquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: account,
kSecValueData as String: data,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
SecItemDelete(addquery as CFDictionary)
let status : OSStatus = SecItemAdd(addquery as CFDictionary, nil)
guard status == errSecSuccess else {
os_log("store: whoops")
return
}
}
func clear() {
let addquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
SecItemDelete(addquery as CFDictionary)
}
func retrieve() -> String? {
let getquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
os_log("keyStore.retrieve SecItemCopyMatching error \(status)")
return nil
}
guard let data = item as? Data? else {
os_log("keyStore.retrieve not data")
return nil
}
return String(data: data!, encoding: String.Encoding.utf8)
}
func getAllKeychainItems() throws {
let classes = [kSecClassGenericPassword as String, // Generic password items
kSecClassInternetPassword as String, // Internet password items
kSecClassCertificate as String, // Certificate items
kSecClassKey as String, // Cryptographic key items
kSecClassIdentity as String,
kSecAttrAccount as String] // Identity items
classes.forEach { secClass in
let items = getAllKeyChainItemsOfClass( secClass )
NSLog(items.description)
}
}
func getAllKeyChainItemsOfClass(_ secClass: String) -> [String: AnyObject] {
let query: [String: Any] = [
kSecClass as String : secClass,
kSecReturnData as String : true,
kSecReturnAttributes as String : true,
kSecReturnRef as String : true,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String: AnyObject]()
if lastResultCode == noErr {
let array = result as? Array<Dictionary<String, Any>>
for item in array! {
if let key = item[kSecAttrAccount as String] as? String,
let value = item[kSecValueData as String] as? Data {
values[key] = String(data: value, encoding:.utf8) as AnyObject?
}
else if let key = item[kSecAttrLabel as String] as? String,
let value = item[kSecValueRef as String] {
values[key] = value as AnyObject
}
}
}
return values
}
}
Any references that could help me share login credentials even when the app is killed would really help
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.
I want to validate or verify JWT signature.
I don't succeed to use SecKey, to use SecKeyVerifySignature function.
Here is my code:
let parts = session.token.components(separatedBy: ".")
let header = parts[0]
let payload = parts[1]
let signature = Data(base64Encoded: parts[2], options: .ignoreUnknownCharacters)
let pubKey = "-----BEGIN PUBLIC KEY-----xxxxxxxx/xxxxxxxx/xxxxxxxx/xxxxxxxx/xxxxxxx-----END PUBLIC KEY-----"
let encodedPubKey = Data(pubKey.utf8).base64EncodedString()
let attributes: [String:Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
let secKey = decodeSecKeyFromBase64(encodedKey: encodedPubKey)
let signingInput = (header + "." + payload).data(using: .ascii)!
let validSignature = SecKeyVerifySignature(secKey!, .rsaSignatureMessagePKCS1v15SHA256, signingInput as CFData, signature as! CFData, nil)
and function decodeSecKeyFromBase64:
// Extract secKey from encoded string - defaults to extracting public keys
func decodeSecKeyFromBase64(encodedKey: String, isPrivate: Bool = false) -> SecKey? {
var keyClass = kSecAttrKeyClassPublic
if isPrivate {
keyClass = kSecAttrKeyClassPrivate
}
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: keyClass,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
guard let secKeyData = Data.init(base64Encoded: encodedKey) else {
print("Error: invalid encodedKey, cannot extract data")
return nil
}
guard let secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else {
print("Error: Problem in SecKeyCreateWithData()")
return nil
}
return secKey
}
I don't know if I have to encode pubKey or not before to valid signature. It prints Error: Problem in SecKeyCreateWithData() from decodeSecKeyFromBase64
I am trying to upgrade the code that I got from this answer for generating CSR, from Swift 2 to Swift 3.
I have most of the code upgraded, but the following code in the Utility block of the original answer failed with the error:
'init' is unavailable: use 'withMemoryRebound(to:capacity:_)' to temporarily view memory as another layout-compatible type.
The error occurs at the line:
let status: OSStatus = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
func loadKeySecKeyFromKeyChain(key: String) -> SecKey{
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE as AnyObject,
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): key as AnyObject,
kSecReturnRef as String : kCFBooleanTrue ]
var dataTypeRef: Unmanaged<AnyObject>? = nil
var resultData: SecKey? = nil
let status: OSStatus = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
NSLog("SecItemCopyMatching: " + status.description)
if status == errSecSuccess {
NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
resultData = (dataTypeRef!.takeRetainedValue() as! SecKey)
NSLog("SecItemCopyMatching returns SecKey: " + resultData.debugDescription)
return resultData!
} else {
return resultData!
}
}
I have been stuck on this for a whole day, is there any suggestions for how to resolve this error?
Just use SecItemCopyMatching. I was able to convert it to Swift 3 and successfully generate CSR.
// Finds the SecKeyRef corresponding to the parameter key and returns it
func loadKeySecKeyFromKeyChain(key: String) -> SecKey {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE as AnyObject,
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): key as AnyObject,
kSecReturnRef as String : kCFBooleanTrue ]
var dataTypeRef: Unmanaged<AnyObject>? = nil
var resultData: SecKey? = nil
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess {
resultData = result as! SecKey
return resultData!
} else {
return resultData!
}
}
I'm attempting to do the following in my iOS application:
Generate a key pair using SecKeyGeneratePair, storing the private key in the Secure Enclave
Sign some data using the private key
It works if I hang on to the private key reference when keys are first generated, but does not work if I attempt to retrieve the reference from the Keychain after discarding the initial pointer.
The keys are generated like this:
func generateKeyPair() -> Bool {
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag : privateTag,
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : publicTag
] as NSDictionary
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
return err == noErr
}
The signing method is below:
func signData(plainText: Data) -> NSData? {
guard privateKey != nil else {
print("Private key unavailable")
return nil
}
let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
var signatureLength = 128
let err = SecKeyRawSign(privateKey!,
.PKCS1SHA1,
[UInt8](digestToSign),
Int(CC_SHA1_DIGEST_LENGTH),
signature,
&signatureLength)
print("Signature status: \(err)")
let sigData = NSData(bytes: signature, length: Int(signatureLength))
return sigData
}
func sha1DigestForData(data: NSData) -> NSData {
let len = Int(CC_SHA1_DIGEST_LENGTH)
let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
CC_SHA1(data.bytes, CC_LONG(data.length), digest)
return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}
This asks for my fingerprint and works flawlessly. I then use another method to get the key reference from the Keychain:
func getPrivateKeyRef() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String: privateTag,
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
print("Get key status: \(status)")
if status == errSecSuccess { return ref as! SecKey? } else { return nil }
}
The SecItemCopyMatching returns a success status, but attempting to use the resulting SecKey item as a private key in SecKeyRawSign results in error -25293 Authorization/Authentication failed. This status only appears after I provide my fingerprint, so the actual fingerprint verification succeeds, but the key somehow remains unusable.
What is the correct way to use a key stored in Secure Enclave to sign data?
Looks like using kSecAttrLabel attribute is necessary for correct fetching of the key. It must be specified when generating the keys, and when fetching them through SecItemCopyMatching.
My working solution for fetching the private key:
func getPrivateKey() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
if status == errSecSuccess {
return (ref as! SecKey)
}
return nil
}
Where the key pair was generated like this:
var publicKey:SecKey?
var privateKey:SecKey?
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag as String : "publicTag",
kSecAttrLabel as String : "publicTag",
] as NSDictionary
// only 256 bit EC keys are supported in the Secure Enclave
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)