core/captcha/captcha_test.go

73 lines
2.0 KiB
Go

package captcha
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"testing"
)
// solve reproduces what the widget does, using the package's own prng, so the
// test proves CreateChallenge and Redeem agree end-to-end.
func solve(token string, ch Challenge) []string {
sols := make([]string, ch.C)
for i := 1; i <= ch.C; i++ {
salt := prng(token+strconv.Itoa(i), ch.S)
target := prng(token+strconv.Itoa(i)+"d", ch.D)
for n := 0; ; n++ {
sum := sha256.Sum256([]byte(salt + strconv.Itoa(n)))
if has := hex.EncodeToString(sum[:]); len(has) >= len(target) && has[:len(target)] == target {
sols[i-1] = strconv.Itoa(n)
break
}
}
}
return sols
}
func TestChallengeRedeemRoundTrip(t *testing.T) {
// Small difficulty keeps the test fast.
s := New([]byte("secret"), WithChallenge(5, 8, 2))
cr, err := s.CreateChallenge()
if err != nil {
t.Fatal(err)
}
sols := solve(cr.Token, cr.Challenge)
res := s.Redeem(cr.Token, sols)
if !res.Success {
t.Fatal("valid solutions rejected")
}
if !s.VerifyToken(res.Token) {
t.Fatal("issued verification token failed VerifyToken")
}
}
func TestRedeemRejects(t *testing.T) {
s := New([]byte("secret"), WithChallenge(5, 8, 2))
cr, _ := s.CreateChallenge()
sols := solve(cr.Token, cr.Challenge)
// wrong solution
bad := append([]string(nil), sols...)
bad[0] = "999999999"
if s.Redeem(cr.Token, bad).Success {
t.Error("wrong solution accepted")
}
// wrong count — use a fresh challenge so this exercises the count check
// itself, not the consumed-nonce path from the wrong-solution redeem above.
crc, _ := s.CreateChallenge()
solsc := solve(crc.Token, crc.Challenge)
if s.Redeem(crc.Token, solsc[:len(solsc)-1]).Success {
t.Error("short solution list accepted")
}
// replay: first redeem consumes the nonce
cr2, _ := s.CreateChallenge()
sols2 := solve(cr2.Token, cr2.Challenge)
if !s.Redeem(cr2.Token, sols2).Success {
t.Fatal("first redeem should succeed")
}
if s.Redeem(cr2.Token, sols2).Success {
t.Error("replayed redeem accepted")
}
}