authgear / authgear-server

Open source alternative to Auth0 / Firebase Auth
https://www.authgear.com
Apache License 2.0
81 stars 37 forks source link

More secure biometric implementation #3101

Open louischan-oursky opened 1 year ago

louischan-oursky commented 1 year ago

The update does not effect the public API. To take effect, biometric has to be disabled, and enabled again.

fungc-io commented 1 year ago
louischan-oursky commented 1 year ago

On iOS, the following snippet generate a P256 key pair in the Secure Enclave and export the public key

let flags: SecAccessControlCreateFlags = [.biometryCurrentSet, .and, .privateKeyUsage]
let access = SecAccessControlCreateWithFlags(
    nil,
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    flags,
    nil)!
let tag = "tag"
let attributes: NSDictionary = [
    kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeySizeInBits: 256,
    kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
    kSecPrivateKeyAttrs: [
        kSecAttrIsPermanent: true,
        kSecAttrApplicationTag: tag,
        kSecAttrAccessControl: access
    ]
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes, &error) else {
    throw error!.takeRetainedValue() as Error
}
let publicKey = SecKeyCopyPublicKey(privateKey)!
let cfData = SecKeyCopyExternalRepresentation(publicKey, nil)!
let data = cfData as Data
let base64 = data.base64EncodedString()

In Go, we can construct the JWK

package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "encoding/base64"
    "encoding/json"
    "fmt"

    // This is v1 API
    "github.com/lestrrat-go/jwx/jwk"
)

func main() {
    publicKeyBase64 := "BBnS2sXMxybCu2sS43Kmv1+zYwpMJNAeAjnc3fTyDJQti0vhB+FDcBT3EHQ2Taj5+O+meX/zRaCxHT9gMGJpzic="
    publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyBase64)
    if err != nil {
        panic(err)
    }
    curve := elliptic.P256()
    x, y := elliptic.Unmarshal(curve, publicKeyBytes)
    if x == nil {
        panic(fmt.Errorf("invalid key"))
    }

    publicKey := &ecdsa.PublicKey{
        Curve: curve,
        X:     x,
        Y:     y,
    }

    jwkKey, err := jwk.New(publicKey)
    if err != nil {
        panic(err)
    }

    jsonBytes, err := json.MarshalIndent(jwkKey, "", "  ")
    if err != nil {
        panic(err)
    }

    fmt.Printf("%v\n", string(jsonBytes))
    // {
    //   "crv": "P-256",
    //   "kty": "EC",
    //   "x": "GdLaxczHJsK7axLjcqa_X7NjCkwk0B4COdzd9PIMlC0",
    //   "y": "i0vhB-FDcBT3EHQ2Taj5-O-meX_zRaCxHT9gMGJpzic"
    // }
}