1// Go FIDO U2F Library
2// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
3// Use of this source code is governed by the MIT
4// license that can be found in the LICENSE file.
5
6package u2f
7
8import (
9	"crypto/ecdsa"
10	"crypto/sha256"
11	"encoding/asn1"
12	"errors"
13	"math/big"
14	"time"
15)
16
17// SignRequest creates a request to initiate an authentication.
18func (c *Challenge) SignRequest(regs []Registration) *WebSignRequest {
19	var sr WebSignRequest
20	sr.AppID = c.AppID
21	sr.Challenge = encodeBase64(c.Challenge)
22	for _, r := range regs {
23		rk := getRegisteredKey(c.AppID, r)
24		sr.RegisteredKeys = append(sr.RegisteredKeys, rk)
25	}
26	return &sr
27}
28
29// ErrCounterTooLow is raised when the counter value received from the device is
30// lower than last stored counter value. This may indicate that the device has
31// been cloned (or is malfunctioning). The application may choose to disable
32// the particular device as precaution.
33var ErrCounterTooLow = errors.New("u2f: counter too low")
34
35// Authenticate validates a SignResponse authentication response.
36// An error is returned if any part of the response fails to validate.
37// The counter should be the counter associated with appropriate device
38// (i.e. resp.KeyHandle).
39// The latest counter value is returned, which the caller should store.
40func (reg *Registration) Authenticate(resp SignResponse, c Challenge, counter uint32) (newCounter uint32, err error) {
41	if time.Now().Sub(c.Timestamp) > timeout {
42		return 0, errors.New("u2f: challenge has expired")
43	}
44	if resp.KeyHandle != encodeBase64(reg.KeyHandle) {
45		return 0, errors.New("u2f: wrong key handle")
46	}
47
48	sigData, err := decodeBase64(resp.SignatureData)
49	if err != nil {
50		return 0, err
51	}
52
53	clientData, err := decodeBase64(resp.ClientData)
54	if err != nil {
55		return 0, err
56	}
57
58	ar, err := parseSignResponse(sigData)
59	if err != nil {
60		return 0, err
61	}
62
63	if ar.Counter < counter {
64		return 0, ErrCounterTooLow
65	}
66
67	if err := verifyClientData(clientData, c); err != nil {
68		return 0, err
69	}
70
71	if err := verifyAuthSignature(*ar, &reg.PubKey, c.AppID, clientData); err != nil {
72		return 0, err
73	}
74
75	if !ar.UserPresenceVerified {
76		return 0, errors.New("u2f: user was not present")
77	}
78
79	return ar.Counter, nil
80}
81
82type ecdsaSig struct {
83	R, S *big.Int
84}
85
86type authResp struct {
87	UserPresenceVerified bool
88	Counter              uint32
89	sig                  ecdsaSig
90	raw                  []byte
91}
92
93func parseSignResponse(sd []byte) (*authResp, error) {
94	if len(sd) < 5 {
95		return nil, errors.New("u2f: data is too short")
96	}
97
98	var ar authResp
99
100	userPresence := sd[0]
101	if userPresence|1 != 1 {
102		return nil, errors.New("u2f: invalid user presence byte")
103	}
104	ar.UserPresenceVerified = userPresence == 1
105
106	ar.Counter = uint32(sd[1])<<24 | uint32(sd[2])<<16 | uint32(sd[3])<<8 | uint32(sd[4])
107
108	ar.raw = sd[:5]
109
110	rest, err := asn1.Unmarshal(sd[5:], &ar.sig)
111	if err != nil {
112		return nil, err
113	}
114	if len(rest) != 0 {
115		return nil, errors.New("u2f: trailing data")
116	}
117
118	return &ar, nil
119}
120
121func verifyAuthSignature(ar authResp, pubKey *ecdsa.PublicKey, appID string, clientData []byte) error {
122	appParam := sha256.Sum256([]byte(appID))
123	challenge := sha256.Sum256(clientData)
124
125	var buf []byte
126	buf = append(buf, appParam[:]...)
127	buf = append(buf, ar.raw...)
128	buf = append(buf, challenge[:]...)
129	hash := sha256.Sum256(buf)
130
131	if !ecdsa.Verify(pubKey, hash[:], ar.sig.R, ar.sig.S) {
132		return errors.New("u2f: invalid signature")
133	}
134
135	return nil
136}
137