xsrftoken: copy from code.google.com/p/xsrftoken

Change-Id: Idd7bad48e585289740327b00f692fde948a2eeb0
Reviewed-on: https://go-review.googlesource.com/17662
Reviewed-by: David Symonds <dsymonds@golang.org>
Run-TryBot: David Symonds <dsymonds@golang.org>
diff --git a/xsrftoken/xsrf.go b/xsrftoken/xsrf.go
new file mode 100644
index 0000000..cb004a6
--- /dev/null
+++ b/xsrftoken/xsrf.go
@@ -0,0 +1,87 @@
+// Copyright 2012 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package xsrftoken provides methods for generating and validating secure XSRF tokens.
+package xsrftoken
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"crypto/subtle"
+	"encoding/base64"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// The duration that XSRF tokens are valid.
+// 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.
+func clean(s string) string {
+	return strings.Replace(s, ":", "_", -1)
+}
+
+// Generate returns a URL-safe secure XSRF token that expires in 24 hours.
+//
+// key is a secret key for your application.
+// userID is a unique identifier for the user.
+// actionID is the action the user is taking (e.g. POSTing to a particular path).
+func Generate(key, userID, actionID string) string {
+	return generateTokenAtTime(key, userID, actionID, time.Now())
+}
+
+// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
+func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
+	h := hmac.New(sha1.New, []byte(key))
+	fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano())
+	tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano())
+	return base64.URLEncoding.EncodeToString([]byte(tok))
+}
+
+// Valid returns true if token is a valid, unexpired token returned by Generate.
+func Valid(token, key, userID, actionID string) bool {
+	return validTokenAtTime(token, key, userID, actionID, time.Now())
+}
+
+// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
+func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
+	// Decode the token.
+	data, err := base64.URLEncoding.DecodeString(token)
+	if err != nil {
+		return false
+	}
+
+	// Extract the issue time of the token.
+	sep := bytes.LastIndex(data, []byte{':'})
+	if sep < 0 {
+		return false
+	}
+	nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64)
+	if err != nil {
+		return false
+	}
+	issueTime := time.Unix(0, nanos)
+
+	// Check that the token is not expired.
+	if now.Sub(issueTime) >= Timeout {
+		return false
+	}
+
+	// Check that the token is not from the future.
+	// Allow 1 minute grace period in case the token is being verified on a
+	// machine whose clock is behind the machine that issued the token.
+	if issueTime.After(now.Add(1 * time.Minute)) {
+		return false
+	}
+
+	expected := generateTokenAtTime(key, userID, actionID, issueTime)
+
+	// Check that the token matches the expected value.
+	// Use constant time comparison to avoid timing attacks.
+	return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
+}
diff --git a/xsrftoken/xsrf_test.go b/xsrftoken/xsrf_test.go
new file mode 100644
index 0000000..8c916eb
--- /dev/null
+++ b/xsrftoken/xsrf_test.go
@@ -0,0 +1,82 @@
+// Copyright 2012 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xsrftoken
+
+import (
+	"encoding/base64"
+	"testing"
+	"time"
+)
+
+const (
+	key      = "quay"
+	userID   = "12345678"
+	actionID = "POST /form"
+)
+
+var (
+	now              = time.Now()
+	oneMinuteFromNow = now.Add(1 * time.Minute)
+)
+
+func TestValidToken(t *testing.T) {
+	tok := generateTokenAtTime(key, userID, actionID, now)
+	if !validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow) {
+		t.Error("One second later: Expected token to be valid")
+	}
+	if !validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond)) {
+		t.Error("Just before timeout: Expected token to be valid")
+	}
+	if !validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute)) {
+		t.Error("One minute in the past: Expected token to be valid")
+	}
+}
+
+// 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")
+	}
+}
+
+func TestInvalidToken(t *testing.T) {
+	invalidTokenTests := []struct {
+		name, key, userID, actionID string
+		t                           time.Time
+	}{
+		{"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
+		{"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
+		{"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
+		{"Expired", key, userID, actionID, now.Add(Timeout)},
+		{"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
+	}
+
+	tok := generateTokenAtTime(key, userID, actionID, now)
+	for _, itt := range invalidTokenTests {
+		if validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t) {
+			t.Errorf("%v: Expected token to be invalid", itt.name)
+		}
+	}
+}
+
+// TestValidateBadData primarily tests that no unexpected panics are triggered
+// during parsing
+func TestValidateBadData(t *testing.T) {
+	badDataTests := []struct {
+		name, tok string
+	}{
+		{"Invalid Base64", "ASDab24(@)$*=="},
+		{"No delimiter", base64.URLEncoding.EncodeToString([]byte("foobar12345678"))},
+		{"Invalid time", base64.URLEncoding.EncodeToString([]byte("foobar:foobar"))},
+	}
+
+	for _, bdt := range badDataTests {
+		if validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow) {
+			t.Errorf("%v: Expected token to be invalid", bdt.name)
+		}
+	}
+}