diff --git a/hscontrol/util/string.go b/hscontrol/util/string.go index b0f0376a..2bcb515f 100644 --- a/hscontrol/util/string.go +++ b/hscontrol/util/string.go @@ -32,9 +32,18 @@ func GenerateRandomBytes(n int) ([]byte, error) { func GenerateRandomStringURLSafe(n int) (string, error) { b, err := GenerateRandomBytes(n) - uenc := base64.RawURLEncoding.EncodeToString(b) + return encodeRandomURLSafe(b, n, err) +} - return uenc[:n], err +// encodeRandomURLSafe URL-safe base64-encodes b and truncates to n. It checks +// err first: on an RNG failure b is nil, so slicing the empty encoding would +// panic instead of returning the ("", err) the caller is promised. +func encodeRandomURLSafe(b []byte, n int, err error) (string, error) { + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(b)[:n], nil } // GenerateRandomStringDNSSafe returns a DNS-safe diff --git a/hscontrol/util/string_random_test.go b/hscontrol/util/string_random_test.go new file mode 100644 index 00000000..c697841b --- /dev/null +++ b/hscontrol/util/string_random_test.go @@ -0,0 +1,24 @@ +package util + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestEncodeRandomURLSafeChecksErrorFirst ensures the URL-safe random string +// encoder returns ("", err) on an RNG failure instead of slicing the empty +// base64 of nil bytes and panicking. +func TestEncodeRandomURLSafeChecksErrorFirst(t *testing.T) { + require.NotPanics(t, func() { + s, err := encodeRandomURLSafe(nil, 32, assert.AnError) + assert.Empty(t, s) + require.Error(t, err) + }) + + s, err := encodeRandomURLSafe(bytes.Repeat([]byte{0x1}, 32), 32, nil) + require.NoError(t, err) + assert.Len(t, s, 32) +}