|
- // 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
- }
|