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