github.com/greenpau/go-identity@v1.1.6/mfa_token.go (about)

     1  // Copyright 2020 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package identity
    16  
    17  import (
    18  	"crypto/ecdsa"
    19  	"crypto/elliptic"
    20  	"crypto/hmac"
    21  	"crypto/sha1"
    22  	"crypto/sha256"
    23  	"crypto/sha512"
    24  	"crypto/subtle"
    25  	"crypto/x509"
    26  	"encoding/base64"
    27  	"encoding/binary"
    28  	"encoding/json"
    29  	"fmt"
    30  	"hash"
    31  	"math"
    32  	"math/big"
    33  	"strings"
    34  	"time"
    35  
    36  	"github.com/greenpau/go-identity/pkg/errors"
    37  	"github.com/greenpau/go-identity/pkg/requests"
    38  )
    39  
    40  // MfaTokenBundle is a collection of public keys.
    41  type MfaTokenBundle struct {
    42  	tokens []*MfaToken
    43  	size   int
    44  }
    45  
    46  // MfaToken is a puiblic key in a public-private key pair.
    47  type MfaToken struct {
    48  	ID               string            `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"`
    49  	Type             string            `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"`
    50  	Algorithm        string            `json:"algorithm,omitempty" xml:"algorithm,omitempty" yaml:"algorithm,omitempty"`
    51  	Comment          string            `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"`
    52  	Secret           string            `json:"secret,omitempty" xml:"secret,omitempty" yaml:"secret,omitempty"`
    53  	Period           int               `json:"period,omitempty" xml:"period,omitempty" yaml:"period,omitempty"`
    54  	Digits           int               `json:"digits,omitempty" xml:"digits,omitempty" yaml:"digits,omitempty"`
    55  	Expired          bool              `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"`
    56  	ExpiredAt        time.Time         `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"`
    57  	CreatedAt        time.Time         `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"`
    58  	Disabled         bool              `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"`
    59  	DisabledAt       time.Time         `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"`
    60  	Device           *MfaDevice        `json:"device,omitempty" xml:"device,omitempty" yaml:"device,omitempty"`
    61  	Parameters       map[string]string `json:"parameters,omitempty" xml:"parameters,omitempty" yaml:"parameters,omitempty"`
    62  	Flags            map[string]bool   `json:"flags,omitempty" xml:"flags,omitempty" yaml:"flags,omitempty"`
    63  	SignatureCounter uint32            `json:"signature_counter,omitempty" xml:"signature_counter,omitempty" yaml:"signature_counter,omitempty"`
    64  	pubkey           *ecdsa.PublicKey
    65  }
    66  
    67  // MfaDevice is the hardware device associated with MfaToken.
    68  type MfaDevice struct {
    69  	Name   string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
    70  	Vendor string `json:"vendor,omitempty" xml:"vendor,omitempty" yaml:"vendor,omitempty"`
    71  	Type   string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"`
    72  }
    73  
    74  // NewMfaTokenBundle returns an instance of MfaTokenBundle.
    75  func NewMfaTokenBundle() *MfaTokenBundle {
    76  	return &MfaTokenBundle{
    77  		tokens: []*MfaToken{},
    78  	}
    79  }
    80  
    81  // Add adds MfaToken to MfaTokenBundle.
    82  func (b *MfaTokenBundle) Add(k *MfaToken) {
    83  	b.tokens = append(b.tokens, k)
    84  	b.size++
    85  }
    86  
    87  // Get returns MfaToken instances of the MfaTokenBundle.
    88  func (b *MfaTokenBundle) Get() []*MfaToken {
    89  	return b.tokens
    90  }
    91  
    92  // Size returns the number of MfaToken instances in MfaTokenBundle.
    93  func (b *MfaTokenBundle) Size() int {
    94  	return b.size
    95  }
    96  
    97  // NewMfaToken returns an instance of MfaToken.
    98  func NewMfaToken(req *requests.Request) (*MfaToken, error) {
    99  	p := &MfaToken{
   100  		ID:         GetRandomString(40),
   101  		CreatedAt:  time.Now().UTC(),
   102  		Parameters: make(map[string]string),
   103  		Flags:      make(map[string]bool),
   104  		Comment:    req.MfaToken.Comment,
   105  		Type:       req.MfaToken.Type,
   106  	}
   107  
   108  	if req.MfaToken.Disabled {
   109  		p.Disabled = true
   110  		p.DisabledAt = time.Now().UTC()
   111  	}
   112  
   113  	switch p.Type {
   114  	case "totp":
   115  		// Shared Secret
   116  		p.Secret = req.MfaToken.Secret
   117  		// Algorithm
   118  		p.Algorithm = strings.ToLower(req.MfaToken.Algorithm)
   119  		switch p.Algorithm {
   120  		case "sha1", "sha256", "sha512":
   121  		case "":
   122  			p.Algorithm = "sha1"
   123  		default:
   124  			return nil, errors.ErrMfaTokenInvalidAlgorithm.WithArgs(p.Algorithm)
   125  		}
   126  		req.MfaToken.Algorithm = p.Algorithm
   127  
   128  		// Period
   129  		p.Period = req.MfaToken.Period
   130  		if p.Period < 30 || p.Period > 300 {
   131  			return nil, errors.ErrMfaTokenInvalidPeriod.WithArgs(p.Period)
   132  		}
   133  		// Digits
   134  		p.Digits = req.MfaToken.Digits
   135  		if p.Digits == 0 {
   136  			p.Digits = 6
   137  		}
   138  		if p.Digits < 4 || p.Digits > 8 {
   139  			return nil, errors.ErrMfaTokenInvalidDigits.WithArgs(p.Digits)
   140  		}
   141  		// Codes
   142  		if err := p.ValidateCodeWithTime(req.MfaToken.Passcode, time.Now().Add(-time.Second*time.Duration(p.Period)).UTC()); err != nil {
   143  			return nil, err
   144  		}
   145  	case "u2f":
   146  		r := &WebAuthnRegisterRequest{}
   147  		if req.WebAuthn.Register == "" {
   148  			return nil, errors.ErrWebAuthnRegisterNotFound
   149  		}
   150  		if req.WebAuthn.Challenge == "" {
   151  			return nil, errors.ErrWebAuthnChallengeNotFound
   152  		}
   153  
   154  		// Decode WebAuthn Register.
   155  		decoded, err := base64.StdEncoding.DecodeString(req.WebAuthn.Register)
   156  		if err != nil {
   157  			return nil, errors.ErrWebAuthnParse.WithArgs(err)
   158  		}
   159  		if err := json.Unmarshal([]byte(decoded), r); err != nil {
   160  			return nil, errors.ErrWebAuthnParse.WithArgs(err)
   161  		}
   162  		// Set WebAuthn Challenge as Secret.
   163  		p.Secret = req.WebAuthn.Challenge
   164  
   165  		if r.ID == "" {
   166  			return nil, errors.ErrWebAuthnEmptyRegisterID
   167  		}
   168  
   169  		switch r.Type {
   170  		case "public-key":
   171  		case "":
   172  			return nil, errors.ErrWebAuthnEmptyRegisterKeyType
   173  		default:
   174  			return nil, errors.ErrWebAuthnInvalidRegisterKeyType.WithArgs(r.Type)
   175  		}
   176  
   177  		for _, tr := range r.Transports {
   178  			switch tr {
   179  			case "usb":
   180  			case "nfc":
   181  			case "ble":
   182  			case "internal":
   183  			case "":
   184  				return nil, errors.ErrWebAuthnEmptyRegisterTransport
   185  			default:
   186  				return nil, errors.ErrWebAuthnInvalidRegisterTransport.WithArgs(tr)
   187  			}
   188  		}
   189  
   190  		if r.AttestationObject == nil {
   191  			return nil, errors.ErrWebAuthnRegisterAttestationObjectNotFound
   192  		}
   193  		if r.AttestationObject.AuthData == nil {
   194  			return nil, errors.ErrWebAuthnRegisterAuthDataNotFound
   195  		}
   196  
   197  		// Extract rpIdHash from authData.
   198  		if r.AttestationObject.AuthData.RelyingPartyID == "" {
   199  			return nil, errors.ErrWebAuthnRegisterEmptyRelyingPartyID
   200  		}
   201  		p.Parameters["rp_id_hash"] = r.AttestationObject.AuthData.RelyingPartyID
   202  
   203  		// Extract flags from authData.
   204  		if r.AttestationObject.AuthData.Flags == nil {
   205  			return nil, errors.ErrWebAuthnRegisterEmptyFlags
   206  		}
   207  		for k, v := range r.AttestationObject.AuthData.Flags {
   208  			p.Flags[k] = v
   209  		}
   210  
   211  		// Extract signature counter from authData.
   212  		p.SignatureCounter = r.AttestationObject.AuthData.SignatureCounter
   213  
   214  		// Extract public key from credentialData.
   215  		if r.AttestationObject.AuthData.CredentialData == nil {
   216  			return nil, errors.ErrWebAuthnRegisterCredentialDataNotFound
   217  		}
   218  
   219  		if r.AttestationObject.AuthData.CredentialData.PublicKey == nil {
   220  			return nil, errors.ErrWebAuthnRegisterPublicKeyNotFound
   221  		}
   222  
   223  		// See https://www.iana.org/assignments/cose/cose.xhtml#key-type
   224  		var keyType string
   225  		if v, exists := r.AttestationObject.AuthData.CredentialData.PublicKey["key_type"]; exists {
   226  			switch v.(float64) {
   227  			case 2:
   228  				keyType = "ec2"
   229  			default:
   230  				return nil, errors.ErrWebAuthnRegisterPublicKeyUnsupported.WithArgs(v)
   231  			}
   232  		} else {
   233  			return nil, errors.ErrWebAuthnRegisterPublicKeyTypeNotFound
   234  		}
   235  
   236  		// See https://www.iana.org/assignments/cose/cose.xhtml#algorithms
   237  		var keyAlgo string
   238  		if v, exists := r.AttestationObject.AuthData.CredentialData.PublicKey["algorithm"]; exists {
   239  			switch v.(float64) {
   240  			case -7:
   241  				keyAlgo = "es256"
   242  			default:
   243  				return nil, errors.ErrWebAuthnRegisterPublicKeyAlgorithmUnsupported.WithArgs(v)
   244  			}
   245  		} else {
   246  			return nil, errors.ErrWebAuthnRegisterPublicKeyAlgorithmNotFound
   247  		}
   248  
   249  		// See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
   250  		var curveType, curveXcoord, curveYcoord string
   251  		if v, exists := r.AttestationObject.AuthData.CredentialData.PublicKey["curve_type"]; exists {
   252  			switch v.(float64) {
   253  			case 1:
   254  				curveType = "p256"
   255  			default:
   256  				return nil, errors.ErrWebAuthnRegisterPublicKeyCurveUnsupported.WithArgs(v)
   257  			}
   258  		}
   259  		if v, exists := r.AttestationObject.AuthData.CredentialData.PublicKey["curve_x"]; exists {
   260  			curveXcoord = v.(string)
   261  		}
   262  		if v, exists := r.AttestationObject.AuthData.CredentialData.PublicKey["curve_y"]; exists {
   263  			curveYcoord = v.(string)
   264  		}
   265  
   266  		switch keyType {
   267  		case "ec2":
   268  			switch keyAlgo {
   269  			case "es256":
   270  			default:
   271  				return nil, errors.ErrWebAuthnRegisterPublicKeyTypeAlgorithmUnsupported.WithArgs(keyType, keyAlgo)
   272  			}
   273  		}
   274  
   275  		p.Parameters["u2f_id"] = r.ID
   276  		p.Parameters["u2f_type"] = r.Type
   277  		p.Parameters["u2f_transports"] = strings.Join(r.Transports, ",")
   278  		p.Parameters["key_type"] = keyType
   279  		p.Parameters["key_algo"] = keyAlgo
   280  		p.Parameters["curve_type"] = curveType
   281  		p.Parameters["curve_xcoord"] = curveXcoord
   282  		p.Parameters["curve_ycoord"] = curveYcoord
   283  		//return nil, fmt.Errorf("XXX: %v", r.AttestationObject.AttestationStatement.Certificates)
   284  		//return nil, fmt.Errorf("XXX: %v", r.AttestationObject.AuthData.CredentialData)
   285  	case "":
   286  		return nil, errors.ErrMfaTokenTypeEmpty
   287  	default:
   288  		return nil, errors.ErrMfaTokenInvalidType.WithArgs(p.Type)
   289  	}
   290  
   291  	return p, nil
   292  }
   293  
   294  // WebAuthnRequest processes WebAuthn requests.
   295  func (p *MfaToken) WebAuthnRequest(payload string) (*WebAuthnAuthenticateRequest, error) {
   296  	switch p.Type {
   297  	case "u2f":
   298  	default:
   299  		return nil, errors.ErrWebAuthnRequest.WithArgs("unsupported token type")
   300  	}
   301  
   302  	for _, reqParam := range []string{"u2f_id", "key_type"} {
   303  		if _, exists := p.Parameters[reqParam]; !exists {
   304  			return nil, errors.ErrWebAuthnRequest.WithArgs(reqParam + " not found")
   305  		}
   306  	}
   307  
   308  	switch p.Parameters["key_type"] {
   309  	case "ec2":
   310  		if p.pubkey == nil {
   311  			if err := p.derivePublicKey(p.Parameters); err != nil {
   312  				return nil, err
   313  			}
   314  		}
   315  	default:
   316  		return nil, errors.ErrWebAuthnRequest.WithArgs("unsupported key type")
   317  	}
   318  
   319  	decoded, err := base64.StdEncoding.DecodeString(payload)
   320  	if err != nil {
   321  		return nil, errors.ErrWebAuthnParse.WithArgs(err)
   322  	}
   323  
   324  	r := &WebAuthnAuthenticateRequest{}
   325  	if err := json.Unmarshal([]byte(decoded), r); err != nil {
   326  		return nil, errors.ErrWebAuthnParse.WithArgs(err)
   327  	}
   328  
   329  	// Validate key id.
   330  	if p.Parameters["u2f_id"] != r.ID {
   331  		return r, errors.ErrWebAuthnRequest.WithArgs("key id mismatch")
   332  	}
   333  
   334  	// Decode ClientDataJSON.
   335  	if strings.TrimSpace(r.ClientDataEncoded) == "" {
   336  		return r, errors.ErrWebAuthnRequest.WithArgs("encoded client data is empty")
   337  	}
   338  	clientDataBytes, err := base64.StdEncoding.DecodeString(r.ClientDataEncoded)
   339  	if err != nil {
   340  		return r, errors.ErrWebAuthnRequest.WithArgs("failed to decode client data")
   341  	}
   342  	clientData := &ClientData{}
   343  	if err := json.Unmarshal(clientDataBytes, clientData); err != nil {
   344  		return nil, errors.ErrWebAuthnParse.WithArgs("failed to unmarshal client data")
   345  	}
   346  	r.ClientData = clientData
   347  	r.clientDataBytes = clientDataBytes
   348  	clientDataHash := sha256.Sum256(clientDataBytes)
   349  	r.ClientDataEncoded = ""
   350  	if r.ClientData == nil {
   351  		return r, errors.ErrWebAuthnRequest.WithArgs("client data is nil")
   352  	}
   353  
   354  	// Decode Signature.
   355  	if strings.TrimSpace(r.SignatureEncoded) == "" {
   356  		return r, errors.ErrWebAuthnRequest.WithArgs("encoded signature is empty")
   357  	}
   358  	signatureBytes, err := base64.StdEncoding.DecodeString(r.SignatureEncoded)
   359  	if err != nil {
   360  		return r, errors.ErrWebAuthnRequest.WithArgs("failed to decode signature")
   361  	}
   362  	r.signatureBytes = signatureBytes
   363  	r.SignatureEncoded = ""
   364  
   365  	// Decode Authenticator Data.
   366  	// See also https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data
   367  	if strings.TrimSpace(r.AuthDataEncoded) == "" {
   368  		return r, errors.ErrWebAuthnRequest.WithArgs("encoded authenticator data is empty")
   369  	}
   370  	authDataBytes, err := base64.StdEncoding.DecodeString(r.AuthDataEncoded)
   371  	if err != nil {
   372  		return r, errors.ErrWebAuthnRequest.WithArgs("failed to decode auth data")
   373  	}
   374  	if err := r.unpackAuthData(authDataBytes); err != nil {
   375  		return r, errors.ErrWebAuthnRequest.WithArgs(err)
   376  	}
   377  	r.authDataBytes = authDataBytes
   378  	if r.AuthData == nil {
   379  		return r, errors.ErrWebAuthnRequest.WithArgs("auth data is nil")
   380  	}
   381  
   382  	// Verifying an Authentication Assertion
   383  	// See also https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion
   384  
   385  	// Verify that the value of C.type is the string webauthn.get.
   386  	if r.ClientData.Type != "webauthn.get" {
   387  		return r, errors.ErrWebAuthnRequest.WithArgs("client data type is not webauthn.get")
   388  	}
   389  
   390  	// Verify that the value of C.crossOrigin is false.
   391  	if r.ClientData.CrossOrigin == true {
   392  		return r, errors.ErrWebAuthnRequest.WithArgs("client data cross origin true is not supported")
   393  	}
   394  
   395  	// TODO(greenpau): Verify that the value of C.origin matches the Relying Party's origin.
   396  
   397  	// Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by
   398  	// the Relying Party.
   399  	if r.AuthData.RelyingPartyID != p.Parameters["rp_id_hash"] {
   400  		return r, errors.ErrWebAuthnRequest.WithArgs("rpIdHash mismatch")
   401  	}
   402  
   403  	// Verify that the User Present bit of the flags in authData is set.
   404  	if r.AuthData.Flags["UP"] != true {
   405  		return r, errors.ErrWebAuthnRequest.WithArgs("authData User Present bit is not set")
   406  	}
   407  
   408  	// TODO(greenpau): If user verification is required for this assertion, verify that the User
   409  	// Verified bit of the flags in authData is set.
   410  	// This requires checking UV key in p.Flags.
   411  
   412  	// Verify signature.
   413  	signedData := append(authDataBytes, clientDataHash[:]...)
   414  	crt := &x509.Certificate{
   415  		PublicKey: p.pubkey,
   416  	}
   417  
   418  	switch p.Parameters["key_algo"] {
   419  	case "es256":
   420  		if err := crt.CheckSignature(x509.ECDSAWithSHA256, signedData, signatureBytes); err != nil {
   421  			return r, errors.ErrWebAuthnRequest.WithArgs(err)
   422  		}
   423  	default:
   424  		return r, errors.ErrWebAuthnRequest.WithArgs("failed signature verification due to unsupported algo")
   425  	}
   426  
   427  	return r, nil
   428  }
   429  
   430  // Disable disables MfaToken instance.
   431  func (p *MfaToken) Disable() {
   432  	p.Expired = true
   433  	p.ExpiredAt = time.Now().UTC()
   434  	p.Disabled = true
   435  	p.DisabledAt = time.Now().UTC()
   436  }
   437  
   438  // ValidateCode validates a passcode
   439  func (p *MfaToken) ValidateCode(code string) error {
   440  	switch p.Type {
   441  	case "totp":
   442  	default:
   443  		return errors.ErrMfaTokenInvalidPasscode.WithArgs("unsupported token type")
   444  	}
   445  	ts := time.Now().UTC()
   446  	return p.ValidateCodeWithTime(code, ts)
   447  }
   448  
   449  // ValidateCodeWithTime validates a passcode at a particular time.
   450  func (p *MfaToken) ValidateCodeWithTime(code string, ts time.Time) error {
   451  	code = strings.TrimSpace(code)
   452  	if code == "" {
   453  		return errors.ErrMfaTokenInvalidPasscode.WithArgs("empty")
   454  	}
   455  	if len(code) < 4 || len(code) > 8 {
   456  		return errors.ErrMfaTokenInvalidPasscode.WithArgs("not 4-8 characters long")
   457  	}
   458  	if len(code) != p.Digits {
   459  		return errors.ErrMfaTokenInvalidPasscode.WithArgs("digits length mismatch")
   460  	}
   461  	tp := uint64(math.Floor(float64(ts.Unix()) / float64(p.Period)))
   462  	tps := []uint64{}
   463  	tps = append(tps, tp)
   464  	tps = append(tps, tp+uint64(1))
   465  	tps = append(tps, tp-uint64(1))
   466  	for _, uts := range tps {
   467  		localCode, err := generateMfaCode(p.Secret, p.Algorithm, p.Digits, uts)
   468  		if err != nil {
   469  			continue
   470  		}
   471  		if subtle.ConstantTimeCompare([]byte(localCode), []byte(code)) == 1 {
   472  			return nil
   473  		}
   474  	}
   475  	return errors.ErrMfaTokenInvalidPasscode.WithArgs("failed")
   476  }
   477  
   478  func generateMfaCode(secret, algo string, digits int, ts uint64) (string, error) {
   479  	var mac hash.Hash
   480  	secretBytes := []byte(secret)
   481  	switch algo {
   482  	case "sha1":
   483  		mac = hmac.New(sha1.New, secretBytes)
   484  	case "sha256":
   485  		mac = hmac.New(sha256.New, secretBytes)
   486  	case "sha512":
   487  		mac = hmac.New(sha512.New, secretBytes)
   488  	case "":
   489  		return "", errors.ErrMfaTokenEmptyAlgorithm
   490  	default:
   491  		return "", errors.ErrMfaTokenInvalidAlgorithm.WithArgs(algo)
   492  	}
   493  
   494  	buf := make([]byte, 8)
   495  	binary.BigEndian.PutUint64(buf, ts)
   496  	mac.Write(buf)
   497  	sum := mac.Sum(nil)
   498  
   499  	off := sum[len(sum)-1] & 0xf
   500  	val := int64(((int(sum[off]) & 0x7f) << 24) |
   501  		((int(sum[off+1] & 0xff)) << 16) |
   502  		((int(sum[off+2] & 0xff)) << 8) |
   503  		(int(sum[off+3]) & 0xff))
   504  	mod := int32(val % int64(math.Pow10(digits)))
   505  	wrap := fmt.Sprintf("%%0%dd", digits)
   506  	return fmt.Sprintf(wrap, mod), nil
   507  }
   508  
   509  func (p *MfaToken) derivePublicKey(params map[string]string) error {
   510  	for _, reqParam := range []string{"curve_xcoord", "curve_ycoord", "key_algo"} {
   511  		if _, exists := params[reqParam]; !exists {
   512  			return errors.ErrWebAuthnRequest.WithArgs(reqParam + " not found")
   513  		}
   514  	}
   515  
   516  	var coords []*big.Int
   517  	for _, ltr := range []string{"x", "y"} {
   518  		coord := "curve_" + ltr + "coord"
   519  		b, err := base64.StdEncoding.DecodeString(params[coord])
   520  		if err != nil {
   521  			return errors.ErrWebAuthnRegisterPublicKeyCurveCoord.WithArgs(ltr, err)
   522  		}
   523  		if len(b) != 32 {
   524  			return errors.ErrWebAuthnRegisterPublicKeyCurveCoord.WithArgs(ltr, "not 32 bytes in length")
   525  		}
   526  		i := new(big.Int)
   527  		i.SetBytes(b)
   528  		coords = append(coords, i)
   529  	}
   530  
   531  	switch params["key_algo"] {
   532  	case "es256":
   533  		p.pubkey = &ecdsa.PublicKey{Curve: elliptic.P256(), X: coords[0], Y: coords[1]}
   534  	default:
   535  		return errors.ErrWebAuthnRegisterPublicKeyAlgorithmUnsupported.WithArgs(params["key_algo"])
   536  	}
   537  	return nil
   538  }
   539  
   540  func (r *WebAuthnAuthenticateRequest) unpackAuthData(b []byte) error {
   541  	data := new(AuthData)
   542  	if len(b) < 37 {
   543  		return fmt.Errorf("auth data is less than 37 bytes long")
   544  	}
   545  	data.RelyingPartyID = fmt.Sprintf("%x", b[0:32])
   546  	data.Flags = make(map[string]bool)
   547  	for _, st := range []struct {
   548  		k string
   549  		v byte
   550  	}{
   551  		{"UP", 0x001},
   552  		{"RFU1", 0x002},
   553  		{"UV", 0x004},
   554  		{"RFU2a", 0x008},
   555  		{"RFU2b", 0x010},
   556  		{"RFU2c", 0x020},
   557  		{"AT", 0x040},
   558  		{"ED", 0x080},
   559  	} {
   560  		if (b[32] & st.v) == st.v {
   561  			data.Flags[st.k] = true
   562  		} else {
   563  			data.Flags[st.k] = false
   564  		}
   565  	}
   566  	data.SignatureCounter = binary.BigEndian.Uint32(b[33:37])
   567  
   568  	// TODO(greenpau): implement AT parser.
   569  	// if (data.Flags["AT"] == true) && len(b) > 37 {
   570  	//   // Extract attested credentials data.
   571  	// }
   572  
   573  	r.AuthData = data
   574  	return nil
   575  }