diff --git a/acme/challenge.go b/acme/challenge.go index 299108842..794fa3ba1 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -383,7 +383,11 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO } oidcOptions := wireOptions.GetOIDCOptions() - verifier := oidcOptions.GetProvider(ctx).Verifier(oidcOptions.GetConfig()) + verifier, err := oidcOptions.GetVerifier(ctx) + if err != nil { + return WrapErrorISE(err, "no OIDC verifier available") + } + idToken, err := verifier.Verify(ctx, oidcPayload.IDToken) if err != nil { return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index b7881fa9c..99f3ff759 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -47,7 +47,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/no-linker": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) return test{ ctx: ctx, expectedErr: &Error{ @@ -59,7 +77,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/unmarshal": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, @@ -82,7 +118,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/wire-parse-id": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, @@ -105,7 +159,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/wire-parse-client-id": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) valueBytes, err := json.Marshal(struct { Name string `json:"name,omitempty"` @@ -139,41 +211,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "fail/no-wire-options": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) - ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) - valueBytes, err := json.Marshal(struct { - Name string `json:"name,omitempty"` - Domain string `json:"domain,omitempty"` - ClientID string `json:"client-id,omitempty"` - Handle string `json:"handle,omitempty"` - }{ - Name: "Alice Smith", - Domain: "wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", - Handle: "wireapp://%40alice_wire@wire.com", - }) - require.NoError(t, err) - return test{ - ctx: ctx, - payload: []byte("{}"), - ch: &Challenge{ - ID: "chID", - AuthorizationID: "azID", - AccountID: "accID", - Token: "token", - Type: "wire-dpop-01", - Status: StatusPending, - Value: string(valueBytes), - }, - expectedErr: &Error{ - Type: "urn:ietf:params:acme:error:serverInternal", - Detail: "The server experienced an internal error", - Status: 500, - Err: errors.New(`failed getting Wire options: no Wire options available`), - }, - } - }, "fail/parse-and-verify": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ Wire: &wireprovisioner.Options{ @@ -1037,7 +1074,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/no-linker": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) return test{ ctx: ctx, expectedErr: &Error{ @@ -1049,7 +1104,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/unmarshal": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, @@ -1078,7 +1151,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } }, "fail/wire-parse-id": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wireprovisioner.Options{ + OIDC: &wireprovisioner.OIDCOptions{ + Provider: &wireprovisioner.Provider{ + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, + }, + Config: &wireprovisioner.Config{ + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, + }, + TransformTemplate: "", + }, + DPOP: &wireprovisioner.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, + }, + })) ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, @@ -1100,41 +1191,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "fail/no-wire-options": func(t *testing.T) test { - ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) - ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) - valueBytes, err := json.Marshal(struct { - Name string `json:"name,omitempty"` - Domain string `json:"domain,omitempty"` - ClientID string `json:"client-id,omitempty"` - Handle string `json:"handle,omitempty"` - }{ - Name: "Alice Smith", - Domain: "wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", - Handle: "wireapp://%40alice_wire@wire.com", - }) - require.NoError(t, err) - return test{ - ctx: ctx, - payload: []byte("{}"), - ch: &Challenge{ - ID: "chID", - AuthorizationID: "azID", - AccountID: "accID", - Token: "token", - Type: "wire-oidc-01", - Status: StatusPending, - Value: string(valueBytes), - }, - expectedErr: &Error{ - Type: "urn:ietf:params:acme:error:serverInternal", - Detail: "The server experienced an internal error", - Status: 500, - Err: errors.New(`failed getting Wire options: no Wire options available`), - }, - } - }, "fail/verify": func(t *testing.T) test { jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token") signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) @@ -2122,8 +2178,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= idTokenString := `eyJhbGciOiJSUzI1NiIsImtpZCI6IjZhNDZlYzQ3YTQzYWI1ZTc4NzU3MzM5NWY1MGY4ZGQ5MWI2OTM5MzcifQ.eyJpc3MiOiJodHRwOi8vZGV4OjE1ODE4L2RleCIsInN1YiI6IkNqcDNhWEpsWVhCd09pOHZTMmh0VjBOTFpFTlRXakoyT1dWTWFHRk9XVlp6WnlFeU5UZzFNVEpoT0RRek5qTXhaV1V6UUhkcGNtVXVZMjl0RWdSc1pHRnciLCJhdWQiOiJ3aXJlYXBwIiwiZXhwIjoxNzA1MDkxNTYyLCJpYXQiOjE3MDUwMDUxNjIsIm5vbmNlIjoib0VjUzBRQUNXLVIyZWkxS09wUmZ2QSIsImF0X2hhc2giOiJoYzk0NmFwS25FeEV5TDVlSzJZMzdRIiwiY19oYXNoIjoidmRubFp2V1d1bVd1Z2NYR1JpOU5FUSIsIm5hbWUiOiJ3aXJlYXBwOi8vJTQwYWxpY2Vfd2lyZUB3aXJlLmNvbSIsInByZWZlcnJlZF91c2VybmFtZSI6IkFsaWNlIFNtaXRoIn0.aEBhWJugBJ9J_0L_4odUCg8SR8HMXVjd__X8uZRo42BSJQQO7-wdpy0jU3S4FOX9fQKr68wD61gS_QsnhfiT7w9U36mLpxaYlNVDCYfpa-gklVFit_0mjUOukXajTLK6H527TGiSss8z22utc40ckS1SbZa2BzKu3yOcqnFHUQwQc5sLYfpRABTB6WBoYFtnWDzdpyWJDaOzz7lfKYv2JBnf9vV8u8SYm-6gNKgtiQ3UUnjhIVUjdfHet2BMvmV2ooZ8V441RULCzKKG_sWZba-D_k_TOnSholGobtUOcKHlmVlmfUe8v7kuyBdhbPcembfgViaNldLQGKZjZfgvLg` ctx := context.Background() o := opts.GetOIDCOptions() - c := o.GetConfig() - verifier := o.GetProvider(ctx).Verifier(c) + verifier, err := o.GetVerifier(ctx) + require.NoError(t, err) idToken, err := verifier.Verify(ctx, idTokenString) require.NoError(t, err) diff --git a/authority/provisioner/wire/oidc_options.go b/authority/provisioner/wire/oidc_options.go index 5bbcbc7a0..22a099438 100644 --- a/authority/provisioner/wire/oidc_options.go +++ b/authority/provisioner/wire/oidc_options.go @@ -40,19 +40,22 @@ type OIDCOptions struct { Config *Config `json:"config,omitempty"` TransformTemplate string `json:"transform,omitempty"` - oidcProviderConfig *oidc.ProviderConfig target *template.Template transform *template.Template + oidcProviderConfig *oidc.ProviderConfig + verifier *oidc.IDTokenVerifier } -func (o *OIDCOptions) GetProvider(ctx context.Context) *oidc.Provider { - if o == nil || o.Provider == nil || o.oidcProviderConfig == nil { - return nil +func (o *OIDCOptions) GetVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) { + if o.verifier == nil { + provider := o.oidcProviderConfig.NewProvider(ctx) // TODO: support the OIDC discovery flow + o.verifier = provider.Verifier(o.getConfig()) } - return o.oidcProviderConfig.NewProvider(ctx) + + return o.verifier, nil } -func (o *OIDCOptions) GetConfig() *oidc.Config { +func (o *OIDCOptions) getConfig() *oidc.Config { if o == nil || o.Config == nil { return &oidc.Config{} } @@ -105,6 +108,9 @@ func parseTransform(transformTemplate string) (*template.Template, error) { } func (o *OIDCOptions) EvaluateTarget(deviceID string) (string, error) { + if deviceID == "" { + return "", errors.New("deviceID must not be empty") + } buf := new(bytes.Buffer) if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil { return "", fmt.Errorf("failed executing OIDC template: %w", err)