forked from forgejo/forgejo
Add support for FIDO U2F (#3971)
* Add support for U2F Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add vendor library Add missing translations Signed-off-by: Jonas Franz <info@jonasfranz.software> * Minor improvements Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F support for Firefox, Chrome (Android) by introducing a custom JS library Add U2F error handling Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F login page to OAuth Signed-off-by: Jonas Franz <info@jonasfranz.software> * Move U2F user settings to a separate file Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add unit tests for u2f model Renamed u2f table name Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix problems caused by refactoring Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F documentation Signed-off-by: Jonas Franz <info@jonasfranz.software> * Remove not needed console.log-s Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add default values to app.ini.sample Add FIDO U2F to comparison Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
parent
f933bcdfee
commit
951309f76a
34 changed files with 1599 additions and 9 deletions
136
vendor/github.com/tstranex/u2f/auth.go
generated
vendored
Normal file
136
vendor/github.com/tstranex/u2f/auth.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Go FIDO U2F Library
|
||||
// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package u2f
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SignRequest creates a request to initiate an authentication.
|
||||
func (c *Challenge) SignRequest(regs []Registration) *WebSignRequest {
|
||||
var sr WebSignRequest
|
||||
sr.AppID = c.AppID
|
||||
sr.Challenge = encodeBase64(c.Challenge)
|
||||
for _, r := range regs {
|
||||
rk := getRegisteredKey(c.AppID, r)
|
||||
sr.RegisteredKeys = append(sr.RegisteredKeys, rk)
|
||||
}
|
||||
return &sr
|
||||
}
|
||||
|
||||
// ErrCounterTooLow is raised when the counter value received from the device is
|
||||
// lower than last stored counter value. This may indicate that the device has
|
||||
// been cloned (or is malfunctioning). The application may choose to disable
|
||||
// the particular device as precaution.
|
||||
var ErrCounterTooLow = errors.New("u2f: counter too low")
|
||||
|
||||
// Authenticate validates a SignResponse authentication response.
|
||||
// An error is returned if any part of the response fails to validate.
|
||||
// The counter should be the counter associated with appropriate device
|
||||
// (i.e. resp.KeyHandle).
|
||||
// The latest counter value is returned, which the caller should store.
|
||||
func (reg *Registration) Authenticate(resp SignResponse, c Challenge, counter uint32) (newCounter uint32, err error) {
|
||||
if time.Now().Sub(c.Timestamp) > timeout {
|
||||
return 0, errors.New("u2f: challenge has expired")
|
||||
}
|
||||
if resp.KeyHandle != encodeBase64(reg.KeyHandle) {
|
||||
return 0, errors.New("u2f: wrong key handle")
|
||||
}
|
||||
|
||||
sigData, err := decodeBase64(resp.SignatureData)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
clientData, err := decodeBase64(resp.ClientData)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ar, err := parseSignResponse(sigData)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if ar.Counter < counter {
|
||||
return 0, ErrCounterTooLow
|
||||
}
|
||||
|
||||
if err := verifyClientData(clientData, c); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := verifyAuthSignature(*ar, ®.PubKey, c.AppID, clientData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !ar.UserPresenceVerified {
|
||||
return 0, errors.New("u2f: user was not present")
|
||||
}
|
||||
|
||||
return ar.Counter, nil
|
||||
}
|
||||
|
||||
type ecdsaSig struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
type authResp struct {
|
||||
UserPresenceVerified bool
|
||||
Counter uint32
|
||||
sig ecdsaSig
|
||||
raw []byte
|
||||
}
|
||||
|
||||
func parseSignResponse(sd []byte) (*authResp, error) {
|
||||
if len(sd) < 5 {
|
||||
return nil, errors.New("u2f: data is too short")
|
||||
}
|
||||
|
||||
var ar authResp
|
||||
|
||||
userPresence := sd[0]
|
||||
if userPresence|1 != 1 {
|
||||
return nil, errors.New("u2f: invalid user presence byte")
|
||||
}
|
||||
ar.UserPresenceVerified = userPresence == 1
|
||||
|
||||
ar.Counter = uint32(sd[1])<<24 | uint32(sd[2])<<16 | uint32(sd[3])<<8 | uint32(sd[4])
|
||||
|
||||
ar.raw = sd[:5]
|
||||
|
||||
rest, err := asn1.Unmarshal(sd[5:], &ar.sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("u2f: trailing data")
|
||||
}
|
||||
|
||||
return &ar, nil
|
||||
}
|
||||
|
||||
func verifyAuthSignature(ar authResp, pubKey *ecdsa.PublicKey, appID string, clientData []byte) error {
|
||||
appParam := sha256.Sum256([]byte(appID))
|
||||
challenge := sha256.Sum256(clientData)
|
||||
|
||||
var buf []byte
|
||||
buf = append(buf, appParam[:]...)
|
||||
buf = append(buf, ar.raw...)
|
||||
buf = append(buf, challenge[:]...)
|
||||
hash := sha256.Sum256(buf)
|
||||
|
||||
if !ecdsa.Verify(pubKey, hash[:], ar.sig.R, ar.sig.S) {
|
||||
return errors.New("u2f: invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue