package rng import ( "context" "crypto/aes" "crypto/cipher" "crypto/rand" "errors" "fmt" "sync" "github.com/aead/serpent" "github.com/seehuhn/fortuna" "github.com/safing/portbase/modules" ) var ( rng *fortuna.Generator rngLock sync.Mutex rngReady = false rngCipher = "aes" // Possible values: "aes", "serpent". module *modules.Module ) func init() { module = modules.Register("rng", nil, start, nil) } func newCipher(key []byte) (cipher.Block, error) { switch rngCipher { case "aes": return aes.NewCipher(key) case "serpent": return serpent.NewCipher(key) default: return nil, fmt.Errorf("unknown or unsupported cipher: %s", rngCipher) } } func start() error { rngLock.Lock() defer rngLock.Unlock() rng = fortuna.NewGenerator(newCipher) if rng == nil { return errors.New("failed to initialize rng") } // add another (async) OS rng seed module.StartWorker("initial rng feed", func(_ context.Context) error { // get entropy from OS osEntropy := make([]byte, minFeedEntropy/8) _, err := rand.Read(osEntropy) if err != nil { return fmt.Errorf("could not read entropy from os: %w", err) } // feed rngLock.Lock() rng.Reseed(osEntropy) rngLock.Unlock() return nil }) // mark as ready rngReady = true // random source: OS module.StartServiceWorker("os rng feeder", 0, osFeeder) // random source: goroutine ticks module.StartServiceWorker("tick rng feeder", 0, tickFeeder) // full feeder module.StartServiceWorker("full feeder", 0, fullFeeder) return nil }