Code Monkey home page Code Monkey logo

ellipticcurvekeypair's Introduction

Elliptic Curve Key Pair

Sign, verify, encrypt and decrypt using the Secure Enclave on iOS and MacOS.

macOS 10.12.1 support iOS 9 support Swift 3 and 4 support Carthage compatible Cocoapods compatible

Features

  • create a private public keypair
  • store the private key on the secure enclave
  • store the public key in keychain
  • each time you use the private key the user will be prompted with FaceID, TouchID, device pass code or application password
  • export the public key as X.509 DER with proper ASN.1 header / structure
  • verify the signature with openssl in command line easily

Supports FaceID, TouchID, device pass code and application password.

About

Using the Security Framework can be a little bit confusing. That’s why I created this. You may use it as example code and guidance or you may use it as a micro framework.

I found it tricky to figure out how to use the SecKeyRawVerify, SecKeyGeneratePair and SecItemCopyMatching C APIs in Swift 3, but the implementation is quite straight forward thanks to awesome Swift features.

Installation

Manual

Just drag Sources/EllipticCurveKeyPair.swift and Sources/SHA256.swift file into your Xcode project.

Cocoapods

pod EllipticCurveKeyPair

Carthage

github "agens-no/EllipticCurveKeyPair"

Use cases

There are lots of great possibilities with Secure Enclave. Here are some examples

Encrypting

  1. Encrypt a message using the public key
  2. Decrypt the message using the private key – only accessible with FaceID / TouchID / device pin

Only available on iOS 10 and above

Signing

  1. Sign some data received by server using the private key – only accessible with FaceID / TouchID / device pin
  2. Verify that the signature is valid using the public key

A use case could be

  1. User is requesting a new agreement / purchase
  2. Server sends a push with a session token that should be signed
  3. On device we sign the session token using the private key - prompting the user to confirm with touch id
  4. The signed token is then sent to server
  5. Server already is in possession of the public key and verifies the signature using the public key
  6. Server is now confident that user signed this agreement with touch id

Examples

For more examples see demo app.

Creating a keypair manager

struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
        let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
        let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
        let config = EllipticCurveKeyPair.Config(
            publicLabel: "payment.sign.public",
            privateLabel: "payment.sign.private",
            operationPrompt: "Confirm payment",
            publicKeyAccessControl: publicAccessControl,
            privateKeyAccessControl: privateAccessControl,
            token: .secureEnclave)
        return EllipticCurveKeyPair.Manager(config: config)
    }()
}

You can also gracefully fallback to use keychain if Secure Enclave is not available by using different access control flags:

let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
    return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
}())

In that case you need to remember to set token variable in Config object to .secureEnclaveIfAvailable.

Getting the public key in DER format

do {
    let key = try KeyPair.manager.publicKey().data().DER // Data
} catch {
    // handle error
}

See demo app for working example

Getting the public key in PEM format

do {
    let key = try KeyPair.manager.publicKey().data().PEM // String
} catch {
    // handle error
}

Signing

do {
    let digest = "some text to sign".data(using: .utf8)!
    let signature = try KeyPair.manager.sign(digest, hash: .sha256)
} catch {
    // handle error
}

You may also pass a LAContext object when signing

Encrypting

do {
    let digest = "some text to encrypt".data(using: .utf8)!
    let encrypted = try KeyPair.manager.encrypt(digest, hash: .sha256)
} catch {
    // handle error
}

Encrypting on a different device/OS/platform

You can also encrypt on a different device/OS/platform using the public key. If you want to know all the details about how to encrypt in a format the Secure Enclave understands, then you need definitely need to read this great write up by @dschuetz!

https://darthnull.org/security/2018/05/31/secure-enclave-ecies/

Decrypting

do {
    let encrypted = ...
    let decrypted = try KeyPair.manager.decrypt(encrypted, hash: .sha256)
    let decryptedString = String(data: decrypted, encoding: .utf8)
} catch {
    // handle error
}

You may also pass a LAContext object when decrypting

Error handling

The most common thing is to catch error related to

  • Secure Enclave not being available
  • User cancels fingerprint dialog
  • No fingerprints enrolled

With do/catch:

do {
    let decrypted = try KeyPair.manager.decrypt(encrypted)
} catch EllipticCurveKeyPair.Error.underlying(_, let underlying) where underlying.code == errSecUnimplemented {
    print("Unsupported device")
} catch EllipticCurveKeyPair.Error.authentication(let authenticationError) where authenticationError.code == .userCancel {
    print("User cancelled/dismissed authentication dialog")
} catch {
    print("Some other error occurred. Error \(error)")
}

With if let:

if case let EllipticCurveKeyPair.Error.underlying(_, underlying) = error, underlying.code == errSecUnimplemented {
    print("Unsupported device")
} else if case let EllipticCurveKeyPair.Error.authentication(authenticationError), authenticationError.code == .userCancel {
  print("User cancelled/dismissed authentication dialog")
} else {
  print("Some other error occurred. Error \(error)")
}

Debugging

In order to inspect the queries going back and forth to Keychain you may print to console every mutation this library does on Keychain like this

EllipticCurveKeyPair.logger = { print($0) }

Verifying a signature

In the demo app you’ll see that each time you create a signature some useful information is logged to console.

Example output

#! /bin/sh
echo 414243 | xxd -r -p > dataToSign.dat
echo 3046022100842512baa16a3ec9b977d4456923319442342e3fdae54f2456af0b7b8a09786b022100a1b8d762b6cb3d85b16f6b07d06d2815cb0663e067e0b2f9a9c9293bde8953bb | xxd -r -p > signature.dat
cat > key.pem <<EOF
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdDONNkwaP8OhqFTmjLxVcByyPa19
ifY2IVDinFei3SvCBv8fgY8AU+Fm5oODksseV0sd4Zy/biSf6AMr0HqHcw==
-----END PUBLIC KEY-----
EOF
/usr/local/opt/openssl/bin/openssl dgst -sha256 -verify key.pem -signature signature.dat dataToSign.dat

In order to run this script you can

  1. Paste it in to a file: verify.sh
  2. Make the file executable: chmod u+x verify.sh
  3. Run it: ./verify.sh

Then you should see

Verified OK

PS: This script will create 4 files in your current directory.

Keywords

Security framework, Swift 3, Swift 4, Swift, SecKeyRawVerify, SecKeyGeneratePair, SecItemCopyMatching, secp256r1, Elliptic Curve Cryptography, ECDSA, ECDH, ASN.1, Apple, iOS, Mac OS, kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyTypeEC, kSecAttrTokenIDSecureEnclave, LAContext, LocalAuthentication, FaceID, Face ID, TouchID, Touch ID, Application Password, Device Pin, Devicepin

Acknowledgements and credits

TrailOfBits

TrailOfBits published some objective-c code a while back which was to great help! Thanks for sharing Tidas and SecureEnclaveCrypto. They also got some other most interesting projects. Check them out!

Quinn “the Eskimo!”, Apple

He shared som very valuable insights with regards to exporting the public key in the proper DER X.509 format.

SHA256

The SHA256 class (originally SHA2.swift) is found in the invaluable CryptoSwift library by Marcin Krzyżanowski. The class has been heavily altered in order to strip it down to its bare minimum for what we needed in this project.

FAQ

Why am I not being prompted with touch id / device pin on simulator?

The simulator doesn’t possess any secure enclave and therefore trying to access it would just lead to errors. If you set fallbackToKeychainIfSecureEnclaveIsNotAvailable to true then the private key will be stored in keychain on simulator making it easy to test your application on simulator as well.

Where can I learn more?

Check out this video on WWDC 2015 about Security in general or click here to skip right to the section about the Secure Enclave.

Why is it wrapped in an enum?

I try to balance drag-and-drop the files you need into xcode and supporting dependency managers like carthage and cocoapods at the same time. If you have better ideas or don't agree with this decision I'm happy to discuss alternatives :)

Feedback

We would 😍 to hear your opinion about this library. Wether you like or don’t. Please file an issue if there’s something you would like to see improved. You can reach me as @hfossli on twitter and elsewhere. 😀

ellipticcurvekeypair's People

Contributors

0xflotus avatar abjurato avatar alkalim avatar chylis avatar dependabot[bot] avatar dschuetz avatar dwbbytedance avatar haraldson avatar hfossli avatar hfossli-agens avatar keefertaylor avatar kostiakoval avatar raphschwander avatar tommyle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ellipticcurvekeypair's Issues

Possible bug in encryptionEciesEdh: SecKeyAlgorithm?

