HiI recently updated to Xcode 11 GM seed. However I've noticed that the dreaded "Unknown class in Interface Builder file" is crashing my app. I haven't changed any class names or storyboards. Interestingly the app runs perfectly in teh simulator, but crashed on my phone.Here is what is being printed in the output window:MyAppName[9513:4230222] Unknown class _TtC10MyApp24SlideTableViewController in Interface Builder file.Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).MyAppName[9513:4230222] Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).I've deleted the class and recreated, removed the View Controller from the story board, made sure the view controller is references correctly as is the target, but the problem persists and I'm out of ideas.Is there a "reset" of the storyboard to reference the elements? Or some other way to resolve this?Many thanksCraig
Post
Replies
Boosts
Views
Activity
HiI'm using the new CryptoKit to generate a 6 or 8 digit TOTP code. Anyone been successful doing this?Using Xcode 11 BETA 5, targeting iOS 13 and Swift 5.1. Here is a snippet of generating an TOTP via CommonCrypto versus CryptoKit in playground (BETA). The base32Decode function returns Data.import CryptoKit
import CommonCrypto
import Foundation
let period = TimeInterval(30)
let digits = 6
let secret = base32Decode(value: "5FAA5JZ7WHO5WDNN")!
var counter = UInt64(Date().timeIntervalSince1970 / period).bigEndian
func cryptoKitOTP() {
// Generate the key based on the counter.
let key = SymmetricKey(data: Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)))
let hash = HMAC<Insecure.SHA1>.authenticationCode(for: secret, using: key)
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.byteCount - 1] & 0x0f
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CryptoKit OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func commonCryptoOTP() {
let key = Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter))
let (hashAlgorithm, hashLength) = (CCHmacAlgorithm(kCCHmacAlgSHA1), Int(CC_SHA1_DIGEST_LENGTH))
let hashPtr = UnsafeMutablePointer.allocate(capacity: Int(hashLength))
defer {
hashPtr.deallocate()
}
secret.withUnsafeBytes { secretBytes in
// Generate the key from the counter value.
counterData.withUnsafeBytes { counterBytes in
CCHmac(hashAlgorithm, secretBytes.baseAddress, secret.count, counterBytes.baseAddress, key.count, hashPtr)
}
}
let hash = Data(bytes: hashPtr, count: Int(hashLength))
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.count - 1] & 0x0F
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CommonCrypto OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func otp() {
commonCryptoOTP()
cryptoKitOTP()
}
otp()The output based on now as in 2:28pm is: CommonCrypto OTP value: 819944 CryptoKit OTP value: 745890To confirm the OTP value, I used oathtool which you can brew install to generate an array of TOTP's. For example:oathtool --totp --base32 5FAA5JZ7WHO5WDNN -w 10Craig
HiWhat is the best way to determine if an iPhone or iPad has a biometry sensor? The user may or may not have registered their face or fingerprint, regardless I would like to know what biometry sensor is on the device. My app targets iOS 11, so it has to be either Touch or Face.Many thanksCraig
HiI have created a private key in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. When I attempt to access the key to perform a signing operation, the Touch ID dialog will sometimes appear, but in most cases I have to touch the biometry sensor, then the dialog is displayed. Here's the code I'm using to create the key and access the key in a sign operation.public static func create(with name: String, authenticationRequired: SecAccessControlCreateFlags? = nil) -> Bool
{
guard !name.isEmpty else
{
return false
}
var error: Unmanaged<CFError>?
// Private key parameters
var privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: name
]
// If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
if authenticationRequired != nil
{
guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
{
return false
}
privateKeyParams[kSecAttrAccessControl as String] = access
}
// Global parameters for our key generation
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
// Generate the keys.
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
{
return false
}
// Private key created!
return true
}This is the code to sign the data that should prompt for the biometry sensor (Touch ID or Face ID).public static func sign(using name: String, value: String, localizedReason: String? = nil, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
{
guard !name.isEmpty else
{
return nil
}
guard !value.isEmpty else
{
return nil
}
// Check if the private key exists in the chain, otherwise return
guard let privateKey: SecKey = getPrivateKey(name, localizedReason: localizedReason ?? "") else
{
return nil
}
let data = value.data(using: .utf8)!
var error: Unmanaged<CFError>?
guard let signedData = SecKeyCreateSignature(privateKey,
rsaSignatureMessagePKCS1v15SHA512,
data as CFData,
&error) as Data? else
{
return nil
}
return signedData.base64EncodedString(options: base64EncodingOptions)
}
fileprivate static func getPrivateKey(_ name: String, localizedReason: String) -> SecKey?
{
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag as String: name,
kSecReturnRef as String: true,
kSecUseOperationPrompt as String : localizedReason
]
var item: CFTypeRef? = nil
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else
{
if status == errSecUserCanceled
{
print("\tError: Accessing private key failed: The user cancelled (%@).", "\(status)")
}
else if status == errSecDuplicateItem
{
print("\tError: The specified item already exists in the keychain (%@).", "\(status)")
}
else if status == errSecItemNotFound
{
print("\tError: The specified item could not be found in the keychain (%@).", "\(status)")
}
else if status == errSecInvalidItemRef
{
print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%@).", "\(status)")
}
else
{
print("\tError: Accessing private key failed (%@).", "\(status)")
}
return nil
}
return (item as! SecKey)
}Then in my app, I would simply call guard let result = sign("mykey", "helloworld") else
{
print("failed to sign")
return
}
print(result)So the getPrivateKey function is the one that calls SecKeyCopyingMatching, the 3 methods are in a helper class; what's the best approach to reliabably display the biometry dialog?Thanks
HiUsing the newer methods in the SDK for iOS 10, I can generate private and public keys, with the private key residing in the Keychain. The public key is exported.Here's my code thus far: func createKeyPair(_ keyName: String, authenticationRequired: SecAccessControlCreateFlags? = nil, completion: (_ success: Bool, _ publicKeyData: Data?) -> Void)
{
guard !keyName.isEmpty else
{
NSLog("\tNo keyname provided.")
return completion(false, nil)
}
var error: Unmanaged<CFError>?
// Private key parameters
var privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: keyName
]
// If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
if authenticationRequired != nil
{
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
{
NSLog("\tError: %@", error!.takeRetainedValue().localizedDescription)
completion(false, nil)
return
}
privateKeyParams[kSecAttrAccessControl as String] = accessControl
}
/
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
// Global parameters for our key generation
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
{
NSLog("\tError generating keypair. %@", "\(error!.takeRetainedValue().localizedDescription)")
return completion(false, nil)
}
// Generate the keys.
guard let publicKey = SecKeyCopyPublicKey(privateKey) else
{
NSLog("\tError obtaining public key.")
return completion(false, nil)
}
// Get the public key.
guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil) else
{
NSLog("\tError obtaining export of private key.")
return completion(false, nil)
}
print("\nPrivate key: \(String(describing: exportPublicKey(privateKeyData as Data)))")
// Extract the public key for export.
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) else
{
NSLog("\tError obtaining export of public key.")
return completion(false, nil)
}
completion(true, publicKeyData as Data)
}
public func exportPublicKey(_ rawPublicKeyBytes: Data, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
{
return rawPublicKeyBytes.base64EncodedString(options: base64EncodingOptions)
}
// Call the function like so.
_ = createKeyPair(keyName)
{
(status, data) in
if status
{
print("exporting public key: \(String(describing: exportPublicKey(data!)))")
}
}If I've understood the documentation, SecKeyCopyExternalRepresentation says that the method returns data in the PCKS #1 format for an RSA key. From the various forums, I'm lead to beleive that simply base64 encoding to a string the output of SecKeyCopyExternalRepresentation is all that is required to export the public key in PEM format (without BEGIN RSA PUBLIC KEY and END RSA PUBLIC KEY)When the public key is used to validate some signed data in a Java app, the public key fails to load with invalid key errors... Anyone provide some guidance on this?Thanks