Skip to content

Commit

Permalink
feat: tpm2 based disk encryption
Browse files Browse the repository at this point in the history
Support disk encryption using tpm2 and pre-calculated signed PCR values.

Fixes: siderolabs#7266

Signed-off-by: Noel Georgi <git@frezbo.dev>
  • Loading branch information
frezbo committed Jul 12, 2023
1 parent 06369e8 commit 79365d9
Show file tree
Hide file tree
Showing 29 changed files with 1,144 additions and 38 deletions.
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ FROM ghcr.io/siderolabs/grub:${PKGS} AS pkg-grub
FROM --platform=amd64 ghcr.io/siderolabs/grub:${PKGS} AS pkg-grub-amd64
FROM --platform=arm64 ghcr.io/siderolabs/grub:${PKGS} AS pkg-grub-arm64

FROM ghcr.io/siderolabs/sd-boot:${PKGS} AS pkg-sd-boot
FROM --platform=amd64 ghcr.io/siderolabs/sd-boot:${PKGS} AS pkg-sd-boot-amd64
FROM --platform=arm64 ghcr.io/siderolabs/sd-boot:${PKGS} AS pkg-sd-boot-arm64

Expand Down Expand Up @@ -551,7 +550,7 @@ END
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
RUN <<END
cleanup.sh /rootfs
mkdir -pv /rootfs/{boot/EFI,etc/cri/conf.d/hosts,lib/firmware,usr/local/share,usr/share/zoneinfo/Etc,mnt,system,opt}
mkdir -pv /rootfs/{boot/EFI,etc/cri/conf.d/hosts,lib/firmware,usr/local/share,usr/share/zoneinfo/Etc,mnt,system,opt,.extra}
mkdir -pv /rootfs/{etc/kubernetes/manifests,etc/cni/net.d,usr/libexec/kubernetes}
mkdir -pv /rootfs/opt/{containerd/bin,containerd/lib}
END
Expand Down Expand Up @@ -615,7 +614,7 @@ END
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
RUN <<END
cleanup.sh /rootfs
mkdir -pv /rootfs/{boot,etc/cri/conf.d/hosts,lib/firmware,usr/local/share,usr/share/zoneinfo/Etc,mnt,system,opt}
mkdir -pv /rootfs/{boot/EFI,etc/cri/conf.d/hosts,lib/firmware,usr/local/share,usr/share/zoneinfo/Etc,mnt,system,opt,.extra}
mkdir -pv /rootfs/{etc/kubernetes/manifests,etc/cni/net.d,usr/libexec/kubernetes}
mkdir -pv /rootfs/opt/{containerd/bin,containerd/lib}
END
Expand Down
5 changes: 5 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,11 @@ func create(ctx context.Context, flags *pflag.FlagSet) (err error) {
})

