您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

client_conv.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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. "strings"
  13. )
  14. type clientState int
  15. const (
  16. clientStarting clientState = iota
  17. clientFirst
  18. clientFinal
  19. clientDone
  20. )
  21. // ClientConversation implements the client-side of an authentication
  22. // conversation with a server. A new conversation must be created for
  23. // each authentication attempt.
  24. type ClientConversation struct {
  25. client *Client
  26. nonceGen NonceGeneratorFcn
  27. hashGen HashGeneratorFcn
  28. minIters int
  29. state clientState
  30. valid bool
  31. gs2 string
  32. nonce string
  33. c1b string
  34. serveSig []byte
  35. }
  36. // Step takes a string provided from a server (or just an empty string for the
  37. // very first conversation step) and attempts to move the authentication
  38. // conversation forward. It returns a string to be sent to the server or an
  39. // error if the server message is invalid. Calling Step after a conversation
  40. // completes is also an error.
  41. func (cc *ClientConversation) Step(challenge string) (response string, err error) {
  42. switch cc.state {
  43. case clientStarting:
  44. cc.state = clientFirst
  45. response, err = cc.firstMsg()
  46. case clientFirst:
  47. cc.state = clientFinal
  48. response, err = cc.finalMsg(challenge)
  49. case clientFinal:
  50. cc.state = clientDone
  51. response, err = cc.validateServer(challenge)
  52. default:
  53. response, err = "", errors.New("Conversation already completed")
  54. }
  55. return
  56. }
  57. // Done returns true if the conversation is completed or has errored.
  58. func (cc *ClientConversation) Done() bool {
  59. return cc.state == clientDone
  60. }
  61. // Valid returns true if the conversation successfully authenticated with the
  62. // server, including counter-validation that the server actually has the
  63. // user's stored credentials.
  64. func (cc *ClientConversation) Valid() bool {
  65. return cc.valid
  66. }
  67. func (cc *ClientConversation) firstMsg() (string, error) {
  68. // Values are cached for use in final message parameters
  69. cc.gs2 = cc.gs2Header()
  70. cc.nonce = cc.client.nonceGen()
  71. cc.c1b = fmt.Sprintf("n=%s,r=%s", encodeName(cc.client.username), cc.nonce)
  72. return cc.gs2 + cc.c1b, nil
  73. }
  74. func (cc *ClientConversation) finalMsg(s1 string) (string, error) {
  75. msg, err := parseServerFirst(s1)
  76. if err != nil {
  77. return "", err
  78. }
  79. // Check nonce prefix and update
  80. if !strings.HasPrefix(msg.nonce, cc.nonce) {
  81. return "", errors.New("server nonce did not extend client nonce")
  82. }
  83. cc.nonce = msg.nonce
  84. // Check iteration count vs minimum
  85. if msg.iters < cc.minIters {
  86. return "", fmt.Errorf("server requested too few iterations (%d)", msg.iters)
  87. }
  88. // Create client-final-message-without-proof
  89. c2wop := fmt.Sprintf(
  90. "c=%s,r=%s",
  91. base64.StdEncoding.EncodeToString([]byte(cc.gs2)),
  92. cc.nonce,
  93. )
  94. // Create auth message
  95. authMsg := cc.c1b + "," + s1 + "," + c2wop
  96. // Get derived keys from client cache
  97. dk := cc.client.getDerivedKeys(KeyFactors{Salt: string(msg.salt), Iters: msg.iters})
  98. // Create proof as clientkey XOR clientsignature
  99. clientSignature := computeHMAC(cc.hashGen, dk.StoredKey, []byte(authMsg))
  100. clientProof := xorBytes(dk.ClientKey, clientSignature)
  101. proof := base64.StdEncoding.EncodeToString(clientProof)
  102. // Cache ServerSignature for later validation
  103. cc.serveSig = computeHMAC(cc.hashGen, dk.ServerKey, []byte(authMsg))
  104. return fmt.Sprintf("%s,p=%s", c2wop, proof), nil
  105. }
  106. func (cc *ClientConversation) validateServer(s2 string) (string, error) {
  107. msg, err := parseServerFinal(s2)
  108. if err != nil {
  109. return "", err
  110. }
  111. if len(msg.err) > 0 {
  112. return "", fmt.Errorf("server error: %s", msg.err)
  113. }
  114. if !hmac.Equal(msg.verifier, cc.serveSig) {
  115. return "", errors.New("server validation failed")
  116. }
  117. cc.valid = true
  118. return "", nil
  119. }
  120. func (cc *ClientConversation) gs2Header() string {
  121. if cc.client.authzID == "" {
  122. return "n,,"
  123. }
  124. return fmt.Sprintf("n,%s,", encodeName(cc.client.authzID))
  125. }