diff --git a/hotp/hotp.go b/hotp/hotp.go index 951cd0b..d190928 100644 --- a/hotp/hotp.go +++ b/hotp/hotp.go @@ -77,6 +77,10 @@ func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passc secret = secret + strings.Repeat("=", 8-n) } + // As noted in issue #24 Google has started producing base32 in lower case, + // but the StdEncoding (and the RFC), expect a dictionary of only upper case letters. + secret = strings.ToUpper(secret) + secretBytes, err := base32.StdEncoding.DecodeString(secret) if err != nil { return "", otp.ErrValidateSecretInvalidBase32 diff --git a/hotp/hotp_test.go b/hotp/hotp_test.go index 9640144..bc885a5 100644 --- a/hotp/hotp_test.go +++ b/hotp/hotp_test.go @@ -120,6 +120,16 @@ func TestValidatePadding(t *testing.T) { require.Equal(t, true, valid, "Valid should be true.") } +func TestValidateLowerCaseSecret(t *testing.T) { + valid, err := ValidateCustom("831097", 0, "jbswy3dpehpk3px", + ValidateOpts{ + Digits: otp.DigitsSix, + Algorithm: otp.AlgorithmSHA1, + }) + require.NoError(t, err, "Expected no error.") + require.Equal(t, true, valid, "Valid should be true.") +} + func TestGenerate(t *testing.T) { k, err := Generate(GenerateOpts{ Issuer: "SnakeOil", diff --git a/totp/totp_test.go b/totp/totp_test.go index 75c114e..ce58883 100644 --- a/totp/totp_test.go +++ b/totp/totp_test.go @@ -144,3 +144,17 @@ func TestGenerate(t *testing.T) { require.NoError(t, err, "Secret size is valid when length not divisable by 5.") require.NotContains(t, k.Secret(), "=", "Secret has no escaped characters.") } + +func TestGoogleLowerCaseSecret(t *testing.T) { + w, err := otp.NewKeyFromURL(`otpauth://totp/Google%3Afoo%40example.com?secret=qlt6vmy6svfx4bt4rpmisaiyol6hihca&issuer=Google`) + require.NoError(t, err) + sec := w.Secret() + require.Equal(t, "qlt6vmy6svfx4bt4rpmisaiyol6hihca", sec) + + n := time.Now().UTC() + code, err := GenerateCode(w.Secret(), n) + require.NoError(t, err) + + valid := Validate(code, w.Secret()) + require.True(t, valid) +}