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