SecKeyCopyKeyExchangeResult does not work as nodejs ecdh in iOS 13 - ios

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

Related

Certificate public key missing some bytes when parsed using iOS vs Android

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

How to extract certificate and private key PEM strings from PKCS#12 file using swift

I have a use case where the user gives a PKCS#12 file and its password as input, and I need both the certificate's and the private key's PEM strings.
I have managed to create a SecIdentity using the p12, and its documentation says:
A SecIdentity object contains a SecKey object and an associated SecCertificate object.
So, I believe I am in the right path, but I couldn't find a way of extracting the SecKey and the SecCertificate from this SecIdentity.
I have also not found a way to get a PEM string from a SecKey or SecCertificate, but that would only be the last step.
This is the code I've used to create the identity:
let key: NSString = kSecImportExportPassphrase as NSString
let options: NSDictionary = [key : p12Password]
var rawItems: CFArray?
let p12Data = try! Data(contentsOf: p12FileUrl)
let data = p12Data! as NSData
let cfdata = CFDataCreate(kCFAllocatorDefault, data.bytes.assumingMemoryBound(to: UInt8.self), data.length)!
let result: OSStatus = SecPKCS12Import(cfdata, options, &rawItems)
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
}
-- UPDATE --
I made some more progress, and managed to extract the certificate in a DER format (which I haven't tried to convert to PEM so far - I believe it should be easy), but I still have no idea how to get the private key.
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
var cert: SecCertificate?
SecIdentityCopyCertificate(identity, &cert)
var certDer = SecCertificateCopyData(cert!) //DER format
var key: SecKey?
SecIdentityCopyPrivateKey(identity, &key)
let keyDict = SecKeyCopyAttributes(key!) //Not sure what we can find here
}

IOS not able to create privatekey if only devicecode is set on device?

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

Migrate from unencrypted realm to encrypted realm

I'm trying to migrate from unencrypted realm to encrypted but I don't know how and where to use Realm().writeCopy(toFile: url, encryptionKey: key).
or even if there is another way to do it.
Thank you.
I found a way to do that, you can find it below:
private static var realm: Realm! {
// Get the encryptionKey
var realmKey = Keychain.realmKey
if realmKey == nil {
var key = Data(count: 64)
key.withUnsafeMutableBytes { (bytes) -> Void in
_ = SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
realmKey = key
Keychain.realmKey = realmKey
}
// Check if the user has the unencrypted Realm
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileManager = FileManager.default
let unencryptedRealmPath = "\(documentDirectory)/default.realm"
let encryptedPath = "\(documentDirectory)/default_new.realm"
let isUnencryptedRealmExsist = fileManager.fileExists(atPath: unencryptedRealmPath)
let isEncryptedRealmExsist = fileManager.fileExists(atPath: encryptedPath)
if isUnencryptedRealmExsist && !isEncryptedRealmExsist {
let unencryptedRealm = try! Realm(configuration: Realm.Configuration(schemaVersion: 7))
// if the user has unencrypted Realm write a copy to new path
try? unencryptedRealm.writeCopy(toFile: URL(fileURLWithPath: encryptedPath), encryptionKey: realmKey)
}
// read from the new encrypted Realm path
let configuration = Realm.Configuration(fileURL: URL(fileURLWithPath: encryptedPath), encryptionKey: realmKey, schemaVersion: 7, migrationBlock: { migration, oldSchemaVersion in })
return try! Realm(configuration: configuration)
}
According to #Abedalkareem Omreyh's answer
You can also Retrieve the existing encryption key for the app if it exists or create a new one.
func getencryptionKey() -> Data {
// Identifier for our keychain entry - should be unique for your application
let keychainIdentifier = "io.Realm.EncryptionExampleKey"
let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!
// First check in the keychain for an existing key
var query: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecReturnData: true as AnyObject
]
// To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
// See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
// swiftlint:disable:next force_cast
return dataTypeRef as! Data
}
// No pre-existing key from this application, so generate a new one
// Generate a random encryption key
var key = Data(count: 64)
key.withUnsafeMutableBytes({ (pointer: UnsafeMutableRawBufferPointer) in
let result = SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!)
assert(result == 0, "Failed to get random bytes")
})
// Store the key in the keychain
query = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecValueData: key as AnyObject
]
status = SecItemAdd(query as CFDictionary, nil)
assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
return key
}
// ...
// Use the getKey() function to get the stored encryption key or create a new one
var config = Realm.Configuration(encryptionKey: getKey())
do {
// Open the realm with the configuration
let realm = try Realm(configuration: config)
// Use the realm as normal
} catch let error as NSError {
// If the encryption key is wrong, `error` will say that it's an invalid database
fatalError("Error opening realm: \(error)")
}
you can use this encryption Key for above code and don't need to check nil value of key.
// Get the encryptionKey
var realmKey = getencryptionKey()
Reference link Encrypt a Realm

RSA Encryption Failing on just the iPhone 4s running iOS 9

I am successfully RSA-Encrypting a string but when i do it on iPhone 4s (iOS 9.3.2) it fails and returns 'nil' as a result. However it is successfully working on all the other iphones(5,6,7,8,X.)
I am using this RSA Public-Key:
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClTlHEResIvOPHR0+o4exJVEI5RQ4NnBBXV9tdoCbqavSgsiuFtZWn5RUVTLb0h7ULpOh8GDcu0yI4lnpMVDZ5U2w0ra2/BNl6XDt9bwwoOh5w2lsdVmdP94t/qVBX4C0OcXw+RdSD1pshucTO7m2YLxtzLuc4ChUwjWZXVEoHdQIDAQAB"
It is actually on this line of code where i am getting &keyRef 'nil'
err = SecItemCopyMatching(dictionary as CFDictionary, &keyRef);
Here is my code;
func encryptString(stringToEncrypt:String) -> String {
print("stringToEncrypt64 = " + stringToEncrypt)
let keyData = NSData(base64Encoded: Constants.RSA_Public_Key, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)
let dictionary: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrApplicationTag: "HBLMobilePublicKeyTag" as AnyObject,
kSecValueData: keyData!,
kSecAttrKeySizeInBits: NSNumber(value: 1024),
kSecReturnRef: true as AnyObject
];
var err = SecItemAdd(dictionary as CFDictionary, nil);
if ((err != noErr) && (err != errSecDuplicateItem)) {
print("error loading public key");
}
var keyRef: AnyObject?;
var base64String: String?
err = SecItemCopyMatching(dictionary as CFDictionary, &keyRef);
if (err == noErr) {
if let keyRef = keyRef as! SecKey? {
let plaintextLen = stringToEncrypt.lengthOfBytes(using: String.Encoding.utf8);
let plaintextBytes = [UInt8](stringToEncrypt.utf8);
var encryptedLen: Int = SecKeyGetBlockSize(keyRef);
var encryptedBytes = [UInt8](repeating: 0, count: encryptedLen);
err = SecKeyEncrypt(keyRef, SecPadding.PKCS1, plaintextBytes, plaintextLen, &encryptedBytes, &encryptedLen);
let data = NSData(bytes: encryptedBytes, length: encryptedBytes.count)
base64String = data.base64EncodedString(options: [])
}
}
SecItemDelete(dictionary as CFDictionary);
return base64String!
}
maybe it's that the iPhone 4s is out of date. If the versions are the same like all phones on ios-9 then it could be the hardware on the phone. Or it could be that you don't have enough space to store the application on the phone for it to work. It could be that half the files are on the phone and that it says Completed Transfer When it didn't complete it.
Sorry, I'm just saying what I know (Which I know nothing about IPhones) about software.

Resources