xsrftoken: escape colons

The current clean() replaces : with _ (colons are internally used as
separators).
This produce can produce same output for different inputs, for example
the user _foo_ can obtain valid tokens for user :foo:.

This CL replace colons with double colons instead of replacing them
with underscores.

Fixes golang/go#34308

Change-Id: I3e4148a0836e62fda1a5f0ba32b375121368afd3
Reviewed-on: https://go-review.googlesource.com/c/net/+/196457
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/xsrftoken/xsrf.go b/xsrftoken/xsrf.go
index bc861e1..4f66adf 100644
--- a/xsrftoken/xsrf.go
+++ b/xsrftoken/xsrf.go
@@ -20,9 +20,9 @@
 // It is exported so clients may set cookie timeouts that match generated tokens.
 const Timeout = 24 * time.Hour
 
-// clean sanitizes a string for inclusion in a token by replacing all ":"s.
+// clean sanitizes a string for inclusion in a token by replacing all ":" with "::".
 func clean(s string) string {
-	return strings.Replace(s, ":", "_", -1)
+	return strings.Replace(s, `:`, `::`, -1)
 }
 
 // Generate returns a URL-safe secure XSRF token that expires in 24 hours.
diff --git a/xsrftoken/xsrf_test.go b/xsrftoken/xsrf_test.go
index 6c8e7d9..fc0a48a 100644
--- a/xsrftoken/xsrf_test.go
+++ b/xsrftoken/xsrf_test.go
@@ -36,10 +36,32 @@
 
 // TestSeparatorReplacement tests that separators are being correctly substituted
 func TestSeparatorReplacement(t *testing.T) {
-	tok := generateTokenAtTime("foo:bar", "baz", "wah", now)
-	tok2 := generateTokenAtTime("foo", "bar:baz", "wah", now)
-	if tok == tok2 {
-		t.Errorf("Expected generated tokens to be different")
+	separatorTests := []struct {
+		name   string
+		token1 string
+		token2 string
+	}{
+		{
+			"Colon",
+			generateTokenAtTime("foo:bar", "baz", "wah", now),
+			generateTokenAtTime("foo", "bar:baz", "wah", now),
+		},
+		{
+			"Colon and Underscore",
+			generateTokenAtTime("key", ":foo:", "wah", now),
+			generateTokenAtTime("key", "_foo_", "wah", now),
+		},
+		{
+			"Colon and Double Colon",
+			generateTokenAtTime("key", ":foo:", "wah", now),
+			generateTokenAtTime("key", "::foo::", "wah", now),
+		},
+	}
+
+	for _, st := range separatorTests {
+		if st.token1 == st.token2 {
+			t.Errorf("%v: Expected generated tokens to be different", st.name)
+		}
 	}
 }