mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-28 04:51:07 +00:00 
			
		
		
		
	* 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>
		
			
				
	
	
		
			136 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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
 | |
| }
 |