provisionOptions = append(provisionOptions, provision.WithKMS(nethelpers.JoinHostPort("0.0.0.0", port)))
case "tpm":
keys = append(keys, &v1alpha1.EncryptionKey{
KeyTPM: &v1alpha1.EncryptionKeyTPM{},
KeySlot: i,
})
default:
return fmt.Errorf("unknown key type %q", key)
}
Expand Down
22 changes: 22 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,28 @@ systemDiskEncryption:
```
gRPC API definitions and a simple reference implementation of the KMS server can be found in this
[repository](https://github.com/siderolabs/kms-client/blob/main/cmd/kms-server/main.go).
"""

[notes.tpm-encryption-]
title = "TPM Disk Encryption"
description="""\
Talos now supports encrypting STATE/EPHEMERAL with keys bound to a TPM device. The TPM device must be TPM2.0 compatible.
This is ideally supported when booting with new Talos SecureBoot UKI ISOs/Metal images. This feature would still work if SecureBoot
is not enabled for UKI images, but not recommended since there is no way to verify the trust of the bootloader.
Example machine config:
```
systemDiskEncryption:
ephemeral:
keys:
- slot: 0
tpm: {}
state:
keys:
- slot: 0
tpm: {}
```
"""

[notes.talosctl-images]
Expand Down
2 changes: 1 addition & 1 deletion hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ case "${WITH_TRUSTED_BOOT_ISO:-false}" in
false)
;;
*)
QEMU_FLAGS+=("--iso-path=_out/talos-uki-amd64.iso" "--with-secureboot" "--with-tpm2")
QEMU_FLAGS+=("--iso-path=_out/talos-uki-amd64.iso" "--with-secureboot" "--with-tpm2" "--encrypt-ephemeral" "--encrypt-state" "--disk-encryption-key-types=tpm")
;;
esac

Expand Down
36 changes: 31 additions & 5 deletions hack/ukify/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
Splash Section = ".splash"
DTB Section = ".dtb"
Uname Section = ".uname"
SBAT Section = ".sbat"
PCRSig Section = ".pcrsig"
PCRPKey Section = ".pcrpkey"
)
Expand All @@ -23,24 +24,49 @@ const (
// .pcrsig section is omitted here since that's what we are calulating here
func OrderedSections() []Section {
// DO NOT REARRANGE
return []Section{Linux, OSRel, CMDLine, Initrd, Splash, DTB, Uname, PCRPKey}
return []Section{Linux, OSRel, CMDLine, Initrd, Splash, DTB, Uname, SBAT, PCRPKey}
}

type Phase string

const (
// EnterInitrd is the phase value extended to the PCR during the initrd.
EnterInitrd Phase = "enter-initrd"
// LeaveInitrd is the phase value extended to the PCR just before switching to machined.
LeaveInitrd Phase = "leave-initrd"
SysInit Phase = "sysinit"
Ready Phase = "ready"
// EnterMachined is the phase value extended to the PCR before starting machined.
// The should be only a signed signature for the enter-machined phase.
EnterMachined Phase = "enter-machined"
// StartTheWorld is the phase value extended to the PCR before starting all services.
StartTheWorld Phase = "start-the-world"
)

type PhaseInfo struct {
Phase Phase
CalculateSignature bool
}

// derived from https://github.com/systemd/systemd/blob/v253/src/boot/measure.c#L295-L308
// ref: https://www.freedesktop.org/software/systemd/man/systemd-pcrphase.service.html#Description
// In the case of Talos disk decryption, happens in machined, so we need to only sign EnterMachined
// so that machined can only decrypt the disk if the system booted with the correct kernel/initrd/cmdline
// OrderedPhases returns the phases that are measured
func OrderedPhases() []Phase {
func OrderedPhases() []PhaseInfo {
// DO NOT REARRANGE
return []Phase{EnterInitrd, LeaveInitrd, SysInit, Ready}
return []PhaseInfo{
{
Phase: EnterInitrd,
CalculateSignature: false,
},
{
Phase: LeaveInitrd,
CalculateSignature: false,
},
{
Phase: EnterMachined,
CalculateSignature: true,
},
}
}

const (
Expand Down
73 changes: 64 additions & 9 deletions hack/ukify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type section struct {
name constants.Section
file string
measure bool
append bool
size uint64
vma uint64
}
Expand Down Expand Up @@ -121,6 +122,10 @@ func buildUKI(source, output string, sections []section) error {

// calculate sections size and VMA
for i := range sections {
if !sections[i].append {
continue
}

st, err := os.Stat(sections[i].file)
if err != nil {
return err
Expand All @@ -137,6 +142,10 @@ func buildUKI(source, output string, sections []section) error {
args := []string{}

for _, section := range sections {
if !section.append {
continue
}

args = append(args, "--add-section", fmt.Sprintf("%s=%s", section.name, section.file), "--change-section-vma", fmt.Sprintf("%s=0x%x", section.name, section.vma))
}

Expand All @@ -149,7 +158,7 @@ func buildUKI(source, output string, sections []section) error {
return cmd.Run()
}

func Measure(tempDir, kernel, signingKey string, sections []section) ([]section, error) {
func Measure(tempDir, signingKey string, sections []section) ([]section, error) {
sectionsData := measure.SectionsData{}

for _, section := range sections {
Expand All @@ -160,9 +169,6 @@ func Measure(tempDir, kernel, signingKey string, sections []section) ([]section,
sectionsData[section.name] = section.file
}

// manually add the linux section
sectionsData[constants.Linux] = kernel

pcrpsigFile := filepath.Join(tempDir, "pcrpsig")

pcrData, err := measure.GenerateSignedPCR(sectionsData, signingKey)
Expand All @@ -183,6 +189,7 @@ func Measure(tempDir, kernel, signingKey string, sections []section) ([]section,
name: constants.PCRSig,
file: pcrpsigFile,
measure: false,
append: true,
})

return sections, nil
Expand Down Expand Up @@ -276,53 +283,78 @@ func run() error {
return err
}

sbat, closeFunc, err := parseSBATFromStub()
if err != nil {
return err
}

defer closeFunc() //nolint:errcheck

sbatFile := filepath.Join(tempDir, "sbat")

if err = os.WriteFile(sbatFile, sbat, 0o644); err != nil {
return err
}

sections := []section{
{
name: constants.OSRel,
file: osReleaseFile,
measure: true,
append: true,
},
{
name: constants.CMDLine,
file: cmdlineFile,
measure: true,
append: true,
},
{
name: constants.Initrd,
file: initrd,
measure: true,
append: true,
},
{
name: constants.Splash,
file: splashFile,
measure: true,
append: true,
},
{
name: constants.Uname,
file: unameFile,
measure: true,
append: true,
},
{
name: constants.SBAT,
file: sbatFile,
measure: true,
},
{
name: constants.PCRPKey,
file: pcrPublicKey,
measure: true,
append: true,
},
}

// systemd-measure
if sections, err = Measure(tempDir, signedKernel, pcrSigningKey, sections); err != nil {
return err
}

// kernel is added last to account for decompression
sections = append(sections,
section{
name: constants.Linux,
file: signedKernel,
measure: true,
append: true,
},
)

// systemd-measure
if sections, err = Measure(tempDir, pcrSigningKey, sections); err != nil {
return err
}

if err = os.RemoveAll(output); err != nil {
return err
}
Expand All @@ -336,6 +368,29 @@ func run() error {
return err
}

func parseSBATFromStub() ([]byte, func() error, error) {
pefile, err := pe.New(sdStub, &pe.Options{Fast: true})
if err != nil {
return nil, pefile.Close, err
}

if err := pefile.Parse(); err != nil {
return nil, pefile.Close, err
}

var sbatData []byte

for _, section := range pefile.Sections {
if section.String() == string(constants.SBAT) {
sbatData = section.Data(section.Header.VirtualAddress, section.Header.VirtualSize, pefile)

break
}
}

return sbatData, pefile.Close, nil
}

func main() {
if err := run(); err != nil {
log.Fatal(err)
Expand Down
16 changes: 10 additions & 6 deletions hack/ukify/measure/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,15 @@ func calculatePCRBankData(pcr int, alg tpm2.TPMAlgID, sectionData SectionsData,
}
}

banks := make([]bankData, len(constants.OrderedPhases()))
banks := make([]bankData, 0)

for i, phase := range constants.OrderedPhases() {
hashData.Extend([]byte(phase))
for _, phaseInfo := range constants.OrderedPhases() {
// extend always, but only calculate signature if requested
hashData.Extend([]byte(phaseInfo.Phase))

if !phaseInfo.CalculateSignature {
continue
}

hash := hashData.Hash()

Expand All @@ -108,12 +113,12 @@ func calculatePCRBankData(pcr int, alg tpm2.TPMAlgID, sectionData SectionsData,
return nil, err
}

banks[i] = bankData{
banks = append(banks, bankData{
PCRS: []int{pcr},
PKFP: hex.EncodeToString(pubKeyFingerprint[:]),
SIG: sigData.SignatureBase64,
POL: sigData.Digest,
}
})
}

return banks, nil
Expand Down Expand Up @@ -204,7 +209,6 @@ func GenerateSignedPCR(sectionsData SectionsData, rsaKey string) (*PCRData, erro
}

func createPCRSelection(s []int) ([]byte, error) {

const sizeOfPCRSelect = 3

PCRs := make(tpmutil.RawBytes, sizeOfPCRSelect)
Expand Down
Loading

0 comments on commit 79365d9

Please sign in to comment.