github.com/3andne/restls-client-go@v0.1.6/u_prng.go (about)

     1  /*
     2   * Copyright (c) 2019, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * Released under utls licence:
     6   * https://github.com/refraction-networking/utls/blob/master/LICENSE
     7   */
     8  
     9  // This code is a pared down version of:
    10  // https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/158caea562287284cc3fa5fcd1b3c97b1addf659/psiphon/common/prng/prng.go
    11  
    12  package tls
    13  
    14  import (
    15  	crypto_rand "crypto/rand"
    16  	"encoding/binary"
    17  	"io"
    18  	"math"
    19  	"math/rand"
    20  	"sync"
    21  
    22  	"golang.org/x/crypto/hkdf"
    23  	"golang.org/x/crypto/sha3"
    24  )
    25  
    26  const (
    27  	PRNGSeedLength = 32
    28  )
    29  
    30  // PRNGSeed is a PRNG seed.
    31  type PRNGSeed [PRNGSeedLength]byte
    32  
    33  // NewPRNGSeed creates a new PRNG seed using crypto/rand.Read.
    34  func NewPRNGSeed() (*PRNGSeed, error) {
    35  	seed := new(PRNGSeed)
    36  	_, err := crypto_rand.Read(seed[:])
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	return seed, nil
    41  }
    42  
    43  // newSaltedPRNGSeed creates a new seed derived from an existing seed and a
    44  // salt. A HKDF is applied to the seed and salt.
    45  //
    46  // newSaltedPRNGSeed is intended for use cases where a single seed needs to be
    47  // used in distinct contexts to produce independent random streams.
    48  func newSaltedPRNGSeed(seed *PRNGSeed, salt string) (*PRNGSeed, error) {
    49  	saltedSeed := new(PRNGSeed)
    50  	_, err := io.ReadFull(
    51  		hkdf.New(sha3.New256, seed[:], []byte(salt), nil), saltedSeed[:])
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	return saltedSeed, nil
    56  }
    57  
    58  // prng is a seeded, unbiased PRNG based on SHAKE256. that is suitable for use
    59  // cases such as obfuscation. Seeding is based on crypto/rand.Read.
    60  //
    61  // This PRNG is _not_ for security use cases including production cryptographic
    62  // key generation.
    63  //
    64  // It is safe to make concurrent calls to a PRNG instance.
    65  //
    66  // PRNG conforms to io.Reader and math/rand.Source, with additional helper
    67  // functions.
    68  type prng struct {
    69  	rand              *rand.Rand
    70  	randomStreamMutex sync.Mutex
    71  	randomStream      sha3.ShakeHash
    72  }
    73  
    74  // newPRNG generates a seed and creates a PRNG with that seed.
    75  func newPRNG() (*prng, error) {
    76  	seed, err := NewPRNGSeed()
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return newPRNGWithSeed(seed)
    81  }
    82  
    83  // newPRNGWithSeed initializes a new PRNG using an existing seed.
    84  func newPRNGWithSeed(seed *PRNGSeed) (*prng, error) {
    85  	shake := sha3.NewShake256()
    86  	_, err := shake.Write(seed[:])
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	p := &prng{
    91  		randomStream: shake,
    92  	}
    93  	p.rand = rand.New(p)
    94  	return p, nil
    95  }
    96  
    97  // newPRNGWithSaltedSeed initializes a new PRNG using a seed derived from an
    98  // existing seed and a salt with NewSaltedSeed.
    99  func newPRNGWithSaltedSeed(seed *PRNGSeed, salt string) (*prng, error) {
   100  	saltedSeed, err := newSaltedPRNGSeed(seed, salt)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	return newPRNGWithSeed(saltedSeed)
   105  }
   106  
   107  // Read reads random bytes from the PRNG stream into b. Read conforms to
   108  // io.Reader and always returns len(p), nil.
   109  func (p *prng) Read(b []byte) (int, error) {
   110  	p.randomStreamMutex.Lock()
   111  	defer p.randomStreamMutex.Unlock()
   112  
   113  	// ShakeHash.Read never returns an error:
   114  	// https://godoc.org/golang.org/x/crypto/sha3#ShakeHash
   115  	_, _ = io.ReadFull(p.randomStream, b)
   116  
   117  	return len(b), nil
   118  }
   119  
   120  // Int63 is equivalent to math/read.Int63.
   121  func (p *prng) Int63() int64 {
   122  	i := p.Uint64()
   123  	return int64(i & (1<<63 - 1))
   124  }
   125  
   126  // Int63 is equivalent to math/read.Uint64.
   127  func (p *prng) Uint64() uint64 {
   128  	var b [8]byte
   129  	p.Read(b[:])
   130  	return binary.BigEndian.Uint64(b[:])
   131  }
   132  
   133  // Seed must exist in order to use a PRNG as a math/rand.Source. This call is
   134  // not supported and ignored.
   135  func (p *prng) Seed(_ int64) {
   136  }
   137  
   138  // FlipWeightedCoin returns the result of a weighted
   139  // random coin flip. If the weight is 0.5, the outcome
   140  // is equally likely to be true or false. If the weight
   141  // is 1.0, the outcome is always true, and if the
   142  // weight is 0.0, the outcome is always false.
   143  //
   144  // Input weights > 1.0 are treated as 1.0.
   145  func (p *prng) FlipWeightedCoin(weight float64) bool {
   146  	if weight > 1.0 {
   147  		weight = 1.0
   148  	}
   149  	f := float64(p.Int63()) / float64(math.MaxInt64)
   150  	return f > 1.0-weight
   151  }
   152  
   153  // Intn is equivalent to math/read.Intn, except it returns 0 if n <= 0
   154  // instead of panicking.
   155  func (p *prng) Intn(n int) int {
   156  	if n <= 0 {
   157  		return 0
   158  	}
   159  	return p.rand.Intn(n)
   160  }
   161  
   162  // Int63n is equivalent to math/read.Int63n, except it returns 0 if n <= 0
   163  // instead of panicking.
   164  func (p *prng) Int63n(n int64) int64 {
   165  	if n <= 0 {
   166  		return 0
   167  	}
   168  	return p.rand.Int63n(n)
   169  }
   170  
   171  // Intn is equivalent to math/read.Perm.
   172  func (p *prng) Perm(n int) []int {
   173  	return p.rand.Perm(n)
   174  }
   175  
   176  // Range selects a random integer in [min, max].
   177  // If min < 0, min is set to 0. If max < min, min is returned.
   178  func (p *prng) Range(min, max int) int {
   179  	if min < 0 {
   180  		min = 0
   181  	}
   182  	if max < min {
   183  		return min
   184  	}
   185  	n := p.Intn(max - min + 1)
   186  	n += min
   187  	return n
   188  }