2023-08-21 13:52:06 +02:00
|
|
|
//go:build !nofido2
|
|
|
|
|
|
|
|
package twofactor
|
2023-07-17 03:23:26 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/keys-pub/go-libfido2"
|
|
|
|
"github.com/quexten/goldwarden/agent/config"
|
2023-09-12 02:54:46 +02:00
|
|
|
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
|
2023-07-17 03:23:26 +02:00
|
|
|
)
|
|
|
|
|
2023-08-21 13:52:06 +02:00
|
|
|
const isFido2Enabled = true
|
|
|
|
|
2023-07-17 03:23:26 +02:00
|
|
|
type Fido2Response struct {
|
|
|
|
Id string `json:"id"`
|
|
|
|
RawId string `json:"rawId"`
|
|
|
|
Type_ string `json:"type"`
|
|
|
|
Extensions struct {
|
|
|
|
Appid bool `json:"appid"`
|
|
|
|
} `json:"extensions"`
|
|
|
|
Response struct {
|
|
|
|
AuthenticatorData string `json:"authenticatorData"`
|
|
|
|
ClientDataJSON string `json:"clientDataJson"`
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
} `json:"response"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func Fido2TwoFactor(challengeB64 string, credentials []string, config *config.Config) (string, error) {
|
|
|
|
url, err := url.Parse(config.ConfigFile.ApiUrl)
|
|
|
|
rpid := url.Host
|
|
|
|
|
|
|
|
locs, err := libfido2.DeviceLocations()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(locs) == 0 {
|
|
|
|
return "", errors.New("no devices found")
|
|
|
|
}
|
|
|
|
|
|
|
|
path := locs[0].Path
|
|
|
|
device, err := libfido2.NewDevice(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
creds := make([][]byte, len(credentials))
|
|
|
|
for i, cred := range credentials {
|
|
|
|
decodedPublicKey, err := base64.RawURLEncoding.DecodeString(cred)
|
|
|
|
if err != nil {
|
2023-08-21 13:52:06 +02:00
|
|
|
twofactorLog.Fatal(err.Error())
|
2023-07-17 03:23:26 +02:00
|
|
|
}
|
|
|
|
creds[i] = decodedPublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
clientDataJson := "{\"type\":\"webauthn.get\",\"challenge\":\"" + challengeB64 + "\",\"origin\":\"https://" + rpid + "\",\"crossOrigin\":false}"
|
|
|
|
clientDataHash := sha256.Sum256([]byte(clientDataJson))
|
|
|
|
clientDataJson = base64.URLEncoding.EncodeToString([]byte(clientDataJson))
|
|
|
|
|
2023-09-12 02:54:46 +02:00
|
|
|
pin, err := pinentry.GetPassword("Fido2 PIN", "Enter your token's PIN")
|
2023-07-17 03:23:26 +02:00
|
|
|
if err != nil {
|
2023-08-21 13:52:06 +02:00
|
|
|
twofactorLog.Fatal(err.Error())
|
2023-07-17 03:23:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
assertion, err := device.Assertion(
|
|
|
|
rpid,
|
|
|
|
clientDataHash[:],
|
|
|
|
creds,
|
|
|
|
pin,
|
|
|
|
&libfido2.AssertionOpts{
|
|
|
|
Extensions: []libfido2.Extension{},
|
|
|
|
UV: libfido2.False,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
authDataRaw := assertion.AuthDataCBOR[2:] // first 2 bytes seem to be from cbor, don't have a proper decoder ATM but this works
|
|
|
|
authData := base64.URLEncoding.EncodeToString(authDataRaw)
|
|
|
|
sig := base64.URLEncoding.EncodeToString(assertion.Sig)
|
|
|
|
credential := base64.URLEncoding.EncodeToString(assertion.CredentialID)
|
|
|
|
|
|
|
|
resp := Fido2Response{
|
|
|
|
Id: credential,
|
|
|
|
RawId: credential,
|
|
|
|
Type_: "public-key",
|
|
|
|
Extensions: struct {
|
|
|
|
Appid bool `json:"appid"`
|
|
|
|
}{Appid: false},
|
|
|
|
Response: struct {
|
|
|
|
AuthenticatorData string `json:"authenticatorData"`
|
|
|
|
ClientDataJSON string `json:"clientDataJson"`
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
}{
|
|
|
|
AuthenticatorData: authData,
|
|
|
|
ClientDataJSON: clientDataJson,
|
|
|
|
Signature: sig,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
respjson, err := json.Marshal(resp)
|
|
|
|
return string(respjson), nil
|
|
|
|
}
|