diff --git a/acme/challenge.go b/acme/challenge.go index b4ff1c20..8bb8cf01 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -446,7 +446,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose az.Fingerprint = data.Fingerprint case "tpm": - data, err := doTPMAttestationFormat(ctx, ch, db, jwk, &att) + data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att) if err != nil { // TODO(hs): we should provide more details in the error reported to the client; // "Attestation statement cannot be verified" is VERY generic. Also holds true for the other formats. @@ -511,13 +511,7 @@ type tpmAttestationData struct { Fingerprint string } -func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) { - p := MustProvisionerFromContext(ctx) - prov, ok := p.(*provisioner.ACME) - if !ok { - return nil, NewErrorISE("provisioner in context is not an ACME provisioner") - } - +func doTPMAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) { ver, ok := att.AttStatement["ver"].(string) if !ok { return nil, NewError(ErrorBadAttestationStatementType, "ver not present") @@ -577,7 +571,7 @@ func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose roots, ok := prov.GetAttestationRoots() if !ok { - return nil, NewErrorISE("failed getting tpm attestation root CAs") + return nil, NewErrorISE("failed getting TPM attestation root CAs") } verifiedChains, err := akCert.Verify(x509.VerifyOptions{ @@ -605,24 +599,6 @@ func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose return nil, NewError(ErrorBadAttestationStatementType, "AK certificate is missing Subject Alternative Name extension") } - // TODO(hs): below code fails if there's a URI SAN, for example. Needs more complete parsing of SANS, - // or skip ASN1 tags that can't be parsed. - // san, err := x509ext.ParseSubjectAltName(sanExtension) // TODO(hs): move to a package under our control? - // if err != nil { - // return nil, WrapError(ErrorBadAttestationStatementType, err, "failed parsing Subject Alternative Name extension") - // } - - // var permanentIdentifiers = make([]string, len(san.PermanentIdentifiers)) - // for i, p := range san.PermanentIdentifiers { - // permanentIdentifiers[i] = p.IdentifierValue - // } - - // TODO(hs): reenable this check when we want to enforce a PermanentIdentifier to be present in - // the AK certificate. - // if len(permanentIdentifiers) == 0 { - // return nil, NewError(ErrorBadAttestationStatementType, "AK certificate doesn't contain a PermanentIdentifier") - // } - // extract and validate pubArea, sig, certInfo and alg properties from the request body pubArea, ok := att.AttStatement["pubArea"].([]byte) if !ok { @@ -680,7 +656,7 @@ func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose return nil, WrapError(ErrorBadAttestationStatementType, err, "invalid certification parameters") } - // decode the "certInfo" data + // decode the "certInfo" data. This won't fail, as it's also done as part of Verify(). tpmCertInfo, err := tpm2.DecodeAttestationData(certInfo) if err != nil { return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding attestation data") @@ -698,7 +674,7 @@ func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose return nil, NewError(ErrorBadAttestationStatementType, "key authorization does not match") } - // decode the (attested) public key and determine its fingerprint + // decode the (attested) public key and determine its fingerprint. This won't fail, as it's also done as part of Verify(). pub, err := tpm2.DecodePublic(pubArea) if err != nil { return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding pubArea") @@ -712,7 +688,6 @@ func doTPMAttestationFormat(ctx context.Context, ch *Challenge, db DB, jwk *jose data := &tpmAttestationData{ Certificate: akCert, VerifiedChains: verifiedChains, - //PermanentIdentifiers: permanentIdentifiers, } if data.Fingerprint, err = keyutil.Fingerprint(publicKey); err != nil { diff --git a/acme/challenge_tpmsimulator_test.go b/acme/challenge_tpmsimulator_test.go index 0cc406e6..c41e7b3c 100644 --- a/acme/challenge_tpmsimulator_test.go +++ b/acme/challenge_tpmsimulator_test.go @@ -12,6 +12,7 @@ import ( "encoding/base64" "encoding/json" "encoding/pem" + "errors" "net/url" "testing" @@ -63,6 +64,7 @@ func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte { func mustAttestTPM(t *testing.T, keyAuthorization string) ([]byte, crypto.Signer, *x509.Certificate) { t.Helper() aca, err := minica.New( + minica.WithName("TPM Testing"), minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) @@ -133,7 +135,7 @@ func mustAttestTPM(t *testing.T, keyAuthorization string) ([]byte, crypto.Signer AttStatement: map[string]interface{}{ "ver": "2.0", "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, - "alg": int64(-257), // RSA + "alg": int64(-257), // RS256 "sig": params.CreateSignature, "certInfo": params.CreateAttestation, "pubArea": params.Public, @@ -292,3 +294,448 @@ func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) { }) } } + +func newBadAttestationStatementError(msg string) *Error { + return &Error{ + Type: "urn:ietf:params:acme:error:badAttestationStatement", + Status: 400, + Err: errors.New(msg), + } +} + +func newInternalServerError(msg string) *Error { + return &Error{ + Type: "urn:ietf:params:acme:error:serverInternal", + Status: 500, + Err: errors.New(msg), + } +} + +func Test_doTPMAttestationFormat(t *testing.T) { + ctx := context.Background() + aca, err := minica.New( + minica.WithName("TPM Testing"), + minica.WithGetSignerFunc( + func() (crypto.Signer, error) { + return keyutil.GenerateSigner("RSA", "", 2048) + }, + ), + ) + require.NoError(t, err) + acaRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: aca.Root.Raw}) + + // prepare simulated TPM and create an AK + stpm := newSimulatedTPM(t) + eks, err := stpm.GetEKs(context.Background()) + require.NoError(t, err) + ak, err := stpm.CreateAK(context.Background(), "first-ak") + require.NoError(t, err) + require.NotNil(t, ak) + + // extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public()) + ap, err := ak.AttestationParameters(context.Background()) + require.NoError(t, err) + akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public) + require.NoError(t, err) + + // create template and sign certificate for the AK public key + keyID := generateKeyID(t, eks[0].Public()) + template := &x509.Certificate{ + Subject: pkix.Name{ + CommonName: "testakcert", + }, + PublicKey: akp.Public, + URIs: []*url.URL{ + {Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}, + }, + } + akCert, err := aca.Sign(template) + require.NoError(t, err) + require.NotNil(t, akCert) + + templateMissingSAN := &x509.Certificate{ + Subject: pkix.Name{ + CommonName: "testakcertmissingsan", + }, + PublicKey: akp.Public, + } + akCertMissingSAN, err := aca.Sign(templateMissingSAN) + require.NoError(t, err) + require.NotNil(t, akCert) + + // generate a JWK and the key authorization value + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + require.NoError(t, err) + keyAuthorization, err := KeyAuthorization("token", jwk) + require.NoError(t, err) + + // create a new key attested by the AK, while including + // the key authorization bytes as qualifying data. + keyAuthSum := sha256.Sum256([]byte(keyAuthorization)) + config := tpm.AttestKeyConfig{ + Algorithm: "RSA", + Size: 2048, + QualifyingData: keyAuthSum[:], + } + key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config) + require.NoError(t, err) + require.NotNil(t, key) + params, err := key.CertificationParameters(context.Background()) + require.NoError(t, err) + + signer, err := key.Signer(context.Background()) + require.NoError(t, err) + fingerprint, err := keyutil.Fingerprint(signer.Public()) + require.NoError(t, err) + + // attest another key and get its certification parameters + anotherKey, err := stpm.AttestKey(context.Background(), "first-ak", "another-key", config) + require.NoError(t, err) + require.NotNil(t, key) + anotherKeyParams, err := anotherKey.CertificationParameters(context.Background()) + require.NoError(t, err) + + type args struct { + ctx context.Context + prov Provisioner + ch *Challenge + jwk *jose.JSONWebKey + att *attestationObject + } + tests := []struct { + name string + args args + want *tpmAttestationData + expErr *Error + }{ + {"ok", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, nil}, + {"fail ver not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("ver not present")}, + {"fail ver type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": []interface{}{}, + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("ver not present")}, + {"fail bogus ver", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "bogus", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError(`version "bogus" is not supported`)}, + {"fail x5c not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c not present")}, + {"fail x5c type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": [][]byte{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c not present")}, + {"fail x5c empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is empty")}, + {"fail leaf type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{"leaf", aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is malformed")}, + {"fail leaf parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw[:100], aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")}, + {"fail intermediate type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, "intermediate"}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is malformed")}, + {"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw[:100]}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")}, + {"fail roots", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newInternalServerError("failed getting TPM attestation root CAs")}, + {"fail verify", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("x5c is not valid: x509: certificate signed by unknown authority")}, + {"fail missing SAN extension", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCertMissingSAN.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("AK certificate is missing Subject Alternative Name extension")}, + {"fail pubArea not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + }, + }}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")}, + {"fail pubArea type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": []interface{}{}, + }, + }}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")}, + {"fail pubArea empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": []byte{}, + }, + }}, nil, newBadAttestationStatementError("pubArea is empty")}, + {"fail sig not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid sig in attestation statement")}, + {"fail sig type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": []interface{}{}, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid sig in attestation statement")}, + {"fail sig empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": []byte{}, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("sig is empty")}, + {"fail certInfo not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")}, + {"fail certInfo type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": []interface{}{}, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")}, + {"fail certInfo empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": []byte{}, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("certInfo is empty")}, + {"fail alg not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid alg in attestation statement")}, + {"fail alg type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(0), // invalid alg + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("invalid alg 0 in attestation statement")}, + {"fail attestation verification", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": anotherKeyParams.Public, + }, + }}, nil, newBadAttestationStatementError("invalid certification parameters: certification refers to a different key")}, + {"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RS256 + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newInternalServerError("failed creating key auth digest: error generating JWK thumbprint: square/go-jose: unknown key type '[]uint8'")}, + {"fail different keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "aDifferentToken"}, jwk, &attestationObject{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }}, nil, newBadAttestationStatementError("key authorization does not match")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := doTPMAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att) + if tt.expErr != nil { + var ae *Error + if assert.True(t, errors.As(err, &ae)) { + assert.EqualError(t, err, tt.expErr.Error()) + assert.Equal(t, ae.StatusCode(), tt.expErr.StatusCode()) + assert.Equal(t, ae.Type, tt.expErr.Type) + } + assert.Nil(t, got) + return + } + + assert.NoError(t, err) + if assert.NotNil(t, got) { + assert.Equal(t, akCert, got.Certificate) + assert.Equal(t, [][]*x509.Certificate{ + { + akCert, aca.Intermediate, aca.Root, + }, + }, got.VerifiedChains) + assert.Equal(t, fingerprint, got.Fingerprint) + assert.Empty(t, got.PermanentIdentifiers) // currently expected to be always empty + } + }) + } +}