Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to crypto secure random number generation #15190

Merged
3 commits merged into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions sdk/internal/recording/recording.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,16 @@ func (r *Recording) Now() time.Time {

func (r *Recording) UUID() uuid.UUID {
r.initRandomSource()

return uuid.FromSource(r.src)
u := uuid.UUID{}
// Set all bits to randomly (or pseudo-randomly) chosen values.
// math/rand.Read() is no-fail so we omit any error checking.
rnd := rand.New(r.src)
rnd.Read(u[:])
u[8] = (u[8] | 0x40) & 0x7F // u.setVariant(ReservedRFC4122)

var version byte = 4
u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4)
return u
}

// GenerateAlphaNumericID will generate a recorded random alpha numeric id
Expand Down
12 changes: 10 additions & 2 deletions sdk/internal/recording/request_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func (s *requestMatcherTests) TestCompareBodies() {
assert.False(isMatch)
}

func newUUID(t *testing.T) string {
u, err := uuid.New()
if err != nil {
t.Fatal(err)
}
return u.String()
}

func (s *requestMatcherTests) TestCompareHeadersIgnoresIgnoredHeaders() {
assert := assert.New(s.T())
context := NewTestContext(func(msg string) { assert.FailNow(msg) }, func(msg string) { s.T().Log(msg) }, func() string { return s.T().Name() })
Expand All @@ -60,8 +68,8 @@ func (s *requestMatcherTests) TestCompareHeadersIgnoresIgnoredHeaders() {
reqHeaders := make(http.Header)
recordedHeaders := make(http.Header)
for headerName := range ignoredHeaders {
reqHeaders[headerName] = []string{uuid.New().String()}
recordedHeaders[headerName] = []string{uuid.New().String()}
reqHeaders[headerName] = []string{newUUID(s.T())}
recordedHeaders[headerName] = []string{newUUID(s.T())}
}

req := http.Request{Header: reqHeaders}
Expand Down
101 changes: 40 additions & 61 deletions sdk/internal/uuid/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,91 +7,70 @@
package uuid

import (
"crypto/rand"
"errors"
"fmt"
"math/rand"
"strconv"
"time"
)

// The UUID reserved variants.
const (
reservedRFC4122 byte = 0x40
)

func init() {
rand.Seed(time.Now().Unix())
}

// A UUID representation compliant with specification in RFC 4122 document.
// A UUID representation compliant with specification in RFC4122 document.
type UUID [16]byte

// New returns a new uuid using RFC 4122 algorithm.
func New() UUID {
// New returns a new UUID using the RFC4122 algorithm.
func New() (UUID, error) {
u := UUID{}
// Set all bits to randomly (or pseudo-randomly) chosen values.
// math/rand.Read() is no-fail so we omit any error checking.
// Set all bits to pseudo-random values.
// NOTE: this takes a process-wide lock
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
rand.Read(u[:])
u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122)

var version byte = 4
u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4)
return u
}

// FromSource returns a new uuid based on the supplied rand.Source as a seed.
func FromSource(src rand.Source) UUID {
u := UUID{}
// Set all bits to randomly (or pseudo-randomly) chosen values.
// math/rand.Read() is no-fail so we omit any error checking.
rnd := rand.New(src)
rnd.Read(u[:])
_, err := rand.Read(u[:])
if err != nil {
return u, err
}
u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122)

var version byte = 4
u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4)
return u
return u, nil
}

// String returns an unparsed version of the generated UUID sequence.
// String returns the UUID in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format.
func (u UUID) String() string {
return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}

// Parse parses a string formatted as "003020100-0504-0706-0809-0a0b0c0d0e0f"
// or "{03020100-0504-0706-0809-0a0b0c0d0e0f}" into a UUID.
func Parse(uuidStr string) UUID {
char := func(hexString string) byte {
i, _ := strconv.ParseUint(hexString, 16, 8)
return byte(i)
// Parse parses a string formatted as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
// or "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" into a UUID.
func Parse(s string) (UUID, error) {
var uuid UUID
// ensure format
switch len(s) {
case 36:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 38:
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
s = s[1:37]
default:
return uuid, errors.New("invalid UUID format")
}
if uuidStr[0] == '{' {
uuidStr = uuidStr[1:] // Skip over the '{'
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
// 03020100 - 05 04 - 07 06 - 08 09 - 0a 0b 0c 0d 0e 0f
// 1 11 1 11 11 1 12 22 2 22 22 22 33 33 33
// 01234567 8 90 12 3 45 67 8 90 12 3 45 67 89 01 23 45
uuidVal := UUID{
char(uuidStr[0:2]),
char(uuidStr[2:4]),
char(uuidStr[4:6]),
char(uuidStr[6:8]),

char(uuidStr[9:11]),
char(uuidStr[11:13]),

char(uuidStr[14:16]),
char(uuidStr[16:18]),

char(uuidStr[19:21]),
char(uuidStr[21:23]),

char(uuidStr[24:26]),
char(uuidStr[26:28]),
char(uuidStr[28:30]),
char(uuidStr[30:32]),
char(uuidStr[32:34]),
char(uuidStr[34:36]),
// parse chunks
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
b, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return uuid, fmt.Errorf("invalid UUID format: %s", err)
}
uuid[i] = byte(b)
}
return uuidVal
return uuid, nil
}
56 changes: 56 additions & 0 deletions sdk/internal/uuid/uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build go1.16
// +build go1.16

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package uuid

import (
"reflect"
"regexp"
"testing"
)

func TestNew(t *testing.T) {
u, err := New()
if err != nil {
t.Fatal(err)
}
if reflect.ValueOf(u).IsZero() {
t.Fatal("unexpected zero-value UUID")
}
s := u.String()
match, err := regexp.MatchString(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`, s)
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("invalid UUID string %s", s)
}
}

func TestParse(t *testing.T) {
testCases := []string{
"72d0f24f-82be-4016-729d-31fd13bd681e",
"{72d0f24f-82be-4016-729d-31fd13bd681e}",
}
for _, input := range testCases {
t.Run(input, func(t *testing.T) {
u, err := Parse(input)
if err != nil {
t.Fatal(err)
}
if reflect.ValueOf(u).IsZero() {
t.Fatal("unexpected zero-value UUID")
}
if len(input) > 36 {
// strip off the {} as String() doesn't output them
input = input[1:37]
}
if s := u.String(); s != input {
t.Fatalf("didn't round trip: %s", s)
}
})
}
}