From 93ba1654ea7660e4ad46cff49816b6a96abe9178 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 24 Jan 2024 13:45:20 +0100 Subject: [PATCH] Fix tests to work with Wire `UserID` and `DeviceID` --- acme/api/order.go | 6 +- acme/api/order_test.go | 157 +++++++++++++++++++++++++----- acme/api/wire_integration_test.go | 6 +- acme/challenge.go | 8 +- acme/challenge_wire_test.go | 8 +- acme/order.go | 6 +- acme/wire/id.go | 17 +++- acme/wire/id_test.go | 34 ++++++- authority/provisioner/acme.go | 13 ++- 9 files changed, 200 insertions(+), 55 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index b7a1114d2..beda4e5c4 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -50,12 +50,12 @@ func (n *NewOrderRequest) Validate() error { return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty") } case acme.WireUser: - _, err := wire.ParseID([]byte(id.Value)) + _, err := wire.ParseUserID([]byte(id.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") } case acme.WireDevice: - wireID, err := wire.ParseID([]byte(id.Value)) + wireID, err := wire.ParseDeviceID([]byte(id.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") } @@ -297,7 +297,7 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'") } case acme.WireDevice: - wireID, err := wire.ParseID([]byte(az.Identifier.Value)) + wireID, err := wire.ParseDeviceID([]byte(az.Identifier.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser") } diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 4948ea947..da95bc23d 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -101,7 +101,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: "{}"}, + {Type: "wireapp-device", Value: "{}"}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "": invalid Wire client ID URI "": error parsing : scheme is missing`), @@ -111,7 +111,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "nowireapp://example.com": invalid Wire client ID scheme "nowireapp"; expected "wireapp"`), @@ -121,7 +121,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "wireapp://user-device@example.com": invalid Wire client ID username "user-device"`), @@ -205,13 +205,28 @@ func TestNewOrderRequest_Validate(t *testing.T) { naf: naf, } }, - "ok/wireapp-idd": func(t *testing.T) test { + "ok/wireapp-user": func(t *testing.T) test { nbf := time.Now().UTC().Add(time.Minute) naf := time.Now().UTC().Add(5 * time.Minute) return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, + "ok/wireapp-device": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, NotAfter: naf, NotBefore: nbf, @@ -1719,7 +1734,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "ok/default-naf-nbf-wireapp": func(t *testing.T) test { + "ok/default-naf-nbf-wireapp-user": func(t *testing.T) test { acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{ Wire: &wire.Options{ OIDC: &wire.OIDCOptions{ @@ -1749,7 +1764,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= acc := &acme.Account{ID: "accID"} nor := &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"client-id": "wireapp://user!client@domain"}`}, + {Type: "wireapp-user", Value: `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, } b, err := json.Marshal(nor) @@ -1758,9 +1773,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ctx = context.WithValue(ctx, accContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( - ch1, ch2 **acme.Challenge - az1ID *string - count = 0 + ch1 **acme.Challenge + az1ID *string ) return test{ ctx: ctx, @@ -1769,20 +1783,113 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ca: &mockCA{}, db: &acme.MockDB{ MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { - switch count { - case 0: - ch.ID = "wireapp-oidc" - assert.Equals(t, ch.Type, acme.WIREOIDC01) - ch1 = &ch - case 1: - ch.ID = "wireapp-dpop" - assert.Equals(t, ch.Type, acme.WIREDPOP01) - ch2 = &ch - default: - assert.FatalError(t, errors.New("test logic error")) - return errors.New("force") - } - count++ + ch.ID = "wireapp-oidc" + assert.Equals(t, ch.Type, acme.WIREOIDC01) + ch1 = &ch + assert.Equals(t, ch.AccountID, "accID") + assert.NotEquals(t, ch.Token, "") + assert.Equals(t, ch.Status, acme.StatusPending) + assert.Equals(t, ch.Value, `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`) + return nil + }, + MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error { + az.ID = "az1ID" + az1ID = &az.ID + assert.Equals(t, az.AccountID, "accID") + assert.NotEquals(t, az.Token, "") + assert.Equals(t, az.Status, acme.StatusPending) + assert.Equals(t, az.Identifier, nor.Identifiers[0]) + assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1}) + assert.Equals(t, az.Wildcard, false) + return nil + }, + MockCreateOrder: func(ctx context.Context, o *acme.Order) error { + o.ID = "ordID" + assert.Equals(t, o.AccountID, "accID") + assert.Equals(t, o.ProvisionerID, prov.GetID()) + assert.Equals(t, o.Status, acme.StatusPending) + assert.Equals(t, o.Identifiers, nor.Identifiers) + assert.Equals(t, o.AuthorizationIDs, []string{*az1ID}) + return nil + }, + MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { + assert.Equals(t, prov.GetID(), provisionerID) + assert.Equals(t, "accID", accountID) + return nil, nil + }, + }, + vr: func(t *testing.T, o *acme.Order) { + now := clock.Now() + testBufferDur := 5 * time.Second + orderExpiry := now.Add(defaultOrderExpiry) + expNbf := now.Add(-defaultOrderBackdate) + expNaf := now.Add(prov.DefaultTLSCertDuration()) + + assert.Equals(t, o.ID, "ordID") + assert.Equals(t, o.Status, acme.StatusPending) + assert.Equals(t, o.Identifiers, nor.Identifiers) + assert.Equals(t, o.AuthorizationURLs, []string{fmt.Sprintf("%s/acme/%s/authz/az1ID", baseURL.String(), escProvName)}) + assert.True(t, o.NotBefore.Add(-testBufferDur).Before(expNbf)) + assert.True(t, o.NotBefore.Add(testBufferDur).After(expNbf)) + assert.True(t, o.NotAfter.Add(-testBufferDur).Before(expNaf)) + assert.True(t, o.NotAfter.Add(testBufferDur).After(expNaf)) + assert.True(t, o.ExpiresAt.Add(-testBufferDur).Before(orderExpiry)) + assert.True(t, o.ExpiresAt.Add(testBufferDur).After(orderExpiry)) + }, + } + }, + "ok/default-naf-nbf-wireapp-device": func(t *testing.T) test { + acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wire.Options{ + OIDC: &wire.OIDCOptions{ + Provider: &wire.Provider{ + IssuerURL: "https://issuer.example.com", + AuthURL: "", + TokenURL: "", + JWKSURL: "", + UserInfoURL: "", + Algorithms: []string{"ES256"}, + }, + Config: &wire.Config{ + ClientID: "integration test", + SignatureAlgorithms: []string{"ES256"}, + SkipClientIDCheck: true, + SkipExpiryCheck: true, + SkipIssuerCheck: true, + InsecureSkipSignatureCheck: true, + Now: time.Now, + }, + }, + DPOP: &wire.DPOPOptions{ + SigningKey: []byte(fakeWireSigningKey), + }, + }, + }) + acc := &acme.Account{ID: "accID"} + nor := &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "wireapp-device", Value: `{"client-id": "wireapp://user!client@domain"}`}, + }, + } + b, err := json.Marshal(nor) + assert.FatalError(t, err) + ctx := acme.NewProvisionerContext(context.Background(), acmeWireProv) + ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) + var ( + ch1 **acme.Challenge + az1ID *string + ) + return test{ + ctx: ctx, + statusCode: 201, + nor: nor, + ca: &mockCA{}, + db: &acme.MockDB{ + MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { + ch.ID = "wireapp-dpop" + assert.Equals(t, ch.Type, acme.WIREDPOP01) + ch1 = &ch assert.Equals(t, ch.AccountID, "accID") assert.NotEquals(t, ch.Token, "") assert.Equals(t, ch.Status, acme.StatusPending) @@ -1796,7 +1903,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.NotEquals(t, az.Token, "") assert.Equals(t, az.Status, acme.StatusPending) assert.Equals(t, az.Identifier, nor.Identifiers[0]) - assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1, *ch2}) + assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1}) assert.Equals(t, az.Wildcard, false) return nil }, diff --git a/acme/api/wire_integration_test.go b/acme/api/wire_integration_test.go index 3bb62a9ae..c59af0693 100644 --- a/acme/api/wire_integration_test.go +++ b/acme/api/wire_integration_test.go @@ -234,7 +234,11 @@ func TestWireIntegration(t *testing.T) { nor := &NewOrderRequest{ Identifiers: []acme.Identifier{ { - Type: "wireapp-id", + Type: "wireapp-user", + Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`, + }, + { + Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`, }, }, diff --git a/acme/challenge.go b/acme/challenge.go index 3a53ed3af..0a80f3ed0 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -373,7 +373,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload") } - wireID, err := wire.ParseID([]byte(ch.Value)) + wireID, err := wire.ParseUserID([]byte(ch.Value)) if err != nil { return WrapErrorISE(err, "error unmarshalling challenge data") } @@ -451,7 +451,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return nil } -func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.ID) (map[string]any, error) { +func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.UserID) (map[string]any, error) { var m map[string]any if err := token.Claims(&m); err != nil { return nil, fmt.Errorf("failed extracting OIDC ID token claims: %w", err) @@ -500,7 +500,7 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload") } - wireID, err := wire.ParseID([]byte(ch.Value)) + wireID, err := wire.ParseDeviceID([]byte(ch.Value)) if err != nil { return WrapErrorISE(err, "error unmarshalling challenge data") } @@ -598,7 +598,7 @@ type wireVerifyParams struct { dpopKeyID string issuer string audience string - wireID wire.ID + wireID wire.DeviceID chToken string t time.Time } diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 5a471a0fc..b7881fa9c 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -100,7 +100,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Type: "urn:ietf:params:acme:error:serverInternal", Detail: "The server experienced an internal error", Status: 500, - Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`), + Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.DeviceID`), }, } }, @@ -1096,7 +1096,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Type: "urn:ietf:params:acme:error:serverInternal", Detail: "The server experienced an internal error", Status: 500, - Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`), + Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.UserID`), }, } }, @@ -2030,7 +2030,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= require.True(t, ok) issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token" - wireID := wire.ID{ + wireID := wire.DeviceID{ ClientID: "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com", Handle: "wireapp://%40alice_wire@wire.com", } @@ -2127,7 +2127,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= idToken, err := verifier.Verify(ctx, idTokenString) require.NoError(t, err) - wireID := wire.ID{ + wireID := wire.UserID{ Name: "Alice Smith", Handle: "wireapp://%40alice_wire@wire.com", } diff --git a/acme/order.go b/acme/order.go index 291cb57d0..1e53eafec 100644 --- a/acme/order.go +++ b/acme/order.go @@ -340,7 +340,7 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util for _, identifier := range o.Identifiers { switch identifier.Type { case WireUser: - wireID, err := wire.ParseID([]byte(identifier.Value)) + wireID, err := wire.ParseUserID([]byte(identifier.Value)) if err != nil { return subject, NewErrorISE("unmarshal wireID: %s", err) } @@ -406,7 +406,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ orderPIDs[indexPID] = n.Value indexPID++ case WireUser: - wireID, err := wire.ParseID([]byte(n.Value)) + wireID, err := wire.ParseUserID([]byte(n.Value)) if err != nil { return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) } @@ -417,7 +417,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ tmpOrderURIs[indexURI] = handle indexURI++ case WireDevice: - wireID, err := wire.ParseID([]byte(n.Value)) + wireID, err := wire.ParseDeviceID([]byte(n.Value)) if err != nil { return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) } diff --git a/acme/wire/id.go b/acme/wire/id.go index 5e5bd03b9..3662cc701 100644 --- a/acme/wire/id.go +++ b/acme/wire/id.go @@ -8,15 +8,26 @@ import ( "go.step.sm/crypto/kms/uri" ) -type ID struct { +type UserID struct { + Name string `json:"name,omitempty"` + Domain string `json:"domain,omitempty"` + Handle string `json:"handle,omitempty"` +} + +type DeviceID struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` ClientID string `json:"client-id,omitempty"` Handle string `json:"handle,omitempty"` } -func ParseID(data []byte) (wireID ID, err error) { - err = json.Unmarshal(data, &wireID) +func ParseUserID(data []byte) (id UserID, err error) { + err = json.Unmarshal(data, &id) + return +} + +func ParseDeviceID(data []byte) (id DeviceID, err error) { + err = json.Unmarshal(data, &id) return } diff --git a/acme/wire/id_test.go b/acme/wire/id_test.go index 36c81d23d..4c008e462 100644 --- a/acme/wire/id_test.go +++ b/acme/wire/id_test.go @@ -7,19 +7,43 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParseID(t *testing.T) { - ok := `{"name": "Alice Smith", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` +func TestParseUserID(t *testing.T) { + ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` tests := []struct { name string data []byte - wantWireID ID + wantWireID UserID expectedErr error }{ - {name: "ok", data: []byte(ok), wantWireID: ID{Name: "Alice Smith", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, + {name: "ok", data: []byte(ok), wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotWireID, err := ParseID(tt.data) + gotWireID, err := ParseUserID(tt.data) + if tt.expectedErr != nil { + assert.EqualError(t, err, tt.expectedErr.Error()) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantWireID, gotWireID) + }) + } +} + +func TestParseDeviceID(t *testing.T) { + ok := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` + tests := []struct { + name string + data []byte + wantWireID DeviceID + expectedErr error + }{ + {name: "ok", data: []byte(ok), wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotWireID, err := ParseDeviceID(tt.data) if tt.expectedErr != nil { assert.EqualError(t, err, tt.expectedErr.Error()) return diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 52b245300..198803387 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -11,8 +11,6 @@ import ( "github.com/pkg/errors" "go.step.sm/linkedca" - - "github.com/smallstep/certificates/acme/wire" ) // ACMEChallenge represents the supported acme challenges. @@ -252,11 +250,12 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti case DNS: err = x509Policy.IsDNSAllowed(identifier.Value) case WireID: - var wireID wire.ID - if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil { - return fmt.Errorf("failed parsing Wire SANs: %w", err) - } - err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle}) + // TODO: parse the value as user or device ID + // var wireID wire.ID + // if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil { + // return fmt.Errorf("failed parsing Wire SANs: %w", err) + // } + // err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle}) default: err = fmt.Errorf("invalid ACME identifier type '%s' provided", identifier.Type) }