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