Платформа ЦРНП "Мирокод" для разработки проектов
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.
230 lines
6.1 KiB
230 lines
6.1 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/elliptic" |
|
"crypto/sha256" |
|
"crypto/x509" |
|
"encoding/asn1" |
|
"encoding/hex" |
|
"errors" |
|
"time" |
|
) |
|
|
|
// Registration represents a single enrolment or pairing between an |
|
// application and a token. This data will typically be stored in a database. |
|
type Registration struct { |
|
// Raw serialized registration data as received from the token. |
|
Raw []byte |
|
|
|
KeyHandle []byte |
|
PubKey ecdsa.PublicKey |
|
|
|
// AttestationCert can be nil for Authenticate requests. |
|
AttestationCert *x509.Certificate |
|
} |
|
|
|
// Config contains configurable options for the package. |
|
type Config struct { |
|
// SkipAttestationVerify controls whether the token attestation |
|
// certificate should be verified on registration. Ideally it should |
|
// always be verified. However, there is currently no public list of |
|
// trusted attestation root certificates so it may be necessary to skip. |
|
SkipAttestationVerify bool |
|
|
|
// RootAttestationCertPool overrides the default root certificates used |
|
// to verify client attestations. If nil, this defaults to the roots that are |
|
// bundled in this library. |
|
RootAttestationCertPool *x509.CertPool |
|
} |
|
|
|
// Register validates a RegisterResponse message to enrol a new token. |
|
// An error is returned if any part of the response fails to validate. |
|
// The returned Registration should be stored by the caller. |
|
func Register(resp RegisterResponse, c Challenge, config *Config) (*Registration, error) { |
|
if config == nil { |
|
config = &Config{} |
|
} |
|
|
|
if time.Now().Sub(c.Timestamp) > timeout { |
|
return nil, errors.New("u2f: challenge has expired") |
|
} |
|
|
|
regData, err := decodeBase64(resp.RegistrationData) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
clientData, err := decodeBase64(resp.ClientData) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
reg, sig, err := parseRegistration(regData) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if err := verifyClientData(clientData, c); err != nil { |
|
return nil, err |
|
} |
|
|
|
if err := verifyAttestationCert(*reg, config); err != nil { |
|
return nil, err |
|
} |
|
|
|
if err := verifyRegistrationSignature(*reg, sig, c.AppID, clientData); err != nil { |
|
return nil, err |
|
} |
|
|
|
return reg, nil |
|
} |
|
|
|
func parseRegistration(buf []byte) (*Registration, []byte, error) { |
|
if len(buf) < 1+65+1+1+1 { |
|
return nil, nil, errors.New("u2f: data is too short") |
|
} |
|
|
|
var r Registration |
|
r.Raw = buf |
|
|
|
if buf[0] != 0x05 { |
|
return nil, nil, errors.New("u2f: invalid reserved byte") |
|
} |
|
buf = buf[1:] |
|
|
|
x, y := elliptic.Unmarshal(elliptic.P256(), buf[:65]) |
|
if x == nil { |
|
return nil, nil, errors.New("u2f: invalid public key") |
|
} |
|
r.PubKey.Curve = elliptic.P256() |
|
r.PubKey.X = x |
|
r.PubKey.Y = y |
|
buf = buf[65:] |
|
|
|
khLen := int(buf[0]) |
|
buf = buf[1:] |
|
if len(buf) < khLen { |
|
return nil, nil, errors.New("u2f: invalid key handle") |
|
} |
|
r.KeyHandle = buf[:khLen] |
|
buf = buf[khLen:] |
|
|
|
// The length of the x509 cert isn't specified so it has to be inferred |
|
// by parsing. We can't use x509.ParseCertificate yet because it returns |
|
// an error if there are any trailing bytes. So parse raw asn1 as a |
|
// workaround to get the length. |
|
sig, err := asn1.Unmarshal(buf, &asn1.RawValue{}) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
buf = buf[:len(buf)-len(sig)] |
|
fixCertIfNeed(buf) |
|
cert, err := x509.ParseCertificate(buf) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
r.AttestationCert = cert |
|
|
|
return &r, sig, nil |
|
} |
|
|
|
// UnmarshalBinary implements encoding.BinaryMarshaler. |
|
func (r *Registration) UnmarshalBinary(data []byte) error { |
|
reg, _, err := parseRegistration(data) |
|
if err != nil { |
|
return err |
|
} |
|
*r = *reg |
|
return nil |
|
} |
|
|
|
// MarshalBinary implements encoding.BinaryUnmarshaler. |
|
func (r *Registration) MarshalBinary() ([]byte, error) { |
|
return r.Raw, nil |
|
} |
|
|
|
func verifyAttestationCert(r Registration, config *Config) error { |
|
if config.SkipAttestationVerify { |
|
return nil |
|
} |
|
rootCertPool := roots |
|
if config.RootAttestationCertPool != nil { |
|
rootCertPool = config.RootAttestationCertPool |
|
} |
|
|
|
opts := x509.VerifyOptions{Roots: rootCertPool} |
|
_, err := r.AttestationCert.Verify(opts) |
|
return err |
|
} |
|
|
|
func verifyRegistrationSignature( |
|
r Registration, signature []byte, appid string, clientData []byte) error { |
|
|
|
appParam := sha256.Sum256([]byte(appid)) |
|
challenge := sha256.Sum256(clientData) |
|
|
|
buf := []byte{0} |
|
buf = append(buf, appParam[:]...) |
|
buf = append(buf, challenge[:]...) |
|
buf = append(buf, r.KeyHandle...) |
|
pk := elliptic.Marshal(r.PubKey.Curve, r.PubKey.X, r.PubKey.Y) |
|
buf = append(buf, pk...) |
|
|
|
return r.AttestationCert.CheckSignature( |
|
x509.ECDSAWithSHA256, buf, signature) |
|
} |
|
|
|
func getRegisteredKey(appID string, r Registration) RegisteredKey { |
|
return RegisteredKey{ |
|
Version: u2fVersion, |
|
KeyHandle: encodeBase64(r.KeyHandle), |
|
AppID: appID, |
|
} |
|
} |
|
|
|
// fixCertIfNeed fixes broken certificates described in |
|
// https://github.com/Yubico/php-u2flib-server/blob/master/src/u2flib_server/U2F.php#L84 |
|
func fixCertIfNeed(cert []byte) { |
|
h := sha256.Sum256(cert) |
|
switch hex.EncodeToString(h[:]) { |
|
case |
|
"349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8", |
|
"dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f", |
|
"1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae", |
|
"d0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb", |
|
"6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897", |
|
"ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511": |
|
|
|
// clear the offending byte. |
|
cert[len(cert)-257] = 0 |
|
} |
|
} |
|
|
|
// NewWebRegisterRequest creates a request to enrol a new token. |
|
// regs is the list of the user's existing registration. The browser will |
|
// refuse to re-register a device if it has an existing registration. |
|
func NewWebRegisterRequest(c *Challenge, regs []Registration) *WebRegisterRequest { |
|
req := RegisterRequest{ |
|
Version: u2fVersion, |
|
Challenge: encodeBase64(c.Challenge), |
|
} |
|
|
|
rr := WebRegisterRequest{ |
|
AppID: c.AppID, |
|
RegisterRequests: []RegisterRequest{req}, |
|
} |
|
|
|
for _, r := range regs { |
|
rk := getRegisteredKey(c.AppID, r) |
|
rr.RegisteredKeys = append(rr.RegisteredKeys, rk) |
|
} |
|
|
|
return &rr |
|
}
|
|
|