google / google/go-tpm

tpm2/credactivation: Generate uses the wrong hash algorithm

Open
#121 6 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
673
Forks
188
PR merge metrics
No merged PRs in 30d

Description

credactivation.Generate uses the AK's NameAlg in a bunch of places, but it should be using the EK's.

I've experimentally verified that this causes problems using tpm2-tools's tpm2_activatecredential tool with an AK that uses SHA1 as its name algorithm.

Incidentally, I think having the API require the user to provide symBlockSize is unnecessarily awkward. Instead, just have them provide the EK as a `tpm2.Public`, and then you can pick out the NameAlg and symmetric cipher specs automatically.

Finally, I was interested in supporting ECC endorsement keys, so I implemented KDFe and support for generating an ECC seed.

Code is still hacky and proof-of-concept-y, but I've experimentally verified that it works with {RSA2048, ECC-P256} x {SHA1, SHA256} EKs on my workstation's TPM2. I plan to eventually try it against Microsoft's TPM simulator so I can test larger RSA/ECC keys.

```
func Protect(ek *tpm2.Public, obj *tpm2.HashValue, blob []byte, random io.Reader) ([]byte, []byte, error) {
cv, err := tpmutil.Pack(tpmutil.U16Bytes(blob))
if err != nil {
return nil, nil, fmt.Errorf("generating cv (TPM2B_Digest): %v", err)
}

objName, err := obj.Encode()
if err != nil {
return nil, nil, fmt.Errorf("encoding objName: %v", err)
}

ekNameAlg := ek.NameAlg
ekNameAlgHash, err := ekNameAlg.HashConstructor()
if err != nil {
return nil, nil, err
}

ekKey, err := ek.Key()
if err != nil {
return nil, nil, err
}

var seed, encSecret []byte
var sym *tpm2.SymScheme
switch ekKey := ekKey.(type) {
case *rsa.PublicKey:
sym = ek.RSAParameters.Symmetric
seed, encSecret, err = generateRSA(ekNameAlgHash(), ekKey, random)
case *ecdsa.PublicKey:
sym = ek.ECCParameters.Symmetric
seed, encSecret, err = generateECC(ekNameAlg, ekKey, random)
default:
err = errors.New("only RSA public keys are supported for credential activation")
}
if err != nil {
return nil, nil, err
}

// TODO(mdempsky): Support additional algorithms/modes?
if sym.Alg != tpm2.AlgAES {
return nil, nil, fmt.Errorf("unsupported symmetric algorithm %v", sym.Alg)
}
if sym.Mode != tpm2.AlgCFB {
return nil, nil, fmt.Errorf("unsupported symmetric mode %v", sym.Mode)
}

encKey, err := tpm2.KDFa(ekNameAlg, seed, labelStorage, objName, nil, int(sym.KeyBits))
if err != nil {
return nil, nil, fmt.Errorf("generating symmetric key: %v", err)
}
macKey, err := tpm2.KDFa(ekNameAlg, seed, labelIntegrity, nil, nil, ekNameAlgHash().Size()*8)
if err != nil {
return nil, nil, fmt.Errorf("generating HMAC key: %v", err)
}

c, err := aes.NewCipher(encKey)
if err != nil {
return nil, nil, fmt.Errorf("symmetric cipher setup: %v", err)
}

// IV is all null bytes. encIdentity represents the encrypted credential.
encIdentity := make([]byte, len(cv))
cipher.NewCFBEncrypter(c, make([]byte, len(encKey))).XORKeyStream(encIdentity, cv)

mac := hmac.New(ekNameAlgHash, macKey)
mac.Write(encIdentity)
mac.Write(objName)
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 generateRSA(ekNameAlgHash hash.Hash, pub *rsa.PublicKey, random io.Reader) ([]byte, []byte, error) {
// 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.

// TODO(mdempsky): The spec suggests the seed size should
// match the hash function instead: "The seed size will be the
// size of a digest produced by the OAEP hash algorithm of the
// new parent." TPM 2.0, Part 1, Section B.10.3.
//
// Experimentally, any size seed seems to be accepted (even
// empty!!).

seed := make([]byte, 16)
if _, err := io.ReadFull(random, seed); err != nil {
return nil, nil, fmt.Errorf("generating seed: %v", err)
}

encSecret, err := rsa.EncryptOAEP(ekNameAlgHash, random, pub, seed, []byte(labelIdentity+"\x00"))
if err != nil {
return nil, nil, fmt.Errorf("generating encrypted seed: %v", err)
}

return seed, encSecret, err
}

func generateECC(ekNameAlg tpm2.Algorithm, pub *ecdsa.PublicKey, random io.Reader) ([]byte, []byte, error) {
N := (pub.Curve.Params().BitSize + 7) / 8

// TODO(mdempsky): Generalize.
bits := 256
if ekNameAlg == tpm2.AlgSHA1 {
bits = 160
}

d, x, y, err := elliptic.GenerateKey(pub.Curve, random)
if err != nil {
return nil, nil, err
}

var encSecret []byte
encSecret = append(encSecret, be16(uint16(N))...)
encSecret = append(encSecret, padEC(x, N)...)
encSecret = append(encSecret, be16(uint16(N))...)
encSecret = append(encSecret, padEC(y, N)...)

px, _ := pub.Curve.ScalarMult(pub.X, pub.Y, d)

seed, err := tpm2.KDFe(ekNameAlg, padEC(px, N), labelIdentity, padEC(x, N), padEC(pub.X, N), bits)
if err != nil {
return nil, nil, err
}

return seed, encSecret, err
}

// padEC pads ECC coordinates according to TPM2.0, Part 1, C.8 "ECC Point Padding".
func padEC(x *big.Int, n int) []byte {
b := x.Bytes()
if len(b) >= n {
return b
}
pad := make([]byte, n - len(b))
return append(pad, b...)
}
```

In tpm2/kdf.go:

```
func KDFe(hashAlg Algorithm, Z []byte, label string, partyU, partyV []byte, bits int) ([]byte, error) {
var h hash.Hash
switch hashAlg {
case AlgSHA1:
h = sha1.New()
case AlgSHA256:
h = sha256.New()
default:
return nil, fmt.Errorf("hash algorithm 0x%x is not supported", hashAlg)
}

var out []byte
var counter uint32
for 8*len(out) < {
counter++
if err := binary.Write(h, binary.BigEndian, counter); err != nil {
return nil, fmt.Errorf("pack counter: %v", err)
}
h.Write(Z)
h.Write([]byte(label))
h.Write([]byte{0}) // Terminating null character for C-string.
h.Write(partyU)
h.Write(partyV)

out = h.Sum(out)
h.Reset()
}

if partial := bits%8; partial != 0 {
out[0] &= (1 << uint(partial)) - 1
}
return out[:(bits+7)/8], nil
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.