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.

server_conv.go 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Copyright 2018 by David A. Golden. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. package scram
  7. import (
  8. "crypto/hmac"
  9. "encoding/base64"
  10. "errors"
  11. "fmt"
  12. )
  13. type serverState int
  14. const (
  15. serverFirst serverState = iota
  16. serverFinal
  17. serverDone
  18. )
  19. // ServerConversation implements the server-side of an authentication
  20. // conversation with a client. A new conversation must be created for
  21. // each authentication attempt.
  22. type ServerConversation struct {
  23. nonceGen NonceGeneratorFcn
  24. hashGen HashGeneratorFcn
  25. credentialCB CredentialLookup
  26. state serverState
  27. credential StoredCredentials
  28. valid bool
  29. gs2Header string
  30. username string
  31. authzID string
  32. nonce string
  33. c1b string
  34. s1 string
  35. }
  36. // Step takes a string provided from a client and attempts to move the
  37. // authentication conversation forward. It returns a string to be sent to the
  38. // client or an error if the client message is invalid. Calling Step after a
  39. // conversation completes is also an error.
  40. func (sc *ServerConversation) Step(challenge string) (response string, err error) {
  41. switch sc.state {
  42. case serverFirst:
  43. sc.state = serverFinal
  44. response, err = sc.firstMsg(challenge)
  45. case serverFinal:
  46. sc.state = serverDone
  47. response, err = sc.finalMsg(challenge)
  48. default:
  49. response, err = "", errors.New("Conversation already completed")
  50. }
  51. return
  52. }
  53. // Done returns true if the conversation is completed or has errored.
  54. func (sc *ServerConversation) Done() bool {
  55. return sc.state == serverDone
  56. }
  57. // Valid returns true if the conversation successfully authenticated the
  58. // client.
  59. func (sc *ServerConversation) Valid() bool {
  60. return sc.valid
  61. }
  62. // Username returns the client-provided username. This is valid to call
  63. // if the first conversation Step() is successful.
  64. func (sc *ServerConversation) Username() string {
  65. return sc.username
  66. }
  67. // AuthzID returns the (optional) client-provided authorization identity, if
  68. // any. If one was not provided, it returns the empty string. This is valid
  69. // to call if the first conversation Step() is successful.
  70. func (sc *ServerConversation) AuthzID() string {
  71. return sc.authzID
  72. }
  73. func (sc *ServerConversation) firstMsg(c1 string) (string, error) {
  74. msg, err := parseClientFirst(c1)
  75. if err != nil {
  76. sc.state = serverDone
  77. return "", err
  78. }
  79. sc.gs2Header = msg.gs2Header
  80. sc.username = msg.username
  81. sc.authzID = msg.authzID
  82. sc.credential, err = sc.credentialCB(msg.username)
  83. if err != nil {
  84. sc.state = serverDone
  85. return "e=unknown-user", err
  86. }
  87. sc.nonce = msg.nonce + sc.nonceGen()
  88. sc.c1b = msg.c1b
  89. sc.s1 = fmt.Sprintf("r=%s,s=%s,i=%d",
  90. sc.nonce,
  91. base64.StdEncoding.EncodeToString([]byte(sc.credential.Salt)),
  92. sc.credential.Iters,
  93. )
  94. return sc.s1, nil
  95. }
  96. // For errors, returns server error message as well as non-nil error. Callers
  97. // can choose whether to send server error or not.
  98. func (sc *ServerConversation) finalMsg(c2 string) (string, error) {
  99. msg, err := parseClientFinal(c2)
  100. if err != nil {
  101. return "", err
  102. }
  103. // Check channel binding matches what we expect; in this case, we expect
  104. // just the gs2 header we received as we don't support channel binding
  105. // with a data payload. If we add binding, we need to independently
  106. // compute the header to match here.
  107. if string(msg.cbind) != sc.gs2Header {
  108. return "e=channel-bindings-dont-match", fmt.Errorf("channel binding received '%s' doesn't match expected '%s'", msg.cbind, sc.gs2Header)
  109. }
  110. // Check nonce received matches what we sent
  111. if msg.nonce != sc.nonce {
  112. return "e=other-error", errors.New("nonce received did not match nonce sent")
  113. }
  114. // Create auth message
  115. authMsg := sc.c1b + "," + sc.s1 + "," + msg.c2wop
  116. // Retrieve ClientKey from proof and verify it
  117. clientSignature := computeHMAC(sc.hashGen, sc.credential.StoredKey, []byte(authMsg))
  118. clientKey := xorBytes([]byte(msg.proof), clientSignature)
  119. storedKey := computeHash(sc.hashGen, clientKey)
  120. // Compare with constant-time function
  121. if !hmac.Equal(storedKey, sc.credential.StoredKey) {
  122. return "e=invalid-proof", errors.New("challenge proof invalid")
  123. }
  124. sc.valid = true
  125. // Compute and return server verifier
  126. serverSignature := computeHMAC(sc.hashGen, sc.credential.ServerKey, []byte(authMsg))
  127. return "v=" + base64.StdEncoding.EncodeToString(serverSignature), nil
  128. }