From 0d14824f7740833a635043ebe91f70bc82b7707e Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 1 Feb 2024 23:58:50 +0000 Subject: [PATCH 1/6] Add plainkey platform This platform provides a way to unlock an encrypted container using a key recovered from unlocking another container, and is suitable when access to one encrypted container implies access to another one. The intention of this is to replace the existing mechanism for unlocking the save partition in run mode with something that uses a proper keyslot. Keys are protected with a "platform key" - this is the key that is stored in the first encrypted container. For the intended use case, this key is stored inside the data partition, and then loaded by snap-bootstrap, which should call plainkey.SetPlatformKeys before unlocking the save partition. Although normally there will only be a single key supplied to SetPlatformKeys, it supports setting multiple keys and then matching a key data object to the correct one. This is to support the possibility of changing this key during reprovisioning (ie, as part of factory reset), where there would need to exist 2 keys (the old and new one) temporarily - this would prevent against loss of data if the process was interrupted at any point. --- plainkey/export_test.go | 46 +++++++ plainkey/keydata.go | 264 +++++++++++++++++++++++++++++++++++ plainkey/keydata_test.go | 206 ++++++++++++++++++++++++++++ plainkey/plainkey_test.go | 33 +++++ plainkey/platform.go | 156 +++++++++++++++++++++ plainkey/platform_test.go | 281 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 986 insertions(+) create mode 100644 plainkey/export_test.go create mode 100644 plainkey/keydata.go create mode 100644 plainkey/keydata_test.go create mode 100644 plainkey/plainkey_test.go create mode 100644 plainkey/platform.go create mode 100644 plainkey/platform_test.go diff --git a/plainkey/export_test.go b/plainkey/export_test.go new file mode 100644 index 00000000..fd2d8a97 --- /dev/null +++ b/plainkey/export_test.go @@ -0,0 +1,46 @@ +// -*- 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 plainkey + +import "github.com/snapcore/secboot" + +const ( + PlatformName = platformName +) + +type ( + AdditionalData = additionalData + HashAlg = hashAlg + KeyData = keyData + PlatformKeyDataHandler = platformKeyDataHandler + PlatformKeyId = platformKeyId +) + +var ( + DeriveAESKey = deriveAESKey +) + +func MockSecbootNewKeyData(fn func(*secboot.KeyParams) (*secboot.KeyData, error)) (restore func()) { + orig := secbootNewKeyData + secbootNewKeyData = fn + return func() { + secbootNewKeyData = orig + } +} diff --git a/plainkey/keydata.go b/plainkey/keydata.go new file mode 100644 index 00000000..565f00d1 --- /dev/null +++ b/plainkey/keydata.go @@ -0,0 +1,264 @@ +// -*- 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 plainkey + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "encoding/asn1" + "encoding/json" + "fmt" + "hash" + "io" + + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" + "golang.org/x/crypto/hkdf" + + "github.com/snapcore/secboot" +) + +const ( + symKeySaltSize = 32 + nonceSize = 12 +) + +var ( + nilHash hashAlg = 0 + 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} + + secbootNewKeyData = secboot.NewKeyData +) + +// hashAlg corresponds to a digest algorithm. +// XXX: This is the third place this appears now - we almost certainly want to put this +// in one place. Maybe for another PR. +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) Size() int { + return crypto.Hash(a).Size() +} + +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 + }) +} + +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" + case crypto.Hash(nilHash): + s = "null" + 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 deriveAESKey(ikm, salt []byte) []byte { + r := hkdf.New(crypto.SHA256.New, ikm, salt, []byte("ENCRYPT")) + + key := make([]byte, 32) + if _, err := io.ReadFull(r, key); err != nil { + panic(fmt.Sprintf("cannot derive key: %v", err)) + } + + return key +} + +type additionalData struct { + Version int + Generation int + KDFAlg hashAlg + AuthMode secboot.AuthMode +} + +func (d additionalData) MarshalASN1(b *cryptobyte.Builder) { + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + b.AddASN1Int64(int64(d.Version)) + b.AddASN1Int64(int64(d.Generation)) + d.KDFAlg.MarshalASN1(b) + b.AddASN1Enum(int64(d.AuthMode)) + }) +} + +type platformKeyId struct { + Alg hashAlg `json:"alg"` + Salt []byte `json:"salt"` + Digest []byte `json:"digest"` +} + +type keyData struct { + Version int `json:"version"` + Nonce []byte `json:"nonce"` + + PlatformKeyID platformKeyId `json:"platform-key-id"` +} + +// NewProtectedKey creates a new key that is protected by this platform with the supplied +// platform key. The platform key is typically stored inside of an encrypted container that +// is unlocked via another mechanism, such as a TPM, and then loaded via [SetPlatformKeys] +// or [AddPlatformKeys] after unlocking that container. +// +// If primaryKey isn't supplied, then one will be generated. +func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.PrimaryKey) (protectedKey *secboot.KeyData, primaryKeyOut secboot.PrimaryKey, unlockKey secboot.DiskUnlockKey, err error) { + if len(primaryKey) == 0 { + primaryKey = make(secboot.PrimaryKey, 32) + if _, err := io.ReadFull(rand, primaryKey); err != nil { + return nil, nil, nil, fmt.Errorf("cannot obtain primary key: %w", err) + } + + } + + kdfAlg := crypto.SHA256 + unlockKey, payload, err := secboot.MakeDiskUnlockKey(rand, kdfAlg, primaryKey) + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot create new unlock key: %w", err) + } + + // The nonce contains a 32 byte salt and 12 byte GCM nonce + nonce := make([]byte, symKeySaltSize+nonceSize) + if _, err := io.ReadFull(rand, nonce); err != nil { + return nil, nil, nil, fmt.Errorf("cannot obtain nonce: %w", err) + } + + aad := additionalData{ + Version: 1, + Generation: secboot.KeyDataGeneration, + KDFAlg: hashAlg(kdfAlg), + AuthMode: secboot.AuthModeNone, + } + builder := cryptobyte.NewBuilder(nil) + aad.MarshalASN1(builder) + aadBytes, err := builder.Bytes() + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot serialize AAD: %w", err) + } + + idAlg := crypto.SHA256 + salt := make([]byte, idAlg.Size()) + if _, err := io.ReadFull(rand, salt); err != nil { + return nil, nil, nil, fmt.Errorf("cannot obtain salt for platform key ID: %w", err) + } + id := platformKeyId{ + Alg: hashAlg(idAlg), + Salt: salt, + } + h := hmac.New(id.Alg.New, platformKey) + h.Write(id.Salt) + id.Digest = h.Sum(nil) + + b, err := aes.NewCipher(deriveAESKey(platformKey, nonce[:symKeySaltSize])) + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot create cipher: %w", err) + } + aead, err := cipher.NewGCM(b) + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot create AEAD: %w", err) + } + ciphertext := aead.Seal(nil, nonce[symKeySaltSize:], payload, aadBytes) + + kd, err := secbootNewKeyData(&secboot.KeyParams{ + Handle: &keyData{ + Version: 1, + Nonce: nonce, + PlatformKeyID: id, + }, + EncryptedPayload: ciphertext, + PlatformName: platformName, + KDFAlg: kdfAlg, + }) + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot create key data: %w", err) + } + + return kd, primaryKey, unlockKey, nil +} diff --git a/plainkey/keydata_test.go b/plainkey/keydata_test.go new file mode 100644 index 00000000..9670708e --- /dev/null +++ b/plainkey/keydata_test.go @@ -0,0 +1,206 @@ +// -*- 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 plainkey_test + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "encoding/json" + + "golang.org/x/crypto/cryptobyte" + . "gopkg.in/check.v1" + + "github.com/snapcore/secboot" + "github.com/snapcore/secboot/internal/testutil" + . "github.com/snapcore/secboot/plainkey" +) + +type keydataSuite struct{} + +var _ = Suite(&keydataSuite{}) + +type testNewProtectedKeyParams struct { + rand []byte + platformKey []byte + primaryKey secboot.PrimaryKey + + expectedPrimaryKey secboot.PrimaryKey + expectedUnlockKey secboot.DiskUnlockKey + expectedNonce []byte + expectedPlatformKeyIdSalt []byte + expectedPlatformKeyId []byte + expectedCiphertext []byte + expectedPlaintext []byte +} + +func (s *keydataSuite) testNewProtectedKey(c *C, params *testNewProtectedKeyParams) { + // Note that these tests will fail if secboot.KeyDataGeneration changes because the + // expected ciphertexts will need to be updated. It would also be worth adapting + // the tests in platformSuite to use the new version as well, as those are based + // on the data here. + var expectedHandle []byte + restore := MockSecbootNewKeyData(func(keyParams *secboot.KeyParams) (*secboot.KeyData, error) { + c.Assert(keyParams.Handle, testutil.ConvertibleTo, &KeyData{}) + + kd := keyParams.Handle.(*KeyData) + c.Check(kd.Version, Equals, 1) + c.Assert(kd.Nonce, DeepEquals, params.expectedNonce) + c.Check(crypto.Hash(kd.PlatformKeyID.Alg), Equals, crypto.SHA256) + c.Check(kd.PlatformKeyID.Salt, DeepEquals, params.expectedPlatformKeyIdSalt) + c.Check(kd.PlatformKeyID.Digest, DeepEquals, params.expectedPlatformKeyId) + + c.Check(keyParams.EncryptedPayload, DeepEquals, params.expectedCiphertext) + c.Check(keyParams.PlatformName, Equals, PlatformName) + c.Check(keyParams.KDFAlg, Equals, crypto.SHA256) + + var err error + expectedHandle, err = json.Marshal(kd) + c.Assert(err, IsNil) + + b, err := aes.NewCipher(DeriveAESKey(params.platformKey, kd.Nonce[:32])) + c.Assert(err, IsNil) + + aead, err := cipher.NewGCM(b) + c.Assert(err, IsNil) + + aad := AdditionalData{ + Version: kd.Version, + Generation: secboot.KeyDataGeneration, + KDFAlg: HashAlg(crypto.SHA256), + AuthMode: secboot.AuthModeNone, + } + builder := cryptobyte.NewBuilder(nil) + aad.MarshalASN1(builder) + aadBytes, err := builder.Bytes() + c.Check(err, IsNil) + + payload, err := aead.Open(nil, kd.Nonce[32:], keyParams.EncryptedPayload, aadBytes) + c.Check(err, IsNil) + c.Check(payload, DeepEquals, params.expectedPlaintext) + + return secboot.NewKeyData(keyParams) + }) + defer restore() + + kd, primaryKey, unlockKey, err := NewProtectedKey(bytes.NewReader(params.rand), params.platformKey, params.primaryKey) + c.Assert(err, IsNil) + c.Check(primaryKey, DeepEquals, params.expectedPrimaryKey) + c.Check(unlockKey, DeepEquals, params.expectedUnlockKey) + + var handle json.RawMessage + c.Check(kd.UnmarshalPlatformHandle(&handle), IsNil) + c.Check([]byte(handle), DeepEquals, expectedHandle) +} + +func (s *keydataSuite) TestNewProtectedKey(c *C) { + s.testNewProtectedKey(c, &testNewProtectedKeyParams{ + rand: testutil.DecodeHexString(c, "179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40edada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + platformKey: testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), + primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), + expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *keydataSuite) TestNewProtectedKeyDifferentRand(c *C) { + s.testNewProtectedKey(c, &testNewProtectedKeyParams{ + rand: testutil.DecodeHexString(c, "c4f8f04115eb2320f2bba3777240234b535d666b64c0ab10fe32e9b44e07c436dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d0931689b9b05919c41170f32cb00132b030249e7b9c614d160be5985a031654c9bba87842c40e8d1b7f8adf0b277e"), + platformKey: testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), + primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedUnlockKey: testutil.DecodeHexString(c, "0187af22705f098123812aa31032ce003f24bd69649d260153604fb7c0293925"), + expectedNonce: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d0931689b9b05919c41170f32cb001"), + expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "32b030249e7b9c614d160be5985a031654c9bba87842c40e8d1b7f8adf0b277e"), + expectedPlatformKeyId: testutil.DecodeHexString(c, "777cae054f1c5103149f5e30152fad0b197b3f0bb4b801327307aca50a02acff"), + expectedCiphertext: testutil.DecodeHexString(c, "b268bb69cafa29a490511819e12f0da25454bf724a76fc9a17b6f72019353371d6c8c13c26251e9e3169936146aa725da7000f39cebdc873adbb6bc6d02c64a6069e71b0c3116657ff8498164a7ab5e6488f552ccc88"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420c4f8f04115eb2320f2bba3777240234b535d666b64c0ab10fe32e9b44e07c436"), + }) +} + +func (s *keydataSuite) TestNewProtectedKeyDifferentPlatformKey(c *C) { + s.testNewProtectedKey(c, &testNewProtectedKeyParams{ + rand: testutil.DecodeHexString(c, "179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40edada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + platformKey: testutil.DecodeHexString(c, "1d3ae75ec26e284ab2f032256202d653025f2a1969d956a7c3b582aa368db198"), + primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), + expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + expectedPlatformKeyId: testutil.DecodeHexString(c, "a7d200c3951659d5132db221a376cfd937d65e6e991e651b62fbed48855efeaf"), + expectedCiphertext: testutil.DecodeHexString(c, "ad5f76499f91a47a04b1a1e26625cb4e18f6ac38e888b0a2882853d23bfdd6a3d8f1feecf0956cf3667817009c2c3023331e2601dc94f5aad80a1996dcc691b3f9b430ddc7a5cad10566ee530311a3bf267bff9a81b8"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *keydataSuite) TestNewProtectedKeyDifferentPrimaryKey(c *C) { + s.testNewProtectedKey(c, &testNewProtectedKeyParams{ + rand: testutil.DecodeHexString(c, "179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40edada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + platformKey: testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), + primaryKey: testutil.DecodeHexString(c, "6e5eb7de5a75ec77bbec2f0927f6503bc7d0e2a6ebcb971d7dbe7a77e0d924a7"), + expectedPrimaryKey: testutil.DecodeHexString(c, "6e5eb7de5a75ec77bbec2f0927f6503bc7d0e2a6ebcb971d7dbe7a77e0d924a7"), + expectedUnlockKey: testutil.DecodeHexString(c, "0685e55582e4465ba2336e95304166eaa839d1a645dec1c0629a63f9748fb182"), + expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c3cab20fc4def991994b1f7ea6582b7eae091c2ed1e40508b38e7cdeca313b33d728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b23778d5679118961498d551cacecf81ee9"), + expectedPlaintext: testutil.DecodeHexString(c, "304404206e5eb7de5a75ec77bbec2f0927f6503bc7d0e2a6ebcb971d7dbe7a77e0d924a70420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *keydataSuite) TestNewProtectedKeyGeneratePrimaryKey(c *C) { + s.testNewProtectedKey(c, &testNewProtectedKeyParams{ + rand: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40edada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + platformKey: testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), + expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), + expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), + expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *keydataSuite) TestKeyDataMarshalAndUnmarshal(c *C) { + orig := &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + } + + b, err := json.Marshal(orig) + c.Check(err, IsNil) + c.Check(b, DeepEquals, []byte(`{"version":1,"nonce":"1LC2+izu+rryH4jqQs+441ODWtnBkEScwBpdJ13chMsHhTXMEBudEtm49A4=","platform-key-id":{"alg":"sha256","salt":"2tqBZOoNYvf8ItCcw0vUNARVS7X/xRk31UbJqX1o4v4=","digest":"EZgSUzlG0EzT/nJib2HPNkh3qPGmZjzo8GBNpSzwuPM="}}`)) + + var unmarshalled *KeyData + c.Assert(json.Unmarshal(b, &unmarshalled), IsNil) + c.Check(unmarshalled, DeepEquals, orig) +} diff --git a/plainkey/plainkey_test.go b/plainkey/plainkey_test.go new file mode 100644 index 00000000..91124697 --- /dev/null +++ b/plainkey/plainkey_test.go @@ -0,0 +1,33 @@ +// -*- 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 plainkey_test + +import ( + "os" + "testing" + + . "gopkg.in/check.v1" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func Test(t *testing.T) { TestingT(t) } diff --git a/plainkey/platform.go b/plainkey/platform.go new file mode 100644 index 00000000..582fcfdd --- /dev/null +++ b/plainkey/platform.go @@ -0,0 +1,156 @@ +// -*- 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 plainkey + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "encoding/json" + "errors" + "fmt" + "sync" + + "golang.org/x/crypto/cryptobyte" + + "github.com/snapcore/secboot" +) + +const ( + platformName = "plainkey" +) + +var ( + platformKeysMu sync.RWMutex + platformKeys [][]byte +) + +// SetPlatformKeys sets the keys that will be used by this platform to recover other +// keys. These are typically stored in and loaded from an encrypted container that is +// unlocked via some other mechanism. +func SetPlatformKeys(keys ...[]byte) { + platformKeysMu.Lock() + platformKeys = keys + platformKeysMu.Unlock() +} + +// AddPlatformKeys adds keys that will be used by this platform to recover other +// keys. These are typically stored in and loaded from an encrypted container that is +// unlocked via some other mechanism. +func AddPlatformKeys(keys ...[]byte) { + platformKeysMu.Lock() + platformKeys = append(platformKeys, keys...) + platformKeysMu.Unlock() +} + +func getPlatformKey(id *platformKeyId) ([]byte, error) { + if !id.Alg.Available() { + return nil, errors.New("digest algorithm unavailable") + } + + platformKeysMu.RLock() + keys := platformKeys + platformKeysMu.RUnlock() + + for _, key := range keys { + h := hmac.New(id.Alg.New, key) + h.Write(id.Salt) + if bytes.Equal(h.Sum(nil), id.Digest) { + return key, nil + } + } + return nil, errors.New("no key available") +} + +type platformKeyDataHandler struct{} + +func (*platformKeyDataHandler) RecoverKeys(data *secboot.PlatformKeyData, encryptedPayload []byte) ([]byte, error) { + var kd keyData + if err := json.Unmarshal(data.EncodedHandle, &kd); err != nil { + return nil, &secboot.PlatformHandlerError{ + Type: secboot.PlatformHandlerErrorInvalidData, + Err: err, + } + } + if len(kd.Nonce) < symKeySaltSize { + return nil, &secboot.PlatformHandlerError{ + Type: secboot.PlatformHandlerErrorInvalidData, + Err: errors.New("invalid nonce size"), + } + } + + aad := additionalData{ + Version: kd.Version, + Generation: data.Generation, + KDFAlg: hashAlg(data.KDFAlg), + AuthMode: data.AuthMode, + } + builder := cryptobyte.NewBuilder(nil) + aad.MarshalASN1(builder) + aadBytes, err := builder.Bytes() + if err != nil { + return nil, &secboot.PlatformHandlerError{ + Type: secboot.PlatformHandlerErrorInvalidData, + Err: fmt.Errorf("cannot serialize AAD: %w", err), + } + } + + key, err := getPlatformKey(&kd.PlatformKeyID) + if err != nil { + return nil, &secboot.PlatformHandlerError{ + Type: secboot.PlatformHandlerErrorInvalidData, + Err: fmt.Errorf("cannot select platform key: %w", err), + } + } + + b, err := aes.NewCipher(deriveAESKey(key, kd.Nonce[:symKeySaltSize])) + if err != nil { + return nil, fmt.Errorf("cannot create cipher: %w", err) + } + + nonce := kd.Nonce[symKeySaltSize:] + aead, err := cipher.NewGCMWithNonceSize(b, len(nonce)) + if err != nil { + return nil, fmt.Errorf("cannot create AEAD: %w", err) + } + + payload, err := aead.Open(nil, nonce, encryptedPayload, aadBytes) + if err != nil { + return nil, &secboot.PlatformHandlerError{ + Type: secboot.PlatformHandlerErrorInvalidData, + Err: fmt.Errorf("cannot open payload: %w", err), + } + } + + return payload, nil +} + +func (*platformKeyDataHandler) RecoverKeysWithAuthKey(data *secboot.PlatformKeyData, encryptedPayload, key []byte) ([]byte, error) { + return nil, errors.New("unsupported action") +} + +func (*platformKeyDataHandler) ChangeAuthKey(data *secboot.PlatformKeyData, old, new []byte) ([]byte, error) { + return nil, errors.New("unsupported action") +} + +func init() { + secboot.RegisterPlatformKeyDataHandler(platformName, &platformKeyDataHandler{}) +} diff --git a/plainkey/platform_test.go b/plainkey/platform_test.go new file mode 100644 index 00000000..a634fb73 --- /dev/null +++ b/plainkey/platform_test.go @@ -0,0 +1,281 @@ +// -*- 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 plainkey_test + +import ( + "crypto" + "crypto/rand" + "encoding/json" + "errors" + + . "gopkg.in/check.v1" + + "github.com/snapcore/secboot" + "github.com/snapcore/secboot/internal/testutil" + . "github.com/snapcore/secboot/plainkey" +) + +type platformSuite struct{} + +var _ = Suite(&platformSuite{}) + +type testRecoverKeysParams struct { + platformKeys [][]byte + generation int + keyData *KeyData + ciphertext []byte + + expectedPlaintext []byte +} + +func (s *platformSuite) testRecoverKeys(c *C, params *testRecoverKeysParams) { + SetPlatformKeys(params.platformKeys...) + defer SetPlatformKeys(nil) + + handle, err := json.Marshal(params.keyData) + c.Assert(err, IsNil) + + var platform PlatformKeyDataHandler + payload, err := platform.RecoverKeys(&secboot.PlatformKeyData{ + Generation: params.generation, + EncodedHandle: handle, + KDFAlg: crypto.SHA256, + AuthMode: secboot.AuthModeNone, + }, params.ciphertext) + c.Check(err, IsNil) + c.Check(payload, DeepEquals, params.expectedPlaintext) +} + +func (s *platformSuite) TestRecoverKeys(c *C) { + s.testRecoverKeys(c, &testRecoverKeysParams{ + platformKeys: [][]byte{testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1")}, + generation: 2, + keyData: &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + }, + ciphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *platformSuite) TestRecoverKeysDifferentKey(c *C) { + s.testRecoverKeys(c, &testRecoverKeysParams{ + platformKeys: [][]byte{testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1")}, + generation: 2, + keyData: &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d0931689b9b05919c41170f32cb001"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "32b030249e7b9c614d160be5985a031654c9bba87842c40e8d1b7f8adf0b277e"), + Digest: testutil.DecodeHexString(c, "777cae054f1c5103149f5e30152fad0b197b3f0bb4b801327307aca50a02acff"), + }, + }, + ciphertext: testutil.DecodeHexString(c, "b268bb69cafa29a490511819e12f0da25454bf724a76fc9a17b6f72019353371d6c8c13c26251e9e3169936146aa725da7000f39cebdc873adbb6bc6d02c64a6069e71b0c3116657ff8498164a7ab5e6488f552ccc88"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420c4f8f04115eb2320f2bba3777240234b535d666b64c0ab10fe32e9b44e07c436"), + }) +} + +func (s *platformSuite) TestRecoverKeysDifferentPlatformKey(c *C) { + s.testRecoverKeys(c, &testRecoverKeysParams{ + platformKeys: [][]byte{testutil.DecodeHexString(c, "1d3ae75ec26e284ab2f032256202d653025f2a1969d956a7c3b582aa368db198")}, + generation: 2, + keyData: &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "a7d200c3951659d5132db221a376cfd937d65e6e991e651b62fbed48855efeaf"), + }, + }, + ciphertext: testutil.DecodeHexString(c, "ad5f76499f91a47a04b1a1e26625cb4e18f6ac38e888b0a2882853d23bfdd6a3d8f1feecf0956cf3667817009c2c3023331e2601dc94f5aad80a1996dcc691b3f9b430ddc7a5cad10566ee530311a3bf267bff9a81b8"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *platformSuite) TestRecoverKeysMultiplePlatformKeys(c *C) { + s.testRecoverKeys(c, &testRecoverKeysParams{ + platformKeys: [][]byte{ + testutil.DecodeHexString(c, "1d3ae75ec26e284ab2f032256202d653025f2a1969d956a7c3b582aa368db198"), + testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), + }, + generation: 2, + keyData: &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + }, + ciphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), + expectedPlaintext: testutil.DecodeHexString(c, "30440420707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa3730420179059840680febea1a486309c881f3486bcedc2f47b579e7699e1621db74696"), + }) +} + +func (s *platformSuite) TestRecoverKeysInvalidNonceSize(c *C) { + kd := &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + } + handle, err := json.Marshal(kd) + c.Check(err, IsNil) + + var platform PlatformKeyDataHandler + _, err = platform.RecoverKeys(&secboot.PlatformKeyData{ + Generation: 2, + EncodedHandle: handle, + KDFAlg: crypto.SHA256, + AuthMode: secboot.AuthModeNone, + }, nil) + c.Check(err, ErrorMatches, `invalid nonce size`) + + var phe *secboot.PlatformHandlerError + c.Assert(errors.As(err, &phe), testutil.IsTrue) + c.Check(phe.Type, Equals, secboot.PlatformHandlerErrorInvalidData) +} + +func (s *platformSuite) TestRecoverKeysInvalidKDFAlg(c *C) { + kd := &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + } + handle, err := json.Marshal(kd) + c.Check(err, IsNil) + + var platform PlatformKeyDataHandler + _, err = platform.RecoverKeys(&secboot.PlatformKeyData{ + Generation: 2, + EncodedHandle: handle, + KDFAlg: crypto.SHA3_256, + AuthMode: secboot.AuthModeNone, + }, nil) + c.Check(err, ErrorMatches, `cannot serialize AAD: unknown hash algorithm: SHA3-256`) + + var phe *secboot.PlatformHandlerError + c.Assert(errors.As(err, &phe), testutil.IsTrue) + c.Check(phe.Type, Equals, secboot.PlatformHandlerErrorInvalidData) +} + +func (s *platformSuite) TestRecoverKeysNoPlatformKey(c *C) { + kd := &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + } + handle, err := json.Marshal(kd) + c.Check(err, IsNil) + + var platform PlatformKeyDataHandler + _, err = platform.RecoverKeys(&secboot.PlatformKeyData{ + Generation: 2, + EncodedHandle: handle, + KDFAlg: crypto.SHA256, + AuthMode: secboot.AuthModeNone, + }, nil) + c.Check(err, ErrorMatches, `cannot select platform key: no key available`) + + var phe *secboot.PlatformHandlerError + c.Assert(errors.As(err, &phe), testutil.IsTrue) + c.Check(phe.Type, Equals, secboot.PlatformHandlerErrorInvalidData) +} + +func (s *platformSuite) TestRecoverKeysCannotOpen(c *C) { + SetPlatformKeys(testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1")) + defer SetPlatformKeys(nil) + + kd := &KeyData{ + Version: 1, + Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + PlatformKeyID: PlatformKeyId{ + Alg: HashAlg(crypto.SHA256), + Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), + Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), + }, + } + handle, err := json.Marshal(kd) + c.Check(err, IsNil) + + var platform PlatformKeyDataHandler + _, err = platform.RecoverKeys(&secboot.PlatformKeyData{ + Generation: 2, + EncodedHandle: handle, + KDFAlg: crypto.SHA384, // make authentication fail. + AuthMode: secboot.AuthModeNone, + }, testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49")) + c.Check(err, ErrorMatches, `cannot open payload: cipher: message authentication failed`) + + var phe *secboot.PlatformHandlerError + c.Assert(errors.As(err, &phe), testutil.IsTrue) + c.Check(phe.Type, Equals, secboot.PlatformHandlerErrorInvalidData) +} + +type platformSuiteIntegrated struct{} + +var _ = Suite(&platformSuiteIntegrated{}) + +func (s *platformSuiteIntegrated) TestRecoverKeys(c *C) { + platformKey := testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1") + SetPlatformKeys(platformKey) + defer SetPlatformKeys(nil) + + kd, expectedPrimaryKey, expectedUnlockKey, err := NewProtectedKey(rand.Reader, platformKey, nil) + c.Assert(err, IsNil) + + unlockKey, primaryKey, err := kd.RecoverKeys() + c.Check(err, IsNil) + c.Check(unlockKey, DeepEquals, expectedUnlockKey) + c.Check(primaryKey, DeepEquals, expectedPrimaryKey) +} + +func (s *platformSuiteIntegrated) TestRecoverKeysNoPlatformKey(c *C) { + platformKey := testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1") + + kd, _, _, err := NewProtectedKey(rand.Reader, platformKey, nil) + c.Assert(err, IsNil) + + _, _, err = kd.RecoverKeys() + c.Check(err, ErrorMatches, `invalid key data: cannot select platform key: no key available`) + + var e *secboot.InvalidKeyDataError + c.Check(errors.As(err, &e), testutil.IsTrue) +} From dcdb9979cf6c9e2ce290f3f0fa76eaea27cc1fee Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 14 Mar 2024 23:16:00 +0000 Subject: [PATCH 2/6] plainkey: add a package description --- plainkey/platform.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plainkey/platform.go b/plainkey/platform.go index 582fcfdd..0511d9db 100644 --- a/plainkey/platform.go +++ b/plainkey/platform.go @@ -17,6 +17,12 @@ * */ +// Package plainkey is a platform for recovering keys that are protected by a key that +// is protected by some other mechanism. +// +// This is typically used to unlock storage containers after unlocking an initial +// storage container with a key that is hardware protected, if access to that storage +// container implies access to others. package plainkey import ( From a8a8e774a56c63b99882b927d736762724cf2c2b Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 14 Mar 2024 23:16:38 +0000 Subject: [PATCH 3/6] plainkey: remove unused function --- plainkey/keydata.go | 2 +- plainkey/platform.go | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/plainkey/keydata.go b/plainkey/keydata.go index 565f00d1..b882ecf5 100644 --- a/plainkey/keydata.go +++ b/plainkey/keydata.go @@ -186,7 +186,7 @@ type keyData struct { // NewProtectedKey creates a new key that is protected by this platform with the supplied // platform key. The platform key is typically stored inside of an encrypted container that // is unlocked via another mechanism, such as a TPM, and then loaded via [SetPlatformKeys] -// or [AddPlatformKeys] after unlocking that container. +// after unlocking that container. // // If primaryKey isn't supplied, then one will be generated. func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.PrimaryKey) (protectedKey *secboot.KeyData, primaryKeyOut secboot.PrimaryKey, unlockKey secboot.DiskUnlockKey, err error) { diff --git a/plainkey/platform.go b/plainkey/platform.go index 0511d9db..ec3a3e51 100644 --- a/plainkey/platform.go +++ b/plainkey/platform.go @@ -58,15 +58,6 @@ func SetPlatformKeys(keys ...[]byte) { platformKeysMu.Unlock() } -// AddPlatformKeys adds keys that will be used by this platform to recover other -// keys. These are typically stored in and loaded from an encrypted container that is -// unlocked via some other mechanism. -func AddPlatformKeys(keys ...[]byte) { - platformKeysMu.Lock() - platformKeys = append(platformKeys, keys...) - platformKeysMu.Unlock() -} - func getPlatformKey(id *platformKeyId) ([]byte, error) { if !id.Alg.Available() { return nil, errors.New("digest algorithm unavailable") From f83d636b35cb3b8b8d743fc61759e14a1ab354d4 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 14 Mar 2024 23:22:22 +0000 Subject: [PATCH 4/6] plainkey: add comment for key ID --- plainkey/keydata.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plainkey/keydata.go b/plainkey/keydata.go index b882ecf5..4f8f75b2 100644 --- a/plainkey/keydata.go +++ b/plainkey/keydata.go @@ -170,16 +170,24 @@ func (d additionalData) MarshalASN1(b *cryptobyte.Builder) { }) } +// platformKeyId is a HMAC created by the platform key used to protect +// a plainkey key blob. It is used to iedntify the loaded platform key +// to use for key recovery. type platformKeyId struct { - Alg hashAlg `json:"alg"` - Salt []byte `json:"salt"` - Digest []byte `json:"digest"` + Alg hashAlg `json:"alg"` // the digest algorithm + Salt []byte `json:"salt"` // the salt, used as data to the HMAC + Digest []byte `json:"digest"` // the resulting HMAC. } type keyData struct { - Version int `json:"version"` - Nonce []byte `json:"nonce"` + Version int `json:"version"` + // Nonce has a dual purpose - it provides a salt that is used to derive + // an encryption key from the platform key, and it provides the GCM nonce. + Nonce []byte `json:"nonce"` + + // PlatformKeyID is used to identify the loaded platform key to + // use for key recovery. PlatformKeyID platformKeyId `json:"platform-key-id"` } From bcf1debff93a8e788fd3c9c40afa50126aec8f91 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 14 Mar 2024 23:42:52 +0000 Subject: [PATCH 5/6] plainkey: split up the salt and nonce value --- plainkey/keydata.go | 32 ++++++++++++++------------ plainkey/keydata_test.go | 27 ++++++++++++++-------- plainkey/platform.go | 13 +++-------- plainkey/platform_test.go | 48 ++++++++++++--------------------------- 4 files changed, 52 insertions(+), 68 deletions(-) diff --git a/plainkey/keydata.go b/plainkey/keydata.go index 4f8f75b2..0e11cb52 100644 --- a/plainkey/keydata.go +++ b/plainkey/keydata.go @@ -182,9 +182,8 @@ type platformKeyId struct { type keyData struct { Version int `json:"version"` - // Nonce has a dual purpose - it provides a salt that is used to derive - // an encryption key from the platform key, and it provides the GCM nonce. - Nonce []byte `json:"nonce"` + Salt []byte `json:"salt"` // Used to derive the symmetric key from the platform key + Nonce []byte `json:"nonce"` // the GCM nonce // PlatformKeyID is used to identify the loaded platform key to // use for key recovery. @@ -212,12 +211,19 @@ func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.Prim return nil, nil, nil, fmt.Errorf("cannot create new unlock key: %w", err) } - // The nonce contains a 32 byte salt and 12 byte GCM nonce - nonce := make([]byte, symKeySaltSize+nonceSize) - if _, err := io.ReadFull(rand, nonce); err != nil { - return nil, nil, nil, fmt.Errorf("cannot obtain nonce: %w", err) + idAlg := crypto.SHA256 + + // Obtain a 32-byte salt for deriving the symmetric key, a 12-byte GCM nonce and + // a 32-byte salt for the platform key ID. + randBytes := make([]byte, symKeySaltSize+nonceSize+idAlg.Size()) + if _, err := io.ReadFull(rand, randBytes); err != nil { + return nil, nil, nil, fmt.Errorf("cannot obtain required random bytes: %w", err) } + salt := randBytes[:symKeySaltSize] + nonce := randBytes[symKeySaltSize : symKeySaltSize+nonceSize] + idSalt := randBytes[symKeySaltSize+nonceSize:] + aad := additionalData{ Version: 1, Generation: secboot.KeyDataGeneration, @@ -231,20 +237,15 @@ func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.Prim return nil, nil, nil, fmt.Errorf("cannot serialize AAD: %w", err) } - idAlg := crypto.SHA256 - salt := make([]byte, idAlg.Size()) - if _, err := io.ReadFull(rand, salt); err != nil { - return nil, nil, nil, fmt.Errorf("cannot obtain salt for platform key ID: %w", err) - } id := platformKeyId{ Alg: hashAlg(idAlg), - Salt: salt, + Salt: idSalt, } h := hmac.New(id.Alg.New, platformKey) h.Write(id.Salt) id.Digest = h.Sum(nil) - b, err := aes.NewCipher(deriveAESKey(platformKey, nonce[:symKeySaltSize])) + b, err := aes.NewCipher(deriveAESKey(platformKey, salt)) if err != nil { return nil, nil, nil, fmt.Errorf("cannot create cipher: %w", err) } @@ -252,11 +253,12 @@ func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.Prim if err != nil { return nil, nil, nil, fmt.Errorf("cannot create AEAD: %w", err) } - ciphertext := aead.Seal(nil, nonce[symKeySaltSize:], payload, aadBytes) + ciphertext := aead.Seal(nil, nonce, payload, aadBytes) kd, err := secbootNewKeyData(&secboot.KeyParams{ Handle: &keyData{ Version: 1, + Salt: salt, Nonce: nonce, PlatformKeyID: id, }, diff --git a/plainkey/keydata_test.go b/plainkey/keydata_test.go index 9670708e..de757135 100644 --- a/plainkey/keydata_test.go +++ b/plainkey/keydata_test.go @@ -45,6 +45,7 @@ type testNewProtectedKeyParams struct { expectedPrimaryKey secboot.PrimaryKey expectedUnlockKey secboot.DiskUnlockKey + expectedSalt []byte expectedNonce []byte expectedPlatformKeyIdSalt []byte expectedPlatformKeyId []byte @@ -63,6 +64,7 @@ func (s *keydataSuite) testNewProtectedKey(c *C, params *testNewProtectedKeyPara kd := keyParams.Handle.(*KeyData) c.Check(kd.Version, Equals, 1) + c.Check(kd.Salt, DeepEquals, params.expectedSalt) c.Assert(kd.Nonce, DeepEquals, params.expectedNonce) c.Check(crypto.Hash(kd.PlatformKeyID.Alg), Equals, crypto.SHA256) c.Check(kd.PlatformKeyID.Salt, DeepEquals, params.expectedPlatformKeyIdSalt) @@ -76,7 +78,7 @@ func (s *keydataSuite) testNewProtectedKey(c *C, params *testNewProtectedKeyPara expectedHandle, err = json.Marshal(kd) c.Assert(err, IsNil) - b, err := aes.NewCipher(DeriveAESKey(params.platformKey, kd.Nonce[:32])) + b, err := aes.NewCipher(DeriveAESKey(params.platformKey, kd.Salt)) c.Assert(err, IsNil) aead, err := cipher.NewGCM(b) @@ -93,7 +95,7 @@ func (s *keydataSuite) testNewProtectedKey(c *C, params *testNewProtectedKeyPara aadBytes, err := builder.Bytes() c.Check(err, IsNil) - payload, err := aead.Open(nil, kd.Nonce[32:], keyParams.EncryptedPayload, aadBytes) + payload, err := aead.Open(nil, kd.Nonce, keyParams.EncryptedPayload, aadBytes) c.Check(err, IsNil) c.Check(payload, DeepEquals, params.expectedPlaintext) @@ -118,7 +120,8 @@ func (s *keydataSuite) TestNewProtectedKey(c *C) { primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), - expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedSalt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + expectedNonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), @@ -133,7 +136,8 @@ func (s *keydataSuite) TestNewProtectedKeyDifferentRand(c *C) { primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedUnlockKey: testutil.DecodeHexString(c, "0187af22705f098123812aa31032ce003f24bd69649d260153604fb7c0293925"), - expectedNonce: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d0931689b9b05919c41170f32cb001"), + expectedSalt: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d09316"), + expectedNonce: testutil.DecodeHexString(c, "89b9b05919c41170f32cb001"), expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "32b030249e7b9c614d160be5985a031654c9bba87842c40e8d1b7f8adf0b277e"), expectedPlatformKeyId: testutil.DecodeHexString(c, "777cae054f1c5103149f5e30152fad0b197b3f0bb4b801327307aca50a02acff"), expectedCiphertext: testutil.DecodeHexString(c, "b268bb69cafa29a490511819e12f0da25454bf724a76fc9a17b6f72019353371d6c8c13c26251e9e3169936146aa725da7000f39cebdc873adbb6bc6d02c64a6069e71b0c3116657ff8498164a7ab5e6488f552ccc88"), @@ -148,7 +152,8 @@ func (s *keydataSuite) TestNewProtectedKeyDifferentPlatformKey(c *C) { primaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), - expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedSalt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + expectedNonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), expectedPlatformKeyId: testutil.DecodeHexString(c, "a7d200c3951659d5132db221a376cfd937d65e6e991e651b62fbed48855efeaf"), expectedCiphertext: testutil.DecodeHexString(c, "ad5f76499f91a47a04b1a1e26625cb4e18f6ac38e888b0a2882853d23bfdd6a3d8f1feecf0956cf3667817009c2c3023331e2601dc94f5aad80a1996dcc691b3f9b430ddc7a5cad10566ee530311a3bf267bff9a81b8"), @@ -163,7 +168,8 @@ func (s *keydataSuite) TestNewProtectedKeyDifferentPrimaryKey(c *C) { primaryKey: testutil.DecodeHexString(c, "6e5eb7de5a75ec77bbec2f0927f6503bc7d0e2a6ebcb971d7dbe7a77e0d924a7"), expectedPrimaryKey: testutil.DecodeHexString(c, "6e5eb7de5a75ec77bbec2f0927f6503bc7d0e2a6ebcb971d7dbe7a77e0d924a7"), expectedUnlockKey: testutil.DecodeHexString(c, "0685e55582e4465ba2336e95304166eaa839d1a645dec1c0629a63f9748fb182"), - expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedSalt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + expectedNonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c3cab20fc4def991994b1f7ea6582b7eae091c2ed1e40508b38e7cdeca313b33d728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b23778d5679118961498d551cacecf81ee9"), @@ -177,7 +183,8 @@ func (s *keydataSuite) TestNewProtectedKeyGeneratePrimaryKey(c *C) { platformKey: testutil.DecodeHexString(c, "8f13251b23450e1d184facfd28752c14c26439fce2765ecd92ff4b060713b5d1"), expectedPrimaryKey: testutil.DecodeHexString(c, "707fe314e4a9024db85cdd78c26932c75a9f1265a0f51a31e17db268061fa373"), expectedUnlockKey: testutil.DecodeHexString(c, "f1cffa65c76b15ac7e21dfd0894f21c5ce8986103bfb4916c4ff435513865980"), - expectedNonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + expectedSalt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + expectedNonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), expectedPlatformKeyIdSalt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), expectedPlatformKeyId: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), expectedCiphertext: testutil.DecodeHexString(c, "d3ee9e1c228a7436f33377239701059b801dd5167dde322e557edda7a42405f345d534e9728c9158c854a0eb8b11399bcd36a299a40e5258c230f61d5e0b948138fe54718b238f4f6063b782ac2b613a58fc1fdc6d49"), @@ -188,7 +195,8 @@ func (s *keydataSuite) TestNewProtectedKeyGeneratePrimaryKey(c *C) { func (s *keydataSuite) TestKeyDataMarshalAndUnmarshal(c *C) { orig := &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -198,7 +206,8 @@ func (s *keydataSuite) TestKeyDataMarshalAndUnmarshal(c *C) { b, err := json.Marshal(orig) c.Check(err, IsNil) - c.Check(b, DeepEquals, []byte(`{"version":1,"nonce":"1LC2+izu+rryH4jqQs+441ODWtnBkEScwBpdJ13chMsHhTXMEBudEtm49A4=","platform-key-id":{"alg":"sha256","salt":"2tqBZOoNYvf8ItCcw0vUNARVS7X/xRk31UbJqX1o4v4=","digest":"EZgSUzlG0EzT/nJib2HPNkh3qPGmZjzo8GBNpSzwuPM="}}`)) + c.Check(b, DeepEquals, []byte(`{"version":1,"salt":"1LC2+izu+rryH4jqQs+441ODWtnBkEScwBpdJ13chMs=","nonce":"B4U1zBAbnRLZuPQO","platform-key-id":{"alg":"sha256","salt":"2tqBZOoNYvf8ItCcw0vUNARVS7X/xRk31UbJqX1o4v4=","digest":"EZgSUzlG0EzT/nJib2HPNkh3qPGmZjzo8GBNpSzwuPM="}}`)) + c.Logf("%s", string(b)) var unmarshalled *KeyData c.Assert(json.Unmarshal(b, &unmarshalled), IsNil) diff --git a/plainkey/platform.go b/plainkey/platform.go index ec3a3e51..6496ccd1 100644 --- a/plainkey/platform.go +++ b/plainkey/platform.go @@ -87,12 +87,6 @@ func (*platformKeyDataHandler) RecoverKeys(data *secboot.PlatformKeyData, encryp Err: err, } } - if len(kd.Nonce) < symKeySaltSize { - return nil, &secboot.PlatformHandlerError{ - Type: secboot.PlatformHandlerErrorInvalidData, - Err: errors.New("invalid nonce size"), - } - } aad := additionalData{ Version: kd.Version, @@ -118,18 +112,17 @@ func (*platformKeyDataHandler) RecoverKeys(data *secboot.PlatformKeyData, encryp } } - b, err := aes.NewCipher(deriveAESKey(key, kd.Nonce[:symKeySaltSize])) + b, err := aes.NewCipher(deriveAESKey(key, kd.Salt)) if err != nil { return nil, fmt.Errorf("cannot create cipher: %w", err) } - nonce := kd.Nonce[symKeySaltSize:] - aead, err := cipher.NewGCMWithNonceSize(b, len(nonce)) + aead, err := cipher.NewGCMWithNonceSize(b, len(kd.Nonce)) if err != nil { return nil, fmt.Errorf("cannot create AEAD: %w", err) } - payload, err := aead.Open(nil, nonce, encryptedPayload, aadBytes) + payload, err := aead.Open(nil, kd.Nonce, encryptedPayload, aadBytes) if err != nil { return nil, &secboot.PlatformHandlerError{ Type: secboot.PlatformHandlerErrorInvalidData, diff --git a/plainkey/platform_test.go b/plainkey/platform_test.go index a634fb73..8333b08f 100644 --- a/plainkey/platform_test.go +++ b/plainkey/platform_test.go @@ -69,7 +69,8 @@ func (s *platformSuite) TestRecoverKeys(c *C) { generation: 2, keyData: &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -87,7 +88,8 @@ func (s *platformSuite) TestRecoverKeysDifferentKey(c *C) { generation: 2, keyData: &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d0931689b9b05919c41170f32cb001"), + Salt: testutil.DecodeHexString(c, "dab12d7fa9c4dfb05bcc70bbd3d56ff87d5658c2f42e9e94e6273173a9d09316"), + Nonce: testutil.DecodeHexString(c, "89b9b05919c41170f32cb001"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "32b030249e7b9c614d160be5985a031654c9bba87842c40e8d1b7f8adf0b277e"), @@ -105,7 +107,8 @@ func (s *platformSuite) TestRecoverKeysDifferentPlatformKey(c *C) { generation: 2, keyData: &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -126,7 +129,8 @@ func (s *platformSuite) TestRecoverKeysMultiplePlatformKeys(c *C) { generation: 2, keyData: &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -138,37 +142,11 @@ func (s *platformSuite) TestRecoverKeysMultiplePlatformKeys(c *C) { }) } -func (s *platformSuite) TestRecoverKeysInvalidNonceSize(c *C) { - kd := &KeyData{ - Version: 1, - Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), - PlatformKeyID: PlatformKeyId{ - Alg: HashAlg(crypto.SHA256), - Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), - Digest: testutil.DecodeHexString(c, "119812533946d04cd3fe72626f61cf364877a8f1a6663ce8f0604da52cf0b8f3"), - }, - } - handle, err := json.Marshal(kd) - c.Check(err, IsNil) - - var platform PlatformKeyDataHandler - _, err = platform.RecoverKeys(&secboot.PlatformKeyData{ - Generation: 2, - EncodedHandle: handle, - KDFAlg: crypto.SHA256, - AuthMode: secboot.AuthModeNone, - }, nil) - c.Check(err, ErrorMatches, `invalid nonce size`) - - var phe *secboot.PlatformHandlerError - c.Assert(errors.As(err, &phe), testutil.IsTrue) - c.Check(phe.Type, Equals, secboot.PlatformHandlerErrorInvalidData) -} - func (s *platformSuite) TestRecoverKeysInvalidKDFAlg(c *C) { kd := &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -195,7 +173,8 @@ func (s *platformSuite) TestRecoverKeysInvalidKDFAlg(c *C) { func (s *platformSuite) TestRecoverKeysNoPlatformKey(c *C) { kd := &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), @@ -225,7 +204,8 @@ func (s *platformSuite) TestRecoverKeysCannotOpen(c *C) { kd := &KeyData{ Version: 1, - Nonce: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb078535cc101b9d12d9b8f40e"), + Salt: testutil.DecodeHexString(c, "d4b0b6fa2ceefabaf21f88ea42cfb8e353835ad9c190449cc01a5d275ddc84cb"), + Nonce: testutil.DecodeHexString(c, "078535cc101b9d12d9b8f40e"), PlatformKeyID: PlatformKeyId{ Alg: HashAlg(crypto.SHA256), Salt: testutil.DecodeHexString(c, "dada8164ea0d62f7fc22d09cc34bd43404554bb5ffc51937d546c9a97d68e2fe"), From 3a1580afaca842df6c39ffad97932333b3943b47 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 14 Mar 2024 23:49:17 +0000 Subject: [PATCH 6/6] plainkey: add additional note to NewProtectedKey --- plainkey/keydata.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plainkey/keydata.go b/plainkey/keydata.go index 0e11cb52..c2b87933 100644 --- a/plainkey/keydata.go +++ b/plainkey/keydata.go @@ -196,6 +196,13 @@ type keyData struct { // after unlocking that container. // // If primaryKey isn't supplied, then one will be generated. +// +// This function requires some cryptographically strong randomness, obtained from the rand +// argument. Whilst this will normally be from [rand.Reader], it can be provided from other +// secure sources or mocked during tests. Note that the underlying implementation of this +// platform uses GCM, so rand must be cryptographically secure in order to prevent nonce +// reuse problems. Calling this function more than once in production with the same platform +// key and the same sequence of random bytes is a bug. func NewProtectedKey(rand io.Reader, platformKey []byte, primaryKey secboot.PrimaryKey) (protectedKey *secboot.KeyData, primaryKeyOut secboot.PrimaryKey, unlockKey secboot.DiskUnlockKey, err error) { if len(primaryKey) == 0 { primaryKey = make(secboot.PrimaryKey, 32)