I have a Two Factor Authentication library that handles all the crypto necessary to convert a pre-shared secret into a 6 number code used to authenticate. I've followed the standard as tightly as possible but I would really like to have a set of tests that'll provide confidence that the library won't result in users being locked out of their accounts.
Right now I feel my tests are underwhelming. I have 10 pre-generated secrets matched up with 10 of their codes (1 code per secret at a given timestamp). I do the process and check if the resulting code matches what's expected. I obviously can't do every possible secret at every possible timestamp to see if the library results in the same code twice. The process is deterministic, but how can I demonstrate that with tests that involve processing 32 random bytes? How can I prove, or at least instill enough confidence, that there isn't some specific set of 32 bytes that when hashed will be some kind of edge case? Would generating a large number of secrets, say 100,000, and running them through the process be enough? 1,000,000? I feel like I have the wrong philosophy behind my tests but I'm unsure what the right approach is. Any insight is welcome.
The test can be found here and duplicated below:
package tfa
import (
"encoding/base32"
"net/url"
"testing"
)
func TestProperSecretGeneration(t *testing.T) {
t.Parallel()
when := uint64(1494475275)
testcases := []struct {
Secret string
Code string
When uint64
Name string
Issuer string
}{
{"V2YJQHDQUJTN4BVBX3XZSKF7HJYM6OYFL3FUWERM3UF4RVFFFJ6Q====", "383561", when, "test", "test"},
{"6Q7D47BGVIMUPVUECDLU7OOTYD4LTGUN7U4KLDOHRXQ3B4ITFYXQ====", "432649", when, "Github", "Github"},
{"UADGUPDFCHJ32F6TAPWRV7AUVKOGM3NKUBFYXBJNSIN3PRD77R2Q====", "678895", when, "Google", "Google"},
{"FGQXKDZT2PTFG6BJJNTSCAPP55RYOH52JTABEJGFSA6IKYFFM3BA====", "778285", when, "Twitter", "Twitter"},
{"QGKPUVKGLEBBRNCHX2WHUWSJZ3KSFKIZWEZA7DGMZXYWAAILXK2Q====", "103713", when, "My Bank", "corporate"},
{"ZG4F2JV5LTFDH2DQDLIPRGYNAI3OKXSS3ZLWOZUEYFC53DHJYD7A====", "649000", when, "www.example.com", "www.example.com"},
{"G4XXKUWWKJX4CAK6DCIINBE7ADV5DLTYQMQYHWF6FVOVLR4HYS3A====", "053682", when, "X", "X"},
{"VE4ZK4JVZOEU544NRCBOTVINANSWUJTQ7EPIHIDFFINWVFC4LQKA====", "603745", when, "Y", "Y"},
{"VSLKYV6IW7YAOWWBLGHXB5MXNBXUYXF2NP4BIRP6ZANOVE72DKHA====", "204418", when, "Z", "Z"},
{"XZLJLLXIUWMVGKPCSSPZETJIBFISFT5XZLL7A6ZAHINKO7DCV5KQ====", "125527", when, "A", "A"},
}
for _, test := range testcases {
test := test
t.Run(test.Code, func(t *testing.T) {
t.Parallel()
secret, err := base32.StdEncoding.DecodeString(test.Secret)
if err != nil {
t.Error("error parsing secret:", err)
}
code := GenerateCode(secret, test.When)
if code != test.Code {
t.Errorf("Expected code: %s, Actual code: %s", test.Code, code)
}
u, err := url.Parse(GenerateQrUrl(secret, test.Issuer, test.Name))
if err != nil {
t.Error("unexpected error parsing QR URL:", err)
}
if u.Scheme != "otpauth" {
t.Error("expected qr url scheme to be otpauth")
}
if u.Path != "/"+url.QueryEscape(test.Name) {
t.Errorf("Qr Name error, expected: /%s, actual: %s", url.QueryEscape(test.Name), u.Path)
}
q := u.Query()
if q.Get("issuer") != url.QueryEscape(test.Issuer) {
t.Errorf("Qr Issuer error, expected: %s, actual: %s", url.QueryEscape(test.Issuer), q.Get("issuer"))
}
if q.Get("secret") != test.Secret {
t.Errorf("Qr Secret error, expected: %s, actual: %s", test.Secret, q.Get("secret"))
}
})
}
}