Skip to content

Commit

Permalink
secboot/keys: add wrapper secboot plainkey
Browse files Browse the repository at this point in the history
  • Loading branch information
valentindavid authored and pedronis committed Feb 7, 2025
1 parent d79798a commit 4820b88
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
35 changes: 35 additions & 0 deletions secboot/keys/export_sb_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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
}
}
85 changes: 85 additions & 0 deletions secboot/keys/plainkey.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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)
}
65 changes: 65 additions & 0 deletions secboot/keys/plainkey_nosb.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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()
}
121 changes: 121 additions & 0 deletions secboot/keys/plainkey_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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,
})
}

0 comments on commit 4820b88

Please sign in to comment.