agens-no / EllipticCurveKeyPair

Sign, verify, encrypt and decrypt using the Secure Enclave
Other
709 stars 115 forks source link

iPad Mini 2 Crash #25

Closed laszlo-major closed 6 years ago

laszlo-major commented 6 years ago

I'm getting a crash when trying to generate keys on an iPad Mini 2 running 11.0.0 with the log:

crash_info_entry_0 Fatal error: 'try!' expression unexpectedly raised an error: EllipticCurveKeyPair.EllipticCurveKeyPair.Error.underlying(message: "Could not generate keypair. Security probably doesn\'t like the access flags you provided. Specifically if this device doesn\'t have secure enclave and you pass .privateKeyUsage. it will produce this error.", error: Error Domain=NSOSStatusErrorDomain Code=-25293 "Could not generate keypair. Security probably doesn't like the access flags you provided. Specifically if this device doesn't have secure enclave and you pass .privateKeyUsage. it will produce this error." UserInfo={NSLocalizedRecoverySuggestion=See https://www.osstatus.com/search/results?platform=all&framework=all&search=-25293, NSLocalizedDescription=Could not generate keypair. Security probably doesn't like the access flags you provided. Specifically if this device doesn't have secure enclave and you pass .privateKeyUsage. it will produce this error.}): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-902.0.48/src/swift/stdlib/public/core/ErrorType.swift, line 184

This device does not have touchID from what I see, so I guess also no secure enclave?

The config I'm using is:

static let handler: EllipticCurveKeyPair.Manager = { EllipticCurveKeyPair.logger = { DDLogDebug($0) } let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: []) let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: { return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence] }()) let config = EllipticCurveKeyPair.Config( publicLabel: PUBLIC_KEY_LABEL, privateLabel:PRIVATE_KEY_LABEL, operationPrompt: "Decrypt message", publicKeyAccessControl: publicAccessControl, privateKeyAccessControl: privateAccessControl, token: .secureEnclaveIfAvailable) return EllipticCurveKeyPair.Manager(config: config) }()

Running latest commit of master.

laszlo-major commented 6 years ago

It seems the it being an iPad mini and the doubts about secure enclave were a bit misleading - I can reproduce the crash with any device after factory resetting it end NOT setting a passcode. I'm confused about why this would matter, if I'm not using the 'kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly' flag. Changing protection on the private access to 'kSecAttrAccessibleAlwaysThisDeviceOnly' also makes no difference.

hfossli commented 6 years ago

Hey! Thanks! Awesome feedback.

Do you have the stack trace of the crash? Why is there a «bang» («try!»)?

laszlo-major commented 6 years ago

The crash is actually just the symptom as I prefer failing fast, and I would have never noticed otherwise in a dev environment where signing isn't validated properly, but proceeding without the keys is not viable.

The failure happens at in the library

let status = SecKeyGeneratePair(query as CFDictionary, &publicOptional, &privateOptional)

With the query

2018-04-19 09:17:09:127 App[330:27259] SecItemCopyMatching: ["kcls": 0, "class": keys, "labl": "no.key.public.3", "r_Ref": true] 2018-04-19 09:17:27:617 OnlineBank[330:27259] SecKeyGeneratePair: ["bsiz": 256, "type": "73", "private": ["u_AuthUI": u_AuthUIA, "u_AuthCtx": <LAContext: 0x1c4877600>, "labl": "no.key.private.3", "perm": true, "accc": <SecAccessControlRef: 0x1c02269e0>], "public": ["labl": "no.key.public.3", "pdmn": dku]]

I'm a bit confused as to how and why this work in the simulator without any security with the .userPresence flag, but not on devices. And "hasSecureEnclave" is actually returning false on an iPhone 7 without passcode, because I guess LAContext can't evaluate anything in this state.

Personally, I don't understand why anyone would use a phone without a passcode, but it's getting pretty annoying that I can't make it work regardless :)

NOTE: if I force .privateKeyUsage AND .secureEnclave as token, it succeeds. It only fails with .secureEnclaveIfAvailable because in that case it switches to keychain, as 'hasTouchId' evaluates to false.

hfossli commented 6 years ago

Thanks again for sharing your insight. I haven’t tested on every device with various setups so this is very valuable.

In this case, where user doesnt have any passcode, what makes sense for your application? In my case we demand passcode for security reasons.

laszlo-major commented 6 years ago

Hm, I guess warning and blocking the user, demanding a passcode would be fine, especially since there's a very small percentage of users with devices with no passcode, and also no secure enclave, I would assume.

So in conclusion, there's nothing wrong with the library, except maybe the check for 'hasSecureEnclave' actually returning false on devices without passcode.

Thanks for the nice library, it really makes the iOS crypto more palatable!

hfossli commented 6 years ago

I think it is possible to ask for a user given password even though the device hasn't any passcode. Instead of .userPresence you may use .applicationPassword.

On way the library could solve these problems is by giving fallback mechanisms if some configuration isn't working. Rough example

static let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: .firstUnlockThisDeviceOnly, flagsByPriority: [
    [.userPresence, .privateKeyUsage],
    [.userPresence],
    [.applicationPassword],
])
hfossli commented 6 years ago

Or maybe

struct Shared {
    static let config = EllipticCurveKeyPair.Config(
        publicLabel: "no.agens.sign.public",
        privateLabel: "no.agens.sign.private",
        operationPrompt: "Sign transaction",
        publicKeyAccessControl: [
            EllipticCurveKeyPair.AccessControl(.accessibleAlwaysThisDeviceOnly, flags: [])
        ],
        privateKeyAccessControl: [
            EllipticCurveKeyPair.AccessControl(.firstUnlockThisDeviceOnly, flagsByPriority: [.userPresence, .privateKeyUsage]),
            EllipticCurveKeyPair.AccessControl(.firstUnlockThisDeviceOnly, flagsByPriority: [.userPresence])
            EllipticCurveKeyPair.AccessControl(.firstUnlockThisDeviceOnly, flagsByPriority: [.applicationPassword])
        ],
        token: .secureEnclaveIfAvailable)
    static let keypair: EllipticCurveKeyPair.Manager = {
        EllipticCurveKeyPair.logger = { print($0) }
        return EllipticCurveKeyPair.Manager(config: config)
    }()
}