google / go-tpm

Apache License 2.0
549 stars 158 forks source link

tpm2/credactivation: Generate uses the wrong hash algorithm #121

Open mdempsky opened 5 years ago

mdempsky commented 5 years ago

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
}
twitchy-jsonp commented 4 years ago

Thanks for the code! I'd be interested in getting this in via a PR if theres appetite, ECC AKs would be great to support.

The original intent behind passing a crypto.Public + symBlockSize was that credential activation would be done on the server, where building into tpm2.Public for the EK would be onerous and (in the case of windows, not all those details would be available).

Which places are the wrong NameAlg being used?

mdempsky commented 4 years ago

Thanks for the code! I'd be interested in getting this in via a PR if theres appetite, ECC AKs would be great to support.

Okay, I'll work on a PR this week.

The original intent behind passing a crypto.Public + symBlockSize was that credential activation would be done on the server, where building into tpm2.Public for the EK would be onerous and (in the case of windows, not all those details would be available).

Can you elaborate? The client already has to send their AK's TPM2B_PUBLIC to the server, so it doesn't seem onerous to send the EK's TPM2B_PUBLIC as well.

However, if there are existing protocols that only send the bare EK public key (e.g., the client only sends its EK's X.509 key certificate) and we want to easily support those, I'd probably design the API like:

// Low-level credential protection primitive.
func Protect(ekNameAlg tpm2.NameAlg, ekPub crypto.PublicKey, ekSym *tpm2.SymScheme, ...) {
    // Above code.
}

// User friendly interface that supports arbitrary EKs.
func ProtectPublic(ek *tpm2.Public, ...) {
    pub, err := ek.Key()
    if err != nil {
        return ..., err
    }
    switch pub.(type) {
    case *rsa.PublicKey:
        return Protect(ek.NameAlg, pub, ek.RSAParameters.Symmetric, ...)
    case *ecdsa.PublicKey:
        return Protect(ek.NameAlg, pub, ek.ECCParameters.Symmetric, ...)
    }
    return ..., errors.New(...)
}

// ProtectDefault assumes ekPub was generated using a default EK template
// defined by "TCG EK Credential Profile For TPM Family 2.0".
func ProtectDefault(ekPub crypto.PublicKey, ...) {
    switch ekPub := ekPub.(type) {
    case *rsa.PublicKey:
        switch ekPub.Size() * 8 {
        case 2048: // Templates L-1 and H-1
            return Protect(tpm2.AlgSHA256, ekPub, symAESCFB128, ...)
        }
    case *ecdsa.PublicKey:
         switch ekPub.Params().Name {
         case "P-256": // Templates L-2 and H-2
             return Protect(tpm2.AlgSHA256, ekPub, symAESCFB128, ...)
         case "P-384": // Template H-3
             return Protect(tpm2.AlgSHA384, ekPub, symAESCFB256, ...)
         case "P-521": // Template H-4
             return Protect(tpm2.AlgSHA512, ekPub, symAESCFB256, ...)
         }
    }
    return ..., errors.New(...)
}

var symAESCFB128 = &tpm2.Symmetric{tpm2.AlgAES, 128, tpm2.AlgCFB}
var symAESCFB256 = &tpm2.Symmetric{tpm2.AlgAES, 256, tpm2.AlgCFB}

Which places are the wrong NameAlg being used?

Everywhere that aik.Alg is mentioned at https://github.com/google/go-tpm/blob/master/tpm2/credactivation/credential_activation.go.

The AK's NameAlg is only needed for hashing the AK's Public structure into its Name. Everything else (i.e., the RSA/ECC key-exchange, the AES and HMAC KDF computations, and the underlying hash used with the HMAC) should use the EK's NameAlg instead.

Trusted Platform Module Library, Part 1: Architecture, Sections 24.4 and 24.5 directly mention "ekNameAlg" for all of the symmetric key operations.

The use of the EK's public key for the RSA/ECC key-exchange is less explicitly worded; but it's a consequence of using RSA Secret Sharing (section B.10) or ECC Secret Sharing (section C.6), and in context their mentions of "nameAlg" refer to the key that the secret is being shared with (which is the EK; see section 24.2, "and then 'wraps' the credential encryption key with the public key of the 'EK.'").

twitchy-jsonp commented 4 years ago

Can you elaborate? The client already has to send their AK's TPM2B_PUBLIC to the server, so it doesn't seem onerous to send the EK's TPM2B_PUBLIC as well.

On windows + using the platform crypto provider, we dont get a direct channel to the TPM that is authorized for the EK. As such, we use other APIs to read the public details of the EK, but we can cant get a TPM2B_PUBLIC for the EK. There is a way to get the AK's TPM2B_PUBLIC however.

Now that I think about it though, the template for the windows EK is well defined + fixed to this, so maybe in the windows case we can just fill in a tpm2.Public manually?

mdempsky commented 4 years ago

On windows + using the platform crypto provider, we dont get a direct channel to the TPM that is authorized for the EK.

I see. Thanks, I'm not familiar with Windows's TPM APIs.

Now that I think about it though, the template for the windows EK is well defined + fixed to this, so maybe in the windows case we can just fill in a tpm2.Public manually?

Windows only supports the Template L-1 EK profile? It doesn't even support ECC EKs?

Either way, if you only have the EK's public key, you can reconstruct the important parts of the EK's TPM2B_PUBLIC; in particular, the nameAlg and parameters.symmetric fields.

As a minor nit though, you can't distinguish Template L-1 from H-1, or L-2 from H-2, based on public key alone. So you wouldn't be able to reconstruct objectAttributes, authPolicy, or unique without extra information. This is why I suggested ProtectDefault above.

twitchy-jsonp commented 4 years ago

Windows only supports the Template L-1 EK profile? It doesn't even support ECC EKs?

I'm unsure if ECC EKs are specified, but they are not required. Windows 8/10+ requires:

Either way, if you only have the EK's public key, you can reconstruct the important parts of the EK's TPM2B_PUBLIC; in particular, the nameAlg and parameters.symmetric fields.

As a minor nit though, you can't distinguish Template L-1 from H-1, or L-2 from H-2, based on public key alone. So you wouldn't be able to reconstruct objectAttributes, authPolicy, or unique without extra information. This is why I suggested ProtectDefault above.

Given the EK is well specified on windows, I think filling in the missing fields would work fine. As such, I'm okay with either (ProtectDefault or passing tpm2.Public) approach.

twitchy-jsonp commented 4 years ago

Heya, just wondering if you had any update on this?