Repository: gammazero/nexus
Pull request #255 (2022-01-15 05:52:52)

Fix WAMPCRA with salting
When a derived key is created using pbkdf2, the challenge needs to be signed with the base65-encoded derived key.  Previously the challenge was signed using the raw bytes of the key.

This fixes compatability with crossbar.

Fixes issue #252


Patch:
diff --git a/wamp/crsign/crsign.go b/wamp/crsign/crsign.go
index 216b154..6e6b08b 100644
--- a/wamp/crsign/crsign.go
+++ b/wamp/crsign/crsign.go
@@ -72,7 +72,7 @@ const (
 //     }
 //     cli, err = client.ConnectNet(routerAddr, cfg)
 //
-func RespondChallenge(pass string, c *wamp.Challenge, h func() hash.Hash) string {
+func RespondChallenge(secret string, c *wamp.Challenge, h func() hash.Hash) string {
 	ch, _ := wamp.AsString(c.Extra["challenge"])
 	// If the client needed to lookup a user's key, this would require decoding
 	// the JSON-encoded challenge string and getting the authid.  For this
@@ -81,7 +81,7 @@ func RespondChallenge(pass string, c *wamp.Challenge, h func() hash.Hash) string
 	saltStr, _ := wamp.AsString(c.Extra["salt"])
 	// If no salt given, use raw password as key.
 	if saltStr == "" {
-		return SignChallenge(ch, []byte(pass))
+		return SignChallenge(ch, []byte(secret))
 	}
 
 	// If salting info give, then compute a derived key using PBKDF2.
@@ -98,9 +98,12 @@ func RespondChallenge(pass string, c *wamp.Challenge, h func() hash.Hash) string
 	if h == nil {
 		h = sha256.New
 	}
+
 	// Compute derived key.
-	dk := pbkdf2.Key([]byte(pass), salt, int(iters), int(keylen), h)
+	dk := pbkdf2.Key([]byte(secret), salt, int(iters), int(keylen), sha256.New)
+	// Get base64 bytes of derived key.
+	derivedKey := []byte(base64.StdEncoding.EncodeToString(dk))
 
-	// Sign challenge using derived key.
-	return SignChallenge(ch, dk)
+	// Sign the challenge with the base64-encoded derived key.
+	return SignChallenge(ch, derivedKey)
 }


Test patch:
diff --git a/aat/auth_test.go b/aat/auth_test.go
index 523cca1..903bde0 100644
--- a/aat/auth_test.go
+++ b/aat/auth_test.go
@@ -3,6 +3,7 @@ package aat
 import (
 	"context"
 	"crypto/sha256"
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"net/http"
@@ -231,9 +232,10 @@ func (ks *serverKeyStore) AuthKey(authid, authmethod string) ([]byte, error) {
 	switch authmethod {
 	case "wampcra":
 		// Lookup the user's key.
-		pw := []byte(password)
+		secret := []byte(password)
 		salt := []byte(pwSalt)
-		return pbkdf2.Key(pw, salt, iterations, keylen, sha256.New), nil
+		dk := pbkdf2.Key([]byte(secret), salt, iterations, keylen, sha256.New)
+		return []byte(base64.StdEncoding.EncodeToString(dk)), nil
 	case "ticket":
 		// Lookup the user's key.
 		return []byte("ticketforjoe1234"), nil
diff --git a/wamp/crsign/crsign_test.go b/wamp/crsign/crsign_test.go
index f24afb0..c57bbe1 100644
--- a/wamp/crsign/crsign_test.go
+++ b/wamp/crsign/crsign_test.go
@@ -2,6 +2,7 @@ package crsign
 
 import (
 	"crypto/sha256"
+	"encoding/base64"
 	"testing"
 
 	"github.com/gammazero/nexus/v3/wamp"
@@ -19,11 +20,14 @@ func TestCRSign(t *testing.T) {
 
 func TestRespondChallenge(t *testing.T) {
 	salt := []byte("salt123")
-	pass := "password"
+	secret := "password"
 
 	// Compute derived key.  Normally this would normally be precomputed and
 	// the router would read it and the salting from from storage.
-	dk := pbkdf2.Key([]byte(pass), salt, defaultIters, defaultKeyLen, sha256.New)
+	// Compute derived key.
+	dk := pbkdf2.Key([]byte(secret), salt, defaultIters, defaultKeyLen, sha256.New)
+	// Get base64 bytes of derived key.
+	derivedKey := []byte(base64.StdEncoding.EncodeToString(dk))
 
 	// Server creates CHALLENGE message containing challenge string and salting
 	// info that was used to create derived key.
@@ -38,17 +42,17 @@ func TestRespondChallenge(t *testing.T) {
 
 	// Client computes derived key from password and salting info, then signes
 	// challenge using derived key.  Response gets sent back to router.
-	sigClient := RespondChallenge(pass, chMsg, nil)
+	sigClient := RespondChallenge(secret, chMsg, nil)
 
 	// Router computes its own signature for the challenge and compares it with
 	// the client's.Sign challenge using derived key.
-	sigServer := SignChallenge(chStr, dk)
+	sigServer := SignChallenge(chStr, derivedKey)
 	if sigClient != sigServer {
 		t.Fatal("Client and server signatures do not match")
 	}
 
 	// Check that signature was what was expected.
-	if sigServer != "qczWyhq0mAtfNeqPCxorzlcz0t4jns97XBUHoTu2Brs=" {
+	if sigServer != "hk/2riA2JqydfL5wLoicrYfAt8uNeP6nikk9kqDhsnM=" {
 		t.Fatal("Wrong signature:", sigServer)
 	}
 }