Review random string/password generation

- factor password generation into lib/random.Password
- call from appropriate places
- choose appropriate use of random.String vs random.Password
This commit is contained in:
Nick Craig-Wood 2019-08-25 08:39:31 +01:00
parent beb8d5c134
commit 193c30d570
5 changed files with 83 additions and 25 deletions

View File

@ -19,8 +19,6 @@ canStream = false
import ( import (
"context" "context"
"crypto/rand"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -44,6 +42,7 @@ import (
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/random"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -664,7 +663,7 @@ func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDir
// another directory to make sure we don't overwrite something // another directory to make sure we don't overwrite something
// in the destination directory by accident // in the destination directory by accident
if doRenameLeaf && doMove { if doRenameLeaf && doMove {
tmpLeaf := newLeaf + "." + randomString(8) tmpLeaf := newLeaf + "." + random.String(8)
err = f.renameLeaf(ctx, isFile, id, tmpLeaf) err = f.renameLeaf(ctx, isFile, id, tmpLeaf)
if err != nil { if err != nil {
return errors.Wrap(err, "Move rename leaf") return errors.Wrap(err, "Move rename leaf")
@ -1010,13 +1009,6 @@ func (o *Object) metaHash() string {
return fmt.Sprintf("remote=%q, size=%d, modTime=%v, id=%q, mimeType=%q", o.remote, o.size, o.modTime, o.id, o.mimeType) return fmt.Sprintf("remote=%q, size=%d, modTime=%v, id=%q, mimeType=%q", o.remote, o.size, o.modTime, o.id, o.mimeType)
} }
// randomString returns a string with 8*bytes bits of entropy
func randomString(bytes int) string {
var pw = make([]byte, bytes)
_, _ = rand.Read(pw)
return base64.RawURLEncoding.EncodeToString(pw)
}
// Update the object with the contents of the io.Reader, modTime and size // Update the object with the contents of the io.Reader, modTime and size
// //
// If existing is set then it updates the object rather than creating a new one // If existing is set then it updates the object rather than creating a new one
@ -1073,7 +1065,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
uploaded := false uploaded := false
var oldID = o.id var oldID = o.id
if o.hasMetaData { if o.hasMetaData {
newLeaf := leaf + "." + randomString(8) newLeaf := leaf + "." + random.String(8)
fs.Debugf(o, "Moving old file out the way to %q", newLeaf) fs.Debugf(o, "Moving old file out the way to %q", newLeaf)
err = o.fs.renameLeaf(ctx, true, oldID, newLeaf) err = o.fs.renameLeaf(ctx, true, oldID, newLeaf)
if err != nil { if err != nil {

View File

@ -64,7 +64,10 @@ See the [rc documentation](/rc/) for more info on the rc flags.
fs.Infof("Using default username: %s \n", rcflags.Opt.HTTPOptions.BasicUser) fs.Infof("Using default username: %s \n", rcflags.Opt.HTTPOptions.BasicUser)
} }
if rcflags.Opt.HTTPOptions.BasicPass == "" { if rcflags.Opt.HTTPOptions.BasicPass == "" {
randomPass := random.String(16) randomPass, err := random.Password(128)
if err != nil {
log.Fatalf("Failed to make password: %v", err)
}
rcflags.Opt.HTTPOptions.BasicPass = randomPass rcflags.Opt.HTTPOptions.BasicPass = randomPass
fs.Infof("No password specified. Using random password: %s \n", randomPass) fs.Infof("No password specified. Using random password: %s \n", randomPass)
} }

View File

@ -35,6 +35,7 @@ import (
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/fspath" "github.com/rclone/rclone/fs/fspath"
"github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/rc"
"github.com/rclone/rclone/lib/random"
"golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/nacl/secretbox"
"golang.org/x/text/unicode/norm" "golang.org/x/text/unicode/norm"
) )
@ -860,16 +861,10 @@ func ChooseOption(o *fs.Option, name string) string {
for { for {
fmt.Printf("Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n") fmt.Printf("Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n")
bits := ChooseNumber("Bits", 64, 1024) bits := ChooseNumber("Bits", 64, 1024)
bytes := bits / 8 password, err := random.Password(bits)
if bits%8 != 0 { if err != nil {
bytes++ log.Fatalf("Failed to make password: %v", err)
} }
var pw = make([]byte, bytes)
n, _ := rand.Read(pw)
if n != bytes {
log.Fatalf("password short read: %d", n)
}
password = base64.RawURLEncoding.EncodeToString(pw)
fmt.Printf("Your password is: %s\n", password) fmt.Printf("Your password is: %s\n", password)
fmt.Printf("Use this password? Please note that an obscured version of this \npassword (and not the " + fmt.Printf("Use this password? Please note that an obscured version of this \npassword (and not the " +
"password itself) will be stored under your \nconfiguration file, so keep this generated password " + "password itself) will be stored under your \nconfiguration file, so keep this generated password " +

View File

@ -1,9 +1,16 @@
// Package random holds a few functions for working with random numbers // Package random holds a few functions for working with random numbers
package random package random
import "math/rand" import (
"encoding/base64"
"math/rand"
// String create a random string for test purposes "github.com/pkg/errors"
)
// String create a random string for test purposes.
//
// Do not use these for passwords.
func String(n int) string { func String(n int) string {
const ( const (
vowel = "aeiou" vowel = "aeiou"
@ -20,3 +27,27 @@ func String(n int) string {
} }
return string(out) return string(out)
} }
// Password creates a crypto strong password which is just about
// memorable. The password is composed of printable ASCII characters
// from the base64 alphabet.
//
// Requres password strength in bits.
// 64 is just about memorable
// 128 is secure
func Password(bits int) (password string, err error) {
bytes := bits / 8
if bits%8 != 0 {
bytes++
}
var pw = make([]byte, bytes)
n, err := rand.Read(pw)
if err != nil {
return "", errors.Wrap(err, "password read failed")
}
if n != bytes {
return "", errors.Errorf("password short read: %d", n)
}
password = base64.RawURLEncoding.EncodeToString(pw)
return password, nil
}

View File

@ -4,10 +4,47 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestString(t *testing.T) { func TestStringLength(t *testing.T) {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
assert.Equal(t, i, len(String(i))) s := String(i)
assert.Equal(t, i, len(s))
}
}
func TestStringDuplicates(t *testing.T) {
seen := map[string]bool{}
for i := 0; i < 100; i++ {
s := String(8)
assert.False(t, seen[s])
assert.Equal(t, 8, len(s))
seen[s] = true
}
}
func TestPasswordLength(t *testing.T) {
for i := 0; i <= 128; i++ {
s, err := Password(i)
require.NoError(t, err)
// expected length is number of bytes rounded up
expected := i / 8
if i%8 != 0 {
expected++
}
// then converted to base 64
expected = (expected*8 + 5) / 6
assert.Equal(t, expected, len(s), i)
}
}
func TestPasswordDuplicates(t *testing.T) {
seen := map[string]bool{}
for i := 0; i < 100; i++ {
s, err := Password(64)
require.NoError(t, err)
assert.False(t, seen[s])
seen[s] = true
} }
} }