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.

client.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package protocol
  2. import (
  3. "fmt"
  4. "net/url"
  5. "strings"
  6. )
  7. // CollectedClientData represents the contextual bindings of both the WebAuthn Relying Party
  8. // and the client. It is a key-value mapping whose keys are strings. Values can be any type
  9. // that has a valid encoding in JSON. Its structure is defined by the following Web IDL.
  10. // https://www.w3.org/TR/webauthn/#sec-client-data
  11. type CollectedClientData struct {
  12. // Type the string "webauthn.create" when creating new credentials,
  13. // and "webauthn.get" when getting an assertion from an existing credential. The
  14. // purpose of this member is to prevent certain types of signature confusion attacks
  15. //(where an attacker substitutes one legitimate signature for another).
  16. Type CeremonyType `json:"type"`
  17. Challenge string `json:"challenge"`
  18. Origin string `json:"origin"`
  19. TokenBinding *TokenBinding `json:"tokenBinding,omitempty"`
  20. // Chromium (Chrome) returns a hint sometimes about how to handle clientDataJSON in a safe manner
  21. Hint string `json:"new_keys_may_be_added_here,omitempty"`
  22. }
  23. type CeremonyType string
  24. const (
  25. CreateCeremony CeremonyType = "webauthn.create"
  26. AssertCeremony CeremonyType = "webauthn.get"
  27. )
  28. type TokenBinding struct {
  29. Status TokenBindingStatus `json:"status"`
  30. ID string `json:"id,omitempty"`
  31. }
  32. type TokenBindingStatus string
  33. const (
  34. // Indicates token binding was used when communicating with the
  35. // Relying Party. In this case, the id member MUST be present.
  36. Present TokenBindingStatus = "present"
  37. // Indicates token binding was used when communicating with the
  38. // negotiated when communicating with the Relying Party.
  39. Supported TokenBindingStatus = "supported"
  40. // Indicates token binding not supported
  41. // when communicating with the Relying Party.
  42. NotSupported TokenBindingStatus = "not-supported"
  43. )
  44. // Returns the origin per the HTML spec: (scheme)://(host)[:(port)]
  45. func FullyQualifiedOrigin(u *url.URL) string {
  46. return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
  47. }
  48. // Handles steps 3 through 6 of verfying the registering client data of a
  49. // new credential and steps 7 through 10 of verifying an authentication assertion
  50. // See https://www.w3.org/TR/webauthn/#registering-a-new-credential
  51. // and https://www.w3.org/TR/webauthn/#verifying-assertion
  52. func (c *CollectedClientData) Verify(storedChallenge string, ceremony CeremonyType, relyingPartyOrigin string) error {
  53. // Registration Step 3. Verify that the value of C.type is webauthn.create.
  54. // Assertion Step 7. Verify that the value of C.type is the string webauthn.get.
  55. if c.Type != ceremony {
  56. err := ErrVerification.WithDetails("Error validating ceremony type")
  57. err.WithInfo(fmt.Sprintf("Expected Value: %s\n Received: %s\n", ceremony, c.Type))
  58. return err
  59. }
  60. // Registration Step 4. Verify that the value of C.challenge matches the challenge
  61. // that was sent to the authenticator in the create() call.
  62. // Assertion Step 8. Verify that the value of C.challenge matches the challenge
  63. // that was sent to the authenticator in the PublicKeyCredentialRequestOptions
  64. // passed to the get() call.
  65. challenge := c.Challenge
  66. if 0 != strings.Compare(storedChallenge, challenge) {
  67. err := ErrVerification.WithDetails("Error validating challenge")
  68. return err.WithInfo(fmt.Sprintf("Expected b Value: %#v\nReceived b: %#v\n", storedChallenge, challenge))
  69. }
  70. // Registration Step 5 & Assertion Step 9. Verify that the value of C.origin matches
  71. // the Relying Party's origin.
  72. clientDataOrigin, err := url.Parse(c.Origin)
  73. if err != nil {
  74. return ErrParsingData.WithDetails("Error decoding clientData origin as URL")
  75. }
  76. if !strings.EqualFold(FullyQualifiedOrigin(clientDataOrigin), relyingPartyOrigin) {
  77. err := ErrVerification.WithDetails("Error validating origin")
  78. return err.WithInfo(fmt.Sprintf("Expected Value: %s\n Received: %s\n", relyingPartyOrigin, FullyQualifiedOrigin(clientDataOrigin)))
  79. }
  80. // Registration Step 6 and Assertion Step 10. Verify that the value of C.tokenBinding.status
  81. // matches the state of Token Binding for the TLS connection over which the assertion was
  82. // obtained. If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id
  83. // matches the base64url encoding of the Token Binding ID for the connection.
  84. if c.TokenBinding != nil {
  85. if c.TokenBinding.Status == "" {
  86. return ErrParsingData.WithDetails("Error decoding clientData, token binding present without status")
  87. }
  88. if c.TokenBinding.Status != Present && c.TokenBinding.Status != Supported && c.TokenBinding.Status != NotSupported {
  89. return ErrParsingData.WithDetails("Error decoding clientData, token binding present with invalid status").WithInfo(fmt.Sprintf("Got: %s\n", c.TokenBinding.Status))
  90. }
  91. }
  92. // Not yet fully implemented by the spec, browsers, and me.
  93. return nil
  94. }