The return value for method
public func CC_SHA256(_ data: UnsafeRawPointer!, _ len: CC_LONG, _ md: UnsafeMutablePointer<UInt8>!) -> UnsafeMutablePointer<UInt8>!
comes as incorrect. For the same input data, different hash values are output containing lot of empty data. This only happens in release configuration.
The below code easily reproduces the issue when run in Release configuration. Release configuration is crucial here since it is not visible in Debug configuration. The issue is also visible when not run concurrently (in a single thread) but the frequency of occurrence is very less. The below code is not the usage but concurrent execution helps to create the issue easily
func sha256(data: Data) -> Data {
var digestBytes = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
let buffer = CC_SHA256(Array(data), UInt32(data.count), &digestBytes)!
return Data(bytes: buffer, count: Int(CC_SHA256_DIGEST_LENGTH))
}
func testHashMismatch() {
let rawInput = "Hello World!" // can be any input
let input = Data(rawInput.utf8)
let inputHash = self.sha256(data: input)
print("inputHash: \(inputHash.base64EncodedString())")
DispatchQueue.global(qos: .background).async {
while (true) {
let computedHash = self.sha256(data: input)
if computedHash != inputHash {
print("Value Mis-match !!! \(computedHash.base64EncodedString())")
}
}
}
DispatchQueue.global(qos: .background).async {
while (true) {
let computedHash = self.sha256(data: input)
if computedHash != inputHash {
print("Value Mis-match !!! \(computedHash.base64EncodedString())")
}
}
}
}
sample output:
inputHash: f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=
Value Mis-match !!! f4OxZX/x/FO5LcGBAAAAAAAAAAAAAAAAAAAAAAAAAAA=
Value Mis-match !!! f4OxZX8AAAAAAAAAAAAAAPwtSx+j1ncoSt3SABJtkGk=
Value Mis-match !!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
Value Mis-match !!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
iOS: 13.x, 14.x
Swift: 5.3
The loop can be run for some value around 100,000 iterations but the mismatch occurrence varies and first occurrence is seen to vary from tens to hundreds to thousands, ten thousands of execution.
However, if the passed in value is used in the sha256(:) method defined above, the issue is not seen and behavior is as expected.
return Data(bytes: digestBytes, count: Int(CC_SHA256_DIGEST_LENGTH))
The only documentation for CC_SHA256 from Apple seems to be CC_SHA256 - https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CC_SHA256.3cc.html which states that return value should be same as the passed in pointer.
All routines return 1 except for the one-shot routines ( CC_SHA1(), etc.), which return the pointer passed in via the md parameter.
PS: This code has been working for a long time but started giving this issue few months back.
Would like to know if a bug needs to be reported and what is the recommendation for the usage?
Post
Replies
Boosts
Views
Activity
We use biometricID (faceID/touchID) authentication to access to a secret stored in keychain.
We create the access control object with the biometryCurrentSet option as shown to make sure if FaceID / TouchID changes the entry should be invalidated.
let secAccessControlObj = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .biometryCurrentSet, accessControlError)
Below is the set and get query,
Set Query:
[String(kSecClass): kSecClassGenericPassword,
String(kSecAttrAccount): group as AnyObject,
String(kSecAttrService): service as AnyObject,
String(kSecUseAuthenticationUI) : kSecUseAuthenticationUIAllow as AnyObject,
String(kSecAttrAccessControl) : secAccessControlObj,
String(kSecValueData) : value as AnyObject,
String(kSecAttrCreationDate) : Date() as AnyObject]
Get Query:
[String(kSecClass): kSecClassGenericPassword,
String(kSecAttrAccount): group as AnyObject,
String(kSecAttrService): service as AnyObject,
String(kSecUseAuthenticationUI) : kSecUseAuthenticationUIAllow as AnyObject,
String(kSecAttrAccessControl) : secAccessControlObj,
String(kSecValueData) : value as AnyObject,
String(kSecAttrCreationDate) : Date() as AnyObject]
Steps:
Set the value in keychain using the set query above
Reset the faceID
Use the get query to get the value from keychain by authenticating against TouchID/ FaceID.
Result:
When we try to get the value from keychain using SecItemCopyMatching(query as CFDictionary, result) we get the error code errSecAuthFailed (-25293) on iOS 15.
Analysis:
Prior to this (iOS 14 and below) the error code would be errSecItemNotFound which makes more sense.
This is an issue for iOS 15 only as we also get errSecAuthFailed when user backgrounds the app while authenticating with FaceID/TouchID. This creates a ambiguity for us.
In our testing when we backgrounded the app while authentication is in progress, we found the actual call to SecItemCopyMatching(::) was made when app's state was actually active but when the call returned the state had become background and the error code was again errSecAuthFailed
This seems to be a bug with iOS 15 as it creates a ambiguity for the caller. I think the error code returned after resetting faceID should still be errSecItemNotFound in which case we can know the secret is actually lost since FaceID is reset and can treat errSecAuthFailed as error where the secret is actually not lost but just that failed temporarily.
Please let us know if we need to file a bug