diff --git a/secboot/keys/export_sb_test.go b/secboot/keys/export_sb_test.go new file mode 100644 index 00000000000..9c8bfdf84cf --- /dev/null +++ b/secboot/keys/export_sb_test.go @@ -0,0 +1,35 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +//go:build !nosecboot + +/* + * Copyright (C) 2024 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 keys + +import ( + "io" + + sb "github.com/snapcore/secboot" +) + +func MockSbNewProtectedKey(f func(rand io.Reader, protectorKey []byte, primaryKey sb.PrimaryKey) (protectedKey *sb.KeyData, primaryKeyOut sb.PrimaryKey, unlockKey sb.DiskUnlockKey, err error)) (restore func()) { + oldSbNewProtectedKey := sbNewProtectedKey + sbNewProtectedKey = f + return func() { + sbNewProtectedKey = oldSbNewProtectedKey + } +} diff --git a/secboot/keys/plainkey.go b/secboot/keys/plainkey.go new file mode 100644 index 00000000000..00082dc9904 --- /dev/null +++ b/secboot/keys/plainkey.go @@ -0,0 +1,85 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +//go:build !nosecboot + +/* + * Copyright (C) 2024 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 keys + +import ( + "crypto/rand" + "io" + "os" + "path/filepath" + + sb "github.com/snapcore/secboot" + sb_plainkey "github.com/snapcore/secboot/plainkey" + + "github.com/snapcore/snapd/osutil" +) + +const ( + protectorKeySize = 32 +) + +var ( + sbNewProtectedKey = sb_plainkey.NewProtectedKey +) + +// ProtectorKey is a key that can be used to protect "plainkey" keys. +type ProtectorKey []byte + +// NewProtectorKey creates a new random ProtectorKey. +func NewProtectorKey() (ProtectorKey, error) { + key := make(ProtectorKey, protectorKeySize) + _, err := rand.Read(key[:]) + return key, err +} + +// SaveToFile saves the ProtectorKey to a file at given path. +func (key ProtectorKey) SaveToFile(path string) error { + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + return osutil.AtomicWriteFile(path, key[:], 0600, 0) +} + +// PlainKey is a wrapper for a secboot KeyData representing a plainkey. +type PlainKey struct { + keyData *sb.KeyData +} + +// CreateProtectedKey creates a protected key for a given ProtectorKey +// and primary key. It returns a the protected key wrapped as a PlainKey +// as well the used primary key and the unlock key. +// If primaryKey is nil, the primary key will be generated. +func (key ProtectorKey) CreateProtectedKey(primaryKey []byte) (*PlainKey, []byte, []byte, error) { + protectedKey, generatedPK, unlockKey, err := sbNewProtectedKey(rand.Reader, key[:], primaryKey) + return &PlainKey{protectedKey}, generatedPK, unlockKey, err +} + +// KeyDataWriter is a the same as KeyDataWriter from +// github.com/canonical/secboot. +type KeyDataWriter interface { + io.Writer + Commit() error +} + +// Write writes a PlainKey to a KeyDataWriter. +func (key *PlainKey) Write(writer KeyDataWriter) error { + return key.keyData.WriteAtomic(writer) +} diff --git a/secboot/keys/plainkey_nosb.go b/secboot/keys/plainkey_nosb.go new file mode 100644 index 00000000000..a86071c9c75 --- /dev/null +++ b/secboot/keys/plainkey_nosb.go @@ -0,0 +1,65 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +//go:build nosecboot + +/* + * Copyright (C) 2024 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 keys + +import ( + "crypto/rand" + "io" + "os" + "path/filepath" + + "github.com/snapcore/snapd/osutil" +) + +const ( + protectorKeySize = 32 +) + +type ProtectorKey []byte + +func NewProtectorKey() (ProtectorKey, error) { + key := make(ProtectorKey, protectorKeySize) + _, err := rand.Read(key[:]) + return key, err +} + +type PlainKey struct { +} + +func (key ProtectorKey) SaveToFile(path string) error { + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + return osutil.AtomicWriteFile(path, key[:], 0600, 0) +} + +func (key ProtectorKey) CreateProtectedKey(primaryKey []byte) (*PlainKey, []byte, []byte, error) { + return &PlainKey{}, primaryKey, []byte{}, nil +} + +type KeyDataWriter interface { + io.Writer + Commit() error +} + +func (key *PlainKey) Write(writer KeyDataWriter) error { + return writer.Commit() +} diff --git a/secboot/keys/plainkey_test.go b/secboot/keys/plainkey_test.go new file mode 100644 index 00000000000..5b353d87b7d --- /dev/null +++ b/secboot/keys/plainkey_test.go @@ -0,0 +1,121 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +//go:build !nosecboot + +/* + * Copyright (C) 2024 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 keys_test + +import ( + "bytes" + "crypto" + "io" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + sb "github.com/snapcore/secboot" + + "github.com/snapcore/snapd/secboot/keys" +) + +type plainkeySuite struct { +} + +var _ = Suite(&plainkeySuite{}) + +func (s *plainkeySuite) SetUpTest(c *C) { +} + +type MyKeyDataWriter struct { + *bytes.Buffer +} + +func NewMyKeyDataWriter() *MyKeyDataWriter { + return &MyKeyDataWriter{ + Buffer: bytes.NewBuffer([]byte{}), + } +} + +func (kdw *MyKeyDataWriter) Commit() error { + return nil +} + +type testCase struct { + nilPrimaryKey bool +} + +func (s *plainkeySuite) testPlainKey(c *C, tc *testCase) { + restore := keys.MockSbNewProtectedKey(func(rand io.Reader, protectorKey []byte, primaryKey sb.PrimaryKey) (protectedKey *sb.KeyData, primaryKeyOut sb.PrimaryKey, unlockKey sb.DiskUnlockKey, err error) { + if tc.nilPrimaryKey { + c.Check(primaryKey, IsNil) + primaryKeyOut = []byte("generated-primary-key") + } else { + c.Check(primaryKey, NotNil) + primaryKeyOut = primaryKey + } + kd, err := sb.NewKeyData(&sb.KeyParams{ + Handle: nil, + Role: "run", + PlatformName: "fakePlatform", + KDFAlg: crypto.SHA256, + }) + c.Assert(err, IsNil) + return kd, primaryKeyOut, []byte("unlock-key"), nil + }) + defer restore() + + protectorKey, err := keys.NewProtectorKey() + c.Assert(err, IsNil) + var primaryKeyIn []byte + if !tc.nilPrimaryKey { + primaryKeyIn = []byte("primary-in") + } + protectedKey, primaryKeyOut, unlockKey, err := protectorKey.CreateProtectedKey(primaryKeyIn) + c.Assert(err, IsNil) + if tc.nilPrimaryKey { + c.Check(primaryKeyOut, DeepEquals, []byte("generated-primary-key")) + } else { + c.Check(primaryKeyOut, DeepEquals, []byte("primary-in")) + } + c.Check(unlockKey, DeepEquals, []byte("unlock-key")) + + kdw := NewMyKeyDataWriter() + protectedKey.Write(kdw) + + c.Check(string(kdw.Bytes()), Equals, `{"generation":2,"platform_name":"fakePlatform","platform_handle":null,"role":"run","kdf_alg":"sha256","encrypted_payload":null}`+"\n") + + root := c.MkDir() + + path := filepath.Join(root, "somedir", "somefile") + err = protectorKey.SaveToFile(path) + c.Assert(err, IsNil) + savedKey, err := os.ReadFile(path) + c.Assert(err, IsNil) + c.Check(savedKey, DeepEquals, []byte(protectorKey)) +} + +func (s *plainkeySuite) TestPlainKey(c *C) { + s.testPlainKey(c, &testCase{}) +} + +func (s *plainkeySuite) TestPlainKeyNilPrimaryKeyIn(c *C) { + s.testPlainKey(c, &testCase{ + nilPrimaryKey: true, + }) +}