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