google/go-tpm

Support importing HMAC keys and operations

salrashid123 opened this issue · 1 comments

FR to support

  • importing external HMAC key
  • hmac some data with it

to quote offline thread

go-tpm does not support tpm2_hmac_start yet, but it does support hash sequences (so the only command that needs to be added is tpm2_hmac_start).

With tpm2_hmac_start in hand, you could import a key and use it for hmacs through the tpm2_hmac_start/tpm2_sequenceupdate/tpm2_sequencecomplete flows. Implementing this should be pretty similar to this PR.


tpm2_tools currently does not support importing hmac but does support ::

maybe something like this

  • tpm2/tpm2.go:
// 	CmdHmacStart                  tpmutil.Command = 0x0000015B

func HmacStart(rw io.ReadWriter, sequenceAuth string, handle tpmutil.Handle, hashAlg Algorithm) (seqHandle tpmutil.Handle, err error) {

	auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)})
	if err != nil {
		return 0, err
	}
	out, err := tpmutil.Pack(handle)
	if err != nil {
		return 0, err
	}
	Cmd, err := concat(out, auth)
	if err != nil {
		return 0, err
	}
	resp, err := runCommand(rw, TagSessions, CmdHmacStart, tpmutil.RawBytes(Cmd), tpmutil.U16Bytes(sequenceAuth), hashAlg)
	if err != nil {
		return 0, err
	}
	var rhandle tpmutil.Handle
	_, err = tpmutil.Unpack(resp, &rhandle)
	return rhandle, err
}

then load an hmac key (the following loads and signs one

package main

import (
	"crypto"
	"crypto/rand"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/google/go-tpm-tools/client"
	"github.com/google/go-tpm/tpm2"
	"github.com/google/go-tpm/tpmutil"
)

const (
	emptyPassword = ""
)

var (
	tpmPath = flag.String("tpm-path", "/dev/tpm0", "Path to the TPM device (character device or a Unix socket).")

	handleNames = map[string][]tpm2.HandleType{
		"all":       []tpm2.HandleType{tpm2.HandleTypeLoadedSession, tpm2.HandleTypeSavedSession, tpm2.HandleTypeTransient},
		"loaded":    []tpm2.HandleType{tpm2.HandleTypeLoadedSession},
		"saved":     []tpm2.HandleType{tpm2.HandleTypeSavedSession},
		"transient": []tpm2.HandleType{tpm2.HandleTypeTransient},
	}

	defaultKeyParams = tpm2.Public{
		Type:    tpm2.AlgRSA,
		NameAlg: tpm2.AlgSHA256,
		Attributes: tpm2.FlagDecrypt | tpm2.FlagRestricted | tpm2.FlagFixedTPM |
			tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth,
		AuthPolicy: []byte{},
		RSAParameters: &tpm2.RSAParams{
			Symmetric: &tpm2.SymScheme{
				Alg:     tpm2.AlgAES,
				KeyBits: 128,
				Mode:    tpm2.AlgCFB,
			},
			KeyBits: 2048,
		},
	}

	secret = []byte("change this password to a secret")
	data   = []byte("foo")
)

func main() {

	flag.Parse()

	rwc, err := tpm2.OpenTPM(*tpmPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't open TPM %s: %v", *tpmPath, err)
		os.Exit(1)
	}
	defer func() {
		if err := rwc.Close(); err != nil {
			fmt.Fprintf(os.Stderr, "can't close TPM %s: %v", *tpmPath, err)
			os.Exit(1)
		}
	}()

	totalHandles := 0
	for _, handleType := range handleNames["all"] {
		handles, err := client.Handles(rwc, handleType)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error getting handles", *tpmPath, err)
			os.Exit(1)
		}
		for _, handle := range handles {
			if err = tpm2.FlushContext(rwc, handle); err != nil {
				fmt.Fprintf(os.Stderr, "Error flushing handle 0x%x: %v\n", handle, err)
				os.Exit(1)
			}
			fmt.Printf("Handle 0x%x flushed\n", handle)
			totalHandles++
		}
	}

	pcrList := []int{}
	pcrSelection := tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: pcrList}

	pkh, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, pcrSelection, emptyPassword, emptyPassword, defaultKeyParams)
	if err != nil {

		fmt.Fprintf(os.Stderr, "Error creating Primary %v\n", err)
		os.Exit(1)
	}
	defer tpm2.FlushContext(rwc, pkh)

	inBuff := []byte(secret)

	// Create a private area containing the input
	private := tpm2.Private{
		Type:      tpm2.AlgKeyedHash,
		AuthValue: nil,
		SeedValue: make([]byte, 32),
		Sensitive: inBuff,
	}
	io.ReadFull(rand.Reader, private.SeedValue)

	privArea, err := private.Encode()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  encoding  private  %v\n", err)
		os.Exit(1)
	}

	duplicate, err := tpmutil.Pack(tpmutil.U16Bytes(privArea))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  encoding  dulicate  %v\n", err)
		os.Exit(1)
	}

	privHash := crypto.SHA256.New()
	privHash.Write(private.SeedValue)
	privHash.Write(private.Sensitive)
	public := tpm2.Public{
		Type:    tpm2.AlgKeyedHash,
		NameAlg: tpm2.AlgSHA256,
		//  Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagSign,  // gives "parameter 2, error code 0x2 : inconsistent attributes"
		Attributes: tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagSign, // works
		KeyedHashParameters: &tpm2.KeyedHashParams{
			Alg:    tpm2.AlgHMAC,
			Hash:   tpm2.AlgSHA256,
			Unique: privHash.Sum(nil),
		},
	}
	pubArea, err := public.Encode()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  encoding  public  %v\n", err)
		os.Exit(1)
	}

	emptyAuth := tpm2.AuthCommand{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession}
	privInternal, err := tpm2.Import(rwc, pkh, emptyAuth, pubArea, duplicate, nil, nil, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error Importing hash key  %v\n", err)
		os.Exit(1)
	}
	newHandle, _, err := tpm2.Load(rwc, pkh, "", pubArea, privInternal)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  loading hash key %v\n", err)
		os.Exit(1)
	}
	defer tpm2.FlushContext(rwc, newHandle)

	// pHandle := tpmutil.Handle(0x81010002)
	// err = tpm2.EvictControl(rwc, emptyPassword, tpm2.HandleOwner, newHandle, pHandle)
	// if err != nil {
	// 	fmt.Fprintf(os.Stderr,"Error  persisting hash key  %v\n", err)
	// 	os.Exit(1)
	// }
	// defer tpm2.FlushContext(rwc, pHandle)

	maxDigestBuffer := 1024
	seqAuth := ""
	seq, err := tpm2.HmacStart(rwc, seqAuth, newHandle, tpm2.AlgSHA256)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  starting hash sequence %v\n", err)
		os.Exit(1)
	}
	defer tpm2.FlushContext(rwc, seq)

	for len(data) > maxDigestBuffer {
		if err = tpm2.SequenceUpdate(rwc, seqAuth, seq, data[:maxDigestBuffer]); err != nil {
			fmt.Fprintf(os.Stderr, "Error  updating hash sequence %v\n", err)
			os.Exit(1)
		}
		data = data[maxDigestBuffer:]
	}

	digest, _, err := tpm2.SequenceComplete(rwc, seqAuth, seq, tpm2.HandleNull, data)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error  completing  hash sequence %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("digest %s\n", hex.EncodeToString(digest))

}