identity/oidc: adds tests for validation of loopback IP redirect URIs (#13939)
* identity/oidc: adds tests for validation of loopback IP redirect URIs * Update vault/identity_store_oidc_provider_test.go Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com> Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>
This commit is contained in:
parent
e811821ac7
commit
4c12c2bb42
|
@ -2,14 +2,11 @@ package vault
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
@ -2159,39 +2156,6 @@ func (i *IdentityStore) populateScopeTemplates(ctx context.Context, s logical.St
|
|||
return populatedTemplates, false, nil
|
||||
}
|
||||
|
||||
// computeHashClaim computes the hash value to be used for the at_hash
|
||||
// and c_hash claims. For details on how this value is computed and the
|
||||
// class of attacks it's used to prevent, see the spec at
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#TokenSubstitution
|
||||
func computeHashClaim(alg string, input string) (string, error) {
|
||||
signatureAlgToHash := map[jose.SignatureAlgorithm]func() hash.Hash{
|
||||
jose.RS256: sha256.New,
|
||||
jose.RS384: sha512.New384,
|
||||
jose.RS512: sha512.New,
|
||||
jose.ES256: sha256.New,
|
||||
jose.ES384: sha512.New384,
|
||||
jose.ES512: sha512.New,
|
||||
|
||||
// We use the Ed25519 curve key for EdDSA, which uses
|
||||
// SHA-512 for its digest algorithm. See details at
|
||||
// https://bitbucket.org/openid/connect/issues/1125.
|
||||
jose.EdDSA: sha512.New,
|
||||
}
|
||||
|
||||
newHash, ok := signatureAlgToHash[jose.SignatureAlgorithm(alg)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported signature algorithm: %q", alg)
|
||||
}
|
||||
h := newHash()
|
||||
|
||||
// Writing to the hash will never return an error
|
||||
_, _ = h.Write([]byte(input))
|
||||
sum := h.Sum(nil)
|
||||
return base64.RawURLEncoding.EncodeToString(sum[:len(sum)/2]), nil
|
||||
}
|
||||
|
||||
// entityHasAssignment returns true if the entity is enabled and a member of any
|
||||
// of the assignments' groups or entities. Otherwise, returns false or an error.
|
||||
func (i *IdentityStore) entityHasAssignment(ctx context.Context, s logical.Storage, entity *identity.Entity, assignments []string) (bool, error) {
|
||||
|
|
|
@ -805,6 +805,78 @@ func TestOIDC_Path_OIDC_Authorize(t *testing.T) {
|
|||
authorizeReq: testAuthorizeReq(s, clientID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authorize request with port-agnostic loopback redirect_uri 127.0.0.1",
|
||||
args: args{
|
||||
entityID: entityID,
|
||||
clientReq: func() *logical.Request {
|
||||
req := testClientReq(s)
|
||||
req.Data["redirect_uris"] = []string{"http://127.0.0.1/callback"}
|
||||
return req
|
||||
}(),
|
||||
providerReq: testProviderReq(s, clientID),
|
||||
assignmentReq: testAssignmentReq(s, entityID, groupID),
|
||||
authorizeReq: func() *logical.Request {
|
||||
req := testAuthorizeReq(s, clientID)
|
||||
req.Data["redirect_uri"] = "http://127.0.0.1:51004/callback"
|
||||
return req
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authorize request with port-agnostic loopback redirect_uri 127.0.0.1 with port",
|
||||
args: args{
|
||||
entityID: entityID,
|
||||
clientReq: func() *logical.Request {
|
||||
req := testClientReq(s)
|
||||
req.Data["redirect_uris"] = []string{"http://127.0.0.1:8251/callback"}
|
||||
return req
|
||||
}(),
|
||||
providerReq: testProviderReq(s, clientID),
|
||||
assignmentReq: testAssignmentReq(s, entityID, groupID),
|
||||
authorizeReq: func() *logical.Request {
|
||||
req := testAuthorizeReq(s, clientID)
|
||||
req.Data["redirect_uri"] = "http://127.0.0.1:51005/callback"
|
||||
return req
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authorize request with port-agnostic loopback redirect_uri localhost",
|
||||
args: args{
|
||||
entityID: entityID,
|
||||
clientReq: func() *logical.Request {
|
||||
req := testClientReq(s)
|
||||
req.Data["redirect_uris"] = []string{"http://localhost:8251/callback"}
|
||||
return req
|
||||
}(),
|
||||
providerReq: testProviderReq(s, clientID),
|
||||
assignmentReq: testAssignmentReq(s, entityID, groupID),
|
||||
authorizeReq: func() *logical.Request {
|
||||
req := testAuthorizeReq(s, clientID)
|
||||
req.Data["redirect_uri"] = "http://localhost:51006/callback"
|
||||
return req
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authorize request with port-agnostic loopback redirect_uri [::1]",
|
||||
args: args{
|
||||
entityID: entityID,
|
||||
clientReq: func() *logical.Request {
|
||||
req := testClientReq(s)
|
||||
req.Data["redirect_uris"] = []string{"http://[::1]:8251/callback"}
|
||||
return req
|
||||
}(),
|
||||
providerReq: testProviderReq(s, clientID),
|
||||
assignmentReq: testAssignmentReq(s, entityID, groupID),
|
||||
authorizeReq: func() *logical.Request {
|
||||
req := testAuthorizeReq(s, clientID)
|
||||
req.Data["redirect_uri"] = "http://[::1]:51007/callback"
|
||||
return req
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// validRedirect checks whether uri is in allowed using special handling for loopback uris.
|
||||
|
@ -36,3 +42,36 @@ func validRedirect(uri string, allowed []string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// computeHashClaim computes the hash value to be used for the at_hash
|
||||
// and c_hash claims. For details on how this value is computed and the
|
||||
// class of attacks it's used to prevent, see the spec at
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||
// - https://openid.net/specs/openid-connect-core-1_0.html#TokenSubstitution
|
||||
func computeHashClaim(alg string, input string) (string, error) {
|
||||
signatureAlgToHash := map[jose.SignatureAlgorithm]func() hash.Hash{
|
||||
jose.RS256: sha256.New,
|
||||
jose.RS384: sha512.New384,
|
||||
jose.RS512: sha512.New,
|
||||
jose.ES256: sha256.New,
|
||||
jose.ES384: sha512.New384,
|
||||
jose.ES512: sha512.New,
|
||||
|
||||
// We use the Ed25519 curve key for EdDSA, which uses
|
||||
// SHA-512 for its digest algorithm. See details at
|
||||
// https://bitbucket.org/openid/connect/issues/1125.
|
||||
jose.EdDSA: sha512.New,
|
||||
}
|
||||
|
||||
newHash, ok := signatureAlgToHash[jose.SignatureAlgorithm(alg)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported signature algorithm: %q", alg)
|
||||
}
|
||||
h := newHash()
|
||||
|
||||
// Writing to the hash will never return an error
|
||||
_, _ = h.Write([]byte(input))
|
||||
sum := h.Sum(nil)
|
||||
return base64.RawURLEncoding.EncodeToString(sum[:len(sum)/2]), nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue