From 1b194f986726f9ca5fb3e40bf5efe904632d64a9 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Fri, 11 Feb 2022 09:54:42 +0100 Subject: [PATCH 01/12] vm.sh update to use tpm --- qemu/vm.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qemu/vm.sh b/qemu/vm.sh index 99de0e360..bb48834df 100755 --- a/qemu/vm.sh +++ b/qemu/vm.sh @@ -88,4 +88,7 @@ qemu-system-x86_64 -kernel $image \ -drive file=$vmdir/vdc.qcow2,if=virtio -drive file=$vmdir/vdd.qcow2,if=virtio \ -drive file=$vmdir/vde.qcow2,if=virtio \ -serial null -serial mon:stdio \ + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis,tpmdev=tpm0 \ ${graphics} From 6b18e703c8d71811ca9862270ef2e604b68bfc38 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Thu, 4 Aug 2022 12:06:59 +0200 Subject: [PATCH 02/12] fix path --- qemu/vm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/vm.sh b/qemu/vm.sh index bb48834df..b57a02a48 100755 --- a/qemu/vm.sh +++ b/qemu/vm.sh @@ -88,7 +88,7 @@ qemu-system-x86_64 -kernel $image \ -drive file=$vmdir/vdc.qcow2,if=virtio -drive file=$vmdir/vdd.qcow2,if=virtio \ -drive file=$vmdir/vde.qcow2,if=virtio \ -serial null -serial mon:stdio \ - -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ + -chardev socket,id=chrtpm,path=/tmp/swtpm-sock \ -tpmdev emulator,id=tpm0,chardev=chrtpm \ -device tpm-tis,tpmdev=tpm0 \ ${graphics} From 09a9614cc35eb586b98feed0df653e2b8da6a08f Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Wed, 10 Aug 2022 14:24:53 +0200 Subject: [PATCH 03/12] Abstract access to node idenity key --- pkg/identity/identityd.go | 28 ++++---- pkg/identity/keys.go | 81 +++------------------- pkg/identity/keys_test.go | 61 ---------------- pkg/identity/store/file.go | 92 +++++++++++++++++++++++++ pkg/identity/store/keys_test.go | 77 +++++++++++++++++++++ pkg/identity/store/store.go | 37 ++++++++++ pkg/network/yggdrasil/yygdrasil_test.go | 7 +- 7 files changed, 231 insertions(+), 152 deletions(-) create mode 100644 pkg/identity/store/file.go create mode 100644 pkg/identity/store/keys_test.go create mode 100644 pkg/identity/store/store.go diff --git a/pkg/identity/identityd.go b/pkg/identity/identityd.go index 2905ae6b2..440eac251 100644 --- a/pkg/identity/identityd.go +++ b/pkg/identity/identityd.go @@ -2,11 +2,11 @@ package identity import ( "fmt" - "os" "github.com/rs/zerolog/log" "github.com/threefoldtech/substrate-client" "github.com/threefoldtech/zos/pkg/crypto" + "github.com/threefoldtech/zos/pkg/identity/store" "github.com/pkg/errors" "github.com/threefoldtech/zos/pkg" @@ -26,36 +26,36 @@ type identityManager struct { // The daemon will auto generate a new seed if the path does // not exist func NewManager(path string) (pkg.IdentityManager, error) { - env, err := environment.Get() - if err != nil { - return nil, err - } + st := store.NewFileStore(path) + key, err := st.Get() var pair KeyPair - if seed, err := LoadSeed(path); os.IsNotExist(err) { + if errors.Is(err, store.ErrKeyDoesNotExist) { pair, err = GenerateKeyPair() if err != nil { return nil, errors.Wrap(err, "failed to generate key pair") } - - if err := pair.Save(path); err != nil { + if err := st.Set(pair.PrivateKey); err != nil { return nil, errors.Wrap(err, "failed to persist key seed") } } else if err != nil { - if err := os.Remove(path); err != nil { - log.Error().Err(err).Msg("failed to delete corrupt seed file") + log.Error().Err(err).Msg("failed to load key. to recover the key data will be deleted and regenerated") + if err := st.Annihilate(); err != nil { + log.Error().Err(err).Msg("failed to clean up key store") } return nil, errors.Wrap(err, "failed to load seed") } else { - pair, err = FromSeed(seed) - if err != nil { - return nil, errors.Wrap(err, "invalid seed file") - } + pair = KeyPairFromKey(key) } sub, err := environment.GetSubstrate() if err != nil { return nil, err } + env, err := environment.Get() + if err != nil { + return nil, err + } + return &identityManager{ key: pair, sub: sub, diff --git a/pkg/identity/keys.go b/pkg/identity/keys.go index 6aefb0b56..d5292fdc5 100644 --- a/pkg/identity/keys.go +++ b/pkg/identity/keys.go @@ -1,12 +1,8 @@ package identity import ( - "encoding/json" - "fmt" - "github.com/jbenet/go-base58" "github.com/threefoldtech/zos/pkg/versioned" - "github.com/tyler-smith/go-bip39" "golang.org/x/crypto/ed25519" ) @@ -30,6 +26,13 @@ type KeyPair struct { PublicKey ed25519.PublicKey } +func KeyPairFromKey(sk ed25519.PrivateKey) KeyPair { + return KeyPair{ + PrivateKey: sk, + PublicKey: sk.Public().(ed25519.PublicKey), + } +} + // Identity implements the Identifier interface func (k KeyPair) Identity() string { return base58.Encode(k.PublicKey) @@ -41,6 +44,7 @@ func GenerateKeyPair() (k KeyPair, err error) { if err != nil { return k, err } + k = KeyPair{ PrivateKey: privateKey, PublicKey: publicKey, @@ -54,72 +58,3 @@ func (k *KeyPair) Save(path string) error { return versioned.WriteFile(path, SeedVersion1, seed, 0400) } - -// LoadSeed from path -func LoadSeed(path string) ([]byte, error) { - version, seed, err := versioned.ReadFile(path) - if versioned.IsNotVersioned(err) { - // this is a compatibility code for seed files - // in case it does not have any version information - if err := versioned.WriteFile(path, SeedVersionLatest, seed, 0400); err != nil { - return nil, err - } - version = SeedVersion1 - } else if err != nil { - return nil, err - } - - if version.NE(SeedVersion1) && version.NE(SeedVersion11) { - return nil, fmt.Errorf("unknown seed version") - } - - if version.EQ(SeedVersion1) { - return seed, nil - } - // it means we read json data instead of the secret - type Seed110Struct struct { - Mnemonics string `json:"mnemonic"` - } - var seed110 Seed110Struct - if err = json.Unmarshal(seed, &seed110); err != nil { - return nil, err - } - return bip39.EntropyFromMnemonic(seed110.Mnemonics) -} - -// LoadKeyPair reads a seed from a file located at path and re-create a -// KeyPair using the seed -func LoadKeyPair(path string) (k KeyPair, err error) { - return loadKeyPair(path) -} - -// LoadLegacyKeyPair load keypair without deprecated message for converted -func LoadLegacyKeyPair(path string) (k KeyPair, err error) { - return loadKeyPair(path) -} - -func loadKeyPair(path string) (k KeyPair, err error) { - seed, err := LoadSeed(path) - if err != nil { - return k, err - } - - return FromSeed(seed) -} - -// FromSeed creates a new key pair from seed -func FromSeed(seed []byte) (pair KeyPair, err error) { - if len(seed) != ed25519.SeedSize { - return pair, fmt.Errorf("seed has the wrong size %d and should be %d", len(seed), ed25519.SeedSize) - } - - privateKey := ed25519.NewKeyFromSeed(seed) - publicKey := make([]byte, ed25519.PublicKeySize) - copy(publicKey, privateKey[32:]) - pair = KeyPair{ - PrivateKey: privateKey, - PublicKey: publicKey, - } - - return pair, nil -} diff --git a/pkg/identity/keys_test.go b/pkg/identity/keys_test.go index 86d56b1a3..00bc014c8 100644 --- a/pkg/identity/keys_test.go +++ b/pkg/identity/keys_test.go @@ -1,13 +1,9 @@ package identity import ( - "io/ioutil" "net/url" - "os" "testing" - "encoding/base64" - "golang.org/x/crypto/ed25519" "github.com/stretchr/testify/assert" @@ -21,27 +17,6 @@ func TestGenerateKey(t *testing.T) { assert.NotNil(t, keypair.PublicKey) } -func TestSerialize(t *testing.T) { - keypair, err := GenerateKeyPair() - require.NoError(t, err) - - f, err := ioutil.TempFile("", "") - require.NoError(t, err) - defer func() { - _ = f.Close() - _ = os.Remove(f.Name()) - }() - - err = keypair.Save(f.Name()) - require.NoError(t, err) - - keypair2, err := LoadKeyPair(f.Name()) - require.NoError(t, err) - - assert.Equal(t, keypair.PrivateKey, keypair2.PrivateKey) - assert.Equal(t, keypair.PublicKey, keypair2.PublicKey) -} - func TestIdentity(t *testing.T) { sk := ed25519.NewKeyFromSeed([]byte("helloworldhelloworldhelloworld12")) keypair := KeyPair{ @@ -52,39 +27,3 @@ func TestIdentity(t *testing.T) { assert.Equal(t, "FkUfMueBVSK6V1DCHVAtzzaqPqCPVzGguDzCQxq7Ep85", id) assert.Equal(t, "FkUfMueBVSK6V1DCHVAtzzaqPqCPVzGguDzCQxq7Ep85", url.PathEscape(id), "identity should be url friendly") } - -func TestLoadSeed110(t *testing.T) { - seedfilecontent := `"1.1.0"{"mnemonic":"crop orient animal script safe inquiry neglect tumble maple board degree you intact busy birth west crack cabin lizard embark seed adjust around talk"}` - f, err := ioutil.TempFile("", "") - require.NoError(t, err) - defer func() { - _ = f.Close() - _ = os.Remove(f.Name()) - }() - _, err = f.WriteString(seedfilecontent) - require.NoError(t, err) - s, err := LoadSeed(f.Name()) - require.NoError(t, err) - assert.Equal(t, len(s), ed25519.SeedSize) - -} - -func TestLoadSeed100(t *testing.T) { - - seedfilebase64 := `IjEuMC4wIkiwBpAxl8Xpc4fgQ4Wq3Is5ssEkObXDJANf7KoOw153` // matches `"1.0.0"H°1—Åés‡àC…ªÜ‹9²Á$9µÃ$_ìªÃ^w` - seedfilebytes, err := base64.StdEncoding.DecodeString(seedfilebase64) - require.NoError(t, err) - - f, err := ioutil.TempFile("", "") - require.NoError(t, err) - defer func() { - _ = f.Close() - _ = os.Remove(f.Name()) - }() - _, err = f.Write(seedfilebytes) - require.NoError(t, err) - s, err := LoadSeed(f.Name()) - require.NoError(t, err) - assert.Equal(t, len(s), ed25519.SeedSize) - -} diff --git a/pkg/identity/store/file.go b/pkg/identity/store/file.go new file mode 100644 index 000000000..c10cc9b3a --- /dev/null +++ b/pkg/identity/store/file.go @@ -0,0 +1,92 @@ +package store + +import ( + "crypto/ed25519" + "encoding/json" + "os" + + "github.com/pkg/errors" + "github.com/threefoldtech/zos/pkg/versioned" + "github.com/tyler-smith/go-bip39" +) + +// Version History: +// 1.0.0: seed binary directly encoded +// 1.1.0: json with key mnemonic and threebot id + +var ( + // SeedVersion1 (binary seed) + SeedVersion1 = versioned.MustParse("1.0.0") + // SeedVersion11 (json mnemonic) + SeedVersion11 = versioned.MustParse("1.1.0") + // SeedVersionLatest link to latest seed version + SeedVersionLatest = SeedVersion11 +) + +type FileStore struct { + path string +} + +var _ Store = (*FileStore)(nil) + +func NewFileStore(path string) FileStore { + return FileStore{path} +} + +func (f *FileStore) Set(key ed25519.PrivateKey) error { + seed := key.Seed() + return versioned.WriteFile(f.path, SeedVersion1, seed, 0400) +} + +func (f *FileStore) Annihilate() error { + return os.Remove(f.path) +} + +func (f *FileStore) Get() (ed25519.PrivateKey, error) { + version, data, err := versioned.ReadFile(f.path) + if versioned.IsNotVersioned(err) { + // this is a compatibility code for seed files + // in case it does not have any version information + if err := versioned.WriteFile(f.path, SeedVersionLatest, data, 0400); err != nil { + return nil, err + } + version = SeedVersion1 + } else if os.IsNotExist(err) { + return nil, ErrKeyDoesNotExist + } else if err != nil { + return nil, err + } + + if version.NE(SeedVersion1) && version.NE(SeedVersion11) { + return nil, errors.Wrap(ErrInvalidKey, "unknown seed version") + } + + if version.EQ(SeedVersion1) { + return keyFromSeed(data) + } + // it means we read json data instead of the secret + type Seed110Struct struct { + Mnemonics string `json:"mnemonic"` + } + var seed110 Seed110Struct + if err = json.Unmarshal(data, &seed110); err != nil { + return nil, errors.Wrapf(ErrInvalidKey, "failed to decode seed: %s", err) + } + + seed, err := bip39.EntropyFromMnemonic(seed110.Mnemonics) + if err != nil { + return nil, errors.Wrapf(ErrInvalidKey, "failed to decode mnemonics: %s", err) + } + + return keyFromSeed(seed) +} + +func (f *FileStore) Exists() (bool, error) { + if _, err := os.Stat(f.path); os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, errors.Wrap(err, "failed to check seed file") + } + + return true, nil +} diff --git a/pkg/identity/store/keys_test.go b/pkg/identity/store/keys_test.go new file mode 100644 index 000000000..e455c4cfd --- /dev/null +++ b/pkg/identity/store/keys_test.go @@ -0,0 +1,77 @@ +package store + +import ( + "crypto/rand" + "encoding/base64" + "io/ioutil" + "os" + "testing" + + "golang.org/x/crypto/ed25519" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSerialize(t *testing.T) { + _, sk, err := ed25519.GenerateKey(rand.Reader) + assert.NoError(t, err) + + f, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + + store := NewFileStore(f.Name()) + err = store.Set(sk) + assert.NoError(t, err) + + loaded, err := store.Get() + assert.NoError(t, err) + + assert.Equal(t, sk, loaded) +} + +func TestLoadSeed110(t *testing.T) { + seedfilecontent := `"1.1.0"{"mnemonic":"crop orient animal script safe inquiry neglect tumble maple board degree you intact busy birth west crack cabin lizard embark seed adjust around talk"}` + f, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + _, err = f.WriteString(seedfilecontent) + require.NoError(t, err) + f.Close() + + store := NewFileStore(f.Name()) + sk, err := store.Get() + require.NoError(t, err) + assert.NotNil(t, sk) + assert.Equal(t, len(sk), ed25519.PrivateKeySize) +} + +func TestLoadSeed100(t *testing.T) { + + seedfilebase64 := `IjEuMC4wIkiwBpAxl8Xpc4fgQ4Wq3Is5ssEkObXDJANf7KoOw153` // matches `"1.0.0"H°1—Åés‡àC…ªÜ‹9²Á$9µÃ$_ìªÃ^w` + seedfilebytes, err := base64.StdEncoding.DecodeString(seedfilebase64) + require.NoError(t, err) + + f, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + _, err = f.Write(seedfilebytes) + require.NoError(t, err) + f.Close() + + store := NewFileStore(f.Name()) + sk, err := store.Get() + require.NoError(t, err) + assert.NotNil(t, sk) + assert.Equal(t, len(sk), ed25519.PrivateKeySize) +} diff --git a/pkg/identity/store/store.go b/pkg/identity/store/store.go new file mode 100644 index 000000000..9922d1e49 --- /dev/null +++ b/pkg/identity/store/store.go @@ -0,0 +1,37 @@ +/* +Key store implements different methods of storing the node identity seed on disk +*/ +package store + +import ( + "crypto/ed25519" + "fmt" + + "github.com/pkg/errors" +) + +var ( + ErrKeyDoesNotExist = fmt.Errorf("key does not exist") + ErrInvalidKey = fmt.Errorf("invalid key data") +) + +type Store interface { + // Get returns the key from the store + Get() (ed25519.PrivateKey, error) + // Updates, or overrides the current key + Set(key ed25519.PrivateKey) error + // Check if key there is a key stored in the + // store + Exists() (bool, error) + // Destroys the key + Annihilate() error +} + +// keyFromSeed creates a new key pair from seed +func keyFromSeed(seed []byte) (key ed25519.PrivateKey, err error) { + if len(seed) != ed25519.SeedSize { + return nil, errors.Wrapf(ErrInvalidKey, "seed has the wrong size %d and should be %d", len(seed), ed25519.SeedSize) + } + + return ed25519.NewKeyFromSeed(seed), nil +} diff --git a/pkg/network/yggdrasil/yygdrasil_test.go b/pkg/network/yggdrasil/yygdrasil_test.go index 55beb606b..117767e73 100644 --- a/pkg/network/yggdrasil/yygdrasil_test.go +++ b/pkg/network/yggdrasil/yygdrasil_test.go @@ -1,19 +1,18 @@ package yggdrasil import ( + "crypto/ed25519" "net" "testing" "github.com/stretchr/testify/require" - "github.com/threefoldtech/zos/pkg/identity" "gotest.tools/assert" ) func TestAddresses(t *testing.T) { - kp, err := identity.FromSeed([]byte("00000000000000000000000000000000")) - require.NoError(t, err) + sk := ed25519.NewKeyFromSeed([]byte("00000000000000000000000000000000")) - cfg := GenerateConfig(kp.PrivateKey) + cfg := GenerateConfig(sk) s := NewYggServer(&cfg) ip, err := s.Address() From 78e054681d4879deab6d276d316ecf740789f526 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Thu, 18 Aug 2022 11:30:29 +0200 Subject: [PATCH 04/12] tpm utils --- pkg/identity/store/tpm.go | 28 ++++++++++++ pkg/identity/store/tpm/utils.go | 68 ++++++++++++++++++++++++++++ pkg/identity/store/tpm/utils_test.go | 23 ++++++++++ 3 files changed, 119 insertions(+) create mode 100644 pkg/identity/store/tpm.go create mode 100644 pkg/identity/store/tpm/utils.go create mode 100644 pkg/identity/store/tpm/utils_test.go diff --git a/pkg/identity/store/tpm.go b/pkg/identity/store/tpm.go new file mode 100644 index 000000000..9d92d158e --- /dev/null +++ b/pkg/identity/store/tpm.go @@ -0,0 +1,28 @@ +package store + +import "crypto/ed25519" + +type TPMStore struct{} + +var _ Store = (*TPMStore)(nil) + +// Get returns the key from the store +func (t *TPMStore) Get() (ed25519.PrivateKey, error) { + panic("unimplemented") +} + +// Updates, or overrides the current key +func (t *TPMStore) Set(key ed25519.PrivateKey) error { + panic("unimplemented") +} + +// Check if key there is a key stored in the +// store +func (t *TPMStore) Exists() (bool, error) { + panic("unimplemented") +} + +// Destroys the key +func (t *TPMStore) Annihilate() error { + panic("unimplemented") +} diff --git a/pkg/identity/store/tpm/utils.go b/pkg/identity/store/tpm/utils.go new file mode 100644 index 000000000..72167861a --- /dev/null +++ b/pkg/identity/store/tpm/utils.go @@ -0,0 +1,68 @@ +package tpm + +import ( + "bytes" + "context" + "fmt" + "os/exec" + + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +func tpm(ctx context.Context, name string, out interface{}, arg ...string) error { + name = fmt.Sprintf("tpm2_%s", name) + + cmd := exec.CommandContext(ctx, name, arg...) + + output, err := cmd.Output() + if err, ok := err.(*exec.ExitError); ok && err != nil { + return errors.Wrapf(err, "error while running command: (%s)", string(err.Stderr)) + } else if err != nil { + return errors.Wrap(err, "failed to run tpm") + } + + if out == nil { + return nil + } + + decoder := yaml.NewDecoder(bytes.NewBuffer(output)) + + return decoder.Decode(out) +} + +// IsTPMSupported checks if TPM is accessible on this system +func IsTPMSupported(ctx context.Context) bool { + pcrs, err := PCRs(ctx) + if err != nil { + return false + } + + return len(pcrs) > 0 +} + +// PersistedHandlers return a list of persisted handlers on the system +func PersistedHandlers(ctx context.Context) (handlers []string, err error) { + err = tpm(ctx, "getcap", &handlers, "handles-persistent") + return +} + +// PCRs returns the available PCRs numbers as map of [hash-algorithm][]int +func PCRs(ctx context.Context) (map[string][]int, error) { + var data struct { + Top []map[string][]int `yaml:"selected-pcrs"` + } + + if err := tpm(ctx, "getcap", &data, "pcrs"); err != nil { + return nil, err + } + + pcrs := make(map[string][]int) + for _, m := range data.Top { + for k, l := range m { + pcrs[k] = l + } + } + + return pcrs, nil +} diff --git a/pkg/identity/store/tpm/utils_test.go b/pkg/identity/store/tpm/utils_test.go new file mode 100644 index 000000000..ea4fe57bf --- /dev/null +++ b/pkg/identity/store/tpm/utils_test.go @@ -0,0 +1,23 @@ +package tpm + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPersistedHandlers(t *testing.T) { + t.Skip("manual test needs specific setup") + handlers, err := PersistedHandlers(context.Background()) + assert.NoError(t, err) + fmt.Println(handlers) +} + +func TestPCRs(t *testing.T) { + //t.Skip("manual test needs specific setup") + pcrs, err := PCRs(context.Background()) + assert.NoError(t, err) + fmt.Println(pcrs) +} From 1af3fd6052b714dbbe94e8447b6e95c5bb400a33 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Fri, 19 Aug 2022 10:02:58 +0200 Subject: [PATCH 05/12] wip: policy create --- pkg/identity/store/tpm/utils.go | 63 ++++++++++++++++++++++++++++ pkg/identity/store/tpm/utils_test.go | 28 ++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/pkg/identity/store/tpm/utils.go b/pkg/identity/store/tpm/utils.go index 72167861a..106a4643a 100644 --- a/pkg/identity/store/tpm/utils.go +++ b/pkg/identity/store/tpm/utils.go @@ -3,13 +3,71 @@ package tpm import ( "bytes" "context" + "encoding/hex" "fmt" + "os" "os/exec" + "sort" + "strings" "github.com/pkg/errors" "gopkg.in/yaml.v2" ) +type HexString string + +func (h HexString) Bytes() ([]byte, error) { + return hex.DecodeString(string(h)) +} + +type HashKind string + +const ( + SHA1 HashKind = "sha1" + SHA256 HashKind = "sha256" + SHA384 HashKind = "sha384" + SHA512 HashKind = "sha512" +) + +type PCRSelector map[HashKind][]int + +func (p PCRSelector) String() string { + // to make it consistent we need to + // sort the the map keys first + var keys []HashKind + for hash := range p { + keys = append(keys, hash) + } + + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + var buf strings.Builder + for _, hash := range keys { + if buf.Len() > 0 { + buf.WriteRune('+') + } + buf.WriteString(string(hash)) + buf.WriteRune(':') + for i, id := range p[hash] { + if i != 0 { + buf.WriteRune(',') + } + buf.WriteString(fmt.Sprint(id)) + } + } + + return buf.String() +} + +// File is a tmp file path to make it easier to pass files around +type File string + +func (f File) Delete() error { + return os.Remove(string(f)) +} + func tpm(ctx context.Context, name string, out interface{}, arg ...string) error { name = fmt.Sprintf("tpm2_%s", name) @@ -66,3 +124,8 @@ func PCRs(ctx context.Context) (map[string][]int, error) { return pcrs, nil } + +func PCRPolicy(ctx context.Context, selector PCRSelector) (out HexString, err error) { + err = tpm(ctx, "createpolicy", &out, "--policy-pcr", "-l", selector.String(), "-L", "/dev/null") + return +} diff --git a/pkg/identity/store/tpm/utils_test.go b/pkg/identity/store/tpm/utils_test.go index ea4fe57bf..71ed43a59 100644 --- a/pkg/identity/store/tpm/utils_test.go +++ b/pkg/identity/store/tpm/utils_test.go @@ -16,8 +16,34 @@ func TestPersistedHandlers(t *testing.T) { } func TestPCRs(t *testing.T) { - //t.Skip("manual test needs specific setup") + t.Skip("manual test needs specific setup") pcrs, err := PCRs(context.Background()) assert.NoError(t, err) fmt.Println(pcrs) } + +func TestPCRSelector(t *testing.T) { + selector := PCRSelector{ + SHA1: {1, 2, 3}, + } + + assert.Equal(t, "sha1:1,2,3", selector.String()) + + selector = PCRSelector{ + SHA1: {1, 2, 3}, + SHA256: {3, 4}, + } + + assert.Equal(t, "sha1:1,2,3+sha256:3,4", selector.String()) +} + +func TestPCRPolicy(t *testing.T) { + t.Skip("manual test") + selector := PCRSelector{ + SHA1: {0, 1, 2}, + } + + hash, err := PCRPolicy(context.Background(), selector) + assert.NoError(t, err) + assert.Equal(t, HexString("f3473b1bc51f0c9179f4c6f377b5b7d20bde98793e1a052c7bc3b736b821d1fe"), hash) +} From 32a72bc5f025e756efd83f36d038412ef3fa3c7a Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Mon, 22 Aug 2022 15:50:27 +0200 Subject: [PATCH 06/12] Build all utils for tpm mgmt --- pkg/identity/store/tpm/utils.go | 126 ++++++++++++++++++++++++--- pkg/identity/store/tpm/utils_test.go | 2 +- 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/pkg/identity/store/tpm/utils.go b/pkg/identity/store/tpm/utils.go index 106a4643a..4a6593ca0 100644 --- a/pkg/identity/store/tpm/utils.go +++ b/pkg/identity/store/tpm/utils.go @@ -5,12 +5,16 @@ import ( "context" "encoding/hex" "fmt" + "io" "os" "os/exec" + "path/filepath" "sort" "strings" + "github.com/google/uuid" "github.com/pkg/errors" + "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" ) @@ -20,21 +24,26 @@ func (h HexString) Bytes() ([]byte, error) { return hex.DecodeString(string(h)) } -type HashKind string +type HashAlgorithm string +type KeyAlgorithm string const ( - SHA1 HashKind = "sha1" - SHA256 HashKind = "sha256" - SHA384 HashKind = "sha384" - SHA512 HashKind = "sha512" + SHA1 HashAlgorithm = "sha1" + SHA256 HashAlgorithm = "sha256" + SHA384 HashAlgorithm = "sha384" + SHA512 HashAlgorithm = "sha512" + + RSA KeyAlgorithm = "rsa" ) -type PCRSelector map[HashKind][]int +type Address uint32 + +type PCRSelector map[HashAlgorithm][]int func (p PCRSelector) String() string { // to make it consistent we need to // sort the the map keys first - var keys []HashKind + var keys []HashAlgorithm for hash := range p { keys = append(keys, hash) } @@ -64,15 +73,39 @@ func (p PCRSelector) String() string { // File is a tmp file path to make it easier to pass files around type File string +// Delete file func (f File) Delete() error { return os.Remove(string(f)) } -func tpm(ctx context.Context, name string, out interface{}, arg ...string) error { +// Read file contents +func (f File) Read() ([]byte, error) { + return os.ReadFile(string(f)) +} + +type Object struct { + public File + private File +} + +func (o *Object) Delete() error { + _ = o.private.Delete() + _ = o.public.Delete() + return nil +} + +// Creates a temporary file handler +func NewFile(suffix string) File { + name := fmt.Sprintf("%s%s", uuid.New().String(), suffix) + return File(filepath.Join(os.TempDir(), name)) +} + +func tpm(ctx context.Context, name string, in io.Reader, out interface{}, arg ...string) error { name = fmt.Sprintf("tpm2_%s", name) cmd := exec.CommandContext(ctx, name, arg...) - + log.Debug().Msgf("executing command: %s", cmd.String()) + cmd.Stdin = in output, err := cmd.Output() if err, ok := err.(*exec.ExitError); ok && err != nil { return errors.Wrapf(err, "error while running command: (%s)", string(err.Stderr)) @@ -101,7 +134,7 @@ func IsTPMSupported(ctx context.Context) bool { // PersistedHandlers return a list of persisted handlers on the system func PersistedHandlers(ctx context.Context) (handlers []string, err error) { - err = tpm(ctx, "getcap", &handlers, "handles-persistent") + err = tpm(ctx, "getcap", nil, &handlers, "handles-persistent") return } @@ -111,7 +144,7 @@ func PCRs(ctx context.Context) (map[string][]int, error) { Top []map[string][]int `yaml:"selected-pcrs"` } - if err := tpm(ctx, "getcap", &data, "pcrs"); err != nil { + if err := tpm(ctx, "getcap", nil, &data, "pcrs"); err != nil { return nil, err } @@ -125,7 +158,72 @@ func PCRs(ctx context.Context) (map[string][]int, error) { return pcrs, nil } -func PCRPolicy(ctx context.Context, selector PCRSelector) (out HexString, err error) { - err = tpm(ctx, "createpolicy", &out, "--policy-pcr", "-l", selector.String(), "-L", "/dev/null") - return +// CreatePCRPolicy creates a pcr policy from selection +func CreatePCRPolicy(ctx context.Context, selector PCRSelector) (File, error) { + policyFile := NewFile(".policy") + return policyFile, tpm(ctx, "createpolicy", nil, nil, "--policy-pcr", "-l", selector.String(), "-L", string(policyFile)) +} + +// CreatePrimary key +func CreatePrimary(ctx context.Context, hash HashAlgorithm, key KeyAlgorithm) (File, error) { + //tpm2_createprimary -C e -g sha1 -G rsa -c primary.context + file := NewFile(".primary.context") + return file, tpm(ctx, "createprimary", nil, nil, "-C", "e", "-g", string(hash), "-G", string(key), "-c", string(file)) +} + +// Create creates an object +func Create(ctx context.Context, hash HashAlgorithm, data io.Reader, primary File, policy File) (Object, error) { + //tpm2_create -g sha256 -u obj.pub -r obj.priv -C primary.context -L policy.digest -a "noda|adminwithpolicy|fixedparent|fixedtpm" -i secret.bin + obj := Object{ + private: NewFile(".priv"), + public: NewFile(".pub"), + } + + return obj, tpm(ctx, + "create", + data, nil, + "-g", string(hash), + "-u", string(obj.public), + "-r", string(obj.private), + "-C", string(primary), + "-L", string(policy), + "-a", "noda|adminwithpolicy|fixedparent|fixedtpm", + "-i", "-", + ) +} + +func Load(ctx context.Context, primary File, obj Object) (loaded File, err error) { + // tpm2_load -C primary.context -u obj.pub -r obj.priv -c load.context + file := NewFile(".load.context") + return file, tpm(ctx, + "load", + nil, nil, + "-C", string(primary), + "-u", string(obj.public), + "-r", string(obj.private), + "-c", string(file), + ) +} + +// EvictControl +func EvictControl(ctx context.Context, loaded File, address Address) error { + // tpm2_evictcontrol -C o -c load.context 0x81000000 + return tpm(ctx, "evictcontrol", + nil, nil, + "-C", "o", + "-c", string(loaded), + fmt.Sprintf("0x%x", address), + ) +} + +// Unseal object +func Unseal(ctx context.Context, address Address, pcrs PCRSelector) (File, error) { + // tpm2_unseal -c 0x81000000 -p pcr:sha1:0,1,2 # -o secret.bin + file := NewFile(".raw") + return file, tpm(ctx, "unseal", + nil, nil, + "-c", fmt.Sprintf("0x%x", address), + "-p", fmt.Sprintf("pcr:%s", pcrs), + "-o", string(file), + ) } diff --git a/pkg/identity/store/tpm/utils_test.go b/pkg/identity/store/tpm/utils_test.go index 71ed43a59..42fd95b80 100644 --- a/pkg/identity/store/tpm/utils_test.go +++ b/pkg/identity/store/tpm/utils_test.go @@ -43,7 +43,7 @@ func TestPCRPolicy(t *testing.T) { SHA1: {0, 1, 2}, } - hash, err := PCRPolicy(context.Background(), selector) + hash, err := CreatePCRPolicy(context.Background(), selector) assert.NoError(t, err) assert.Equal(t, HexString("f3473b1bc51f0c9179f4c6f377b5b7d20bde98793e1a052c7bc3b736b821d1fe"), hash) } From 7f09b4fd326daefb6c197e7fd360f5d3c22dde07 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 23 Aug 2022 10:52:03 +0200 Subject: [PATCH 07/12] Build tpm store --- pkg/identity/store/tpm.go | 86 +++++++++++++++++++++++++++++++-- pkg/identity/store/tpm/utils.go | 38 ++++++++++++--- 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/pkg/identity/store/tpm.go b/pkg/identity/store/tpm.go index 9d92d158e..25a7e2410 100644 --- a/pkg/identity/store/tpm.go +++ b/pkg/identity/store/tpm.go @@ -1,6 +1,24 @@ package store -import "crypto/ed25519" +import ( + "bytes" + "context" + "crypto/ed25519" + "fmt" + "time" + + "github.com/threefoldtech/zos/pkg/identity/store/tpm" +) + +const ( + keyAddress = tpm.Address(0x81000000) +) + +var ( + selector = tpm.PCRSelector{ + tpm.SHA1: []int{0, 1, 2}, // what values should we use for PCRs + } +) type TPMStore struct{} @@ -8,21 +26,79 @@ var _ Store = (*TPMStore)(nil) // Get returns the key from the store func (t *TPMStore) Get() (ed25519.PrivateKey, error) { - panic("unimplemented") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + handler, err := tpm.Unseal(ctx, keyAddress, selector) + if err != nil { + return nil, err + } + defer handler.Delete() + return handler.Read() } // Updates, or overrides the current key func (t *TPMStore) Set(key ed25519.PrivateKey) error { - panic("unimplemented") + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + primary, err := tpm.CreatePrimary(ctx, tpm.SHA1, tpm.RSA) + if err != nil { + return fmt.Errorf("failed to create primary key: %w", err) + } + + defer primary.Delete() + + policy, err := tpm.CreatePCRPolicy(ctx, selector) + if err != nil { + return fmt.Errorf("failed to create policy: %w", err) + } + + defer policy.Delete() + + object, err := tpm.Create(ctx, tpm.SHA256, bytes.NewBuffer(key), primary, policy) + if err != nil { + return err + } + + defer object.Delete() + + loaded, err := tpm.Load(ctx, primary, object) + if err != nil { + return fmt.Errorf("failed to load object: %w", err) + } + + defer loaded.Delete() + + if err := tpm.EvictControl(ctx, &loaded, keyAddress); err != nil { + return fmt.Errorf("failed to evict the key: %w", err) + } + + return nil } // Check if key there is a key stored in the // store func (t *TPMStore) Exists() (bool, error) { - panic("unimplemented") + handlers, err := tpm.PersistedHandlers(context.Background()) + if err != nil { + return false, err + } + + for _, handler := range handlers { + if keyAddress == handler { + return true, nil + } + } + + return false, nil } // Destroys the key func (t *TPMStore) Annihilate() error { - panic("unimplemented") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + return tpm.EvictControl(ctx, nil, keyAddress) +} + +func IsTPMEnabled() bool { + return tpm.IsTPMEnabled(context.Background()) } diff --git a/pkg/identity/store/tpm/utils.go b/pkg/identity/store/tpm/utils.go index 4a6593ca0..c39b07c36 100644 --- a/pkg/identity/store/tpm/utils.go +++ b/pkg/identity/store/tpm/utils.go @@ -122,8 +122,8 @@ func tpm(ctx context.Context, name string, in io.Reader, out interface{}, arg .. return decoder.Decode(out) } -// IsTPMSupported checks if TPM is accessible on this system -func IsTPMSupported(ctx context.Context) bool { +// IsTPMEnabled checks if TPM is accessible on this system +func IsTPMEnabled(ctx context.Context) bool { pcrs, err := PCRs(ctx) if err != nil { return false @@ -133,9 +133,24 @@ func IsTPMSupported(ctx context.Context) bool { } // PersistedHandlers return a list of persisted handlers on the system -func PersistedHandlers(ctx context.Context) (handlers []string, err error) { - err = tpm(ctx, "getcap", nil, &handlers, "handles-persistent") - return +func PersistedHandlers(ctx context.Context) (handlers []Address, err error) { + var strHandlers []string + if err := tpm(ctx, "getcap", nil, &strHandlers, "handles-persistent"); err != nil { + return nil, err + } + + var addresses []Address + for _, handler := range strHandlers { + var u Address + _, err := fmt.Sscanf(handler, "0x%x", &u) + if err != nil { + return nil, fmt.Errorf("failed to scan address(%s): %w", handler, err) + } + + addresses = append(addresses, u) + } + + return addresses, nil } // PCRs returns the available PCRs numbers as map of [hash-algorithm][]int @@ -206,12 +221,21 @@ func Load(ctx context.Context, primary File, obj Object) (loaded File, err error } // EvictControl -func EvictControl(ctx context.Context, loaded File, address Address) error { +func EvictControl(ctx context.Context, loaded *File, address Address) error { // tpm2_evictcontrol -C o -c load.context 0x81000000 + if loaded != nil { + // set + return tpm(ctx, "evictcontrol", + nil, nil, + "-C", "o", + "-c", string(*loaded), + fmt.Sprintf("0x%x", address), + ) + } + // evict return tpm(ctx, "evictcontrol", nil, nil, "-C", "o", - "-c", string(loaded), fmt.Sprintf("0x%x", address), ) } From 6114d648914037be87a07c4369639a55af85cd5d Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 23 Aug 2022 13:46:36 +0200 Subject: [PATCH 08/12] migrate from file to tpm store if available also change the vm script to auto-start tpm if required --- cmds/identityd/main.go | 7 ++----- pkg/identity/identityd.go | 8 ++++++-- pkg/identity/store/file.go | 8 ++++++-- pkg/identity/store/store.go | 2 ++ pkg/identity/store/tpm.go | 29 +++++++++++++++++++++++++-- qemu/vm.sh | 39 +++++++++++++++++++++++++++++++------ 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go index 4a0d87e7d..cab6ac6fe 100644 --- a/cmds/identityd/main.go +++ b/cmds/identityd/main.go @@ -6,7 +6,6 @@ import ( "math/rand" "os" "os/signal" - "path/filepath" "syscall" "time" @@ -33,8 +32,7 @@ const ( ) const ( - module = "identityd" - seedName = "seed.txt" + module = "identityd" ) // Safe makes sure function call not interrupted @@ -307,9 +305,8 @@ func upgradeLoop( } func getIdentityMgr(root string) (pkg.IdentityManager, error) { - seedPath := filepath.Join(root, seedName) - manager, err := identity.NewManager(seedPath) + manager, err := identity.NewManager(root) if err != nil { return nil, err } diff --git a/pkg/identity/identityd.go b/pkg/identity/identityd.go index 440eac251..4121934e6 100644 --- a/pkg/identity/identityd.go +++ b/pkg/identity/identityd.go @@ -25,8 +25,12 @@ type identityManager struct { // NewManager creates an identity daemon from seed // The daemon will auto generate a new seed if the path does // not exist -func NewManager(path string) (pkg.IdentityManager, error) { - st := store.NewFileStore(path) +func NewManager(root string) (pkg.IdentityManager, error) { + st, err := NewStore(root) + if err != nil { + return nil, errors.Wrap(err, "failed to create key store") + } + log.Info().Str("kind", st.Kind()).Msg("key store loaded") key, err := st.Get() var pair KeyPair if errors.Is(err, store.ErrKeyDoesNotExist) { diff --git a/pkg/identity/store/file.go b/pkg/identity/store/file.go index c10cc9b3a..8c88f928c 100644 --- a/pkg/identity/store/file.go +++ b/pkg/identity/store/file.go @@ -29,8 +29,12 @@ type FileStore struct { var _ Store = (*FileStore)(nil) -func NewFileStore(path string) FileStore { - return FileStore{path} +func NewFileStore(path string) *FileStore { + return &FileStore{path} +} + +func (f *FileStore) Kind() string { + return "file-store" } func (f *FileStore) Set(key ed25519.PrivateKey) error { diff --git a/pkg/identity/store/store.go b/pkg/identity/store/store.go index 9922d1e49..5e2155a65 100644 --- a/pkg/identity/store/store.go +++ b/pkg/identity/store/store.go @@ -25,6 +25,8 @@ type Store interface { Exists() (bool, error) // Destroys the key Annihilate() error + // Kind returns store kind + Kind() string } // keyFromSeed creates a new key pair from seed diff --git a/pkg/identity/store/tpm.go b/pkg/identity/store/tpm.go index 25a7e2410..00c2e6007 100644 --- a/pkg/identity/store/tpm.go +++ b/pkg/identity/store/tpm.go @@ -24,8 +24,25 @@ type TPMStore struct{} var _ Store = (*TPMStore)(nil) +func NewTPM() *TPMStore { + return &TPMStore{} +} + +func (f *TPMStore) Kind() string { + return "tpm-store" +} + // Get returns the key from the store func (t *TPMStore) Get() (ed25519.PrivateKey, error) { + exists, err := t.Exists() + if err != nil { + return nil, err + } + + if !exists { + return nil, ErrKeyDoesNotExist + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() handler, err := tpm.Unseal(ctx, keyAddress, selector) @@ -33,7 +50,12 @@ func (t *TPMStore) Get() (ed25519.PrivateKey, error) { return nil, err } defer handler.Delete() - return handler.Read() + seed, err := handler.Read() + if err != nil { + return nil, fmt.Errorf("failed to read seed: %w", err) + } + + return keyFromSeed(seed) } // Updates, or overrides the current key @@ -54,7 +76,8 @@ func (t *TPMStore) Set(key ed25519.PrivateKey) error { defer policy.Delete() - object, err := tpm.Create(ctx, tpm.SHA256, bytes.NewBuffer(key), primary, policy) + // we store the key seed, not the seed itself + object, err := tpm.Create(ctx, tpm.SHA256, bytes.NewBuffer(key.Seed()), primary, policy) if err != nil { return err } @@ -68,6 +91,8 @@ func (t *TPMStore) Set(key ed25519.PrivateKey) error { defer loaded.Delete() + _ = tpm.EvictControl(ctx, nil, keyAddress) + if err := tpm.EvictControl(ctx, &loaded, keyAddress); err != nil { return fmt.Errorf("failed to evict the key: %w", err) } diff --git a/qemu/vm.sh b/qemu/vm.sh index b57a02a48..49b32f80d 100755 --- a/qemu/vm.sh +++ b/qemu/vm.sh @@ -9,6 +9,7 @@ bridge=zos0 graphics="-nographic -nodefaults" smp=1 mem=3 +tpm=0 usage() { cat < /dev/null; then exit 1 fi +tpmargs="" +if [[ $tpm -eq "1" ]]; then + if ! command -v swtpm &> /dev/null; then + echo "tpm option require `swtpm` please install first" + exit 1 + fi + pkill swtpm + tpm_dir="$vmdir/tpm" + tpm_socket="$vmdir/swtpm.sock" + mkdir -p $tpm_dir + rm $tpm_socket &> /dev/null || true + runs int he backgroun + swtpm \ + socket --tpm2 \ + --tpmstate dir=$tpm_dir \ + --ctrl type=unixio,path=$vmdir/swtpm.sock \ + --log level=20 &> tpm.logs & + + while [ ! -S "$tpm_socket" ]; do + echo "waiting for tpm" + sleep 1s + done + sleep 1s + tpmargs="-chardev socket,id=chrtpm,path=${tpm_socket} -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0" +fi + echo "boot $image" qemu-system-x86_64 -kernel $image \ @@ -88,7 +116,6 @@ qemu-system-x86_64 -kernel $image \ -drive file=$vmdir/vdc.qcow2,if=virtio -drive file=$vmdir/vdd.qcow2,if=virtio \ -drive file=$vmdir/vde.qcow2,if=virtio \ -serial null -serial mon:stdio \ - -chardev socket,id=chrtpm,path=/tmp/swtpm-sock \ - -tpmdev emulator,id=tpm0,chardev=chrtpm \ - -device tpm-tis,tpmdev=tpm0 \ - ${graphics} + ${graphics} \ + ${tpmargs} \ + ; From a2539c85df3007cb398f92e4748b1cb55dc37a27 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 23 Aug 2022 13:48:46 +0200 Subject: [PATCH 09/12] fix typo --- qemu/vm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/vm.sh b/qemu/vm.sh index 49b32f80d..6044cc9dd 100755 --- a/qemu/vm.sh +++ b/qemu/vm.sh @@ -86,7 +86,7 @@ if [[ $tpm -eq "1" ]]; then tpm_socket="$vmdir/swtpm.sock" mkdir -p $tpm_dir rm $tpm_socket &> /dev/null || true - runs int he backgroun + # runs in the backgroun swtpm \ socket --tpm2 \ --tpmstate dir=$tpm_dir \ From 8f967fe5e90cb1b22053a615d840b59780aaba6b Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 23 Aug 2022 13:54:10 +0200 Subject: [PATCH 10/12] add missing file --- pkg/identity/builder.go | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pkg/identity/builder.go diff --git a/pkg/identity/builder.go b/pkg/identity/builder.go new file mode 100644 index 000000000..d1dbeb076 --- /dev/null +++ b/pkg/identity/builder.go @@ -0,0 +1,62 @@ +package identity + +import ( + "fmt" + "path/filepath" + + "github.com/rs/zerolog/log" + "github.com/threefoldtech/zos/pkg/identity/store" +) + +const ( + seedName = "seed.txt" +) + +// NewStore tries to build the best key store available +// for this ndoe. +// On a machine with no tpm support, that would be a file +// store. +// If TPM is supported, TPM will be used. +// There is a special case if tpm is supported, but a file seed +// exits, this file key will be migrated to the TPM store then +// deleted. +func NewStore(root string) (store.Store, error) { + file := store.NewFileStore(filepath.Join(root, seedName)) + if !store.IsTPMEnabled() { + return file, nil + } + + // tpm is supported, but do we have a key + tpm := store.NewTPM() + exists, err := file.Exists() + if err != nil { + return nil, fmt.Errorf("failed to check for seed file: %s", err) + } + + if !exists { + return tpm, nil + } + + // if we failed to get the key from store + // may be better generate a new one? + // todo: need discussion + + key, err := file.Get() + if err != nil { + return nil, fmt.Errorf("failed to load key from file: %w", err) + } + + // migration of key + if err := tpm.Set(key); err != nil { + // we failed to do migration but we have a valid key. + // we shouldn't then fail instead use the file store + log.Error().Err(err).Msg("failed to migrate key to tpm store") + return file, nil + } + + if err := file.Annihilate(); err != nil { + log.Error().Err(err).Msg("failed to clear up key file") + } + + return tpm, nil +} From d1b088c7cd2aa3581dec5d9dead792f833e69761 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 23 Aug 2022 15:15:15 +0200 Subject: [PATCH 11/12] fix ci --- pkg/identity/builder.go | 7 +++++++ pkg/identity/store/tpm.go | 22 +++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pkg/identity/builder.go b/pkg/identity/builder.go index d1dbeb076..38e07bd23 100644 --- a/pkg/identity/builder.go +++ b/pkg/identity/builder.go @@ -37,6 +37,13 @@ func NewStore(root string) (store.Store, error) { return tpm, nil } + if ok, err := tpm.Exists(); err == nil && ok { + // so there is a key on disk, but tpm already has a stored key + // then we still just return no need for migration to avoid + // overriding the key in tpm + return tpm, nil + } + // if we failed to get the key from store // may be better generate a new one? // todo: need discussion diff --git a/pkg/identity/store/tpm.go b/pkg/identity/store/tpm.go index 00c2e6007..3de93af1f 100644 --- a/pkg/identity/store/tpm.go +++ b/pkg/identity/store/tpm.go @@ -49,7 +49,11 @@ func (t *TPMStore) Get() (ed25519.PrivateKey, error) { if err != nil { return nil, err } - defer handler.Delete() + + defer func() { + _ = handler.Delete() + }() + seed, err := handler.Read() if err != nil { return nil, fmt.Errorf("failed to read seed: %w", err) @@ -67,14 +71,18 @@ func (t *TPMStore) Set(key ed25519.PrivateKey) error { return fmt.Errorf("failed to create primary key: %w", err) } - defer primary.Delete() + defer func() { + _ = primary.Delete() + }() policy, err := tpm.CreatePCRPolicy(ctx, selector) if err != nil { return fmt.Errorf("failed to create policy: %w", err) } - defer policy.Delete() + defer func() { + _ = policy.Delete() + }() // we store the key seed, not the seed itself object, err := tpm.Create(ctx, tpm.SHA256, bytes.NewBuffer(key.Seed()), primary, policy) @@ -82,14 +90,18 @@ func (t *TPMStore) Set(key ed25519.PrivateKey) error { return err } - defer object.Delete() + defer func() { + _ = object.Delete() + }() loaded, err := tpm.Load(ctx, primary, object) if err != nil { return fmt.Errorf("failed to load object: %w", err) } - defer loaded.Delete() + defer func() { + _ = loaded.Delete() + }() _ = tpm.EvictControl(ctx, nil, keyAddress) From 0defcf26a335c7f027bd74f9dd282f19d96659f1 Mon Sep 17 00:00:00 2001 From: Muhamad Azamy Date: Tue, 6 Sep 2022 12:20:40 +0200 Subject: [PATCH 12/12] Make sure identityd resstart when bins are updated This to make sure identityd daemon uses the latest tpm binaries. It simply make sure identityd restarts when binaries are installed before continuing with the update --- cmds/identityd/main.go | 45 +++++++++++++++++++---------- go.sum | 1 + pkg/identity.go | 3 ++ pkg/identity/builder.go | 10 ++++--- pkg/identity/identityd.go | 27 ++++++++++++----- pkg/network/nr/net_resource_test.go | 4 +++ pkg/stubs/identity_stub.go | 16 ++++++++++ 7 files changed, 79 insertions(+), 27 deletions(-) diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go index cab6ac6fe..9f6a916ba 100644 --- a/cmds/identityd/main.go +++ b/cmds/identityd/main.go @@ -101,7 +101,7 @@ func main() { // 2. Register the node to BCDB // at this point we are running latest version - idMgr, err := getIdentityMgr(root) + idMgr, err := getIdentityMgr(root, debug) if err != nil { log.Fatal().Err(err).Msg("failed to create identity manager") } @@ -125,7 +125,12 @@ func main() { log.Fatal().Err(err).Msg("failed to initialize upgrader") } - installBinaries(&boot, upgrader) + err = installBinaries(&boot, upgrader) + if err == upgrade.ErrRestartNeeded { + return + } else if err != nil { + log.Error().Err(err).Msg("failed to install binaries") + } go func() { if err := server.Run(ctx); err != nil && err != context.Canceled { @@ -178,10 +183,8 @@ func debugReinstall(boot *upgrade.Boot, up *upgrade.Upgrader) { }() } -func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) { - +func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) error { bins, _ := boot.CurrentBins() - env, _ := environment.Get() repoWatcher := upgrade.FListRepo{ @@ -191,7 +194,11 @@ func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) { current, toAdd, toDel, err := repoWatcher.Diff() if err != nil { - log.Error().Err(err).Msg("failed to list latest binaries to install") + return errors.Wrap(err, "failed to list latest binaries to install") + } + + if len(toAdd) == 0 && len(toDel) == 0 { + return nil } for _, pkg := range toDel { @@ -206,7 +213,11 @@ func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) { } } - boot.SetBins(current) + if err := boot.SetBins(current); err != nil { + return errors.Wrap(err, "failed to commit pkg status") + } + + return upgrade.ErrRestartNeeded } func upgradeLoop( @@ -263,7 +274,13 @@ func upgradeLoop( continue } - installBinaries(boot, upgrader) + err = installBinaries(boot, upgrader) + if err == upgrade.ErrRestartNeeded { + log.Info().Msg("restarting upgraded") + return + } else if err != nil { + log.Error().Err(err).Msg("failed to update runtime binaries") + } // next check for update exp := backoff.NewExponentialBackOff() @@ -278,12 +295,11 @@ func upgradeLoop( if err == upgrade.ErrRestartNeeded { return backoff.Permanent(err) - } - - if err != nil { + } else if err != nil { log.Error().Err(err).Msg("update failure. retrying") } - return err + + return nil }, exp) if err == upgrade.ErrRestartNeeded { @@ -304,9 +320,8 @@ func upgradeLoop( } } -func getIdentityMgr(root string) (pkg.IdentityManager, error) { - - manager, err := identity.NewManager(root) +func getIdentityMgr(root string, debug bool) (pkg.IdentityManager, error) { + manager, err := identity.NewManager(root, debug) if err != nil { return nil, err } diff --git a/go.sum b/go.sum index 95a1f388e..bdd1e8f4c 100644 --- a/go.sum +++ b/go.sum @@ -341,6 +341,7 @@ github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/jennifer v1.3.0 h1:p3tl41zjjCZTNBytMwrUuiAnherNUZktlhPTKoF/sEk= github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/pkg/identity.go b/pkg/identity.go index 371c8430d..a128614d7 100644 --- a/pkg/identity.go +++ b/pkg/identity.go @@ -20,6 +20,9 @@ func (s StrIdentifier) Identity() string { // IdentityManager interface. type IdentityManager interface { + // Store returns the key store kind + StoreKind() string + // NodeID returns the node id (public key) NodeID() StrIdentifier diff --git a/pkg/identity/builder.go b/pkg/identity/builder.go index 38e07bd23..a04932a1a 100644 --- a/pkg/identity/builder.go +++ b/pkg/identity/builder.go @@ -19,8 +19,8 @@ const ( // If TPM is supported, TPM will be used. // There is a special case if tpm is supported, but a file seed // exits, this file key will be migrated to the TPM store then -// deleted. -func NewStore(root string) (store.Store, error) { +// deleted (only if delete is set to true) +func NewStore(root string, delete bool) (store.Store, error) { file := store.NewFileStore(filepath.Join(root, seedName)) if !store.IsTPMEnabled() { return file, nil @@ -61,8 +61,10 @@ func NewStore(root string) (store.Store, error) { return file, nil } - if err := file.Annihilate(); err != nil { - log.Error().Err(err).Msg("failed to clear up key file") + if delete { + if err := file.Annihilate(); err != nil { + log.Error().Err(err).Msg("failed to clear up key file") + } } return tpm, nil diff --git a/pkg/identity/identityd.go b/pkg/identity/identityd.go index 4121934e6..7ef13d4bd 100644 --- a/pkg/identity/identityd.go +++ b/pkg/identity/identityd.go @@ -14,9 +14,10 @@ import ( ) type identityManager struct { - key KeyPair - sub substrate.Manager - env environment.Environment + kind string + key KeyPair + sub substrate.Manager + env environment.Environment farm string node uint32 @@ -25,8 +26,12 @@ type identityManager struct { // NewManager creates an identity daemon from seed // The daemon will auto generate a new seed if the path does // not exist -func NewManager(root string) (pkg.IdentityManager, error) { - st, err := NewStore(root) +// debug flag is used to change the behavior slightly when zos is running in debug +// mode. Right now only the key store uses this flag. In case of debug migrated keys +// to tpm are not deleted from disks. This allow switching back and forth between tpm +// and non-tpm key stores. +func NewManager(root string, debug bool) (pkg.IdentityManager, error) { + st, err := NewStore(root, !debug) if err != nil { return nil, errors.Wrap(err, "failed to create key store") } @@ -61,12 +66,18 @@ func NewManager(root string) (pkg.IdentityManager, error) { } return &identityManager{ - key: pair, - sub: sub, - env: env, + kind: st.Kind(), + key: pair, + sub: sub, + env: env, }, nil } +// StoreKind returns store kind +func (d *identityManager) StoreKind() string { + return d.kind +} + // NodeID returns the node identity func (d *identityManager) NodeID() pkg.StrIdentifier { return pkg.StrIdentifier(d.key.Identity()) diff --git a/pkg/network/nr/net_resource_test.go b/pkg/network/nr/net_resource_test.go index 9630d4dab..9ab00b9fb 100644 --- a/pkg/network/nr/net_resource_test.go +++ b/pkg/network/nr/net_resource_test.go @@ -19,6 +19,10 @@ type testIdentityManager struct { var _ pkg.IdentityManager = (*testIdentityManager)(nil) +func (t *testIdentityManager) StoreKind() string { + return "test" +} + // NodeID returns the node id (public key) func (t *testIdentityManager) NodeID() pkg.StrIdentifier { return pkg.StrIdentifier(t.id) diff --git a/pkg/stubs/identity_stub.go b/pkg/stubs/identity_stub.go index 10bc76072..42e890ee2 100644 --- a/pkg/stubs/identity_stub.go +++ b/pkg/stubs/identity_stub.go @@ -208,6 +208,22 @@ func (s *IdentityManagerStub) Sign(ctx context.Context, arg0 []uint8) (ret0 []ui return } +func (s *IdentityManagerStub) StoreKind(ctx context.Context) (ret0 string) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "StoreKind", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +} + func (s *IdentityManagerStub) Verify(ctx context.Context, arg0 []uint8, arg1 []uint8) (ret0 error) { args := []interface{}{arg0, arg1} result, err := s.client.RequestContext(ctx, s.module, s.object, "Verify", args...)