diff --git a/attest/activation.go b/attest/activation.go new file mode 100644 index 0000000..fe637fe --- /dev/null +++ b/attest/activation.go @@ -0,0 +1,280 @@ +package attest + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "errors" + "fmt" + "io" + + tpm1 "github.com/google/go-tpm/tpm" + "github.com/google/go-tpm/tpm2" + + // TODO(jsonp): Move activation generation code to internal package. + "github.com/google/go-tpm/tpm2/credactivation" + "github.com/google/go-tspi/verification" +) + +const ( + // minRSABits is the minimum accepted bit size of an RSA key. + minRSABits = 2048 + // activationSecretLen is the size in bytes of the generated secret + // which is generated for credential activation. + activationSecretLen = 32 + // symBlockSize is the block size used for symmetric ciphers used + // when generating the credential activation challenge. + symBlockSize = 16 + // tpm20GeneratedMagic is a magic tag when can only be present on a + // TPM structure if the structure was generated wholly by the TPM. + tpm20GeneratedMagic = 0xff544347 +) + +func cryptoHash(h tpm2.Algorithm) (crypto.Hash, error) { + switch h { + case tpm2.AlgSHA1: + return crypto.SHA1, nil + case tpm2.AlgSHA256: + return crypto.SHA256, nil + case tpm2.AlgSHA384: + return crypto.SHA384, nil + case tpm2.AlgSHA512: + return crypto.SHA512, nil + default: + return crypto.Hash(0), fmt.Errorf("unsupported signature digest: %v", h) + } +} + +// ActivationParameters encapsulates the inputs for activating an AIK. +type ActivationParameters struct { + // TPMVersion holds the version of the TPM, either 1.2 or 2.0. + TPMVersion TPMVersion + + // EK, the endorsement key, describes an asymmetric key who's + // private key is permenantly bound to the TPM. + // + // Activation will verify that the provided EK is held on the same + // TPM as the AIK. However, it is the callers responsibility to + // ensure the EK they provide corresponds to the the device which + // they are trying to associate the AIK with. + EK crypto.PublicKey + + // AIK, the Attestation Identity Key, describes the properties of + // an asymmetric key (managed by the TPM) which signs attestation + // structures. + // The values from this structure can be obtained by calling + // Parameters() on an attest.AIK. + AIK AttestationParameters + + // Rand is a source of randomness to generate a seed and secret for the + // challenge. + // + // If nil, this defaults to crypto.Rand. + Rand io.Reader +} + +// checkAIKParameters examines properties of an AIK and a creation +// attestation, to determine if it is suitable for use as an attestation key. +func (p *ActivationParameters) checkAIKParameters() error { + switch p.TPMVersion { + case TPMVersion12: + return p.checkTPM12AIKParameters() + + case TPMVersion20: + return p.checkTPM20AIKParameters() + + default: + return fmt.Errorf("TPM version %d not supported", p.TPMVersion) + } +} + +func (p *ActivationParameters) checkTPM12AIKParameters() error { + // TODO(jsonp): Implement helper to parse public blobs, ie: + // func ParsePublic(publicBlob []byte) (crypto.Public, error) + + pub, err := tpm1.UnmarshalPubRSAPublicKey(p.AIK.Public) + if err != nil { + return fmt.Errorf("unmarshalling public key: %v", err) + } + if bits := pub.Size() * 8; bits < minRSABits { + return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, bits) + } + return nil +} + +func (p *ActivationParameters) checkTPM20AIKParameters() error { + if len(p.AIK.CreateSignature) < 8 { + return fmt.Errorf("signature is too short to be valid: only %d bytes", len(p.AIK.CreateSignature)) + } + + pub, err := tpm2.DecodePublic(p.AIK.Public) + if err != nil { + return fmt.Errorf("DecodePublic() failed: %v", err) + } + _, err = tpm2.DecodeCreationData(p.AIK.CreateData) + if err != nil { + return fmt.Errorf("DecodeCreationData() failed: %v", err) + } + att, err := tpm2.DecodeAttestationData(p.AIK.CreateAttestation) + if err != nil { + return fmt.Errorf("DecodeAttestationData() failed: %v", err) + } + if att.Type != tpm2.TagAttestCreation { + return fmt.Errorf("attestation does not apply to creation data, got tag %x", att.Type) + } + + // TODO: Support ECC AIKs. + switch pub.Type { + case tpm2.AlgRSA: + if pub.RSAParameters.KeyBits < minRSABits { + return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits) + } + default: + return fmt.Errorf("public key of alg 0x%x not supported", pub.Type) + } + + // Compute & verify that the creation data matches the digest in the + // attestation structure. + nameHashConstructor, err := pub.NameAlg.HashConstructor() + if err != nil { + return fmt.Errorf("HashConstructor() failed: %v", err) + } + h := nameHashConstructor() + h.Write(p.AIK.CreateData) + if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) { + return errors.New("attestation refers to different public key") + } + + // Make sure the AIK has sane key parameters (Attestation can be faked if an AIK + // can be used for arbitrary signatures). + // We verify the following: + // - Key is TPM backed. + // - Key is TPM generated. + // - Key is a restricted key (means it cannot do arbitrary signing/decrypt ops). + // - Key cannot be duplicated. + // - Key was generated by a call to TPM_Create*. + if att.Magic != tpm20GeneratedMagic { + return errors.New("creation attestation was not produced by a TPM") + } + if (pub.Attributes & tpm2.FlagFixedTPM) == 0 { + return errors.New("AIK is exportable") + } + if ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0) { + return errors.New("provided key is not limited to attestation") + } + + // Verify the attested creation name matches what is computed from + // the public key. + match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub) + if err != nil { + return err + } + if !match { + return errors.New("creation attestation refers to a different key") + } + + // Check the signature over the attestation data verifies correctly. + pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus} + signHashConstructor, err := pub.RSAParameters.Sign.Hash.HashConstructor() + if err != nil { + return err + } + hsh := signHashConstructor() + hsh.Write(p.AIK.CreateAttestation) + verifyHash, err := cryptoHash(pub.RSAParameters.Sign.Hash) + if err != nil { + return err + } + + if len(p.AIK.CreateSignature) < 8 { + return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AIK.CreateSignature)) + } + + sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AIK.CreateSignature)) + if err != nil { + return fmt.Errorf("DecodeSignature() failed: %v", err) + } + + if err := rsa.VerifyPKCS1v15(&pk, verifyHash, hsh.Sum(nil), sig.RSA.Signature); err != nil { + return fmt.Errorf("could not verify attestation: %v", err) + } + + return nil +} + +// Generate returns a credential activation challenge, which can be provided +// to the TPM to verify the AIK parameters given are authentic & the AIK +// is present on the same TPM as the EK. +func (p *ActivationParameters) Generate() (secret []byte, ec *EncryptedCredential, err error) { + if err := p.checkAIKParameters(); err != nil { + return nil, nil, err + } + + if p.EK == nil { + return nil, nil, errors.New("no EK provided") + } + + rnd, secret := p.Rand, make([]byte, activationSecretLen) + if rnd == nil { + rnd = rand.Reader + } + if _, err = io.ReadFull(rnd, secret); err != nil { + return nil, nil, fmt.Errorf("error generating activation secret: %v", err) + } + + switch p.TPMVersion { + case TPMVersion12: + ec, err = p.generateChallengeTPM12(secret) + case TPMVersion20: + ec, err = p.generateChallengeTPM20(secret) + default: + return nil, nil, fmt.Errorf("unrecognised TPM version: %v", p.TPMVersion) + } + + if err != nil { + return nil, nil, err + } + return secret, ec, nil +} + +func (p *ActivationParameters) generateChallengeTPM20(secret []byte) (*EncryptedCredential, error) { + att, err := tpm2.DecodeAttestationData(p.AIK.CreateAttestation) + if err != nil { + return nil, fmt.Errorf("DecodeAttestationData() failed: %v", err) + } + cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSize, secret) + if err != nil { + return nil, fmt.Errorf("credactivation.Generate() failed: %v", err) + } + + return &EncryptedCredential{ + Credential: cred, + Secret: encSecret, + }, nil +} + +func (p *ActivationParameters) generateChallengeTPM12(secret []byte) (*EncryptedCredential, error) { + pk, ok := p.EK.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("got EK of type %T, want an RSA key", p.EK) + } + + var ( + cred, encSecret []byte + err error + ) + if p.AIK.UseTCSDActivationFormat { + cred, encSecret, err = verification.GenerateChallengeEx(pk, p.AIK.Public, secret) + } else { + cred, encSecret, err = generateChallenge12(pk, p.AIK.Public, secret) + } + + if err != nil { + return nil, fmt.Errorf("challenge generation failed: %v", err) + } + return &EncryptedCredential{ + Credential: cred, + Secret: encSecret, + }, nil +} diff --git a/attest/activation_test.go b/attest/activation_test.go new file mode 100644 index 0000000..faccd8f --- /dev/null +++ b/attest/activation_test.go @@ -0,0 +1,69 @@ +package attest + +import ( + "bytes" + "crypto/rsa" + "encoding/base64" + "math/big" + "math/rand" + "testing" +) + +func decodeBase10(base10 string, t *testing.T) *big.Int { + i, ok := new(big.Int).SetString(base10, 10) + if !ok { + t.Fatalf("failed decode of base10: %q", base10) + } + return i +} + +func decodeBase64(in string, t *testing.T) []byte { + out, err := base64.StdEncoding.DecodeString(in) + if err != nil { + t.Fatal(err) + } + return out +} + +func ekCertSigner(t *testing.T) *rsa.PrivateKey { + return &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: decodeBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557", t), + E: 3, + }, + D: decodeBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731", t), + Primes: []*big.Int{ + decodeBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433", t), + decodeBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029", t), + }, + } +} + +func TestActivationTPM20(t *testing.T) { + priv := ekCertSigner(t) + rand := rand.New(rand.NewSource(123456)) + + // These parameters represent an AIK generated on a real-world, infineon TPM. + params := ActivationParameters{ + TPMVersion: TPMVersion20, + AIK: AttestationParameters{ + Public: decodeBase64("AAEACwAFBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAFAAECAAAAAAAAQC/08gj/04z4xGMIVTmr02lzhI5epufXgU831xEpf2qpXfvtNGUfqTcgWF2EUux2HDPqgcj59dtXRobQdlr4uCGNzfZIGAej4JusLa4MjpG6W2DtJPot6F1Mry63talzJ36U47niy9Iesd34CO2p9Xk3+86ZmBnQ6PQ2roUNK3l7bKz6cFLM9drOLwCqU0AUl6pHvzYPPz+xXsPl3iaA2cM97oneUiJNmJM7wtR9OcaKyIA4wVlX5TndB9NwWq5Iuj8q2Sp40Dg0noXXGSPliAtVD8flkXtAcuI9UHkQbzu9cGPRdSJPMn743GONg3bYalFtcgh2VpACXkPbXB32J7B", t), + CreateData: decodeBase64("AAAAAAAg47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUBAAsAIgALWI9hwDRB3zYSkannqM5z0J1coQNA1Jz/oCRxJQwTaNwAIgALmyFYBhHeIU3FUKIAPgXFD3NXyasP3siQviDEyH7avu4AAA==", t), + CreateAttestation: decodeBase64("/1RDR4AaACIAC41+jhmEOue1MZhJjIk79ENar6i15rBvamXLpQnGTBCOAAAAAAAAD3GRNfU4syzJ1jQGATDCDteFC5C4ACIAC3ToMYGy9GXxcf8A0HvOuLOHbU7HPEppM47C7CMcU8TtACBDmJFUFO1f5+BYevaYdd3VtfMCsxIuHhoTZJczzLP2BA==", t), + CreateSignature: decodeBase64("ABQABAEALVzJSnKRJU39gHjETaI89/sM1L6HwBPGNekw6NojSW8bwD5/W1cLRDakCsYKUQu68mmbjs8xaIVBRvVM2YWP10tbTWNB0iJc9b8rERhkk3QIIFm/XsiVZsb0mysTxfeh8zygaAKQ/50sYyzp+raD0Ho0mYIRKJOEdQ6chsBflM3eB8mCXGTugUfrET80q3iu0gncaKWbfxQaQUb9ZTPSJrTN64HQ9tlOfnGT+8++WA3hV0NqKMnoAqiI9GZnI5MPXs6XxEncu/GJLJpAYZakBiS74Jvlr34Pur32B4xjm1M25AUGHEIgb6r49S0sV+hzaKu45858lQRMXj01GcyBhw==", t), + }, + EK: &rsa.PublicKey{ + E: priv.E, + N: priv.N, + }, + Rand: rand, + } + + secret, _, err := params.Generate() + if err != nil { + t.Fatalf("Generate() returned err: %v", err) + } + if got, want := secret, decodeBase64("0vhS7HtORX9uf/iyQ8Sf9WkpJuoJ1olCfTjSZuyNNxY=", t); !bytes.Equal(got, want) { + t.Fatalf("secret = %v, want %v", got, want) + } +} diff --git a/attest/attest.go b/attest/attest.go index 754b9cd..0fdd00e 100644 --- a/attest/attest.go +++ b/attest/attest.go @@ -99,7 +99,7 @@ type aik interface { Marshal() ([]byte, error) ActivateCredential(tpm *TPM, in EncryptedCredential) ([]byte, error) Quote(t *TPM, nonce []byte, alg HashAlg) (*Quote, error) - Parameters() AIKParameters + AttestationParameters() AttestationParameters Public() crypto.PublicKey } @@ -134,8 +134,8 @@ func (k *AIK) Quote(tpm *TPM, nonce []byte, alg HashAlg) (*Quote, error) { // Parameters returns information about the AIK, typically used to generate // a credential activation challenge. -func (k *AIK) Parameters() AIKParameters { - return k.aik.Parameters() +func (k *AIK) AttestationParameters() AttestationParameters { + return k.aik.AttestationParameters() } // Public returns the public part of the AIK. @@ -177,9 +177,9 @@ type PlatformEK struct { Public crypto.PublicKey } -// AIKParameters describes information about an AIK. This information -// is typically used to generate an activation challenge. -type AIKParameters struct { +// AttestationParameters describes information about a key which is necessary +// for verifying its properties remotely. +type AttestationParameters struct { // Public represents the public key in a TPM-version specific encoding. // For TPM 2.0 devices, this is encoded as a TPMT_PUBLIC structure. // For TPM 1.2 devices, this is a TPM_PUBKEY structure, as defined in @@ -187,6 +187,11 @@ type AIKParameters struct { // https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011.pdf Public []byte + // UseTCSDActivationFormat is set when tcsd (trousers daemon) is operating + // as an intermediary between this library and the TPM. A value of true + // indicates that activation challenges should use the TCSD-specific format. + UseTCSDActivationFormat bool + // Subsequent fields are only populated for AIKs generated on a TPM // implementing version 2.0 of the specification. The specific structures // referenced for each field are defined in the TPM Revision 2, Part 2 - diff --git a/attest/attest_simulated_tpm20_test.go b/attest/attest_simulated_tpm20_test.go index 11037a5..2e153af 100644 --- a/attest/attest_simulated_tpm20_test.go +++ b/attest/attest_simulated_tpm20_test.go @@ -26,7 +26,6 @@ import ( "github.com/google/go-tpm-tools/simulator" "github.com/google/go-tpm/tpm2" - "github.com/google/go-tpm/tpm2/credactivation" ) func setupSimulatedTPM(t *testing.T) (*simulator.Simulator, *TPM) { @@ -133,21 +132,17 @@ func TestSimTPM20ActivateCredential(t *testing.T) { } ek := chooseEKPub(t, EKs) - att, err := tpm2.DecodeAttestationData(aik.aik.(*key20).createAttestation) - if err != nil { - t.Fatalf("tpm2.DecodeAttestationData() failed: %v", err) + ap := ActivationParameters{ + TPMVersion: TPMVersion20, + AIK: aik.AttestationParameters(), + EK: ek, } - secret := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} - - id, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, ek, 16, secret) + secret, challenge, err := ap.Generate() if err != nil { - t.Fatalf("credactivation.Generate() failed: %v", err) + t.Fatalf("Generate() failed: %v", err) } - decryptedSecret, err := aik.ActivateCredential(tpm, EncryptedCredential{ - Credential: id, - Secret: encSecret, - }) + decryptedSecret, err := aik.ActivateCredential(tpm, *challenge) if err != nil { t.Errorf("aik.ActivateCredential() failed: %v", err) } diff --git a/attest/attest_tpm12_test.go b/attest/attest_tpm12_test.go index a9d396a..d2775dd 100644 --- a/attest/attest_tpm12_test.go +++ b/attest/attest_tpm12_test.go @@ -20,9 +20,6 @@ import ( "flag" "sort" "testing" - - "github.com/google/certificate-transparency-go/x509" - "github.com/google/go-tspi/verification" ) var ( @@ -143,27 +140,10 @@ func TestTPMQuote(t *testing.T) { t.Logf("Quote{version: %v, quote: %x, signature: %x}\n", quote.Version, quote.Quote, quote.Signature) } -// chooseEKCertRaw selects the EK cert which will be activated against. -func chooseEKCertRaw(t *testing.T, eks []PlatformEK) []byte { - t.Helper() - - for _, ek := range eks { - if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA || ek.Cert.PublicKeyAlgorithm == x509.RSAESOAEP { - return ek.Cert.Raw - } - } - - t.Skip("No suitable RSA EK found") - return nil -} - func TestTPMActivateCredential(t *testing.T) { if !*testTPM12 { t.SkipNow() } - var challenge EncryptedCredential - nonce := make([]byte, 20) - rand.Read(nonce) tpm, err := OpenTPM(tpm12config) if err != nil { @@ -180,20 +160,25 @@ func TestTPMActivateCredential(t *testing.T) { if err != nil { t.Fatalf("failed to read EKs: %v", err) } - ekcert := chooseEKCertRaw(t, EKs) + ek := chooseEKPub(t, EKs) - challenge.Credential, challenge.Secret, err = verification.GenerateChallenge(ekcert, aik.aik.(*key12).public, nonce) + ap := ActivationParameters{ + TPMVersion: TPMVersion12, + AIK: aik.AttestationParameters(), + EK: ek, + } + secret, challenge, err := ap.Generate() if err != nil { - t.Fatalf("GenerateChallenge failed: %v", err) + t.Fatalf("Generate() failed: %v", err) } - validation, err := aik.ActivateCredential(tpm, challenge) + validation, err := aik.ActivateCredential(tpm, *challenge) if err != nil { t.Fatalf("ActivateCredential failed: %v", err) } - if !bytes.Equal(validation, nonce) { - t.Errorf("secret mismatch: expected %x, got %x", nonce, validation) + if !bytes.Equal(validation, secret) { + t.Errorf("secret mismatch: expected %x, got %x", secret, validation) } t.Logf("validation: %x", validation) diff --git a/attest/challenge.go b/attest/challenge.go new file mode 100644 index 0000000..171cd51 --- /dev/null +++ b/attest/challenge.go @@ -0,0 +1,140 @@ +package attest + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "encoding/binary" + "fmt" +) + +const ( + ekBlobTag = 0x000c + ekBlobActivateTag = 0x002b + ekTypeActivate = 0x0001 + + algXOR = 0x0000000a + + schemeESNone = 0x0001 +) + +type symKeyHeader struct { + Alg uint32 + Scheme uint16 + KeySize uint16 +} + +type activationBlobHeader struct { + Tag uint16 + KeyHeader symKeyHeader +} + +func makeEmptyPCRInfo() []byte { + var b bytes.Buffer + binary.Write(&b, binary.BigEndian, uint16(3)) // SIZE_OF_SELECT + b.Write([]byte{0x00, 0x00, 0x00}) // empty bitfield for 3 PCRs + b.Write([]byte{0x01}) // TPM_LOCALITY_SELECTION = TPM_LOC_ZERO + b.Write(bytes.Repeat([]byte{0}, sha1.Size)) // TPM_COMPOSITE_HASH + return b.Bytes() +} + +func makeActivationBlob(symKey, aikpub []byte) (blob []byte, err error) { + aikHash := sha1.Sum(aikpub) + + var out bytes.Buffer + if err := binary.Write(&out, binary.BigEndian, activationBlobHeader{ + Tag: ekBlobActivateTag, + KeyHeader: symKeyHeader{ + Alg: algXOR, + Scheme: schemeESNone, + KeySize: uint16(len(symKey)), + }, + }); err != nil { + return nil, err + } + + out.Write(symKey) + out.Write(aikHash[:]) + out.Write(makeEmptyPCRInfo()) + return out.Bytes(), nil +} + +type ekBlobHeader struct { + Tag uint16 + EkType uint16 + BlobLen uint32 +} + +func makeEkBlob(activationBlob []byte) []byte { + var out bytes.Buffer + binary.Write(&out, binary.BigEndian, ekBlobHeader{ + Tag: ekBlobTag, + EkType: ekTypeActivate, + BlobLen: uint32(len(activationBlob)), + }) + out.Write(activationBlob) + + return out.Bytes() +} + +func pad(plaintext []byte, bsize int) []byte { + pad := bsize - (len(plaintext) % bsize) + if pad == 0 { + pad = bsize + } + for i := 0; i < pad; i++ { + plaintext = append(plaintext, byte(pad)) + } + return plaintext +} + +// generateChallenge12 generates a TPM_EK_BLOB challenge for a TPM 1.2 device. +// This process is defined in section 15.1 of the TPM 1.2 commands spec, +// available at: https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-3-Commands_v1.2_rev116_01032011.pdf +// +// asymenc is a TPM_EK_BLOB structure containing a TPM_EK_BLOB_ACTIVATE structure, +// encrypted with the EK of the TPM. The contained credential is the aes key +// for symenc. +// symenc is a structure with TPM_SYM_MODE_CBC leading, then the IV, and then +// the secret encrypted with the session key credential contained in asymenc. +// To use this, pass asymenc as the input to the TPM_ActivateIdentity command. +// Use the returned credential as the aes key to decode the secret in symenc. +func generateChallenge12(pubkey *rsa.PublicKey, aikpub, secret []byte) (asymenc []byte, symenc []byte, err error) { + aeskey := make([]byte, 16) + iv := make([]byte, 16) + if _, err = rand.Read(aeskey); err != nil { + return nil, nil, err + } + if _, err = rand.Read(iv); err != nil { + return nil, nil, err + } + + activationBlob, err := makeActivationBlob(aeskey, aikpub) + if err != nil { + return nil, nil, err + } + label := []byte{'T', 'C', 'P', 'A'} + asymenc, err = rsa.EncryptOAEP(sha1.New(), rand.Reader, pubkey, makeEkBlob(activationBlob), label) + if err != nil { + return nil, nil, fmt.Errorf("EncryptOAEP() failed: %v", err) + } + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, nil, err + } + cbc := cipher.NewCBCEncrypter(block, iv) + secret = pad(secret, len(iv)) + symenc = make([]byte, len(secret)) + cbc.CryptBlocks(symenc, secret) + + var symOut bytes.Buffer + binary.Write(&symOut, binary.BigEndian, uint32(0x02)) // TPM_SYM_MODE_CBC + symOut.Write(iv) + symOut.Write(symenc) + + return asymenc, symOut.Bytes(), nil +} diff --git a/attest/challenge_test.go b/attest/challenge_test.go new file mode 100644 index 0000000..f0277de --- /dev/null +++ b/attest/challenge_test.go @@ -0,0 +1,65 @@ +package attest + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "testing" +) + +func TestMakeActivationBlob(t *testing.T) { + blob, err := makeActivationBlob([]byte{1, 2, 3, 4, 5}, []byte{5, 6, 7, 8}) + if err != nil { + t.Fatal(err) + } + + if got, want := blob[0:2], []byte{0, 0x2b}; !bytes.Equal(got, want) { + t.Errorf("tag = %v, want %v", got, want) + } + if got, want := blob[2:6], []byte{0, 0, 0, 0x0a}; !bytes.Equal(got, want) { + t.Errorf("alg = %v, want %v", got, want) + } + if got, want := blob[6:8], []byte{0, 1}; !bytes.Equal(got, want) { + t.Errorf("scheme = %v, want %v", got, want) + } + if got, want := blob[8:10], []byte{0, 5}; !bytes.Equal(got, want) { + t.Errorf("len = %v, want %v", got, want) + } + if got, want := blob[10:15], []byte{1, 2, 3, 4, 5}; !bytes.Equal(got, want) { + t.Errorf("symKey = %v, want %v", got, want) + } + if got, want := blob[15:35], []byte{133, 217, 101, 29, 154, 57, 154, 103, 224, 21, 208, 71, 253, 158, 106, 148, 30, 107, 32, 187}; !bytes.Equal(got, want) { + t.Errorf("aik digest = %v, want %v", got, want) + } + if got, want := blob[35:37], []byte{0, 3}; !bytes.Equal(got, want) { + t.Errorf("size of select = %v, want %v", got, want) + } + if got, want := blob[37:40], []byte{0, 0, 0}; !bytes.Equal(got, want) { + t.Errorf("select bitfield = %v, want %v", got, want) + } + if got, want := blob[40:41], []byte{1}; !bytes.Equal(got, want) { + t.Errorf("locality = %v, want %v", got, want) + } + if got, want := blob[41:61], bytes.Repeat([]byte{0}, 20); !bytes.Equal(got, want) { + t.Errorf("select digest = %v, want %v", got, want) + } +} + +func TestGenerateChallengeSymHeader(t *testing.T) { + cert, err := x509.ParseCertificate(decodeBase64("MIID2jCCA4CgAwIBAgIKFsBPsR6KEUuzHjAKBggqhkjOPQQDAjBVMVMwHwYDVQQDExhOdXZvdG9uIFRQTSBSb290IENBIDIxMTAwJQYDVQQKEx5OdXZvdG9uIFRlY2hub2xvZ3kgQ29ycG9yYXRpb24wCQYDVQQGEwJUVzAeFw0xNzEwMTgyMzQ5MjBaFw0zNzEwMTQyMzQ5MjBaMAAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsiqa4C+4hxqQgQ93aFmVq+hvbV6FDvNod24lA1s24pVJzUdOW/D0ORY3TdvRKS1xEh+yPD+iao+XrRGELHSOrGxid/kaTiOF8KdR5BWJwCoedQasqMyQsgZNlU6nKoERcex2G6DDozkdUgrJ/A04qG8tkpEfwmS+0SVWEtDoTb4fzdehKmS32gKcY/I3ZnmpE/5+FCJHKUwIPxHPwAGdhWoYEGsb7ZwG3/S4UuPEfHaab/iwj/WxwbnGtysMu9r1ZkHjQx6FblWVLoXCqrTl0q0samTuW52MffybbOEzn9R0pnfiyQlpL8CLKP4/kBPUGkkvZm2MJsF2cwNRJtEXVAgMBAAGjggHAMIIBvDBKBgNVHREBAf8EQDA+pDwwOjE4MBQGBWeBBQIBEwtpZDo0RTU0NDMwMDAQBgVngQUCAhMHTlBDVDZ4eDAOBgVngQUCAxMFaWQ6MTMwDAYDVR0TAQH/BAIwADAQBgNVHSUECTAHBgVngQUIATAfBgNVHSMEGDAWgBSfu3mqD1JieL7RUJKacXHpajW+9zAOBgNVHQ8BAf8EBAMCBSAwcAYDVR0JBGkwZzAWBgVngQUCEDENMAsMAzIuMAIBAAIBdDBNBgVngQUCEjFEMEICAQABAf+gAwoBAaEDCgEAogMKAQCjFTATFgMzLjEKAQQKAQEBAf+gAwoBAqQPMA0WBTE0MC0yCgECAQEApQMBAQAwQQYDVR0gBDowODA2BgRVHSAAMC4wLAYIKwYBBQUHAgEWIGh0dHA6Ly93d3cubnV2b3Rvbi5jb20vc2VjdXJpdHkvMGgGCCsGAQUFBwEBBFwwWjBYBggrBgEFBQcwAoZMaHR0cDovL3d3dy5udXZvdG9uLmNvbS9zZWN1cml0eS9OVEMtVFBNLUVLLUNlcnQvTnV2b3RvbiBUUE0gUm9vdCBDQSAyMTEwLmNlcjAKBggqhkjOPQQDAgNIADBFAiEAtct+vD/l1Vv9TJOl6oRSI+IZk+k31YIqcscDZEGpZI0CIFFAsVKlFnQnXKTxo7sx9dGOio92Bschl0TQLQhVv0K7", t)) + if err != nil { + t.Fatal(err) + } + + _, sym, err := generateChallenge12(cert.PublicKey.(*rsa.PublicKey), []byte("pubkey yo"), []byte("secretz")) + if err != nil { + t.Fatal(err) + } + + if got, want := len(sym), 36; got != want { + t.Errorf("len(sym) = %v, want %v", got, want) + } + if got, want := sym[0:4], []byte{0, 0, 0, 2}; !bytes.Equal(got, want) { + t.Errorf("symmetric mode = %v, want %v", got, want) + } +} diff --git a/attest/key_linux.go b/attest/key_linux.go index 46b8a69..8714b78 100644 --- a/attest/key_linux.go +++ b/attest/key_linux.go @@ -90,10 +90,11 @@ func (k *key12) Public() crypto.PublicKey { return k.publicKey } -// Parameters returns information about the AIK. -func (k *key12) Parameters() AIKParameters { - return AIKParameters{ - Public: k.public, +// AttestationParameters returns information about the AIK. +func (k *key12) AttestationParameters() AttestationParameters { + return AttestationParameters{ + Public: k.public, + UseTCSDActivationFormat: true, } } @@ -187,9 +188,9 @@ func (k *key20) Quote(t *TPM, nonce []byte, alg HashAlg) (*Quote, error) { return quote20(t.rwc, k.hnd, tpm2.Algorithm(alg), nonce) } -// Parameters returns information about the AIK. -func (k *key20) Parameters() AIKParameters { - return AIKParameters{ +// AttestationParameters returns information about the AIK. +func (k *key20) AttestationParameters() AttestationParameters { + return AttestationParameters{ Public: k.public, CreateData: k.createData, CreateAttestation: k.createAttestation, diff --git a/attest/key_windows.go b/attest/key_windows.go index c27f582..7e97009 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -111,9 +111,9 @@ func (k *key12) Close(tpm *TPM) error { return closeNCryptObject(k.hnd) } -// Parameters returns information about the AIK. -func (k *key12) Parameters() AIKParameters { - return AIKParameters{ +// AttestationParameters returns information about the AIK. +func (k *key12) AttestationParameters() AttestationParameters { + return AttestationParameters{ Public: k.public, } } @@ -202,9 +202,9 @@ func (k *key20) Delete(tpm *TPM) error { return tpm.pcp.DeleteKey(k.hnd) } -// Parameters returns information about the AIK. -func (k *key20) Parameters() AIKParameters { - return AIKParameters{ +// AttestationParameters returns information about the AIK. +func (k *key20) AttestationParameters() AttestationParameters { + return AttestationParameters{ Public: k.public, CreateData: k.createData, CreateAttestation: k.createAttestation, diff --git a/attest/tpm_linux.go b/attest/tpm_linux.go index 3b423ca..8f1d96b 100644 --- a/attest/tpm_linux.go +++ b/attest/tpm_linux.go @@ -286,7 +286,7 @@ func (t *TPM) MintAIK(opts *MintOptions) (*AIK, error) { return nil, fmt.Errorf("failed to get SRK handle: %v", err) } - _, blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", aikTemplate) + blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", aikTemplate) if err != nil { return nil, fmt.Errorf("CreateKeyEx() failed: %v", err) } diff --git a/go.mod b/go.mod index fc95dc3..235ca8a 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.12 require ( github.com/golang/protobuf v1.3.1 github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8 - github.com/google/go-tpm v0.1.2-0.20190430183152-dcb1ada1f875 + github.com/google/go-tpm v0.1.2-0.20190720204220-b46f7071bbfd github.com/google/go-tpm-tools v0.0.0-20190328013357-5d2fd7f4b3e5 - github.com/google/go-tspi v0.2.0 - golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd // indirect - golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 + github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect + golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 ) diff --git a/go.sum b/go.sum index 91b0e2a..b563c73 100644 --- a/go.sum +++ b/go.sum @@ -2,25 +2,22 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/certificate-transparency-go v1.0.22-0.20190403155334-84853901c6b8 h1:pZtGL2P6rU7wOnemTcvTgoH9s+QB646LB5dBcZ1w5yE= -github.com/google/certificate-transparency-go v1.0.22-0.20190403155334-84853901c6b8/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8 h1:G3Wse9lGL7PmAl2jqdr0HgwhPkGA5KHu7guIPREa7DU= github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/go-tpm v0.1.2-0.20190430183152-dcb1ada1f875 h1:4+5g5+b2aKnnAXX1XiDcbkU/+daEu8T1HOGbRciBu08= -github.com/google/go-tpm v0.1.2-0.20190430183152-dcb1ada1f875/go.mod h1:70+xJCEPKoR1UFyG62ftF/qOTka+OVFVQpNcWmByY0g= +github.com/google/go-tpm v0.1.2-0.20190720204220-b46f7071bbfd h1:vdJl7SmJKhMKpc7XTDMjYCq/hvZT2u1YzABQD1VOqeA= +github.com/google/go-tpm v0.1.2-0.20190720204220-b46f7071bbfd/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm-tools v0.0.0-20190328013357-5d2fd7f4b3e5 h1:/moKuMi+BJ+OEva3jTms88ruyRkxaZn+f9EIZoGpQeY= github.com/google/go-tpm-tools v0.0.0-20190328013357-5d2fd7f4b3e5/go.mod h1:ApmLTU8fd5JJJ4J67y9sV16nOTR00GW2OabMwk7kSnE= -github.com/google/go-tspi v0.2.0 h1:PMrHThARFgHtsCF6B8YNjLlnnGMDdFjVHZnxaqkcbzQ= -github.com/google/go-tspi v0.2.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad h1:LnpS22S8V1HqbxjveESGAazHhi6BX9SwI2Rij7qZcXQ= +github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9HgerkXPyIeCSO6k0zUMGfFk= -golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=