mirror of
https://github.com/kairos-io/kcrypt.git
synced 2025-08-31 22:50:49 +00:00
Rework TPM workflow
In order to expose more values especific to the tpm part Signed-off-by: Itxaka <itxaka@kairos.io>
This commit is contained in:
162
pkg/lib/lock.go
162
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) {
|
||||
|
Reference in New Issue
Block a user