I'm trying to figure out how to generate a message from another tool (right now, I'm working in python) and then decrypt it inside the demo app. In the process of working all this out (which is far more complicated and poorly-documented than I'd expected :( ), I may have found a pair of typos.

In the encryptionEciesEcdh: SecKeyAlgorithm switch statement, the algorithms returned for .sha256 and .sha384 seem to have been swapped. That is, case .sha256 returns SecKeyAlgorithm....SHA384AESGCM, while .sha384 returns ....SHA256AESGCM.

Document how to use on macOS

Can't quite get the basic examples working on macOS, it's complaining about the parameters being incorrect.

I'll update here my findings when I figure it out.

no prompts shown for face id auth on simulator

Thank you for great library. Learned a lot.

I am trying the sample on iOS simulator via XCode Version 10.1 (10B61). I configured Face ID in simulator. But I can't get Face ID prompt working for both sign or decrypt operations. Has anything changed recently in simulator behavior?

iOS device I selected in simulator: iPhoneXS Max
Xcode version: 10.1 (10B61)
Mac OSX version: macOS High Siera Version 10.13.6

concern about userPresence

The code is

EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]

Will it be able to handle text passcode or should it be only Numeric value? (Other than FaceID if valid)

Publish new version in CocoaPods?

Hi,

The last publish / tagged version of this library was in November 2017 and there's been a lot of updates and helpful functionality pushed since then. While Carthage will let me consume the library from head, CocoaPods won't let another Pod consume an arbitrary version of a library. Is it possible to publish a new version of this library in CocoaPods / Carthage?

You should just be able to update the version numbers in the podspec, run git tag and pod truck push.

Better support for `operationPrompt`

Continuing the discussion from #17.

I think it is a good idea, but I am still conflicted. I don't want to make the API and code more complicated than it is.

I would love to just use the LAContext under the hood, but sadly the localizedReason property was introduced on iOS 11... Thus forcing us to instead send operationPrompt parameter all the way down to the query.

Example of what's needed to change.

Current code

public final class Manager {

    ...1
        
    public func privateKey(context: LAContext? = nil) throws -> PrivateKey {
        ...2
    }
    enum Prompt {
        case context(LAContext)
        case string(String)
    }

    public final class Manager {

        ...1
            
        public func privateKey(context: LAContext? = nil) throws -> PrivateKey {    
            return privateKey(prompt: .context(context))
        }

        public func privateKey(operationPrompt: String) throws -> PrivateKey {    
            return privateKey(prompt: .string(operationPrompt))
        }

        private func privateKey(prompt: Prompt) throws -> PrivateKey {    
            ...2 
            - let key = try helper.getPrivateKey(context: context)
            + let key = try helper.getPrivateKey(prompt: prompt)
        }

Sign twice strings

Hello,

How can I sign two strings like (blaBla1) then (BlaBla2) in one call of

Shared.keypair.sign(digest, hash: .sha256) and return two signatures

Thank you in advance?

Demo Project Error

Getting an error in the init for the Manager class. The how do you pass the config to the Helper method?

EllipticCurveKeyPair.swift:99:42: Argument passed to call that takes no arguments
self.helper = Helper(config: config)

Dismiss TouchID authentication

Hi

I am not sure if this is possible.
Can we cancel TouchID authentication happening on decrypt operation if we dont want to proceed?
LAContext has a method "invalidate" which invalidates LAContext and throws LAErrorAppCancel error.

e.g. We prompt user to authenticate using TouchID as soon as app is opened. We also make a background call to check if user is running on latest app.
If user is running on old version then we cancel TouchID operation and show a message which says "Please update app to latest version."

No prompts for Touch ID when accessing private key

It's probably me, but with master-code I experience the following problems:
I won't be prompted for touch-id, when accessing the private key. In addition to that the signature I am creating is different all the time (for the same digest).

So it seems that the private key isn't accessed at all - and the keypair is regenerated every time (therefore no prompt). Though the logs say: SecItemCopyMatching: ["kcls": 1, "class": keys, "labl": "my.key", "u_OpPrompt": "Auth", "u_AuthCtx": <LAContext: 0x1c5861900>, "r_Ref": true]

I followed your great tutorial, but didn't get it to work :(
https://github.com/agens-no/EllipticCurveKeyPair/blob/master/Demo-iOS/SignatureViewController.swift

Here is my code:

  struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
      EllipticCurveKeyPair.logger = { print($0) }
      let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
      let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags: {
        return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
      }())
      let config = EllipticCurveKeyPair.Config(
        publicLabel: "my.key.public",
        privateLabel: "my.key",
        operationPrompt: "Auth",
        publicKeyAccessControl: publicAccessControl,
        privateKeyAccessControl: privateAccessControl,
        token: .secureEnclaveIfAvailable)
      return EllipticCurveKeyPair.Manager(config: config)
    }()
  }

  var context: LAContext! = LAContext()

  DispatchQueue.global(qos: .background).async {
    do {
      let digest = givenDigest.data(using: .utf8)!
      signature = try KeyPair.manager.sign(digest, hash: .sha256, context: self.context)

      signature.base64EncodedString() // different every time
    } else {
      // ...
    }
  }

What am I doing wrong?

Encrypting messages using CLI Open SSL

I'm playing around with both signing and ecrypting. The example project provides a way to verify signatures using openssl (by printing the statements in the debug log), which is working perfectly fine in the POC i'm working on.

I was wondering if you can assist in doing the same for encrypting messages using the public key from the device. Can openssl CLI encrypt messages which the device can decrypt using it's private key (which is stored in the secure enclave)?

Thanks in advance

Jim

Is secp256k1 curve supported?

I have generated the public and private keys of the secp256k1 curve. Currently, I only need to use the generated public and private keys to encrypt data with ECC encryption. Does the library support this?

"Could not generate keypair." when running on simulator

Hi! :)

thanks for developing and maintaining this library - this is great!

I just added some code to generate a keypair and sign a digest with it. While this works perfectly on my device (using secure enclave), I have some trouble getting it to run on simulator (using the keychain).

I get this error:

underlying(message: "Could not generate keypair.", error: Error Domain=NSOSStatusErrorDomain Code=-25293 "Could not generate keypair." UserInfo={NSLocalizedRecoverySuggestion=See https://www.osstatus.com/search/results?platform=all&framework=all&search=-25293, NSLocalizedDescription=Could not generate keypair.})

Here is my code:

struct KeyPair {
  static let manager: EllipticCurveKeyPair.Manager = {
    let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
    let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
    let config = EllipticCurveKeyPair.Config(
      publicLabel: "n.sign.public",
      privateLabel: "n.sign.private",
      operationPrompt: "N SecureElement",
      publicKeyAccessControl: publicAccessControl,
      privateKeyAccessControl: privateAccessControl,
      publicKeyAccessGroup: nil,
      privateKeyAccessGroup: nil,
      fallbackToKeychainIfSecureEnclaveIsNotAvailable: true
    )
    return EllipticCurveKeyPair.Manager(config: config)
  }()
}

and

do {
      let digest = "some_digest".data(using: .utf8)!
      signature = try KeyPair.manager.sign(digest)
} catch {
...
}

Any ideas what I am doing wrong?
Thank you.

Create access control with only [.privateKeyUsage]

Hi, thanks for providing this library. Makes my live less complicated :)

I have a follow up question on the matter of issue #15: I do as you suggest (using only .privateKeyUsage as flags, which results in that error: throw EllipticCurveKeyPair.Error.inconcistency(message: "Couldn't create access control flag. Keychain chokes if you try to create access control with only [.privateKeyUsage] on devices older than iOS 11 and macOS 10.13.x")

Is that meant to throw? I'm running this on iOS 12+ (getting it actually on iOS 14) and am wondering whether this should only throw for the iOS / macOS versions mentioned in the crash description.

Commenting those lines out that check if only .privateKeyUsage is set, everything works fine...

That's my code:

static let manager: EllipticCurveKeyPair.Manager = {
            let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [])
            let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
                return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.privateKeyUsage] : []
            }())
            let config = EllipticCurveKeyPair.Config(
                publicLabel: "public",
                privateLabel: "private",
                operationPrompt: "",
                publicKeyAccessControl: publicAccessControl,
                privateKeyAccessControl: privateAccessControl,
                token: .secureEnclaveIfAvailable)
            return EllipticCurveKeyPair.Manager(config: config)
        }()

Originally posted by @b00tsy in #15 (comment)

Encryption on MacOS

I want to learn any encryption/decryption or generate keypair operation on MacOS which has Secure Enclave?

errSecItemNotFound after iCloud backup

Hi,
after a backup from one iPhone to other, my app, throw this exception:

> Found public key, but couldn't find or access private key. The errSecItemNotFound error is sometimes wrongfully reported when LAContext authentication fails

