From a0855d6876fdb87be793cf52861a738f03479e96 Mon Sep 17 00:00:00 2001 From: Itxaka Date: Tue, 21 May 2024 14:27:14 +0200 Subject: [PATCH] Rework TPM workflow In order to expose more values especific to the tpm part Signed-off-by: Itxaka --- pkg/lib/lock.go | 162 ++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 60 deletions(-) diff --git a/pkg/lib/lock.go b/pkg/lib/lock.go index 7953c98..a76e77c 100644 --- a/pkg/lib/lock.go +++ b/pkg/lib/lock.go @@ -13,11 +13,8 @@ import ( "time" ) -func CreateLuks(dev, password, version string, cryptsetupArgs ...string) error { - if version == "" { - version = "luks2" - } - args := []string{"luksFormat", "--type", version, "--iter-time", "5", "-q", dev} +func CreateLuks(dev, password string, cryptsetupArgs ...string) error { + args := []string{"luksFormat", "--type", "luks2", "--iter-time", "5", "-q", dev} args = append(args, cryptsetupArgs...) cmd := exec.Command("cryptsetup", args...) cmd.Stdin = strings.NewReader(password) @@ -48,87 +45,132 @@ func getRandomString(length int) string { // This is because the label of the encrypted partition is not accessible unless // the partition is decrypted first and the uuid changed after encryption so // any stored information needs to be updated (by the caller). -func Luksify(label, version string, tpm bool) (string, error) { +func Luksify(label string) (string, error) { var pass string - if version == "" { - version = "luks1" - } - if version != "luks1" && version != "luks2" { - return "", fmt.Errorf("version must be luks1 or luks2") - } part, b, err := FindPartition(label) if err != nil { return "", err } - if tpm { - // On TPM locking we generate a random password that will only be used here then discarded. - // only unlocking method will be PCR values - pass = getRandomString(32) - } else { - pass, err = GetPassword(b) - if err != nil { - return "", err - } + pass, err = GetPassword(b) + if err != nil { + return "", err } part = fmt.Sprintf("/dev/%s", part) - devMapper := fmt.Sprintf("/dev/mapper/%s", b.Name) + partUUID := uuid.NewV5(uuid.NamespaceURL, label) + extraArgs := []string{"--uuid", partUUID.String()} + + if err := CreateLuks(part, pass, extraArgs...); err != nil { + return "", err + } + + err = formatLuks(part, label, pass) + if err != nil { + return "", err + } + + return configpkg.PartitionToString(b), nil +} + +// LuksifyMeasurements takes a label and a list if public-keys and pcrs to bind and uses the measurements +// in the current node to encrypt the partition with those and bind those to the given pcrs +// this expects systemd 255 as it needs the SRK public key that systemd extracts +// Sets a random password, enrolls the policy, unlocks and formats the partition, closes it and tfinally removes the random password from it +// Note that there is a diff between the publicKeyPcrs and normal Pcrs +// The former links to a policy type that allows anything signed by that policy to unlcok the partitions so its +// really useful for binding to PCR11 which is the UKI measurements in order to be able to upgrade the system and still be able +// to unlock the partitions. +// The later binds to a SINGLE measurement, so if that changes, it will not unlock anything. +// This is useful for things like PCR7 which measures the secureboot state and certificates if you dont expect those to change during +// the whole lifetime of a machine +// It can also be used to bind to things like the firmware code or efi drivers that we dont expect to change +// default for publicKeyPcrs is 11 +// default for pcrs is nothing, so it doesn't bind as we want to expand things like DBX and be able to blacklist certs and such +func LuksifyMeasurements(label string, publicKeyPcrs []string, pcrs []string) error { + part, _, err := FindPartition(label) + if err != nil { + return err + } + + // On TPM locking we generate a random password that will only be used here then discarded. + // only unlocking method will be PCR values + pass := getRandomString(32) + + part = fmt.Sprintf("/dev/%s", part) partUUID := uuid.NewV5(uuid.NamespaceURL, label) extraArgs := []string{"--uuid", partUUID.String()} - if err := CreateLuks(part, pass, version, extraArgs...); err != nil { - return "", err - } - if tpm { - // Enroll PCR policy as a keyslot - // We pass the current signature of the booted system to confirm that we would be able to unlock with the current booted system - // That checks the policy against the signatures and fails if a UKI with those signatures wont be able to unlock the device - // Files are generated by systemd automatically and are extracted from the UKI binary directly - // public pem cert -> .pcrpkey section fo the elf file - // signatures -> .pcrsig section of the elf file - // leave --tpm2-pcrs= to an empty value so it doesnt bind to a single measure - args := []string{"--tpm2-public-key=/run/systemd/tpm2-pcr-public-key.pem", "--tpm2-public-key-pcrs=11", "--tpm2-pcrs=", "--tpm2-signature=/run/systemd/tpm2-pcr-signature.json", "--tpm2-device-key=/run/systemd/tpm2-srk-public-key.tpm2b_public", part} - cmd := exec.Command("systemd-cryptenroll", args...) - cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", pass)) // cannot pass it via stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return "", err - } + if err := CreateLuks(part, pass, extraArgs...); err != nil { + return err } - if err := LuksUnlock(part, b.Name, pass); err != nil { - return "", fmt.Errorf("unlock err: %w", err) + if len(publicKeyPcrs) == 0 { + publicKeyPcrs = []string{"11"} } + // Enroll PCR policy as a keyslot + // We pass the current signature of the booted system to confirm that we would be able to unlock with the current booted system + // That checks the policy against the signatures and fails if a UKI with those signatures wont be able to unlock the device + // Files are generated by systemd automatically and are extracted from the UKI binary directly + // public pem cert -> .pcrpkey section fo the elf file + // signatures -> .pcrsig section of the elf file + args := []string{ + "--tpm2-public-key=/run/systemd/tpm2-pcr-public-key.pem", + fmt.Sprintf("--tpm2-public-key-pcrs=%s", strings.Join(publicKeyPcrs, "+")), + fmt.Sprintf("--tpm2-pcrs=%s", strings.Join(pcrs, "+")), + "--tpm2-signature=/run/systemd/tpm2-pcr-signature.json", + "--tpm2-device-key=/run/systemd/tpm2-srk-public-key.tpm2b_public", + part} + cmd := exec.Command("systemd-cryptenroll", args...) + cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", pass)) // cannot pass it via stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return err + } + + err = formatLuks(part, label, pass) + if err != nil { + return err + } + + // Delete password slot from luks device + out, err := SH(fmt.Sprintf("systemd-cryptenroll --wipe-slot=password %s", part)) + if err != nil { + return fmt.Errorf("err: %w, out: %s", err, out) + } + return nil +} + +// format luks will unlock the device, wait for it and then format it +// device is the actual /dev/X luks device +// label is the label we will set to the formatted partition +// password is the pass to unlock the device to be able to format the underlying mapper +func formatLuks(device, label, pass string) error { + devMapper := fmt.Sprintf("/dev/mapper/%s", device) + + if err := LuksUnlock(device, device, pass); err != nil { + return fmt.Errorf("unlock err: %w", err) + } if err := Waitdevice(devMapper, 10); err != nil { - return "", fmt.Errorf("waitdevice err: %w", err) + return fmt.Errorf("waitdevice err: %w", err) } - cmd := fmt.Sprintf("mkfs.ext4 -L %s %s", label, devMapper) - out, err := SH(cmd) + cmdFormat := fmt.Sprintf("mkfs.ext4 -L %s %s", label, devMapper) + out, err := SH(cmdFormat) if err != nil { - return "", fmt.Errorf("mkfs err: %w, out: %s", err, out) + return fmt.Errorf("mkfs err: %w, out: %s", err, out) } - out, err = SH(fmt.Sprintf("cryptsetup close %s", b.Name)) + out, err = SH(fmt.Sprintf("cryptsetup close %s", device)) if err != nil { - return "", fmt.Errorf("lock err: %w, out: %s", err, out) + return fmt.Errorf("lock err: %w, out: %s", err, out) } - - if tpm { - // Delete password slot from luks device - out, err := SH(fmt.Sprintf("systemd-cryptenroll --wipe-slot=password %s", part)) - if err != nil { - return "", fmt.Errorf("err: %w, out: %s", err, out) - } - } - - return configpkg.PartitionToString(b), nil + return nil } func FindPartition(label string) (string, *block.Partition, error) {