Skip to content

Commit

Permalink
consent: Introduce pairwise support
Browse files Browse the repository at this point in the history
This patch introduces the OpenID Connect pairwise Subject Identifier Algorithm.

Closes #950

Signed-off-by: arekkas <aeneas@ory.am>
  • Loading branch information
arekkas authored and arekkas committed Aug 10, 2018
1 parent 78e6552 commit 479acd7
Show file tree
Hide file tree
Showing 65 changed files with 1,105 additions and 706 deletions.
6 changes: 3 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Expand Up @@ -75,7 +75,7 @@

[[constraint]]
name = "github.com/ory/fosite"
version = "0.21.1"
version = "0.21.2"

[[constraint]]
name = "github.com/ory/graceful"
Expand Down
1 change: 1 addition & 0 deletions client/sdk_test.go
Expand Up @@ -53,6 +53,7 @@ func createTestClient(prefix string) hydra.OAuth2Client {
ClientSecretExpiresAt: 0,
TokenEndpointAuthMethod: "client_secret_basic",
UserinfoSignedResponseAlg: "none",
SubjectType: "public",
//SectorIdentifierUri: "https://sector.com/foo",
}
}
Expand Down
2 changes: 1 addition & 1 deletion client/validator.go
Expand Up @@ -111,7 +111,7 @@ func (v *Validator) Validate(c *Client) error {
return errors.WithStack(fosite.ErrInvalidRequest.WithHint(fmt.Sprintf("Subject type %s is not supported by server, only %v are allowed.", c.SubjectType, v.SubjectTypes)))
}
} else {
if !stringslice.Has(v.SubjectTypes, "public") {
if stringslice.Has(v.SubjectTypes, "public") {
c.SubjectType = "public"
} else {
c.SubjectType = v.SubjectTypes[0]
Expand Down
18 changes: 16 additions & 2 deletions client/validator_test.go
Expand Up @@ -34,12 +34,13 @@ import (
func TestValidate(t *testing.T) {
v := &Validator{
DefaultClientScopes: []string{"openid"},
SubjectTypes: []string{"public", "pairwise"},
SubjectTypes: []string{"pairwise", "public"},
}
for k, tc := range []struct {
in *Client
check func(t *testing.T, c *Client)
expectErr bool
v *Validator
}{
{
in: new(Client),
Expand Down Expand Up @@ -79,6 +80,16 @@ func TestValidate(t *testing.T) {
assert.Equal(t, "public", c.SubjectType)
},
},
{
v: &Validator{
DefaultClientScopes: []string{"openid"},
SubjectTypes: []string{"pairwise"},
},
in: &Client{ClientID: "foo"},
check: func(t *testing.T, c *Client) {
assert.Equal(t, "pairwise", c.SubjectType)
},
},
{
in: &Client{ClientID: "foo", SubjectType: "pairwise"},
check: func(t *testing.T, c *Client) {
Expand All @@ -91,7 +102,10 @@ func TestValidate(t *testing.T) {
},
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
err := v.Validate(tc.in)
if tc.v == nil {
tc.v = v
}
err := tc.v.Validate(tc.in)
if tc.expectErr {
require.Error(t, err)
} else {
Expand Down
2 changes: 2 additions & 0 deletions cmd/cli/handler_client.go
Expand Up @@ -100,6 +100,7 @@ func (h *ClientHandler) CreateClient(cmd *cobra.Command, args []string) {
policyUri, _ := cmd.Flags().GetString("policy-uri")
logoUri, _ := cmd.Flags().GetString("logo-uri")
clientUri, _ := cmd.Flags().GetString("client-uri")
subjectType, _ := cmd.Flags().GetString("subject-type")

if secret == "" {
var secretb []byte
Expand All @@ -124,6 +125,7 @@ func (h *ClientHandler) CreateClient(cmd *cobra.Command, args []string) {
PolicyUri: policyUri,
LogoUri: logoUri,
ClientUri: clientUri,
SubjectType: subjectType,
}

result, response, err := m.CreateOAuth2Client(cc)
Expand Down
1 change: 1 addition & 0 deletions cmd/clients_create.go
Expand Up @@ -53,6 +53,7 @@ func init() {
clientsCreateCmd.Flags().String("tos-uri", "", "A URL string that points to a human-readable terms of service document for the client that describes a contractual relationship between the end-user and the client that the end-user accepts when authorizing the client")
clientsCreateCmd.Flags().String("client-uri", "", "A URL string of a web page providing information about the client")
clientsCreateCmd.Flags().String("logo-uri", "", "A URL string that references a logo for the client")
clientsCreateCmd.Flags().String("subject-type", "public", "A URL string that references a logo for the client")
clientsCreateCmd.Flags().String("secret", "", "Provide the client's secret")
clientsCreateCmd.Flags().StringP("name", "n", "", "The client's name")
}
3 changes: 3 additions & 0 deletions cmd/root.go
Expand Up @@ -191,6 +191,9 @@ func initConfig() {
viper.BindEnv("OIDC_SUBJECT_TYPES_SUPPORTED")
viper.SetDefault("OIDC_SUBJECT_TYPES_SUPPORTED", "public")

viper.BindEnv("OIDC_SUBJECT_TYPE_PAIRWISE_SALT")
viper.SetDefault("OIDC_SUBJECT_TYPE_PAIRWISE_SALT", "public")

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Printf(`Config file not found because "%s"`, err)
Expand Down
14 changes: 12 additions & 2 deletions cmd/serve.go
Expand Up @@ -140,10 +140,20 @@ OPENID CONNECT CONTROLS
"scope" key in the registration payload, effectively disabling the concept of whitelisted scopes.
Example: OIDC_DYNAMIC_CLIENT_REGISTRATION_DEFAULT_SCOPE=openid,offline,scope-a,scope-b
- OIDC_SUBJECT_TYPES_SUPPORTED: Sets which pairwise identifier algorithms (comma-separated) should be supported.
Can be "public" or "pairwise" or both. Defaults to "public".
- OIDC_SUBJECT_TYPES_SUPPORTED: Sets which identifier algorithms (comma-separated) should be supported.
Can be "public" or "pairwise" or both. Defaults to "public". Please note that "pairwise" does not work with the
JWT OAuth 2.0 Access Token Strategy.
Example: OIDC_SUBJECT_TYPES_SUPPORTED=public,pairwise
- OIDC_SUBJECT_TYPE_PAIRWISE_SALT: Is the salt of the pairwise identifier algorithm and must be set if pairwise is enabled.
The length must be longer than 8 characters.
!! Warning !!
This value should not be changed once set in production. Changing it will cause all client applications
to receive new user IDs from ORY Hydra which will lead to serious complications with authentication on their side!
Example: OIDC_SUBJECT_TYPE_PAIRWISE_SALT=5be780ef690045aebf50845d56acd72c
HTTPS CONTROLS
==============
Expand Down
3 changes: 3 additions & 0 deletions cmd/server/handler.go
Expand Up @@ -62,6 +62,7 @@ func enhanceRouter(c *config.Config, cmd *cobra.Command, serverHandler *Handler,

func RunServeAdmin(c *config.Config) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
c.MustValidate()
checkDatabaseAllowed(c)
serverHandler, _, backend, mws := setup(c, cmd, args, "admin")

Expand All @@ -78,6 +79,7 @@ func RunServeAdmin(c *config.Config) func(cmd *cobra.Command, args []string) {

func RunServePublic(c *config.Config) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
c.MustValidate()
checkDatabaseAllowed(c)
serverHandler, frontend, _, mws := setup(c, cmd, args, "public")

Expand All @@ -94,6 +96,7 @@ func RunServePublic(c *config.Config) func(cmd *cobra.Command, args []string) {

func RunServeAll(c *config.Config) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
c.MustValidate()
serverHandler, frontend, backend, mws := setup(c, cmd, args, "all")

var wg sync.WaitGroup
Expand Down
10 changes: 10 additions & 0 deletions cmd/server/handler_oauth2_factory.go
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/ory/fosite/compose"
foauth2 "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/ory/go-convenience/stringslice"
"github.com/ory/herodot"
"github.com/ory/hydra/client"
"github.com/ory/hydra/config"
Expand Down Expand Up @@ -170,6 +171,14 @@ func newOAuth2Handler(c *config.Config, frontend, backend *httprouter.Router, cm
}
}

sias := map[string]consent.SubjectIdentifierAlgorithm{}
if stringslice.Has(c.GetSubjectTypesSupported(), "pairwise") {
sias["pairwise"] = consent.NewSubjectIdentifierAlgorithmPairwise([]byte(c.SubjectIdentifierAlgorithmSalt))
}
if stringslice.Has(c.GetSubjectTypesSupported(), "pairwise") {
sias["public"] = consent.NewSubjectIdentifierAlgorithmPublic()
}

handler := &oauth2.Handler{
ScopesSupported: c.OpenIDDiscoveryScopesSupported,
UserinfoEndpoint: c.OpenIDDiscoveryUserinfoEndpoint,
Expand All @@ -184,6 +193,7 @@ func newOAuth2Handler(c *config.Config, frontend, backend *httprouter.Router, cm
!c.ForceHTTP, time.Minute*15,
oidcStrategy,
openid.NewOpenIDConnectRequestValidator(nil, oidcStrategy),
sias,
),
Storage: c.Context().FositeStore,
ErrorURL: *errorURL,
Expand Down
14 changes: 13 additions & 1 deletion config/config.go
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/ory/fosite"
foauth2 "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/token/hmac"
"github.com/ory/go-convenience/stringslice"
"github.com/ory/go-convenience/urlx"
"github.com/ory/hydra/health"
"github.com/ory/hydra/metrics/prometheus"
Expand Down Expand Up @@ -63,7 +64,6 @@ type Config struct {
ConsentURL string `mapstructure:"OAUTH2_CONSENT_URL" yaml:"-"`
LoginURL string `mapstructure:"OAUTH2_LOGIN_URL" yaml:"-"`
DefaultClientScope string `mapstructure:"OIDC_DYNAMIC_CLIENT_REGISTRATION_DEFAULT_SCOPE" yaml:"-"`
SubjectTypesSupported string `mapstructure:"OIDC_SUBJECT_TYPES_SUPPORTED" yaml:"-"`
ErrorURL string `mapstructure:"OAUTH2_ERROR_URL" yaml:"-"`
AllowTLSTermination string `mapstructure:"HTTPS_ALLOW_TERMINATION_FROM" yaml:"-"`
BCryptWorkFactor int `mapstructure:"BCRYPT_COST" yaml:"-"`
Expand All @@ -76,6 +76,8 @@ type Config struct {
LogLevel string `mapstructure:"LOG_LEVEL" yaml:"-"`
LogFormat string `mapstructure:"LOG_FORMAT" yaml:"-"`
AccessControlResourcePrefix string `mapstructure:"RESOURCE_NAME_PREFIX" yaml:"-"`
SubjectTypesSupported string `mapstructure:"OIDC_SUBJECT_TYPES_SUPPORTED" yaml:"-"`
SubjectIdentifierAlgorithmSalt string `mapstructure:"OIDC_SUBJECT_TYPE_PAIRWISE_SALT" yaml:"-"`
OpenIDDiscoveryClaimsSupported string `mapstructure:"OIDC_DISCOVERY_CLAIMS_SUPPORTED" yaml:"-"`
OpenIDDiscoveryScopesSupported string `mapstructure:"OIDC_DISCOVERY_SCOPES_SUPPORTED" yaml:"-"`
OpenIDDiscoveryUserinfoEndpoint string `mapstructure:"OIDC_DISCOVERY_USERINFO_ENDPOINT" yaml:"-"`
Expand All @@ -94,6 +96,16 @@ type Config struct {
systemSecret []byte `yaml:"-"`
}

func (c *Config) MustValidate() {
if stringslice.Has(c.GetSubjectTypesSupported(), "pairwise") && c.OAuth2AccessTokenStrategy == "jwt" {
c.logger.Fatalf(`The pairwise subject identifier algorithm is not supported by the JWT OAuth 2.0 Access Token Strategy. Please remove "pairwise" from OIDC_SUBJECT_TYPES_SUPPORTED or set OAUTH2_ACCESS_TOKEN_STRATEGY to "opaque"`)
}

if stringslice.Has(c.GetSubjectTypesSupported(), "pairwise") && len(c.SubjectIdentifierAlgorithmSalt) < 8 {
c.logger.Fatalf(`The pairwise subject identifier algorithm was set but length of OIDC_SUBJECT_TYPE_PAIRWISE_SALT is too small (%d < 8), please set OIDC_SUBJECT_TYPE_PAIRWISE_SALT to a random string with 8 characters or more`, len(c.SubjectIdentifierAlgorithmSalt))
}
}

func (c *Config) GetSubjectTypesSupported() []string {
types := strings.Split(c.SubjectTypesSupported, ",")
if len(types) == 0 {
Expand Down
9 changes: 9 additions & 0 deletions consent/manager.go
Expand Up @@ -20,6 +20,12 @@

package consent

type ForcedObfuscatedAuthenticationSession struct {
ClientID string `db:"client_id"`
Subject string `db:"subject"`
SubjectObfuscated string `db:"subject_obfuscated"`
}

type Manager interface {
CreateConsentRequest(*ConsentRequest) error
GetConsentRequest(challenge string) (*ConsentRequest, error)
Expand All @@ -41,4 +47,7 @@ type Manager interface {
GetAuthenticationRequest(challenge string) (*AuthenticationRequest, error)
HandleAuthenticationRequest(challenge string, r *HandledAuthenticationRequest) (*AuthenticationRequest, error)
VerifyAndInvalidateAuthenticationRequest(verifier string) (*HandledAuthenticationRequest, error)

CreateForcedObfuscatedAuthenticationSession(*ForcedObfuscatedAuthenticationSession) error
GetForcedObfuscatedAuthenticationSession(client, obfuscated string) (*ForcedObfuscatedAuthenticationSession, error)
}
24 changes: 24 additions & 0 deletions consent/manager_memory.go
Expand Up @@ -36,6 +36,7 @@ type MemoryManager struct {
authRequests map[string]AuthenticationRequest
handledAuthRequests map[string]HandledAuthenticationRequest
authSessions map[string]AuthenticationSession
pairwise []ForcedObfuscatedAuthenticationSession
m map[string]*sync.RWMutex
store pkg.FositeStorer
}
Expand All @@ -47,6 +48,7 @@ func NewMemoryManager(store pkg.FositeStorer) *MemoryManager {
authRequests: map[string]AuthenticationRequest{},
handledAuthRequests: map[string]HandledAuthenticationRequest{},
authSessions: map[string]AuthenticationSession{},
pairwise: []ForcedObfuscatedAuthenticationSession{},
store: store,
m: map[string]*sync.RWMutex{
"consentRequests": new(sync.RWMutex),
Expand All @@ -58,6 +60,28 @@ func NewMemoryManager(store pkg.FositeStorer) *MemoryManager {
}
}

func (m *MemoryManager) CreateForcedObfuscatedAuthenticationSession(s *ForcedObfuscatedAuthenticationSession) error {
for k, v := range m.pairwise {
if v.Subject == s.Subject && v.ClientID == s.ClientID {
m.pairwise[k] = *s
return nil
}
}

m.pairwise = append(m.pairwise, *s)
return nil
}

func (m *MemoryManager) GetForcedObfuscatedAuthenticationSession(client, obfuscated string) (*ForcedObfuscatedAuthenticationSession, error) {
for _, v := range m.pairwise {
if v.SubjectObfuscated == obfuscated && v.ClientID == client {
return &v, nil
}
}

return nil, errors.WithStack(pkg.ErrNotFound)
}

func (m *MemoryManager) RevokeUserConsentSession(user string) error {
return m.RevokeUserClientConsentSession(user, "")
}
Expand Down

0 comments on commit 479acd7

Please sign in to comment.