Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
943253fe
Commit
943253fe
authored
Apr 07, 2017
by
Eric Chiang
Committed by
GitHub
Apr 07, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #898 from ericchiang/saml-cleanup
connector/saml: clean up SAML verification logic and comments
parents
258ec4ff
362e0798
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
193 additions
and
267 deletions
+193
-267
saml.go
connector/saml/saml.go
+144
-116
saml_test.go
connector/saml/saml_test.go
+47
-150
types.go
connector/saml/types.go
+2
-1
No files found.
connector/saml/saml.go
View file @
943253fe
...
@@ -2,10 +2,8 @@
...
@@ -2,10 +2,8 @@
package
saml
package
saml
import
(
import
(
"crypto/rand"
"crypto/x509"
"crypto/x509"
"encoding/base64"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"encoding/pem"
"encoding/xml"
"encoding/xml"
"errors"
"errors"
...
@@ -270,12 +268,22 @@ func (p *provider) POSTData(s connector.Scopes, id string) (action, value string
...
@@ -270,12 +268,22 @@ func (p *provider) POSTData(s connector.Scopes, id string) (action, value string
return
p
.
ssoURL
,
base64
.
StdEncoding
.
EncodeToString
(
data
),
nil
return
p
.
ssoURL
,
base64
.
StdEncoding
.
EncodeToString
(
data
),
nil
}
}
// HandlePOST interprets a request from a SAML provider attempting to verify a
// user's identity.
//
// The steps taken are:
//
// * Verify signature on XML document (or verify sig on assertion elements).
// * Verify various parts of the Assertion element. Conditions, audience, etc.
// * Map the Assertion's attribute elements to user info.
//
func
(
p
*
provider
)
HandlePOST
(
s
connector
.
Scopes
,
samlResponse
,
inResponseTo
string
)
(
ident
connector
.
Identity
,
err
error
)
{
func
(
p
*
provider
)
HandlePOST
(
s
connector
.
Scopes
,
samlResponse
,
inResponseTo
string
)
(
ident
connector
.
Identity
,
err
error
)
{
rawResp
,
err
:=
base64
.
StdEncoding
.
DecodeString
(
samlResponse
)
rawResp
,
err
:=
base64
.
StdEncoding
.
DecodeString
(
samlResponse
)
if
err
!=
nil
{
if
err
!=
nil
{
return
ident
,
fmt
.
Errorf
(
"decode response: %v"
,
err
)
return
ident
,
fmt
.
Errorf
(
"decode response: %v"
,
err
)
}
}
// Root element is allowed to not be signed if the Assertion element is.
rootElementSigned
:=
true
rootElementSigned
:=
true
if
p
.
validator
!=
nil
{
if
p
.
validator
!=
nil
{
rawResp
,
rootElementSigned
,
err
=
verifyResponseSig
(
p
.
validator
,
rawResp
)
rawResp
,
rootElementSigned
,
err
=
verifyResponseSig
(
p
.
validator
,
rawResp
)
...
@@ -289,6 +297,8 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
...
@@ -289,6 +297,8 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
return
ident
,
fmt
.
Errorf
(
"unmarshal response: %v"
,
err
)
return
ident
,
fmt
.
Errorf
(
"unmarshal response: %v"
,
err
)
}
}
// If the root element isn't signed, there's no reason to inspect these
// elements. They're not verified.
if
rootElementSigned
{
if
rootElementSigned
{
if
p
.
ssoIssuer
!=
""
&&
resp
.
Issuer
!=
nil
&&
resp
.
Issuer
.
Issuer
!=
p
.
ssoIssuer
{
if
p
.
ssoIssuer
!=
""
&&
resp
.
Issuer
!=
nil
&&
resp
.
Issuer
.
Issuer
!=
p
.
ssoIssuer
{
return
ident
,
fmt
.
Errorf
(
"expected Issuer value %s, got %s"
,
p
.
ssoIssuer
,
resp
.
Issuer
.
Issuer
)
return
ident
,
fmt
.
Errorf
(
"expected Issuer value %s, got %s"
,
p
.
ssoIssuer
,
resp
.
Issuer
.
Issuer
)
...
@@ -303,10 +313,14 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
...
@@ -303,10 +313,14 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
// Destination is optional.
// Destination is optional.
if
resp
.
Destination
!=
""
&&
resp
.
Destination
!=
p
.
redirectURI
{
if
resp
.
Destination
!=
""
&&
resp
.
Destination
!=
p
.
redirectURI
{
return
ident
,
fmt
.
Errorf
(
"expected destination %q got %q"
,
p
.
redirectURI
,
resp
.
Destination
)
return
ident
,
fmt
.
Errorf
(
"expected destination %q got %q"
,
p
.
redirectURI
,
resp
.
Destination
)
}
// Status is a required element.
if
resp
.
Status
==
nil
{
return
ident
,
fmt
.
Errorf
(
"Response did not contain a Status element"
)
}
}
if
err
=
p
.
validateStatus
(
&
resp
);
err
!=
nil
{
if
err
=
p
.
validateStatus
(
resp
.
Status
);
err
!=
nil
{
return
ident
,
err
return
ident
,
err
}
}
}
}
...
@@ -315,16 +329,25 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
...
@@ -315,16 +329,25 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
if
assertion
==
nil
{
if
assertion
==
nil
{
return
ident
,
fmt
.
Errorf
(
"response did not contain an assertion"
)
return
ident
,
fmt
.
Errorf
(
"response did not contain an assertion"
)
}
}
// Subject is usually optional, but we need it for the user ID, so complain
// if it's not present.
subject
:=
assertion
.
Subject
subject
:=
assertion
.
Subject
if
subject
==
nil
{
if
subject
==
nil
{
return
ident
,
fmt
.
Errorf
(
"response did not contain a subject"
)
return
ident
,
fmt
.
Errorf
(
"response did not contain a subject"
)
}
}
if
err
=
p
.
validateConditions
(
assertion
);
err
!=
nil
{
// Validate that the response is to the request we originally sent.
if
err
=
p
.
validateSubject
(
subject
,
inResponseTo
);
err
!=
nil
{
return
ident
,
err
return
ident
,
err
}
}
if
err
=
p
.
validateSubjectConfirmation
(
subject
);
err
!=
nil
{
return
ident
,
err
// Conditions element is optional, but must be validated if present.
if
assertion
.
Conditions
!=
nil
{
// Validate that dex is the intended audience of this response.
if
err
=
p
.
validateConditions
(
assertion
.
Conditions
);
err
!=
nil
{
return
ident
,
err
}
}
}
switch
{
switch
{
...
@@ -336,53 +359,57 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
...
@@ -336,53 +359,57 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
return
ident
,
fmt
.
Errorf
(
"subject does not contain an NameID element"
)
return
ident
,
fmt
.
Errorf
(
"subject does not contain an NameID element"
)
}
}
// After verifying the assertion, map data in the attribute statements to
// various user info.
attributes
:=
assertion
.
AttributeStatement
attributes
:=
assertion
.
AttributeStatement
if
attributes
==
nil
{
if
attributes
==
nil
{
return
ident
,
fmt
.
Errorf
(
"response did not contain a AttributeStatement"
)
return
ident
,
fmt
.
Errorf
(
"response did not contain a AttributeStatement"
)
}
}
// Grab the email.
if
ident
.
Email
,
_
=
attributes
.
get
(
p
.
emailAttr
);
ident
.
Email
==
""
{
if
ident
.
Email
,
_
=
attributes
.
get
(
p
.
emailAttr
);
ident
.
Email
==
""
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
emailAttr
,
attributes
.
names
())
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
emailAttr
,
attributes
.
names
())
}
}
// TODO(ericchiang): Does SAML have an email_verified equivalent?
ident
.
EmailVerified
=
true
ident
.
EmailVerified
=
true
// Grab the username.
if
ident
.
Username
,
_
=
attributes
.
get
(
p
.
usernameAttr
);
ident
.
Username
==
""
{
if
ident
.
Username
,
_
=
attributes
.
get
(
p
.
usernameAttr
);
ident
.
Username
==
""
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
usernameAttr
,
attributes
.
names
())
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
usernameAttr
,
attributes
.
names
())
}
}
if
s
.
Groups
&&
p
.
groupsAttr
!=
""
{
if
!
s
.
Groups
||
p
.
groupsAttr
==
""
{
if
p
.
groupsDelim
!=
""
{
// Groups not requested or not configured. We're done.
groupsStr
,
ok
:=
attributes
.
get
(
p
.
groupsAttr
)
return
ident
,
nil
if
!
ok
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
groupsAttr
,
attributes
.
names
())
}
// TODO(ericchiang): Do we need to further trim whitespace?
ident
.
Groups
=
strings
.
Split
(
groupsStr
,
p
.
groupsDelim
)
}
else
{
groups
,
ok
:=
attributes
.
all
(
p
.
groupsAttr
)
if
!
ok
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
groupsAttr
,
attributes
.
names
())
}
ident
.
Groups
=
groups
}
}
}
// Grab the groups.
if
p
.
groupsDelim
!=
""
{
groupsStr
,
ok
:=
attributes
.
get
(
p
.
groupsAttr
)
if
!
ok
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
groupsAttr
,
attributes
.
names
())
}
// TODO(ericchiang): Do we need to further trim whitespace?
ident
.
Groups
=
strings
.
Split
(
groupsStr
,
p
.
groupsDelim
)
}
else
{
groups
,
ok
:=
attributes
.
all
(
p
.
groupsAttr
)
if
!
ok
{
return
ident
,
fmt
.
Errorf
(
"no attribute with name %q: %s"
,
p
.
groupsAttr
,
attributes
.
names
())
}
ident
.
Groups
=
groups
}
return
ident
,
nil
return
ident
,
nil
}
}
// Validate that the StatusCode of the Response is success.
// validateStatus verifies that the response has a good status code or
// Otherwise return a human readable message to the end user
// formats a human readble error based on the bad status.
func
(
p
*
provider
)
validateStatus
(
resp
*
response
)
error
{
func
(
p
*
provider
)
validateStatus
(
status
*
status
)
error
{
// Status is mandatory in the Response type
status
:=
resp
.
Status
if
status
==
nil
{
return
fmt
.
Errorf
(
"response did not contain a Status"
)
}
// StatusCode is mandatory in the Status type
// StatusCode is mandatory in the Status type
statusCode
:=
status
.
StatusCode
statusCode
:=
status
.
StatusCode
if
statusCode
==
nil
{
if
statusCode
==
nil
{
return
fmt
.
Errorf
(
"response did not contain a StatusCode"
)
return
fmt
.
Errorf
(
"response did not contain a StatusCode"
)
}
}
if
statusCode
.
Value
!=
statusCodeSuccess
{
if
statusCode
.
Value
!=
statusCodeSuccess
{
parts
:=
strings
.
Split
(
statusCode
.
Value
,
":"
)
parts
:=
strings
.
Split
(
statusCode
.
Value
,
":"
)
lastPart
:=
parts
[
len
(
parts
)
-
1
]
lastPart
:=
parts
[
len
(
parts
)
-
1
]
...
@@ -396,96 +423,107 @@ func (p *provider) validateStatus(resp *response) error {
...
@@ -396,96 +423,107 @@ func (p *provider) validateStatus(resp *response) error {
return
nil
return
nil
}
}
//
Multiple subject SubjectConfirmation can be in the assertion
//
validateSubject ensures the response is to the request we expect.
//
and at least one SubjectConfirmation must be valid.
//
// This is described in the spec "Profiles for the OASIS Security
// This is described in the spec "Profiles for the OASIS Security
// Assertion Markup Language" in section 3.3 Bearer.
// Assertion Markup Language" in section 3.3 Bearer.
// see https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf
// see https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf
func
(
p
*
provider
)
validateSubjectConfirmation
(
subject
*
subject
)
error
{
//
validSubjectConfirmation
:=
false
// Some of these fields are optional, but we're going to be strict here since
subjectConfirmations
:=
subject
.
SubjectConfirmations
// we have no other way of guarenteeing that this is actually the response to
if
subjectConfirmations
!=
nil
&&
len
(
subjectConfirmations
)
>
0
{
// the request we expect.
for
_
,
subjectConfirmation
:=
range
subjectConfirmations
{
func
(
p
*
provider
)
validateSubject
(
subject
*
subject
,
inResponseTo
string
)
error
{
// skip if method is wrong
// Optional according to the spec, but again, we're going to be strict here.
method
:=
subjectConfirmation
.
Method
if
len
(
subject
.
SubjectConfirmations
)
==
0
{
if
method
!=
""
&&
method
!=
subjectConfirmationMethodBearer
{
return
fmt
.
Errorf
(
"Subject contained no SubjectConfrimations"
)
continue
}
var
errs
[]
error
// One of these must match our assumptions, not all.
for
_
,
c
:=
range
subject
.
SubjectConfirmations
{
err
:=
func
()
error
{
if
c
.
Method
!=
subjectConfirmationMethodBearer
{
return
fmt
.
Errorf
(
"unexpected subject confirmation method: %v"
,
c
.
Method
)
}
}
subjectConfirmationData
:=
subjectConfirmation
.
SubjectConfirmationData
if
subjectConfirmationData
==
nil
{
data
:=
c
.
SubjectConfirmationData
continue
if
data
==
nil
{
return
fmt
.
Errorf
(
"SubjectConfirmation contained no SubjectConfirmationData"
)
}
}
inResponseTo
:=
subjectConfirmationData
.
InResponseTo
if
data
.
InResponseTo
!=
inResponseTo
{
if
inResponseTo
!=
""
{
return
fmt
.
Errorf
(
"expected SubjectConfirmationData InResponseTo value %q, got %q"
,
inResponseTo
,
data
.
InResponseTo
)
// TODO also validate InResponseTo if present
}
}
// only validate that subjectConfirmationData is not expired
notBefore
:=
time
.
Time
(
data
.
NotBefore
)
notOnOrAfter
:=
time
.
Time
(
data
.
NotOnOrAfter
)
now
:=
p
.
now
()
now
:=
p
.
now
()
notOnOrAfter
:=
time
.
Time
(
subjectConfirmationData
.
NotOnOrAfter
)
if
!
notBefore
.
IsZero
()
&&
before
(
now
,
notBefore
)
{
if
!
notOnOrAfter
.
IsZero
()
{
return
fmt
.
Errorf
(
"at %s got response that cannot be processed before %s"
,
now
,
notBefore
)
if
now
.
After
(
notOnOrAfter
)
{
continue
}
}
}
// validate recipient if present
if
!
notOnOrAfter
.
IsZero
()
&&
after
(
now
,
notOnOrAfter
)
{
recipient
:=
subjectConfirmationData
.
Recipient
return
fmt
.
Errorf
(
"at %s got response that cannot be processed because it expired at %s"
,
now
,
notOnOrAfter
)
if
recipient
!=
""
&&
recipient
!=
p
.
redirectURI
{
continue
}
}
validSubjectConfirmation
=
true
if
r
:=
data
.
Recipient
;
r
!=
""
&&
r
!=
p
.
redirectURI
{
return
fmt
.
Errorf
(
"expected Recipient %q got %q"
,
p
.
redirectURI
,
r
)
}
return
nil
}()
if
err
==
nil
{
// Subject is valid.
return
nil
}
}
errs
=
append
(
errs
,
err
)
}
}
if
!
validSubjectConfirmation
{
return
fmt
.
Errorf
(
"no valid SubjectConfirmation was found on this Response"
)
if
len
(
errs
)
==
1
{
return
fmt
.
Errorf
(
"failed to validate subject confirmation: %v"
,
errs
[
0
])
}
}
return
nil
return
fmt
.
Errorf
(
"failed to validate subject confirmation: %v"
,
errs
)
}
}
// Validates the Conditions element and all of it's content
// validationConditions ensures that dex is the intended audience
// for the request, and not another service provider.
//
//
// See: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
// See: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
// "2.3.3 Element <Assertion>"
// "2.3.3 Element <Assertion>"
func
(
p
*
provider
)
validateConditions
(
assertion
*
assertion
)
error
{
func
(
p
*
provider
)
validateConditions
(
conditions
*
conditions
)
error
{
// Checks if a Conditions element exists
// Ensure the conditions haven't expired.
conditions
:=
assertion
.
Conditions
if
conditions
==
nil
{
return
nil
}
// Validates Assertion timestamps
now
:=
p
.
now
()
now
:=
p
.
now
()
notBefore
:=
time
.
Time
(
conditions
.
NotBefore
)
notBefore
:=
time
.
Time
(
conditions
.
NotBefore
)
if
!
notBefore
.
IsZero
()
{
if
!
notBefore
.
IsZero
()
&&
before
(
now
,
notBefore
)
{
if
now
.
Add
(
allowedClockDrift
)
.
Before
(
notBefore
)
{
return
fmt
.
Errorf
(
"at %s got response that cannot be processed before %s"
,
now
,
notBefore
)
return
fmt
.
Errorf
(
"at %s got response that cannot be processed before %s"
,
now
,
notBefore
)
}
}
}
notOnOrAfter
:=
time
.
Time
(
conditions
.
NotOnOrAfter
)
notOnOrAfter
:=
time
.
Time
(
conditions
.
NotOnOrAfter
)
if
!
notOnOrAfter
.
IsZero
()
{
if
!
notOnOrAfter
.
IsZero
()
&&
after
(
now
,
notOnOrAfter
)
{
if
now
.
After
(
notOnOrAfter
.
Add
(
allowedClockDrift
))
{
return
fmt
.
Errorf
(
"at %s got response that cannot be processed because it expired at %s"
,
now
,
notOnOrAfter
)
return
fmt
.
Errorf
(
"at %s got response that cannot be processed because it expired at %s"
,
now
,
notOnOrAfter
)
}
}
}
// Validates audience
audienceValue
:=
p
.
entityIssuer
// Sometimes, dex's issuer string can be different than the redirect URI,
if
audienceValue
==
""
{
// but if dex's issuer isn't explicitly provided assume the redirect URI.
audienceValue
=
p
.
redirectURI
expAud
:=
p
.
entityIssuer
}
if
expAud
==
""
{
audienceRestriction
:=
conditions
.
AudienceRestriction
expAud
=
p
.
redirectURI
if
audienceRestriction
!=
nil
{
}
audiences
:=
audienceRestriction
.
Audiences
if
audiences
!=
nil
&&
len
(
audiences
)
>
0
{
// AudienceRestriction elements indicate the intended audience(s) of an
values
:=
make
([]
string
,
len
(
audiences
))
// assertion. If dex isn't in these audiences, reject the assertion.
issuerInAudiences
:=
false
//
for
i
,
audience
:=
range
audiences
{
// Note that if there are multiple AudienceRestriction elements, each must
if
audience
.
Value
==
audienceValue
{
// individually contain dex in their audience list.
issuerInAudiences
=
true
for
_
,
r
:=
range
conditions
.
AudienceRestriction
{
break
values
:=
make
([]
string
,
len
(
r
.
Audiences
))
}
issuerInAudiences
:=
false
values
[
i
]
=
audience
.
Value
for
i
,
aud
:=
range
r
.
Audiences
{
}
if
aud
.
Value
==
expAud
{
if
!
issuerInAudiences
{
issuerInAudiences
=
true
return
fmt
.
Errorf
(
"required audience %s was not in Response audiences %s"
,
audienceValue
,
values
)
break
}
}
values
[
i
]
=
aud
.
Value
}
if
!
issuerInAudiences
{
return
fmt
.
Errorf
(
"required audience %s was not in Response audiences %s"
,
expAud
,
values
)
}
}
}
}
return
nil
return
nil
...
@@ -544,24 +582,14 @@ func verifyResponseSig(validator *dsig.ValidationContext, data []byte) (signed [
...
@@ -544,24 +582,14 @@ func verifyResponseSig(validator *dsig.ValidationContext, data []byte) (signed [
return
signed
,
false
,
err
return
signed
,
false
,
err
}
}
func
uuidv4
()
string
{
// before determines if a given time is before the current time, with an
u
:=
make
([]
byte
,
16
)
// allowed clock drift.
if
_
,
err
:=
rand
.
Read
(
u
);
err
!=
nil
{
func
before
(
now
,
notBefore
time
.
Time
)
bool
{
panic
(
err
)
return
now
.
Add
(
allowedClockDrift
)
.
Before
(
notBefore
)
}
}
u
[
6
]
=
(
u
[
6
]
|
0x40
)
&
0x4F
u
[
8
]
=
(
u
[
8
]
|
0x80
)
&
0xBF
// after determines if a given time is after the current time, with an
// allowed clock drift.
r
:=
make
([]
byte
,
36
)
func
after
(
now
,
notOnOrAfter
time
.
Time
)
bool
{
r
[
8
]
=
'-'
return
now
.
After
(
notOnOrAfter
.
Add
(
allowedClockDrift
))
r
[
13
]
=
'-'
r
[
18
]
=
'-'
r
[
23
]
=
'-'
hex
.
Encode
(
r
,
u
[
0
:
4
])
hex
.
Encode
(
r
[
9
:
],
u
[
4
:
6
])
hex
.
Encode
(
r
[
14
:
],
u
[
6
:
8
])
hex
.
Encode
(
r
[
19
:
],
u
[
8
:
10
])
hex
.
Encode
(
r
[
24
:
],
u
[
10
:
])
return
string
(
r
)
}
}
connector/saml/saml_test.go
View file @
943253fe
...
@@ -7,7 +7,6 @@ import (
...
@@ -7,7 +7,6 @@ import (
"errors"
"errors"
"io/ioutil"
"io/ioutil"
"sort"
"sort"
"strings"
"testing"
"testing"
"time"
"time"
...
@@ -47,6 +46,7 @@ type responseTest struct {
...
@@ -47,6 +46,7 @@ type responseTest struct {
now
string
now
string
inResponseTo
string
inResponseTo
string
redirectURI
string
redirectURI
string
entityIssuer
string
// Attribute customization.
// Attribute customization.
usernameAttr
string
usernameAttr
string
...
@@ -196,6 +196,51 @@ func TestAssertionSignedNotResponse(t *testing.T) {
...
@@ -196,6 +196,51 @@ func TestAssertionSignedNotResponse(t *testing.T) {
test
.
run
(
t
)
test
.
run
(
t
)
}
}
func
TestInvalidSubjectInResponseTo
(
t
*
testing
.
T
)
{
test
:=
responseTest
{
caFile
:
"testdata/ca.crt"
,
respFile
:
"testdata/assertion-signed.xml"
,
now
:
"2017-04-04T04:34:59.330Z"
,
usernameAttr
:
"Name"
,
emailAttr
:
"email"
,
inResponseTo
:
"invalid-id"
,
// Bad InResponseTo value.
redirectURI
:
"http://127.0.0.1:5556/dex/callback"
,
wantErr
:
true
,
}
test
.
run
(
t
)
}
func
TestInvalidSubjectRecipient
(
t
*
testing
.
T
)
{
test
:=
responseTest
{
caFile
:
"testdata/ca.crt"
,
respFile
:
"testdata/assertion-signed.xml"
,
now
:
"2017-04-04T04:34:59.330Z"
,
usernameAttr
:
"Name"
,
emailAttr
:
"email"
,
inResponseTo
:
"6zmm5mguyebwvajyf2sdwwcw6m"
,
redirectURI
:
"http://bad.com/dex/callback"
,
// Doesn't match Recipient value.
wantErr
:
true
,
}
test
.
run
(
t
)
}
func
TestInvalidAssertionAudience
(
t
*
testing
.
T
)
{
test
:=
responseTest
{
caFile
:
"testdata/ca.crt"
,
respFile
:
"testdata/assertion-signed.xml"
,
now
:
"2017-04-04T04:34:59.330Z"
,
usernameAttr
:
"Name"
,
emailAttr
:
"email"
,
inResponseTo
:
"6zmm5mguyebwvajyf2sdwwcw6m"
,
redirectURI
:
"http://127.0.0.1:5556/dex/callback"
,
// EntityIssuer overrides RedirectURI when determining the expected
// audience. In this case, ensure the audience is invalid.
entityIssuer
:
"http://localhost:5556/dex/callback"
,
wantErr
:
true
,
}
test
.
run
(
t
)
}
// TestTwoAssertionFirstSigned tries to catch an edge case where an attacker
// TestTwoAssertionFirstSigned tries to catch an edge case where an attacker
// provides a second assertion that's not signed.
// provides a second assertion that's not signed.
func
TestTwoAssertionFirstSigned
(
t
*
testing
.
T
)
{
func
TestTwoAssertionFirstSigned
(
t
*
testing
.
T
)
{
...
@@ -236,6 +281,7 @@ func (r responseTest) run(t *testing.T) {
...
@@ -236,6 +281,7 @@ func (r responseTest) run(t *testing.T) {
EmailAttr
:
r
.
emailAttr
,
EmailAttr
:
r
.
emailAttr
,
GroupsAttr
:
r
.
groupsAttr
,
GroupsAttr
:
r
.
groupsAttr
,
RedirectURI
:
r
.
redirectURI
,
RedirectURI
:
r
.
redirectURI
,
EntityIssuer
:
r
.
entityIssuer
,
// Never logging in, don't need this.
// Never logging in, don't need this.
SSOURL
:
"http://foo.bar/"
,
SSOURL
:
"http://foo.bar/"
,
}
}
...
@@ -355,152 +401,3 @@ func TestVerifySignedMessageAndSignedAssertion(t *testing.T) {
...
@@ -355,152 +401,3 @@ func TestVerifySignedMessageAndSignedAssertion(t *testing.T) {
func
TestVerifyUnsignedMessageAndUnsignedAssertion
(
t
*
testing
.
T
)
{
func
TestVerifyUnsignedMessageAndUnsignedAssertion
(
t
*
testing
.
T
)
{
runVerify
(
t
,
"testdata/idp-cert.pem"
,
"testdata/idp-resp.xml"
,
false
)
runVerify
(
t
,
"testdata/idp-cert.pem"
,
"testdata/idp-resp.xml"
,
false
)
}
}
func
TestValidateStatus
(
t
*
testing
.
T
)
{
p
:=
newProvider
(
""
,
""
)
var
err
error
resp
:=
response
{}
// Test missing Status element
err
=
p
.
validateStatus
(
&
resp
)
if
err
==
nil
||
!
strings
.
HasSuffix
(
err
.
Error
(),
`Status`
)
{
t
.
Fatalf
(
"validation should fail with missing Status"
)
}
// Test missing StatusCode element
resp
.
Status
=
&
status
{}
err
=
p
.
validateStatus
(
&
resp
)
if
err
==
nil
||
!
strings
.
HasSuffix
(
err
.
Error
(),
`StatusCode`
)
{
t
.
Fatalf
(
"validation should fail with missing StatusCode"
)
}
// Test failed request without StatusMessage
resp
.
Status
.
StatusCode
=
&
statusCode
{
Value
:
":Requester"
,
}
err
=
p
.
validateStatus
(
&
resp
)
if
err
==
nil
||
!
strings
.
HasSuffix
(
err
.
Error
(),
`"Requester"`
)
{
t
.
Fatalf
(
"validation should fail with code %q"
,
"Requester"
)
}
// Test failed request with StatusMessage
resp
.
Status
.
StatusMessage
=
&
statusMessage
{
Value
:
"Failed"
,
}
err
=
p
.
validateStatus
(
&
resp
)
if
err
==
nil
||
!
strings
.
HasSuffix
(
err
.
Error
(),
`"Requester" -> Failed`
)
{
t
.
Fatalf
(
"validation should fail with code %q and message %q"
,
"Requester"
,
"Failed"
)
}
}
func
TestValidateSubjectConfirmation
(
t
*
testing
.
T
)
{
p
:=
newProvider
(
""
,
""
)
var
err
error
var
notAfter
time
.
Time
subj
:=
&
subject
{}
// Subject without any SubjectConfirmation
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
"Subject without any SubjectConfirmation"
)
}
// SubjectConfirmation without Method and SubjectConfirmationData
subj
.
SubjectConfirmations
=
[]
subjectConfirmation
{
subjectConfirmation
{}}
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
"SubjectConfirmation without Method and SubjectConfirmationData"
)
}
// SubjectConfirmation with invalid Method and no SubjectConfirmationData
subj
.
SubjectConfirmations
=
[]
subjectConfirmation
{
subjectConfirmation
{
Method
:
"invalid"
,
}}
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
"SubjectConfirmation with invalid Method and no SubjectConfirmationData"
)
}
// SubjectConfirmation with valid Method and empty SubjectConfirmationData
subjConfirmationData
:=
subjectConfirmationData
{}
subj
.
SubjectConfirmations
=
[]
subjectConfirmation
{
subjectConfirmation
{
Method
:
"urn:oasis:names:tc:SAML:2.0:cm:bearer"
,
SubjectConfirmationData
:
&
subjConfirmationData
,
}}
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"SubjectConfirmation with valid Method and empty SubjectConfirmationData"
)
}
// SubjectConfirmationData with invalid Recipient
subjConfirmationData
.
Recipient
=
"invalid"
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
"SubjectConfirmationData with invalid Recipient"
)
}
// expired SubjectConfirmationData
notAfter
=
p
.
now
()
.
Add
(
-
time
.
Duration
(
60
)
*
time
.
Second
)
subjConfirmationData
.
NotOnOrAfter
=
xmlTime
(
notAfter
)
subjConfirmationData
.
Recipient
=
defaultRedirectURI
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
" expired SubjectConfirmationData"
)
}
// valid SubjectConfirmationData
notAfter
=
p
.
now
()
.
Add
(
+
time
.
Duration
(
60
)
*
time
.
Second
)
subjConfirmationData
.
NotOnOrAfter
=
xmlTime
(
notAfter
)
subjConfirmationData
.
Recipient
=
defaultRedirectURI
err
=
p
.
validateSubjectConfirmation
(
subj
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succed"
,
"valid SubjectConfirmationData"
)
}
}
func
TestValidateConditions
(
t
*
testing
.
T
)
{
p
:=
newProvider
(
""
,
""
)
var
err
error
var
notAfter
,
notBefore
time
.
Time
cond
:=
conditions
{
AudienceRestriction
:
&
audienceRestriction
{},
}
assert
:=
&
assertion
{}
// Assertion without Conditions
err
=
p
.
validateConditions
(
assert
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"Assertion without Conditions"
)
}
// Assertion with empty Conditions
assert
.
Conditions
=
&
cond
err
=
p
.
validateConditions
(
assert
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"Assertion with empty Conditions"
)
}
// Conditions with valid timestamps
notBefore
=
p
.
now
()
.
Add
(
-
time
.
Duration
(
60
)
*
time
.
Second
)
notAfter
=
p
.
now
()
.
Add
(
+
time
.
Duration
(
60
)
*
time
.
Second
)
cond
.
NotBefore
=
xmlTime
(
notBefore
)
cond
.
NotOnOrAfter
=
xmlTime
(
notAfter
)
err
=
p
.
validateConditions
(
assert
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"Conditions with valid timestamps"
)
}
// Conditions where notBefore is 45 seconds after now
notBefore
=
p
.
now
()
.
Add
(
+
time
.
Duration
(
45
)
*
time
.
Second
)
cond
.
NotBefore
=
xmlTime
(
notBefore
)
err
=
p
.
validateConditions
(
assert
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should fail"
,
"Conditions where notBefore is 45 seconds after now"
)
}
// Conditions where notBefore is 15 seconds after now
notBefore
=
p
.
now
()
.
Add
(
+
time
.
Duration
(
15
)
*
time
.
Second
)
cond
.
NotBefore
=
xmlTime
(
notBefore
)
err
=
p
.
validateConditions
(
assert
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"Conditions where notBefore is 15 seconds after now"
)
}
// Audiences contains the redirectURI
validAudience
:=
audience
{
Value
:
p
.
redirectURI
}
cond
.
AudienceRestriction
.
Audiences
=
[]
audience
{
validAudience
}
err
=
p
.
validateConditions
(
assert
)
if
err
!=
nil
{
t
.
Fatalf
(
"validation of %q should succeed: %v"
,
"Audiences contains the redirectURI"
,
err
)
}
// Audiences is not empty and not contains the issuer
invalidAudience
:=
audience
{
Value
:
"invalid"
}
cond
.
AudienceRestriction
.
Audiences
=
[]
audience
{
invalidAudience
}
err
=
p
.
validateConditions
(
assert
)
if
err
==
nil
{
t
.
Fatalf
(
"validation of %q should succeed"
,
"Audiences is not empty and not contains the issuer"
)
}
}
connector/saml/types.go
View file @
943253fe
...
@@ -86,6 +86,7 @@ type nameID struct {
...
@@ -86,6 +86,7 @@ type nameID struct {
type
subjectConfirmationData
struct
{
type
subjectConfirmationData
struct
{
XMLName
xml
.
Name
`xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData"`
XMLName
xml
.
Name
`xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData"`
NotBefore
xmlTime
`xml:"NotBefore,attr,omitempty"`
NotOnOrAfter
xmlTime
`xml:"NotOnOrAfter,attr,omitempty"`
NotOnOrAfter
xmlTime
`xml:"NotOnOrAfter,attr,omitempty"`
Recipient
string
`xml:"Recipient,attr,omitempty"`
Recipient
string
`xml:"Recipient,attr,omitempty"`
InResponseTo
string
`xml:"InResponseTo,attr,omitempty"`
InResponseTo
string
`xml:"InResponseTo,attr,omitempty"`
...
@@ -115,7 +116,7 @@ type conditions struct {
...
@@ -115,7 +116,7 @@ type conditions struct {
NotBefore
xmlTime
`xml:"NotBefore,attr,omitempty"`
NotBefore
xmlTime
`xml:"NotBefore,attr,omitempty"`
NotOnOrAfter
xmlTime
`xml:"NotOnOrAfter,attr,omitempty"`
NotOnOrAfter
xmlTime
`xml:"NotOnOrAfter,attr,omitempty"`
AudienceRestriction
*
audienceRestriction
`xml:"AudienceRestriction,omitempty"`
AudienceRestriction
[]
audienceRestriction
`xml:"AudienceRestriction,omitempty"`
}
}
type
statusCode
struct
{
type
statusCode
struct
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment