Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/platform/security/csrf_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/platform/security/csrf_test.go |
| Original SHA-256 | 250e2c6e2c683a61f939deb2d6c05ec545fa9ecfe2c3b4ab603d92e5bb7666a7 |
| Git blob SHA | 5554be0379d35508840d6ff5b728dc983c893475 |
| Reading-copy SHA-256 | 250e2c6e2c683a61f939deb2d6c05ec545fa9ecfe2c3b4ab603d92e5bb7666a7 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/platform/security/csrf_test.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/platform/security/csrf_test.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓129 lines · 4.4 KB
package security
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
var csrfKey = []byte("csrf test key, thirty two bytes!")
// A token minted for one session must not verify against another. This is the property the type
// claims (the config calls the key "HMAC key binding a CSRF token to its session") and the property
// that makes signed double submit worth more than an unsigned one: without it, anyone who can call
// the public mint endpoint holds a token that verifies inside every victim's session.
func TestCSRFTokenDoesNotCrossSessions(t *testing.T) {
c := NewCSRF(csrfKey)
victim, attacker := "victim-session-token", "attacker-session-token"
tok, err := c.Issue(attacker)
if err != nil {
t.Fatalf("issue: %v", err)
}
if !c.Valid(tok, attacker) {
t.Fatal("token issued for a session must verify in that session")
}
if c.Valid(tok, victim) {
t.Fatal("token issued for one session verified in another, the token is not session bound")
}
if c.Valid(tok, "") {
t.Fatal("session bound token verified as anonymous")
}
}
// The anonymous binding is a binding of its own, not a wildcard: the pre login endpoints need it, and
// it must stop working once a session exists rather than becoming a universal token.
func TestCSRFAnonymousTokenIsNotUniversal(t *testing.T) {
c := NewCSRF(csrfKey)
tok, err := c.Issue("")
if err != nil {
t.Fatalf("issue: %v", err)
}
if !c.Valid(tok, "") {
t.Fatal("anonymous token must verify with no session")
}
if c.Valid(tok, "some-session-token") {
t.Fatal("anonymous token verified inside a session")
}
}
// A token in the old unbound format (HMAC over the nonce alone) must be refused, not honored for
// compatibility and not panic the verifier.
func TestCSRFLegacyUnboundTokenIsRefused(t *testing.T) {
c := NewCSRF(csrfKey)
nonce := base64.RawURLEncoding.EncodeToString([]byte("0123456789abcdef"))
mac := hmac.New(sha256.New, csrfKey)
mac.Write([]byte(nonce))
legacy := nonce + "." + base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
if c.Valid(legacy, "") || c.Valid(legacy, "any-session") {
t.Fatal("a token minted before session binding must fail closed")
}
for _, malformed := range []string{"", ".", "nodot", ".sigonly", "nonce."} {
if c.Valid(malformed, "") {
t.Fatalf("malformed token %q accepted", malformed)
}
}
}
// The middleware must recompute the binding from the session cookie on the request, so a stolen or
// separately minted token cannot be replayed against another user's session cookie.
func TestCSRFProtectBindsToRequestSession(t *testing.T) {
c := NewCSRF(csrfKey)
const csrfCookie, sessCookie, header = "mm_csrf", "mm_session", "X-CSRF-Token"
mw := c.Protect(csrfCookie, header, sessCookie, []string{"https://app.example.gov"})
handled := false
h := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handled = true }))
post := func(token, session string) int {
handled = false
r := httptest.NewRequest(http.MethodPost, "/users/invite", nil)
r.Header.Set("Origin", "https://app.example.gov")
r.Header.Set(header, token)
r.AddCookie(&http.Cookie{Name: csrfCookie, Value: token})
if session != "" {
r.AddCookie(&http.Cookie{Name: sessCookie, Value: session})
}
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
return w.Code
}
mine, err := c.Issue("my-session")
if err != nil {
t.Fatalf("issue: %v", err)
}
if code := post(mine, "my-session"); code != http.StatusOK || !handled {
t.Fatalf("own session request rejected: status %d handled %v", code, handled)
}
if code := post(mine, "victim-session"); code != http.StatusForbidden || handled {
t.Fatalf("token replayed against another session was allowed: status %d handled %v", code, handled)
}
anon, err := c.Issue("")
if err != nil {
t.Fatalf("issue: %v", err)
}
if code := post(anon, ""); code != http.StatusOK {
t.Fatalf("anonymous request rejected: status %d", code)
}
if code := post(anon, "victim-session"); code != http.StatusForbidden {
t.Fatalf("anonymous token accepted inside a session: status %d", code)
}
}
// The signing input must not be reinterpretable: a nonce and a binding that concatenate to the same
// bytes under a different split must not produce the same signature.
func TestCSRFSigningInputIsUnambiguous(t *testing.T) {
c := NewCSRF(csrfKey)
a := c.sign("abc", "def")
b := c.sign("abcdef", "")
if strings.EqualFold(a, b) {
t.Fatal("signing input is ambiguous across the nonce and session boundary")
}
}