add more documentation, examples with tests to illustrate reusable components

This commit is contained in:
Mechiel Lukkien
2023-12-12 15:47:26 +01:00
parent 810cbdc61d
commit d1b66035a9
40 changed files with 973 additions and 119 deletions

70
scram/examples_test.go Normal file
View File

@ -0,0 +1,70 @@
package scram_test
import (
"crypto/sha256"
"fmt"
"log"
"github.com/mjl-/mox/scram"
)
func Example() {
// Prepare credentials.
//
// The client normally remembers the password and uses it during authentication.
//
// The server sets the iteration count, generates a salt and uses the password once
// to generate salted password hash. The salted password hash is used to
// authenticate the client during authentication.
iterations := 4096
salt := scram.MakeRandom()
password := "test1234"
saltedPassword := scram.SaltPassword(sha256.New, password, salt, iterations)
check := func(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
// Make a new client for authenticating user mjl with SCRAM-SHA-256.
username := "mjl"
authz := ""
client := scram.NewClient(sha256.New, username, authz)
clientFirst, err := client.ClientFirst()
check(err, "client.ClientFirst")
// Instantia a new server with the initial message from the client.
server, err := scram.NewServer(sha256.New, []byte(clientFirst))
check(err, "NewServer")
// Generate first message from server to client, with a challenge.
serverFirst, err := server.ServerFirst(iterations, salt)
check(err, "server.ServerFirst")
// Continue at client with first message from server, resulting in message from
// client to server.
clientFinal, err := client.ServerFirst([]byte(serverFirst), password)
check(err, "client.ServerFirst")
// Continue at server with message from client.
// The server authenticates the client in this step.
serverFinal, err := server.Finish([]byte(clientFinal), saltedPassword)
if err != nil {
fmt.Println("server does not accept client credentials")
} else {
fmt.Println("server has accepted client credentials")
}
// Finally, the client verifies that the server knows the salted password hash.
err = client.ServerFinal([]byte(serverFinal))
if err != nil {
fmt.Println("client does not accept server")
} else {
fmt.Println("client has accepted server")
}
// Output:
// server has accepted client credentials
// client has accepted server
}

View File

@ -2,7 +2,8 @@
//
// SCRAM-SHA-256 and SCRAM-SHA-1 allow a client to authenticate to a server using a
// password without handing plaintext password over to the server. The client also
// verifies the server knows (a derivative of) the password.
// verifies the server knows (a derivative of) the password. Both the client and
// server side are implemented.
package scram
// todo: test with messages that contains extensions
@ -88,8 +89,8 @@ func SaltPassword(h func() hash.Hash, password string, salt []byte, iterations i
return pbkdf2.Key([]byte(password), salt, iterations, h().Size(), h)
}
// HMAC returns the hmac with key over msg.
func HMAC(h func() hash.Hash, key []byte, msg string) []byte {
// hmac0 returns the hmac with key over msg.
func hmac0(h func() hash.Hash, key []byte, msg string) []byte {
mac := hmac.New(h, key)
mac.Write([]byte(msg))
return mac.Sum(nil)
@ -211,19 +212,19 @@ func (s *Server) Finish(clientFinal []byte, saltedPassword []byte) (serverFinal
msg := s.clientFirstBare + "," + s.serverFirst + "," + s.clientFinalWithoutProof
clientKey := HMAC(s.h, saltedPassword, "Client Key")
clientKey := hmac0(s.h, saltedPassword, "Client Key")
h := s.h()
h.Write(clientKey)
storedKey := h.Sum(nil)
clientSig := HMAC(s.h, storedKey, msg)
clientSig := hmac0(s.h, storedKey, msg)
xor(clientSig, clientKey) // Now clientProof.
if !bytes.Equal(clientSig, proof) {
return "e=" + string(ErrInvalidProof), ErrInvalidProof
}
serverKey := HMAC(s.h, saltedPassword, "Server Key")
serverSig := HMAC(s.h, serverKey, msg)
serverKey := hmac0(s.h, saltedPassword, "Server Key")
serverSig := hmac0(s.h, serverKey, msg)
return fmt.Sprintf("v=%s", base64.StdEncoding.EncodeToString(serverSig)), nil
}
@ -321,11 +322,11 @@ func (c *Client) ServerFirst(serverFirst []byte, password string) (clientFinal s
c.authMessage = c.clientFirstBare + "," + c.serverFirst + "," + c.clientFinalWithoutProof
c.saltedPassword = SaltPassword(c.h, password, salt, iterations)
clientKey := HMAC(c.h, c.saltedPassword, "Client Key")
clientKey := hmac0(c.h, c.saltedPassword, "Client Key")
h := c.h()
h.Write(clientKey)
storedKey := h.Sum(nil)
clientSig := HMAC(c.h, storedKey, c.authMessage)
clientSig := hmac0(c.h, storedKey, c.authMessage)
xor(clientSig, clientKey) // Now clientProof.
clientProof := clientSig
@ -350,8 +351,8 @@ func (c *Client) ServerFinal(serverFinal []byte) (rerr error) {
p.xtake("v=")
verifier := p.xbase64()
serverKey := HMAC(c.h, c.saltedPassword, "Server Key")
serverSig := HMAC(c.h, serverKey, c.authMessage)
serverKey := hmac0(c.h, c.saltedPassword, "Server Key")
serverSig := hmac0(c.h, serverKey, c.authMessage)
if !bytes.Equal(verifier, serverSig) {
return fmt.Errorf("incorrect server signature")
}