I can not understand what the problem is.
Some advice? thank you.

Deprecation Warnings and Making export() Method of PublicKey Public

First, THANK YOU for developing this library. We used it to develop a
VPN solution for our company.

While working with EllipticCurveKeyPair.swift, We noticed that there
are several places where the code now generates deprecation warnings.
Also, the export() method of the PublicKey class is marked private.
For our application, I needed to export the public key to generate a
CSR. I can't think of a reason why this method should be private.

I would be happy to provide a PR for these issues. I thought it would
be more friendly to start with an issue (instead of doing a drive by
PR).

I want to export the public key as X.509 DER with proper ASN.1 header / structure

I need to export the public key as X.509 DER with proper ASN.1 header / structure

please give me an example.

I have a public key with format

-----BEGIN CERTIFICATE-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbEicao0J2djMo1Q3ECOuprI+XdZPJ13NeYyr1t/hp2VFe90SEAdv+LaJ6Qcffq7EdLUXCVekev7tBmAzEAU6FTYbjrWUeXFvsstWlUi+HfCowmRBzemCzMe7lzMiIZ49FTlVqxXHp2QWeummVHnZpLnzYSI/aQIZl9vf14cDKxQIDAQAB-----END CERTIFICATE-----

How to convert this in X.509 DER with proper ASN.1 header / structure

any help will be appreciated.
thanks

Access control is not respected

Hi,
I faced issues when using your code with a kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly protection level.

        let protection = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
        let config = EllipticCurveKeyPair.Config(
            publicLabel: self.publicLabel,
            privateLabel: self.privateLabel,
            operationPrompt: "",
            accessControl: try! SecAccessControl.create(protection: protection, flags: [.privateKeyUsage]),
            fallbackToKeychainIfSecureEnclaveIsNotAvailable: true)
        return EllipticCurveKeyPair.Manager(config: config)

If you lock the device, wait a few seconds (10+), and you try to decrypt (I found it useful to trigger the decrypt method with a notification), you get this error:

decode keys,rowid=760 failed (-25308): Error Domain=NSOSStatusErrorDomain Code=-25308 "ks_crypt: e00002e2 failed to 'od' item (class 6, bag: 0) Access to item attempted while keychain is locked." UserInfo={NSDescription=ks_crypt: e00002e2 failed to 'od' item (class 6, bag: 0) Access to item attempted while keychain is locked.}

Class 6 is: kSecAttrAccessibleWhenUnlocked (see this thread on SO)

The item should not belong to Class 6.

I managed to solve this issue by applying this change:

        static func forceSavePublicKey(_ publicKey: PublicKey, label: String) throws {
            // I will create a SecAccessControl
            var error: Unmanaged<CFError>?
            
            let sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, [], &error)
            //
            
            let query: [String: Any] = [
                kSecClass as String: kSecClassKey,
                kSecAttrLabel as String: label,
                kSecValueRef as String: publicKey.underlying,
                /* I will apply the SecAccessControl object to the public key */
                kSecAttrAccessControl as String: sacObject!,
            ]
            
            var raw: CFTypeRef?
            var status = SecItemAdd(query as CFDictionary, &raw)
            if status == errSecDuplicateItem {
                status = SecItemDelete(query as CFDictionary)
                status = SecItemAdd(query as CFDictionary, &raw)
            }
            guard status == errSecSuccess else {
                throw Error.osStatus(message: "Could not save public key", osStatus: status)
            }
        }
    } 

This is a rough patch, imho you should re-use what you already have in the Config object.

SecItemCopyMatching not works in iphone 12

I face issue with Face Id in some of iphone 12 pro and pro max the issue happen in SecKeyCreateSignature not generate signature but it works fine in different iphones like iphone 11.

even I cannot get the Face Id prompt.

my app not show in Face id & Passcode -> other apps.

In iphone 12 pro not in all device happen only a few devices.

Support application password

Hey, it's me again :-)

Still happily using the master-branch.
Lately, I wanted to try the application password method for devices that haven't set fingerprint or passcode.

I added the flag .applicationPassword resulting in: flags: EllipticCurveKeyPair.Device.hasSecureEnclave ? [.applicationPassword, .privateKeyUsage] : [.applicationPassword].

My understanding is, that I'd have to provide the actual password to the LAContext before key creation. Something like this:
context.setCredential("passwordFromServer".data(using:String.Encoding.utf8)!, type: LACredentialType.applicationPassword)

