From ce69219cb5c855a2bd6405fef0d4d9dc74f37a98 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Tue, 6 Aug 2024 18:21:42 +0300 Subject: [PATCH] add activate credential service to metadata server Activate credential provides proof that the Endorsement Key (EK) and a signig key (in this case eve created AIK) are owned by the same TPM. This is way to extend the trust from EK (which theoretically comes with OEM certificate) to a arbitrary TPM resident, restricted, signing key. Signed-off-by: Shahriyar Jalayeri --- pkg/pillar/cmd/msrv/activatecred.go | 134 ++++++++++++ pkg/pillar/cmd/msrv/activatecred_test.go | 191 ++++++++++++++++ pkg/pillar/cmd/msrv/handlers.go | 73 +++++++ pkg/pillar/cmd/msrv/msrv.go | 3 + pkg/pillar/go.mod | 1 - .../credactivation/credential_activation.go | 206 ++++++++++++++++++ pkg/pillar/vendor/modules.txt | 1 + tests/tpm/prep-and-test.sh | 34 ++- 8 files changed, 636 insertions(+), 7 deletions(-) create mode 100644 pkg/pillar/cmd/msrv/activatecred.go create mode 100644 pkg/pillar/cmd/msrv/activatecred_test.go create mode 100644 pkg/pillar/vendor/github.com/google/go-tpm/legacy/tpm2/credactivation/credential_activation.go diff --git a/pkg/pillar/cmd/msrv/activatecred.go b/pkg/pillar/cmd/msrv/activatecred.go new file mode 100644 index 00000000000..8539d700ae1 --- /dev/null +++ b/pkg/pillar/cmd/msrv/activatecred.go @@ -0,0 +1,134 @@ +package msrv + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/tpmutil" + etpm "github.com/lf-edge/eve/pkg/pillar/evetpm" +) + +// ActivateCredTpmParam provides the parameters required to activate the +// credential using HWTPM EK and HWTPM AIK. +type ActivateCredTpmParam struct { + Ek string `json:"ek"` // HWTPM EK public key + AikPub string `json:"aikpub"` // HWTPM AIK public key + AikName string `json:"aikname"` // HWTPM AIK name +} + +// ActivateCredGenerated contains the generated credential and data to sign. +type ActivateCredGenerated struct { + Data string `json:"data"` + Cred string `json:"cred"` + Secret string `json:"secret"` +} + +// ActivateCredActivated contains the activated credential (decrypted secret) +// and signature of the ActivateCredGenerated.Data. +type ActivateCredActivated struct { + Secret string `json:"secret"` + Sig string `json:"sig"` +} + +// handles the GET request /tmp/activate-credential/, this is used to get the +// HTPM EK public key, HWTPM AIK public key, HWTPM AIK name. +func getActivateCredentialParams() ([]byte, []byte, []byte, error) { + rw, err := tpm2.OpenTPM(etpm.TpmDevicePath) + if err != nil { + return nil, nil, nil, err + } + defer rw.Close() + + ekPub, _, _, err := tpm2.ReadPublic(rw, etpm.TpmEKHdl) + if err != nil { + return nil, nil, nil, err + } + + ekPubByte, err := ekPub.Encode() + if err != nil { + return nil, nil, nil, err + } + + var aikName tpmutil.U16Bytes + aikPub, aikName, _, err := tpm2.ReadPublic(rw, etpm.TpmAIKHdl) + if err != nil { + return nil, nil, nil, err + } + + aikPubByte, err := aikPub.Encode() + if err != nil { + return nil, nil, nil, err + } + + aikNameMarshaled := &bytes.Buffer{} + if err := aikName.TPMMarshal(aikNameMarshaled); err != nil { + return nil, nil, nil, err + } + + return ekPubByte, aikPubByte, aikNameMarshaled.Bytes(), nil +} + +// handles the POST request /tmp/activate-credential/, this is used to activate +// the credential (decrypt the secret) using HWTPM EK and HWTPM AIK. +func activateCredential(jsonData []byte) ([]byte, []byte, error) { + rw, err := tpm2.OpenTPM(etpm.TpmDevicePath) + if err != nil { + return nil, nil, err + } + defer rw.Close() + + var credPayload ActivateCredGenerated + if err := json.Unmarshal(jsonData, &credPayload); err != nil { + return nil, nil, err + } + + credBlob, err := base64.StdEncoding.DecodeString(credPayload.Cred) + if err != nil { + return nil, nil, err + } + + encryptedSecret, err := base64.StdEncoding.DecodeString(credPayload.Secret) + if err != nil { + return nil, nil, err + } + + // we need to skip the first 2 bytes of the credBlob and encryptedSecret + // as it contains the type. so make sure the length is greater than 2. + if len(credBlob) < 2 || len(encryptedSecret) < 2 { + return nil, nil, fmt.Errorf("malformed parameters") + } + credBlob = credBlob[2:] + encryptedSecret = encryptedSecret[2:] + + // activate the credential + recoveredCred, err := tpm2.ActivateCredential(rw, + etpm.TpmAIKHdl, + etpm.TpmEKHdl, + etpm.EmptyPassword, + etpm.EmptyPassword, + credBlob, + encryptedSecret) + if err != nil { + return nil, nil, err + } + + dataToSign, err := base64.StdEncoding.DecodeString(credPayload.Data) + if err != nil { + return nil, nil, err + } + + digest, validation, err := tpm2.Hash(rw, tpm2.AlgSHA256, dataToSign, tpm2.HandleOwner) + if err != nil { + return nil, nil, err + } + + sig, err := tpm2.Sign(rw, etpm.TpmAIKHdl, etpm.EmptyPassword, digest, validation, nil) + if err != nil { + return nil, nil, err + } + + return recoveredCred, sig.RSA.Signature, nil +} diff --git a/pkg/pillar/cmd/msrv/activatecred_test.go b/pkg/pillar/cmd/msrv/activatecred_test.go new file mode 100644 index 00000000000..53be6788aa2 --- /dev/null +++ b/pkg/pillar/cmd/msrv/activatecred_test.go @@ -0,0 +1,191 @@ +package msrv_test + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/legacy/tpm2/credactivation" + "github.com/lf-edge/eve/pkg/pillar/base" + "github.com/lf-edge/eve/pkg/pillar/cmd/msrv" + etpm "github.com/lf-edge/eve/pkg/pillar/evetpm" + "github.com/lf-edge/eve/pkg/pillar/pubsub" + "github.com/onsi/gomega" + "github.com/sirupsen/logrus" +) + +const TpmDevicePath = "/tmp/eve-tpm/srv.sock" + +var log = base.NewSourceLogObject(logrus.StandardLogger(), "test", 1234) + +func waitForTpmReadyState() error { + for i := 0; i < 10; i++ { + if err := etpm.SealDiskKey(log, []byte("secret"), etpm.DiskKeySealingPCRs); err != nil { + // this is RCRetry, so retry + if strings.Contains(err.Error(), "code 0x22") { + time.Sleep(100 * time.Millisecond) + continue + } else { + return fmt.Errorf("Something is wrong with the TPM : %w", err) + } + } else { + return nil + } + } + + return fmt.Errorf("TPM did't become ready after 10 attempts, failing the test") +} + +// TestTpmActivateCred contains TPM kong-fu, not for the faint of heart. +func TestTpmActivateCred(t *testing.T) { + _, err := os.Stat(TpmDevicePath) + if err != nil { + t.Skip("TPM is not available, skipping the test.") + } + + // for some reason testing SWTPM TPM might return RCRetry for the first + // few operations, so we need to wait for it to become ready. + if err := waitForTpmReadyState(); err != nil { + log.Fatalf("Failed to wait for TPM ready state: %v", err) + } + + t.Parallel() + g := gomega.NewGomegaWithT(t) + + logger := logrus.StandardLogger() + log := base.NewSourceLogObject(logger, "pubsub", 1234) + ps := pubsub.New(pubsub.NewMemoryDriver(), logger, log) + + srv := &msrv.Msrv{ + Log: log, + PubSub: ps, + Logger: logger, + } + + dir, err := os.MkdirTemp("/tmp", "msrv_test") + g.Expect(err).ToNot(gomega.HaveOccurred()) + defer os.RemoveAll(dir) + + err = srv.Init(dir, true) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + err = srv.Activate() + g.Expect(err).ToNot(gomega.HaveOccurred()) + + handler := srv.MakeMetadataHandler() + + // Get the activate credential parameters + pCred := httptest.NewRequest(http.MethodGet, "/eve/v1/tpm/activatecredential/", nil) + pCred.RemoteAddr = "192.168.1.1:0" + pCredRec := httptest.NewRecorder() + + handler.ServeHTTP(pCredRec, pCred) + defer pCredRec.Body.Reset() + g.Expect(pCredRec.Code).To(gomega.Equal(http.StatusOK)) + + var credParam msrv.ActivateCredTpmParam + err = json.Unmarshal(pCredRec.Body.Bytes(), &credParam) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + // Decode the EK back to tpm2.Public, in practices you need to find a + // way to trust EK using decive cert or OEM cert or whatever. + eKBytes, err := base64.StdEncoding.DecodeString(credParam.Ek) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + ekPub, err := tpm2.DecodePublic(eKBytes) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + // Decode the name back to name tpm2.Name + nameBytes, err := base64.StdEncoding.DecodeString(credParam.AikName) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + name, err := tpm2.DecodeName(bytes.NewBuffer(nameBytes)) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + // Decode the AIK back to tpm2.Public + aikBytes, err := base64.StdEncoding.DecodeString(credParam.AikPub) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + aikPub, err := tpm2.DecodePublic(aikBytes) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + // Verify the name matches the AIK + nameHash, err := name.Digest.Alg.Hash() + g.Expect(err).ToNot(gomega.HaveOccurred()) + + p, err := aikPub.Encode() + g.Expect(err).ToNot(gomega.HaveOccurred()) + + aikPubHash := nameHash.New() + aikPubHash.Write(p) + aikPubDigest := aikPubHash.Sum(nil) + g.Expect(bytes.Equal(name.Digest.Value, aikPubDigest)).To(gomega.BeTrue()) + + // Verify the AIK is a restricted signing key + g.Expect((aikPub.Attributes & tpm2.FlagFixedTPM)).To(gomega.BeEquivalentTo(tpm2.FlagFixedTPM)) + g.Expect((aikPub.Attributes & tpm2.FlagRestricted)).To(gomega.BeEquivalentTo(tpm2.FlagRestricted)) + g.Expect((aikPub.Attributes & tpm2.FlagFixedParent)).To(gomega.BeEquivalentTo(tpm2.FlagFixedParent)) + g.Expect((aikPub.Attributes & tpm2.FlagSensitiveDataOrigin)).To(gomega.BeEquivalentTo(tpm2.FlagSensitiveDataOrigin)) + + // Generate a credential + encKey, err := ekPub.Key() + g.Expect(err).ToNot(gomega.HaveOccurred()) + + dataToSign := []byte("Data to sign") + credential := make([]byte, 32) + rand.Read(credential) + symBlockSize := int(ekPub.RSAParameters.Symmetric.KeyBits) / 8 + credBlob, encryptedSecret, err := credactivation.Generate(name.Digest, encKey, symBlockSize, credential) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + var activeCredParam msrv.ActivateCredGenerated + activeCredParam.Cred = base64.StdEncoding.EncodeToString(credBlob) + activeCredParam.Secret = base64.StdEncoding.EncodeToString(encryptedSecret) + activeCredParam.Data = base64.StdEncoding.EncodeToString(dataToSign) + jsonStr, err := json.Marshal(activeCredParam) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + // Ask TPM to activate (decrypt) the credential + aCred := httptest.NewRequest(http.MethodPost, "/eve/v1/tpm/activatecredential/", bytes.NewBuffer(jsonStr)) + aCred.RemoteAddr = "192.168.1.1:0" + aCredRec := httptest.NewRecorder() + + handler.ServeHTTP(aCredRec, aCred) + defer aCredRec.Body.Reset() + g.Expect(aCredRec.Code).To(gomega.Equal(http.StatusOK)) + + var actCred msrv.ActivateCredActivated + err = json.Unmarshal(aCredRec.Body.Bytes(), &actCred) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + recovered, err := base64.StdEncoding.DecodeString(actCred.Secret) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + g.Expect(bytes.Equal(recovered, credential)).To(gomega.BeTrue()) + + // Verify the the signature + sig, err := base64.StdEncoding.DecodeString(actCred.Sig) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + dataHash := crypto.SHA256.New() + dataHash.Write(dataToSign) + dataDigest := dataHash.Sum(nil) + + sinerPubKey, err := aikPub.Key() + g.Expect(err).ToNot(gomega.HaveOccurred()) + + sinerPub := sinerPubKey.(*rsa.PublicKey) + err = rsa.VerifyPKCS1v15(sinerPub, crypto.SHA256, dataDigest[:], sig) + g.Expect(err).ToNot(gomega.HaveOccurred()) +} diff --git a/pkg/pillar/cmd/msrv/handlers.go b/pkg/pillar/cmd/msrv/handlers.go index 75b8d72cbdd..17f5526be2a 100644 --- a/pkg/pillar/cmd/msrv/handlers.go +++ b/pkg/pillar/cmd/msrv/handlers.go @@ -709,3 +709,76 @@ func (msrv *Msrv) withPatchEnvelopesByIP() func(http.Handler) http.Handler { }) } } + +// handleActivateCredentialGet handles get request of the activate-credential exchange, +// it returns a json containing the EK, AIK public keys and AIK name. +func (msrv *Msrv) handleActivateCredentialGet() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + ekPubByte, aikPubByte, aiKnameMarshaled, err := getActivateCredentialParams() + if err != nil { + msrv.Log.Errorf("handleActivateCredntialGet: %v", err) + sendError(w, http.StatusInternalServerError, "Operation failed") + return + } + + activateCred := ActivateCredTpmParam{ + Ek: base64.StdEncoding.EncodeToString(ekPubByte), + AikPub: base64.StdEncoding.EncodeToString(aikPubByte), + AikName: base64.StdEncoding.EncodeToString(aiKnameMarshaled), + } + out, err := json.Marshal(activateCred) + if err != nil { + msrv.Log.Errorf("handleActivateCredntialGet: error marshaling JSON payload %v", err) + sendError(w, http.StatusInternalServerError, "Operation failed") + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(out) + return + } + + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } +} + +// handleActivateCredentialPost handles post request of the activate-credential exchange, +// it returns a json containing the activated credential and signature of the data. +func (msrv *Msrv) handleActivateCredentialPost() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + in, err := io.ReadAll(io.LimitReader(r.Body, SignerMaxSize)) + if err != nil { + msrv.Log.Errorf("handleActivateCredntial, ReadAll : %v", err) + sendError(w, http.StatusInternalServerError, "Operation failed") + return + } + + cred, sig, err := activateCredential(in) + if err != nil { + msrv.Log.Errorf("handleActivateCredntial, activateCredntial: %v", err) + sendError(w, http.StatusInternalServerError, "Operation failed") + return + } + + activateCred := ActivateCredActivated{ + Secret: base64.StdEncoding.EncodeToString(cred), + Sig: base64.StdEncoding.EncodeToString(sig), + } + out, err := json.Marshal(activateCred) + if err != nil { + msrv.Log.Errorf("handleActivateCredntial, error marshaling JSON payload : %v", err) + sendError(w, http.StatusInternalServerError, "Operation failed") + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(out) + return + } + + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } +} diff --git a/pkg/pillar/cmd/msrv/msrv.go b/pkg/pillar/cmd/msrv/msrv.go index 2777bc1e8e6..014761cbf50 100644 --- a/pkg/pillar/cmd/msrv/msrv.go +++ b/pkg/pillar/cmd/msrv/msrv.go @@ -629,6 +629,9 @@ func (msrv *Msrv) MakeMetadataHandler() http.Handler { r.Post("/tpm/signer", msrv.handleSigner(&zedcloudCtx)) + r.Post("/tpm/activatecredential/", msrv.handleActivateCredentialPost()) + r.Get("/tpm/activatecredential/", msrv.handleActivateCredentialGet()) + r.Route("/patch", func(r chi.Router) { r.Use(msrv.withPatchEnvelopesByIP()) diff --git a/pkg/pillar/go.mod b/pkg/pillar/go.mod index 1b189b33edb..75cbb203d57 100644 --- a/pkg/pillar/go.mod +++ b/pkg/pillar/go.mod @@ -2,7 +2,6 @@ module github.com/lf-edge/eve/pkg/pillar go 1.22 - require ( github.com/anatol/smart.go v0.0.0-20220615232124-371056cd18c3 github.com/andrewd-zededa/go-libzfs v0.0.0-20240304231806-6a64e99da97d diff --git a/pkg/pillar/vendor/github.com/google/go-tpm/legacy/tpm2/credactivation/credential_activation.go b/pkg/pillar/vendor/github.com/google/go-tpm/legacy/tpm2/credactivation/credential_activation.go new file mode 100644 index 00000000000..90e9123d6ff --- /dev/null +++ b/pkg/pillar/vendor/github.com/google/go-tpm/legacy/tpm2/credactivation/credential_activation.go @@ -0,0 +1,206 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package credactivation implements generation of data blobs to be used +// when invoking the ActivateCredential command, on a TPM. +package credactivation + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "errors" + "fmt" + "io" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/tpmutil" +) + +// Labels for use in key derivation or OAEP encryption. +const ( + labelIdentity = "IDENTITY" + labelStorage = "STORAGE" + labelIntegrity = "INTEGRITY" +) + +// Generate returns a TPM2B_ID_OBJECT & TPM2B_ENCRYPTED_SECRET for use in +// credential activation. +// This has been tested on EKs compliant with TCG 2.0 EK Credential Profile +// specification, revision 14. +// The pub parameter must be a pointer to rsa.PublicKey. +// The secret parameter must not be longer than the longest digest size implemented +// by the TPM. A 32 byte secret is a safe, recommended default. +// +// This function implements Credential Protection as defined in section 24 of the TPM +// specification revision 2 part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +func Generate(aik *tpm2.HashValue, pub crypto.PublicKey, symBlockSize int, secret []byte) ([]byte, []byte, error) { + return generate(aik, pub, symBlockSize, secret, rand.Reader) +} + +func generate(aik *tpm2.HashValue, pub crypto.PublicKey, symBlockSize int, secret []byte, rnd io.Reader) ([]byte, []byte, error) { + var seed, encSecret []byte + var err error + switch ekKey := pub.(type) { + case *ecdh.PublicKey: + seed, encSecret, err = createECSeed(aik, ekKey, rnd) + if err != nil { + return nil, nil, fmt.Errorf("creating seed: %v", err) + } + case *ecdsa.PublicKey: + ecdhKey, err := ekKey.ECDH() + if err != nil { + return nil, nil, fmt.Errorf("transmuting ecdsa key to ecdh key: %v", err) + } + return generate(aik, ecdhKey, symBlockSize, secret, rnd) + case *rsa.PublicKey: + seed, encSecret, err = createRSASeed(aik, ekKey, symBlockSize, rnd) + if err != nil { + return nil, nil, fmt.Errorf("creating seed: %v", err) + } + default: + return nil, nil, errors.New("only RSA and EC public keys are supported for credential activation") + } + + // Generate the encrypted credential by convolving the seed with the digest of + // the AIK, and using the result as the key to encrypt the secret. + // See section 24.4 of TPM 2.0 specification, part 1. + aikNameEncoded, err := aik.Encode() + if err != nil { + return nil, nil, fmt.Errorf("encoding aikName: %v", err) + } + symmetricKey, err := tpm2.KDFa(aik.Alg, seed, labelStorage, aikNameEncoded, nil, symBlockSize*8) + if err != nil { + return nil, nil, fmt.Errorf("generating symmetric key: %v", err) + } + c, err := aes.NewCipher(symmetricKey) + if err != nil { + return nil, nil, fmt.Errorf("symmetric cipher setup: %v", err) + } + cv, err := tpmutil.Pack(tpmutil.U16Bytes(secret)) + if err != nil { + return nil, nil, fmt.Errorf("generating cv (TPM2B_Digest): %v", err) + } + + // IV is all null bytes. encIdentity represents the encrypted credential. + encIdentity := make([]byte, len(cv)) + cipher.NewCFBEncrypter(c, make([]byte, len(symmetricKey))).XORKeyStream(encIdentity, cv) + + // Generate the integrity HMAC, which is used to protect the integrity of the + // encrypted structure. + // See section 24.5 of the TPM 2.0 specification. + cryptohash, err := aik.Alg.Hash() + if err != nil { + return nil, nil, err + } + macKey, err := tpm2.KDFa(aik.Alg, seed, labelIntegrity, nil, nil, cryptohash.Size()*8) + if err != nil { + return nil, nil, fmt.Errorf("generating HMAC key: %v", err) + } + + mac := hmac.New(cryptohash.New, macKey) + mac.Write(encIdentity) + mac.Write(aikNameEncoded) + integrityHMAC := mac.Sum(nil) + + idObject := &tpm2.IDObject{ + IntegrityHMAC: integrityHMAC, + EncIdentity: encIdentity, + } + id, err := tpmutil.Pack(idObject) + if err != nil { + return nil, nil, fmt.Errorf("encoding IDObject: %v", err) + } + + packedID, err := tpmutil.Pack(tpmutil.U16Bytes(id)) + if err != nil { + return nil, nil, fmt.Errorf("packing id: %v", err) + } + packedEncSecret, err := tpmutil.Pack(tpmutil.U16Bytes(encSecret)) + if err != nil { + return nil, nil, fmt.Errorf("packing encSecret: %v", err) + } + + return packedID, packedEncSecret, nil +} + +func createRSASeed(aik *tpm2.HashValue, ek *rsa.PublicKey, symBlockSize int, rnd io.Reader) ([]byte, []byte, error) { + crypothash, err := aik.Alg.Hash() + if err != nil { + return nil, nil, err + } + + // The seed length should match the keysize used by the EKs symmetric cipher. + // For typical RSA EKs, this will be 128 bits (16 bytes). + // Spec: TCG 2.0 EK Credential Profile revision 14, section 2.1.5.1. + seed := make([]byte, symBlockSize) + if _, err := io.ReadFull(rnd, seed); err != nil { + return nil, nil, fmt.Errorf("generating seed: %v", err) + } + + // Encrypt the seed value using the provided public key. + // See annex B, section 10.4 of the TPM specification revision 2 part 1. + label := append([]byte(labelIdentity), 0) + encryptedSeed, err := rsa.EncryptOAEP(crypothash.New(), rnd, ek, seed, label) + if err != nil { + return nil, nil, fmt.Errorf("generating encrypted seed: %v", err) + } + + encryptedSeed, err = tpmutil.Pack(encryptedSeed) + return seed, encryptedSeed, err +} + +func createECSeed(ak *tpm2.HashValue, ek *ecdh.PublicKey, rnd io.Reader) (seed, encryptedSeed []byte, err error) { + ephemeralPriv, err := ek.Curve().GenerateKey(rnd) + if err != nil { + return nil, nil, err + } + ephemeralX, ephemeralY := deconstructECDHPublicKey(ephemeralPriv.PublicKey()) + + z, err := ephemeralPriv.ECDH(ek) + if err != nil { + return nil, nil, err + } + + ekX, _ := deconstructECDHPublicKey(ek) + + crypothash, err := ak.Alg.Hash() + if err != nil { + return nil, nil, err + } + + seed, err = tpm2.KDFe( + ak.Alg, + z, + labelIdentity, + ephemeralX, + ekX, + crypothash.Size()*8) + if err != nil { + return nil, nil, err + } + encryptedSeed, err = tpmutil.Pack(tpmutil.U16Bytes(ephemeralX), tpmutil.U16Bytes(ephemeralY)) + return seed, encryptedSeed, err +} + +func deconstructECDHPublicKey(key *ecdh.PublicKey) (x []byte, y []byte) { + b := key.Bytes()[1:] + return b[:len(b)/2], b[len(b)/2:] +} diff --git a/pkg/pillar/vendor/modules.txt b/pkg/pillar/vendor/modules.txt index ce755a9dce5..3e555864d99 100644 --- a/pkg/pillar/vendor/modules.txt +++ b/pkg/pillar/vendor/modules.txt @@ -495,6 +495,7 @@ github.com/google/go-containerregistry/pkg/v1/types # github.com/google/go-tpm v0.9.1 ## explicit; go 1.22 github.com/google/go-tpm/legacy/tpm2 +github.com/google/go-tpm/legacy/tpm2/credactivation github.com/google/go-tpm/tpmutil github.com/google/go-tpm/tpmutil/tbs # github.com/google/gofuzz v1.2.0 diff --git a/tests/tpm/prep-and-test.sh b/tests/tpm/prep-and-test.sh index 94bd09920e3..c875c56ed7c 100755 --- a/tests/tpm/prep-and-test.sh +++ b/tests/tpm/prep-and-test.sh @@ -7,8 +7,10 @@ # add more TPM tests here TESTS=( "/pillar/evetpm" + "/pillar/cmd/msrv" ) +CWD=$(pwd) TPM_SRV_PORT=1337 TPM_CTR_PORT=$((TPM_SRV_PORT + 1)) ENDO_SEED=0x4000000B @@ -19,8 +21,31 @@ EVE_TPM_CTRL="$EVE_TPM_STATE/ctrl.sock" EVE_TPM_SRV="$EVE_TPM_STATE/srv.sock" echo "[+] Installing swtpm and tpm2-tools ..." -sudo apt-get -qq update -y > /dev/null -sudo apt-get install swtpm tpm2-tools -y -qq > /dev/null +#sudo apt-get -qq update -y > /dev/null +#sudo apt-get install git curl swtpm tpm2-tools -y -qq > /dev/null + + +echo "[+] Installing zfs (pillar dependency)..." +ZFS_URL="https://github.com/openzfs/zfs/archive/refs/tags/zfs-2.2.2.tar.gz" +mkdir -p /tmp/zfs +cd /tmp/zfs +curl -s -LO $ZFS_URL > /dev/null +tar -xf zfs-2.2.2.tar.gz --strip-components=1 > /dev/null +./autogen.sh > /dev/null 2>&1 +./configure \ + --prefix=/usr \ + --with-tirpc \ + --sysconfdir=/etc \ + --mandir=/usr/share/man \ + --infodir=/usr/share/info \ + --localstatedir=/var \ + --with-config=user \ + --with-udevdir=/lib/udev \ + --disable-systemd \ + --disable-static > /dev/null 2>&1 +./scripts/make_gitrev.sh > /dev/null 2>&1 +make -j "$(getconf _NPROCESSORS_ONLN)" > /dev/null 2>&1 +sudo make install-strip > /dev/null 2>&1 echo "[+] preparing the environment ..." rm -rf $EVE_TPM_STATE @@ -76,9 +101,6 @@ tpm2 evictcontrol -C o -c srk.ctx $SRK_HANDLE # clean up rm session.ctx ek.ctx srk.pub srk.priv srk.ctx -# just dump persistent handles, good for debugging§ -tpm2 getcap handles-persistent - # kill swtpm kill $PID @@ -103,5 +125,5 @@ echo "[+] Running tests ..." echo "========================================================" for T in "${TESTS[@]}"; do name=$(basename "$T") - cd pkg$T && go test -v -coverprofile="$name.coverage.txt" -covermode=atomic + cd "$CWD/pkg$T" && go test -v -coverprofile="$name.coverage.txt" -covermode=atomic done