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...)
}
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
}