Skip to content

Commit

Permalink
Update to crypto secure random number generation (Azure#15190)
Browse files Browse the repository at this point in the history
  • Loading branch information
RickWinter authored and vindicatesociety committed Sep 18, 2021
1 parent 47927cd commit 8702aa5
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 65 deletions.
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
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)
}
})
}
}

0 comments on commit 8702aa5

Please sign in to comment.