Though, I don't pass the LAContext to key creation. Regarding your API I can only provide it when decrypting or signing.

iOS 13 and CryptoKit

Apple seems to have broken Security.framework in iOS 13+ simulators, while at the same releasing CryptoKit as a standard way of doing crypto operations, limited to iOS 13 and higher.

It would be really nice if the library could transparently implement operations with Cryptokit where available while keeping the same pleasant API.

Authenticate only with Biometrics

Im trying to authenticate only with biometric with no passcode but, just like context .deviceOwnerAuthenticationWithBiometrics., but no success.
Always when the Touch ID fails, show the modal with the possibility to enter code.

iOS 13+

Hi @hfossli-agens,

Thank you for creating this library. I have been using this in some of my projects.

I was wondering if you had an interest in bumping the minimum project version to iOS 13+ as this will allow the project to use CryptoKit and potentially simplify some parts of the library. I am happy to open a pr.

Here is an example of some improvements:

SHA256.swift: This whole file can be replaced with:

import Foundation
import CryptoKit

extension Data {
    func sha256() -> Data {
        Data(SHA256.hash(data: self))
    }
}

Device Token Type

After

import CryptoKit

public enum Token {
    case secureEnclave
    case keychain

    public static var secureEnclaveIfAvailable: Token {
        SecureEnclave.isAvailable ? .secureEnclave : .keychain
    }
}

Before

public enum Token {
    case secureEnclave
    case keychain
    
    public static var secureEnclaveIfAvailable: Token {
        return Device.hasSecureEnclave ? .secureEnclave : .keychain
    }
}

public enum Device {
    public static var hasTouchID: Bool {
        if #available(macOS 10.12.2, *) {
            var error: NSError?
            let result = LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
            EllipticCurveKeyPair.logger?(String(describing: error))
            return result
        } else {
            return false
        }
    }

    public static var isSimulator: Bool {
        return TARGET_OS_SIMULATOR != 0
    }

    public static var hasSecureEnclave: Bool {
        return hasTouchID && !isSimulator
    }   
}

Thanks!

Should we drop iOS 9 support

Let me know in comments or with reactions what you prefer

👍 = drop support (minimum required iOS version will be 10)
👎 = please support iOS 9

Not an issue

Turns out I was incorrect on this issue (even though I did check three times before posting).

Signature validates locally but not on remote server

Hi, I wanto to use your library for my project which uses JSON Web Token
JWT = base64(header) + "." + base64(payload) + "." + base64(signature)

In the documentation of my project I have an example

eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0.SGNpyl3zRA_ptRhA0lFH0o7-nhf3mpxE95ss37_jHYbCnwlRb4zDvVaYCj9DlpppU4U0y3vIPEqM44vV2UZ5Iw

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7u8bg5nOOsxZvkdmK+Zcvx+byi93
iQ+lMWHsAcOaOAwbmcSU3lKEXKu3gp/ymiXUhIyFuw2Pkxfe7T1e4HSmqA==
-----END PUBLIC KEY-----

And when I paste that token here https://jwt.io/ with the public key - it validates.
However when I use your library to sign the header and payload I get info about invalid signature (on JWT page)

let signatureInput = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0"
let signature = createSignature(signatureInput)
let token = signatureInput + "." + signature


func createSignature(_ input: String) -> String {
        guard
            let data = input.data(using: .utf8),
            let signature = try? keysManager.sign(data, hash: .sha256)
        else {
            fatalError("Cannot create signature")
        }
        return signature.base64EncodedString(options: .init(rawValue: 0))
    }

The verification passes your method

do {
    try keysManager.verify(signature: Data(base64Encoded: signature)!,
         originalDigest: signatureInput.data(using: .utf8)!,
         hash: .sha256)
     print("valid")
}
catch {
     print("error \(error)")
}

Unbased content of header and payload are

header // eyJhbGciOiJFUzI1NiJ9
{
  "alg": "ES256"
}

payload // eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0
{
  "iss": "testusername",
  "sub": "testclientid",
  "iat": 1501509782,
  "exp": 1501509842
}

Am I doing something wrong with signing?

deinitialize() and deallocate() causing Compiler Error in SHA256.swift

Installed via cocoapods and got these errors when building:

