Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
3.4 KiB
136 lines
3.4 KiB
// 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 |
|
}
|
|
|