diff --git a/bootscope/bootscope_test.go b/bootscope/bootscope_test.go
new file mode 100644
index 00000000..563dc19a
--- /dev/null
+++ b/bootscope/bootscope_test.go
@@ -0,0 +1,28 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
diff --git a/bootscope/export_test.go b/bootscope/export_test.go
new file mode 100644
index 00000000..3f8b0224
--- /dev/null
+++ b/bootscope/export_test.go
@@ -0,0 +1,81 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/x509"
+ "sync/atomic"
+
+ "github.com/snapcore/secboot"
+ internal_crypto "github.com/snapcore/secboot/internal/crypto"
+)
+
+var (
+ ComputeSnapModelHash = computeSnapModelHash
+)
+
+func ClearBootModeAndModel() {
+ currentModel = atomic.Value{}
+ currentBootMode = atomic.Value{}
+}
+
+func (d *KeyDataScope) TestSetVersion(version int) {
+ d.data.Version = version
+}
+
+func (d *KeyDataScope) TestMatch(KDFAlg crypto.Hash, keyIdentifier []byte) bool {
+ der, err := x509.MarshalPKIXPublicKey(d.data.PublicKey.PublicKey)
+ if err != nil {
+ return false
+ }
+
+ h := KDFAlg.New()
+ h.Write(der)
+ return bytes.Equal(h.Sum(nil), keyIdentifier)
+}
+
+func (d *KeyDataScope) DeriveSigner(key secboot.PrimaryKey, role string) (crypto.Signer, error) {
+ return d.deriveSigner(key, role)
+}
+
+func NewHashAlg(alg crypto.Hash) hashAlg {
+ return hashAlg(alg)
+}
+
+func NewEcdsaPublicKey(rand []byte) (ecdsaPublicKey, error) {
+ var pk ecdsaPublicKey
+
+ privateKey, err := internal_crypto.GenerateECDSAKey(elliptic.P256(), bytes.NewReader(rand))
+ if err != nil {
+ return pk, err
+ }
+
+ pk.PublicKey = privateKey.Public().(*ecdsa.PublicKey)
+
+ return pk, nil
+}
+
+func (d *KeyDataScope) Data() keyDataScope {
+ return d.data
+}
diff --git a/bootscope/keydata.go b/bootscope/keydata.go
new file mode 100644
index 00000000..d2882b47
--- /dev/null
+++ b/bootscope/keydata.go
@@ -0,0 +1,527 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/x509"
+ "encoding/asn1"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "hash"
+
+ "github.com/snapcore/secboot"
+ internal_crypto "github.com/snapcore/secboot/internal/crypto"
+ "golang.org/x/crypto/cryptobyte"
+ cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
+ "golang.org/x/crypto/hkdf"
+ "golang.org/x/xerrors"
+)
+
+const (
+ nilHash hashAlg = 0
+)
+
+var (
+ sha1Oid = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
+ sha224Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 4}
+ sha256Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
+ sha384Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
+ sha512Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
+)
+
+// hashAlg corresponds to a digest algorithm.
+type hashAlg crypto.Hash
+
+func (a hashAlg) Available() bool {
+ return crypto.Hash(a).Available()
+}
+
+func (a hashAlg) New() hash.Hash {
+ return crypto.Hash(a).New()
+}
+
+func (a hashAlg) HashFunc() crypto.Hash {
+ return crypto.Hash(a)
+}
+
+func (a hashAlg) MarshalJSON() ([]byte, error) {
+ var s string
+
+ switch crypto.Hash(a) {
+ case crypto.SHA1:
+ s = "sha1"
+ case crypto.SHA224:
+ s = "sha224"
+ case crypto.SHA256:
+ s = "sha256"
+ case crypto.SHA384:
+ s = "sha384"
+ case crypto.SHA512:
+ s = "sha512"
+ default:
+ return nil, fmt.Errorf("unknown hash algorithm: %v", crypto.Hash(a))
+ }
+
+ return json.Marshal(s)
+}
+
+func (a *hashAlg) UnmarshalJSON(b []byte) error {
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ switch s {
+ case "sha1":
+ *a = hashAlg(crypto.SHA1)
+ case "sha224":
+ *a = hashAlg(crypto.SHA224)
+ case "sha256":
+ *a = hashAlg(crypto.SHA256)
+ case "sha384":
+ *a = hashAlg(crypto.SHA384)
+ case "sha512":
+ *a = hashAlg(crypto.SHA512)
+ default:
+ // be permissive here and allow everything to be
+ // unmarshalled.
+ *a = nilHash
+ }
+
+ return nil
+}
+
+func (a hashAlg) marshalASN1(b *cryptobyte.Builder) {
+ b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // AlgorithmIdentifier ::= SEQUENCE {
+ var oid asn1.ObjectIdentifier
+
+ switch crypto.Hash(a) {
+ case crypto.SHA1:
+ oid = sha1Oid
+ case crypto.SHA224:
+ oid = sha224Oid
+ case crypto.SHA256:
+ oid = sha256Oid
+ case crypto.SHA384:
+ oid = sha384Oid
+ case crypto.SHA512:
+ oid = sha512Oid
+ default:
+ b.SetError(fmt.Errorf("unknown hash algorithm: %v", crypto.Hash(a)))
+ return
+ }
+ b.AddASN1ObjectIdentifier(oid) // algorithm OBJECT IDENTIFIER
+ b.AddASN1NULL() // parameters ANY DEFINED BY algorithm OPTIONAL
+ })
+}
+
+// digestList corresponds to a list of digests.
+type digestList struct {
+ Alg hashAlg `json:"alg"` // The digest algorithm
+ Digests [][]byte `json:"digests"` // The list of digests
+}
+
+func (l *digestList) marshalASN1WithTag(tag cryptobyte_asn1.Tag, b *cryptobyte.Builder) {
+ b.AddASN1(tag, func(b *cryptobyte.Builder) { // DigestList ::= SEQUENCE {
+ l.Alg.marshalASN1(b) // algorithm AlgorithmIdentifier
+ b.AddASN1(cryptobyte_asn1.SET, func(b *cryptobyte.Builder) { // digests Digests
+ for _, digest := range l.Digests {
+ b.AddASN1OctetString(digest)
+ }
+ })
+ })
+}
+
+type scopeParams struct {
+ ModelDigests digestList `json:"model_digests,omitempty"`
+ Modes []string `json:"modes,omitempty"`
+}
+
+func (s *scopeParams) marshalASN1(b *cryptobyte.Builder) {
+ b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // Scope ::= SEQUENCE {
+ if len(s.ModelDigests.Digests) > 0 {
+ s.ModelDigests.marshalASN1WithTag(cryptobyte_asn1.Tag(0).ContextSpecific().Constructed(), b) // modelDigests [0] IMPLICIT DigestList OPTIONAL
+ }
+ if len(s.Modes) > 0 {
+ b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { // modes [1] IMPLICIT BootModes OPTIONAL
+ for _, mode := range s.Modes {
+ b.AddASN1(cryptobyte_asn1.UTF8String, func(b *cryptobyte.Builder) {
+ b.AddBytes([]byte(mode))
+ })
+ }
+ })
+ }
+ })
+}
+
+type ecdsaPublicKey struct {
+ *ecdsa.PublicKey
+}
+
+func (k ecdsaPublicKey) MarshalJSON() ([]byte, error) {
+ der, err := x509.MarshalPKIXPublicKey(k.PublicKey)
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(der)
+}
+
+func (k *ecdsaPublicKey) UnmarshalJSON(data []byte) error {
+ var der []byte
+ if err := json.Unmarshal(data, &der); err != nil {
+ return err
+ }
+ pubKey, err := x509.ParsePKIXPublicKey(der)
+ if err != nil {
+ return err
+ }
+ ecdsaKey, isECDSA := pubKey.(*ecdsa.PublicKey)
+ if !isECDSA {
+ return errors.New("invalid key type")
+ }
+ k.PublicKey = ecdsaKey
+ return nil
+}
+
+type keyDataScope struct {
+ Version int `json:"version"`
+
+ Params scopeParams `json:"params"`
+ Signature []byte `json:"signature"`
+ PublicKey ecdsaPublicKey `json:"pubkey"`
+
+ KDFAlg hashAlg `json:"kdf_alg"`
+ MDAlg hashAlg `json:"md_alg"`
+}
+
+type additionalData struct {
+ // Version corresponds to the version field of the keyDataScope object
+ Version int
+ // Generation corresponds to the generation field of the keyData object
+ Generation int
+ KdfAlg hashAlg
+ AuthMode secboot.AuthMode
+ KeyIdentifierAlg hashAlg
+ KeyIdentifier []byte
+}
+
+func (d *additionalData) marshalASN1(b *cryptobyte.Builder) {
+ b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // SEQUENCE {
+ b.AddASN1Int64(int64(d.Version)) // version INTEGER
+ b.AddASN1Int64(int64(d.Generation)) // generation INTEGER
+ d.KdfAlg.marshalASN1(b) // kdfAlg AlgorithmIdentifier
+ b.AddASN1Enum(int64(d.AuthMode)) // authMode ENUMERATED
+ d.KeyIdentifierAlg.marshalASN1(b) // keyIdentifierAlg AlgorithmIdentifier
+ b.AddASN1OctetString(d.KeyIdentifier) // keyIdentifier OCTET STRING
+ })
+}
+
+// KeyDataScopeParams defines the parameters for the creation of a
+// key data scope object.
+type KeyDataScopeParams struct {
+ PrimaryKey secboot.PrimaryKey
+ Role string
+
+ // KDFAlg specifies the algorithm used to derive the role unique
+ // signing key from the primary key.
+ KDFAlg crypto.Hash
+
+ // MDAlg specifies the algorithm used to compute the digest of the scope
+ // object (which includes model digests and the boot modes). This is signed
+ // with the role unique signing key to produce the scope signature.
+ MDAlg crypto.Hash
+
+ // ModelAlg specifies the algorithm used to compute the model digests.
+ ModelAlg crypto.Hash
+}
+
+// KeyDataScope represents a key data's scope object which encapsulates information
+// about the scope of the key such as valid models or boot modes.
+type KeyDataScope struct {
+ data keyDataScope
+}
+
+func (d KeyDataScope) MarshalJSON() ([]byte, error) {
+ return json.Marshal(d.data)
+}
+
+func (d *KeyDataScope) UnmarshalJSON(data []byte) error {
+ var kds keyDataScope
+ if err := json.Unmarshal(data, &kds); err != nil {
+ return err
+ }
+ d.data = kds
+ return nil
+}
+
+// NewKeyDataScope creates a new scope object from the given parameters.
+//
+// The PrimaryKey and the role parameters are used to derive a role unique
+// signing key which is used to sign a hash (using MDAlg) of a DER encoded
+// 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) {
+
+ if params.ModelAlg == 0 {
+ return nil, errors.New("No model digest algorithm specified")
+ }
+
+ out := &KeyDataScope{
+ data: keyDataScope{
+ Version: 1,
+ KDFAlg: hashAlg(params.KDFAlg),
+ MDAlg: hashAlg(params.MDAlg),
+ Params: scopeParams{
+ ModelDigests: digestList{
+ Alg: hashAlg(params.ModelAlg),
+ },
+ },
+ },
+ }
+
+ signer, err := out.deriveSigner(params.PrimaryKey, params.Role)
+ if err != nil {
+ return nil, err
+ }
+ out.data.PublicKey.PublicKey = signer.Public().(*ecdsa.PublicKey)
+
+ if err := out.authorize(params.PrimaryKey, params.Role); err != nil {
+ return nil, err
+ }
+
+ return out, nil
+}
+
+func (d *KeyDataScope) deriveSigner(key secboot.PrimaryKey, role string) (crypto.Signer, error) {
+ alg := d.data.KDFAlg
+ if !alg.Available() {
+ return nil, errors.New("KDF algorithm unavailable")
+ }
+
+ r := hkdf.New(func() hash.Hash { return alg.New() }, key, []byte(role), []byte("SCOPE-AUTH"))
+ return internal_crypto.GenerateECDSAKey(elliptic.P256(), r)
+}
+
+func (d *KeyDataScope) authorize(key secboot.PrimaryKey, role string) error {
+ signer, err := d.deriveSigner(key, role)
+ if err != nil {
+ return fmt.Errorf("cannot derive signing key: %w", err)
+ }
+
+ if !signer.Public().(interface{ Equal(crypto.PublicKey) bool }).Equal(d.data.PublicKey.PublicKey) {
+ return errors.New("incorrect key supplied")
+ }
+
+ builder := cryptobyte.NewBuilder(nil)
+ d.data.Params.marshalASN1(builder)
+ scope, err := builder.Bytes()
+ if err != nil {
+ return xerrors.Errorf("cannot serialize scope: %w", err)
+ }
+
+ alg := d.data.MDAlg
+ if !alg.Available() {
+ return errors.New("MD algorithm unavailable")
+ }
+
+ h := alg.New()
+ h.Write(scope)
+ sig, err := signer.Sign(rand.Reader, h.Sum(nil), alg)
+ if err != nil {
+ return err
+ }
+ d.data.Signature = sig
+
+ return nil
+}
+
+func (d *KeyDataScope) isAuthorized() (bool, error) {
+ builder := cryptobyte.NewBuilder(nil)
+ d.data.Params.marshalASN1(builder)
+ scope, err := builder.Bytes()
+ if err != nil {
+ return false, fmt.Errorf("cannot serialize scope: %w", err)
+ }
+
+ alg := d.data.MDAlg
+ if !alg.Available() {
+ return false, errors.New("MD algorithm unavailable")
+ }
+
+ h := alg.New()
+ h.Write(scope)
+ return ecdsa.VerifyASN1(d.data.PublicKey.PublicKey, h.Sum(nil), d.data.Signature), nil
+}
+
+// SetAuthorizedSnapModels is used to set new authorized models for an existing key data scope.
+//
+// Each supplied model is DER encoded and a digest is produced (using a model digest
+// algorithm that can be specific per digest list). The PrimaryKey and the role parameters
+// are used to derive a role unique signing key which is used to sign a hash (using scope's
+// 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) {
+ alg := d.data.Params.ModelDigests.Alg
+ if !alg.Available() {
+ return
+ }
+
+ var modelDigests [][]byte
+ for _, model := range models {
+ digest, err := computeSnapModelHash(crypto.Hash(alg), model)
+ if err != nil {
+ return fmt.Errorf("cannot compute snap model digest: %w", err)
+ }
+ modelDigests = append(modelDigests, digest)
+ }
+
+ currentModelDigests := d.data.Params.ModelDigests.Digests
+ d.data.Params.ModelDigests.Digests = modelDigests
+
+ defer func() {
+ if err == nil {
+ return
+ }
+ d.data.Params.ModelDigests.Digests = currentModelDigests
+ }()
+
+ return d.authorize(key, role)
+}
+
+// SetAuthorizedBootModes is used to set new authorized boot modes for existing key data scope.
+//
+// The PrimaryKey and the role parameters are used to derive a role unique signing key which is
+// 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) {
+ currentModes := d.data.Params.Modes
+ d.data.Params.Modes = modes
+
+ defer func() {
+ if err == nil {
+ return
+ }
+ d.data.Params.Modes = currentModes
+ }()
+
+ return d.authorize(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.
+func (d *KeyDataScope) IsBootEnvironmentAuthorized() error {
+ ok, err := d.isAuthorized()
+ if err != nil {
+ return fmt.Errorf("cannot verify signature: %w", err)
+ }
+ if !ok {
+ return errors.New("invalid signature")
+ }
+
+ alg := d.data.Params.ModelDigests.Alg
+ if !alg.Available() {
+ return errors.New("model digest algorithm unavailable")
+ }
+
+ if len(d.data.Params.ModelDigests.Digests) > 0 {
+ model, ok := currentModel.Load().(secboot.SnapModel)
+ if !ok {
+ return errors.New("SetModel hasn't been called yet")
+ }
+
+ currentModelDigest, err := computeSnapModelHash(crypto.Hash(alg), model)
+ if err != nil {
+ return fmt.Errorf("cannot compute snap model digest: %w", err)
+ }
+
+ found := false
+ for _, modelDigest := range d.data.Params.ModelDigests.Digests {
+ if bytes.Equal(modelDigest, currentModelDigest) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return errors.New("unauthorized model")
+ }
+ }
+
+ if len(d.data.Params.Modes) > 0 {
+ mode, ok := currentBootMode.Load().(string)
+ if !ok {
+ return errors.New("SetBootMode hasn't been called yet")
+ }
+
+ found := false
+ for _, m := range d.data.Params.Modes {
+ if m == mode {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return errors.New("unauthorized boot mode")
+ }
+ }
+
+ return nil
+}
+
+// MakeAEADAdditionalData constructs the additional data that need to be integrity protected for
+// a key data scope. For example a platform using AES-GCM can use it to ensure that the authentication
+// mode of a key data object is immutable and tampering of this can be detected by the early boot
+// environment.
+func (d *KeyDataScope) MakeAEADAdditionalData(generation int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) {
+ alg := d.data.MDAlg
+ if !alg.Available() {
+ return nil, errors.New("MD algorithm unavailable")
+ }
+
+ der, err := x509.MarshalPKIXPublicKey(d.data.PublicKey.PublicKey)
+ if err != nil {
+ return nil, xerrors.Errorf("cannot marshal public key: %w", err)
+ }
+
+ h := alg.New()
+ h.Write(der)
+
+ aad := &additionalData{
+ Version: d.data.Version,
+ Generation: generation,
+ KdfAlg: hashAlg(kdfAlg),
+ AuthMode: authMode,
+ KeyIdentifierAlg: alg,
+ KeyIdentifier: h.Sum(nil),
+ }
+
+ builder := cryptobyte.NewBuilder(nil)
+ aad.marshalASN1(builder)
+
+ return builder.Bytes()
+}
diff --git a/bootscope/keydata_test.go b/bootscope/keydata_test.go
new file mode 100644
index 00000000..453b6784
--- /dev/null
+++ b/bootscope/keydata_test.go
@@ -0,0 +1,1040 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope_test
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/ecdsa"
+ "crypto/hmac"
+ "crypto/rand"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "hash"
+
+ "golang.org/x/xerrors"
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/secboot"
+ . "github.com/snapcore/secboot"
+ . "github.com/snapcore/secboot/bootscope"
+ "github.com/snapcore/secboot/internal/testutil"
+ snapd_testutil "github.com/snapcore/snapd/testutil"
+)
+
+type keyDataPlatformSuite struct {
+ snapd_testutil.BaseTest
+}
+
+func (s *keyDataPlatformSuite) SetUpTest(c *C) {
+ ClearBootModeAndModel()
+}
+
+var _ = Suite(&keyDataPlatformSuite{})
+
+func NewPrimaryKey(sz1 int) (secboot.PrimaryKey, error) {
+ primaryKey := make(secboot.PrimaryKey, sz1)
+ _, err := rand.Read(primaryKey)
+ if err != nil {
+ return nil, err
+ }
+
+ return primaryKey, nil
+}
+
+func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ role := "test"
+ kdfAlg := crypto.SHA256
+ mdAlg := crypto.SHA256
+ modelAlg := crypto.SHA256
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: kdfAlg,
+ MDAlg: mdAlg,
+ ModelAlg: modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ c.Check(kds.IsBootEnvironmentAuthorized(), IsNil)
+
+ data := kds.Data()
+
+ c.Check(data.Version, Equals, 1)
+ c.Check(crypto.Hash(data.Params.ModelDigests.Alg), Equals, modelAlg)
+ c.Check(crypto.Hash(data.KDFAlg), Equals, kdfAlg)
+ c.Check(crypto.Hash(data.MDAlg), Equals, mdAlg)
+
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+ c.Check(data.PublicKey.PublicKey, DeepEquals, signer.Public().(*ecdsa.PublicKey))
+}
+
+func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingKDF(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: "test",
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ _, err = NewKeyDataScope(params)
+ c.Assert(err, ErrorMatches, "KDF algorithm unavailable")
+}
+
+func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingMD(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: "test",
+ KDFAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ _, err = NewKeyDataScope(params)
+ c.Assert(err, ErrorMatches, "MD algorithm unavailable")
+}
+
+func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: "test",
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ }
+
+ _, err = NewKeyDataScope(params)
+ c.Assert(err, ErrorMatches, "No model digest algorithm specified")
+}
+
+type testMakeAEADAdditionalDataData struct {
+ primaryKey PrimaryKey
+ keyDataScopeVersion int
+ generation int
+ authMode AuthMode
+ mdAlg crypto.Hash
+ keyDigestHashAlg crypto.Hash
+ // These are used to derive the signing key whose digest go
+ // into the additional data.
+ signingKeyDerivationAlg crypto.Hash
+ role string
+ expectedAad []byte
+}
+
+func (s *keyDataPlatformSuite) testMakeAEADAdditionalData(c *C, data *testMakeAEADAdditionalDataData) {
+ params := &KeyDataScopeParams{
+ PrimaryKey: data.primaryKey,
+ Role: data.role,
+ KDFAlg: data.signingKeyDerivationAlg,
+ MDAlg: data.mdAlg,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ if data.keyDataScopeVersion != 0 {
+ kds.TestSetVersion(data.keyDataScopeVersion)
+ }
+
+ aadBytes, err := kds.MakeAEADAdditionalData(data.generation, data.keyDigestHashAlg, data.authMode)
+ c.Check(err, IsNil)
+
+ c.Check(aadBytes, DeepEquals, data.expectedAad)
+}
+
+func (s *keyDataPlatformSuite) TestMakeAEADAdditionalData(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "ab40b798dd6b47ca77d93241f40036d6d86e03f365b4ef9171b23e2bc38b9ef3")
+ expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0100300d06096086480165030402010500042077511e42d7c0b2df1881189bd4720806fc92a6dee76cd1c9fe40c32310f6068d")
+
+ s.testMakeAEADAdditionalData(c, &testMakeAEADAdditionalDataData{
+ primaryKey: primaryKey,
+ generation: 1,
+ authMode: AuthModeNone,
+ mdAlg: crypto.SHA256,
+ keyDigestHashAlg: crypto.SHA256,
+ signingKeyDerivationAlg: crypto.SHA256,
+ role: "foo",
+ expectedAad: expectedAad,
+ })
+}
+
+func (s *keyDataPlatformSuite) TestMakeAEADAdditionalDataWithPassphrase(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "45db13f9857336d338c12a5e71aae5434032c3419b9e4e82c2de42cf510d93ee")
+ expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0101300d060960864801650304020105000420765f9750024ce485a32d50c6595fa16fca71b4ea110a2e8361d070a975ba9bcc")
+
+ s.testMakeAEADAdditionalData(c, &testMakeAEADAdditionalDataData{
+ primaryKey: primaryKey,
+ generation: 1,
+ authMode: AuthModePassphrase,
+ mdAlg: crypto.SHA256,
+ keyDigestHashAlg: crypto.SHA256,
+ signingKeyDerivationAlg: crypto.SHA256,
+ role: "foo",
+ expectedAad: expectedAad,
+ })
+}
+
+func (s *keyDataPlatformSuite) makeMockModelAssertion(c *C, modelName string) SnapModel {
+ return testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": modelName,
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")
+}
+
+func (s *keyDataPlatformSuite) TestBootEnvAuthStateErrors(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: "test",
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ authModels := []SnapModel{
+ s.makeMockModelAssertion(c, "fake-model"),
+ }
+
+ c.Check(kds.SetAuthorizedSnapModels(primaryKey, params.Role, authModels...), IsNil)
+
+ err = kds.IsBootEnvironmentAuthorized()
+ c.Check(err, ErrorMatches, "SetModel hasn't been called yet")
+
+ SetModel(authModels[0])
+
+ authModes := []string{
+ "modeFoo",
+ }
+
+ c.Check(kds.SetAuthorizedBootModes(primaryKey, params.Role, authModes...), IsNil)
+ err = kds.IsBootEnvironmentAuthorized()
+ c.Check(err, ErrorMatches, "SetBootMode hasn't been called yet")
+}
+
+type testSetAuthorizedSnapModelsData struct {
+ kdfAlg crypto.Hash
+ mdAlg crypto.Hash
+ modelAlg crypto.Hash
+ validRole string
+ role string
+ validModels []SnapModel
+}
+
+func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAuthorizedSnapModelsData) error {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: data.validRole,
+ KDFAlg: data.kdfAlg,
+ MDAlg: data.mdAlg,
+ ModelAlg: data.modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ err = kds.SetAuthorizedSnapModels(primaryKey, data.role, data.validModels...)
+
+ if err == nil {
+ kdsData := kds.Data()
+
+ c.Check(kdsData.Version, Equals, 1)
+ c.Check(crypto.Hash(kdsData.Params.ModelDigests.Alg), Equals, data.modelAlg)
+ c.Check(crypto.Hash(kdsData.KDFAlg), Equals, data.kdfAlg)
+ c.Check(crypto.Hash(kdsData.MDAlg), Equals, data.mdAlg)
+
+ signer, err := kds.DeriveSigner(primaryKey, data.validRole)
+ c.Assert(err, IsNil)
+ c.Check(kdsData.PublicKey.PublicKey, DeepEquals, signer.Public().(*ecdsa.PublicKey))
+ }
+
+ return err
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModels(c *C) {
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+ c.Check(
+ s.testSetAuthorizedSnapModels(c, &testSetAuthorizedSnapModelsData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "test",
+ validModels: validModels,
+ }), IsNil)
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsInvalidRole(c *C) {
+ // test authorization error when SetAuthorizedSnapModels is called with
+ // a role different than the one set in its keyDataScope.
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+ c.Check(
+ s.testSetAuthorizedSnapModels(c, &testSetAuthorizedSnapModelsData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "different",
+ validModels: validModels,
+ }), ErrorMatches, "incorrect key supplied")
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+
+ validRole := "test"
+ kdfAlg := crypto.SHA256
+ mdAlg := crypto.SHA256
+ modelAlg := crypto.SHA256
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: validRole,
+ KDFAlg: kdfAlg,
+ MDAlg: mdAlg,
+ ModelAlg: modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ wrongKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+ err = kds.SetAuthorizedSnapModels(wrongKey, "different", validModels...)
+ c.Check(err, ErrorMatches, "incorrect key supplied")
+}
+
+type testSetAuthorizedBootModesData struct {
+ kdfAlg crypto.Hash
+ mdAlg crypto.Hash
+ modelAlg crypto.Hash
+ validRole string
+ role string
+ validModes []string
+}
+
+func (s *keyDataPlatformSuite) testSetAuthorizedBootModes(c *C, data *testSetAuthorizedBootModesData) error {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: data.validRole,
+ KDFAlg: data.kdfAlg,
+ MDAlg: data.mdAlg,
+ ModelAlg: data.modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ err = kds.SetAuthorizedBootModes(primaryKey, data.role, data.validModes...)
+
+ if err == nil {
+ kdsData := kds.Data()
+
+ c.Check(kdsData.Version, Equals, 1)
+ c.Check(crypto.Hash(kdsData.Params.ModelDigests.Alg), Equals, data.modelAlg)
+ c.Check(crypto.Hash(kdsData.KDFAlg), Equals, data.kdfAlg)
+ c.Check(crypto.Hash(kdsData.MDAlg), Equals, data.mdAlg)
+
+ signer, err := kds.DeriveSigner(primaryKey, data.validRole)
+ c.Assert(err, IsNil)
+ c.Check(kdsData.PublicKey.PublicKey, DeepEquals, signer.Public().(*ecdsa.PublicKey))
+ }
+
+ return err
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedBootModes(c *C) {
+ validModes := []string{
+ "modeFoo",
+ }
+ c.Check(
+ s.testSetAuthorizedBootModes(c, &testSetAuthorizedBootModesData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "test",
+ validModes: validModes,
+ }), IsNil)
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesInvalidRole(c *C) {
+ // test authorization error when SetAuthorizedBootModes is called with
+ // a role different than the one set in its keyDataScope.
+ validModes := []string{
+ "modeFoo",
+ }
+
+ c.Check(
+ s.testSetAuthorizedBootModes(c, &testSetAuthorizedBootModesData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "different",
+ validModes: validModes,
+ }), ErrorMatches, "incorrect key supplied")
+}
+
+func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ validModes := []string{
+ "modeFoo",
+ }
+ data := &testSetAuthorizedBootModesData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "different",
+ validModes: validModes,
+ }
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: data.validRole,
+ KDFAlg: data.kdfAlg,
+ MDAlg: data.mdAlg,
+ ModelAlg: data.modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ wrongKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+ err = kds.SetAuthorizedBootModes(wrongKey, data.role, data.validModes...)
+ c.Check(err, ErrorMatches, "incorrect key supplied")
+}
+
+type testBootEnvAuthData struct {
+ kdfAlg crypto.Hash
+ mdAlg crypto.Hash
+ modelAlg crypto.Hash
+ validRole string
+ role string
+ validModels []SnapModel
+ model SnapModel
+ validModes []string
+ bootMode string
+}
+
+func (s *keyDataPlatformSuite) testBootEnvAuth(c *C, data *testBootEnvAuthData) error {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: data.validRole,
+ KDFAlg: data.kdfAlg,
+ MDAlg: data.mdAlg,
+ ModelAlg: data.modelAlg,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+
+ err = kds.SetAuthorizedSnapModels(primaryKey, data.role, data.validModels...)
+ if err != nil {
+ return err
+ }
+
+ err = kds.SetAuthorizedBootModes(primaryKey, data.role, data.validModes...)
+ if err != nil {
+ return err
+ }
+
+ SetModel(data.model)
+ SetBootMode(data.bootMode)
+
+ return kds.IsBootEnvironmentAuthorized()
+}
+
+func (s *keyDataPlatformSuite) TestBootEnvAuthValid1(c *C) {
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+
+ validModes := []string{
+ "modeFoo",
+ }
+
+ c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "test",
+ validModels: validModels,
+ model: validModels[0],
+ validModes: validModes,
+ bootMode: validModes[0],
+ }), IsNil)
+}
+
+func (s *keyDataPlatformSuite) TestBootEnvAuthValid2(c *C) {
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ s.makeMockModelAssertion(c, "model-b"),
+ }
+
+ validModes := []string{
+ "modeFoo",
+ }
+
+ c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "test",
+ validModels: validModels,
+ model: validModels[1],
+ validModes: validModes,
+ bootMode: validModes[0],
+ }), IsNil)
+}
+
+func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidModel(c *C) {
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+
+ invalidModel := s.makeMockModelAssertion(c, "model-b")
+
+ validModes := []string{
+ "modeFoo",
+ }
+
+ c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ role: "test",
+ validRole: "test",
+ validModels: validModels,
+ model: invalidModel,
+ validModes: validModes,
+ bootMode: validModes[0],
+ }), ErrorMatches, "unauthorized model")
+}
+
+func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidBootMode(c *C) {
+ validModels := []SnapModel{
+ s.makeMockModelAssertion(c, "model-a"),
+ }
+
+ validModes := []string{
+ "modeFoo",
+ }
+
+ invalidBootMode := "modeBar"
+
+ c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{
+ kdfAlg: crypto.SHA256,
+ mdAlg: crypto.SHA256,
+ modelAlg: crypto.SHA256,
+ validRole: "test",
+ role: "test",
+ validModels: validModels,
+ model: validModels[0],
+ validModes: validModes,
+ bootMode: invalidBootMode,
+ }), ErrorMatches, "unauthorized boot mode")
+}
+
+func (s *keyDataPlatformSuite) TestHashAlgMarshalJSON(c *C) {
+ for _, t := range []struct {
+ alg crypto.Hash
+ nameAlg string
+ }{
+ {crypto.SHA1, "\"sha1\""},
+ {crypto.SHA224, "\"sha224\""},
+ {crypto.SHA256, "\"sha256\""},
+ {crypto.SHA384, "\"sha384\""},
+ {crypto.SHA512, "\"sha512\""},
+ } {
+ hashAlg := NewHashAlg(t.alg)
+ hashAlgJSON, err := hashAlg.MarshalJSON()
+ c.Assert(err, IsNil)
+ c.Check(string(hashAlgJSON), Equals, t.nameAlg)
+ }
+}
+
+func (s *keyDataPlatformSuite) TestHashAlgMarshalJSONInvalid(c *C) {
+ unsupportedAlgorithms := []crypto.Hash{
+ crypto.MD4,
+ crypto.MD5,
+ crypto.MD5SHA1,
+ crypto.RIPEMD160,
+ crypto.SHA3_224,
+ crypto.SHA3_256,
+ crypto.SHA3_384,
+ crypto.SHA3_512,
+ crypto.SHA512_224,
+ crypto.SHA512_256,
+ crypto.BLAKE2s_256,
+ crypto.BLAKE2b_256,
+ crypto.BLAKE2b_384,
+ crypto.BLAKE2b_512,
+ }
+
+ for _, alg := range unsupportedAlgorithms {
+ hashAlg := NewHashAlg(alg)
+ hashAlgJSON, err := hashAlg.MarshalJSON()
+ c.Assert(string(hashAlgJSON), Equals, "")
+ c.Check(err.Error(), Equals, fmt.Sprintf("unknown hash algorithm: %v", crypto.Hash(alg)))
+ }
+}
+
+func (s *keyDataPlatformSuite) TestHashAlgUnmarshalJSON(c *C) {
+ for _, t := range []struct {
+ alg crypto.Hash
+ nameAlg string
+ }{
+ {crypto.SHA1, "\"sha1\""},
+ {crypto.SHA224, "\"sha224\""},
+ {crypto.SHA256, "\"sha256\""},
+ {crypto.SHA384, "\"sha384\""},
+ {crypto.SHA512, "\"sha512\""},
+ {0, "\"foo\""},
+ } {
+ hashAlg := NewHashAlg(crypto.SHA256)
+ err := hashAlg.UnmarshalJSON([]byte(t.nameAlg))
+ c.Assert(err, IsNil)
+ c.Check(crypto.Hash(hashAlg), Equals, t.alg)
+ }
+}
+
+func (s *keyDataPlatformSuite) TestHashAlgUnmarshalJSONInvalid(c *C) {
+ hashAlg := NewHashAlg(crypto.SHA256)
+ err := hashAlg.UnmarshalJSON([]byte("}"))
+
+ e, ok := err.(*json.SyntaxError)
+ c.Assert(ok, Equals, true)
+ c.Assert(e, ErrorMatches, "invalid character '}' looking for beginning of value")
+}
+
+func (s *keyDataPlatformSuite) TestEcdsaPublicKeyMarshalJSONAndUnmarshalJSON(c *C) {
+ rand := testutil.DecodeHexString(c, "12617b35cd4dea2364d2b5c99165c7d8a24249afdf58519796748335d842d0484a6b953e5a42a97d7f9a012d401ab007f1be6e964f48ed1138fdd902eadbea10d50e0eab02ed1a4935867bfa65e270df2100439d2a631b1c501da698a43031e709092b96")
+
+ pk, err := NewEcdsaPublicKey(rand)
+ c.Assert(err, IsNil)
+
+ expected, err := base64.StdEncoding.DecodeString("Ik1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXlmU0tTbGJTSjcyYnQ1Yk1WWmpyd2tJeDVXZFNrRlcrMjJ1TXp6Um13VEVFN3VwZW9hYWZ3RmNheFBDSTA1NWI5UnlPdC9xbmRxQ3ZqSnhKQmwrNWpRPT0i")
+ c.Assert(err, IsNil)
+
+ pkBytes, err := pk.MarshalJSON()
+ c.Assert(err, IsNil)
+
+ c.Check(pkBytes, DeepEquals, expected)
+
+ unmarshalledPk, err := NewEcdsaPublicKey(rand)
+ c.Assert(err, IsNil)
+
+ err = unmarshalledPk.UnmarshalJSON(pkBytes)
+ c.Assert(err, IsNil)
+
+ c.Check(unmarshalledPk, DeepEquals, pk)
+}
+
+func (s *keyDataPlatformSuite) TestEcdsaPublicKeyUnmarshalJSONInvalid(c *C) {
+ // Test with a serialized RSA key
+ pkBytes, err := base64.StdEncoding.DecodeString("Ik1Ed3dEUVlKS29aSWh2Y05BUUVCQlFBREt3QXdLQUloQU1jbC9Vdks0ZzdFZE5LQ0gwQTlraklzd1ZHOFI1S1BUOEVvQjd1V0dDZlRBZ01CQUFFPSI=")
+ c.Assert(err, IsNil)
+
+ rand := testutil.DecodeHexString(c, "617b35cd4dea2364d2b5c99165c7d8a24249afdf58519796748335d842d0484a6b953e5a42a97d7f9a012d401ab007f1be6e964f48ed1138fdd902eadbea10d50e0eab02ed1a4935867bfa65e270df2100439d2a631b1c501da698a43031e709092b96")
+ unmarshalledPk, err := NewEcdsaPublicKey(rand)
+ c.Assert(err, IsNil)
+
+ err = unmarshalledPk.UnmarshalJSON(pkBytes)
+ c.Check(err, ErrorMatches, "invalid key type")
+}
+
+func (s *keyDataPlatformSuite) TestKeyDataScopeMarshalJSONAndUnmarshalJSON(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+ role := "test"
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ kdsBytes, err := kds.MarshalJSON()
+ c.Check(err, IsNil)
+
+ var kds2 KeyDataScope
+
+ kds2.UnmarshalJSON(kdsBytes)
+
+ kdsBytes2, err := kds2.MarshalJSON()
+ c.Assert(err, IsNil)
+ c.Check(kdsBytes2, DeepEquals, kdsBytes)
+}
+
+func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) {
+ primaryKey, err := NewPrimaryKey(32)
+ c.Assert(err, IsNil)
+ role := "test"
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ c.Check(kds.IsBootEnvironmentAuthorized(), IsNil)
+
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+
+ prevKey, ok := signer.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+
+ for i := 0; i < 10; i++ {
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+
+ key, ok := signer.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+ c.Check(key.Equal(prevKey), Equals, true)
+ prevKey = key
+ }
+}
+
+func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey1(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "90e29c3b7902dfc239c1c7aa5928ee232be2f1e7a4018aa7c5465a03a4c0be30")
+ role := "test"
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+
+ privKey, ok := signer.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+
+ expectedDerivedKey := testutil.DecodeHexString(c, "ff7ac99d7a0f16980777b9ace6c316e43e3edb4b0575fab5c22ea80d3e031c1d")
+ c.Check(privKey.X.Bytes(), DeepEquals, expectedDerivedKey)
+}
+
+func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey2(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "cc0ba15ded8561e2278d78a5c4c215653c9b1f872325a9e67882a89088e57023")
+ role := "test"
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+
+ privKey, ok := signer.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+
+ expectedDerivedKey := testutil.DecodeHexString(c, "05962e1c19be2dc1c676b4d6fe0934f2f4af6f584bf03640f5acd9c399b960c6")
+ c.Check(privKey.X.Bytes(), DeepEquals, expectedDerivedKey)
+}
+
+func (s *keyDataPlatformSuite) TestDeriveSignerDifferentRoleMismatch(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "90e29c3b7902dfc239c1c7aa5928ee232be2f1e7a4018aa7c5465a03a4c0be30")
+ role := "test"
+
+ params := &KeyDataScopeParams{
+ PrimaryKey: primaryKey,
+ Role: role,
+ KDFAlg: crypto.SHA256,
+ MDAlg: crypto.SHA256,
+ ModelAlg: crypto.SHA256,
+ }
+
+ kds, err := NewKeyDataScope(params)
+ c.Assert(err, IsNil)
+ c.Check(kds, NotNil)
+
+ signer, err := kds.DeriveSigner(primaryKey, role)
+ c.Assert(err, IsNil)
+
+ privKey, ok := signer.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+
+ expectedDerivedKey := testutil.DecodeHexString(c, "ff7ac99d7a0f16980777b9ace6c316e43e3edb4b0575fab5c22ea80d3e031c1d")
+ c.Check(privKey.X.Bytes(), DeepEquals, expectedDerivedKey)
+
+ signer2, err := kds.DeriveSigner(primaryKey, "different")
+ c.Assert(err, IsNil)
+
+ privKey2, ok := signer2.(*ecdsa.PrivateKey)
+ c.Assert(ok, Equals, true)
+
+ expectedDerivedKey2 := testutil.DecodeHexString(c, "d518d18c366e6faac72c8fc1a180e01a7d52bc3e60512e990c10309fd6c82c9d")
+ c.Check(privKey2.X.Bytes(), DeepEquals, expectedDerivedKey2)
+
+ c.Check(privKey2.X.Bytes(), Not(DeepEquals), privKey.X.Bytes())
+}
+
+type mockPlatformKeyDataHandle struct {
+ Key []byte `json:"key"`
+ IV []byte `json:"iv"`
+ AuthKeyHMAC []byte `json:"auth-key-hmac"`
+}
+
+const (
+ mockPlatformDeviceStateOK = iota
+ mockPlatformDeviceStateUnavailable
+ mockPlatformDeviceStateUninitialized
+)
+
+type mockPlatformKeyDataHandler struct {
+ state int
+ scopes []*KeyDataScope
+}
+
+func (h *mockPlatformKeyDataHandler) checkState() error {
+ switch h.state {
+ case mockPlatformDeviceStateUnavailable:
+ return &PlatformHandlerError{Type: PlatformHandlerErrorUnavailable, Err: errors.New("the platform device is unavailable")}
+ case mockPlatformDeviceStateUninitialized:
+ return &PlatformHandlerError{Type: PlatformHandlerErrorUninitialized, Err: errors.New("the platform device is uninitialized")}
+ default:
+ return nil
+ }
+}
+
+func (h *mockPlatformKeyDataHandler) unmarshalHandle(data *PlatformKeyData) (*mockPlatformKeyDataHandle, error) {
+ var handle mockPlatformKeyDataHandle
+ if err := json.Unmarshal(data.EncodedHandle, &handle); err != nil {
+ return nil, &PlatformHandlerError{Type: PlatformHandlerErrorInvalidData, Err: xerrors.Errorf("JSON decode error: %w", err)}
+ }
+ return &handle, nil
+}
+
+func (h *mockPlatformKeyDataHandler) checkKey(handle *mockPlatformKeyDataHandle, key []byte) error {
+ m := hmac.New(func() hash.Hash { return crypto.SHA256.New() }, handle.Key)
+ m.Write(key)
+ if !bytes.Equal(handle.AuthKeyHMAC, m.Sum(nil)) {
+ return &PlatformHandlerError{Type: PlatformHandlerErrorInvalidAuthKey, Err: errors.New("the supplied key is incorrect")}
+ }
+
+ return nil
+}
+
+func (h *mockPlatformKeyDataHandler) recoverKeys(handle *mockPlatformKeyDataHandle, payload []byte) ([]byte, error) {
+ var authorized bool
+ var err error
+ for _, s := range h.scopes {
+ err = s.IsBootEnvironmentAuthorized()
+ if err == nil {
+ authorized = true
+ break
+ }
+ }
+
+ if len(h.scopes) > 0 && !authorized {
+ return nil, err
+ }
+
+ b, err := aes.NewCipher(handle.Key)
+ if err != nil {
+ return nil, xerrors.Errorf("cannot create cipher: %w", err)
+ }
+
+ s := cipher.NewCFBDecrypter(b, handle.IV)
+ out := make([]byte, len(payload))
+ s.XORKeyStream(out, payload)
+ return out, nil
+}
+
+func (h *mockPlatformKeyDataHandler) RecoverKeys(data *PlatformKeyData, encryptedPayload []byte) ([]byte, error) {
+ if err := h.checkState(); err != nil {
+ return nil, err
+ }
+
+ handle, err := h.unmarshalHandle(data)
+ if err != nil {
+ return nil, err
+ }
+
+ return h.recoverKeys(handle, encryptedPayload)
+}
+
+func (h *mockPlatformKeyDataHandler) RecoverKeysWithAuthKey(data *PlatformKeyData, encryptedPayload []byte, key []byte) ([]byte, error) {
+ if err := h.checkState(); err != nil {
+ return nil, err
+ }
+
+ handle, err := h.unmarshalHandle(data)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := h.checkKey(handle, key); err != nil {
+ return nil, err
+ }
+
+ return h.recoverKeys(handle, encryptedPayload)
+}
+
+func (h *mockPlatformKeyDataHandler) ChangeAuthKey(data *PlatformKeyData, old, new []byte) ([]byte, error) {
+ if err := h.checkState(); err != nil {
+ return nil, err
+ }
+
+ handle, err := h.unmarshalHandle(data)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := h.checkKey(handle, old); err != nil {
+ return nil, err
+ }
+
+ m := hmac.New(func() hash.Hash { return crypto.SHA256.New() }, handle.Key)
+ m.Write(new)
+ handle.AuthKeyHMAC = m.Sum(nil)
+
+ return json.Marshal(&handle)
+}
+
+type keyDataScopeSuite struct {
+ handler *mockPlatformKeyDataHandler
+}
+
+func (s *keyDataScopeSuite) SetUpTest(c *C) {
+ s.handler = &mockPlatformKeyDataHandler{}
+ RegisterPlatformKeyDataHandler("mock-scope", s.handler)
+ s.handler.scopes = nil
+}
+
+var _ = Suite(&keyDataScopeSuite{})
+
+func (s *keyDataScopeSuite) mockProtectKeys(c *C, primaryKey PrimaryKey, KDFAlg crypto.Hash, modelAuthHash crypto.Hash) (out *KeyParams, unlockKey DiskUnlockKey) {
+ unique := make([]byte, len(primaryKey))
+ _, err := rand.Read(unique)
+ c.Assert(err, IsNil)
+
+ reader := new(bytes.Buffer)
+ reader.Write(unique)
+
+ unlockKey, payload, err := MakeDiskUnlockKey(reader, crypto.SHA256, primaryKey)
+ c.Assert(err, IsNil)
+
+ k := make([]byte, 48)
+ _, err = rand.Read(k)
+ c.Assert(err, IsNil)
+
+ handle := mockPlatformKeyDataHandle{
+ Key: k[:32],
+ IV: k[32:],
+ }
+
+ h := hmac.New(func() hash.Hash { return crypto.SHA256.New() }, handle.Key)
+ h.Write(make([]byte, 32))
+ handle.AuthKeyHMAC = h.Sum(nil)
+
+ b, err := aes.NewCipher(handle.Key)
+ c.Assert(err, IsNil)
+ stream := cipher.NewCFBEncrypter(b, handle.IV)
+
+ out = &KeyParams{
+ PlatformName: "mock-scope",
+ Handle: &handle,
+ EncryptedPayload: make([]byte, len(payload)),
+ KDFAlg: KDFAlg}
+ stream.XORKeyStream(out.EncryptedPayload, payload)
+
+ return out, unlockKey
+}
diff --git a/bootscope/scope.go b/bootscope/scope.go
new file mode 100644
index 00000000..3fdc3d75
--- /dev/null
+++ b/bootscope/scope.go
@@ -0,0 +1,45 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope implements key scoping support 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.
+package bootscope
+
+import (
+ "sync/atomic"
+
+ "github.com/snapcore/secboot"
+)
+
+var (
+ currentModel atomic.Value
+ currentBootMode atomic.Value
+)
+
+func SetModel(model secboot.SnapModel) {
+ currentModel.Store(model)
+}
+
+func SetBootMode(mode string) {
+ currentBootMode.Store(mode)
+}
diff --git a/bootscope/snap.go b/bootscope/snap.go
new file mode 100644
index 00000000..489fc9e9
--- /dev/null
+++ b/bootscope/snap.go
@@ -0,0 +1,71 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2023 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 bootscope
+
+import (
+ "crypto"
+ "encoding/asn1"
+ "encoding/base64"
+
+ "github.com/snapcore/secboot"
+ "golang.org/x/crypto/cryptobyte"
+ cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
+ "golang.org/x/xerrors"
+)
+
+var sha3_384oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 9}
+
+func computeSnapModelHash(alg crypto.Hash, model secboot.SnapModel) ([]byte, error) {
+ signKeyId, err := base64.RawURLEncoding.DecodeString(model.SignKeyID())
+ if err != nil {
+ return nil, xerrors.Errorf("cannot decode signing key ID: %w", err)
+ }
+
+ builder := cryptobyte.NewBuilder(nil)
+ builder.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // SnapModel ::= SEQUENCE {
+ b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // signer DigestInfo
+ b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // digestAlgorithm AlgorithmIdentifier
+ b.AddASN1ObjectIdentifier(sha3_384oid) // algorithm OBJECT IDENTIFIER
+ b.AddASN1NULL() // parameters ANY DEFINED BY algorithm OPTIONAL
+ })
+ b.AddASN1OctetString(signKeyId) // digest OCTET STRING
+ })
+ b.AddASN1(cryptobyte_asn1.UTF8String, func(b *cryptobyte.Builder) { // brand UTF8String
+ b.AddBytes([]byte(model.BrandID()))
+ })
+ b.AddASN1(cryptobyte_asn1.UTF8String, func(b *cryptobyte.Builder) { // model UTF8String
+ b.AddBytes([]byte(model.Model()))
+ })
+ b.AddASN1(cryptobyte_asn1.UTF8String, func(b *cryptobyte.Builder) { // series UTF8String
+ b.AddBytes([]byte(model.Series()))
+ })
+ b.AddASN1Enum(int64(model.Grade().Code())) // grade ENUMERATED
+ b.AddASN1Boolean(model.Classic()) // classic BOOLEAN
+ })
+
+ b, err := builder.Bytes()
+ if err != nil {
+ return nil, xerrors.Errorf("cannot serialize model properties: %w", err)
+ }
+
+ h := alg.New()
+ h.Write(b)
+ return h.Sum(nil), nil
+}
diff --git a/bootscope/snap_test.go b/bootscope/snap_test.go
new file mode 100644
index 00000000..4dc76127
--- /dev/null
+++ b/bootscope/snap_test.go
@@ -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 .
+ *
+ */
+
+package bootscope_test
+
+import (
+ "crypto"
+ "encoding/base64"
+
+ "github.com/snapcore/secboot/bootscope"
+ "github.com/snapcore/secboot/internal/testutil"
+ . "gopkg.in/check.v1"
+)
+
+type snapSuite struct {
+}
+
+var _ = Suite(&snapSuite{})
+
+func (s *snapSuite) TestComputeSnapModelHash(c *C) {
+ alg := crypto.SHA256
+ model := testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")
+
+ expected, err := base64.StdEncoding.DecodeString("OdtD1Oz+LVG4A77RTkE1JaKopD8p/AxcUQsa9M/PPrU=")
+ c.Assert(err, IsNil)
+
+ modelAsn, err := bootscope.ComputeSnapModelHash(alg, model)
+ c.Assert(err, IsNil)
+ c.Check(modelAsn, DeepEquals, expected)
+}
diff --git a/crypt.go b/crypt.go
index 0a6d3930..91bfbbf7 100755
--- a/crypt.go
+++ b/crypt.go
@@ -149,8 +149,23 @@ func (s *activateWithKeyDataState) errors() (out []*activateWithKeyDataError) {
}
func (s *activateWithKeyDataState) tryActivateWithRecoveredKey(key DiskUnlockKey, slot int, keyData *KeyData, auxKey PrimaryKey) error {
- if s.model != SkipSnapModelCheck {
- authorized, err := keyData.IsSnapModelAuthorized(auxKey, s.model)
+ model := s.model
+ // Snap model checking is skipped for generation 2 keys regardless of the model argument.
+ // Although a gen 1 key could fake the generation field which is unprotected to also
+ // bypass the model version check, that will result in an umarshalling error later on.
+ switch keyData.Generation() {
+ case 1:
+ if model == nil {
+ return errors.New("nil Model for generation 1 key")
+ }
+ default:
+ // Model authorization checking is skipped for version 2 keys and
+ // up as it is now responsibility of the platform to verify the model.
+ model = SkipSnapModelCheck
+ }
+
+ if model != SkipSnapModelCheck {
+ authorized, err := keyData.IsSnapModelAuthorized(auxKey, model)
switch {
case err != nil:
return xerrors.Errorf("cannot check if snap model is authorized: %w", err)
@@ -429,10 +444,6 @@ func ActivateVolumeWithKeyData(volumeName, sourceDevicePath string, authRequesto
if options.RecoveryKeyTries < 0 {
return errors.New("invalid RecoveryKeyTries")
}
- if options.Model == nil {
- return errors.New("nil Model")
- }
-
if (options.PassphraseTries > 0 || options.RecoveryKeyTries > 0) && authRequestor == nil {
return errors.New("nil authRequestor")
}
diff --git a/crypt_test.go b/crypt_test.go
index 5aa29df3..0fa8fea2 100644
--- a/crypt_test.go
+++ b/crypt_test.go
@@ -813,7 +813,6 @@ func (s *cryptSuite) TestActivateVolumeWithRecoveryKeyErrorHandling6(c *C) {
}
type testActivateVolumeWithKeyDataData struct {
- authorizedModels []SnapModel
passphrase string
volumeName string
sourceDevicePath string
@@ -838,8 +837,6 @@ func (s *cryptSuite) testActivateVolumeWithKeyData(c *C, data *testActivateVolum
}
slot := s.addMockKeyslot(data.sourceDevicePath, unlockKey)
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, data.authorizedModels...), IsNil)
-
authRequestor := &mockAuthRequestor{passphraseResponses: data.authResponses}
options := &ActivateVolumeOptions{
@@ -884,17 +881,9 @@ func (s *cryptSuite) testActivateVolumeWithKeyData(c *C, data *testActivateVolum
}
func (s *cryptSuite) TestActivateVolumeWithKeyData1(c *C) {
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
model: models[0]})
@@ -902,47 +891,14 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData1(c *C) {
func (s *cryptSuite) TestActivateVolumeWithKeyData2(c *C) {
// Test with different volumeName / sourceDevicePath
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
volumeName: "foo",
sourceDevicePath: "/dev/vda2",
model: models[0]})
}
-func (s *cryptSuite) TestActivateVolumeWithKeyData3(c *C) {
- // Test with different authorized models
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
- volumeName: "data",
- sourceDevicePath: "/dev/sda1",
- model: models[0]})
-}
-
func (s *cryptSuite) TestActivateVolumeWithKeyData4(c *C) {
// Test that skipping the snap model check works
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
@@ -953,39 +909,21 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData4(c *C) {
func (s *cryptSuite) TestActivateVolumeWithKeyData5(c *C) {
// Test with passphrase
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
passphrase: "1234",
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
passphraseTries: 1,
authResponses: []interface{}{"1234"},
- model: models[0]})
+ })
}
func (s *cryptSuite) TestActivateVolumeWithKeyData6(c *C) {
// Test with passphrase using multiple tries
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
passphrase: "1234",
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
passphraseTries: 3,
@@ -995,17 +933,9 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData6(c *C) {
func (s *cryptSuite) TestActivateVolumeWithKeyData7(c *C) {
// Test with LUKS token
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
model: models[0],
@@ -1015,17 +945,9 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData7(c *C) {
func (s *cryptSuite) TestActivateVolumeWithKeyData8(c *C) {
// Test with LUKS token with passphrase
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
model: models[0],
@@ -1040,17 +962,9 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData9(c *C) {
// Test with LUKS token and keyslot != 0
s.addMockKeyslot("/dev/sda1", nil) // add an empty slot
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
s.testActivateVolumeWithKeyData(c, &testActivateVolumeWithKeyDataData{
- authorizedModels: models,
volumeName: "data",
sourceDevicePath: "/dev/sda1",
model: models[0],
@@ -1059,8 +973,8 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData9(c *C) {
}
type testActivateVolumeWithKeyDataErrorHandlingData struct {
- primaryKey DiskUnlockKey
- recoveryKey RecoveryKey
+ diskUnlockKey DiskUnlockKey
+ recoveryKey RecoveryKey
authRequestor *mockAuthRequestor
@@ -1077,7 +991,7 @@ type testActivateVolumeWithKeyDataErrorHandlingData struct {
}
func (s *cryptSuite) testActivateVolumeWithKeyDataErrorHandling(c *C, data *testActivateVolumeWithKeyDataErrorHandlingData) error {
- s.addMockKeyslot("/dev/sda1", data.primaryKey)
+ s.addMockKeyslot("/dev/sda1", data.diskUnlockKey)
s.addMockKeyslot("/dev/sda1", data.recoveryKey[:])
var authRequestor AuthRequestor
@@ -1148,7 +1062,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling2(c *C) {
s.handler.state = mockPlatformDeviceStateUnavailable
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{recoveryKeyResponses: []interface{}{recoveryKey}},
recoveryKeyTries: 1,
@@ -1166,7 +1080,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling3(c *C) {
s.handler.state = mockPlatformDeviceStateUninitialized
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{recoveryKeyResponses: []interface{}{recoveryKey}},
recoveryKeyTries: 1,
@@ -1199,7 +1113,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling5(c *C) {
s.handler.state = mockPlatformDeviceStateUnavailable
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
recoveryKeyTries: 0,
keyData: keyData,
@@ -1220,7 +1134,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling6(c *C) {
s.handler.state = mockPlatformDeviceStateUnavailable
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{recoveryKeyResponses: []interface{}{RecoveryKey{}}},
recoveryKeyTries: 1,
@@ -1243,7 +1157,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling7(c *C) {
s.handler.state = mockPlatformDeviceStateUnavailable
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{recoveryKeyResponses: []interface{}{RecoveryKey{}, recoveryKey}},
recoveryKeyTries: 2,
@@ -1261,7 +1175,7 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling8(c *C) {
s.handler.state = mockPlatformDeviceStateUnavailable
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
+ diskUnlockKey: key,
recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{recoveryKeyResponses: []interface{}{errors.New("some error"), recoveryKey}},
recoveryKeyTries: 2,
@@ -1290,8 +1204,8 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling10(c *C) {
recoveryKey := s.newRecoveryKey()
s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
- recoveryKey: recoveryKey,
+ diskUnlockKey: key,
+ recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{
passphraseResponses: []interface{}{"incorrect", "invalid"},
recoveryKeyResponses: []interface{}{recoveryKey}},
@@ -1335,8 +1249,8 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling13(c *C) {
recoveryKey := s.newRecoveryKey()
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
- recoveryKey: recoveryKey,
+ diskUnlockKey: key,
+ recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{
passphraseResponses: []interface{}{""},
recoveryKeyResponses: []interface{}{RecoveryKey{}}},
@@ -1352,38 +1266,6 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling13(c *C) {
"systemd-cryptsetup failed with: exit status 1")
}
-func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling14(c *C) {
- // Test with an invalid value for SnapModel
- keyData, _, _ := s.newNamedKeyData(c, "")
-
- c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- keyData: keyData,
- }), ErrorMatches, "nil Model")
-}
-
-func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling15(c *C) {
- // Test that activation fails if the supplied model is not authorized
- keyData, key, _ := s.newNamedKeyData(c, "foo")
- recoveryKey := s.newRecoveryKey()
-
- c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
- recoveryKey: recoveryKey,
- recoveryKeyTries: 0,
- keyData: keyData,
- model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- activateTries: 0,
- }), ErrorMatches, "cannot activate with platform protected keys:\n"+
- "- foo: snap model is not authorized\n"+
- "and activation with recovery key failed: no recovery key tries permitted")
-}
-
func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling16(c *C) {
// Test that error in authRequestor error surfaces
var kdf testutil.MockKDF
@@ -1391,8 +1273,8 @@ func (s *cryptSuite) TestActivateVolumeWithKeyDataErrorHandling16(c *C) {
recoveryKey := s.newRecoveryKey()
c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
- primaryKey: key,
- recoveryKey: recoveryKey,
+ diskUnlockKey: key,
+ recoveryKey: recoveryKey,
authRequestor: &mockAuthRequestor{
passphraseResponses: []interface{}{errors.New("")},
recoveryKeyResponses: []interface{}{RecoveryKey{}}},
@@ -1457,16 +1339,7 @@ func (s *cryptSuite) testActivateVolumeWithMultipleKeyData(c *C, data *testActiv
func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData1(c *C) {
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
@@ -1483,16 +1356,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData2(c *C) {
// Test with a different volumeName / sourceDevicePath
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
@@ -1509,16 +1373,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData3(c *C) {
// Try with an invalid first key - the second key should be used for activation.
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys[1:],
@@ -1537,16 +1392,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData4(c *C) {
passphrases := []string{"1234", "5678"}
keyData, keys, auxKeys := s.newMultipleNamedKeyDataWithPassphrases(c, passphrases, &kdf, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
@@ -1567,16 +1413,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData5(c *C) {
passphrases := []string{"1234", "5678"}
keyData, keys, auxKeys := s.newMultipleNamedKeyDataWithPassphrases(c, passphrases, &kdf, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
@@ -1602,16 +1439,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData6(c *C) {
unlockKeys := []DiskUnlockKey{unlockKey1, unlockKey2}
primaryKeys := []PrimaryKey{primaryKey1, primaryKey2}
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(primaryKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(primaryKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: unlockKeys,
@@ -1632,16 +1460,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData7(c *C) {
passphrases := []string{"1234", "5678"}
keyData, keys, auxKeys := s.newMultipleNamedKeyDataWithPassphrases(c, passphrases, &kdf, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
@@ -1668,16 +1487,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData8(c *C) {
unlockKeys := []DiskUnlockKey{unlockKey1, unlockKey2}
primaryKeys := []PrimaryKey{primaryKey1, primaryKey2}
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(primaryKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(primaryKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: unlockKeys[1:],
@@ -1692,55 +1502,9 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData8(c *C) {
validAuxKey: primaryKeys[1]})
}
-func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData9(c *C) {
- // Try where the supplied model cannot be authorized via the first key - the
- // second key should be used for activation.
- keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- }
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models[0]), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models[1]), IsNil)
-
- s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
- keys: keys,
- keyData: keyData,
- volumeName: "data",
- sourceDevicePath: "/dev/sda1",
- model: models[1],
- activateSlots: []int{luks2.AnySlot},
- validKey: keys[1],
- validAuxKey: auxKeys[1]})
-}
-
func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData10(c *C) {
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
-
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: keys,
keyData: keyData,
@@ -1756,18 +1520,9 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData11(c *C) {
// Test priority for LUKS stored keys
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "luks1", "luks2")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+ models := []SnapModel{nil}
for i := range keyData {
- c.Check(keyData[i].SetAuthorizedSnapModels(auxKeys[i], models...), IsNil)
-
w := makeMockKeyDataWriter()
c.Check(keyData[i].WriteAtomic(w), IsNil)
@@ -1795,16 +1550,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData13(c *C) {
// Test that external keyData has precedence over the LUKS stored ones
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "luks", "external")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
w := makeMockKeyDataWriter()
c.Check(keyData[0].WriteAtomic(w), IsNil)
@@ -1828,42 +1574,6 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData13(c *C) {
validAuxKey: auxKeys[1]})
}
-func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData14(c *C) {
- // Test unauthorized external keyData with authorized LUKS keyData
- keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "luks", "external")
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
-
- w := makeMockKeyDataWriter()
- c.Check(keyData[0].WriteAtomic(w), IsNil)
-
- token := &luksview.KeyDataToken{
- TokenBase: luksview.TokenBase{
- TokenKeyslot: 0,
- TokenName: "default",
- },
- Data: w.final.Bytes()}
- s.addMockToken("/dev/sda1", token)
-
- s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
- keys: keys,
- keyData: keyData[1:],
- volumeName: "data",
- sourceDevicePath: "/dev/sda1",
- model: models[0],
- activateSlots: []int{0},
- validKey: keys[0],
- validAuxKey: auxKeys[0]})
-}
-
func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData15(c *C) {
// Test activation with empty LUKS token but valid external token
slot := s.addMockKeyslot("/dev/sda1", nil) // add an empty slot for the empty token
@@ -1876,15 +1586,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData15(c *C) {
keyData, key, auxKey := s.newNamedKeyData(c, "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData.SetAuthorizedSnapModels(auxKey, models...), IsNil)
+ models := []SnapModel{nil}
s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
keys: []DiskUnlockKey{key},
@@ -1910,15 +1612,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData16(c *C) {
keyData, key, auxKey := s.newNamedKeyData(c, "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData.SetAuthorizedSnapModels(auxKey, models...), IsNil)
+ models := []SnapModel{nil}
stderr := new(bytes.Buffer)
restore := MockStderr(stderr)
@@ -1940,20 +1634,11 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData16(c *C) {
func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData17(c *C) {
// Test activation with invalid (containing an invalid key) and valid LUKS token.
- s.addMockKeyslot("/dev/sda1", nil) // add an empty slot for the invalid token
-
- keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ s.addMockKeyslot("/dev/sda1", nil) // add an empty slot for the invalid token
+
+ keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
+
+ models := []SnapModel{nil}
for i, kd := range keyData {
w := makeMockKeyDataWriter()
@@ -1984,16 +1669,7 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyData18(c *C) {
// valid LUKS token.
keyData, keys, auxKeys := s.newMultipleNamedKeyData(c, "", "")
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData[0].SetAuthorizedSnapModels(auxKeys[0], models...), IsNil)
- c.Check(keyData[1].SetAuthorizedSnapModels(auxKeys[1], models...), IsNil)
+ models := []SnapModel{nil}
w := makeMockKeyDataWriter()
c.Check(keyData[1].WriteAtomic(w), IsNil)
@@ -2321,38 +1997,6 @@ func (s *cryptSuite) TestActivateVolumeWithMultipleKeyDataErrorHandling13(c *C)
"systemd-cryptsetup failed with: exit status 1")
}
-func (s *cryptSuite) TestActivateVolumeWithMultipleKeyDataErrorHandling14(c *C) {
- // Test with an invalid value for SnapModel.
- keyData, _, _ := s.newMultipleNamedKeyData(c, "", "")
-
- c.Check(s.testActivateVolumeWithMultipleKeyDataErrorHandling(c, &testActivateVolumeWithMultipleKeyDataErrorHandlingData{
- keyData: keyData,
- }), ErrorMatches, "nil Model")
-}
-func (s *cryptSuite) TestActivateVolumeWithMultipleKeyDataErrorHandling15(c *C) {
- // Test with an unauthorized snap model.
- keyData, keys, _ := s.newMultipleNamedKeyData(c, "foo", "bar")
- recoveryKey := s.newRecoveryKey()
-
- c.Check(s.testActivateVolumeWithMultipleKeyDataErrorHandling(c, &testActivateVolumeWithMultipleKeyDataErrorHandlingData{
- keys: keys,
- recoveryKey: recoveryKey,
- keyData: keyData,
- model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- recoveryKeyTries: 0,
- activateTries: 0,
- }), ErrorMatches, "cannot activate with platform protected keys:\n"+
- "- foo: snap model is not authorized\n"+
- "- bar: snap model is not authorized\n"+
- "and activation with recovery key failed: no recovery key tries permitted")
-}
-
type testActivateVolumeWithKeyData struct {
keyData []byte
expectedKeyData []byte
@@ -4049,3 +3693,616 @@ func (s *cryptSuiteUnmocked) TestRenameLUKS2ContainerRecoveryKey(c *C) {
TokenName: "bar",
TokenKeyslot: 1}}})
}
+
+// Legacy
+func (s *cryptSuite) TestActivateVolumeWithLegacyKeyData3(c *C) {
+ var err error
+ var unlockKey DiskUnlockKey
+ var primaryKey PrimaryKey
+ var keyData *KeyData
+ var kdf testutil.MockKDF
+
+ data := &testActivateVolumeWithKeyDataData{
+ volumeName: "data",
+ sourceDevicePath: "/dev/sda1"}
+
+ model := testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")
+
+ primaryKey = testutil.DecodeHexString(c, "b410288b4d466cbeb08b490e5a1728dad0282b27c15f1f4828cac62e88fb7ff5")
+ unlockKey = testutil.DecodeHexString(c, "d765126a3f3ff1cde33445d9eb178ac6302deb813d023020e3a56abf60398dd1")
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"0GCaTfIgLy9dCqqcfOTjMs9CXm4rPQUnvJNmPKhnIes=",` +
+ `"iv":"jRuLy2H7lDV2tyMd8t5L6g==",` +
+ `"auth-key-hmac":"6b9WLMjXPvtVSyUZ2/Cwu8ksvZla1nyqtBPK3jL4q7I=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"DqgmsMD4d2NMqQ9ugLBTLRZW+ZCOkjgR6rRyIAXOb2Rdd0wA21SN09N9Nmkt5fzNou34P6OVTEu8wQd+nToGzQk8Tlc=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"qX+OkuhbLRAmB3BvgSQR7U0qUMJguOQqPG/V8aMarqk=",` +
+ `"digest":"PrtdZnxX2aE0rCxgn/vmHSUKWS4Cr2P+B7Hj70W1D7w="},` +
+ `"hmacs":["6PbEHuaRXkghoQlYYRZbj4PWcq2XfL/qXuPzTfxKjDE=",` +
+ `"JVhzcAvNFHYQYgPM82TVVtIsuTBbxjBs8wCb1yDY5mA="]}}
+ `)
+
+ keyData, err = ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ slot := s.addMockKeyslot(data.sourceDevicePath, unlockKey)
+
+ authRequestor := &mockAuthRequestor{passphraseResponses: data.authResponses}
+
+ options := &ActivateVolumeOptions{
+ PassphraseTries: data.passphraseTries,
+ KeyringPrefix: data.keyringPrefix,
+ Model: model}
+
+ slot = luks2.AnySlot
+ err = ActivateVolumeWithKeyData(data.volumeName, data.sourceDevicePath, authRequestor, &kdf, options, keyData)
+
+ c.Assert(err, IsNil)
+
+ c.Check(s.luks2.operations, DeepEquals, []string{
+ "newLUKSView(" + data.sourceDevicePath + ",0)",
+ fmt.Sprintf("Activate("+data.volumeName+","+data.sourceDevicePath+",%d)", slot),
+ })
+
+ c.Check(authRequestor.passphraseRequests, HasLen, len(data.authResponses))
+ for _, rsp := range authRequestor.passphraseRequests {
+ c.Check(rsp.volumeName, Equals, data.volumeName)
+ c.Check(rsp.sourceDevicePath, Equals, data.sourceDevicePath)
+ }
+
+ // This should be done last because it may fail in some circumstances.
+ s.checkKeyDataKeysInKeyring(c, data.keyringPrefix, data.sourceDevicePath, unlockKey, primaryKey)
+}
+
+func (s *cryptSuite) TestActivateVolumeWithLegacyKeyDataErrorHandling14(c *C) {
+ // Test with an invalid value for SnapModel for legacy keys
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"EKvGikEsIkaMQpQGr6PA1pzC224nYteGa56YD0PUaLU=",` +
+ `"iv":"8VkzdjS3JTQwiF8V8/dVKw==",` +
+ `"auth-key-hmac":"8q4FsJLVf4FMje665gkwOjlMlhVghEcrRKC+vdbn+sk=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"oCi+ViIX3cX6OcxzERB8x5GnDBiQtI3mnP919E0JHj/J9IbE8Pqq22YuHlp+/tYjE8Gkhf2YEJKRjwke45HEKXOA/eE=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"HBRH/GTYQ2so2Fau3U6ZvAYgiRmnb6t4WHpuOKNpkK8=",` +
+ `"digest":"eNjOwEPldwEXNSOkgAk/oJ8OhU3hjr+UnYqVf6lEFi0="},` +
+ `"hmacs":null}}
+`)
+
+ keyData, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
+ keyData: keyData,
+ }), ErrorMatches, "cannot activate with platform protected keys:\n"+
+ "- : nil Model for generation 1 key\n"+
+ "and activation with recovery key failed: no recovery key tries permitted")
+}
+
+func (s *cryptSuite) TestActivateVolumeWithLegacyKeyDataErrorHandling15(c *C) {
+ // Test that activation fails for legacy keys lif the supplied model is not authorized
+ key := testutil.DecodeHexString(c, "f7fa464710317654f14f22ab6eff4c88f13a77d78045f2a882e47c62286093b2")
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"fGSmc6pljAph4q00AKuniTSl19yZSHOO5ClFBnm3mEg=",` +
+ `"iv":"GanDRGxWSx4stoOC8ueRaQ==",` +
+ `"auth-key-hmac":"NPjHH7EG+guHv7ZUl5tetrD7268e6+kx4TIiOUzC2ks=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"kDm5zMabUoz83oLJMhmjWMmFexRSPJi0+yYgyGlp6l9hr20e4NZCzyiIchrHRXjS/ipVLy42H2pPm0fdTF3YXnYuKnk=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"7G4XkozL+sVJ2+vcp0zof6m3M6XRNSooHdV07GFmG74=",` +
+ `"digest":"bCda3tRyxm9yobtWLPflFzdpXOWoSyBkLjAI4Ni/+pE="},` +
+ `"hmacs":null}}
+`)
+
+ keyData, err := ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ recoveryKey := s.newRecoveryKey()
+
+ c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
+ diskUnlockKey: key,
+ recoveryKey: recoveryKey,
+ recoveryKeyTries: 0,
+ keyData: keyData,
+ model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
+ activateTries: 0,
+ }), ErrorMatches, "cannot activate with platform protected keys:\n"+
+ "- foo: snap model is not authorized\n"+
+ "and activation with recovery key failed: no recovery key tries permitted")
+}
+
+func (s *cryptSuite) TestActivateVolumeWithLegacyKeyDataErrorHandling17(c *C) {
+ // Test that activation fails with unmarshalling error if a legacy key fakes the generation
+ // field to bypass snap model verification
+ key := testutil.DecodeHexString(c, "97999b1af0988ee671ad3313bff8c47e09673d40b4b8a0600b6b2a691f0ed305")
+
+ j := []byte(
+ `{` +
+ `"generation":2,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"suC0CHFlXXv6yUy96YU1Teb5kSS5wzXIWKVawHDP2g8=",` +
+ `"iv":"7opsk4XdsYrV6OYaif9Z3A==",` +
+ `"auth-key-hmac":"3mTDfXUVrXRiFqDyzqzq6/shJe+oWL7QCSvRSADzXyI=",` +
+ `"exp-generation":2,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0` +
+ `},` +
+ `` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"QDlIsEnR3y9KTj4Sv9o99GIve2G7RYdTKIxjMS1LxUWmQrCUND0Eojpn1bAThpQWBS2Gj2dXplyCpZiNLJEagzAnyyQ=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"JDdYOi26PGM2/sIoHeMAfEFV6pwUpUAGIbGSJk65gi0=",` +
+ `"digest":"WmBY95DnbednRIQqMj+sYlWZBxaHIumjE6zI+1nEkIg="` +
+ `},` +
+ `` +
+ `"hmacs":null}}
+`)
+
+ keyData, err := ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ recoveryKey := s.newRecoveryKey()
+
+ c.Check(s.testActivateVolumeWithKeyDataErrorHandling(c, &testActivateVolumeWithKeyDataErrorHandlingData{
+ diskUnlockKey: key,
+ recoveryKey: recoveryKey,
+ recoveryKeyTries: 0,
+ keyData: keyData,
+ model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
+ activateTries: 0,
+ }), ErrorMatches, "cannot activate with platform protected keys:\n"+
+ "- foo: cannot recover key: invalid key data: cannot unmarshal cleartext key payload: malformed input\n"+
+ "and activation with recovery key failed: no recovery key tries permitted")
+}
+
+func (s *cryptSuite) TestActivateVolumeWithMultipleLegacyKeyData9(c *C) {
+ // Try where the supplied model cannot be authorized via the first key - the
+ // second key should be used for activation.
+ var keyData []*KeyData
+ var keys []DiskUnlockKey
+ var primaryKeys []PrimaryKey
+
+ keys = append(keys, testutil.DecodeHexString(c, "ea2acab1d4c292fb47580c7a324d4b7a037dbbc182cb495e6e10ed12601f1286"))
+ keys = append(keys, testutil.DecodeHexString(c, "1be119d0ecf75cc4716f2e30b1a9c3406d4edfacd6d407c07b431a23a1945556"))
+ primaryKeys = append(primaryKeys, testutil.DecodeHexString(c, "c2f01b85dad3f609a522454005368491a33febfc125773138f3844539518d717"))
+ primaryKeys = append(primaryKeys, testutil.DecodeHexString(c, "c8f5ae362f24ddab000a61c5e5a688f4eb6a4d117d62fae7d42fba70ac1a0826"))
+
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"Mbe9jfsXuzwadGP43ReLafF88yrUJWl9dBmUVgslnyY=",` +
+ `"iv":"ZgrEKJcNZ7UKTe1eZ92JTQ==",` +
+ `"auth-key-hmac":"2himzm8giL4MiusN/wLP277Cww2MXwuYY+jrZtIg8iw=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"kfcx26i0fXh0D+V6L8/QBglGbV7wavLBBWMO5oDywSQuBhl+rfSQY0eE7ClPHHqXntlTBgqwPkbuRnT/ScE6hwtlm6M=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"0pND7IfM0fnKpq0uquaMfdiGmYIXnIO2y24jbG9y/fc=",` +
+ `"digest":"AvLUNyjCNuxEFOgWfT/U7AcCgYfXrfEfm8ADkcfUF8s="},` +
+ `"hmacs":null}}
+
+`)
+
+ kd, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ j = []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"X8Fpc9zrqbU3zR2ON65nfGKf1fGu1OGCudn7BZb4mMw=",` +
+ `"iv":"0+Tc+gGgDBlOsuvIMOFkSw==",` +
+ `"auth-key-hmac":"nk0nw4qcWMlsKQcBx7Tkqm6H68UVxL+UPV1IjJsXf6s=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"HcskA6HBVj9JBQPa7S1ci+Yn9tlbzby+5V3ygb/MW0cFFu4GgQgbtOGXBEGB/yPC2vaH3Q+e4W21NEFDExCqp3bTFlU=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"MsiD5TLUa52lG8ovkDpWu16c4iz8mvbRX4fi858RklA=",` +
+ `"digest":"o/GwjEc83qhhiyXWHV900kfqQf0yv33M7k/OYzflHCs="},` +
+ `"hmacs":null}}
+`)
+
+ kd, err = ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ models := []SnapModel{
+ testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
+ testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "other-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
+ }
+
+ c.Check(keyData[0].SetAuthorizedSnapModels(primaryKeys[0], models[0]), IsNil)
+ c.Check(keyData[1].SetAuthorizedSnapModels(primaryKeys[1], models[1]), IsNil)
+
+ s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
+ keys: keys,
+ keyData: keyData,
+ volumeName: "data",
+ sourceDevicePath: "/dev/sda1",
+ model: models[1],
+ activateSlots: []int{luks2.AnySlot},
+ validKey: keys[1],
+ validAuxKey: primaryKeys[1]})
+}
+
+func (s *cryptSuite) TestActivateVolumeWithMultipleLegacyKeyData14(c *C) {
+ // Test unauthorized external keyData with authorized LUKS keyData
+ var keyData []*KeyData
+ var keys []DiskUnlockKey
+ var primaryKeys []PrimaryKey
+
+ keys = append(keys, testutil.DecodeHexString(c, "ea2acab1d4c292fb47580c7a324d4b7a037dbbc182cb495e6e10ed12601f1286"))
+ keys = append(keys, testutil.DecodeHexString(c, "1be119d0ecf75cc4716f2e30b1a9c3406d4edfacd6d407c07b431a23a1945556"))
+ primaryKeys = append(primaryKeys, testutil.DecodeHexString(c, "c2f01b85dad3f609a522454005368491a33febfc125773138f3844539518d717"))
+ primaryKeys = append(primaryKeys, testutil.DecodeHexString(c, "c8f5ae362f24ddab000a61c5e5a688f4eb6a4d117d62fae7d42fba70ac1a0826"))
+
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"Mbe9jfsXuzwadGP43ReLafF88yrUJWl9dBmUVgslnyY=",` +
+ `"iv":"ZgrEKJcNZ7UKTe1eZ92JTQ==",` +
+ `"auth-key-hmac":"2himzm8giL4MiusN/wLP277Cww2MXwuYY+jrZtIg8iw=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"kfcx26i0fXh0D+V6L8/QBglGbV7wavLBBWMO5oDywSQuBhl+rfSQY0eE7ClPHHqXntlTBgqwPkbuRnT/ScE6hwtlm6M=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"0pND7IfM0fnKpq0uquaMfdiGmYIXnIO2y24jbG9y/fc=",` +
+ `"digest":"AvLUNyjCNuxEFOgWfT/U7AcCgYfXrfEfm8ADkcfUF8s="},` +
+ `"hmacs":null}}
+
+`)
+
+ kd, err := ReadKeyData(&mockKeyDataReader{"luks", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ j = []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"X8Fpc9zrqbU3zR2ON65nfGKf1fGu1OGCudn7BZb4mMw=",` +
+ `"iv":"0+Tc+gGgDBlOsuvIMOFkSw==",` +
+ `"auth-key-hmac":"nk0nw4qcWMlsKQcBx7Tkqm6H68UVxL+UPV1IjJsXf6s=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"HcskA6HBVj9JBQPa7S1ci+Yn9tlbzby+5V3ygb/MW0cFFu4GgQgbtOGXBEGB/yPC2vaH3Q+e4W21NEFDExCqp3bTFlU=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"MsiD5TLUa52lG8ovkDpWu16c4iz8mvbRX4fi858RklA=",` +
+ `"digest":"o/GwjEc83qhhiyXWHV900kfqQf0yv33M7k/OYzflHCs="},` +
+ `"hmacs":null}}
+`)
+
+ kd, err = ReadKeyData(&mockKeyDataReader{"external", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ models := []SnapModel{
+ testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
+
+ c.Check(keyData[0].SetAuthorizedSnapModels(primaryKeys[0], models...), IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData[0].WriteAtomic(w), IsNil)
+
+ token := &luksview.KeyDataToken{
+ TokenBase: luksview.TokenBase{
+ TokenKeyslot: 0,
+ TokenName: "default",
+ },
+ Data: w.final.Bytes()}
+ s.addMockToken("/dev/sda1", token)
+
+ s.testActivateVolumeWithMultipleKeyData(c, &testActivateVolumeWithMultipleKeyDataData{
+ keys: keys,
+ keyData: keyData[1:],
+ volumeName: "data",
+ sourceDevicePath: "/dev/sda1",
+ model: models[0],
+ activateSlots: []int{0},
+ validKey: keys[0],
+ validAuxKey: primaryKeys[0]})
+}
+
+func (s *cryptSuite) TestActivateVolumeWithMultipleLegacyKeyDataErrorHandling14(c *C) {
+ // Test with an invalid value for SnapModel.
+ var keyData []*KeyData
+
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"Mbe9jfsXuzwadGP43ReLafF88yrUJWl9dBmUVgslnyY=",` +
+ `"iv":"ZgrEKJcNZ7UKTe1eZ92JTQ==",` +
+ `"auth-key-hmac":"2himzm8giL4MiusN/wLP277Cww2MXwuYY+jrZtIg8iw=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"kfcx26i0fXh0D+V6L8/QBglGbV7wavLBBWMO5oDywSQuBhl+rfSQY0eE7ClPHHqXntlTBgqwPkbuRnT/ScE6hwtlm6M=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"0pND7IfM0fnKpq0uquaMfdiGmYIXnIO2y24jbG9y/fc=",` +
+ `"digest":"AvLUNyjCNuxEFOgWfT/U7AcCgYfXrfEfm8ADkcfUF8s="},` +
+ `"hmacs":null}}
+
+`)
+
+ kd, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ j = []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"X8Fpc9zrqbU3zR2ON65nfGKf1fGu1OGCudn7BZb4mMw=",` +
+ `"iv":"0+Tc+gGgDBlOsuvIMOFkSw==",` +
+ `"auth-key-hmac":"nk0nw4qcWMlsKQcBx7Tkqm6H68UVxL+UPV1IjJsXf6s=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"HcskA6HBVj9JBQPa7S1ci+Yn9tlbzby+5V3ygb/MW0cFFu4GgQgbtOGXBEGB/yPC2vaH3Q+e4W21NEFDExCqp3bTFlU=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"MsiD5TLUa52lG8ovkDpWu16c4iz8mvbRX4fi858RklA=",` +
+ `"digest":"o/GwjEc83qhhiyXWHV900kfqQf0yv33M7k/OYzflHCs="},` +
+ `"hmacs":null}}
+`)
+
+ kd, err = ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ c.Check(s.testActivateVolumeWithMultipleKeyDataErrorHandling(c, &testActivateVolumeWithMultipleKeyDataErrorHandlingData{
+ keyData: keyData,
+ }), ErrorMatches, "cannot activate with platform protected keys:\n"+
+ "- : nil Model for generation 1 key\n"+
+ "- : nil Model for generation 1 key\n"+
+ "and activation with recovery key failed: no recovery key tries permitted")
+}
+
+func (s *cryptSuite) TestActivateVolumeWithMultipleLegacyKeyDataErrorHandling15(c *C) {
+ // Test with an unauthorized snap model.
+ var keyData []*KeyData
+ var keys []DiskUnlockKey
+
+ keys = append(keys, testutil.DecodeHexString(c, "ea2acab1d4c292fb47580c7a324d4b7a037dbbc182cb495e6e10ed12601f1286"))
+ keys = append(keys, testutil.DecodeHexString(c, "1be119d0ecf75cc4716f2e30b1a9c3406d4edfacd6d407c07b431a23a1945556"))
+
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"Mbe9jfsXuzwadGP43ReLafF88yrUJWl9dBmUVgslnyY=",` +
+ `"iv":"ZgrEKJcNZ7UKTe1eZ92JTQ==",` +
+ `"auth-key-hmac":"2himzm8giL4MiusN/wLP277Cww2MXwuYY+jrZtIg8iw=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"kfcx26i0fXh0D+V6L8/QBglGbV7wavLBBWMO5oDywSQuBhl+rfSQY0eE7ClPHHqXntlTBgqwPkbuRnT/ScE6hwtlm6M=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"0pND7IfM0fnKpq0uquaMfdiGmYIXnIO2y24jbG9y/fc=",` +
+ `"digest":"AvLUNyjCNuxEFOgWfT/U7AcCgYfXrfEfm8ADkcfUF8s="},` +
+ `"hmacs":null}}
+
+`)
+
+ kd, err := ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ j = []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"X8Fpc9zrqbU3zR2ON65nfGKf1fGu1OGCudn7BZb4mMw=",` +
+ `"iv":"0+Tc+gGgDBlOsuvIMOFkSw==",` +
+ `"auth-key-hmac":"nk0nw4qcWMlsKQcBx7Tkqm6H68UVxL+UPV1IjJsXf6s=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"HcskA6HBVj9JBQPa7S1ci+Yn9tlbzby+5V3ygb/MW0cFFu4GgQgbtOGXBEGB/yPC2vaH3Q+e4W21NEFDExCqp3bTFlU=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"MsiD5TLUa52lG8ovkDpWu16c4iz8mvbRX4fi858RklA=",` +
+ `"digest":"o/GwjEc83qhhiyXWHV900kfqQf0yv33M7k/OYzflHCs="},` +
+ `"hmacs":null}}
+`)
+
+ kd, err = ReadKeyData(&mockKeyDataReader{"bar", bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+ keyData = append(keyData, kd)
+
+ recoveryKey := s.newRecoveryKey()
+
+ c.Check(s.testActivateVolumeWithMultipleKeyDataErrorHandling(c, &testActivateVolumeWithMultipleKeyDataErrorHandlingData{
+ keys: keys,
+ recoveryKey: recoveryKey,
+ keyData: keyData,
+ model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
+ "authority-id": "fake-brand",
+ "series": "16",
+ "brand-id": "fake-brand",
+ "model": "fake-model",
+ "grade": "secured",
+ }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
+ recoveryKeyTries: 0,
+ activateTries: 0,
+ }), ErrorMatches, "cannot activate with platform protected keys:\n"+
+ "- foo: snap model is not authorized\n"+
+ "- bar: snap model is not authorized\n"+
+ "and activation with recovery key failed: no recovery key tries permitted")
+}
diff --git a/export_test.go b/export_test.go
index b09f2b34..46e17c53 100644
--- a/export_test.go
+++ b/export_test.go
@@ -20,6 +20,8 @@
package secboot
import (
+ "bytes"
+ "encoding/binary"
"io"
"github.com/snapcore/secboot/internal/luks2"
@@ -149,3 +151,14 @@ func MockHashAlgAvailable() (restore func()) {
func (d *KeyData) DerivePassphraseKeys(passphrase string, kdf KDF) (key, iv, auth []byte, err error) {
return d.derivePassphraseKeys(passphrase, kdf)
}
+
+// MarshalV1Keys serializes the supplied disk unlock key and auxiliary key in
+// the v1 format that is ready to be encrypted by a platform's secure device.
+func MarshalV1Keys(key DiskUnlockKey, auxKey PrimaryKey) []byte {
+ w := new(bytes.Buffer)
+ binary.Write(w, binary.BigEndian, uint16(len(key)))
+ w.Write(key)
+ binary.Write(w, binary.BigEndian, uint16(len(auxKey)))
+ w.Write(auxKey)
+ return w.Bytes()
+}
diff --git a/keydata.go b/keydata.go
index 163e4676..92da3814 100644
--- a/keydata.go
+++ b/keydata.go
@@ -33,8 +33,6 @@ import (
"hash"
"io"
- drbg "github.com/canonical/go-sp800.90a-drbg"
-
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
"golang.org/x/crypto/hkdf"
@@ -50,13 +48,12 @@ const (
)
var (
- keyDataGeneration int = 2
- snapModelHMACKDFLabel = []byte("SNAP-MODEL-HMAC")
- sha1Oid = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
- sha224Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 4}
- sha256Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
- sha384Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
- sha512Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
+ keyDataGeneration int = 2
+ sha1Oid = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
+ sha224Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 4}
+ sha256Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
+ sha384Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
+ sha512Oid = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
)
// ErrNoPlatformHandlerRegistered is returned from KeyData methods if no
@@ -136,19 +133,13 @@ type KeyParams struct {
// already encoded to JSON can be supplied using the json.RawMessage type.
Handle interface{}
+ Role string
+
// EncryptedPayload contains the encrypted and authenticated payload. The
// plaintext payload should be created with [MakeDiskUnlockKey].
EncryptedPayload []byte
- // PrimaryKey is a key used to authorize changes to the key data.
- // It must match the key protected inside PlatformKeyData.EncryptedPayload.
- PrimaryKey PrimaryKey
-
- // SnapModelAuthHash is the digest algorithm used for HMACs of Snap
- // device models, and also the digest algorithm used to produce the
- // key digest.
- SnapModelAuthHash crypto.Hash
- PlatformName string // Name of the platform that produced this data
+ PlatformName string // Name of the platform that produced this data
// KDFAlg is the digest algorithm used to derive additional keys during
// the use of the created KeyData. It must match the algorithm passed to
@@ -279,119 +270,6 @@ func (a hashAlg) marshalASN1(b *cryptobyte.Builder) {
})
}
-type snapModelHMAC []byte
-
-type snapModelHMACList []snapModelHMAC
-
-func (l snapModelHMACList) contains(h snapModelHMAC) bool {
- for _, v := range l {
- if bytes.Equal(v, h) {
- return true
- }
- }
- return false
-}
-
-// keyDigest contains a salted digest to verify the correctness of a key.
-type keyDigest struct {
- Alg hashAlg `json:"alg"`
- Salt []byte `json:"salt"`
- Digest []byte `json:"digest"`
-}
-
-// hkdfData contains the parameters used to derive a key using HKDF.
-type hkdfData struct {
- Alg hashAlg `json:"alg"` // Digest algorithm to use for HKDF
-}
-
-type authorizedSnapModelsRaw struct {
- Alg hashAlg `json:"alg"`
- KDFAlg hashAlg `json:"kdf_alg,omitempty"`
- KeyDigest json.RawMessage `json:"key_digest"`
- Hmacs snapModelHMACList `json:"hmacs"`
-}
-
-// authorizedSnapModels defines the Snap models that have been
-// authorized to access the data protected by a key.
-type authorizedSnapModels struct {
- alg hashAlg // Digest algorithm used for the authorized model HMACs
- kdfAlg hashAlg // Digest algorithm used to derive the HMAC key with HKDF. Zero for legacy (DRBG) derivation.
- keyDigest keyDigest // information used to validate the correctness of the HMAC key
- hmacs snapModelHMACList // the list of HMACs of authorized models
-
- // legacyKeyDigest is true when keyDigest should be marshalled
- // as a plain key rather than a keyDigest object.
- legacyKeyDigest bool
-}
-
-// MarshalJSON implements custom marshalling to handle older key data
-// objects where the key_digest field was just a base64 encoded key.
-func (m authorizedSnapModels) MarshalJSON() ([]byte, error) {
- var digest json.RawMessage
- var err error
- if m.legacyKeyDigest {
- digest, err = json.Marshal(m.keyDigest.Digest)
- } else {
- digest, err = json.Marshal(&m.keyDigest)
- }
- if err != nil {
- return nil, err
- }
-
- return json.Marshal(&authorizedSnapModelsRaw{
- Alg: m.alg,
- KDFAlg: m.kdfAlg,
- KeyDigest: digest,
- Hmacs: m.hmacs})
-}
-
-// UnmarshalJSON implements custom unmarshalling to handle older key data
-// objects where the key_digest field was just a base64 encoded key.
-func (m *authorizedSnapModels) UnmarshalJSON(b []byte) error {
- var raw authorizedSnapModelsRaw
- if err := json.Unmarshal(b, &raw); err != nil {
- return err
- }
-
- *m = authorizedSnapModels{
- alg: raw.Alg,
- kdfAlg: raw.KDFAlg,
- hmacs: raw.Hmacs}
-
- token, err := json.NewDecoder(bytes.NewReader(raw.KeyDigest)).Token()
- switch {
- case err == io.EOF:
- // Empty field, ignore
- return nil
- case err != nil:
- return err
- }
-
- switch t := token.(type) {
- case json.Delim:
- // Newer data, where the KeyDigest field is an object.
- if t != '{' {
- return fmt.Errorf("invalid delim (%v) at start of key_digest field", t)
- }
- if err := json.Unmarshal(raw.KeyDigest, &m.keyDigest); err != nil {
- return err
- }
- case string:
- // Older data, where the KeyDigest field was a base64 encoded key.
- // Convert it to an object.
- _ = t
- m.keyDigest.Alg = raw.Alg
- m.legacyKeyDigest = true
- if err := json.Unmarshal(raw.KeyDigest, &m.keyDigest.Digest); err != nil {
- return err
- }
- default:
- return fmt.Errorf("invalid token (%v) at start of key_digest field", token)
- }
-
- return nil
-}
-
// kdfData corresponds to the arguments to a KDF and matches the
// corresponding object in the LUKS2 specification.
type kdfData struct {
@@ -417,7 +295,7 @@ type passphraseParams struct {
type keyData struct {
// Generation is a number used to differentiate between different key formats.
// i.e Gen1 keys are binary serialized and include a primary and an unlock key while
- // Gen2 keys are ASN1 serialized and include a primary key and a unique key which is
+ // Gen2 keys are DER encoded and include a primary key and a unique key which is
// used to derive the unlock key.
Generation int `json:"generation,omitempty"`
@@ -428,6 +306,17 @@ type keyData struct {
// the encrypted payloads.
PlatformHandle json.RawMessage `json:"platform_handle"`
+ // Role describes the role of this key, and is used to restrict the
+ // scope of authorizations associated with it (such as PCR policies).
+ // XXX: It's a bit strange having it here because it's not used by
+ // this package, but it does allow the configuration manager to filter
+ // keys by role without having to decode the platform specific part.
+ // Maybe in the future, KeyData should be an interface implemented
+ // entirely by each platform with some shared helpers rather than
+ // what we have now (a concrete KeyData implementation with an
+ // opaque blob).
+ Role string `json:"role"`
+
// KDFAlg is the algorithm that is used to derive the unlock key from a primary key.
// It is also used to derive additional keys from the passphrase derived key in
// derivePassphraseKeys.
@@ -440,7 +329,9 @@ type keyData struct {
// AuthorizedSnapModels contains information about the Snap models
// that have been authorized to access the data protected by this key.
- AuthorizedSnapModels authorizedSnapModels `json:"authorized_snap_models"`
+ // This field is only used by gen 1 keys. Gen 2 keys handle authorized
+ // snap models differently depending on the platform implementation.
+ AuthorizedSnapModels *authorizedSnapModels `json:"authorized_snap_models,omitempty"`
}
func processPlatformHandlerError(err error) error {
@@ -468,51 +359,6 @@ type KeyData struct {
data keyData
}
-func (d *KeyData) snapModelAuthKeyLegacy(auxKey PrimaryKey) ([]byte, error) {
- rng, err := drbg.NewCTRWithExternalEntropy(32, auxKey, nil, snapModelHMACKDFLabel, nil)
- if err != nil {
- return nil, xerrors.Errorf("cannot instantiate DRBG: %w", err)
- }
-
- alg := d.data.AuthorizedSnapModels.alg
- if alg == nilHash {
- return nil, errors.New("invalid digest algorithm")
- }
-
- hmacKey := make([]byte, alg.Size())
- if _, err := rng.Read(hmacKey); err != nil {
- return nil, xerrors.Errorf("cannot derive key: %w", err)
- }
-
- return hmacKey, nil
-}
-
-func (d *KeyData) snapModelAuthKey(auxKey PrimaryKey) ([]byte, error) {
- kdfAlg := d.data.AuthorizedSnapModels.kdfAlg
- if kdfAlg == nilHash {
- return d.snapModelAuthKeyLegacy(auxKey)
- }
- if !kdfAlg.Available() {
- return nil, errors.New("invalid KDF digest algorithm")
- }
-
- alg := d.data.AuthorizedSnapModels.alg
- if alg == nilHash {
- return nil, errors.New("invalid digest algorithm")
- }
-
- r := hkdf.Expand(func() hash.Hash { return kdfAlg.New() }, auxKey, snapModelHMACKDFLabel)
-
- // Derive a key with a length matching the output size of the
- // algorithm used for the HMAC.
- hmacKey := make([]byte, alg.Size())
- if _, err := io.ReadFull(r, hmacKey); err != nil {
- return nil, err
- }
-
- return hmacKey, nil
-}
-
func (d *KeyData) derivePassphraseKeys(passphrase string, kdf KDF) (key, iv, auth []byte, err error) {
if d.data.PassphraseParams == nil {
return nil, nil, nil, errors.New("no passphrase params")
@@ -720,6 +566,10 @@ func (d *KeyData) AuthMode() (out AuthMode) {
}
}
+func (d *KeyData) Role() string {
+ return d.data.Role
+}
+
// UnmarshalPlatformHandle unmarshals the JSON platform handle payload into the
// supplied handle, which must be a non-nil pointer.
func (d *KeyData) UnmarshalPlatformHandle(handle interface{}) error {
@@ -800,76 +650,6 @@ func (d *KeyData) RecoverKeysWithPassphrase(passphrase string, kdf KDF) (DiskUnl
return d.recoverKeysCommon(c)
}
-// IsSnapModelAuthorized indicates whether the supplied Snap device model is trusted to
-// access the data on the encrypted volume protected by this key data.
-//
-// The supplied auxKey is obtained using one of the RecoverKeys* functions.
-func (d *KeyData) IsSnapModelAuthorized(auxKey PrimaryKey, model SnapModel) (bool, error) {
- hmacKey, err := d.snapModelAuthKey(auxKey)
- if err != nil {
- return false, xerrors.Errorf("cannot obtain auth key: %w", err)
- }
-
- alg := d.data.AuthorizedSnapModels.alg
- if !alg.Available() {
- return false, errors.New("invalid digest algorithm")
- }
-
- h, err := computeSnapModelHMAC(crypto.Hash(alg), hmacKey, model)
- if err != nil {
- return false, xerrors.Errorf("cannot compute HMAC of model: %w", err)
- }
-
- return d.data.AuthorizedSnapModels.hmacs.contains(h), nil
-}
-
-// SetAuthorizedSnapModels marks the supplied Snap device models as trusted to access
-// the data on the encrypted volume protected by this key data. This function replaces all
-// previously trusted models.
-//
-// This makes changes to the key data, which will need to persisted afterwards using
-// WriteAtomic.
-//
-// The supplied auxKey is obtained using one of the RecoverKeys* functions. If the
-// supplied auxKey is incorrect, then an error will be returned.
-func (d *KeyData) SetAuthorizedSnapModels(auxKey PrimaryKey, models ...SnapModel) error {
- hmacKey, err := d.snapModelAuthKey(auxKey)
- if err != nil {
- return xerrors.Errorf("cannot obtain auth key: %w", err)
- }
-
- alg := d.data.AuthorizedSnapModels.keyDigest.Alg
- if !alg.Available() {
- return errors.New("invalid digest algorithm")
- }
-
- h := alg.New()
- h.Write(hmacKey)
- h.Write(d.data.AuthorizedSnapModels.keyDigest.Salt)
- if !bytes.Equal(h.Sum(nil), d.data.AuthorizedSnapModels.keyDigest.Digest) {
- return errors.New("incorrect key supplied")
- }
-
- alg = d.data.AuthorizedSnapModels.alg
- if !alg.Available() {
- return errors.New("invalid digest algorithm")
- }
-
- var modelHMACs snapModelHMACList
-
- for _, model := range models {
- h, err := computeSnapModelHMAC(crypto.Hash(alg), hmacKey, model)
- if err != nil {
- return xerrors.Errorf("cannot compute HMAC of model: %w", err)
- }
-
- modelHMACs = append(modelHMACs, h)
- }
-
- d.data.AuthorizedSnapModels.hmacs = modelHMACs
- return nil
-}
-
// ChangePassphrase updates the passphrase used to recover the keys from this key data
// via the KeyData.RecoverKeysWithPassphrase API. This can only be called if a passhphrase
// has been set previously (KeyData.AuthMode returns AuthModePassphrase).
@@ -933,35 +713,17 @@ func NewKeyData(params *KeyParams) (*KeyData, error) {
return nil, xerrors.Errorf("cannot encode platform handle: %w", err)
}
- var salt [32]byte
- if _, err := rand.Read(salt[:]); err != nil {
- return nil, xerrors.Errorf("cannot read salt: %w", err)
- }
-
kd := &KeyData{
data: keyData{
Generation: keyDataGeneration,
PlatformName: params.PlatformName,
+ Role: params.Role,
PlatformHandle: json.RawMessage(encodedHandle),
KDFAlg: hashAlg(params.KDFAlg),
EncryptedPayload: params.EncryptedPayload,
- AuthorizedSnapModels: authorizedSnapModels{
- alg: hashAlg(params.SnapModelAuthHash),
- kdfAlg: hashAlg(params.SnapModelAuthHash),
- keyDigest: keyDigest{
- Alg: hashAlg(params.SnapModelAuthHash),
- Salt: salt[:]}}}}
-
- authKey, err := kd.snapModelAuthKey(params.PrimaryKey)
- if err != nil {
- return nil, xerrors.Errorf("cannot compute snap model auth key: %w", err)
+ },
}
- h := kd.data.AuthorizedSnapModels.keyDigest.Alg.New()
- h.Write(authKey)
- h.Write(kd.data.AuthorizedSnapModels.keyDigest.Salt)
- kd.data.AuthorizedSnapModels.keyDigest.Digest = h.Sum(nil)
-
return kd, nil
}
diff --git a/keydata_file_test.go b/keydata_file_test.go
index 0792139d..83cffe51 100644
--- a/keydata_file_test.go
+++ b/keydata_file_test.go
@@ -26,7 +26,6 @@ import (
"path/filepath"
. "github.com/snapcore/secboot"
- "github.com/snapcore/secboot/internal/testutil"
"golang.org/x/sys/unix"
@@ -99,24 +98,6 @@ func (s *keyDataFileSuite) TestReader(c *C) {
keyData, err := NewKeyData(protected)
c.Assert(err, IsNil)
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
-
expectedId, err := keyData.UniqueID()
c.Check(err, IsNil)
@@ -141,8 +122,4 @@ func (s *keyDataFileSuite) TestReader(c *C) {
c.Check(err, IsNil)
c.Check(recoveredUnlockKey, DeepEquals, unlockKey)
c.Check(recoveredPrimaryKey, DeepEquals, primaryKey)
-
- authorized, err := keyData.IsSnapModelAuthorized(recoveredPrimaryKey, models[0])
- c.Check(err, IsNil)
- c.Check(authorized, testutil.IsTrue)
}
diff --git a/keydata_legacy.go b/keydata_legacy.go
index ff585ba5..40b1ce3c 100644
--- a/keydata_legacy.go
+++ b/keydata_legacy.go
@@ -21,11 +21,24 @@ package secboot
import (
"bytes"
+ "crypto"
"encoding/binary"
+ "encoding/json"
+ "errors"
"fmt"
+ "hash"
+ "io"
+
+ drbg "github.com/canonical/go-sp800.90a-drbg"
+ "golang.org/x/crypto/hkdf"
+ "golang.org/x/xerrors"
+)
+
+var (
+ snapModelHMACKDFLabel = []byte("SNAP-MODEL-HMAC")
)
-func unmarshalV1KeyPayload(data []byte) (unlockKey DiskUnlockKey, primaryKey PrimaryKey, err error) {
+func unmarshalV1KeyPayload(data []byte) (unlockKey DiskUnlockKey, auxKey PrimaryKey, err error) {
r := bytes.NewReader(data)
var sz uint16
@@ -45,8 +58,8 @@ func unmarshalV1KeyPayload(data []byte) (unlockKey DiskUnlockKey, primaryKey Pri
}
if sz > 0 {
- primaryKey = make(PrimaryKey, sz)
- if _, err := r.Read(primaryKey); err != nil {
+ auxKey = make(PrimaryKey, sz)
+ if _, err := r.Read(auxKey); err != nil {
return nil, nil, err
}
}
@@ -55,5 +68,254 @@ func unmarshalV1KeyPayload(data []byte) (unlockKey DiskUnlockKey, primaryKey Pri
return nil, nil, fmt.Errorf("%v excess byte(s)", r.Len())
}
- return unlockKey, primaryKey, nil
+ return unlockKey, auxKey, nil
+}
+
+type snapModelHMAC []byte
+
+type snapModelHMACList []snapModelHMAC
+
+func (l snapModelHMACList) contains(h snapModelHMAC) bool {
+ for _, v := range l {
+ if bytes.Equal(v, h) {
+ return true
+ }
+ }
+ return false
+}
+
+type authorizedSnapModelsRaw struct {
+ Alg hashAlg `json:"alg"`
+ KDFAlg hashAlg `json:"kdf_alg,omitempty"`
+ KeyDigest json.RawMessage `json:"key_digest"`
+ Hmacs snapModelHMACList `json:"hmacs"`
+}
+
+// keyDigest contains a salted digest to verify the correctness of a key.
+type keyDigest struct {
+ Alg hashAlg `json:"alg"`
+ Salt []byte `json:"salt"`
+ Digest []byte `json:"digest"`
+}
+
+// authorizedSnapModels defines the Snap models that have been
+// authorized to access the data protected by a key.
+type authorizedSnapModels struct {
+ alg hashAlg // Digest algorithm used for the authorized model HMACs
+ kdfAlg hashAlg // Digest algorithm used to derive the HMAC key with HKDF. Zero for legacy (DRBG) derivation.
+ keyDigest keyDigest // information used to validate the correctness of the HMAC key
+ hmacs snapModelHMACList // the list of HMACs of authorized models
+
+ // legacyKeyDigest is true when keyDigest should be marshalled
+ // as a plain key rather than a keyDigest object.
+ legacyKeyDigest bool
+}
+
+// MarshalJSON implements custom marshalling to handle older key data
+// objects where the key_digest field was just a base64 encoded key.
+func (m authorizedSnapModels) MarshalJSON() ([]byte, error) {
+ var digest json.RawMessage
+ var err error
+ if m.legacyKeyDigest {
+ digest, err = json.Marshal(m.keyDigest.Digest)
+ } else {
+ digest, err = json.Marshal(&m.keyDigest)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return json.Marshal(&authorizedSnapModelsRaw{
+ Alg: m.alg,
+ KDFAlg: m.kdfAlg,
+ KeyDigest: digest,
+ Hmacs: m.hmacs})
+}
+
+// UnmarshalJSON implements custom unmarshalling to handle older key data
+// objects where the key_digest field was just a base64 encoded key.
+func (m *authorizedSnapModels) UnmarshalJSON(b []byte) error {
+ var raw authorizedSnapModelsRaw
+ if err := json.Unmarshal(b, &raw); err != nil {
+ return err
+ }
+
+ *m = authorizedSnapModels{
+ alg: raw.Alg,
+ kdfAlg: raw.KDFAlg,
+ hmacs: raw.Hmacs}
+
+ token, err := json.NewDecoder(bytes.NewReader(raw.KeyDigest)).Token()
+ switch {
+ case err == io.EOF:
+ // Empty field, ignore
+ return nil
+ case err != nil:
+ return err
+ }
+
+ switch t := token.(type) {
+ case json.Delim:
+ // Newer data, where the KeyDigest field is an object.
+ if t != '{' {
+ return fmt.Errorf("invalid delim (%v) at start of key_digest field", t)
+ }
+ if err := json.Unmarshal(raw.KeyDigest, &m.keyDigest); err != nil {
+ return err
+ }
+ case string:
+ // Older data, where the KeyDigest field was a base64 encoded key.
+ // Convert it to an object.
+ _ = t
+ m.keyDigest.Alg = raw.Alg
+ m.legacyKeyDigest = true
+ if err := json.Unmarshal(raw.KeyDigest, &m.keyDigest.Digest); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("invalid token (%v) at start of key_digest field", token)
+ }
+
+ return nil
+}
+
+func (d *KeyData) snapModelHMACKeyLegacy(key PrimaryKey) ([]byte, error) {
+ if d.data.AuthorizedSnapModels == nil {
+ return nil, errors.New("no authorized_snap_models")
+ }
+ alg := d.data.AuthorizedSnapModels.alg
+ if alg == nilHash {
+ return nil, errors.New("invalid digest algorithm")
+ }
+
+ rng, err := drbg.NewCTRWithExternalEntropy(32, key, nil, snapModelHMACKDFLabel, nil)
+ if err != nil {
+ return nil, xerrors.Errorf("cannot instantiate DRBG: %w", err)
+ }
+
+ hmacKey := make([]byte, alg.Size())
+ if _, err := rng.Read(hmacKey); err != nil {
+ return nil, xerrors.Errorf("cannot derive key: %w", err)
+ }
+
+ return hmacKey, nil
+}
+
+func (d *KeyData) snapModelHMACKey(key PrimaryKey) ([]byte, error) {
+ if d.data.AuthorizedSnapModels == nil {
+ return nil, errors.New("no authorized_snap_models")
+ }
+ kdfAlg := d.data.AuthorizedSnapModels.kdfAlg
+ if kdfAlg == nilHash {
+ return d.snapModelHMACKeyLegacy(key)
+ }
+ if !kdfAlg.Available() {
+ return nil, errors.New("invalid KDF digest algorithm")
+ }
+
+ alg := d.data.AuthorizedSnapModels.alg
+ if alg == nilHash {
+ return nil, errors.New("invalid digest algorithm")
+ }
+
+ r := hkdf.Expand(func() hash.Hash { return kdfAlg.New() }, key, snapModelHMACKDFLabel)
+
+ // Derive a key with a length matching the output size of the
+ // algorithm used for the HMAC.
+ hmacKey := make([]byte, alg.Size())
+ if _, err := io.ReadFull(r, hmacKey); err != nil {
+ return nil, err
+ }
+
+ return hmacKey, nil
+}
+
+// IsSnapModelAuthorized indicates whether the supplied Snap device model is trusted to
+// access the data on the encrypted volume protected by this key data.
+//
+// The supplied key is obtained using one of the RecoverKeys* functions.
+//
+// This is deprecated where [Generation] returns greater than 1, and will return an error.
+// The value returned by [Generation] is indirectly protected because its used to decide
+// how to decode the payload returned by [RecoverKeys].
+func (d *KeyData) IsSnapModelAuthorized(key PrimaryKey, model SnapModel) (bool, error) {
+ switch d.Generation() {
+ case 1:
+ hmacKey, err := d.snapModelHMACKey(key)
+ if err != nil {
+ return false, xerrors.Errorf("cannot obtain auth key: %w", err)
+ }
+
+ alg := d.data.AuthorizedSnapModels.alg
+ if !alg.Available() {
+ return false, errors.New("invalid digest algorithm")
+ }
+
+ h, err := computeSnapModelHMAC(crypto.Hash(alg), hmacKey, model)
+ if err != nil {
+ return false, xerrors.Errorf("cannot compute HMAC of model: %w", err)
+ }
+
+ return d.data.AuthorizedSnapModels.hmacs.contains(h), nil
+ case 2:
+ return false, errors.New("unsupported key data generation number")
+ default:
+ return false, fmt.Errorf("invalid keydata generation number %d", d.Generation())
+ }
+}
+
+// SetAuthorizedSnapModels marks the supplied Snap device models as trusted to access
+// the data on the encrypted volume protected by this key data. This function replaces all
+// previously trusted models.
+//
+// This makes changes to the key data, which will need to persisted afterwards using
+// WriteAtomic.
+//
+// The supplied key is obtained using one of the RecoverKeys* functions. If the
+// supplied auxKey is incorrect, then an error will be returned.
+//
+// This is deprecated where [Generation] returns greater than 1, and will return an error.
+func (d *KeyData) SetAuthorizedSnapModels(key PrimaryKey, models ...SnapModel) (err error) {
+ switch d.Generation() {
+ case 1:
+ hmacKey, err := d.snapModelHMACKey(key)
+ if err != nil {
+ return xerrors.Errorf("cannot obtain auth key: %w", err)
+ }
+
+ alg := d.data.AuthorizedSnapModels.keyDigest.Alg
+ if !alg.Available() {
+ return errors.New("invalid digest algorithm")
+ }
+
+ h := alg.New()
+ h.Write(hmacKey)
+ h.Write(d.data.AuthorizedSnapModels.keyDigest.Salt)
+ if !bytes.Equal(h.Sum(nil), d.data.AuthorizedSnapModels.keyDigest.Digest) {
+ return errors.New("incorrect key supplied")
+ }
+
+ alg = d.data.AuthorizedSnapModels.alg
+ if !alg.Available() {
+ return errors.New("invalid digest algorithm")
+ }
+
+ var modelHMACs snapModelHMACList
+
+ for _, model := range models {
+ h, err := computeSnapModelHMAC(crypto.Hash(alg), hmacKey, model)
+ if err != nil {
+ return xerrors.Errorf("cannot compute HMAC of model: %w", err)
+ }
+
+ modelHMACs = append(modelHMACs, h)
+ }
+
+ d.data.AuthorizedSnapModels.hmacs = modelHMACs
+ return nil
+ case 2:
+ return errors.New("unsupported key data generation number")
+ default:
+ return fmt.Errorf("invalid keydata generation number %d", d.Generation())
+ }
}
diff --git a/keydata_legacy_test.go b/keydata_legacy_test.go
index 39d58bf7..dcf2b114 100644
--- a/keydata_legacy_test.go
+++ b/keydata_legacy_test.go
@@ -64,11 +64,9 @@ func (s *keyDataLegacyTestBase) mockProtectKeys(c *C, key DiskUnlockKey, auxKey
stream := cipher.NewCFBEncrypter(b, handle.IV)
out = &KeyParams{
- PlatformName: s.mockPlatformName,
- Handle: &handle,
- EncryptedPayload: make([]byte, len(payload)),
- PrimaryKey: auxKey,
- SnapModelAuthHash: modelAuthHash}
+ PlatformName: s.mockPlatformName,
+ Handle: &handle,
+ EncryptedPayload: make([]byte, len(payload))}
stream.XORKeyStream(out.EncryptedPayload, payload)
return
}
diff --git a/keydata_luks_test.go b/keydata_luks_test.go
index 97a89c53..54ce2411 100644
--- a/keydata_luks_test.go
+++ b/keydata_luks_test.go
@@ -248,24 +248,6 @@ func (s *keyDataLuksSuite) testReader(c *C, data *testKeyDataLuksReaderData) {
keyData, err := NewKeyData(protected)
c.Assert(err, IsNil)
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
-
expectedId, err := keyData.UniqueID()
c.Check(err, IsNil)
@@ -295,10 +277,6 @@ func (s *keyDataLuksSuite) testReader(c *C, data *testKeyDataLuksReaderData) {
c.Check(err, IsNil)
c.Check(recoveredUnlockKey, DeepEquals, unlockKey)
c.Check(recoveredPrimaryKey, DeepEquals, primaryKey)
-
- authorized, err := keyData.IsSnapModelAuthorized(recoveredPrimaryKey, models[0])
- c.Check(err, IsNil)
- c.Check(authorized, testutil.IsTrue)
}
func (s *keyDataLuksSuite) TestReader(c *C) {
diff --git a/keydata_test.go b/keydata_test.go
index 44d57441..ce4bb163 100644
--- a/keydata_test.go
+++ b/keydata_test.go
@@ -304,12 +304,10 @@ func (s *keyDataTestBase) mockProtectKeys(c *C, primaryKey PrimaryKey, kdfAlg cr
stream := cipher.NewCFBEncrypter(b, handle.IV)
out = &KeyParams{
- PlatformName: s.mockPlatformName,
- Handle: &handle,
- EncryptedPayload: make([]byte, len(payload)),
- PrimaryKey: primaryKey,
- KDFAlg: kdfAlg,
- SnapModelAuthHash: modelAuthHash}
+ PlatformName: s.mockPlatformName,
+ Handle: &handle,
+ EncryptedPayload: make([]byte, len(payload)),
+ KDFAlg: kdfAlg}
stream.XORKeyStream(out.EncryptedPayload, payload)
return out, unlockKey
@@ -339,33 +337,14 @@ func (s *keyDataTestBase) mockProtectKeysWithPassphrase(c *C, primaryKey Primary
return kpp, unlockKey
}
-func (s *keyDataTestBase) checkKeyDataJSONCommon(c *C, j map[string]interface{}, creationParams *KeyParams, nmodels int) {
- c.Check(j["platform_name"], Equals, creationParams.PlatformName)
-
- expectedHandle, ok := creationParams.Handle.(*mockPlatformKeyDataHandle)
- c.Assert(ok, testutil.IsTrue)
-
- handleBytes, err := json.Marshal(j["platform_handle"])
- c.Check(err, IsNil)
-
- var handle *mockPlatformKeyDataHandle
- c.Check(json.Unmarshal(handleBytes, &handle), IsNil)
-
- c.Check(handle.Key, DeepEquals, expectedHandle.Key)
- c.Check(handle.IV, DeepEquals, expectedHandle.IV)
-
- _, ok = j["kdf_alg"].(string)
- c.Check(ok, testutil.IsTrue)
-
- generation, ok := j["generation"].(float64)
- c.Check(ok, testutil.IsTrue)
- c.Check(generation, Equals, float64(2))
+func (s *keyDataTestBase) checkKeyDataJSONDecodedLegacyFields(c *C, j map[string]interface{}, creationParams *KeyParams, nmodels int) {
+ snapModelAuthHash := crypto.SHA256
m, ok := j["authorized_snap_models"].(map[string]interface{})
c.Assert(ok, testutil.IsTrue)
h := toHash(c, m["alg"])
- c.Check(h, Equals, creationParams.SnapModelAuthHash)
+ c.Check(h, Equals, snapModelAuthHash)
c.Check(m, testutil.HasKey, "hmacs")
if nmodels == 0 {
@@ -384,13 +363,13 @@ func (s *keyDataTestBase) checkKeyDataJSONCommon(c *C, j map[string]interface{},
}
h = toHash(c, m["kdf_alg"])
- c.Check(h, Equals, creationParams.SnapModelAuthHash)
+ c.Check(h, Equals, snapModelAuthHash)
m1, ok := m["key_digest"].(map[string]interface{})
c.Assert(ok, testutil.IsTrue)
h = toHash(c, m1["alg"])
- c.Check(h, Equals, creationParams.SnapModelAuthHash)
+ c.Check(h, Equals, snapModelAuthHash)
str, ok := m1["salt"].(string)
c.Check(ok, testutil.IsTrue)
@@ -405,6 +384,29 @@ func (s *keyDataTestBase) checkKeyDataJSONCommon(c *C, j map[string]interface{},
c.Check(digest, HasLen, h.Size())
}
+func (s *keyDataTestBase) checkKeyDataJSONCommon(c *C, j map[string]interface{}, creationParams *KeyParams, nmodels int) {
+ c.Check(j["platform_name"], Equals, creationParams.PlatformName)
+
+ expectedHandle, ok := creationParams.Handle.(*mockPlatformKeyDataHandle)
+ c.Assert(ok, testutil.IsTrue)
+
+ handleBytes, err := json.Marshal(j["platform_handle"])
+ c.Check(err, IsNil)
+
+ var handle *mockPlatformKeyDataHandle
+ c.Check(json.Unmarshal(handleBytes, &handle), IsNil)
+
+ c.Check(handle.Key, DeepEquals, expectedHandle.Key)
+ c.Check(handle.IV, DeepEquals, expectedHandle.IV)
+
+ _, ok = j["kdf_alg"].(string)
+ c.Check(ok, testutil.IsTrue)
+
+ generation, ok := j["generation"].(float64)
+ c.Check(ok, testutil.IsTrue)
+ c.Check(generation, Equals, float64(expectedHandle.ExpectedGeneration))
+}
+
func (s *keyDataTestBase) checkKeyDataJSONDecodedAuthModeNone(c *C, j map[string]interface{}, creationParams *KeyParams, nmodels int) {
s.checkKeyDataJSONCommon(c, j, creationParams, nmodels)
@@ -728,8 +730,10 @@ func (s *keyDataSuite) TestRecoverKeys(c *C) {
keyData, err := NewKeyData(protected)
c.Assert(err, IsNil)
+
recoveredUnlockKey, recoveredPrimaryKey, err := keyData.RecoverKeys()
- c.Check(err, IsNil)
+ c.Assert(err, IsNil)
+
c.Check(recoveredUnlockKey, DeepEquals, unlockKey)
c.Check(recoveredPrimaryKey, DeepEquals, primaryKey)
}
@@ -901,6 +905,18 @@ func (s *keyDataSuite) TestRecoverKeysWithPassphraseUnavailableKDF(c *C) {
errMsg: fmt.Sprintf("unavailable leaf KDF digest algorithm %d", crypto.SHA256),
})
}
+func (s *keyDataSuite) TestRecoverKeysWithPassphraseAuthModeNone(c *C) {
+ // Test that RecoverKeyWithPassphrase for a key without a passphrase set fails
+ auxKey := s.newPrimaryKey(c, 32)
+ protected, _ := s.mockProtectKeys(c, auxKey, crypto.SHA256, crypto.SHA256)
+
+ keyData, err := NewKeyData(protected)
+ c.Assert(err, IsNil)
+ recoveredKey, recoveredAuxKey, err := keyData.RecoverKeysWithPassphrase("", nil)
+ c.Check(err, ErrorMatches, "cannot recover key with passphrase")
+ c.Check(recoveredKey, IsNil)
+ c.Check(recoveredAuxKey, IsNil)
+}
func (s *keyDataSuite) TestNewKeyDataWithPassphraseNotSupported(c *C) {
// Test that creation of a new key data with passphrase fails when the
@@ -1067,19 +1083,304 @@ func (s *keyDataSuite) TestChangePassphraseWrongPassphrase(c *C) {
s.checkKeyDataJSONAuthModePassphrase(c, keyData, protected, 0, "12345678", kdfOptions)
}
-type testSnapModelAuthData struct {
- alg crypto.Hash
- authModels []SnapModel
+type testWriteAtomicData struct {
+ keyData *KeyData
+ params *KeyParams
+ nmodels int
+}
+
+func (s *keyDataSuite) testWriteAtomic(c *C, data *testWriteAtomicData) {
+ s.checkKeyDataJSONAuthModeNone(c, data.keyData, data.params, data.nmodels)
+}
+
+func (s *keyDataSuite) TestWriteAtomic1(c *C) {
+ primaryKey := s.newPrimaryKey(c, 32)
+ protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+
+ keyData, err := NewKeyData(protected)
+ c.Assert(err, IsNil)
+
+ s.testWriteAtomic(c, &testWriteAtomicData{
+ keyData: keyData,
+ params: protected})
+}
+
+type testReadKeyDataData struct {
+ unlockKey DiskUnlockKey
+ primaryKey PrimaryKey
+ id KeyID
+ r KeyDataReader
model SnapModel
authorized bool
}
-func (s *keyDataSuite) testSnapModelAuth(c *C, data *testSnapModelAuthData) {
+func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) {
+ keyData, err := ReadKeyData(data.r)
+ c.Assert(err, IsNil)
+ c.Check(keyData.ReadableName(), Equals, data.r.ReadableName())
+
+ id, err := keyData.UniqueID()
+ c.Check(err, IsNil)
+ c.Check(id, DeepEquals, data.id)
+
+ unlockKey, primaryKey, err := keyData.RecoverKeys()
+ c.Check(err, IsNil)
+ c.Check(unlockKey, DeepEquals, data.unlockKey)
+ c.Check(primaryKey, DeepEquals, data.primaryKey)
+}
+
+func (s *keyDataSuite) TestReadKeyData1(c *C) {
primaryKey := s.newPrimaryKey(c, 32)
- protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+ protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+
+ keyData, err := NewKeyData(protected)
+ c.Assert(err, IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData.WriteAtomic(w), IsNil)
+
+ id, err := keyData.UniqueID()
+ c.Check(err, IsNil)
+
+ s.testReadKeyData(c, &testReadKeyDataData{
+ unlockKey: unlockKey,
+ primaryKey: primaryKey,
+ id: id,
+ r: &mockKeyDataReader{"foo", w.Reader()},
+ })
+}
+
+func (s *keyDataSuite) TestReadKeyData2(c *C) {
+ primaryKey := s.newPrimaryKey(c, 32)
+ protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+
+ keyData, err := NewKeyData(protected)
+ c.Assert(err, IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData.WriteAtomic(w), IsNil)
+
+ id, err := keyData.UniqueID()
+ c.Check(err, IsNil)
+
+ s.testReadKeyData(c, &testReadKeyDataData{
+ unlockKey: unlockKey,
+ primaryKey: primaryKey,
+ id: id,
+ r: &mockKeyDataReader{"bar", w.Reader()},
+ })
+}
+
+func (s *keyDataSuite) TestReadKeyData3(c *C) {
+ primaryKey := s.newPrimaryKey(c, 32)
+ protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
keyData, err := NewKeyData(protected)
c.Assert(err, IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData.WriteAtomic(w), IsNil)
+
+ id, err := keyData.UniqueID()
+ c.Check(err, IsNil)
+
+ params := &testReadKeyDataData{
+ unlockKey: unlockKey,
+ primaryKey: primaryKey,
+ id: id,
+ r: &mockKeyDataReader{"foo", w.Reader()},
+ }
+
+ s.testReadKeyData(c, params)
+}
+
+func (s *keyDataSuite) TestReadKeyData4(c *C) {
+ primaryKey := s.newPrimaryKey(c, 32)
+ protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+
+ keyData, err := NewKeyData(protected)
+ c.Assert(err, IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData.WriteAtomic(w), IsNil)
+
+ id, err := keyData.UniqueID()
+ c.Check(err, IsNil)
+
+ params := &testReadKeyDataData{
+ unlockKey: unlockKey,
+ primaryKey: primaryKey,
+ id: id,
+ r: &mockKeyDataReader{"foo", w.Reader()},
+ }
+
+ s.testReadKeyData(c, params)
+}
+
+func (s *keyDataSuite) TestMakeDiskUnlockKey(c *C) {
+ primaryKey := testutil.DecodeHexString(c, "1850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
+ kdfAlg := crypto.SHA256
+ unique := testutil.DecodeHexString(c, "1850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
+
+ unlockKey, clearTextPayload, err := MakeDiskUnlockKey(bytes.NewReader(unique), kdfAlg, primaryKey)
+ c.Assert(err, IsNil)
+
+ knownGoodUnlockKey := testutil.DecodeHexString(c, "8b78ddabd8e38a6513e654638c0f7b8c738d5461a403564d19d98e7f8ed469cb")
+ c.Check(unlockKey, DeepEquals, DiskUnlockKey(knownGoodUnlockKey))
+
+ knownGoodPayload := testutil.DecodeHexString(c, "304404201850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b8604201850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
+ c.Check(clearTextPayload, DeepEquals, knownGoodPayload)
+
+ st := cryptobyte.String(clearTextPayload)
+ c.Assert(st.ReadASN1(&st, cryptobyte_asn1.SEQUENCE), Equals, true)
+
+ var p PrimaryKey
+ c.Assert(st.ReadASN1Bytes((*[]byte)(&p), cryptobyte_asn1.OCTET_STRING), Equals, true)
+ c.Check(p, DeepEquals, PrimaryKey(primaryKey))
+
+ var u []byte
+ c.Assert(st.ReadASN1Bytes(&u, cryptobyte_asn1.OCTET_STRING), Equals, true)
+ c.Check(u, DeepEquals, unique)
+}
+
+// Legacy tests
+func (s *keyDataSuite) testLegacyWriteAtomic(c *C, data *testWriteAtomicData) {
+ w := makeMockKeyDataWriter()
+ c.Check(data.keyData.WriteAtomic(w), IsNil)
+
+ var j map[string]interface{}
+
+ d := json.NewDecoder(w.Reader())
+ c.Check(d.Decode(&j), IsNil)
+
+ s.checkKeyDataJSONDecodedAuthModeNone(c, j, data.params, data.nmodels)
+ s.checkKeyDataJSONDecodedLegacyFields(c, j, data.params, data.nmodels)
+}
+
+func (s *keyDataSuite) TestLegacyWriteAtomic1(c *C) {
+ key, err := base64.StdEncoding.DecodeString("O+AgNjD0LZWfVfwrnicZLedbsJVSySR2HMr3dAPrdX0=")
+ c.Assert(err, IsNil)
+ iv, err := base64.StdEncoding.DecodeString("BIVYsrYcNuNzuMouhgi4YA==")
+ c.Assert(err, IsNil)
+
+ handle := mockPlatformKeyDataHandle{
+ Key: key,
+ IV: iv,
+ ExpectedGeneration: 1,
+ ExpectedKDFAlg: crypto.SHA256,
+ ExpectedAuthMode: AuthModeNone,
+ }
+
+ encPayload, err := base64.StdEncoding.DecodeString("tb68rp1ruzrFuuas86Nv8/Q9PzsxCt3brGRQNaArY8sFiUXz20oFHPyHa13Cz00NOZL04fD/1RSYvBcHUF/xOFe2SoA=")
+ c.Assert(err, IsNil)
+
+ protected := &KeyParams{
+ PlatformName: s.mockPlatformName,
+ Handle: &handle,
+ EncryptedPayload: encPayload,
+ KDFAlg: crypto.SHA256}
+
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"O+AgNjD0LZWfVfwrnicZLedbsJVSySR2HMr3dAPrdX0=",` +
+ `"iv":"BIVYsrYcNuNzuMouhgi4YA==",` +
+ `"auth-key-hmac":"DoOW+jLaY8T3eKGKvz3c125oRIpXGC2T7B0KWYzoajQ=",` +
+ `"exp-generation":1,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"tb68rp1ruzrFuuas86Nv8/Q9PzsxCt3brGRQNaArY8sFiUXz20oFHPyHa13Cz00NOZL04fD/1RSYvBcHUF/xOFe2SoA=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"dkyuuVDN7/b0IJ9bqrnFZstrA0ctFuOCVrbErt2PPnM=",` +
+ `"digest":"sqSEkBclP9uIxT/vyCh8+gNByfhwN618j+Y8G3GgTqM="},` +
+ `"hmacs":null}}
+`)
+
+ keyData, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ s.testLegacyWriteAtomic(c, &testWriteAtomicData{
+ keyData: keyData,
+ params: protected})
+}
+
+func (s *keyDataSuite) TestLegacyKeyPayloadUnmarshalInvalid1(c *C) {
+ payload := make([]byte, 66)
+ for i := range payload {
+ payload[i] = 0xff
+ }
+
+ key, auxKey, err := UnmarshalV1KeyPayload(payload)
+ c.Check(err, ErrorMatches, "EOF")
+ c.Check(key, IsNil)
+ c.Check(auxKey, IsNil)
+}
+
+func (s *keyDataSuite) TestLegacyKeyPayloadUnmarshalInvalid2(c *C) {
+ payload := MarshalV1Keys(make(DiskUnlockKey, 32), make(PrimaryKey, 32))
+ payload = append(payload, 0xff)
+
+ key, auxKey, err := UnmarshalV1KeyPayload(payload)
+ c.Check(err, ErrorMatches, "1 excess byte\\(s\\)")
+ c.Check(key, IsNil)
+ c.Check(auxKey, IsNil)
+ return
+}
+
+type testLegacySnapModelAuthData struct {
+ alg crypto.Hash
+ authModels []SnapModel
+ model SnapModel
+ authorized bool
+}
+
+func (s *keyDataSuite) testLegacySnapModelAuth(c *C, data *testLegacySnapModelAuthData) {
+
+ primaryKey := testutil.DecodeHexString(c, "cc4b23dbdd28fdabf80af71a68e50458621c632340978b08bd3b645f25e1b8c0")
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"i7hWLt1p+iyBQOd/edg9qhC/8ylr4rYjkmqAYp5QSRk=",` +
+ `"iv":"2+7pAYQIphbVAbbhegQJ7g==",` +
+ `"auth-key-hmac":"EGPHpICORhpYywUDL31U19TWKRw0PQrgNuWCmDzjIfw=",` +
+ `"exp-generation":2,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"mlcscFyWBjFHb3G2zxg1j4PZb/FGG2jxD9Vqu+Ds5qK4MIIYBq055ISCjI++evAkbWEp9+gqGW0mzu+c+hrQaGbd33w=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"uxHxz5z0cOBF/kIwI7TuJ+eGU6uwSHdW5VZjNpj+eE4=",` +
+ `"digest":"uUfn0pt0h1jh4/Iel6UjRaH+aXwPCEKeA7Mac1B0Jdo="},` +
+ `"hmacs":null}}
+ `)
+
+ keyData, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
+ c.Assert(err, IsNil)
+
+ w := makeMockKeyDataWriter()
+ c.Check(keyData.WriteAtomic(w), IsNil)
+
c.Check(keyData.SetAuthorizedSnapModels(primaryKey, data.authModels...), IsNil)
authorized, err := keyData.IsSnapModelAuthorized(primaryKey, data.model)
@@ -1087,7 +1388,7 @@ func (s *keyDataSuite) testSnapModelAuth(c *C, data *testSnapModelAuthData) {
c.Check(authorized, Equals, data.authorized)
}
-func (s *keyDataSuite) TestSnapModelAuth1(c *C) {
+func (s *keyDataSuite) TestLegacySnapModelAuth1(c *C) {
models := []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
@@ -1096,14 +1397,14 @@ func (s *keyDataSuite) TestSnapModelAuth1(c *C) {
"model": "fake-model",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA256,
authModels: models,
model: models[0],
authorized: true})
}
-func (s *keyDataSuite) TestSnapModelAuth2(c *C) {
+func (s *keyDataSuite) TestLegacySnapModelAuth2(c *C) {
models := []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
@@ -1119,15 +1420,15 @@ func (s *keyDataSuite) TestSnapModelAuth2(c *C) {
"model": "other-model",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA256,
authModels: models,
model: models[1],
authorized: true})
}
-func (s *keyDataSuite) TestSnapModelAuth3(c *C) {
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+func (s *keyDataSuite) TestLegacySnapModelAuth3(c *C) {
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA256,
authModels: []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
@@ -1147,7 +1448,7 @@ func (s *keyDataSuite) TestSnapModelAuth3(c *C) {
authorized: false})
}
-func (s *keyDataSuite) TestSnapModelAuth4(c *C) {
+func (s *keyDataSuite) TestLegacySnapModelAuth4(c *C) {
models := []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
@@ -1156,13 +1457,14 @@ func (s *keyDataSuite) TestSnapModelAuth4(c *C) {
"model": "fake-model",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA512,
authModels: models,
model: models[0],
authorized: true})
}
-func (s *keyDataSuite) TestSnapModelAuth5(c *C) {
+
+func (s *keyDataSuite) TestLegacySnapModelAuth5(c *C) {
models := []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
@@ -1182,14 +1484,14 @@ func (s *keyDataSuite) TestSnapModelAuth5(c *C) {
"distribution": "ubuntu",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA256,
authModels: models,
model: models[1],
authorized: true})
}
-func (s *keyDataSuite) TestSnapModelAuth6(c *C) {
+func (s *keyDataSuite) TestLegacySnapModelAuth6(c *C) {
models := []SnapModel{
testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
@@ -1205,7 +1507,7 @@ func (s *keyDataSuite) TestSnapModelAuth6(c *C) {
"model": "other-model",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- s.testSnapModelAuth(c, &testSnapModelAuthData{
+ s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{
alg: crypto.SHA256,
authModels: models,
model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
@@ -1220,188 +1522,48 @@ func (s *keyDataSuite) TestSnapModelAuth6(c *C) {
authorized: false})
}
-func (s *keyDataSuite) TestSetAuthorizedSnapModelsWithWrongKey(c *C) {
- primaryKey := s.newPrimaryKey(c, 32)
- protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
-
- keyData, err := NewKeyData(protected)
- c.Assert(err, IsNil)
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(make(PrimaryKey, 32), models...), ErrorMatches, "incorrect key supplied")
-}
-
-type testWriteAtomicData struct {
- keyData *KeyData
- params *KeyParams
- nmodels int
-}
-
-func (s *keyDataSuite) testWriteAtomic(c *C, data *testWriteAtomicData) {
- s.checkKeyDataJSONAuthModeNone(c, data.keyData, data.params, data.nmodels)
-}
-
-func (s *keyDataSuite) TestWriteAtomic1(c *C) {
+func (s *keyDataSuite) TestLegacySnapModelAuthErrorHandling(c *C) {
primaryKey := s.newPrimaryKey(c, 32)
protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
-
- keyData, err := NewKeyData(protected)
- c.Assert(err, IsNil)
-
- s.testWriteAtomic(c, &testWriteAtomicData{
- keyData: keyData,
- params: protected})
-}
-
-type testReadKeyDataData struct {
- key DiskUnlockKey
- auxKey PrimaryKey
- id KeyID
- r KeyDataReader
- model SnapModel
- authorized bool
-}
-
-func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) {
- keyData, err := ReadKeyData(data.r)
- c.Assert(err, IsNil)
- c.Check(keyData.ReadableName(), Equals, data.r.ReadableName())
-
- id, err := keyData.UniqueID()
- c.Check(err, IsNil)
- c.Check(id, DeepEquals, data.id)
-
- key, auxKey, err := keyData.RecoverKeys()
- c.Check(err, IsNil)
- c.Check(key, DeepEquals, data.key)
- c.Check(auxKey, DeepEquals, data.auxKey)
-
- authorized, err := keyData.IsSnapModelAuthorized(auxKey, data.model)
- c.Check(err, IsNil)
- c.Check(authorized, Equals, data.authorized)
-
- c.Check(keyData.SetAuthorizedSnapModels(auxKey), IsNil)
-}
-
-func (s *keyDataSuite) TestReadKeyData1(c *C) {
- primaryKey := s.newPrimaryKey(c, 32)
- protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
-
- keyData, err := NewKeyData(protected)
- c.Assert(err, IsNil)
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
-
- w := makeMockKeyDataWriter()
- c.Check(keyData.WriteAtomic(w), IsNil)
-
- id, err := keyData.UniqueID()
- c.Check(err, IsNil)
-
- s.testReadKeyData(c, &testReadKeyDataData{
- key: unlockKey,
- auxKey: primaryKey,
- id: id,
- r: &mockKeyDataReader{"foo", w.Reader()},
- model: models[0],
- authorized: true})
-}
-
-func (s *keyDataSuite) TestReadKeyData2(c *C) {
- primaryKey := s.newPrimaryKey(c, 32)
- protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
-
- keyData, err := NewKeyData(protected)
- c.Assert(err, IsNil)
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
-
- w := makeMockKeyDataWriter()
- c.Check(keyData.WriteAtomic(w), IsNil)
-
- id, err := keyData.UniqueID()
- c.Check(err, IsNil)
-
- s.testReadKeyData(c, &testReadKeyDataData{
- key: unlockKey,
- auxKey: primaryKey,
- id: id,
- r: &mockKeyDataReader{"bar", w.Reader()},
- model: models[0],
- authorized: true})
-}
-
-func (s *keyDataSuite) TestReadKeyData3(c *C) {
- primaryKey := s.newPrimaryKey(c, 32)
- protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
-
keyData, err := NewKeyData(protected)
- c.Assert(err, IsNil)
-
- models := []SnapModel{
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "fake-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
-
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
w := makeMockKeyDataWriter()
c.Check(keyData.WriteAtomic(w), IsNil)
- id, err := keyData.UniqueID()
- c.Check(err, IsNil)
-
- s.testReadKeyData(c, &testReadKeyDataData{
- key: unlockKey,
- auxKey: primaryKey,
- id: id,
- r: &mockKeyDataReader{"foo", w.Reader()},
- model: models[1],
- authorized: true})
+ authorized, err := keyData.IsSnapModelAuthorized(primaryKey, nil)
+ c.Check(err, ErrorMatches, "unsupported key data generation number")
+ c.Check(authorized, Equals, false)
}
-func (s *keyDataSuite) TestReadKeyData4(c *C) {
- primaryKey := s.newPrimaryKey(c, 32)
- protected, unlockKey := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256)
+func (s *keyDataSuite) TestLegacySetAuthorizedSnapModelsWithWrongKey(c *C) {
+ j := []byte(
+ `{` +
+ `"generation":1,` +
+ `"platform_name":"mock",` +
+ `"platform_handle":` +
+ `{` +
+ `"key":"csOUHfZ4qYJ5ga5fbW60bFt1HEI7C/RcHsfFZkgNUso=",` +
+ `"iv":"L1J+Z+FlAxdq2MWkxPTRYw==",` +
+ `"auth-key-hmac":"aC1HlXH/zlGEUWPpu9sehNBL7Zoz7e9RcRyrP7ph98s=",` +
+ `"exp-generation":2,` +
+ `"exp-kdf_alg":5,` +
+ `"exp-auth-mode":0},` +
+ `"role":"",` +
+ `"kdf_alg":"sha256",` +
+ `"encrypted_payload":"PBEOUTROv/kvHa8Gr4HVxJqRqrnqWqTTBQVHX9xIEYnLObXMJ7QXc/CjS5jbWFpIU88qw5NYgWawQB9ee/isXbC5F/4=",` +
+ `"authorized_snap_models":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"kdf_alg":"sha256",` +
+ `"key_digest":` +
+ `{` +
+ `"alg":"sha256",` +
+ `"salt":"AjCl21fNBTarpehNqnFdgcFmDteO6yAKd8kkw5kl7zQ=",` +
+ `"digest":"wvTQvmHAt8szska7rcF2uEo8Vb/ntIQ268wbtn8wQHs="},` +
+ `"hmacs":null}}
+`)
- keyData, err := NewKeyData(protected)
+ keyData, err := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)})
c.Assert(err, IsNil)
models := []SnapModel{
@@ -1413,27 +1575,7 @@ func (s *keyDataSuite) TestReadKeyData4(c *C) {
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")}
- c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil)
-
- w := makeMockKeyDataWriter()
- c.Check(keyData.WriteAtomic(w), IsNil)
-
- id, err := keyData.UniqueID()
- c.Check(err, IsNil)
-
- s.testReadKeyData(c, &testReadKeyDataData{
- key: unlockKey,
- auxKey: primaryKey,
- id: id,
- r: &mockKeyDataReader{"foo", w.Reader()},
- model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
- "authority-id": "fake-brand",
- "series": "16",
- "brand-id": "fake-brand",
- "model": "other-model",
- "grade": "secured",
- }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"),
- authorized: false})
+ c.Check(keyData.SetAuthorizedSnapModels(make(PrimaryKey, 32), models...), ErrorMatches, "incorrect key supplied")
}
func (s *keyDataSuite) TestKeyDataDerivePassphraseKeysExpectedInfoFields(c *C) {
@@ -1509,6 +1651,10 @@ func (s *keyDataSuite) TestReadAndWriteWithUnsaltedKeyDigest(c *C) {
`{` +
`"platform_name":"mock",` +
`"platform_handle":"iTnGw6iFTfDgGS+KMtDHx2yF0bpNaTWyzeLtsbaC9YaspcssRrHzcRsNrubyEVT9",` +
+ // The new role field will be added as "" by default during unmarshalling
+ // with ReadKeyData even if it is missing.
+ // Explicitly adding the role field here so that the test passes.
+ `"role":"",` +
`"encrypted_payload":"fYM/SYjIRZj7JOJA710c9hSsxp5NpEchEVXgozd1KgxqZ/TOzIvWF9WYSrRcXiy1vsyjhkF0Svh3ihfApzvje7tTQRI=",` +
`"authorized_snap_models":{` +
`"alg":"sha256",` +
@@ -1565,6 +1711,10 @@ func (s *keyDataSuite) TestReadAndWriteWithLegacySnapModelAuthKey(c *C) {
`"key":"u2wBdkkDL0c5ovbM9z/3VoRVy6cHMs3YdwiUL+mNl/Q=",` +
`"iv":"sXJZ9DUc26Qz5x4/FwjFzA==",` +
`"auth-key-hmac":"JVayPium5JZZrEkqb7bsiQXPWJHEhX3r0aHjByulHXs="},` +
+ // The new role field will be added as "" by default during unmarshalling
+ // with ReadKeyData even if it is missing.
+ // Explicitly adding the role field here so that the test passes.
+ `"role":"",` +
`"encrypted_payload":"eDTWEozwRLFh1td/i+eufBDIFHiYJoQqhw51jPuWAy0hfJaw22ywTau+UdqRXQTh4bTl8LZhaDpBGk3wBMjLO8Y3l4Q=",` +
`"authorized_snap_models":{` +
`"alg":"sha256",` +
@@ -1627,6 +1777,10 @@ func (s *keyDataSuite) TestLegacyKeyData(c *C) {
`"exp-generation":1,` +
`"exp-kdf_alg":0,` +
`"exp-auth-mode":0},` +
+ // The new role field will be added as "" by default during unmarshalling
+ // with ReadKeyData even if it is missing.
+ // Explicitly adding the role field here so that the test passes.
+ `"role":"",` +
`"encrypted_payload":"eMeLrknRAi/dFBM607WPxFOCE1L9RZ4xxUs+Leodz78s/id7Eq+IHhZdOC/stXSNe+Gn/PWgPxcd0TfEPUs5TA350lo=",` +
`"authorized_snap_models":{` +
`"alg":"sha256",` +
@@ -1662,6 +1816,7 @@ func (s *keyDataSuite) TestLegacyKeyData(c *C) {
c.Check(keyData.WriteAtomic(w), IsNil)
j2, err := ioutil.ReadAll(w.Reader())
+
c.Check(err, IsNil)
c.Check(j2, DeepEquals, j)
@@ -1692,6 +1847,10 @@ func (s *keyDataSuite) TestLegacyKeyData(c *C) {
`"exp-generation":1,`+
`"exp-kdf_alg":0,`+
`"exp-auth-mode":0},`+
+ // The new role field will be added as "" by default during unmarshalling
+ // with ReadKeyData even if it is missing.
+ // Explicitly adding the role field here so that the test passes.
+ `"role":"",`+
`"encrypted_payload":"eMeLrknRAi/dFBM607WPxFOCE1L9RZ4xxUs+Leodz78s/id7Eq+IHhZdOC/stXSNe+Gn/PWgPxcd0TfEPUs5TA350lo=",`+
`"authorized_snap_models":{`+
`"alg":"sha256",`+
@@ -1703,29 +1862,3 @@ func (s *keyDataSuite) TestLegacyKeyData(c *C) {
`"hmacs":["JWziaukXiAIsPU22X1RTC/2wEkPN4IdNvgDEzSnWXIc="]}}
`))
}
-
-func (s *keyDataSuite) TestMakeDiskUnlockKey(c *C) {
- primaryKey := testutil.DecodeHexString(c, "1850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
- kdfAlg := crypto.SHA256
- unique := testutil.DecodeHexString(c, "1850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
-
- unlockKey, clearTextPayload, err := MakeDiskUnlockKey(bytes.NewReader(unique), kdfAlg, primaryKey)
- c.Assert(err, IsNil)
-
- knownGoodUnlockKey := testutil.DecodeHexString(c, "8b78ddabd8e38a6513e654638c0f7b8c738d5461a403564d19d98e7f8ed469cb")
- c.Check(unlockKey, DeepEquals, DiskUnlockKey(knownGoodUnlockKey))
-
- knownGoodPayload := testutil.DecodeHexString(c, "304404201850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b8604201850fbecbe8b3db83a894cb975756c8b69086040f097b03bd4f3b1a3e19c4b86")
- c.Check(clearTextPayload, DeepEquals, knownGoodPayload)
-
- st := cryptobyte.String(clearTextPayload)
- c.Assert(st.ReadASN1(&st, cryptobyte_asn1.SEQUENCE), Equals, true)
-
- var p PrimaryKey
- c.Assert(st.ReadASN1Bytes((*[]byte)(&p), cryptobyte_asn1.OCTET_STRING), Equals, true)
- c.Check(p, DeepEquals, PrimaryKey(primaryKey))
-
- var u []byte
- c.Assert(st.ReadASN1Bytes(&u, cryptobyte_asn1.OCTET_STRING), Equals, true)
- c.Check(u, DeepEquals, unique)
-}
diff --git a/platform.go b/platform.go
index e74953bc..a8a0b9c7 100644
--- a/platform.go
+++ b/platform.go
@@ -68,6 +68,7 @@ func (e *PlatformHandlerError) Unwrap() error {
type PlatformKeyData struct {
Generation int
EncodedHandle []byte // The JSON encoded platform handle
+ Role string
KDFAlg crypto.Hash
AuthMode AuthMode