diff --git a/examples/tpm-fakeekcert/tpm-fakeekcert.go b/examples/tpm-fakeekcert/tpm-fakeekcert.go new file mode 100644 index 00000000..a6cfb8cb --- /dev/null +++ b/examples/tpm-fakeekcert/tpm-fakeekcert.go @@ -0,0 +1,132 @@ +// Copyright (c) 2019, 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 main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/binary" + "flag" + "fmt" + "io" + "math/big" + "os" + "time" + + "github.com/google/go-tpm/tpm" +) + +var ( + ownerAuthEnvVar = "TPM_OWNER_AUTH" + + tpmPath = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") + certPath = flag.String("cert", "ek.der", "The path to write the EK out to") + certOrg = flag.String("cert_org", "Fake EK", "The organization string to use in the EKCert") +) + +func generateCertificate(pub *rsa.PublicKey) ([]byte, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{*certOrg}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + } + + return x509.CreateCertificate(rand.Reader, &template, &template, pub, priv) +} + +func writePCCert(f io.Writer, der []byte) error { + // Write the header as documented in: TCG PC Specific Implementation + // Specification, section 7.3.2. + if _, err := f.Write([]byte{0x10, 0x01, 0x00}); err != nil { + return err + } + certLength := make([]byte, 2) + binary.BigEndian.PutUint16(certLength, uint16(len(der))) + if _, err := f.Write(certLength); err != nil { + return err + } + + _, err := f.Write(der) + return err +} + +func main() { + flag.Parse() + + var ownerAuth [20]byte + ownerInput := os.Getenv(ownerAuthEnvVar) + if ownerInput != "" { + oa := sha1.Sum([]byte(ownerInput)) + copy(ownerAuth[:], oa[:]) + } + + rwc, err := tpm.OpenTPM(*tpmPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't open the TPM at %q: %v\n", *tpmPath, err) + return + } + + pubEK, err := tpm.OwnerReadPubEK(rwc, ownerAuth) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't read the endorsement key: %v\n", err) + return + } + pub, err := tpm.DecodePublic(pubEK) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't decode the endorsement key: %v\n", err) + return + } + + der, err := generateCertificate(pub.(*rsa.PublicKey)) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't generate a certificate: %v\n", err) + return + } + + f, err := os.OpenFile(*certPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0744) + if err != nil { + fmt.Fprintf(os.Stderr, "Could open certificate path %q: %v\n", *certPath, err) + return + } + defer func() { + if err := f.Close(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to close %q: %v\n", *certPath, err) + } + }() + + if err := writePCCert(f, der); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write certificate: %v\n", err) + return + } +} diff --git a/examples/tpm-nvwrite/tpm-nvwrite.go b/examples/tpm-nvwrite/tpm-nvwrite.go new file mode 100644 index 00000000..00344e56 --- /dev/null +++ b/examples/tpm-nvwrite/tpm-nvwrite.go @@ -0,0 +1,74 @@ +// Copyright (c) 2019, 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 main + +import ( + "crypto/sha1" + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/google/go-tpm/tpm" +) + +const ekCertIndex = 268496896 + +var ( + ownerAuthEnvVar = "TPM_OWNER_AUTH" + + tpmPath = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") + index = flag.Uint("index", ekCertIndex, "NV index to write to") + defineSpace = flag.Bool("define_space", false, "Whether to define the region in NVRAM") +) + +func main() { + flag.Parse() + if flag.NArg() < 1 { + fmt.Fprintf(os.Stderr, "Error: Missing required argument.") + return + } + + rwc, err := tpm.OpenTPM(*tpmPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmPath, err) + return + } + + var ownerAuth [20]byte + ownerInput := os.Getenv(ownerAuthEnvVar) + if ownerInput != "" { + oa := sha1.Sum([]byte(ownerInput)) + copy(ownerAuth[:], oa[:]) + } + + data, err := ioutil.ReadFile(flag.Arg(0)) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to read input data from %s: %s\n", flag.Arg(0), err) + return + } + + if *defineSpace { + if err := tpm.NVDefineSpace(rwc, uint32(*index), uint32(len(data)), 0, ownerAuth); err != nil { + fmt.Fprintf(os.Stderr, "NVDefineSpace failed: %v\n", err) + return + } + } + + if err := tpm.NVWriteValue(rwc, uint32(*index), 0, data, ownerAuth); err != nil { + fmt.Fprintf(os.Stderr, "NVWriteValue failed: %v\n", err) + return + } +} diff --git a/tpm/commands.go b/tpm/commands.go index b9033d40..df8b5663 100644 --- a/tpm/commands.go +++ b/tpm/commands.go @@ -170,6 +170,28 @@ func nvReadValue(rw io.ReadWriter, index, offset, len uint32, ca *commandAuth) ( return b, &ra, ret, nil } +func nvWriteValue(rw io.ReadWriter, index, offset uint32, data []byte, ca *commandAuth) (*responseAuth, uint32, error) { + var ra responseAuth + in := []interface{}{index, offset, tpmutil.U32Bytes(data), ca} + out := []interface{}{&ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVWriteValue, in, out) + if err != nil { + return nil, 0, err + } + return &ra, ret, nil +} + +func nvDefineSpace(rw io.ReadWriter, pub *nvPublicDescription, ca *commandAuth) (*responseAuth, uint32, error) { + var ra responseAuth + in := []interface{}{pub, ca} + out := []interface{}{&ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVDefineSpace, in, out) + if err != nil { + return nil, 0, err + } + return &ra, ret, nil +} + // quote2 signs arbitrary data under a given set of PCRs and using a key // specified by keyHandle. It returns information about the PCRs it signed // under, the signature, auth information, and optionally information about the diff --git a/tpm/constants.go b/tpm/constants.go index 3b5b4f5f..b9433719 100644 --- a/tpm/constants.go +++ b/tpm/constants.go @@ -53,6 +53,8 @@ const ( ordOwnerReadInternalPub uint32 = 0x00000081 ordFlushSpecific uint32 = 0x000000BA ordPcrReset uint32 = 0x000000C8 + ordNVDefineSpace uint32 = 0x000000CC + ordNVWriteValue uint32 = 0x000000CD ordNVReadValue uint32 = 0x000000CF ) @@ -170,3 +172,22 @@ const quoteVersion uint32 = 0x01010000 // oaepLabel is the label used for OEAP encryption in esRSAEsOAEPSHA1MGF1 var oaepLabel = []byte{byte('T'), byte('C'), byte('P'), byte('A')} + +// NV Attributes, as defined by section 19.2 of +// the TPM 1.2 structures specification. +const ( + NVAttrPPWrite NVAttr = (1 << 0) + NVAttrOwnerWrite NVAttr = (1 << 1) + NVAttrAuthWrite NVAttr = (1 << 2) + NVAttrWriteAll NVAttr = (1 << 12) + NVAttrWriteDefine NVAttr = (1 << 13) + NVAttrWriteSTClear NVAttr = (1 << 14) + NVAttrGlobalLock NVAttr = (1 << 15) + NVAttrPPRead NVAttr = (1 << 16) + NVAttrOwnerRead NVAttr = (1 << 17) + NVAttrAuthRead NVAttr = (1 << 18) + NVAttrReadSTClear NVAttr = (1 << 31) +) + +// NVAttr represents a NV area attributes value. +type NVAttr uint32 diff --git a/tpm/structures.go b/tpm/structures.go index 98be26f3..45fffdd3 100644 --- a/tpm/structures.go +++ b/tpm/structures.go @@ -56,8 +56,8 @@ type pcrInfoLong struct { // pcrInfoShort stores detailed information about PCRs. type pcrInfoShort struct { - LocAtRelease byte PCRsAtRelease pcrSelection + LocAtRelease byte DigestAtRelease digest } @@ -314,3 +314,49 @@ func convertPubKey(pk crypto.PublicKey) (*pubKey, error) { return &pubKey, nil } + +// DecodePublic consumes bytes representing a TPM_PUBKEY, and returns a +// crypto.PublicKey representing the encoded public key. Currently, this +// function only supports 2048-bit RSA keys. +func DecodePublic(b []byte) (crypto.PublicKey, error) { + var pk pubKey + if _, err := tpmutil.Unpack(b, &pk); err != nil { + return nil, err + } + if pk.AlgorithmParams.AlgID != algRSA { + return nil, fmt.Errorf("expected RSA algorithm, got %v", pk.AlgorithmParams.AlgID) + } + var kp rsaKeyParams + if _, err := tpmutil.Unpack(pk.AlgorithmParams.Params, &kp); err != nil { + return nil, fmt.Errorf("unpacking rsaKeyParams: %v", err) + } + if kp.KeyLength != 2048 { + return nil, fmt.Errorf("only 2048-bit keys supported, got %d-bit", kp.KeyLength) + } + + return &rsa.PublicKey{ + E: int(big.NewInt(0).SetBytes(kp.Exponent).Int64()), + N: big.NewInt(0).SetBytes(pk.Key), + }, nil +} + +// NVAttributesArea describes the permissions set on an NV area. +type NVAttributesArea struct { + Tag uint16 + Attributes NVAttr +} + +// nvPublicDescription describes the public description and controls on +// a NV area. This struct mirrors the layout of the TPM_NV_DATA_PUBLIC +// structure in the TPM 1.2 specification. +type nvPublicDescription struct { + Tag uint16 + Index uint32 + ReadPCRState pcrInfoShort + WritePCRState pcrInfoShort + Attributes NVAttributesArea + ReadSTClear byte + WriteSTClear byte + WriteDefine byte + DataSize uint32 +} diff --git a/tpm/tpm.go b/tpm/tpm.go index 2ab9dc5d..61ff18e7 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -934,6 +934,77 @@ func NVReadValue(rw io.ReadWriter, index, offset, len uint32, ownAuth digest) ([ return data, nil } +// NVDefineSpace creates a new region in NVRAM at the specified index, and +// with the specified properties. See TPM-Main-Part-2-TPM-Structures 19.3. +func NVDefineSpace(rw io.ReadWriter, index, len uint32, attrs NVAttr, ownAuth digest) error { + pcrs, err := newPCRSelection([]int{}) + if err != nil { + return fmt.Errorf("newPCRSelection failed: %v", err) + } + pcrState := pcrInfoShort{ + LocAtRelease: 0x1f, + PCRsAtRelease: *pcrs, + } + + pub := nvPublicDescription{ + Tag: 0x0018, + Index: index, + ReadPCRState: pcrState, + WritePCRState: pcrState, + Attributes: NVAttributesArea{ + Tag: 0x0017, + Attributes: attrs, + }, + DataSize: len, + } + + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:]) + if err != nil { + return fmt.Errorf("failed to start new auth session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + authIn := []interface{}{ordNVDefineSpace, &pub} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + if err != nil { + return fmt.Errorf("failed to construct owner auth fields: %v", err) + } + ra, ret, err := nvDefineSpace(rw, &pub, ca) + if err != nil { + return fmt.Errorf("failed to define space in NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVDefineSpace} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return fmt.Errorf("failed to verify authenticity of response: %v", err) + } + return nil +} + +// NVWriteValue writes the value into a given index & offset in NVRAM. +// See TPM-Main-Part-2-TPM-Commands 20.2. +func NVWriteValue(rw io.ReadWriter, index, offset uint32, data []byte, ownAuth digest) error { + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:]) + if err != nil { + return fmt.Errorf("failed to start new auth session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + authIn := []interface{}{ordNVWriteValue, index, offset, tpmutil.U32Bytes(data)} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + if err != nil { + return fmt.Errorf("failed to construct owner auth fields: %v", err) + } + ra, ret, err := nvWriteValue(rw, index, offset, data, ca) + if err != nil { + return fmt.Errorf("failed to read from NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVWriteValue} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return fmt.Errorf("failed to verify authenticity of response: %v", err) + } + return nil +} + // OwnerReadPubEK uses owner auth to get a blob representing the public part of the // endorsement key. func OwnerReadPubEK(rw io.ReadWriter, ownerAuth digest) ([]byte, error) { diff --git a/tpm/tpm_test.go b/tpm/tpm_test.go index fdfb18e9..d9cae7ee 100644 --- a/tpm/tpm_test.go +++ b/tpm/tpm_test.go @@ -17,6 +17,7 @@ package tpm import ( "bytes" "crypto/rand" + "crypto/rsa" "crypto/sha1" "crypto/x509" "io/ioutil" @@ -554,3 +555,33 @@ func TestForceClear(t *testing.T) { t.Fatal("Couldn't clear the TPM without owner auth in physical presence mode:", err) } } + +func TestEncodeDecodePubkey(t *testing.T) { + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + pk, err := convertPubKey(k.Public()) + if err != nil { + t.Fatalf("convertPubKey() failed: %v", err) + } + + packed, err := tpmutil.Pack(&pk) + if err != nil { + t.Fatalf("tpmutil.Pack(&pk) failed: %v", err) + } + pk2, err := DecodePublic(packed) + if err != nil { + t.Fatalf("DecodePublic() failed: %v", err) + } + decodedPK := pk2.(*rsa.PublicKey) + + if k.N.Cmp(decodedPK.N) != 0 { + t.Errorf("k.N != decodedPK.N") + t.Logf("Got: %v", k.N) + t.Logf("Want: %v", decodedPK.N) + } + if k.E != decodedPK.E { + t.Errorf("decodedPK.E = %v, want %v", decodedPK.E, k.E) + } +}