From d23d04de2a5dee30ccf21efe767daf229de78bdb Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 7 Jul 2023 23:50:22 +0400 Subject: [PATCH] feat: seed the kernel random pool from the TPM Use the TPM2 feature to provide high-quality random bytes. Signed-off-by: Andrey Smirnov --- go.mod | 1 + go.sum | 2 + internal/app/init/main.go | 7 ++++ internal/pkg/rng/pool.go | 41 ++++++++++++++++++ internal/pkg/rng/rng.go | 6 +++ internal/pkg/rng/tpm.go | 87 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+) create mode 100644 internal/pkg/rng/pool.go create mode 100644 internal/pkg/rng/rng.go create mode 100644 internal/pkg/rng/tpm.go diff --git a/go.mod b/go.mod index 5a368f30e..774e28862 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 + github.com/google/go-tpm v0.9.0 github.com/google/gopacket v1.1.19 github.com/google/nftables v0.1.0 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 7e749cec5..184c067bd 100644 --- a/go.sum +++ b/go.sum @@ -687,6 +687,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= diff --git a/internal/app/init/main.go b/internal/app/init/main.go index 7453c3b82..d539b3e9f 100644 --- a/internal/app/init/main.go +++ b/internal/app/init/main.go @@ -24,6 +24,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/mount" "github.com/siderolabs/talos/internal/pkg/mount/switchroot" + "github.com/siderolabs/talos/internal/pkg/rng" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/extensions" "github.com/siderolabs/talos/pkg/version" @@ -51,6 +52,12 @@ func run() (err error) { return err } + // Seed RNG. + if err = rng.TPMSeed(); err != nil { + // not making this fatal error + log.Printf("failed to seed from the TPM: %s", err) + } + log.Printf("booting Talos %s", version.Tag) // Mount the rootfs. diff --git a/internal/pkg/rng/pool.go b/internal/pkg/rng/pool.go new file mode 100644 index 000000000..ed0aecdb0 --- /dev/null +++ b/internal/pkg/rng/pool.go @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package rng + +import ( + "bytes" + "fmt" + "os" + "strconv" + + "golang.org/x/sys/unix" +) + +// GetPoolSize returns kernel random pool size. +func GetPoolSize() (int, error) { + contents, err := os.ReadFile("/proc/sys/kernel/random/poolsize") + if err != nil { + return 0, err + } + + return strconv.Atoi(string(bytes.TrimSpace(contents))) +} + +// WriteEntropy writes entropy data to the pool. +func WriteEntropy(data []byte) error { + fd, err := os.OpenFile("/dev/urandom", os.O_WRONLY|unix.O_CLOEXEC|unix.O_NOCTTY, 0) + if err != nil { + return err + } + + defer fd.Close() //nolint:errcheck + + _, err = fd.Write(data) + if err != nil { + return fmt.Errorf("error writing entropy: %w", err) + } + + return fd.Close() +} diff --git a/internal/pkg/rng/rng.go b/internal/pkg/rng/rng.go new file mode 100644 index 000000000..67cfca5bc --- /dev/null +++ b/internal/pkg/rng/rng.go @@ -0,0 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package rng handles interaction with kernel random number generator. +package rng diff --git a/internal/pkg/rng/tpm.go b/internal/pkg/rng/tpm.go new file mode 100644 index 000000000..7b6353364 --- /dev/null +++ b/internal/pkg/rng/tpm.go @@ -0,0 +1,87 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package rng + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" +) + +// TPMSeed seeds the random entropy pool from the TPM. +// +//nolint:gocyclo +func TPMSeed() error { + t, err := transport.OpenTPM() + if err != nil { + if os.IsNotExist(err) { + log.Printf("TPM device is not available") + + return nil + } + + return fmt.Errorf("error opening TPM device: %w", err) + } + + defer t.Close() //nolint:errcheck + + caps, err := tpm2.GetCapability{ + Capability: tpm2.TPMCapTPMProperties, + Property: uint32(tpm2.TPMPTManufacturer), + PropertyCount: 1, + }.Execute(t) + if err != nil { + return fmt.Errorf("error getting TPM capabilities: %w", err) + } + + props, err := caps.CapabilityData.Data.TPMProperties() + if err != nil { + return fmt.Errorf("error getting properties: %w", err) + } + + log.Printf("TPM manufacturer ID: %08x", props.TPMProperty[0].Value) + + poolSize, err := GetPoolSize() + if err != nil { + return fmt.Errorf("error getting pool size: %w", err) + } + + remaining := poolSize + start := time.Now() + + for remaining > 0 { + chunk := 32 // default to small chunk (size of AES key) + if remaining < chunk { + chunk = remaining + } + + cmd := tpm2.GetRandom{ + BytesRequested: uint16(chunk), + } + + resp, err := cmd.Execute(t) + if err != nil { + return fmt.Errorf("error getting random data from the TPM: %w", err) + } + + if len(resp.RandomBytes.Buffer) == 0 { + return fmt.Errorf("received zero random bytes from the TPM: %w", err) + } + + if err = WriteEntropy(resp.RandomBytes.Buffer); err != nil { + return fmt.Errorf("error writing random pool entropy: %w", err) + } + + remaining -= len(resp.RandomBytes.Buffer) + } + + log.Printf("seeded random pool with %d bytes from the TPM in %s", poolSize, time.Since(start)) + + return nil +}