diff --git a/.golangci.yml b/.golangci.yml index bee66f3..978463e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,6 +12,12 @@ version: "2" +output: + sort-order: + - file + - linter + - severity + issues: # Maximum count of issues with the same text. # Set to 0 to disable. @@ -454,6 +460,8 @@ linters: - -QF1008 # We often explicitly enable old/deprecated ciphers for research. - -SA1019 + # Covered by revive. + - -ST1003 usetesting: # Enable/disable `os.TempDir()` detections. @@ -472,6 +480,8 @@ linters: rules: - path: 'ahash/ahash.go' linters: [ staticcheck, gosec ] + - path: 'twofactor/.*.go' + linters: [ exhaustive, mnd, revive ] - path: 'backoff/backoff_test.go' linters: [ testpackage ] - path: 'dbg/dbg_test.go' diff --git a/CHANGELOG b/CHANGELOG index fc3d07f..811308f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,13 @@ CHANGELOG +v1.12.4 - 2025-11-16 + +Changed: + +- Linting fixes for twofactor that were previously masked. + +v1.12.3 erroneously tagged and pushed + v1.12.2 - 2025-11-16 Changed: diff --git a/twofactor/.circleci/config.yml b/twofactor/.circleci/config.yml deleted file mode 100644 index 87ba58a..0000000 --- a/twofactor/.circleci/config.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/2.0/configuration-reference/#jobs -jobs: - testbuild: - working_directory: ~/repo - # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor - docker: - - image: cimg/go:1.22.2 - # Add steps to the job - # See: https://circleci.com/docs/2.0/configuration-reference/#steps - steps: - - checkout - - restore_cache: - keys: - - go-mod-v4-{{ checksum "go.sum" }} - - run: - name: Install Dependencies - command: go mod download - - save_cache: - key: go-mod-v4-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" - - run: - name: Run tests - command: go test ./... - - run: - name: Run build - command: go build ./... - - store_test_results: - path: /tmp/test-reports - -# Invoke jobs via workflows -# See: https://circleci.com/docs/2.0/configuration-reference/#workflows -workflows: - testbuild: - jobs: - - testbuild diff --git a/twofactor/doc.go b/twofactor/doc.go index 6f1eef8..3f85db6 100644 --- a/twofactor/doc.go +++ b/twofactor/doc.go @@ -1,4 +1,4 @@ -// twofactor implements two-factor authentication. +// Package twofactor implements two-factor authentication. // // Currently supported are RFC 4226 HOTP one-time passwords and // RFC 6238 TOTP SHA-1 one-time passwords. diff --git a/twofactor/hotp.go b/twofactor/hotp.go index 292838a..bbb91ac 100644 --- a/twofactor/hotp.go +++ b/twofactor/hotp.go @@ -2,7 +2,7 @@ package twofactor import ( "crypto" - "crypto/sha1" + "crypto/sha1" // #nosec G505 - required by RFC "encoding/base32" "io" "net/url" @@ -15,11 +15,6 @@ type HOTP struct { *OATH } -// Type returns OATH_HOTP. -func (otp *HOTP) Type() Type { - return OATH_HOTP -} - // NewHOTP takes the key, the initial counter value, and the number // of digits (typically 6 or 8) and returns a new HOTP instance. func NewHOTP(key []byte, counter uint64, digits int) *HOTP { @@ -34,6 +29,11 @@ func NewHOTP(key []byte, counter uint64, digits int) *HOTP { } } +// Type returns OATH_HOTP. +func (otp *HOTP) Type() Type { + return OATH_HOTP +} + // OTP returns the next OTP and increments the counter. func (otp *HOTP) OTP() string { code := otp.OATH.OTP(otp.counter) @@ -79,7 +79,7 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { digits = int(tmpDigits) } - var counter uint64 = 0 + var counter uint64 if scounter := v.Get("counter"); scounter != "" { var err error counter, err = strconv.ParseUint(scounter, 10, 64) diff --git a/twofactor/hotp_test.go b/twofactor/hotp_internal_test.go similarity index 61% rename from twofactor/hotp_test.go rename to twofactor/hotp_internal_test.go index 3fda2fc..1e77127 100644 --- a/twofactor/hotp_test.go +++ b/twofactor/hotp_internal_test.go @@ -1,7 +1,6 @@ package twofactor import ( - "fmt" "testing" ) @@ -25,22 +24,19 @@ var rfcHotpExpected = []string{ // ensures that this implementation is in compliance. func TestHotpRFC(t *testing.T) { otp := NewHOTP(rfcHotpKey, 0, 6) - for i := 0; i < len(rfcHotpExpected); i++ { + for i := range rfcHotpExpected { if otp.Counter() != uint64(i) { - fmt.Printf("twofactor: invalid counter (should be %d, is %d", + t.Fatalf("twofactor: invalid counter (should be %d, is %d", i, otp.Counter()) - t.FailNow() } code := otp.OTP() if code == "" { - fmt.Printf("twofactor: failed to produce an OTP\n") - t.FailNow() + t.Fatal("twofactor: failed to produce an OTP") } else if code != rfcHotpExpected[i] { - fmt.Printf("twofactor: invalid OTP\n") - fmt.Printf("\tExpected: %s\n", rfcHotpExpected[i]) - fmt.Printf("\t Actual: %s\n", code) - fmt.Printf("\t Counter: %d\n", otp.counter) - t.FailNow() + t.Logf("twofactor: invalid OTP\n") + t.Logf("\tExpected: %s\n", rfcHotpExpected[i]) + t.Logf("\t Actual: %s\n", code) + t.Fatalf("\t Counter: %d\n", otp.counter) } } } @@ -50,15 +46,13 @@ func TestHotpRFC(t *testing.T) { // expected. func TestHotpBadRFC(t *testing.T) { otp := NewHOTP(testKey, 0, 6) - for i := 0; i < len(rfcHotpExpected); i++ { + for i := range rfcHotpExpected { code := otp.OTP() switch code { case "": - fmt.Printf("twofactor: failed to produce an OTP\n") - t.FailNow() + t.Error("twofactor: failed to produce an OTP") case rfcHotpExpected[i]: - fmt.Printf("twofactor: should not have received a valid OTP\n") - t.FailNow() + t.Error("twofactor: should not have received a valid OTP") } } } diff --git a/twofactor/oath.go b/twofactor/oath.go index 6c60608..547ed26 100644 --- a/twofactor/oath.go +++ b/twofactor/oath.go @@ -8,6 +8,7 @@ import ( "fmt" "hash" "net/url" + "strconv" "rsc.io/qr" ) @@ -25,12 +26,12 @@ type OATH struct { } // Size returns the output size (in characters) of the password. -func (o OATH) Size() int { +func (o *OATH) Size() int { return o.size } // Counter returns the OATH token's counter. -func (o OATH) Counter() uint64 { +func (o *OATH) Counter() uint64 { return o.counter } @@ -40,18 +41,18 @@ func (o *OATH) SetCounter(counter uint64) { } // Key returns the token's secret key. -func (o OATH) Key() []byte { - return o.key[:] +func (o *OATH) Key() []byte { + return o.key } // Hash returns the token's hash function. -func (o OATH) Hash() func() hash.Hash { +func (o *OATH) Hash() func() hash.Hash { return o.hash } // URL constructs a URL appropriate for the token (i.e. for use in a // QR code). -func (o OATH) URL(t Type, label string) string { +func (o *OATH) URL(t Type, label string) string { secret := base32.StdEncoding.EncodeToString(o.key) u := url.URL{} v := url.Values{} @@ -65,10 +66,10 @@ func (o OATH) URL(t Type, label string) string { u.Path = label v.Add("secret", secret) if o.Counter() != 0 && t == OATH_HOTP { - v.Add("counter", fmt.Sprintf("%d", o.Counter())) + v.Add("counter", strconv.FormatUint(o.Counter(), 10)) } if o.Size() != defaultSize { - v.Add("digits", fmt.Sprintf("%d", o.Size())) + v.Add("digits", strconv.Itoa(o.Size())) } switch o.algo { @@ -84,7 +85,6 @@ func (o OATH) URL(t Type, label string) string { u.RawQuery = v.Encode() return u.String() - } var digits = []int64{ @@ -101,10 +101,10 @@ var digits = []int64{ 10: 10000000000, } -// The top-level type should provide a counter; for example, HOTP +// OTP top-level type should provide a counter; for example, HOTP // will provide the counter directly while TOTP will provide the // time-stepped counter. -func (o OATH) OTP(counter uint64) string { +func (o *OATH) OTP(counter uint64) string { var ctr [8]byte binary.BigEndian.PutUint64(ctr[:], counter) @@ -140,7 +140,7 @@ func truncate(in []byte) int64 { // QR generates a byte slice containing the a QR code encoded as a // PNG with level Q error correction. -func (o OATH) QR(t Type, label string) ([]byte, error) { +func (o *OATH) QR(t Type, label string) ([]byte, error) { u := o.URL(t, label) code, err := qr.Encode(u, qr.Q) if err != nil { diff --git a/twofactor/oath_test.go b/twofactor/oath_internal_test.go similarity index 77% rename from twofactor/oath_test.go rename to twofactor/oath_internal_test.go index 7b145b0..c92a8a0 100644 --- a/twofactor/oath_test.go +++ b/twofactor/oath_internal_test.go @@ -1,7 +1,6 @@ package twofactor import ( - "fmt" "testing" ) @@ -17,14 +16,12 @@ var truncExpect int64 = 0x50ef7f19 // This test runs through the truncation example given in the RFC. func TestTruncate(t *testing.T) { if result := truncate(sha1Hmac); result != truncExpect { - fmt.Printf("hotp: expected truncate -> %d, saw %d\n", + t.Fatalf("hotp: expected truncate -> %d, saw %d\n", truncExpect, result) - t.FailNow() } sha1Hmac[19]++ if result := truncate(sha1Hmac); result == truncExpect { - fmt.Println("hotp: expected truncation to fail") - t.FailNow() + t.Fatal("hotp: expected truncation to fail") } } diff --git a/twofactor/otp.go b/twofactor/otp.go index 5a78358..317ffbf 100644 --- a/twofactor/otp.go +++ b/twofactor/otp.go @@ -24,7 +24,7 @@ var ( ErrInvalidAlgo = errors.New("twofactor: invalid algorithm") ) -// Type OTP represents a one-time password token -- whether a +// OTP represents a one-time password token -- whether a // software taken (as in the case of Google Authenticator) or a // hardware token (as in the case of a YubiKey). type OTP interface { @@ -65,8 +65,8 @@ func otpString(otp OTP) string { } // FromURL constructs a new OTP token from a URL string. -func FromURL(URL string) (OTP, string, error) { - u, err := url.Parse(URL) +func FromURL(otpURL string) (OTP, string, error) { + u, err := url.Parse(otpURL) if err != nil { return nil, "", err } diff --git a/twofactor/otp_test.go b/twofactor/otp_internal_test.go similarity index 64% rename from twofactor/otp_test.go rename to twofactor/otp_internal_test.go index 412a0b3..7683d25 100644 --- a/twofactor/otp_test.go +++ b/twofactor/otp_internal_test.go @@ -1,7 +1,6 @@ package twofactor import ( - "fmt" "io" "testing" ) @@ -10,8 +9,7 @@ func TestHOTPString(t *testing.T) { hotp := NewHOTP(nil, 0, 6) hotpString := otpString(hotp) if hotpString != "OATH-HOTP, 6" { - fmt.Println("twofactor: invalid OTP string") - t.FailNow() + t.Fatal("twofactor: invalid OTP string") } } @@ -23,35 +21,32 @@ func TestURL(t *testing.T) { otp := NewHOTP(testKey, 0, 6) url := otp.URL("testuser@foo") otp2, id, err := FromURL(url) - if err != nil { - fmt.Printf("hotp: failed to parse HOTP URL\n") - t.FailNow() - } else if id != ident { - fmt.Printf("hotp: bad label\n") - fmt.Printf("\texpected: %s\n", ident) - fmt.Printf("\t actual: %s\n", id) - t.FailNow() - } else if otp2.Counter() != otp.Counter() { - fmt.Printf("hotp: OTP counters aren't synced\n") - fmt.Printf("\toriginal: %d\n", otp.Counter()) - fmt.Printf("\t second: %d\n", otp2.Counter()) - t.FailNow() + switch { + case err != nil: + t.Fatal("hotp: failed to parse HOTP URL\n") + case id != ident: + t.Logf("hotp: bad label\n") + t.Logf("\texpected: %s\n", ident) + t.Fatalf("\t actual: %s\n", id) + case otp2.Counter() != otp.Counter(): + t.Logf("hotp: OTP counters aren't synced\n") + t.Logf("\toriginal: %d\n", otp.Counter()) + t.Fatalf("\t second: %d\n", otp2.Counter()) } code1 := otp.OTP() code2 := otp2.OTP() if code1 != code2 { - fmt.Printf("hotp: mismatched OTPs\n") - fmt.Printf("\texpected: %s\n", code1) - fmt.Printf("\t actual: %s\n", code2) + t.Logf("hotp: mismatched OTPs\n") + t.Logf("\texpected: %s\n", code1) + t.Fatalf("\t actual: %s\n", code2) } // There's not much we can do test the QR code, except to // ensure it doesn't fail. _, err = otp.QR(ident) if err != nil { - fmt.Printf("hotp: failed to generate QR code PNG (%v)\n", err) - t.FailNow() + t.Fatalf("hotp: failed to generate QR code PNG (%v)\n", err) } // This should fail because the maximum size of an alphanumeric @@ -63,16 +58,14 @@ func TestURL(t *testing.T) { var tooBigIdent = make([]byte, 8192) _, err = io.ReadFull(PRNG, tooBigIdent) if err != nil { - fmt.Printf("hotp: failed to read identity (%v)\n", err) - t.FailNow() + t.Fatalf("hotp: failed to read identity (%v)\n", err) } else if _, err = otp.QR(string(tooBigIdent)); err == nil { - fmt.Println("hotp: QR code should fail to encode oversized URL") - t.FailNow() + t.Fatal("hotp: QR code should fail to encode oversized URL") } } // This test makes sure we can generate codes for padded and non-padded -// entries +// entries. func TestPaddedURL(t *testing.T) { var urlList = []string{ "otpauth://hotp/?secret=ME", @@ -95,17 +88,15 @@ func TestPaddedURL(t *testing.T) { for i := range urlList { if o, id, err := FromURL(urlList[i]); err != nil { - fmt.Println("hotp: URL should have parsed successfully (id=", id, ")") - fmt.Printf("\turl was: %s\n", urlList[i]) - t.FailNow() - fmt.Printf("\t%s, %s\n", o.OTP(), id) + t.Log("hotp: URL should have parsed successfully (id=", id, ")") + t.Logf("\turl was: %s\n", urlList[i]) + t.Fatalf("\t%s, %s\n", o.OTP(), id) } else { code2 := o.OTP() if code2 != codeList[i] { - fmt.Printf("hotp: mismatched OTPs\n") - fmt.Printf("\texpected: %s\n", codeList[i]) - fmt.Printf("\t actual: %s\n", code2) - t.FailNow() + t.Logf("hotp: mismatched OTPs\n") + t.Logf("\texpected: %s\n", codeList[i]) + t.Fatalf("\t actual: %s\n", code2) } } } @@ -128,9 +119,8 @@ func TestBadURL(t *testing.T) { for i := range urlList { if _, _, err := FromURL(urlList[i]); err == nil { - fmt.Println("hotp: URL should not have parsed successfully") - fmt.Printf("\turl was: %s\n", urlList[i]) - t.FailNow() + t.Log("hotp: URL should not have parsed successfully") + t.Fatalf("\turl was: %s\n", urlList[i]) } } } diff --git a/twofactor/totp.go b/twofactor/totp.go index 9259e6c..f8a58bf 100644 --- a/twofactor/totp.go +++ b/twofactor/totp.go @@ -2,7 +2,7 @@ package twofactor import ( "crypto" - "crypto/sha1" + "crypto/sha1" // #nosec G505 - required by RFC "crypto/sha256" "crypto/sha512" "encoding/base32" @@ -23,6 +23,42 @@ type TOTP struct { step uint64 } +// NewTOTP takes a new key, a starting time, a step, the number of +// digits of output (typically 6 or 8) and the hash algorithm to +// use, and builds a new OTP. +func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP { + h := hashFromAlgo(algo) + if h == nil { + return nil + } + + return &TOTP{ + OATH: &OATH{ + key: key, + counter: start, + size: digits, + hash: h, + algo: algo, + }, + step: step, + } +} + +// NewGoogleTOTP takes a secret as a base32-encoded string and +// returns an appropriate Google Authenticator TOTP instance. +func NewGoogleTOTP(secret string) (*TOTP, error) { + key, err := base32.StdEncoding.DecodeString(secret) + if err != nil { + return nil, err + } + return NewTOTP(key, 0, 30, 6, crypto.SHA1), nil +} + +// NewTOTPSHA1 will build a new TOTP using SHA-1. +func NewTOTPSHA1(key []byte, start uint64, step uint64, digits int) *TOTP { + return NewTOTP(key, start, step, digits, crypto.SHA1) +} + // Type returns OATH_TOTP. func (otp *TOTP) Type() Type { return OATH_TOTP @@ -53,34 +89,7 @@ func (otp *TOTP) otpCounter(t uint64) uint64 { // OTPCounter returns the current time value for the OTP. func (otp *TOTP) OTPCounter() uint64 { - return otp.otpCounter(uint64(timeSource.Now().Unix())) -} - -// NewTOTP takes a new key, a starting time, a step, the number of -// digits of output (typically 6 or 8) and the hash algorithm to -// use, and builds a new OTP. -func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP { - h := hashFromAlgo(algo) - if h == nil { - return nil - } - - return &TOTP{ - OATH: &OATH{ - key: key, - counter: start, - size: digits, - hash: h, - algo: algo, - }, - step: step, - } - -} - -// NewTOTPSHA1 will build a new TOTP using SHA-1. -func NewTOTPSHA1(key []byte, start uint64, step uint64, digits int) *TOTP { - return NewTOTP(key, start, step, digits, crypto.SHA1) + return otp.otpCounter(uint64(timeSource.Now().Unix() & 0x7FFFFFFF)) //#nosec G115 - masked out overflow bits } func hashFromAlgo(algo crypto.Hash) func() hash.Hash { @@ -105,16 +114,6 @@ func GenerateGoogleTOTP() *TOTP { return NewTOTP(key, 0, 30, 6, crypto.SHA1) } -// NewGoogleTOTP takes a secret as a base32-encoded string and -// returns an appropriate Google Authenticator TOTP instance. -func NewGoogleTOTP(secret string) (*TOTP, error) { - key, err := base32.StdEncoding.DecodeString(secret) - if err != nil { - return nil, err - } - return NewTOTP(key, 0, 30, 6, crypto.SHA1), nil -} - func totpFromURL(u *url.URL) (*TOTP, string, error) { label := u.Path[1:] v := u.Query() @@ -126,11 +125,12 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { var algo = crypto.SHA1 if algorithm := v.Get("algorithm"); algorithm != "" { - if strings.EqualFold(algorithm, "SHA256") { + switch { + case strings.EqualFold(algorithm, "SHA256"): algo = crypto.SHA256 - } else if strings.EqualFold(algorithm, "SHA512") { + case strings.EqualFold(algorithm, "SHA512"): algo = crypto.SHA512 - } else if !strings.EqualFold(algorithm, "SHA1") { + case !strings.EqualFold(algorithm, "SHA1"): return nil, "", ErrInvalidAlgo } } diff --git a/twofactor/totp_test.go b/twofactor/totp_internal_test.go similarity index 86% rename from twofactor/totp_test.go rename to twofactor/totp_internal_test.go index ef5628f..52ab35c 100644 --- a/twofactor/totp_test.go +++ b/twofactor/totp_internal_test.go @@ -2,7 +2,6 @@ package twofactor import ( "crypto" - "fmt" "testing" "time" @@ -14,6 +13,7 @@ var rfcTotpKey = map[crypto.Hash][]byte{ crypto.SHA256: []byte("12345678901234567890123456789012"), crypto.SHA512: []byte("1234567890123456789012345678901234567890123456789012345678901234"), } + var rfcTotpStep uint64 = 30 var rfcTotpTests = []struct { @@ -46,17 +46,15 @@ func TestTotpRFC(t *testing.T) { for _, tc := range rfcTotpTests { otp := NewTOTP(rfcTotpKey[tc.Algo], 0, rfcTotpStep, 8, tc.Algo) if otp.otpCounter(tc.Time) != tc.T { - fmt.Printf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) - fmt.Printf("\texpected: %d\n", tc.T) - fmt.Printf("\t actual: %d\n", otp.otpCounter(tc.Time)) - t.Fail() + t.Logf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) + t.Logf("\texpected: %d\n", tc.T) + t.Errorf("\t actual: %d\n", otp.otpCounter(tc.Time)) } if code := otp.otp(otp.otpCounter(tc.Time)); code != tc.Code { - fmt.Printf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) - fmt.Printf("\texpected: %s\n", tc.Code) - fmt.Printf("\t actual: %s\n", code) - t.Fail() + t.Logf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) + t.Logf("\texpected: %s\n", tc.Code) + t.Errorf("\t actual: %s\n", code) } } } diff --git a/twofactor/util.go b/twofactor/util.go index af15c0f..10cc4ef 100644 --- a/twofactor/util.go +++ b/twofactor/util.go @@ -5,7 +5,7 @@ import ( ) // Pad calculates the number of '='s to add to our encoded string -// to make base32.StdEncoding.DecodeString happy +// to make base32.StdEncoding.DecodeString happy. func Pad(s string) string { if !strings.HasSuffix(s, "=") && len(s)%8 != 0 { for len(s)%8 != 0 { diff --git a/twofactor/util_test.go b/twofactor/util_test.go index a4eee6b..3764db3 100644 --- a/twofactor/util_test.go +++ b/twofactor/util_test.go @@ -1,11 +1,12 @@ -package twofactor +package twofactor_test import ( "encoding/base32" - "fmt" "math/rand" "strings" "testing" + + "git.wntrmute.dev/kyle/goutils/twofactor" ) const letters = "1234567890!@#$%^&*()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -19,34 +20,31 @@ func randString() string { } func TestPadding(t *testing.T) { - for i := 0; i < 300; i++ { + for range 300 { b := randString() - origEncoding := string(b) - modEncoding := strings.ReplaceAll(string(b), "=", "") + origEncoding := b + modEncoding := strings.ReplaceAll(b, "=", "") str, err := base32.StdEncoding.DecodeString(origEncoding) if err != nil { - fmt.Println("Can't decode: ", string(b)) - t.FailNow() + t.Fatal("Can't decode: ", b) } - paddedEncoding := Pad(modEncoding) + paddedEncoding := twofactor.Pad(modEncoding) if origEncoding != paddedEncoding { - fmt.Println("Padding failed:") - fmt.Printf("Expected: '%s'", origEncoding) - fmt.Printf("Got: '%s'", paddedEncoding) - t.FailNow() + t.Log("Padding failed:") + t.Logf("Expected: '%s'", origEncoding) + t.Fatalf("Got: '%s'", paddedEncoding) } else { - mstr, err := base32.StdEncoding.DecodeString(paddedEncoding) + var mstr []byte + mstr, err = base32.StdEncoding.DecodeString(paddedEncoding) if err != nil { - fmt.Println("Can't decode: ", paddedEncoding) - t.FailNow() + t.Fatal("Can't decode: ", paddedEncoding) } if string(mstr) != string(str) { - fmt.Println("Re-padding failed:") - fmt.Printf("Expected: '%s'", str) - fmt.Printf("Got: '%s'", mstr) - t.FailNow() + t.Log("Re-padding failed:") + t.Logf("Expected: '%s'", str) + t.Fatalf("Got: '%s'", mstr) } } }