github.com/safing/portbase@v0.19.5/rng/rng.go (about)

     1  package rng
     2  
     3  import (
     4  	"context"
     5  	"crypto/aes"
     6  	"crypto/cipher"
     7  	"crypto/rand"
     8  	"errors"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"github.com/aead/serpent"
    13  	"github.com/seehuhn/fortuna"
    14  
    15  	"github.com/safing/portbase/modules"
    16  )
    17  
    18  var (
    19  	rng      *fortuna.Generator
    20  	rngLock  sync.Mutex
    21  	rngReady = false
    22  
    23  	rngCipher = "aes"
    24  	// Possible values: "aes", "serpent".
    25  
    26  	module *modules.Module
    27  )
    28  
    29  func init() {
    30  	module = modules.Register("rng", nil, start, nil)
    31  }
    32  
    33  func newCipher(key []byte) (cipher.Block, error) {
    34  	switch rngCipher {
    35  	case "aes":
    36  		return aes.NewCipher(key)
    37  	case "serpent":
    38  		return serpent.NewCipher(key)
    39  	default:
    40  		return nil, fmt.Errorf("unknown or unsupported cipher: %s", rngCipher)
    41  	}
    42  }
    43  
    44  func start() error {
    45  	rngLock.Lock()
    46  	defer rngLock.Unlock()
    47  
    48  	rng = fortuna.NewGenerator(newCipher)
    49  	if rng == nil {
    50  		return errors.New("failed to initialize rng")
    51  	}
    52  
    53  	// add another (async) OS rng seed
    54  	module.StartWorker("initial rng feed", func(_ context.Context) error {
    55  		// get entropy from OS
    56  		osEntropy := make([]byte, minFeedEntropy/8)
    57  		_, err := rand.Read(osEntropy)
    58  		if err != nil {
    59  			return fmt.Errorf("could not read entropy from os: %w", err)
    60  		}
    61  		// feed
    62  		rngLock.Lock()
    63  		rng.Reseed(osEntropy)
    64  		rngLock.Unlock()
    65  		return nil
    66  	})
    67  
    68  	// mark as ready
    69  	rngReady = true
    70  
    71  	// random source: OS
    72  	module.StartServiceWorker("os rng feeder", 0, osFeeder)
    73  
    74  	// random source: goroutine ticks
    75  	module.StartServiceWorker("tick rng feeder", 0, tickFeeder)
    76  
    77  	// full feeder
    78  	module.StartServiceWorker("full feeder", 0, fullFeeder)
    79  
    80  	return nil
    81  }