Skip to content

Commit

Permalink
randutil: get randutil from snapd (canonical#17)
Browse files Browse the repository at this point in the history
Get latest version of randutil from github.com/snapcore/snapd.git,
including all the related commit history.
  • Loading branch information
flotter committed Jan 12, 2023
2 parents 5339b8b + 3e58d34 commit a5240cb
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 0 deletions.
61 changes: 61 additions & 0 deletions randutil/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package randutil

import (
cryptorand "crypto/rand"
"encoding/base64"
"fmt"
"io/ioutil"
"strings"
)

// CryptoTokenBytes returns a crypthographically secure token of
// nbytes random bytes.
func CryptoTokenBytes(nbytes int) ([]byte, error) {
b := make([]byte, nbytes)
_, err := cryptorand.Read(b)
if err != nil {
return nil, fmt.Errorf("cannot obtain %d crypto random bytes: %v", nbytes, err)
}
return b, nil
}

// CryptoToken returns a crypthographically secure token string
// encoding nbytes random bytes.
// The result is URL-safe.
func CryptoToken(nbytes int) (string, error) {
b, err := CryptoTokenBytes(nbytes)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}

// RandomKernelUUID will return a UUID from the kernel's procfs API at
// /proc/sys/kernel/random/uuid. Only to be used in very specific uses, most
// random code should use CryptoToken(Bytes) instead.
func RandomKernelUUID() string {
b, err := ioutil.ReadFile("/proc/sys/kernel/random/uuid")
if err != nil {
panic("cannot read kernel generated uuid")
}
return strings.TrimSpace(string(b))
}
47 changes: 47 additions & 0 deletions randutil/crypto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package randutil_test

import (
"encoding/base64"

. "gopkg.in/check.v1"

"github.com/canonical/x-go/randutil"
)

type cryptoRandutilSuite struct{}

var _ = Suite(&cryptoRandutilSuite{})

func (s *cryptoRandutilSuite) TestCryptoTokenBytes(c *C) {
x, err := randutil.CryptoTokenBytes(5)
c.Assert(err, IsNil)
c.Check(x, HasLen, 5)
}

func (s *cryptoRandutilSuite) TestCryptoToken(c *C) {
x, err := randutil.CryptoToken(5)
c.Assert(err, IsNil)

b, err := base64.RawURLEncoding.DecodeString(x)
c.Assert(err, IsNil)
c.Check(b, HasLen, 5)
}
95 changes: 95 additions & 0 deletions randutil/rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

// Package randutil initialises properly random value generation and
// exposes a streamlined set of functions for it, including for crypto
// random tokens.
package randutil

import (
"crypto/sha256"
"encoding/binary"
"math/rand"
"net"
"os"
"sync"
"time"
)

func init() {
// golang does not init Seed() itself
rand.Seed(time.Now().UnixNano() + int64(os.Getpid()))
}

var moreMixedSeedOnce sync.Once

func moreMixedSeed() {
moreMixedSeedOnce.Do(func() {
h := sha256.New224()
// do this instead of asking for time and pid again
var b [8]byte
rand.Read(b[:])
h.Write(b[:])
// mix in the hostname
if hostname, err := os.Hostname(); err == nil {
h.Write([]byte(hostname))
}
// mix in net interfaces hw addresses (MACs etc)
if ifaces, err := net.Interfaces(); err == nil {
for _, iface := range ifaces {
h.Write(iface.HardwareAddr)
}
}
hs := h.Sum(nil)
s := binary.LittleEndian.Uint64(hs[0:])
rand.Seed(int64(s))
})
}

const letters = "BCDFGHJKLMNPQRSTVWXYbcdfghjklmnpqrstvwxy0123456789"

// RandomString returns a random string of length length.
//
// The vowels are omitted to avoid that words are created by pure
// chance. Numbers are included.
//
// Not cryptographically secure.
func RandomString(length int) string {
out := ""
for i := 0; i < length; i++ {
out += string(letters[rand.Intn(len(letters))])
}

return out
}

// Re-exported from math/rand for streamlining.
var (
Intn = rand.Intn
Int63n = rand.Int63n
)

// RandomDuration returns a random duration up to the given length.
func RandomDuration(d time.Duration) time.Duration {
// try to switch to more mixed seed to avoid subsets of a
// fleet of machines with similar initial conditions to behave
// the same
moreMixedSeed()
return time.Duration(Int63n(int64(d)))
}
62 changes: 62 additions & 0 deletions randutil/rand_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package randutil_test

import (
"math/rand"
"testing"
"time"

. "gopkg.in/check.v1"

"github.com/canonical/x-go/randutil"
)

func Test(t *testing.T) { TestingT(t) }

type randutilSuite struct{}

var _ = Suite(&randutilSuite{})

func (s *randutilSuite) TestRandomString(c *C) {
// for our tests
rand.Seed(1)

s1 := randutil.RandomString(10)
c.Assert(s1, Equals, "pw7MpXh0JB")

s2 := randutil.RandomString(5)
c.Assert(s2, Equals, "4PQyl")
}

func (s *randutilSuite) TestRandomDuration(c *C) {
// ensure moreMixedSeed is done
d := randutil.RandomDuration(time.Hour)
c.Check(d < time.Hour, Equals, true)

// for our tests
rand.Seed(1)

d1 := randutil.RandomDuration(time.Hour)
c.Assert(d1, Equals, time.Duration(1991947779410))

d2 := randutil.RandomDuration(4 * time.Hour)
c.Assert(d2, Equals, time.Duration(4423082153551))
}

0 comments on commit a5240cb

Please sign in to comment.