'deinitialize()' is unavailable: the default argument to deinitialize(count:) has been removed, please specify the count explicitly
'deallocate(capacity:)' is unavailable: Swift currently only supports freeing entire heap blocks, use deallocate() instead

'deinitialize()' was obsoleted in Swift 5.0
'deallocate(capacity:)' was obsoleted in Swift 5.0

iPad Mini 2 Crash

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.

Document (in comments or notes) signature algorithm

ecdsaSignatureDigestX962SHA256 is used to sign data; however, it appears to sign data that has already been digested into sha-256.

We should document what's going on here (double digest?) and also how to verify it in OpenSSL. The demo app outputs some weird code to do with hexdumps and simple sha256 verification, as opposed to OpenSSL's -ecdsa-with-SHA1 (I think this is something I don't personally understand about crypto, so it's my problem, but I'd like to elucidate it further).

How to use ECKeyPair without local authentication

Not a bug report but rather a usage question:

I'd like to generate a key pair that is stored in the enclave, except where the private key can be used whenever the genuine app is running to sign/encrypt data, no local auth required. Could you provide guidance on the best way to achieve this?

Can I export the EC Private key ?

I need to export the EC Private Key as I need to send the private key to my server in encrypted format?
Any help is appreciated. Thanks

LAContext passed when decrypting will not be respected

I'd like to decrypt multiple things in a short amount of time. I don't want to prompt the user for authentication every time.

I am aware of the bug concerning touchIDAuthenticationAllowableReuseDuration (#9). Though I'd like to use the default time-limit (10 minutest) when passing an already authentified LAContext.

When passing the LAContext I am still prompted for authentication. I guess that's because the context is not regarded by the library:

@available(iOS 10.3, *) // API available at 10.0, but bugs made it unusable on versions lower than 10.3
public func decrypt(_ encrypted: Data, hash: Hash = .sha256, context: LAContext? = nil) throws -> Data {
  return try helper.decrypt(encrypted, privateKey: privateKey(), hash: hash)
}

Create a better API for handling various devices better

It should be easy to configure for

  • devices with SE & with passcode & with biometry
  • devices with SE & with passcode & without biometry
  • devices with SE & without passcode & with biometry (is this possible?)
  • devices with SE & without passcode & without biometry
  • devices without SE & with passcode
  • devices without SE & without passcode

Fallback to .applicationPassword etc should be easy to do and it should be hard to forget devices / setups.

#25

Error when signing or decrypting on Simulator (iOS 13)

Hi!

If I run the demo project on a simulator on iOS 13, whenever I try to perform a signature or decryption I get this error:

Error: authentication(error: __C_Synthesized.related decl 'e' for LAError(_nsError: Error Domain=com.apple.LocalAuthentication Code=-1 "Found public key, but couldn't find or access private key. The errSecItemNotFound error is sometimes wrongfully reported when LAContext authentication fails" UserInfo={NSLocalizedFailureReason=Found public key, but couldn't find or access private key. The errSecItemNotFound error is sometimes wrongfully reported when LAContext authentication fails, NSUnderlyingError=0x600003c582a0 {Error Domain=NSOSStatusErrorDomain Code=-25300 "Could not get key for query: ["r_Ref": true, "u_OpPrompt": "Sign transaction", "labl": "no.agens.sign.private", "kcls": 1, "class": keys, "u_AuthCtx": <LAContext: 0x60000275a040>]" UserInfo={NSLocalizedRecoverySuggestion=See https://www.osstatus.com/search/results?platform=all&framework=all&search=-25300, NSLocalizedDescription=Could not get key for query: ["r_Ref": true, "u_OpPrompt": "Sign transaction", "labl": "no.agens.sign.private", "kcls": 1, "class": keys, "u_AuthCtx": <LAContext: 0x60000275a040>]}}}))

Can it be related to the discussion in this thread?
https://stackoverflow.com/questions/56700680/keychain-query-always-returns-errsecitemnotfound-after-upgrading-to-ios-13

AccessControl for publicKey doesn't restrict to biometryCurrentSet flag

Hi,

I've found your helper is really useful. Despite, I couldn't understand the AccessControl for publicKey. When I set it to [.biometryCurrentSet, .privateKeyUsage] it doesn't ask me to authenticate with FaceID/TouchID when encrypting data.

Do you know why is it happening?

I believe there is no point to have those flags when create KeyPair manager, we can just provide AccessControl for privateKey.

Thanks,
Roman

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.