As an exercise, I'm trying to implement a mock SMTP server with CRAM-MD5 authentication in Go (without following RFC 2195, since it looks like it doesn't matter to the client what format the pre-hashed challenge is in; I also assume there is only one user "bob" with password "pass"). But I can't seem to get it right as the hash in response is always different from what I have on the server. I send the email using Go as such (running it as a separate package):
{...}
smtp.SendMail("localhost:25", smtp.CRAMMD5Auth("bob", "pass"),
"bob@localhost", []string{"alice@localhost"}, []byte("Hey Alice!
"))
{...}
Here's what I do when I get the authentication acknowledgement from the client:
{...}
case strings.Contains(ms, "AUTH CRAM-MD5"):
rndbts = make([]byte, 16) // Declared at package level
b64b := make([]byte, base64.StdEncoding.EncodedLen(16))
rand.Read(rndbts)
base64.StdEncoding.Encode(b64b, rndbts)
_, err = conn.Write([]byte(fmt.Sprintf("334 %x
", b64b)))
{...}
And this is what I do with the client's response:
{...}
{
ms = strings.TrimRight(ms, "
") // The response to the challenge
ds, _ := base64.StdEncoding.DecodeString(ms)
s := strings.Split(string(ds), " ")
login := s[0] // I can get the login from the response.
h := hmac.New(md5.New, []byte("pass"))
h.Write(rndbts)
c := make([]byte, 0, ourHash.Size()) // From smtp/auth.go, not sure why we need this.
validPass := hmac.Equal(h.Sum(c), []byte(s[1]))
{...}
}
{...}
And the validPass
is never true
. I omitted error handling from the excerpts for brevity, but they're there in the actual code (though they're always nil
). Why are the hashes different? I have looked at the source code for net/smtp, and it seems to me that I'm going in the right direction, but not quite.