73 lines
2.0 KiB
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")
|
|
}
|
|
}
|