From 74f8b089d610f8b11f388f08c1f7daad4bd8839d Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Mon, 16 Oct 2023 14:30:08 +0200 Subject: [PATCH 01/49] platform.go: add role field to PlatformKeyData Under the new design, the platform agnostic API provides a new role field so each key's use can be identified by snapd. This role field will be authenticated "indirectly" for the case of v3 keys by binding it to the authorization policy of the sealed object. --- platform.go | 1 + 1 file changed, 1 insertion(+) 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 From 4645ce83e66d0c464d5c26d305a044345be9f5de Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Mon, 16 Oct 2023 16:38:55 +0200 Subject: [PATCH 02/49] keydata.go, keydata_legacy.go: refactor handling of authorized snap models Under the new design, the mechanism used to restrict access to confidential keys to a specific set of authorized models will be moved out of the common keyData implementation and into platform implementations that require it. As an example of the opposite, UEFI+TPM platforms use measured boot already to enforce restrictions based on snap models. AuthorizedSnapModels field is made optional and relevant functions are moved to keydata_legacy.go. PrimaryKey is no longer an argument of NewKeyData as it is was relevant for deriving the authKey which was used for checking authorized snap models. It is now moved to NewKeyDataScope under bootenv. SnapModelAuthKey*() functions have been renamed to SnapModelHMACKey*() to better reflect the legacy HMAC behavior. --- keydata.go | 276 ++-------------------------------------------- keydata_legacy.go | 262 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+), 267 deletions(-) diff --git a/keydata.go b/keydata.go index 163e4676..edd0c115 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 @@ -140,15 +137,7 @@ type KeyParams struct { // 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 +268,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 { @@ -440,7 +316,7 @@ 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"` + AuthorizedSnapModels *authorizedSnapModels `json:"authorized_snap_models,omitempty"` } func processPlatformHandlerError(err error) error { @@ -468,51 +344,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") @@ -800,76 +631,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,11 +694,6 @@ 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, @@ -945,23 +701,9 @@ func NewKeyData(params *KeyParams) (*KeyData, error) { 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_legacy.go b/keydata_legacy.go index ff585ba5..60c7117d 100644 --- a/keydata_legacy.go +++ b/keydata_legacy.go @@ -21,8 +21,21 @@ 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) { @@ -57,3 +70,252 @@ func unmarshalV1KeyPayload(data []byte) (unlockKey DiskUnlockKey, primaryKey Pri return unlockKey, primaryKey, 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()) + } +} From ed975b3e35a6b88890cd0dad1df49821f25515f5 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 20:15:28 +0200 Subject: [PATCH 03/49] keydata_test.go: fix mockProtectKeys after scope changes --- keydata_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/keydata_test.go b/keydata_test.go index 44d57441..6d2588fe 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 From a76f332fd8ad7e6c6e41a776c1e816b16fb65d7a Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Mon, 16 Oct 2023 14:30:32 +0200 Subject: [PATCH 04/49] bootenv: move keydata model authorization to separate package under the new design, the cross-platform API is no longer responsible for authorizing snap models. This functionality is relevant for specific platforms that don't use measured boot and therefore has been moved to a separate package. --- bootenv/env.go | 39 ++++ bootenv/keydata.go | 459 +++++++++++++++++++++++++++++++++++++++++++++ bootenv/snap.go | 71 +++++++ 3 files changed, 569 insertions(+) create mode 100644 bootenv/env.go create mode 100644 bootenv/keydata.go create mode 100644 bootenv/snap.go diff --git a/bootenv/env.go b/bootenv/env.go new file mode 100644 index 00000000..cf01d6e3 --- /dev/null +++ b/bootenv/env.go @@ -0,0 +1,39 @@ +// -*- 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 bootenv + +import ( + "sync/atomic" + + "github.com/snapcore/secboot" +) + +var ( + currentModel atomic.Value + currentBootMode atomic.Value +) + +func SetModel(model secboot.SnapModel) bool { + return currentModel.CompareAndSwap(nil, model) +} + +func SetBootMode(mode string) bool { + return currentBootMode.CompareAndSwap(nil, mode) +} diff --git a/bootenv/keydata.go b/bootenv/keydata.go new file mode 100644 index 00000000..773a79f6 --- /dev/null +++ b/bootenv/keydata.go @@ -0,0 +1,459 @@ +// -*- 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 bootenv + +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 int + baseVersion 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.baseVersion)) // baseVersion 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 + }) +} + +type KeyDataScopeParams struct { + PrimaryKey secboot.PrimaryKey + Role string + KDFAlg crypto.Hash + MDAlg crypto.Hash +} + +type KeyDataScope struct { + data keyDataScope +} + +func NewKeyDataScope(params *KeyDataScopeParams) (*KeyDataScope, error) { + out := &KeyDataScope{ + data: keyDataScope{ + Version: 1, + KDFAlg: hashAlg(params.KDFAlg), + MDAlg: hashAlg(params.MDAlg), + }, + } + + 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 +} + +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) +} + +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) +} + +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 +} + +func (d *KeyDataScope) MakeAdditionalData(baseVersion 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, + authMode: authMode, + keyIdentifierAlg: alg, + keyIdentifier: h.Sum(nil), + } + + builder := cryptobyte.NewBuilder(nil) + aad.marshalASN1(builder) + + return builder.Bytes() +} diff --git a/bootenv/snap.go b/bootenv/snap.go new file mode 100644 index 00000000..ed5cc84c --- /dev/null +++ b/bootenv/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 bootenv + +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 +} From cc16a83f2981d2e77ed6ab1adae3baa9285f3d8e Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Mon, 16 Oct 2023 14:20:16 +0200 Subject: [PATCH 05/49] keydata.go: add role field in secboot.keydata --- keydata.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/keydata.go b/keydata.go index edd0c115..7bc59f6d 100644 --- a/keydata.go +++ b/keydata.go @@ -133,6 +133,8 @@ 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 @@ -304,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. @@ -551,6 +564,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 { @@ -698,6 +715,7 @@ func NewKeyData(params *KeyParams) (*KeyData, error) { data: keyData{ Generation: keyDataGeneration, PlatformName: params.PlatformName, + Role: params.Role, PlatformHandle: json.RawMessage(encodedHandle), KDFAlg: hashAlg(params.KDFAlg), EncryptedPayload: params.EncryptedPayload, From 1af77a73f757d89b7b08d83b4f97347cea87acef Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 18:55:34 +0200 Subject: [PATCH 06/49] crypt.go: make SkipSnapModelCheck the default behaviour since for v2 keys model authorization operations are responsibility of each platform, the default behaviour should be skipping any model checking. --- crypt.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) 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") } From bfe89806e59d85d9bd10e307880a15d461ce9b21 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Fri, 27 Oct 2023 11:25:32 +0300 Subject: [PATCH 07/49] keydata_test.go: fix legacy test for the new role field --- keydata_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/keydata_test.go b/keydata_test.go index 6d2588fe..177183ff 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -1507,6 +1507,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",` + @@ -1563,6 +1567,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",` + @@ -1625,6 +1633,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",` + @@ -1660,6 +1672,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) @@ -1690,6 +1703,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",`+ From 6c9fb0f780ce61720562b3119fd20f8a8ecc44f3 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Fri, 27 Oct 2023 22:23:19 +0300 Subject: [PATCH 08/49] bootenv/keydata.go: initialize model digest hash algorithm the digest algorithm used for creating the model digests needs to be initialized when creating a new keyDataScope object otherwise SetAuthorizedSnapModels() fails. --- bootenv/keydata.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 773a79f6..5ac8dc5f 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -242,6 +242,7 @@ type KeyDataScopeParams struct { Role string KDFAlg crypto.Hash MDAlg crypto.Hash + ModelAlg crypto.Hash } type KeyDataScope struct { @@ -249,11 +250,21 @@ type KeyDataScope struct { } 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), + }, + }, }, } From 84e6d65ffa989b7cbd59f6d0d22e8c35b18c8807 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Fri, 27 Oct 2023 22:25:09 +0300 Subject: [PATCH 09/49] bootenv/keydata.go: add kdfAlg, baseVersion fields in additionalData --- bootenv/keydata.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 5ac8dc5f..8098f288 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -458,6 +458,8 @@ func (d *KeyDataScope) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, a aad := &additionalData{ version: d.data.Version, + baseVersion: baseVersion, + kdfAlg: hashAlg(kdfAlg), authMode: authMode, keyIdentifierAlg: alg, keyIdentifier: h.Sum(nil), From 48fab3f2ee7c7ceb2a4c68731c0d4d69657dec5a Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Fri, 27 Oct 2023 22:27:23 +0300 Subject: [PATCH 10/49] bootenv: add tests for keyDataScope --- bootenv/bootenv_test.go | 28 +++ bootenv/env.go | 21 +- bootenv/export_test.go | 58 +++++ bootenv/keydata.go | 12 +- bootenv/keydata_test.go | 461 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 572 insertions(+), 8 deletions(-) create mode 100644 bootenv/bootenv_test.go create mode 100644 bootenv/export_test.go create mode 100644 bootenv/keydata_test.go diff --git a/bootenv/bootenv_test.go b/bootenv/bootenv_test.go new file mode 100644 index 00000000..9a61ee55 --- /dev/null +++ b/bootenv/bootenv_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 bootenv + +import ( + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } diff --git a/bootenv/env.go b/bootenv/env.go index cf01d6e3..e5ff8cc6 100644 --- a/bootenv/env.go +++ b/bootenv/env.go @@ -20,6 +20,7 @@ package bootenv import ( + "errors" "sync/atomic" "github.com/snapcore/secboot" @@ -30,10 +31,26 @@ var ( currentBootMode atomic.Value ) -func SetModel(model secboot.SnapModel) bool { +var SetModel = func(model secboot.SnapModel) bool { return currentModel.CompareAndSwap(nil, model) } -func SetBootMode(mode string) bool { +var SetBootMode = func(mode string) bool { return currentBootMode.CompareAndSwap(nil, mode) } + +var loadCurrentModel = func() (secboot.SnapModel, error) { + model, ok := currentModel.Load().(secboot.SnapModel) + if !ok { + return nil, errors.New("SetModel hasn't been called yet") + } + return model, nil +} + +var loadCurrentBootMode = func() (string, error) { + mode, ok := currentBootMode.Load().(string) + if !ok { + return "", errors.New("SetBootMode hasn't been called yet") + } + return mode, nil +} diff --git a/bootenv/export_test.go b/bootenv/export_test.go new file mode 100644 index 00000000..4a21a15f --- /dev/null +++ b/bootenv/export_test.go @@ -0,0 +1,58 @@ +// -*- 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 bootenv + +import "github.com/snapcore/secboot" + +var ( + ComputeSnapModelHash = computeSnapModelHash +) + +func MockSetModel(f func(secboot.SnapModel) bool) (restore func()) { + origSetModel := SetModel + SetModel = f + return func() { + SetModel = origSetModel + } +} + +func MockSetBootMode(f func(string) bool) (restore func()) { + origSetBootMode := SetBootMode + SetBootMode = f + return func() { + SetBootMode = origSetBootMode + } +} + +func MockLoadCurrentModel(f func() (secboot.SnapModel, error)) (restore func()) { + origLoadCurrentModel := loadCurrentModel + loadCurrentModel = f + return func() { + loadCurrentModel = origLoadCurrentModel + } +} + +func MockLoadCurrenBootMode(f func() (string, error)) (restore func()) { + origLoadCurrentBootMode := loadCurrentBootMode + loadCurrentBootMode = f + return func() { + loadCurrentBootMode = origLoadCurrentBootMode + } +} diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 8098f288..abd0ca4f 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -399,9 +399,9 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { } 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") + model, err := loadCurrentModel() + if err != nil { + return err } currentModelDigest, err := computeSnapModelHash(crypto.Hash(alg), model) @@ -422,9 +422,9 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { } if len(d.data.Params.Modes) > 0 { - mode, ok := currentBootMode.Load().(string) - if !ok { - return errors.New("SetBootMode hasn't been called yet") + mode, err := loadCurrentBootMode() + if err != nil { + return err } found := false diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go new file mode 100644 index 00000000..24f85f8b --- /dev/null +++ b/bootenv/keydata_test.go @@ -0,0 +1,461 @@ +// -*- 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 bootenv_test + +import ( + "crypto" + "crypto/rand" + + . "gopkg.in/check.v1" + + "github.com/snapcore/secboot" + . "github.com/snapcore/secboot" + . "github.com/snapcore/secboot/bootenv" + "github.com/snapcore/secboot/internal/testutil" + snapd_testutil "github.com/snapcore/snapd/testutil" +) + +type keyDataPlatformSuite struct { + snapd_testutil.BaseTest + Model secboot.SnapModel + BootMode string +} + +var _ = Suite(&keyDataPlatformSuite{}) + +func (s *keyDataPlatformSuite) newPrimaryKey(c *C, sz1 int) PrimaryKey { + primaryKey := make(PrimaryKey, sz1) + _, err := rand.Read(primaryKey) + c.Assert(err, IsNil) + + return primaryKey +} + +func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + + params := &KeyDataScopeParams{ + PrimaryKey: primaryKey, + Role: "test", + KDFAlg: crypto.SHA256, + MDAlg: crypto.SHA256, + ModelAlg: crypto.SHA256, + } + + kds, err := NewKeyDataScope(params) + c.Assert(err, IsNil) + c.Check(kds, NotNil) + + err = kds.IsBootEnvironmentAuthorized() + c.Check(err, IsNil) +} + +func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingKDF(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + + 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 := s.newPrimaryKey(c, 32) + + 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 := s.newPrimaryKey(c, 32) + + params := &KeyDataScopeParams{ + PrimaryKey: primaryKey, + Role: "test", + KDFAlg: crypto.SHA256, + MDAlg: crypto.SHA256, + } + + _, err := NewKeyDataScope(params) + c.Assert(err, ErrorMatches, "No model digest algorithm specified") +} + +func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + + params := &KeyDataScopeParams{ + PrimaryKey: primaryKey, + Role: "test", + KDFAlg: crypto.SHA256, + MDAlg: crypto.SHA256, + ModelAlg: crypto.SHA256, + } + + kds, err := NewKeyDataScope(params) + c.Assert(err, IsNil) + + _, err = kds.MakeAdditionalData(1, crypto.SHA256, secboot.AuthModeNone) + c.Check(err, IsNil) + +} + +func (s *keyDataPlatformSuite) mockState(c *C) { + s.AddCleanup( + MockSetModel(func(model SnapModel) bool { + s.Model = model + return true + }), + ) + s.AddCleanup( + MockSetBootMode(func(mode string) bool { + s.BootMode = mode + return true + }), + ) + s.AddCleanup( + MockLoadCurrentModel(func() (SnapModel, error) { + return s.Model, nil + }), + ) + s.AddCleanup( + MockLoadCurrenBootMode(func() (string, error) { + return s.BootMode, nil + }), + ) +} + +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 := s.newPrimaryKey(c, 32) + + 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 := s.newPrimaryKey(c, 32) + + 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...) + 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") +} + +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 := s.newPrimaryKey(c, 32) + + 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...) + + 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") +} + +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 := s.newPrimaryKey(c, 32) + + 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", + } + + s.mockState(c) + + 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", + } + + s.mockState(c) + + 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", + } + + s.mockState(c) + + 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" + + s.mockState(c) + + 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") +} From a2146396251aa5a59aeb2fe50af2c71521a642ab Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 16 Jan 2024 15:09:49 +0200 Subject: [PATCH 11/49] bootenv/keydata.go: add doc comments --- bootenv/keydata.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index abd0ca4f..0920bc3e 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -237,6 +237,8 @@ func (d *additionalData) marshalASN1(b *cryptobyte.Builder) { }) } +// KeyDataScopeParams defines the parameters for the creation of a +// key data scope object. type KeyDataScopeParams struct { PrimaryKey secboot.PrimaryKey Role string @@ -245,10 +247,19 @@ type KeyDataScopeParams struct { 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 } +// 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 an ASN1 marshalled +// 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 { @@ -342,6 +353,14 @@ func (d *KeyDataScope) isAuthorized() (bool, error) { 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 ASN1 serialized 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 an ASN1 marshalled 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() { @@ -370,6 +389,12 @@ func (d *KeyDataScope) SetAuthorizedSnapModels(key secboot.PrimaryKey, role stri 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 an ASN1 marshalled 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 @@ -384,6 +409,8 @@ func (d *KeyDataScope) SetAuthorizedBootModes(key secboot.PrimaryKey, role strin 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 { @@ -442,6 +469,8 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { return nil } +// MakeAdditionalData constructs the additional data that need to be integrity protected for +// a key data scope (in AES-GCM for example). func (d *KeyDataScope) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) { alg := d.data.MDAlg if !alg.Available() { From 78e3364b71b8edac58d581f8bcf62d4ae39830ea Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 17 Jan 2024 23:56:36 +0200 Subject: [PATCH 12/49] crypt_test.go: rename primaryKey to diskUnlockKey --- crypt_test.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crypt_test.go b/crypt_test.go index 5aa29df3..3a904aa0 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -1059,8 +1059,8 @@ func (s *cryptSuite) TestActivateVolumeWithKeyData9(c *C) { } type testActivateVolumeWithKeyDataErrorHandlingData struct { - primaryKey DiskUnlockKey - recoveryKey RecoveryKey + diskUnlockKey DiskUnlockKey + recoveryKey RecoveryKey authRequestor *mockAuthRequestor @@ -1077,7 +1077,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 +1148,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 +1166,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 +1199,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 +1220,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 +1243,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 +1261,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 +1290,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 +1335,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{}}}, @@ -1391,8 +1391,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{}}}, From a6e352021a4485af18060e5173e6714440048a7c Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Mon, 22 Jan 2024 22:11:12 +0200 Subject: [PATCH 13/49] crypt_test.go, keydata*test.go: modify tests for new keydata format existing tests were modified to work for the new keydata format: 1) the bulk of the tests was modified to remove any code related to model authorization operations. 2) several tests that only applied for legacy keys were renamed and modified to test hardcoded legacy key scenarios. --- crypt_test.go | 1015 +++++++++++++++++++++++++--------------- export_test.go | 13 + keydata_file_test.go | 23 - keydata_legacy_test.go | 8 +- keydata_luks_test.go | 22 - keydata_test.go | 793 ++++++++++++++++++------------- 6 files changed, 1126 insertions(+), 748 deletions(-) diff --git a/crypt_test.go b/crypt_test.go index 3a904aa0..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], @@ -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 @@ -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) @@ -1942,18 +1636,9 @@ 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) + 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_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_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 177183ff..503749cc 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -337,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 { @@ -382,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) @@ -403,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) @@ -726,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) } @@ -899,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 @@ -1046,195 +1064,17 @@ func (s *keyDataSuite) TestChangePassphraseForceIterations(c *C) { kdfOptions: &KDFOptions{ForceIterations: 3, MemoryKiB: 32 * 1024}}) } -func (s *keyDataSuite) TestChangePassphraseWrongPassphrase(c *C) { - s.handler.passphraseSupport = true - - primaryKey := s.newPrimaryKey(c, 32) - - kdfOptions := &KDFOptions{ - TargetDuration: 100 * time.Millisecond, - } - protected, _ := s.mockProtectKeysWithPassphrase(c, primaryKey, kdfOptions, 32, crypto.SHA256, crypto.SHA256) - - var kdf testutil.MockKDF - keyData, err := NewKeyDataWithPassphrase(protected, "12345678", &kdf) - c.Check(err, IsNil) - - c.Check(keyData.ChangePassphrase("passphrase", "12345678", &kdf), Equals, ErrInvalidPassphrase) - - s.checkKeyDataJSONAuthModePassphrase(c, keyData, protected, 0, "12345678", kdfOptions) -} - -type testSnapModelAuthData struct { - alg crypto.Hash - authModels []SnapModel - model SnapModel - authorized bool -} - -func (s *keyDataSuite) testSnapModelAuth(c *C, data *testSnapModelAuthData) { - primaryKey := s.newPrimaryKey(c, 32) - protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256) - - keyData, err := NewKeyData(protected) - c.Assert(err, IsNil) - c.Check(keyData.SetAuthorizedSnapModels(primaryKey, data.authModels...), IsNil) - - authorized, err := keyData.IsSnapModelAuthorized(primaryKey, data.model) - c.Check(err, IsNil) - c.Check(authorized, Equals, data.authorized) -} - -func (s *keyDataSuite) TestSnapModelAuth1(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")} - s.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA256, - authModels: models, - model: models[0], - authorized: true}) -} - -func (s *keyDataSuite) TestSnapModelAuth2(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"), - 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.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA256, - authModels: models, - model: models[1], - authorized: true}) -} - -func (s *keyDataSuite) TestSnapModelAuth3(c *C) { - s.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA256, - authModels: []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")}, - 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}) -} - -func (s *keyDataSuite) TestSnapModelAuth4(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")} - s.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA512, - authModels: models, - model: models[0], - authorized: true}) -} -func (s *keyDataSuite) TestSnapModelAuth5(c *C) { - models := []SnapModel{ - testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ - "authority-id": "fake-brand", - "series": "16", - "brand-id": "fake-brand", - "model": "fake-model", - "classic": "true", - "distribution": "ubuntu", - "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", - "classic": "true", - "distribution": "ubuntu", - "grade": "secured", - }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")} - s.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA256, - authModels: models, - model: models[1], - authorized: true}) -} - -func (s *keyDataSuite) TestSnapModelAuth6(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"), - 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.testSnapModelAuth(c, &testSnapModelAuthData{ - alg: crypto.SHA256, - authModels: models, - model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ - "authority-id": "fake-brand", - "series": "16", - "brand-id": "fake-brand", - "model": "other-model", - "classic": "true", - "distribution": "ubuntu", - "grade": "secured", - }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"), - authorized: false}) -} - -func (s *keyDataSuite) TestSetAuthorizedSnapModelsWithWrongKey(c *C) { +func (s *keyDataSuite) TestSnapModelAuthErrorHandling(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")} + w := makeMockKeyDataWriter() + c.Check(keyData.WriteAtomic(w), IsNil) - c.Check(keyData.SetAuthorizedSnapModels(make(PrimaryKey, 32), models...), ErrorMatches, "incorrect key supplied") + authorized, err := keyData.IsSnapModelAuthorized(primaryKey, nil) + c.Check(err, ErrorMatches, "unsupported key data generation number") + c.Check(authorized, Equals, false) } type testWriteAtomicData struct { @@ -1260,12 +1100,12 @@ func (s *keyDataSuite) TestWriteAtomic1(c *C) { } type testReadKeyDataData struct { - key DiskUnlockKey - auxKey PrimaryKey - id KeyID - r KeyDataReader - model SnapModel - authorized bool + diskUnlockKey DiskUnlockKey + primaryKey PrimaryKey + id KeyID + r KeyDataReader + model SnapModel + authorized bool } func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) { @@ -1277,16 +1117,10 @@ func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) { 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) + unlockKey, primaryKey, err := keyData.RecoverKeys() c.Check(err, IsNil) - c.Check(authorized, Equals, data.authorized) - - c.Check(keyData.SetAuthorizedSnapModels(auxKey), IsNil) + c.Check(unlockKey, DeepEquals, data.diskUnlockKey) + c.Check(primaryKey, DeepEquals, data.primaryKey) } func (s *keyDataSuite) TestReadKeyData1(c *C) { @@ -1296,17 +1130,6 @@ func (s *keyDataSuite) TestReadKeyData1(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")} - - c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil) - w := makeMockKeyDataWriter() c.Check(keyData.WriteAtomic(w), IsNil) @@ -1314,12 +1137,11 @@ func (s *keyDataSuite) TestReadKeyData1(c *C) { c.Check(err, IsNil) s.testReadKeyData(c, &testReadKeyDataData{ - key: unlockKey, - auxKey: primaryKey, - id: id, - r: &mockKeyDataReader{"foo", w.Reader()}, - model: models[0], - authorized: true}) + diskUnlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"foo", w.Reader()}, + }) } func (s *keyDataSuite) TestReadKeyData2(c *C) { @@ -1329,17 +1151,6 @@ func (s *keyDataSuite) TestReadKeyData2(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")} - - c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil) - w := makeMockKeyDataWriter() c.Check(keyData.WriteAtomic(w), IsNil) @@ -1347,12 +1158,11 @@ func (s *keyDataSuite) TestReadKeyData2(c *C) { c.Check(err, IsNil) s.testReadKeyData(c, &testReadKeyDataData{ - key: unlockKey, - auxKey: primaryKey, - id: id, - r: &mockKeyDataReader{"bar", w.Reader()}, - model: models[0], - authorized: true}) + diskUnlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"bar", w.Reader()}, + }) } func (s *keyDataSuite) TestReadKeyData3(c *C) { @@ -1362,37 +1172,20 @@ func (s *keyDataSuite) TestReadKeyData3(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) - 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}) + params := &testReadKeyDataData{ + diskUnlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"foo", w.Reader()}, + } + + s.testReadKeyData(c, params) } func (s *keyDataSuite) TestReadKeyData4(c *C) { @@ -1402,6 +1195,250 @@ func (s *keyDataSuite) TestReadKeyData4(c *C) { 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{ + diskUnlockKey: 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) TestRecoverLegacyKeyWithPassphrase(c *C) { + s.handler.passphraseSupport = true + + var kdf testutil.MockKDF + + primaryKey := testutil.DecodeHexString(c, "5a08905fcc1977e02ab206198f56cf2e5bd3b43660e0c3b3055ff0040b722a18") + unlockKey := testutil.DecodeHexString(c, "5cf4329807a9378f0164f94894f8e3ec9fe38ee8f090816ec8626ae9c631a68b") + j := []byte( + `{` + + `"generation":1,` + + `"platform_name":"mock",` + + `"platform_handle":` + + `{` + + `"key":"fwrbemoaBvzUpbEeP1GxThfwpSVmzJvKuGjrFCYoBGI=",` + + `"iv":"JzQxiiSLllGke9XlKXzytw==",` + + `"auth-key-hmac":"qgJDnOuFLKwgMAkSpq5U4qwN/APDjrtI6qMZ/0DhcUs=",` + + `"exp-generation":1,` + + `"exp-kdf_alg":5,` + + `"exp-auth-mode":1},` + + `"role":"",` + + `"kdf_alg":"sha256",` + + `"encrypted_payload":"WwOAw8GqWvsCk2TDEu9PLaLf3Z+4Ybroj0xT++6BXPIL5aT8aKWWHFDYl7nKx8KlOa818KO6AS/S+l6Hu6BnUJ3XjDw=",` + + `"passphrase_params":` + + `{` + + `"kdf":` + + `{` + + `"type":"argon2i",` + + `"salt":"FiMNjylKKlJgNsAX80OYKg==",` + + `"time":4,` + + `"memory":1024063,` + + `"cpus":4},` + + `"encryption":"aes-cfb",` + + `"derived_key_size":32,` + + `"encryption_key_size":32,` + + `"auth_key_size":32},` + + `"authorized_snap_models":` + + `{` + + `"alg":"sha256",` + + `"kdf_alg":"sha256",` + + `"key_digest":` + + `{` + + `"alg":"sha256",` + + `"salt":"LeNyUyr7cuWxenyUhODfMBUzOfUH2rNDQWXfDywGvI0=",` + + `"digest":"qKezdUsVhiShyjxF9/oFW411A+p7Q+4ytkBJzd83luc="},` + + `"hmacs":null}} +`) + + keyData, err := ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)}) + c.Assert(err, IsNil) + + recoveredUnlockKey, recoveredPrimaryKey, err := keyData.RecoverKeysWithPassphrase("passphrase", &kdf) + c.Check(err, IsNil) + c.Check(recoveredUnlockKey, DeepEquals, DiskUnlockKey(unlockKey)) + c.Check(recoveredPrimaryKey, DeepEquals, PrimaryKey(primaryKey)) +} + +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) + c.Check(err, IsNil) + c.Check(authorized, Equals, data.authorized) +} + +func (s *keyDataSuite) TestLegacySnapModelAuth1(c *C) { models := []SnapModel{ testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ "authority-id": "fake-brand", @@ -1410,30 +1447,174 @@ func (s *keyDataSuite) TestReadKeyData4(c *C) { "model": "fake-model", "grade": "secured", }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")} + s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA256, + authModels: models, + model: models[0], + authorized: true}) +} - c.Check(keyData.SetAuthorizedSnapModels(primaryKey, models...), IsNil) +func (s *keyDataSuite) TestLegacySnapModelAuth2(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"), + 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.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA256, + authModels: models, + model: models[1], + authorized: true}) +} - w := makeMockKeyDataWriter() - c.Check(keyData.WriteAtomic(w), IsNil) +func (s *keyDataSuite) TestLegacySnapModelAuth3(c *C) { + s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA256, + authModels: []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")}, + 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}) +} - id, err := keyData.UniqueID() - c.Check(err, IsNil) +func (s *keyDataSuite) TestLegacySnapModelAuth4(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")} + s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA512, + authModels: models, + model: models[0], + authorized: true}) +} - s.testReadKeyData(c, &testReadKeyDataData{ - key: unlockKey, - auxKey: primaryKey, - id: id, - r: &mockKeyDataReader{"foo", w.Reader()}, +func (s *keyDataSuite) TestLegacySnapModelAuth5(c *C) { + models := []SnapModel{ + testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ + "authority-id": "fake-brand", + "series": "16", + "brand-id": "fake-brand", + "model": "fake-model", + "classic": "true", + "distribution": "ubuntu", + "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", + "classic": "true", + "distribution": "ubuntu", + "grade": "secured", + }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")} + s.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA256, + authModels: models, + model: models[1], + authorized: true}) +} + +func (s *keyDataSuite) TestLegacySnapModelAuth6(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"), + 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.testLegacySnapModelAuth(c, &testLegacySnapModelAuthData{ + alg: crypto.SHA256, + authModels: models, model: testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ "authority-id": "fake-brand", "series": "16", "brand-id": "fake-brand", "model": "other-model", + "classic": "true", + "distribution": "ubuntu", "grade": "secured", }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"), authorized: false}) } +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 := ReadKeyData(&mockKeyDataReader{Reader: bytes.NewReader(j)}) + 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") +} + func (s *keyDataSuite) TestKeyDataDerivePassphraseKeysExpectedInfoFields(c *C) { // Test that key derivation from passphrase is using expected info fields s.handler.passphraseSupport = true @@ -1718,29 +1899,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) -} From f9a7f8e903e939cd654a6940acafedc4b71c4f4e Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 01:48:42 +0200 Subject: [PATCH 14/49] bootenv: add tests for MakeAdditionalData bootenv/keydata.go: - additionalData -> AdditionalData - new unmarshalHashAlg() to unmarshal to hashAlg from ASN1. bootenv/export_test.go: - UnmarshalAdditionalData() to unmarshal to AdditionalData from ASN1. - KeyDataScope.TestSetVersion() to allow setting the version of key data scope objects in tests. - KeyDataScope.TestMatch() to compare the unmarshalled key identifier field to the expected derived one. bootenv/keydata_test.go: - add more complete test for MakeAdditionalData() --- bootenv/export_test.go | 68 ++++++++++++++++++++++++++++++++++- bootenv/keydata.go | 79 ++++++++++++++++++++++++++++++----------- bootenv/keydata_test.go | 59 +++++++++++++++++++++++++++--- 3 files changed, 179 insertions(+), 27 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 4a21a15f..5a89e610 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -19,7 +19,16 @@ package bootenv -import "github.com/snapcore/secboot" +import ( + "bytes" + "crypto" + "crypto/x509" + "errors" + + "github.com/snapcore/secboot" + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" +) var ( ComputeSnapModelHash = computeSnapModelHash @@ -56,3 +65,60 @@ func MockLoadCurrenBootMode(f func() (string, error)) (restore func()) { loadCurrentBootMode = origLoadCurrentBootMode } } + +func (d *KeyDataScope) TestSetVersion(version int) { + d.data.Version = version +} + +func UnmarshalAdditionalData(data []byte) (*AdditionalData, error) { + s := cryptobyte.String(data) + + if !s.ReadASN1(&s, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("malformed input") + } + + aad := new(AdditionalData) + + if !s.ReadASN1Integer(&aad.Version) { + return nil, errors.New("malformed version") + } + + if !s.ReadASN1Integer(&aad.BaseVersion) { + return nil, errors.New("malformed base version") + } + + kdfAlg, err := unmarshalHashAlg(&s) + if err != nil { + return nil, errors.New("malformed kdf") + } + aad.KdfAlg = kdfAlg + + var authMode int + if !s.ReadASN1Enum(&authMode) { + return nil, errors.New("malformed Auth mode") + } + aad.AuthMode = secboot.AuthMode(authMode) + + keyIdAlg, err := unmarshalHashAlg(&s) + if err != nil { + return nil, errors.New("malformed kdf") + } + aad.KeyIdentifierAlg = keyIdAlg + + if !s.ReadASN1Bytes(&aad.KeyIdentifier, cryptobyte_asn1.OCTET_STRING) { + return nil, errors.New("malformed Key identifier") + } + + return aad, nil +} + +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) +} diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 0920bc3e..ef5bd4b7 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -138,6 +138,43 @@ func (a hashAlg) marshalASN1(b *cryptobyte.Builder) { }) } +func unmarshalHashAlg(s *cryptobyte.String) (hashAlg, error) { + var str cryptobyte.String + + if !s.ReadASN1(&str, cryptobyte_asn1.SEQUENCE) { + return 0, errors.New("malformed input") + } + + var oid asn1.ObjectIdentifier + + if !str.ReadASN1ObjectIdentifier(&oid) { + return 0, errors.New("malformed Algorithm identifier") + } + + var null uint8 + + if !str.ReadUint8(&null) { + return 0, errors.New("malformed input") + } + + if len(oid) == len(sha1Oid) { + return hashAlg(crypto.SHA1), nil + } + + switch oid[8] { + case sha224Oid[8]: + return hashAlg(crypto.SHA224), nil + case sha256Oid[8]: + return hashAlg(crypto.SHA256), nil + case sha384Oid[8]: + return hashAlg(crypto.SHA384), nil + case sha512Oid[8]: + return hashAlg(crypto.SHA512), nil + default: + return 0, errors.New("unsupported hash algorithm") + } +} + // digestList corresponds to a list of digests. type digestList struct { Alg hashAlg `json:"alg"` // The digest algorithm @@ -217,23 +254,23 @@ type keyDataScope struct { MDAlg hashAlg `json:"md_alg"` } -type additionalData struct { - version int - baseVersion int - kdfAlg hashAlg - authMode secboot.AuthMode - keyIdentifierAlg hashAlg - keyIdentifier []byte +type AdditionalData struct { + Version int + BaseVersion int + KdfAlg hashAlg + AuthMode secboot.AuthMode + KeyIdentifierAlg hashAlg + KeyIdentifier []byte } -func (d *additionalData) marshalASN1(b *cryptobyte.Builder) { +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.baseVersion)) // baseVersion 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 + b.AddASN1Int64(int64(d.Version)) // version INTEGER + b.AddASN1Int64(int64(d.BaseVersion)) // baseVersion 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 }) } @@ -485,13 +522,13 @@ func (d *KeyDataScope) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, a h := alg.New() h.Write(der) - aad := &additionalData{ - version: d.data.Version, - baseVersion: baseVersion, - kdfAlg: hashAlg(kdfAlg), - authMode: authMode, - keyIdentifierAlg: alg, - keyIdentifier: h.Sum(nil), + aad := &AdditionalData{ + Version: d.data.Version, + BaseVersion: baseVersion, + KdfAlg: hashAlg(kdfAlg), + AuthMode: authMode, + KeyIdentifierAlg: alg, + KeyIdentifier: h.Sum(nil), } builder := cryptobyte.NewBuilder(nil) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 24f85f8b..04558895 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -109,23 +109,72 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { c.Assert(err, ErrorMatches, "No model digest algorithm specified") } -func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { +type testMakeAdditionalDataData struct { + keyDataScopeVersion int + baseVersion int + authMode secboot.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 +} + +func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditionalDataData) { primaryKey := s.newPrimaryKey(c, 32) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, - Role: "test", - KDFAlg: crypto.SHA256, - MDAlg: crypto.SHA256, + Role: data.role, + KDFAlg: data.signingKeyDerivationAlg, + MDAlg: data.mdAlg, ModelAlg: crypto.SHA256, } kds, err := NewKeyDataScope(params) c.Assert(err, IsNil) - _, err = kds.MakeAdditionalData(1, crypto.SHA256, secboot.AuthModeNone) + if data.keyDataScopeVersion != 0 { + kds.TestSetVersion(data.keyDataScopeVersion) + } + + aadBytes, err := kds.MakeAdditionalData(data.baseVersion, data.keyDigestHashAlg, data.authMode) c.Check(err, IsNil) + aad, err := UnmarshalAdditionalData(aadBytes) + c.Assert(err, IsNil) + + c.Check(aad.Version, Equals, 1) + c.Check(aad.BaseVersion, Equals, data.baseVersion) + c.Check(crypto.Hash(aad.KdfAlg), Equals, data.keyDigestHashAlg) + c.Check(aad.AuthMode, Equals, data.authMode) + c.Check(crypto.Hash(aad.KeyIdentifierAlg), Equals, data.signingKeyDerivationAlg) + + c.Check(kds.TestMatch(data.keyDigestHashAlg, aad.KeyIdentifier), Equals, true) + +} + +func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { + s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + baseVersion: 1, + authMode: secboot.AuthModeNone, + mdAlg: crypto.SHA256, + keyDigestHashAlg: crypto.SHA256, + signingKeyDerivationAlg: crypto.SHA256, + role: "foo", + }) +} + +func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { + s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + baseVersion: 1, + authMode: secboot.AuthModePassphrase, + mdAlg: crypto.SHA256, + keyDigestHashAlg: crypto.SHA256, + signingKeyDerivationAlg: crypto.SHA256, + role: "foo", + }) } func (s *keyDataPlatformSuite) mockState(c *C) { From 8810ae526f0dd23305bd12deac77f36e30da5df6 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 04:07:40 +0200 Subject: [PATCH 15/49] snap_test.go: add test for computeSnapModelHash --- bootenv/snap_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 bootenv/snap_test.go diff --git a/bootenv/snap_test.go b/bootenv/snap_test.go new file mode 100644 index 00000000..7bb7b96d --- /dev/null +++ b/bootenv/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 bootenv_test + +import ( + "crypto" + "encoding/base64" + + "github.com/snapcore/secboot/bootenv" + "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 := bootenv.ComputeSnapModelHash(alg, model) + c.Assert(err, IsNil) + c.Check(modelAsn, DeepEquals, expected) +} From dad82f91ed534edf5747f0a209c4d1aa0f29e113 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 04:08:13 +0200 Subject: [PATCH 16/49] bootenv/keydata_test.go: add tests for hashAlg/ecdsaPublicKey marshalling/unmarshalling --- bootenv/export_test.go | 20 ++++++++ bootenv/keydata_test.go | 111 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 5a89e610..624a1dbf 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -22,10 +22,13 @@ package bootenv import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/x509" "errors" "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" ) @@ -122,3 +125,20 @@ func (d *KeyDataScope) TestMatch(KDFAlg crypto.Hash, keyIdentifier []byte) bool h.Write(der) return bytes.Equal(h.Sum(nil), keyIdentifier) } + +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 +} diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 04558895..cb51c916 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -22,6 +22,9 @@ package bootenv_test import ( "crypto" "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" . "gopkg.in/check.v1" @@ -508,3 +511,111 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidBootMode(c *C) { 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") +} From d9b9a2aded9c2d22e539fb128832af1a1cb46360 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 04:36:44 +0200 Subject: [PATCH 17/49] bootenv/keydata_test.go: add a test for wrong key supplied to SetAuthorizedSnapModels --- bootenv/keydata_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index cb51c916..dd9d8637 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -308,6 +308,37 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsInvalidRole(c *C) { }), ErrorMatches, "incorrect key supplied") } +func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + + validModels := []SnapModel{ + s.makeMockModelAssertion(c, "model-a"), + } + data := &testSetAuthorizedSnapModelsData{ + kDFAlg: crypto.SHA256, + mDAlg: crypto.SHA256, + modelAlg: crypto.SHA256, + validRole: "test", + role: "different", + validModels: validModels, + } + + 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 := s.newPrimaryKey(c, 32) + err = kds.SetAuthorizedSnapModels(wrongKey, data.role, data.validModels...) + c.Check(err, ErrorMatches, "incorrect key supplied") +} + type testSetAuthorizedBootModesData struct { kDFAlg crypto.Hash mDAlg crypto.Hash From 2e4c29ad48e613c6062554c0dd7f252decfd2ed4 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 04:39:14 +0200 Subject: [PATCH 18/49] bootenv/keydata_test.go: add a test for wrong key supplied to SetAuthorizedBootModes --- bootenv/keydata_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index dd9d8637..2bffe029 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -400,6 +400,37 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesInvalidRole(c *C) { }), ErrorMatches, "incorrect key supplied") } +func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + + 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 := s.newPrimaryKey(c, 32) + err = kds.SetAuthorizedBootModes(wrongKey, data.role, data.validModes...) + c.Check(err, ErrorMatches, "incorrect key supplied") +} + type testBootEnvAuthData struct { kDFAlg crypto.Hash mDAlg crypto.Hash From f2ffc65def9af9a1684d107cf1063757c2d32784 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 23 Jan 2024 04:40:21 +0200 Subject: [PATCH 19/49] bootenv/keydata_test.go: add test for deterministic derivation of elliptic key --- bootenv/export_test.go | 4 ++++ bootenv/keydata_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 624a1dbf..70692fbe 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -126,6 +126,10 @@ func (d *KeyDataScope) TestMatch(KDFAlg crypto.Hash, keyIdentifier []byte) bool 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) } diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 2bffe029..9ec8bf54 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -21,6 +21,7 @@ package bootenv_test import ( "crypto" + "crypto/ecdsa" "crypto/rand" "encoding/base64" "encoding/json" @@ -681,3 +682,40 @@ func (s *keyDataPlatformSuite) TestEcdsaPublicKeyUnmarshalJSONInvalid(c *C) { err = unmarshalledPk.UnmarshalJSON(pkBytes) c.Check(err, ErrorMatches, "invalid key type") } + +func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + 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) + + err = kds.IsBootEnvironmentAuthorized() + c.Check(err, 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 + } + +} From 2f0e2959d3b87261ca93a0967e37ea285066a8da Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 31 Jan 2024 16:51:10 +0200 Subject: [PATCH 20/49] bootenv: add scope tests the mockPlatformhandler was copied over to the bootenv package and all the test logic related to the new scope functionality was decoupled from keydata_test.go. --- bootenv/export_test.go | 11 ++ bootenv/keydata_test.go | 349 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 329 insertions(+), 31 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 70692fbe..23d98c5a 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -24,6 +24,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" "crypto/x509" "errors" @@ -146,3 +147,13 @@ func NewEcdsaPublicKey(rand []byte) (ecdsaPublicKey, error) { return pk, nil } + +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 +} diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 9ec8bf54..b5d21d18 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -20,16 +20,22 @@ package bootenv_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/bootenv" "github.com/snapcore/secboot/internal/testutil" @@ -38,22 +44,15 @@ import ( type keyDataPlatformSuite struct { snapd_testutil.BaseTest - Model secboot.SnapModel + Model SnapModel BootMode string } var _ = Suite(&keyDataPlatformSuite{}) -func (s *keyDataPlatformSuite) newPrimaryKey(c *C, sz1 int) PrimaryKey { - primaryKey := make(PrimaryKey, sz1) - _, err := rand.Read(primaryKey) - c.Assert(err, IsNil) - - return primaryKey -} - func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -72,7 +71,8 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) { } func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingKDF(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -81,12 +81,13 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingKDF(c *C) { ModelAlg: crypto.SHA256, } - _, err := NewKeyDataScope(params) + _, err = NewKeyDataScope(params) c.Assert(err, ErrorMatches, "KDF algorithm unavailable") } func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingMD(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -95,12 +96,13 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingMD(c *C) { ModelAlg: crypto.SHA256, } - _, err := NewKeyDataScope(params) + _, err = NewKeyDataScope(params) c.Assert(err, ErrorMatches, "MD algorithm unavailable") } func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -109,14 +111,14 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { MDAlg: crypto.SHA256, } - _, err := NewKeyDataScope(params) + _, err = NewKeyDataScope(params) c.Assert(err, ErrorMatches, "No model digest algorithm specified") } type testMakeAdditionalDataData struct { keyDataScopeVersion int baseVersion int - authMode secboot.AuthMode + authMode AuthMode mdAlg crypto.Hash keyDigestHashAlg crypto.Hash // These are used to derive the signing key whose digest go @@ -126,7 +128,8 @@ type testMakeAdditionalDataData struct { } func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditionalDataData) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -156,13 +159,12 @@ func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditi c.Check(crypto.Hash(aad.KeyIdentifierAlg), Equals, data.signingKeyDerivationAlg) c.Check(kds.TestMatch(data.keyDigestHashAlg, aad.KeyIdentifier), Equals, true) - } func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ baseVersion: 1, - authMode: secboot.AuthModeNone, + authMode: AuthModeNone, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, signingKeyDerivationAlg: crypto.SHA256, @@ -173,7 +175,7 @@ func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ baseVersion: 1, - authMode: secboot.AuthModePassphrase, + authMode: AuthModePassphrase, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, signingKeyDerivationAlg: crypto.SHA256, @@ -217,7 +219,8 @@ func (s *keyDataPlatformSuite) makeMockModelAssertion(c *C, modelName string) Sn } func (s *keyDataPlatformSuite) TestBootEnvAuthStateErrors(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -260,7 +263,8 @@ type testSetAuthorizedSnapModelsData struct { } func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAuthorizedSnapModelsData) error { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -310,7 +314,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsInvalidRole(c *C) { } func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) validModels := []SnapModel{ s.makeMockModelAssertion(c, "model-a"), @@ -335,7 +340,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { kds, err := NewKeyDataScope(params) c.Assert(err, IsNil) - wrongKey := s.newPrimaryKey(c, 32) + wrongKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) err = kds.SetAuthorizedSnapModels(wrongKey, data.role, data.validModels...) c.Check(err, ErrorMatches, "incorrect key supplied") } @@ -350,7 +356,8 @@ type testSetAuthorizedBootModesData struct { } func (s *keyDataPlatformSuite) testSetAuthorizedBootModes(c *C, data *testSetAuthorizedBootModesData) error { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -402,7 +409,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesInvalidRole(c *C) { } func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) validModes := []string{ "modeFoo", @@ -427,7 +435,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { kds, err := NewKeyDataScope(params) c.Assert(err, IsNil) - wrongKey := s.newPrimaryKey(c, 32) + wrongKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) err = kds.SetAuthorizedBootModes(wrongKey, data.role, data.validModes...) c.Check(err, ErrorMatches, "incorrect key supplied") } @@ -445,7 +454,8 @@ type testBootEnvAuthData struct { } func (s *keyDataPlatformSuite) testBootEnvAuth(c *C, data *testBootEnvAuthData) error { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, @@ -684,7 +694,8 @@ func (s *keyDataPlatformSuite) TestEcdsaPublicKeyUnmarshalJSONInvalid(c *C) { } func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) { - primaryKey := s.newPrimaryKey(c, 32) + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) role := "test" params := &KeyDataScopeParams{ @@ -717,5 +728,281 @@ func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) { c.Check(key.Equal(prevKey), Equals, true) prevKey = key } +} + +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 +} + +type testRecoverKeysData struct { + *KeyDataScopeParams + authRole string + modes []string + models []SnapModel +} + +func (s *keyDataScopeSuite) testRecoverKeys(c *C, params *testRecoverKeysData) error { + kds, err := NewKeyDataScope(params.KeyDataScopeParams) + c.Assert(err, IsNil) + + err = kds.SetAuthorizedBootModes(params.KeyDataScopeParams.PrimaryKey, params.authRole, params.modes...) + if err != nil { + return err + } + + err = kds.SetAuthorizedSnapModels(params.KeyDataScopeParams.PrimaryKey, params.authRole, params.models...) + if err != nil { + return err + } + + s.handler.scopes = append(s.handler.scopes, kds) + + protected, unlockKey := s.mockProtectKeys(c, params.KeyDataScopeParams.PrimaryKey, params.KeyDataScopeParams.KDFAlg, params.KeyDataScopeParams.ModelAlg) + + keyData, err := NewKeyData(protected) + if err != nil { + return err + } + + recoveredUnlockKey, recoveredPrimaryKey, err := keyData.RecoverKeys() + if err != nil { + return err + } + c.Check(recoveredUnlockKey, DeepEquals, unlockKey) + c.Check(recoveredPrimaryKey, DeepEquals, params.KeyDataScopeParams.PrimaryKey) + return nil +} + +func (s *keyDataScopeSuite) TestRecoverKeysModeMismatch(c *C) { + mode := "modeFoo" + 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") + + SetBootMode(mode) + SetModel(model) + + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) + c.Check(s.testRecoverKeys(c, &testRecoverKeysData{ + KeyDataScopeParams: &KeyDataScopeParams{ + PrimaryKey: primaryKey, + Role: "roleFoo", + KDFAlg: crypto.SHA256, + MDAlg: crypto.SHA256, + ModelAlg: crypto.SHA256, + }, + authRole: "roleFoo", + modes: []string{"modeBar"}, + }), ErrorMatches, "cannot perform action because of an unexpected error: unauthorized boot mode") +} + +func (s *keyDataScopeSuite) TestRecoverKeysModelMismatch(c *C) { + mode := "modeFoo" + 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") + + SetBootMode(mode) + SetModel(model) + + modelB := testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ + "authority-id": "fake-brand", + "series": "16", + "brand-id": "fake-brand", + "model": "other-fake-model", + "grade": "secured", + }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij") + + primaryKey, err := NewPrimaryKey(32) + c.Assert(err, IsNil) + c.Check(s.testRecoverKeys(c, &testRecoverKeysData{ + KeyDataScopeParams: &KeyDataScopeParams{ + PrimaryKey: primaryKey, + Role: "foo", + KDFAlg: crypto.SHA256, + MDAlg: crypto.SHA256, + ModelAlg: crypto.SHA256, + }, + authRole: "foo", + modes: []string{mode}, + models: []SnapModel{modelB}, + }), ErrorMatches, "cannot perform action because of an unexpected error: unauthorized model") } From 4c10b4022a7a56732201f3a00884f626b1a0cb28 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 15:37:51 +0200 Subject: [PATCH 21/49] bootenv/keydata_test.go: add deriveSigner tests with fixed keys --- bootenv/keydata_test.go | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index b5d21d18..ab5f2739 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -730,6 +730,95 @@ func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) { } } +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) + + prevKey, ok := signer.(*ecdsa.PrivateKey) + c.Assert(ok, Equals, true) + + expectedDerivedKey := testutil.DecodeHexString(c, "ff7ac99d7a0f16980777b9ace6c316e43e3edb4b0575fab5c22ea80d3e031c1d") + c.Check(prevKey.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"` From 05216263c6f047c2ea987507efbba879e6e0d45c Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 15:42:08 +0200 Subject: [PATCH 22/49] keydata_legacy.go: rename primaryKey to auxKey in unmarshalV1KeyPayload as this is related to legacy behaviour. --- keydata_legacy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/keydata_legacy.go b/keydata_legacy.go index 60c7117d..40b1ce3c 100644 --- a/keydata_legacy.go +++ b/keydata_legacy.go @@ -38,7 +38,7 @@ 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 @@ -58,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 } } @@ -68,7 +68,7 @@ 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 From 6625be39d3f6b959c9df13ecc60eb515a5b708dd Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 15:47:56 +0200 Subject: [PATCH 23/49] keydata.go: expand doc comment for AuthorizedSnapModels --- keydata.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keydata.go b/keydata.go index 7bc59f6d..afb991a3 100644 --- a/keydata.go +++ b/keydata.go @@ -329,6 +329,8 @@ type keyData struct { // AuthorizedSnapModels contains information about the Snap models // that have been authorized to access the data protected by this key. + // 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"` } From 767ac132c358a220dc08201c518658cfc9ed9a4c Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:21:50 +0200 Subject: [PATCH 24/49] bootenv: make additionalData private --- bootenv/export_test.go | 4 ++-- bootenv/keydata.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 23d98c5a..563f2473 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -74,14 +74,14 @@ func (d *KeyDataScope) TestSetVersion(version int) { d.data.Version = version } -func UnmarshalAdditionalData(data []byte) (*AdditionalData, error) { +func UnmarshalAdditionalData(data []byte) (*additionalData, error) { s := cryptobyte.String(data) if !s.ReadASN1(&s, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed input") } - aad := new(AdditionalData) + aad := new(additionalData) if !s.ReadASN1Integer(&aad.Version) { return nil, errors.New("malformed version") diff --git a/bootenv/keydata.go b/bootenv/keydata.go index ef5bd4b7..e86f89bb 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -254,7 +254,7 @@ type keyDataScope struct { MDAlg hashAlg `json:"md_alg"` } -type AdditionalData struct { +type additionalData struct { Version int BaseVersion int KdfAlg hashAlg @@ -263,7 +263,7 @@ type AdditionalData struct { KeyIdentifier []byte } -func (d *AdditionalData) marshalASN1(b *cryptobyte.Builder) { +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.BaseVersion)) // baseVersion INTEGER @@ -522,7 +522,7 @@ func (d *KeyDataScope) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, a h := alg.New() h.Write(der) - aad := &AdditionalData{ + aad := &additionalData{ Version: d.data.Version, BaseVersion: baseVersion, KdfAlg: hashAlg(kdfAlg), From dae8903db13e25e022bbc2c02547f2f73db45975 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:35:36 +0200 Subject: [PATCH 25/49] bootenv/keydata.go: expand MakeAdditionalData comment --- bootenv/keydata.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index e86f89bb..8cb900d5 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -507,7 +507,9 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { } // MakeAdditionalData constructs the additional data that need to be integrity protected for -// a key data scope (in AES-GCM for example). +// 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) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) { alg := d.data.MDAlg if !alg.Available() { From d42e414711d56d71388c3c11e5e0c7a16662f01d Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:37:50 +0200 Subject: [PATCH 26/49] bootenv: move unmarshalHashAlg to export_test.go --- bootenv/export_test.go | 38 ++++++++++++++++++++++++++++++++++++++ bootenv/keydata.go | 37 ------------------------------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 563f2473..48822180 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -26,6 +26,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" + "encoding/asn1" "errors" "github.com/snapcore/secboot" @@ -74,6 +75,43 @@ func (d *KeyDataScope) TestSetVersion(version int) { d.data.Version = version } +func unmarshalHashAlg(s *cryptobyte.String) (hashAlg, error) { + var str cryptobyte.String + + if !s.ReadASN1(&str, cryptobyte_asn1.SEQUENCE) { + return 0, errors.New("malformed input") + } + + var oid asn1.ObjectIdentifier + + if !str.ReadASN1ObjectIdentifier(&oid) { + return 0, errors.New("malformed Algorithm identifier") + } + + var null uint8 + + if !str.ReadUint8(&null) { + return 0, errors.New("malformed input") + } + + if len(oid) == len(sha1Oid) { + return hashAlg(crypto.SHA1), nil + } + + switch oid[8] { + case sha224Oid[8]: + return hashAlg(crypto.SHA224), nil + case sha256Oid[8]: + return hashAlg(crypto.SHA256), nil + case sha384Oid[8]: + return hashAlg(crypto.SHA384), nil + case sha512Oid[8]: + return hashAlg(crypto.SHA512), nil + default: + return 0, errors.New("unsupported hash algorithm") + } +} + func UnmarshalAdditionalData(data []byte) (*additionalData, error) { s := cryptobyte.String(data) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 8cb900d5..8a9c3d8a 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -138,43 +138,6 @@ func (a hashAlg) marshalASN1(b *cryptobyte.Builder) { }) } -func unmarshalHashAlg(s *cryptobyte.String) (hashAlg, error) { - var str cryptobyte.String - - if !s.ReadASN1(&str, cryptobyte_asn1.SEQUENCE) { - return 0, errors.New("malformed input") - } - - var oid asn1.ObjectIdentifier - - if !str.ReadASN1ObjectIdentifier(&oid) { - return 0, errors.New("malformed Algorithm identifier") - } - - var null uint8 - - if !str.ReadUint8(&null) { - return 0, errors.New("malformed input") - } - - if len(oid) == len(sha1Oid) { - return hashAlg(crypto.SHA1), nil - } - - switch oid[8] { - case sha224Oid[8]: - return hashAlg(crypto.SHA224), nil - case sha256Oid[8]: - return hashAlg(crypto.SHA256), nil - case sha384Oid[8]: - return hashAlg(crypto.SHA384), nil - case sha512Oid[8]: - return hashAlg(crypto.SHA512), nil - default: - return 0, errors.New("unsupported hash algorithm") - } -} - // digestList corresponds to a list of digests. type digestList struct { Alg hashAlg `json:"alg"` // The digest algorithm From 8e77c31d07271e391d29eb1ab8a0c5d19e24f0b2 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:43:17 +0200 Subject: [PATCH 27/49] bootenv: change baseVersion to generation --- bootenv/export_test.go | 2 +- bootenv/keydata.go | 12 +++++++----- bootenv/keydata_test.go | 10 +++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 48822180..2559e24e 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -125,7 +125,7 @@ func UnmarshalAdditionalData(data []byte) (*additionalData, error) { return nil, errors.New("malformed version") } - if !s.ReadASN1Integer(&aad.BaseVersion) { + if !s.ReadASN1Integer(&aad.Generation) { return nil, errors.New("malformed base version") } diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 8a9c3d8a..60862cad 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -218,8 +218,10 @@ type keyDataScope struct { } type additionalData struct { - Version int - BaseVersion int + // 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 @@ -229,7 +231,7 @@ type additionalData struct { 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.BaseVersion)) // baseVersion 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 @@ -473,7 +475,7 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { // 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) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) { +func (d *KeyDataScope) MakeAdditionalData(generation int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) { alg := d.data.MDAlg if !alg.Available() { return nil, errors.New("MD algorithm unavailable") @@ -489,7 +491,7 @@ func (d *KeyDataScope) MakeAdditionalData(baseVersion int, kdfAlg crypto.Hash, a aad := &additionalData{ Version: d.data.Version, - BaseVersion: baseVersion, + Generation: generation, KdfAlg: hashAlg(kdfAlg), AuthMode: authMode, KeyIdentifierAlg: alg, diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index ab5f2739..935ce419 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -117,7 +117,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { type testMakeAdditionalDataData struct { keyDataScopeVersion int - baseVersion int + generation int authMode AuthMode mdAlg crypto.Hash keyDigestHashAlg crypto.Hash @@ -146,14 +146,14 @@ func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditi kds.TestSetVersion(data.keyDataScopeVersion) } - aadBytes, err := kds.MakeAdditionalData(data.baseVersion, data.keyDigestHashAlg, data.authMode) + aadBytes, err := kds.MakeAdditionalData(data.generation, data.keyDigestHashAlg, data.authMode) c.Check(err, IsNil) aad, err := UnmarshalAdditionalData(aadBytes) c.Assert(err, IsNil) c.Check(aad.Version, Equals, 1) - c.Check(aad.BaseVersion, Equals, data.baseVersion) + c.Check(aad.Generation, Equals, data.generation) c.Check(crypto.Hash(aad.KdfAlg), Equals, data.keyDigestHashAlg) c.Check(aad.AuthMode, Equals, data.authMode) c.Check(crypto.Hash(aad.KeyIdentifierAlg), Equals, data.signingKeyDerivationAlg) @@ -163,7 +163,7 @@ func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditi func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ - baseVersion: 1, + generation: 1, authMode: AuthModeNone, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, @@ -174,7 +174,7 @@ func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ - baseVersion: 1, + generation: 1, authMode: AuthModePassphrase, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, From b1e8248e90cc18ae89db52016e279b39f217ecdb Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:54:46 +0200 Subject: [PATCH 28/49] bootenv/keydata.go: add doc comments for KeyDataScopeParams fields --- bootenv/keydata.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 60862cad..17279ee4 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -244,9 +244,18 @@ func (d *additionalData) marshalASN1(b *cryptobyte.Builder) { type KeyDataScopeParams struct { PrimaryKey secboot.PrimaryKey Role string - KDFAlg crypto.Hash - MDAlg crypto.Hash - ModelAlg crypto.Hash + + // 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 From 8299964556add8de951fa28b9c37b29c074018aa Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 16:57:19 +0200 Subject: [PATCH 29/49] multiple: fix wording for comments mentioning ASN1 --- bootenv/keydata.go | 8 ++++---- keydata.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bootenv/keydata.go b/bootenv/keydata.go index 17279ee4..de80582d 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -267,7 +267,7 @@ type KeyDataScope struct { // 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 an ASN1 marshalled +// 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. @@ -366,10 +366,10 @@ func (d *KeyDataScope) isAuthorized() (bool, error) { // SetAuthorizedSnapModels is used to set new authorized models for an existing key data scope. // -// Each supplied model is ASN1 serialized and a digest is produced (using a model digest +// 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 an ASN1 marshalled payload containing the already authorized boot modes and the +// 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) { @@ -403,7 +403,7 @@ func (d *KeyDataScope) SetAuthorizedSnapModels(key secboot.PrimaryKey, role stri // 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 an ASN1 marshalled payload containing the already +// 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) { diff --git a/keydata.go b/keydata.go index afb991a3..92da3814 100644 --- a/keydata.go +++ b/keydata.go @@ -295,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"` From 8cec665d2f52f25b8768a6c1740a192656291aca Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:06:14 +0200 Subject: [PATCH 30/49] keydata_test.go: add extra checks in TestNewKeyDataScopeSuccess --- bootenv/export_test.go | 4 ++++ bootenv/keydata_test.go | 24 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 2559e24e..185aa37e 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -195,3 +195,7 @@ func NewPrimaryKey(sz1 int) (secboot.PrimaryKey, error) { return primaryKey, nil } + +func (d *KeyDataScope) Data() keyDataScope { + return d.data +} diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 935ce419..2ceb8f50 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -54,12 +54,17 @@ 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: "test", - KDFAlg: crypto.SHA256, - MDAlg: crypto.SHA256, - ModelAlg: crypto.SHA256, + Role: role, + KDFAlg: kdfAlg, + MDAlg: mdAlg, + ModelAlg: modelAlg, } kds, err := NewKeyDataScope(params) @@ -68,6 +73,17 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) { err = kds.IsBootEnvironmentAuthorized() c.Check(err, 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) { From 8177fbc0b9c0a3473dbcf49cdbca1226958c2f83 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:08:49 +0200 Subject: [PATCH 31/49] bootenv/keydata_test.go: add small style change --- bootenv/keydata_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 2ceb8f50..d6089505 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -71,8 +71,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeSuccess(c *C) { c.Assert(err, IsNil) c.Check(kds, NotNil) - err = kds.IsBootEnvironmentAuthorized() - c.Check(err, IsNil) + c.Check(kds.IsBootEnvironmentAuthorized(), IsNil) data := kds.Data() @@ -726,8 +725,7 @@ func (s *keyDataPlatformSuite) TestDeriveSigner(c *C) { c.Assert(err, IsNil) c.Check(kds, NotNil) - err = kds.IsBootEnvironmentAuthorized() - c.Check(err, IsNil) + c.Check(kds.IsBootEnvironmentAuthorized(), IsNil) signer, err := kds.DeriveSigner(primaryKey, role) c.Assert(err, IsNil) From 6bc418622e11e71950c2ab08412273d6a0d3e557 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:09:42 +0200 Subject: [PATCH 32/49] bootenv: move NewPrimaryKey to keydata_test.go --- bootenv/export_test.go | 11 ----------- bootenv/keydata_test.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 185aa37e..031da366 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -24,7 +24,6 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" - "crypto/rand" "crypto/x509" "encoding/asn1" "errors" @@ -186,16 +185,6 @@ func NewEcdsaPublicKey(rand []byte) (ecdsaPublicKey, error) { return pk, nil } -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 (d *KeyDataScope) Data() keyDataScope { return d.data } diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index d6089505..f5e4891d 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -36,6 +36,7 @@ import ( "golang.org/x/xerrors" . "gopkg.in/check.v1" + "github.com/snapcore/secboot" . "github.com/snapcore/secboot" . "github.com/snapcore/secboot/bootenv" "github.com/snapcore/secboot/internal/testutil" @@ -50,6 +51,16 @@ type keyDataPlatformSuite struct { 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) From da49e743d9d1447c79fce9afc32eccdc910c03f3 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:26:19 +0200 Subject: [PATCH 33/49] bootenv: convert MakeAdditionalData tests to fixed to ensure that any future changes don't break backwards compatibility. --- bootenv/export_test.go | 42 ----------------------------------------- bootenv/keydata_test.go | 28 +++++++++++++-------------- 2 files changed, 14 insertions(+), 56 deletions(-) diff --git a/bootenv/export_test.go b/bootenv/export_test.go index 031da366..d3cf454e 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -111,48 +111,6 @@ func unmarshalHashAlg(s *cryptobyte.String) (hashAlg, error) { } } -func UnmarshalAdditionalData(data []byte) (*additionalData, error) { - s := cryptobyte.String(data) - - if !s.ReadASN1(&s, cryptobyte_asn1.SEQUENCE) { - return nil, errors.New("malformed input") - } - - aad := new(additionalData) - - if !s.ReadASN1Integer(&aad.Version) { - return nil, errors.New("malformed version") - } - - if !s.ReadASN1Integer(&aad.Generation) { - return nil, errors.New("malformed base version") - } - - kdfAlg, err := unmarshalHashAlg(&s) - if err != nil { - return nil, errors.New("malformed kdf") - } - aad.KdfAlg = kdfAlg - - var authMode int - if !s.ReadASN1Enum(&authMode) { - return nil, errors.New("malformed Auth mode") - } - aad.AuthMode = secboot.AuthMode(authMode) - - keyIdAlg, err := unmarshalHashAlg(&s) - if err != nil { - return nil, errors.New("malformed kdf") - } - aad.KeyIdentifierAlg = keyIdAlg - - if !s.ReadASN1Bytes(&aad.KeyIdentifier, cryptobyte_asn1.OCTET_STRING) { - return nil, errors.New("malformed Key identifier") - } - - return aad, nil -} - func (d *KeyDataScope) TestMatch(KDFAlg crypto.Hash, keyIdentifier []byte) bool { der, err := x509.MarshalPKIXPublicKey(d.data.PublicKey.PublicKey) if err != nil { diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index f5e4891d..9cbedc29 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -142,6 +142,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { } type testMakeAdditionalDataData struct { + primaryKey PrimaryKey keyDataScopeVersion int generation int authMode AuthMode @@ -151,14 +152,12 @@ type testMakeAdditionalDataData struct { // into the additional data. signingKeyDerivationAlg crypto.Hash role string + expectedAad []byte } func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditionalDataData) { - primaryKey, err := NewPrimaryKey(32) - c.Assert(err, IsNil) - params := &KeyDataScopeParams{ - PrimaryKey: primaryKey, + PrimaryKey: data.primaryKey, Role: data.role, KDFAlg: data.signingKeyDerivationAlg, MDAlg: data.mdAlg, @@ -175,37 +174,38 @@ func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditi aadBytes, err := kds.MakeAdditionalData(data.generation, data.keyDigestHashAlg, data.authMode) c.Check(err, IsNil) - aad, err := UnmarshalAdditionalData(aadBytes) - c.Assert(err, IsNil) - - c.Check(aad.Version, Equals, 1) - c.Check(aad.Generation, Equals, data.generation) - c.Check(crypto.Hash(aad.KdfAlg), Equals, data.keyDigestHashAlg) - c.Check(aad.AuthMode, Equals, data.authMode) - c.Check(crypto.Hash(aad.KeyIdentifierAlg), Equals, data.signingKeyDerivationAlg) - - c.Check(kds.TestMatch(data.keyDigestHashAlg, aad.KeyIdentifier), Equals, true) + c.Check(aadBytes, DeepEquals, data.expectedAad) } func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { + primaryKey := testutil.DecodeHexString(c, "ab40b798dd6b47ca77d93241f40036d6d86e03f365b4ef9171b23e2bc38b9ef3") + expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0100300d06096086480165030402010500042077511e42d7c0b2df1881189bd4720806fc92a6dee76cd1c9fe40c32310f6068d") + s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + primaryKey: primaryKey, generation: 1, authMode: AuthModeNone, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, signingKeyDerivationAlg: crypto.SHA256, role: "foo", + expectedAad: expectedAad, }) } func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { + primaryKey := testutil.DecodeHexString(c, "45db13f9857336d338c12a5e71aae5434032c3419b9e4e82c2de42cf510d93ee") + expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0101300d060960864801650304020105000420765f9750024ce485a32d50c6595fa16fca71b4ea110a2e8361d070a975ba9bcc") + s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + primaryKey: primaryKey, generation: 1, authMode: AuthModePassphrase, mdAlg: crypto.SHA256, keyDigestHashAlg: crypto.SHA256, signingKeyDerivationAlg: crypto.SHA256, role: "foo", + expectedAad: expectedAad, }) } From f65f537627bddbc5227c5cf8bb6693cafcb40f87 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:34:13 +0200 Subject: [PATCH 34/49] bootenv/keydata_test.go: add extra checks for TestSetAuthorizedSnapModels --- bootenv/keydata_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 9cbedc29..c2e32f77 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -304,6 +304,20 @@ func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAu 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 } From 0cd840ad0624033bc1b197b9025476148b09a014 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:38:31 +0200 Subject: [PATCH 35/49] bootenv/keydata_test.go: cleanup tests --- bootenv/keydata_test.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index c2e32f77..6b7c7df2 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -360,21 +360,18 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { validModels := []SnapModel{ s.makeMockModelAssertion(c, "model-a"), } - data := &testSetAuthorizedSnapModelsData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, - modelAlg: crypto.SHA256, - validRole: "test", - role: "different", - validModels: validModels, - } + + validRole := "test" + kdfAlg := crypto.SHA256 + mdAlg := crypto.SHA256 + modelAlg := crypto.SHA256 params := &KeyDataScopeParams{ PrimaryKey: primaryKey, - Role: data.validRole, - KDFAlg: data.kDFAlg, - MDAlg: data.mDAlg, - ModelAlg: data.modelAlg, + Role: validRole, + KDFAlg: kdfAlg, + MDAlg: mdAlg, + ModelAlg: modelAlg, } kds, err := NewKeyDataScope(params) @@ -382,7 +379,7 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { wrongKey, err := NewPrimaryKey(32) c.Assert(err, IsNil) - err = kds.SetAuthorizedSnapModels(wrongKey, data.role, data.validModels...) + err = kds.SetAuthorizedSnapModels(wrongKey, "different", validModels...) c.Check(err, ErrorMatches, "incorrect key supplied") } From 159895f1199fe8acff03b7ff6c8a498099323d13 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:40:03 +0200 Subject: [PATCH 36/49] bootenv/keydata_test.go: consistent variable names in tests --- bootenv/keydata_test.go | 68 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 6b7c7df2..2e7f11b7 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -280,8 +280,8 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthStateErrors(c *C) { } type testSetAuthorizedSnapModelsData struct { - kDFAlg crypto.Hash - mDAlg crypto.Hash + kdfAlg crypto.Hash + mdAlg crypto.Hash modelAlg crypto.Hash validRole string role string @@ -295,8 +295,8 @@ func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAu params := &KeyDataScopeParams{ PrimaryKey: primaryKey, Role: data.validRole, - KDFAlg: data.kDFAlg, - MDAlg: data.mDAlg, + KDFAlg: data.kdfAlg, + MDAlg: data.mdAlg, ModelAlg: data.modelAlg, } @@ -310,8 +310,8 @@ func (s *keyDataPlatformSuite) testSetAuthorizedSnapModels(c *C, data *testSetAu 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) + 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) @@ -327,8 +327,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModels(c *C) { } c.Check( s.testSetAuthorizedSnapModels(c, &testSetAuthorizedSnapModelsData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "test", @@ -344,8 +344,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsInvalidRole(c *C) { } c.Check( s.testSetAuthorizedSnapModels(c, &testSetAuthorizedSnapModelsData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "different", @@ -384,8 +384,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedSnapModelsWrongKey(c *C) { } type testSetAuthorizedBootModesData struct { - kDFAlg crypto.Hash - mDAlg crypto.Hash + kdfAlg crypto.Hash + mdAlg crypto.Hash modelAlg crypto.Hash validRole string role string @@ -399,8 +399,8 @@ func (s *keyDataPlatformSuite) testSetAuthorizedBootModes(c *C, data *testSetAut params := &KeyDataScopeParams{ PrimaryKey: primaryKey, Role: data.validRole, - KDFAlg: data.kDFAlg, - MDAlg: data.mDAlg, + KDFAlg: data.kdfAlg, + MDAlg: data.mdAlg, ModelAlg: data.modelAlg, } @@ -418,8 +418,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModes(c *C) { } c.Check( s.testSetAuthorizedBootModes(c, &testSetAuthorizedBootModesData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "test", @@ -436,8 +436,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesInvalidRole(c *C) { c.Check( s.testSetAuthorizedBootModes(c, &testSetAuthorizedBootModesData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "different", @@ -453,8 +453,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { "modeFoo", } data := &testSetAuthorizedBootModesData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "different", @@ -464,8 +464,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { params := &KeyDataScopeParams{ PrimaryKey: primaryKey, Role: data.validRole, - KDFAlg: data.kDFAlg, - MDAlg: data.mDAlg, + KDFAlg: data.kdfAlg, + MDAlg: data.mdAlg, ModelAlg: data.modelAlg, } @@ -479,8 +479,8 @@ func (s *keyDataPlatformSuite) TestSetAuthorizedBootModesWrongKey(c *C) { } type testBootEnvAuthData struct { - kDFAlg crypto.Hash - mDAlg crypto.Hash + kdfAlg crypto.Hash + mdAlg crypto.Hash modelAlg crypto.Hash validRole string role string @@ -497,8 +497,8 @@ func (s *keyDataPlatformSuite) testBootEnvAuth(c *C, data *testBootEnvAuthData) params := &KeyDataScopeParams{ PrimaryKey: primaryKey, Role: data.validRole, - KDFAlg: data.kDFAlg, - MDAlg: data.mDAlg, + KDFAlg: data.kdfAlg, + MDAlg: data.mdAlg, ModelAlg: data.modelAlg, } @@ -533,8 +533,8 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthValid1(c *C) { s.mockState(c) c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "test", @@ -558,8 +558,8 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthValid2(c *C) { s.mockState(c) c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "test", @@ -584,8 +584,8 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidModel(c *C) { s.mockState(c) c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, role: "test", validRole: "test", @@ -610,8 +610,8 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidBootMode(c *C) { s.mockState(c) c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ - kDFAlg: crypto.SHA256, - mDAlg: crypto.SHA256, + kdfAlg: crypto.SHA256, + mdAlg: crypto.SHA256, modelAlg: crypto.SHA256, validRole: "test", role: "test", From e6408fdbe10ca06ce024e6047c3565cf9d2fa1df Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:42:22 +0200 Subject: [PATCH 37/49] bootenv/keydata_test.go: add extra checks for TestSetAuthorizedBootModes --- bootenv/keydata_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 2e7f11b7..44456096 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -409,6 +409,19 @@ func (s *keyDataPlatformSuite) testSetAuthorizedBootModes(c *C, data *testSetAut 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 } From 284ebca92864c866453f76f15aa0cada22c93966 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:51:34 +0200 Subject: [PATCH 38/49] keydata_test.go: rename diskUnlockKey to unlockKey --- keydata_test.go | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/keydata_test.go b/keydata_test.go index 503749cc..cf077ab8 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -1100,12 +1100,12 @@ func (s *keyDataSuite) TestWriteAtomic1(c *C) { } type testReadKeyDataData struct { - diskUnlockKey DiskUnlockKey - primaryKey PrimaryKey - id KeyID - r KeyDataReader - model SnapModel - authorized bool + unlockKey DiskUnlockKey + primaryKey PrimaryKey + id KeyID + r KeyDataReader + model SnapModel + authorized bool } func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) { @@ -1119,7 +1119,7 @@ func (s *keyDataSuite) testReadKeyData(c *C, data *testReadKeyDataData) { unlockKey, primaryKey, err := keyData.RecoverKeys() c.Check(err, IsNil) - c.Check(unlockKey, DeepEquals, data.diskUnlockKey) + c.Check(unlockKey, DeepEquals, data.unlockKey) c.Check(primaryKey, DeepEquals, data.primaryKey) } @@ -1137,10 +1137,10 @@ func (s *keyDataSuite) TestReadKeyData1(c *C) { c.Check(err, IsNil) s.testReadKeyData(c, &testReadKeyDataData{ - diskUnlockKey: unlockKey, - primaryKey: primaryKey, - id: id, - r: &mockKeyDataReader{"foo", w.Reader()}, + unlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"foo", w.Reader()}, }) } @@ -1158,10 +1158,10 @@ func (s *keyDataSuite) TestReadKeyData2(c *C) { c.Check(err, IsNil) s.testReadKeyData(c, &testReadKeyDataData{ - diskUnlockKey: unlockKey, - primaryKey: primaryKey, - id: id, - r: &mockKeyDataReader{"bar", w.Reader()}, + unlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"bar", w.Reader()}, }) } @@ -1179,10 +1179,10 @@ func (s *keyDataSuite) TestReadKeyData3(c *C) { c.Check(err, IsNil) params := &testReadKeyDataData{ - diskUnlockKey: unlockKey, - primaryKey: primaryKey, - id: id, - r: &mockKeyDataReader{"foo", w.Reader()}, + unlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"foo", w.Reader()}, } s.testReadKeyData(c, params) @@ -1202,10 +1202,10 @@ func (s *keyDataSuite) TestReadKeyData4(c *C) { c.Check(err, IsNil) params := &testReadKeyDataData{ - diskUnlockKey: unlockKey, - primaryKey: primaryKey, - id: id, - r: &mockKeyDataReader{"foo", w.Reader()}, + unlockKey: unlockKey, + primaryKey: primaryKey, + id: id, + r: &mockKeyDataReader{"foo", w.Reader()}, } s.testReadKeyData(c, params) From 2a5277c270d370e4be1e1041500e00be561b402f Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 18:55:23 +0200 Subject: [PATCH 39/49] keydata_test.go: remove TestRecoverLegacyKeyWithPassphrase as new gen 2 keys break passphrase support in a non backwards compatible way. --- keydata_test.go | 56 ------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/keydata_test.go b/keydata_test.go index cf077ab8..4846b3db 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -1309,62 +1309,6 @@ func (s *keyDataSuite) TestLegacyWriteAtomic1(c *C) { params: protected}) } -func (s *keyDataSuite) TestRecoverLegacyKeyWithPassphrase(c *C) { - s.handler.passphraseSupport = true - - var kdf testutil.MockKDF - - primaryKey := testutil.DecodeHexString(c, "5a08905fcc1977e02ab206198f56cf2e5bd3b43660e0c3b3055ff0040b722a18") - unlockKey := testutil.DecodeHexString(c, "5cf4329807a9378f0164f94894f8e3ec9fe38ee8f090816ec8626ae9c631a68b") - j := []byte( - `{` + - `"generation":1,` + - `"platform_name":"mock",` + - `"platform_handle":` + - `{` + - `"key":"fwrbemoaBvzUpbEeP1GxThfwpSVmzJvKuGjrFCYoBGI=",` + - `"iv":"JzQxiiSLllGke9XlKXzytw==",` + - `"auth-key-hmac":"qgJDnOuFLKwgMAkSpq5U4qwN/APDjrtI6qMZ/0DhcUs=",` + - `"exp-generation":1,` + - `"exp-kdf_alg":5,` + - `"exp-auth-mode":1},` + - `"role":"",` + - `"kdf_alg":"sha256",` + - `"encrypted_payload":"WwOAw8GqWvsCk2TDEu9PLaLf3Z+4Ybroj0xT++6BXPIL5aT8aKWWHFDYl7nKx8KlOa818KO6AS/S+l6Hu6BnUJ3XjDw=",` + - `"passphrase_params":` + - `{` + - `"kdf":` + - `{` + - `"type":"argon2i",` + - `"salt":"FiMNjylKKlJgNsAX80OYKg==",` + - `"time":4,` + - `"memory":1024063,` + - `"cpus":4},` + - `"encryption":"aes-cfb",` + - `"derived_key_size":32,` + - `"encryption_key_size":32,` + - `"auth_key_size":32},` + - `"authorized_snap_models":` + - `{` + - `"alg":"sha256",` + - `"kdf_alg":"sha256",` + - `"key_digest":` + - `{` + - `"alg":"sha256",` + - `"salt":"LeNyUyr7cuWxenyUhODfMBUzOfUH2rNDQWXfDywGvI0=",` + - `"digest":"qKezdUsVhiShyjxF9/oFW411A+p7Q+4ytkBJzd83luc="},` + - `"hmacs":null}} -`) - - keyData, err := ReadKeyData(&mockKeyDataReader{"foo", bytes.NewReader(j)}) - c.Assert(err, IsNil) - - recoveredUnlockKey, recoveredPrimaryKey, err := keyData.RecoverKeysWithPassphrase("passphrase", &kdf) - c.Check(err, IsNil) - c.Check(recoveredUnlockKey, DeepEquals, DiskUnlockKey(unlockKey)) - c.Check(recoveredPrimaryKey, DeepEquals, PrimaryKey(primaryKey)) -} - func (s *keyDataSuite) TestLegacyKeyPayloadUnmarshalInvalid1(c *C) { payload := make([]byte, 66) for i := range payload { From c12c559b921d7fc843287a897c0f497146b1be00 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Tue, 27 Feb 2024 19:04:57 +0200 Subject: [PATCH 40/49] keydata_test.go: add TestChangePassphraseWrongPassphrase again --- keydata_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/keydata_test.go b/keydata_test.go index 4846b3db..99941e2a 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -1064,6 +1064,25 @@ func (s *keyDataSuite) TestChangePassphraseForceIterations(c *C) { kdfOptions: &KDFOptions{ForceIterations: 3, MemoryKiB: 32 * 1024}}) } +func (s *keyDataSuite) TestChangePassphraseWrongPassphrase(c *C) { + s.handler.passphraseSupport = true + + primaryKey := s.newPrimaryKey(c, 32) + + kdfOptions := &KDFOptions{ + TargetDuration: 100 * time.Millisecond, + } + protected, _ := s.mockProtectKeysWithPassphrase(c, primaryKey, kdfOptions, 32, crypto.SHA256, crypto.SHA256) + + var kdf testutil.MockKDF + keyData, err := NewKeyDataWithPassphrase(protected, "12345678", &kdf) + c.Check(err, IsNil) + + c.Check(keyData.ChangePassphrase("passphrase", "12345678", &kdf), Equals, ErrInvalidPassphrase) + + s.checkKeyDataJSONAuthModePassphrase(c, keyData, protected, 0, "12345678", kdfOptions) +} + func (s *keyDataSuite) TestSnapModelAuthErrorHandling(c *C) { primaryKey := s.newPrimaryKey(c, 32) protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256) From 6051c176854c02fe702b2c7934eea0ea467a9796 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:10:36 +0200 Subject: [PATCH 41/49] bootenv: refactor use of currentBootMode and currentModel in tests --- bootenv/env.go | 25 ++++--------------------- bootenv/export_test.go | 34 ++++------------------------------ bootenv/keydata.go | 12 ++++++------ bootenv/keydata_test.go | 39 ++++----------------------------------- 4 files changed, 18 insertions(+), 92 deletions(-) diff --git a/bootenv/env.go b/bootenv/env.go index e5ff8cc6..32383281 100644 --- a/bootenv/env.go +++ b/bootenv/env.go @@ -20,7 +20,6 @@ package bootenv import ( - "errors" "sync/atomic" "github.com/snapcore/secboot" @@ -31,26 +30,10 @@ var ( currentBootMode atomic.Value ) -var SetModel = func(model secboot.SnapModel) bool { - return currentModel.CompareAndSwap(nil, model) +func SetModel(model secboot.SnapModel) { + currentModel.Store(model) } -var SetBootMode = func(mode string) bool { - return currentBootMode.CompareAndSwap(nil, mode) -} - -var loadCurrentModel = func() (secboot.SnapModel, error) { - model, ok := currentModel.Load().(secboot.SnapModel) - if !ok { - return nil, errors.New("SetModel hasn't been called yet") - } - return model, nil -} - -var loadCurrentBootMode = func() (string, error) { - mode, ok := currentBootMode.Load().(string) - if !ok { - return "", errors.New("SetBootMode hasn't been called yet") - } - return mode, nil +func SetBootMode(mode string) { + currentBootMode.Store(mode) } diff --git a/bootenv/export_test.go b/bootenv/export_test.go index d3cf454e..c54b6797 100644 --- a/bootenv/export_test.go +++ b/bootenv/export_test.go @@ -27,6 +27,7 @@ import ( "crypto/x509" "encoding/asn1" "errors" + "sync/atomic" "github.com/snapcore/secboot" internal_crypto "github.com/snapcore/secboot/internal/crypto" @@ -38,36 +39,9 @@ var ( ComputeSnapModelHash = computeSnapModelHash ) -func MockSetModel(f func(secboot.SnapModel) bool) (restore func()) { - origSetModel := SetModel - SetModel = f - return func() { - SetModel = origSetModel - } -} - -func MockSetBootMode(f func(string) bool) (restore func()) { - origSetBootMode := SetBootMode - SetBootMode = f - return func() { - SetBootMode = origSetBootMode - } -} - -func MockLoadCurrentModel(f func() (secboot.SnapModel, error)) (restore func()) { - origLoadCurrentModel := loadCurrentModel - loadCurrentModel = f - return func() { - loadCurrentModel = origLoadCurrentModel - } -} - -func MockLoadCurrenBootMode(f func() (string, error)) (restore func()) { - origLoadCurrentBootMode := loadCurrentBootMode - loadCurrentBootMode = f - return func() { - loadCurrentBootMode = origLoadCurrentBootMode - } +func ClearBootModeAndModel() { + currentModel = atomic.Value{} + currentBootMode = atomic.Value{} } func (d *KeyDataScope) TestSetVersion(version int) { diff --git a/bootenv/keydata.go b/bootenv/keydata.go index de80582d..d2eab3ac 100644 --- a/bootenv/keydata.go +++ b/bootenv/keydata.go @@ -437,9 +437,9 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { } if len(d.data.Params.ModelDigests.Digests) > 0 { - model, err := loadCurrentModel() - if err != nil { - return err + model, ok := currentModel.Load().(secboot.SnapModel) + if !ok { + return errors.New("SetModel hasn't been called yet") } currentModelDigest, err := computeSnapModelHash(crypto.Hash(alg), model) @@ -460,9 +460,9 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { } if len(d.data.Params.Modes) > 0 { - mode, err := loadCurrentBootMode() - if err != nil { - return err + mode, ok := currentBootMode.Load().(string) + if !ok { + return errors.New("SetBootMode hasn't been called yet") } found := false diff --git a/bootenv/keydata_test.go b/bootenv/keydata_test.go index 44456096..9eecbba8 100644 --- a/bootenv/keydata_test.go +++ b/bootenv/keydata_test.go @@ -45,8 +45,10 @@ import ( type keyDataPlatformSuite struct { snapd_testutil.BaseTest - Model SnapModel - BootMode string +} + +func (s *keyDataPlatformSuite) SetUpTest(c *C) { + ClearBootModeAndModel() } var _ = Suite(&keyDataPlatformSuite{}) @@ -209,31 +211,6 @@ func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { }) } -func (s *keyDataPlatformSuite) mockState(c *C) { - s.AddCleanup( - MockSetModel(func(model SnapModel) bool { - s.Model = model - return true - }), - ) - s.AddCleanup( - MockSetBootMode(func(mode string) bool { - s.BootMode = mode - return true - }), - ) - s.AddCleanup( - MockLoadCurrentModel(func() (SnapModel, error) { - return s.Model, nil - }), - ) - s.AddCleanup( - MockLoadCurrenBootMode(func() (string, error) { - return s.BootMode, nil - }), - ) -} - func (s *keyDataPlatformSuite) makeMockModelAssertion(c *C, modelName string) SnapModel { return testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ "authority-id": "fake-brand", @@ -543,8 +520,6 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthValid1(c *C) { "modeFoo", } - s.mockState(c) - c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ kdfAlg: crypto.SHA256, mdAlg: crypto.SHA256, @@ -568,8 +543,6 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthValid2(c *C) { "modeFoo", } - s.mockState(c) - c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ kdfAlg: crypto.SHA256, mdAlg: crypto.SHA256, @@ -594,8 +567,6 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidModel(c *C) { "modeFoo", } - s.mockState(c) - c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ kdfAlg: crypto.SHA256, mdAlg: crypto.SHA256, @@ -620,8 +591,6 @@ func (s *keyDataPlatformSuite) TestBootEnvAuthInvalidBootMode(c *C) { invalidBootMode := "modeBar" - s.mockState(c) - c.Check(s.testBootEnvAuth(c, &testBootEnvAuthData{ kdfAlg: crypto.SHA256, mdAlg: crypto.SHA256, From 8f2f77008f73e568acbdcdf528d615c3e4df78fd Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:18:53 +0200 Subject: [PATCH 42/49] bootenv: rename to bootscope --- bootenv/bootenv_test.go => bootscope/bootscope_test.go | 2 +- {bootenv => bootscope}/env.go | 2 +- {bootenv => bootscope}/export_test.go | 2 +- {bootenv => bootscope}/keydata.go | 2 +- {bootenv => bootscope}/keydata_test.go | 4 ++-- {bootenv => bootscope}/snap.go | 2 +- {bootenv => bootscope}/snap_test.go | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) rename bootenv/bootenv_test.go => bootscope/bootscope_test.go (97%) rename {bootenv => bootscope}/env.go (98%) rename {bootenv => bootscope}/export_test.go (99%) rename {bootenv => bootscope}/keydata.go (99%) rename {bootenv => bootscope}/keydata_test.go (99%) rename {bootenv => bootscope}/snap.go (99%) rename {bootenv => bootscope}/snap_test.go (91%) diff --git a/bootenv/bootenv_test.go b/bootscope/bootscope_test.go similarity index 97% rename from bootenv/bootenv_test.go rename to bootscope/bootscope_test.go index 9a61ee55..563dc19a 100644 --- a/bootenv/bootenv_test.go +++ b/bootscope/bootscope_test.go @@ -17,7 +17,7 @@ * */ -package bootenv +package bootscope import ( "testing" diff --git a/bootenv/env.go b/bootscope/env.go similarity index 98% rename from bootenv/env.go rename to bootscope/env.go index 32383281..10a7a4cd 100644 --- a/bootenv/env.go +++ b/bootscope/env.go @@ -17,7 +17,7 @@ * */ -package bootenv +package bootscope import ( "sync/atomic" diff --git a/bootenv/export_test.go b/bootscope/export_test.go similarity index 99% rename from bootenv/export_test.go rename to bootscope/export_test.go index c54b6797..bbef6248 100644 --- a/bootenv/export_test.go +++ b/bootscope/export_test.go @@ -17,7 +17,7 @@ * */ -package bootenv +package bootscope import ( "bytes" diff --git a/bootenv/keydata.go b/bootscope/keydata.go similarity index 99% rename from bootenv/keydata.go rename to bootscope/keydata.go index d2eab3ac..63b8380d 100644 --- a/bootenv/keydata.go +++ b/bootscope/keydata.go @@ -17,7 +17,7 @@ * */ -package bootenv +package bootscope import ( "bytes" diff --git a/bootenv/keydata_test.go b/bootscope/keydata_test.go similarity index 99% rename from bootenv/keydata_test.go rename to bootscope/keydata_test.go index 9eecbba8..b2afdc4e 100644 --- a/bootenv/keydata_test.go +++ b/bootscope/keydata_test.go @@ -17,7 +17,7 @@ * */ -package bootenv_test +package bootscope_test import ( "bytes" @@ -38,7 +38,7 @@ import ( "github.com/snapcore/secboot" . "github.com/snapcore/secboot" - . "github.com/snapcore/secboot/bootenv" + . "github.com/snapcore/secboot/bootscope" "github.com/snapcore/secboot/internal/testutil" snapd_testutil "github.com/snapcore/snapd/testutil" ) diff --git a/bootenv/snap.go b/bootscope/snap.go similarity index 99% rename from bootenv/snap.go rename to bootscope/snap.go index ed5cc84c..489fc9e9 100644 --- a/bootenv/snap.go +++ b/bootscope/snap.go @@ -17,7 +17,7 @@ * */ -package bootenv +package bootscope import ( "crypto" diff --git a/bootenv/snap_test.go b/bootscope/snap_test.go similarity index 91% rename from bootenv/snap_test.go rename to bootscope/snap_test.go index 7bb7b96d..4dc76127 100644 --- a/bootenv/snap_test.go +++ b/bootscope/snap_test.go @@ -17,13 +17,13 @@ * */ -package bootenv_test +package bootscope_test import ( "crypto" "encoding/base64" - "github.com/snapcore/secboot/bootenv" + "github.com/snapcore/secboot/bootscope" "github.com/snapcore/secboot/internal/testutil" . "gopkg.in/check.v1" ) @@ -46,7 +46,7 @@ func (s *snapSuite) TestComputeSnapModelHash(c *C) { expected, err := base64.StdEncoding.DecodeString("OdtD1Oz+LVG4A77RTkE1JaKopD8p/AxcUQsa9M/PPrU=") c.Assert(err, IsNil) - modelAsn, err := bootenv.ComputeSnapModelHash(alg, model) + modelAsn, err := bootscope.ComputeSnapModelHash(alg, model) c.Assert(err, IsNil) c.Check(modelAsn, DeepEquals, expected) } From 27bdb5fb5ffffbb0b2ea4543ba59f1630bc1c4af Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:28:40 +0200 Subject: [PATCH 43/49] bootscope: rename env.go to scope.go and add package doc comment --- bootscope/{env.go => scope.go} | 6 ++++++ 1 file changed, 6 insertions(+) rename bootscope/{env.go => scope.go} (74%) diff --git a/bootscope/env.go b/bootscope/scope.go similarity index 74% rename from bootscope/env.go rename to bootscope/scope.go index 10a7a4cd..3fdc3d75 100644 --- a/bootscope/env.go +++ b/bootscope/scope.go @@ -17,6 +17,12 @@ * */ +// 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 ( From 65291ec8f6990cca4be849200356d4ae3177f8ae Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:30:54 +0200 Subject: [PATCH 44/49] bootscope: rename MakeAdditionalData to MakeAEADAdditionalData --- bootscope/keydata.go | 4 ++-- bootscope/keydata_test.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bootscope/keydata.go b/bootscope/keydata.go index 63b8380d..e393c939 100644 --- a/bootscope/keydata.go +++ b/bootscope/keydata.go @@ -480,11 +480,11 @@ func (d *KeyDataScope) IsBootEnvironmentAuthorized() error { return nil } -// MakeAdditionalData constructs the additional data that need to be integrity protected for +// 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) MakeAdditionalData(generation int, kdfAlg crypto.Hash, authMode secboot.AuthMode) ([]byte, error) { +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") diff --git a/bootscope/keydata_test.go b/bootscope/keydata_test.go index b2afdc4e..0aacf8d7 100644 --- a/bootscope/keydata_test.go +++ b/bootscope/keydata_test.go @@ -143,7 +143,7 @@ func (s *keyDataPlatformSuite) TestNewKeyDataScopeErrorMissingModelAlg(c *C) { c.Assert(err, ErrorMatches, "No model digest algorithm specified") } -type testMakeAdditionalDataData struct { +type testMakeAEADAdditionalDataData struct { primaryKey PrimaryKey keyDataScopeVersion int generation int @@ -157,7 +157,7 @@ type testMakeAdditionalDataData struct { expectedAad []byte } -func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditionalDataData) { +func (s *keyDataPlatformSuite) testMakeAEADAdditionalData(c *C, data *testMakeAEADAdditionalDataData) { params := &KeyDataScopeParams{ PrimaryKey: data.primaryKey, Role: data.role, @@ -173,17 +173,17 @@ func (s *keyDataPlatformSuite) testMakeAdditionalData(c *C, data *testMakeAdditi kds.TestSetVersion(data.keyDataScopeVersion) } - aadBytes, err := kds.MakeAdditionalData(data.generation, data.keyDigestHashAlg, data.authMode) + aadBytes, err := kds.MakeAEADAdditionalData(data.generation, data.keyDigestHashAlg, data.authMode) c.Check(err, IsNil) c.Check(aadBytes, DeepEquals, data.expectedAad) } -func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { +func (s *keyDataPlatformSuite) TestMakeAEADAdditionalData(c *C) { primaryKey := testutil.DecodeHexString(c, "ab40b798dd6b47ca77d93241f40036d6d86e03f365b4ef9171b23e2bc38b9ef3") expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0100300d06096086480165030402010500042077511e42d7c0b2df1881189bd4720806fc92a6dee76cd1c9fe40c32310f6068d") - s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + s.testMakeAEADAdditionalData(c, &testMakeAEADAdditionalDataData{ primaryKey: primaryKey, generation: 1, authMode: AuthModeNone, @@ -195,11 +195,11 @@ func (s *keyDataPlatformSuite) TestMakeAdditionalData(c *C) { }) } -func (s *keyDataPlatformSuite) TestMakeAdditionalDataWithPassphrase(c *C) { +func (s *keyDataPlatformSuite) TestMakeAEADAdditionalDataWithPassphrase(c *C) { primaryKey := testutil.DecodeHexString(c, "45db13f9857336d338c12a5e71aae5434032c3419b9e4e82c2de42cf510d93ee") expectedAad := testutil.DecodeHexString(c, "3049020101020101300d060960864801650304020105000a0101300d060960864801650304020105000420765f9750024ce485a32d50c6595fa16fca71b4ea110a2e8361d070a975ba9bcc") - s.testMakeAdditionalData(c, &testMakeAdditionalDataData{ + s.testMakeAEADAdditionalData(c, &testMakeAEADAdditionalDataData{ primaryKey: primaryKey, generation: 1, authMode: AuthModePassphrase, From 07bab2969a120132fc590b52192db00a51bbb4fd Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:32:16 +0200 Subject: [PATCH 45/49] bootscope/export_test.go: remove unmarshalHashAlg --- bootscope/export_test.go | 41 ---------------------------------------- 1 file changed, 41 deletions(-) diff --git a/bootscope/export_test.go b/bootscope/export_test.go index bbef6248..3f8b0224 100644 --- a/bootscope/export_test.go +++ b/bootscope/export_test.go @@ -25,14 +25,10 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/x509" - "encoding/asn1" - "errors" "sync/atomic" "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" ) var ( @@ -48,43 +44,6 @@ func (d *KeyDataScope) TestSetVersion(version int) { d.data.Version = version } -func unmarshalHashAlg(s *cryptobyte.String) (hashAlg, error) { - var str cryptobyte.String - - if !s.ReadASN1(&str, cryptobyte_asn1.SEQUENCE) { - return 0, errors.New("malformed input") - } - - var oid asn1.ObjectIdentifier - - if !str.ReadASN1ObjectIdentifier(&oid) { - return 0, errors.New("malformed Algorithm identifier") - } - - var null uint8 - - if !str.ReadUint8(&null) { - return 0, errors.New("malformed input") - } - - if len(oid) == len(sha1Oid) { - return hashAlg(crypto.SHA1), nil - } - - switch oid[8] { - case sha224Oid[8]: - return hashAlg(crypto.SHA224), nil - case sha256Oid[8]: - return hashAlg(crypto.SHA256), nil - case sha384Oid[8]: - return hashAlg(crypto.SHA384), nil - case sha512Oid[8]: - return hashAlg(crypto.SHA512), nil - default: - return 0, errors.New("unsupported hash algorithm") - } -} - func (d *KeyDataScope) TestMatch(KDFAlg crypto.Hash, keyIdentifier []byte) bool { der, err := x509.MarshalPKIXPublicKey(d.data.PublicKey.PublicKey) if err != nil { From 6a0df9b7a09b3192a8309341458936f5bf592311 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:34:26 +0200 Subject: [PATCH 46/49] bootscope/keydata_test.go: remove tests for mockPlatformKeyDataHandler --- bootscope/keydata_test.go | 104 -------------------------------------- 1 file changed, 104 deletions(-) diff --git a/bootscope/keydata_test.go b/bootscope/keydata_test.go index 0aacf8d7..56119132 100644 --- a/bootscope/keydata_test.go +++ b/bootscope/keydata_test.go @@ -1009,107 +1009,3 @@ func (s *keyDataScopeSuite) mockProtectKeys(c *C, primaryKey PrimaryKey, KDFAlg return out, unlockKey } - -type testRecoverKeysData struct { - *KeyDataScopeParams - authRole string - modes []string - models []SnapModel -} - -func (s *keyDataScopeSuite) testRecoverKeys(c *C, params *testRecoverKeysData) error { - kds, err := NewKeyDataScope(params.KeyDataScopeParams) - c.Assert(err, IsNil) - - err = kds.SetAuthorizedBootModes(params.KeyDataScopeParams.PrimaryKey, params.authRole, params.modes...) - if err != nil { - return err - } - - err = kds.SetAuthorizedSnapModels(params.KeyDataScopeParams.PrimaryKey, params.authRole, params.models...) - if err != nil { - return err - } - - s.handler.scopes = append(s.handler.scopes, kds) - - protected, unlockKey := s.mockProtectKeys(c, params.KeyDataScopeParams.PrimaryKey, params.KeyDataScopeParams.KDFAlg, params.KeyDataScopeParams.ModelAlg) - - keyData, err := NewKeyData(protected) - if err != nil { - return err - } - - recoveredUnlockKey, recoveredPrimaryKey, err := keyData.RecoverKeys() - if err != nil { - return err - } - c.Check(recoveredUnlockKey, DeepEquals, unlockKey) - c.Check(recoveredPrimaryKey, DeepEquals, params.KeyDataScopeParams.PrimaryKey) - return nil -} - -func (s *keyDataScopeSuite) TestRecoverKeysModeMismatch(c *C) { - mode := "modeFoo" - 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") - - SetBootMode(mode) - SetModel(model) - - primaryKey, err := NewPrimaryKey(32) - c.Assert(err, IsNil) - c.Check(s.testRecoverKeys(c, &testRecoverKeysData{ - KeyDataScopeParams: &KeyDataScopeParams{ - PrimaryKey: primaryKey, - Role: "roleFoo", - KDFAlg: crypto.SHA256, - MDAlg: crypto.SHA256, - ModelAlg: crypto.SHA256, - }, - authRole: "roleFoo", - modes: []string{"modeBar"}, - }), ErrorMatches, "cannot perform action because of an unexpected error: unauthorized boot mode") -} - -func (s *keyDataScopeSuite) TestRecoverKeysModelMismatch(c *C) { - mode := "modeFoo" - 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") - - SetBootMode(mode) - SetModel(model) - - modelB := testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ - "authority-id": "fake-brand", - "series": "16", - "brand-id": "fake-brand", - "model": "other-fake-model", - "grade": "secured", - }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij") - - primaryKey, err := NewPrimaryKey(32) - c.Assert(err, IsNil) - c.Check(s.testRecoverKeys(c, &testRecoverKeysData{ - KeyDataScopeParams: &KeyDataScopeParams{ - PrimaryKey: primaryKey, - Role: "foo", - KDFAlg: crypto.SHA256, - MDAlg: crypto.SHA256, - ModelAlg: crypto.SHA256, - }, - authRole: "foo", - modes: []string{mode}, - models: []SnapModel{modelB}, - }), ErrorMatches, "cannot perform action because of an unexpected error: unauthorized model") -} From 29521d8019e64114ad6b49cbe0718edc57e9dc7f Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:37:01 +0200 Subject: [PATCH 47/49] keydata_test.go: move and rename TestSnapModelAuthErrorHandling --- keydata_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/keydata_test.go b/keydata_test.go index 99941e2a..ce4bb163 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -1083,19 +1083,6 @@ func (s *keyDataSuite) TestChangePassphraseWrongPassphrase(c *C) { s.checkKeyDataJSONAuthModePassphrase(c, keyData, protected, 0, "12345678", kdfOptions) } -func (s *keyDataSuite) TestSnapModelAuthErrorHandling(c *C) { - primaryKey := s.newPrimaryKey(c, 32) - protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256) - keyData, err := NewKeyData(protected) - - w := makeMockKeyDataWriter() - c.Check(keyData.WriteAtomic(w), IsNil) - - authorized, err := keyData.IsSnapModelAuthorized(primaryKey, nil) - c.Check(err, ErrorMatches, "unsupported key data generation number") - c.Check(authorized, Equals, false) -} - type testWriteAtomicData struct { keyData *KeyData params *KeyParams @@ -1535,6 +1522,19 @@ func (s *keyDataSuite) TestLegacySnapModelAuth6(c *C) { authorized: false}) } +func (s *keyDataSuite) TestLegacySnapModelAuthErrorHandling(c *C) { + primaryKey := s.newPrimaryKey(c, 32) + protected, _ := s.mockProtectKeys(c, primaryKey, crypto.SHA256, crypto.SHA256) + keyData, err := NewKeyData(protected) + + w := makeMockKeyDataWriter() + c.Check(keyData.WriteAtomic(w), IsNil) + + authorized, err := keyData.IsSnapModelAuthorized(primaryKey, nil) + c.Check(err, ErrorMatches, "unsupported key data generation number") + c.Check(authorized, Equals, false) +} + func (s *keyDataSuite) TestLegacySetAuthorizedSnapModelsWithWrongKey(c *C) { j := []byte( `{` + From 576b3cfc083e392dd31fce11e0e0e63bd2cc1650 Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 01:39:02 +0200 Subject: [PATCH 48/49] bootscope/keydata_test.go: fix typo --- bootscope/keydata_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootscope/keydata_test.go b/bootscope/keydata_test.go index 56119132..4fe3c4db 100644 --- a/bootscope/keydata_test.go +++ b/bootscope/keydata_test.go @@ -767,11 +767,11 @@ func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey1(c *C) { signer, err := kds.DeriveSigner(primaryKey, role) c.Assert(err, IsNil) - prevKey, ok := signer.(*ecdsa.PrivateKey) + privKey, ok := signer.(*ecdsa.PrivateKey) c.Assert(ok, Equals, true) expectedDerivedKey := testutil.DecodeHexString(c, "ff7ac99d7a0f16980777b9ace6c316e43e3edb4b0575fab5c22ea80d3e031c1d") - c.Check(prevKey.X.Bytes(), DeepEquals, expectedDerivedKey) + c.Check(privKey.X.Bytes(), DeepEquals, expectedDerivedKey) } func (s *keyDataPlatformSuite) TestDeriveSignerFixedKey2(c *C) { From 7957f527fd1a9f6fadee5cc293be5628e256ea5d Mon Sep 17 00:00:00 2001 From: Spyros Seimenis Date: Wed, 28 Feb 2024 02:09:42 +0200 Subject: [PATCH 49/49] bootscope/keydata: add marshal and unmarshal JSON for KeyDataScope --- bootscope/keydata.go | 13 +++++++++++++ bootscope/keydata_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/bootscope/keydata.go b/bootscope/keydata.go index e393c939..d2882b47 100644 --- a/bootscope/keydata.go +++ b/bootscope/keydata.go @@ -264,6 +264,19 @@ 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 diff --git a/bootscope/keydata_test.go b/bootscope/keydata_test.go index 4fe3c4db..453b6784 100644 --- a/bootscope/keydata_test.go +++ b/bootscope/keydata_test.go @@ -712,6 +712,35 @@ func (s *keyDataPlatformSuite) TestEcdsaPublicKeyUnmarshalJSONInvalid(c *C) { 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)