From 22485bd4f6ecfd0a3e3d0542e6099be7d398f18e Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 26 Mar 2020 08:40:52 +0000 Subject: [PATCH] Add some example binaries This adds some example binaries which are useful for manual testing: - activate-volume: An example that makes use of ActivateVolumeWithTPMSealedKey and ActivateVolumeWithRecoveryKey. - change-pin: An example that makes use of ChangePIN. - provision-status: An example that makes use of ProvisionStatus. - provision-tpm: An example that makes use of ProvistionTPM. - seal-key: An example that makes use of SealKeyToTPM to seal a key to PCR7 on Ubuntu classic systems. - unseal-key: An example that makes use of SealedKeyObject.UnsealFromTPM. --- .gitignore | 6 ++ examples/activate-volume/main.go | 139 ++++++++++++++++++++++++ examples/change-pin/main.go | 68 ++++++++++++ examples/provision-status/main.go | 84 +++++++++++++++ examples/provision-tpm/main.go | 111 +++++++++++++++++++ examples/seal-key/main.go | 174 ++++++++++++++++++++++++++++++ examples/unseal-key/main.go | 94 ++++++++++++++++ 7 files changed, 676 insertions(+) create mode 100644 examples/activate-volume/main.go create mode 100644 examples/change-pin/main.go create mode 100644 examples/provision-status/main.go create mode 100644 examples/provision-tpm/main.go create mode 100644 examples/seal-key/main.go create mode 100644 examples/unseal-key/main.go diff --git a/.gitignore b/.gitignore index 5a46d357..eb913738 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ +examples/activate-volume/activate-volume +examples/change-pin/change-pin +examples/provision-status/provision-status +examples/provision-tpm/provision-tpm +examples/seal-key/seal-key +examples/unseal-key/unseal-key vendor/*/ diff --git a/examples/activate-volume/main.go b/examples/activate-volume/main.go new file mode 100644 index 00000000..d25d9e1e --- /dev/null +++ b/examples/activate-volume/main.go @@ -0,0 +1,139 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "flag" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/snapcore/secboot" +) + +func run() int { + args := flag.Args() + if len(args) == 0 { + fmt.Printf("Usage: activate-volume VOLUME SOURCE-DEVICE SEALED-KEY-FILE [AUTH-FILE] [OPTIONS]\n") + return 0 + } + + if len(args) < 3 { + fmt.Fprintf(os.Stderr, "Cannot activate device: insufficient arguments\n") + return 1 + } + + volume := args[0] + sourceDevice := args[1] + + var keyFilePath string + if args[2] != "" && args[2] != "-" && args[2] != "none" { + keyFilePath = args[2] + } + + var authFilePath string + if len(args) >= 4 && args[3] != "" && args[3] != "-" && args[3] != "none" { + authFilePath = args[3] + } + + var lock bool + var forceRecovery bool + pinTries := 1 + recoveryTries := 1 + var activateOptions []string + + if len(args) >= 5 && args[4] != "" && args[4] != "-" && args[4] != "none" { + opts := strings.Split(args[4], ",") + for _, opt := range opts { + switch { + case opt == "lock": + lock = true + case opt == "force-recovery": + forceRecovery = true + case strings.HasPrefix(opt, "pin-tries="): + u, err := strconv.ParseUint(strings.TrimPrefix(opt, "pin-tries="), 10, 8) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot activate device %s: invalid value for \"recovery-tries=\"\n", sourceDevice) + return 1 + } + pinTries = int(u) + case strings.HasPrefix(opt, "recovery-tries="): + u, err := strconv.ParseUint(strings.TrimPrefix(opt, "recovery-tries="), 10, 8) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot activate device %s: invalid value for \"recovery-tries=\"\n", sourceDevice) + return 1 + } + recoveryTries = int(u) + default: + activateOptions = append(activateOptions, opt) + } + } + } + + var authReader io.Reader + if authFilePath != "" { + f, err := os.Open(authFilePath) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open auth file: %v\n", err) + return 1 + } + defer f.Close() + authReader = f + } + + if !forceRecovery { + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + options := secboot.ActivateWithTPMSealedKeyOptions{ + PINTries: pinTries, + RecoveryKeyTries: recoveryTries, + ActivateOptions: activateOptions, + LockSealedKeyAccess: lock} + if success, err := secboot.ActivateVolumeWithTPMSealedKey(tpm, volume, sourceDevice, keyFilePath, authReader, &options); err != nil { + if !success { + fmt.Fprintf(os.Stderr, "Activation failed: %v\n", err) + return 1 + } + fmt.Printf("Activation succeeded with fallback recovery key: %v\n", err) + } + } else { + options := secboot.ActivateWithRecoveryKeyOptions{ + Tries: recoveryTries, + ActivateOptions: activateOptions} + if err := secboot.ActivateVolumeWithRecoveryKey(volume, sourceDevice, authReader, &options); err != nil { + fmt.Fprintf(os.Stderr, "Activation with recovery key failed: %v\n", err) + return 1 + } + } + + return 0 +} + +func main() { + flag.Parse() + os.Exit(run()) +} diff --git a/examples/change-pin/main.go b/examples/change-pin/main.go new file mode 100644 index 00000000..77c732ac --- /dev/null +++ b/examples/change-pin/main.go @@ -0,0 +1,68 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/snapcore/secboot" +) + +var keyFile string +var currentPin string + +func init() { + flag.StringVar(¤tPin, "current-pin", "", "") + flag.StringVar(&keyFile, "key-file", "", "") +} + +func run() int { + if keyFile == "" { + fmt.Fprintf(os.Stderr, "Cannot change PIN: missing -key-file\n") + return 1 + } + + args := flag.Args() + var pin string + if len(args) > 0 { + pin = args[0] + } + + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + if err := secboot.ChangePIN(tpm, keyFile, currentPin, pin); err != nil { + fmt.Fprintf(os.Stderr, "Cannot change PIN: %v\n", err) + return 1 + } + + return 0 +} + +func main() { + flag.Parse() + os.Exit(run()) +} diff --git a/examples/provision-status/main.go b/examples/provision-status/main.go new file mode 100644 index 00000000..b68c822a --- /dev/null +++ b/examples/provision-status/main.go @@ -0,0 +1,84 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "fmt" + "os" + + "github.com/snapcore/secboot" +) + +func run() int { + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + status, err := secboot.ProvisionStatus(tpm) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot determine status: %v\n", err) + return 1 + } + + if status&secboot.AttrValidSRK > 0 { + fmt.Println("Valid SRK found in TPM") + } else { + fmt.Println("** ERROR: TPM does not have a valid SRK **") + } + + if status&secboot.AttrValidEK > 0 { + fmt.Println("Valid EK found in TPM") + } else { + fmt.Println("** ERROR: TPM does not have a valid EK **") + } + + if status&secboot.AttrDAParamsOK > 0 { + fmt.Println("TPM's DA parameters are correct") + } else { + fmt.Println("** ERROR: TPM's DA parameters are not the values set during provisioning **") + } + + if status&secboot.AttrOwnerClearDisabled > 0 { + fmt.Println("TPM does not allow clearing with the lockout hierarchy authorization") + } else { + fmt.Println("** ERROR: TPM allows clearing with the lockout hierarchy authorization **") + } + + if status&secboot.AttrLockoutAuthSet > 0 { + fmt.Println("The lockout hierarchy authorization is set") + } else { + fmt.Println("** ERROR: The lockout hierarchy authorization is not set **") + } + + if status&secboot.AttrValidLockNVIndex > 0 { + fmt.Println("Valid lock NV index found in TPM") + } else { + fmt.Println("** ERROR: TPM does not have a valid lock NV index **") + } + + return 0 +} + +func main() { + os.Exit(run()) +} diff --git a/examples/provision-tpm/main.go b/examples/provision-tpm/main.go new file mode 100644 index 00000000..2eefcdb4 --- /dev/null +++ b/examples/provision-tpm/main.go @@ -0,0 +1,111 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/snapcore/secboot" +) + +var ( + clear bool + lockoutAuth string + noLockoutAuth bool + ownerAuth string + endorsementAuth string + requestClear bool +) + +func init() { + flag.BoolVar(&clear, "clear", false, "Attempt to clear the TPM before provisioning") + flag.StringVar(&lockoutAuth, "lockout-auth", "", "The current lockout hierarchy authorization value") + flag.BoolVar(&noLockoutAuth, "no-lockout-auth", false, + "Don't perform provisioning actions that require the use of the lockout hierarchy authorization") + flag.StringVar(&ownerAuth, "owner-auth", "", "The current storage hierarchy authorization value") + flag.StringVar(&endorsementAuth, "endorsement-auth", "", "The current endorsement hierarchy authorization value") + flag.BoolVar(&requestClear, "request-clear", false, "Request to clear the TPM via the physical presence interface") +} + +func run() int { + args := flag.Args() + + if requestClear { + if err := secboot.RequestTPMClearUsingPPI(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to request clearing the TPM via the PPI: %v\n", err) + os.Exit(1) + } + fmt.Println("Request to clear the TPM submitted successfully. Now perform a system restart") + return 0 + } + + if clear && noLockoutAuth { + fmt.Fprintf(os.Stderr, "-clear and -no-lockout-auth can't be used at the same time\n") + return 1 + } + + var newLockoutAuth string + if len(args) > 0 { + newLockoutAuth = args[0] + } + + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + var mode secboot.ProvisionMode + switch { + case clear: + mode = secboot.ProvisionModeClear + case noLockoutAuth: + mode = secboot.ProvisionModeWithoutLockout + default: + mode = secboot.ProvisionModeFull + } + + tpm.OwnerHandleContext().SetAuthValue([]byte(ownerAuth)) + tpm.EndorsementHandleContext().SetAuthValue([]byte(endorsementAuth)) + tpm.LockoutHandleContext().SetAuthValue([]byte(lockoutAuth)) + + if err := secboot.ProvisionTPM(tpm, mode, []byte(newLockoutAuth)); err != nil { + switch err { + case secboot.ErrTPMClearRequiresPPI: + fmt.Fprintf(os.Stderr, "Clearing requires the use of the physical presence interface. Re-run with -request-clear\n") + case secboot.ErrTPMLockout: + fmt.Fprintf(os.Stderr, "The lockout hierarchy is in dictionary attack lockout mode. Either wait for the recovery time to expire, "+ + "or request to clear the TPM with -request-clear\n") + default: + fmt.Fprintf(os.Stderr, "Failed to provision the TPM: %v\n", err) + } + return 1 + } + + return 0 +} + +func main() { + flag.Parse() + os.Exit(run()) +} diff --git a/examples/seal-key/main.go b/examples/seal-key/main.go new file mode 100644 index 00000000..7f0ac11c --- /dev/null +++ b/examples/seal-key/main.go @@ -0,0 +1,174 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/canonical/go-tpm2" + "github.com/snapcore/secboot" +) + +type pathList []string + +func (l *pathList) String() string { + var builder bytes.Buffer + for i, path := range *l { + if i > 0 { + builder.WriteString(", ") + } + builder.WriteString(path) + } + return builder.String() +} + +func (l *pathList) Set(value string) error { + *l = append(*l, value) + return nil +} + +var create bool +var clearKeyFile string +var keyFile string +var policyUpdateDataFile string +var pinIndex string +var ownerAuth string +var kernels pathList +var grubs pathList +var shims pathList + +func init() { + flag.BoolVar(&create, "new", false, "Create a new key file using the SealKeyToTPM API") + flag.StringVar(&clearKeyFile, "clear-key-file", "", "Path of the file containing the cleartext key to seal (with -new)") + flag.StringVar(&keyFile, "key-file", "", "Path of the sealed key data file to create (with -new) or to update (without -new)") + flag.StringVar(&policyUpdateDataFile, "policy-update-data-file", "", + "Path of the file containing data required for updating policy, to create (with -new) or to use (without -new)") + flag.StringVar(&pinIndex, "pin-index", "", "Handle to use for the PIN NV index (with -new)") + flag.StringVar(&ownerAuth, "auth", "", "Authorization value for the storage hierarchy (with -new)") + flag.Var(&kernels, "with-kernel", "") + flag.Var(&grubs, "with-grub", "") + flag.Var(&shims, "with-shim", "") +} + +func run() int { + if keyFile == "" { + fmt.Fprintf(os.Stderr, "Missing -key-file\n") + return 1 + } + + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + pcrProfile := secboot.NewPCRProtectionProfile() + if len(shims) == 0 && len(grubs) == 0 && len(kernels) == 0 { + pcrProfile.AddPCRValueFromTPM(tpm2.HashAlgorithmSHA256, 7) + } else { + secureBootParams := &secboot.EFISecureBootPolicyProfileParams{PCRAlgorithm: tpm2.HashAlgorithmSHA256} + for _, shim := range shims { + s := &secboot.EFIImageLoadEvent{Source: secboot.Firmware, Image: secboot.FileEFIImage(shim)} + for _, grub := range grubs { + g := &secboot.EFIImageLoadEvent{Source: secboot.Shim, Image: secboot.FileEFIImage(grub)} + for _, kernel := range kernels { + k := &secboot.EFIImageLoadEvent{Source: secboot.Shim, Image: secboot.FileEFIImage(kernel)} + g.Next = append(g.Next, k) + } + s.Next = append(s.Next, g) + } + secureBootParams.LoadSequences = append(secureBootParams.LoadSequences, s) + } + if err := secboot.AddEFISecureBootPolicyProfile(pcrProfile, secureBootParams); err != nil { + fmt.Fprintf(os.Stderr, "Cannot add EFI secure boot policy profile to PCR profile: %v\n", err) + return 1 + } + } + + if create { + if clearKeyFile == "" { + fmt.Fprintf(os.Stderr, "Missing -master-key-file\n") + return 1 + } + if pinIndex == "" { + fmt.Fprintf(os.Stderr, "Missing -pin-index\n") + return 1 + } + + var in *os.File + if clearKeyFile == "-" { + in = os.Stdin + } else { + f, err := os.Open(clearKeyFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open key file: %v\n", err) + return 1 + } + in = f + defer in.Close() + } + + var pinHandle tpm2.Handle + if h, err := hex.DecodeString(pinIndex); err != nil { + fmt.Fprintf(os.Stderr, "Invalid -pin-index: %v\n", err) + return 1 + } else { + pinHandle = tpm2.Handle(binary.BigEndian.Uint32(h)) + } + + key, err := ioutil.ReadAll(in) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot read key: %v\n", err) + return 1 + } + + createParams := secboot.KeyCreationParams{PCRProfile: pcrProfile, PINHandle: pinHandle} + tpm.OwnerHandleContext().SetAuthValue([]byte(ownerAuth)) + + if err := secboot.SealKeyToTPM(tpm, key, keyFile, policyUpdateDataFile, &createParams); err != nil { + fmt.Fprintf(os.Stderr, "Cannot seal key to TPM: %v\n", err) + return 1 + } + } else { + if policyUpdateDataFile == "" { + fmt.Fprintf(os.Stderr, "Missing -policy-update-data-file\n") + return 1 + } + + if err := secboot.UpdateKeyPCRProtectionPolicy(tpm, keyFile, policyUpdateDataFile, pcrProfile); err != nil { + fmt.Fprintf(os.Stderr, "Cannot update key PCR protection policy: %v\n", err) + return 1 + } + } + + return 0 +} + +func main() { + flag.Parse() + os.Exit(run()) +} diff --git a/examples/unseal-key/main.go b/examples/unseal-key/main.go new file mode 100644 index 00000000..9268b872 --- /dev/null +++ b/examples/unseal-key/main.go @@ -0,0 +1,94 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/snapcore/secboot" +) + +var keyFile string +var outFile string +var pin string + +func init() { + flag.StringVar(&keyFile, "key-file", "", "Path of the sealed key data file") + flag.StringVar(&outFile, "out-file", "", "Path of the file to store the cleartext key in") + flag.StringVar(&pin, "pin", "", "") +} + +func run() int { + if keyFile == "" { + fmt.Fprintf(os.Stderr, "Missing -key-file\n") + return 1 + } + if outFile == "" { + fmt.Fprintf(os.Stderr, "Missing -out-file\n") + return 1 + } + + var out *os.File + if outFile == "-" { + out = os.Stdout + } else { + f, err := os.OpenFile(outFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open output file: %v\n", err) + return 1 + } + out = f + defer out.Close() + } + + tpm, err := secboot.ConnectToDefaultTPM() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot connect to TPM: %v\n", err) + return 1 + } + defer tpm.Close() + + k, err := secboot.ReadSealedKeyObject(keyFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot load sealed key object: %v\n", err) + return 1 + } + + key, err := k.UnsealFromTPM(tpm, pin) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot unseal key: %v\n", err) + return 1 + } + + _, err = out.Write(key) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot write unsealed key: %v\n", err) + return 1 + } + + return 0 +} + +func main() { + flag.Parse() + os.Exit(run()) +}