Skip to content

Commit

Permalink
Merge pull request #297 from chrisccoulson/add-hooks-platform
Browse files Browse the repository at this point in the history
Add hooks platform.

This adds a KeyData platform that permits sealing and recovery of keys
via hooks provided via a device's gadget and kernel snaps, for platforms
where we don't have a native secboot platform.
  • Loading branch information
chrisccoulson authored Jun 19, 2024
2 parents 7968277 + df6251f commit aafa7ff
Show file tree
Hide file tree
Showing 17 changed files with 2,723 additions and 40 deletions.
25 changes: 15 additions & 10 deletions bootscope/keydata.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"errors"
"fmt"
"hash"
"io"

"github.com/snapcore/secboot"
internal_crypto "github.com/snapcore/secboot/internal/crypto"
Expand Down Expand Up @@ -198,7 +198,7 @@ func (d *KeyDataScope) UnmarshalJSON(data []byte) error {
// payload containing model digests and boot modes (which are now considered as
// authorized for the scope). Initially that payload is empty.
// The produced signature is stored in the scope object.
func NewKeyDataScope(params *KeyDataScopeParams) (*KeyDataScope, error) {
func NewKeyDataScope(rand io.Reader, params *KeyDataScopeParams) (*KeyDataScope, error) {

if params.ModelAlg == 0 {
return nil, errors.New("No model digest algorithm specified")
Expand All @@ -223,7 +223,7 @@ func NewKeyDataScope(params *KeyDataScopeParams) (*KeyDataScope, error) {
}
out.data.PublicKey.PublicKey = signer.Public().(*ecdsa.PublicKey)

if err := out.authorize(params.PrimaryKey, params.Role); err != nil {
if err := out.authorize(rand, params.PrimaryKey, params.Role); err != nil {
return nil, err
}

Expand All @@ -240,7 +240,7 @@ func (d *KeyDataScope) deriveSigner(key secboot.PrimaryKey, role string) (crypto
return internal_crypto.GenerateECDSAKey(elliptic.P256(), r)
}

func (d *KeyDataScope) authorize(key secboot.PrimaryKey, role string) error {
func (d *KeyDataScope) authorize(rand io.Reader, key secboot.PrimaryKey, role string) error {
signer, err := d.deriveSigner(key, role)
if err != nil {
return fmt.Errorf("cannot derive signing key: %w", err)
Expand All @@ -264,7 +264,7 @@ func (d *KeyDataScope) authorize(key secboot.PrimaryKey, role string) error {

h := alg.New()
h.Write(scope)
sig, err := signer.Sign(rand.Reader, h.Sum(nil), alg)
sig, err := signer.Sign(rand, h.Sum(nil), alg)
if err != nil {
return err
}
Expand Down Expand Up @@ -299,7 +299,7 @@ func (d *KeyDataScope) isAuthorized() (bool, error) {
// MDAlg) of a DER encoded payload containing the already authorized boot modes and the
// new models' digest list.
// On error the scope's already authorized model digests remain unchanged.
func (d *KeyDataScope) SetAuthorizedSnapModels(key secboot.PrimaryKey, role string, models ...secboot.SnapModel) (err error) {
func (d *KeyDataScope) SetAuthorizedSnapModels(rand io.Reader, key secboot.PrimaryKey, role string, models ...secboot.SnapModel) (err error) {
alg := d.data.Params.ModelDigests.Alg
if !alg.Available() {
return
Expand All @@ -324,7 +324,7 @@ func (d *KeyDataScope) SetAuthorizedSnapModels(key secboot.PrimaryKey, role stri
d.data.Params.ModelDigests.Digests = currentModelDigests
}()

return d.authorize(key, role)
return d.authorize(rand, key, role)
}

// SetAuthorizedBootModes is used to set new authorized boot modes for existing key data scope.
Expand All @@ -333,7 +333,7 @@ func (d *KeyDataScope) SetAuthorizedSnapModels(key secboot.PrimaryKey, role stri
// used to sign a hash (using scope's MDAlg) of a DER encoded payload containing the already
// authorized model digests and the new boot modes.
// On error the scope's already authorized boot modes remain unchanged.
func (d *KeyDataScope) SetAuthorizedBootModes(key secboot.PrimaryKey, role string, modes ...string) (err error) {
func (d *KeyDataScope) SetAuthorizedBootModes(rand io.Reader, key secboot.PrimaryKey, role string, modes ...string) (err error) {
currentModes := d.data.Params.Modes
d.data.Params.Modes = modes

Expand All @@ -344,11 +344,16 @@ func (d *KeyDataScope) SetAuthorizedBootModes(key secboot.PrimaryKey, role strin
d.data.Params.Modes = currentModes
}()

return d.authorize(key, role)
return d.authorize(rand, key, role)
}

// IsBootEnvironmentAuthorized checks if the current boot environment (model and boot mode) is
// matches the key data's scope authorized models and boot modes.
// compatible with the bound authorized models and boot modes.
//
// This must be called from within an environment where the integrity is protected by
// some other mechanism, such as verified boot, or where the platform device has some way
// of authenticating the current environment, and it must be called before the authenticated
// boot environment parameters are processed and used.
func (d *KeyDataScope) IsBootEnvironmentAuthorized() error {
ok, err := d.isAuthorized()
if err != nil {
Expand Down
48 changes: 24 additions & 24 deletions bootscope/keydata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) {
ModelAlg: modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand Down Expand Up @@ -109,7 +109,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingKDF(c *C) {
ModelAlg: crypto.SHA256,
}

_, err = NewKeyDataScope(params)
_, err = NewKeyDataScope(rand.Reader, params)
c.Assert(err, ErrorMatches, "KDF algorithm unavailable")
}

Expand All @@ -124,7 +124,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingMD(c *C) {
ModelAlg: crypto.SHA256,
}

_, err = NewKeyDataScope(params)
_, err = NewKeyDataScope(rand.Reader, params)
c.Assert(err, ErrorMatches, "MD algorithm unavailable")
}

Expand All @@ -139,7 +139,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) {
MDAlg: crypto.SHA256,
}

_, err = NewKeyDataScope(params)
_, err = NewKeyDataScope(rand.Reader, params)
c.Assert(err, ErrorMatches, "No model digest algorithm specified")
}

Expand All @@ -166,7 +166,7 @@ func (s *keyDataPlatformSuite) testMakeAEADAdditionalData(c *C, data *testMakeAE
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

if data.keyDataScopeVersion != 0 {
Expand Down Expand Up @@ -233,14 +233,14 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthStateErrors(c *C) {
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

authModels := []SnapModel{
s.makeMockModelAssertion(c, "fake-model"),
}

c.Check(kds.SetAuthorizedSnapModels(primaryKey, params.Role, authModels...), IsNil)
c.Check(kds.SetAuthorizedSnapModels(rand.Reader, primaryKey, params.Role, authModels...), IsNil)

err = kds.IsBootEnvironmentAuthorized()
c.Check(err, ErrorMatches, "SetModel hasn't been called yet")
Expand All @@ -251,7 +251,7 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthStateErrors(c *C) {
"modeFoo",
}

c.Check(kds.SetAuthorizedBootModes(primaryKey, params.Role, authModes...), IsNil)
c.Check(kds.SetAuthorizedBootModes(rand.Reader, primaryKey, params.Role, authModes...), IsNil)
err = kds.IsBootEnvironmentAuthorized()
c.Check(err, ErrorMatches, "SetBootMode hasn't been called yet")
}
Expand All @@ -277,10 +277,10 @@ func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAu
ModelAlg: data.modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

err = kds.SetAuthorizedSnapModels(primaryKey, data.role, data.validModels...)
err = kds.SetAuthorizedSnapModels(rand.Reader, primaryKey, data.role, data.validModels...)

if err == nil {
kdsData := kds.Data()
Expand Down Expand Up @@ -351,12 +351,12 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) {
ModelAlg: modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

wrongKey, err := NewPrimaryKey(32)
c.Assert(err, IsNil)
err = kds.SetAuthorizedSnapModels(wrongKey, "different", validModels...)
err = kds.SetAuthorizedSnapModels(rand.Reader, wrongKey, "different", validModels...)
c.Check(err, ErrorMatches, "incorrect key supplied")
}

Expand All @@ -381,10 +381,10 @@ func (s *keyDataPlatformSuite) testSetAuthorizedBootModes(c *C, data *testSetAut
ModelAlg: data.modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

err = kds.SetAuthorizedBootModes(primaryKey, data.role, data.validModes...)
err = kds.SetAuthorizedBootModes(rand.Reader, primaryKey, data.role, data.validModes...)

if err == nil {
kdsData := kds.Data()
Expand Down Expand Up @@ -459,12 +459,12 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) {
ModelAlg: data.modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

wrongKey, err := NewPrimaryKey(32)
c.Assert(err, IsNil)
err = kds.SetAuthorizedBootModes(wrongKey, data.role, data.validModes...)
err = kds.SetAuthorizedBootModes(rand.Reader, wrongKey, data.role, data.validModes...)
c.Check(err, ErrorMatches, "incorrect key supplied")
}

Expand Down Expand Up @@ -492,15 +492,15 @@ func (s *keyDataPlatformSuite) testBootEnvAuth(c *C, data *testBootEnvAuthData)
ModelAlg: data.modelAlg,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)

err = kds.SetAuthorizedSnapModels(primaryKey, data.role, data.validModels...)
err = kds.SetAuthorizedSnapModels(rand.Reader, primaryKey, data.role, data.validModels...)
if err != nil {
return err
}

err = kds.SetAuthorizedBootModes(primaryKey, data.role, data.validModes...)
err = kds.SetAuthorizedBootModes(rand.Reader, primaryKey, data.role, data.validModes...)
if err != nil {
return err
}
Expand Down Expand Up @@ -725,7 +725,7 @@ func (s *keyDataPlatformSuite) TestKeyDataScopeMarshalJSONAndUnmarshalJSON(c *C)
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand Down Expand Up @@ -754,7 +754,7 @@ func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) {
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand Down Expand Up @@ -789,7 +789,7 @@ func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey1(c *C) {
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand All @@ -815,7 +815,7 @@ func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey2(c *C) {
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand All @@ -841,7 +841,7 @@ func (s *keyDataPlatformSuite) TestDeriveSignerDifferentRoleMismatch(c *C) {
ModelAlg: crypto.SHA256,
}

kds, err := NewKeyDataScope(params)
kds, err := NewKeyDataScope(rand.Reader, params)
c.Assert(err, IsNil)
c.Check(kds, NotNil)

Expand Down
9 changes: 5 additions & 4 deletions bootscope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
*
*/

// Package bootscope implements key scoping support for platforms that
// don't support measured boot.
// Package bootscope provides a way to bind keys to certain system properties for
// platforms that don't support measured boot.
//
// It is used to track the currently used boot mode and model, provides
// the KeyDataScope object which encapsulates boot environment information
// and helper functions used to authenticate and associate a scope with a key.
// the KeyDataScope object which encapsulates the binding of boot environment
// information to a key, and helper functions used to authenticate and bind a
// scope with a key.
package bootscope

import (
Expand Down
52 changes: 52 additions & 0 deletions hooks/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* 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 hooks

import "github.com/snapcore/secboot"

type (
AeadCompatData = aeadCompatData
HooksPlatform = hooksPlatform
PrivateKeyData = keyData
)

const (
PlatformName = platformName
)

func (d *KeyData) Data() *keyData {
return &d.data
}

func (d *KeyData) K() *secboot.KeyData {
return d.k
}

func MakeKeyData(d *keyData) *KeyData {
return &KeyData{data: *d}
}

func MockSecbootNewKeyData(fn func(*secboot.KeyParams) (*secboot.KeyData, error)) (restore func()) {
orig := secbootNewKeyData
secbootNewKeyData = fn
return func() {
secbootNewKeyData = orig
}
}
33 changes: 33 additions & 0 deletions hooks/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* 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 hooks_test

import (
"os"
"testing"

. "gopkg.in/check.v1"
)

func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func Test(t *testing.T) { TestingT(t) }
Loading

0 comments on commit aafa7ff

Please sign in to comment.