Bump github.com/crewjam/saml from 0.4.13 to 0.4.14

Bumps [github.com/crewjam/saml](https://github.com/crewjam/saml) from 0.4.13 to 0.4.14.
- [Commits](https://github.com/crewjam/saml/compare/v0.4.13...v0.4.14)

---
updated-dependencies:
- dependency-name: github.com/crewjam/saml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-10-17 21:44:43 +02:00
committed by Ralf Haferkamp
parent b5ad0796c4
commit b3a92548b7
18 changed files with 321 additions and 179 deletions
+16 -22
View File
@@ -7,41 +7,35 @@
linters:
enable:
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
- errcheck # Inspects source code for security problems [fast: true, auto-fix: false]
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
- gosec # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- deadcode # Finds unused code [fast: true, auto-fix: false]
- revive # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
disable:
# TODO(ross): fix errors reported by these checkers and enable them
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
- errcheck # Inspects source code for security problems [fast: true, auto-fix: false]
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
- interfacer # Linter that suggests narrower interface types [fast: false, auto-fix: false]
- lll # Reports long lines [fast: true, auto-fix: false]
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
- prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
- scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
- revive # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
- structcheck # Finds unused struct fields [fast: true, auto-fix: false]
- stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false]
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
- unparam # Reports unused function parameters [fast: false, auto-fix: false]
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
- varcheck # Finds unused global variables and constants [fast: true, auto-fix: false]
disable:
# TODO(ross): fix errors reported by these checkers and enable them
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- lll # Reports long lines [fast: true, auto-fix: false]
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
linters-settings:
goimports:
local-prefixes: github.com/crewjam/saml
+53 -43
View File
@@ -9,7 +9,6 @@ import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@@ -96,6 +95,7 @@ type AssertionMaker interface {
// and password).
type IdentityProvider struct {
Key crypto.PrivateKey
Signer crypto.Signer
Logger logger.Interface
Certificate *x509.Certificate
Intermediates []*x509.Certificate
@@ -196,10 +196,13 @@ func (idp *IdentityProvider) Handler() http.Handler {
}
// ServeMetadata is an http.HandlerFunc that serves the IDP metadata
func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, r *http.Request) {
func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, _ *http.Request) {
buf, _ := xml.MarshalIndent(idp.Metadata(), "", " ")
w.Header().Set("Content-Type", "application/samlmetadata+xml")
w.Write(buf)
if _, err := w.Write(buf); err != nil {
idp.Logger.Printf("ERROR: %s", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
// ServeSSO handles SAML auth requests.
@@ -362,7 +365,7 @@ func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnReques
if err != nil {
return nil, fmt.Errorf("cannot decode request: %s", err)
}
req.RequestBuffer, err = ioutil.ReadAll(newSaferFlateReader(bytes.NewReader(compressedRequest)))
req.RequestBuffer, err = io.ReadAll(newSaferFlateReader(bytes.NewReader(compressedRequest)))
if err != nil {
return nil, fmt.Errorf("cannot decompress request: %s", err)
}
@@ -716,9 +719,7 @@ func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Sessio
})
}
for _, ca := range session.CustomAttributes {
attributes = append(attributes, ca)
}
attributes = append(attributes, session.CustomAttributes...)
if len(session.Groups) != 0 {
groupMemberAttributeValues := []AttributeValue{}
@@ -830,24 +831,8 @@ const canonicalizerPrefixList = ""
// MakeAssertionEl sets `AssertionEl` to a signed, possibly encrypted, version of `Assertion`.
func (req *IdpAuthnRequest) MakeAssertionEl() error {
keyPair := tls.Certificate{
Certificate: [][]byte{req.IDP.Certificate.Raw},
PrivateKey: req.IDP.Key,
Leaf: req.IDP.Certificate,
}
for _, cert := range req.IDP.Intermediates {
keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
}
keyStore := dsig.TLSCertKeyStore(keyPair)
signatureMethod := req.IDP.SignatureMethod
if signatureMethod == "" {
signatureMethod = dsig.RSASHA1SignatureMethod
}
signingContext := dsig.NewDefaultSigningContext(keyStore)
signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList)
if err := signingContext.SetSignatureMethod(signatureMethod); err != nil {
signingContext, err := req.signingContext()
if err != nil {
return err
}
@@ -1048,24 +1033,8 @@ func (req *IdpAuthnRequest) MakeResponse() error {
// Sign the response element (we've already signed the Assertion element)
{
keyPair := tls.Certificate{
Certificate: [][]byte{req.IDP.Certificate.Raw},
PrivateKey: req.IDP.Key,
Leaf: req.IDP.Certificate,
}
for _, cert := range req.IDP.Intermediates {
keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
}
keyStore := dsig.TLSCertKeyStore(keyPair)
signatureMethod := req.IDP.SignatureMethod
if signatureMethod == "" {
signatureMethod = dsig.RSASHA1SignatureMethod
}
signingContext := dsig.NewDefaultSigningContext(keyStore)
signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList)
if err := signingContext.SetSignatureMethod(signatureMethod); err != nil {
signingContext, err := req.signingContext()
if err != nil {
return err
}
@@ -1083,3 +1052,44 @@ func (req *IdpAuthnRequest) MakeResponse() error {
req.ResponseEl = responseEl
return nil
}
// signingContext will create a signing context for the request.
func (req *IdpAuthnRequest) signingContext() (*dsig.SigningContext, error) {
// Create a cert chain based off of the IDP cert and its intermediates.
certificates := [][]byte{req.IDP.Certificate.Raw}
for _, cert := range req.IDP.Intermediates {
certificates = append(certificates, cert.Raw)
}
var signingContext *dsig.SigningContext
var err error
// If signer is set, use it instead of the private key.
if req.IDP.Signer != nil {
signingContext, err = dsig.NewSigningContext(req.IDP.Signer, certificates)
if err != nil {
return nil, err
}
} else {
keyPair := tls.Certificate{
Certificate: certificates,
PrivateKey: req.IDP.Key,
Leaf: req.IDP.Certificate,
}
keyStore := dsig.TLSCertKeyStore(keyPair)
signingContext = dsig.NewDefaultSigningContext(keyStore)
}
// Default to using SHA1 if the signature method isn't set.
signatureMethod := req.IDP.SignatureMethod
if signatureMethod == "" {
signatureMethod = dsig.RSASHA1SignatureMethod
}
signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList)
if err := signingContext.SetSignatureMethod(signatureMethod); err != nil {
return nil, err
}
return signingContext, nil
}
+1
View File
@@ -1,3 +1,4 @@
// Package logger provides a logging interface.
package logger
import (
+108 -1
View File
@@ -2,6 +2,8 @@ package saml
import (
"encoding/xml"
"fmt"
"net/url"
"time"
"github.com/beevik/etree"
@@ -19,6 +21,9 @@ const HTTPArtifactBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
// SOAPBinding is the official URN for the SOAP binding (transport)
const SOAPBinding = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
// SOAPBindingV1 is the URN for the SOAP binding in SAML 1.0
const SOAPBindingV1 = "urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
// EntitiesDescriptor represents the SAML object of the same name.
//
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.1
@@ -65,7 +70,7 @@ type EntityDescriptor struct {
}
// MarshalXML implements xml.Marshaler
func (m EntityDescriptor) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (m EntityDescriptor) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias EntityDescriptor
aux := &struct {
ValidUntil RelaxedTime `xml:"validUntil,attr,omitempty"`
@@ -188,6 +193,76 @@ type Endpoint struct {
ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
}
func checkEndpointLocation(binding string, location string) (string, error) {
// Within the SAML standard, the complex type EndpointType describes a
// SAML protocol binding endpoint at which a SAML entity can be sent
// protocol messages. In particular, the location of an endpoint type is
// defined as follows in the Metadata for the OASIS Security Assertion
// Markup Language (SAML) V2.0 - 2.2.2 Complex Type EndpointType:
//
// Location [Required] A required URI attribute that specifies the
// location of the endpoint. The allowable syntax of this URI depends
// on the protocol binding.
switch binding {
case HTTPPostBinding,
HTTPRedirectBinding,
HTTPArtifactBinding,
SOAPBinding,
SOAPBindingV1:
locationURL, err := url.Parse(location)
if err != nil {
return "", fmt.Errorf("invalid url %q: %w", location, err)
}
switch locationURL.Scheme {
case "http", "https":
// ok
default:
return "", fmt.Errorf("invalid url scheme %q for binding %q",
locationURL.Scheme, binding)
}
default:
// We don't know what form location should take, but the protocol
// requires that we validate its syntax.
//
// In practice, lots of metadata contains random bindings, for example
// "urn:mace:shibboleth:1.0:profiles:AuthnRequest" from our own test suite.
//
// We can't fail, but we also can't allow a location parameter whose syntax we
// cannot verify. The least-bad course of action here is to set location to
// and empty string, and hope the caller doesn't care need it.
location = ""
}
return location, nil
}
// UnmarshalXML implements xml.Unmarshaler
func (m *Endpoint) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type Alias Endpoint
aux := &struct {
*Alias
}{
Alias: (*Alias)(m),
}
if err := d.DecodeElement(aux, &start); err != nil {
return err
}
var err error
m.Location, err = checkEndpointLocation(m.Binding, m.Location)
if err != nil {
return err
}
if m.ResponseLocation != "" {
m.ResponseLocation, err = checkEndpointLocation(m.Binding, m.ResponseLocation)
if err != nil {
return err
}
}
return nil
}
// IndexedEndpoint represents the SAML IndexedEndpointType object.
//
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.3
@@ -199,6 +274,38 @@ type IndexedEndpoint struct {
IsDefault *bool `xml:"isDefault,attr"`
}
// UnmarshalXML implements xml.Unmarshaler
func (m *IndexedEndpoint) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type Alias IndexedEndpoint
aux := &struct {
*Alias
}{
Alias: (*Alias)(m),
}
if err := d.DecodeElement(aux, &start); err != nil {
return err
}
var err error
m.Location, err = checkEndpointLocation(m.Binding, m.Location)
if err != nil {
return err
}
if m.ResponseLocation != nil {
responseLocation, err := checkEndpointLocation(m.Binding, *m.ResponseLocation)
if err != nil {
return err
}
if responseLocation != "" {
m.ResponseLocation = &responseLocation
} else {
m.ResponseLocation = nil
}
}
return nil
}
// SSODescriptor represents the SAML complex type SSODescriptor
//
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.2
+1 -1
View File
@@ -14,7 +14,7 @@ type ErrorFunction func(w http.ResponseWriter, r *http.Request, err error)
// DefaultOnError is the default ErrorFunction implementation. It prints
// an message via the standard log package and returns a simple text
// "Forbidden" message to the user.
func DefaultOnError(w http.ResponseWriter, r *http.Request, err error) {
func DefaultOnError(w http.ResponseWriter, _ *http.Request, err error) {
if parseErr, ok := err.(*saml.InvalidResponseError); ok {
log.Printf("WARNING: received invalid saml response: %s (now: %s) %s",
parseErr.Response, parseErr.Now, parseErr.PrivateErr)
+9 -3
View File
@@ -5,13 +5,15 @@ import (
"context"
"encoding/xml"
"errors"
"io/ioutil"
"io"
"net/http"
"net/url"
"github.com/crewjam/httperr"
xrv "github.com/mattermost/xml-roundtrip-validator"
"github.com/crewjam/saml/logger"
"github.com/crewjam/saml"
)
@@ -61,12 +63,16 @@ func FetchMetadata(ctx context.Context, httpClient *http.Client, metadataURL url
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
logger.DefaultLogger.Printf("Error while closing response body during fetch metadata: %v", err)
}
}()
if resp.StatusCode >= 400 {
return nil, httperr.Response(*resp)
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
+23 -10
View File
@@ -1,6 +1,7 @@
package samlsp
import (
"bytes"
"encoding/xml"
"net/http"
@@ -65,16 +66,22 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// ServeMetadata handles requests for the SAML metadata endpoint.
func (m *Middleware) ServeMetadata(w http.ResponseWriter, r *http.Request) {
func (m *Middleware) ServeMetadata(w http.ResponseWriter, _ *http.Request) {
buf, _ := xml.MarshalIndent(m.ServiceProvider.Metadata(), "", " ")
w.Header().Set("Content-Type", "application/samlmetadata+xml")
w.Write(buf)
return
if _, err := w.Write(buf); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// ServeACS handles requests for the SAML ACS endpoint.
func (m *Middleware) ServeACS(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
err := r.ParseForm()
if err != nil {
m.OnError(w, r, err)
return
}
possibleRequestIDs := []string{}
if m.ServiceProvider.AllowIDPInitiated {
@@ -93,7 +100,6 @@ func (m *Middleware) ServeACS(w http.ResponseWriter, r *http.Request) {
}
m.CreateSessionFromAssertion(w, r, assertion, m.ServiceProvider.DefaultRedirectURI)
return
}
// RequireAccount is HTTP middleware that requires that each request be
@@ -114,7 +120,6 @@ func (m *Middleware) RequireAccount(handler http.Handler) http.Handler {
}
m.OnError(w, r, err)
return
})
}
@@ -173,9 +178,14 @@ func (m *Middleware) HandleStartAuthFlow(w http.ResponseWriter, r *http.Request)
"script-src 'sha256-AjPdJSbZmeWHnEc5ykvJFay8FTWeTeRbs9dutfZ0HqE='; "+
"reflected-xss block; referrer no-referrer;")
w.Header().Add("Content-type", "text/html")
w.Write([]byte(`<!DOCTYPE html><html><body>`))
w.Write(authReq.Post(relayState))
w.Write([]byte(`</body></html>`))
var buf bytes.Buffer
buf.WriteString(`<!DOCTYPE html><html><body>`)
buf.Write(authReq.Post(relayState))
buf.WriteString(`</body></html>`)
if _, err := w.Write(buf.Bytes()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
panic("not reached")
@@ -195,7 +205,10 @@ func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.R
return
}
} else {
m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex)
if err := m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex); err != nil {
m.OnError(w, r, err)
return
}
redirectURI = trackedRequest.URI
}
+7 -7
View File
@@ -25,7 +25,7 @@ var _ TrackedRequestCodec = JWTTrackedRequestCodec{}
// JWTTrackedRequestClaims represents the JWT claims for a tracked request.
type JWTTrackedRequestClaims struct {
jwt.StandardClaims
jwt.RegisteredClaims
TrackedRequest
SAMLAuthnRequest bool `json:"saml-authn-request"`
}
@@ -34,12 +34,12 @@ type JWTTrackedRequestClaims struct {
func (s JWTTrackedRequestCodec) Encode(value TrackedRequest) (string, error) {
now := saml.TimeNow()
claims := JWTTrackedRequestClaims{
StandardClaims: jwt.StandardClaims{
Audience: s.Audience,
ExpiresAt: now.Add(s.MaxAge).Unix(),
IssuedAt: now.Unix(),
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{s.Audience},
ExpiresAt: jwt.NewNumericDate(now.Add(s.MaxAge)),
IssuedAt: jwt.NewNumericDate(now),
Issuer: s.Issuer,
NotBefore: now.Unix(), // TODO(ross): correct for clock skew
NotBefore: jwt.NewNumericDate(now), // TODO(ross): correct for clock skew
Subject: value.Index,
},
TrackedRequest: value,
@@ -67,7 +67,7 @@ func (s JWTTrackedRequestCodec) Decode(signed string) (*TrackedRequest, error) {
if !claims.VerifyIssuer(s.Issuer, true) {
return nil, fmt.Errorf("expected issuer %q, got %q", s.Issuer, claims.Issuer)
}
if claims.SAMLAuthnRequest != true {
if !claims.SAMLAuthnRequest {
return nil, fmt.Errorf("expected saml-authn-request")
}
claims.TrackedRequest.Index = claims.Subject
+1 -1
View File
@@ -106,7 +106,7 @@ func (c JWTSessionCodec) Decode(signed string) (Session, error) {
if !claims.VerifyIssuer(c.Issuer, true) {
return nil, fmt.Errorf("expected issuer %q, got %q", c.Issuer, claims.Issuer)
}
if claims.SAMLSession != true {
if !claims.SAMLSession {
return nil, errors.New("expected saml-session")
}
return claims, nil
+14 -14
View File
@@ -48,7 +48,7 @@ type AuthnRequest struct {
NameIDPolicy *NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"`
Conditions *Conditions
RequestedAuthnContext *RequestedAuthnContext
//Scoping *Scoping // TODO
// Scoping *Scoping // TODO
ForceAuthn *bool `xml:",attr"`
IsPassive *bool `xml:",attr"`
@@ -108,7 +108,7 @@ func (r *LogoutRequest) Element() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *LogoutRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *LogoutRequest) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias LogoutRequest
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
@@ -209,9 +209,9 @@ func (r *AuthnRequest) Element() *etree.Element {
if r.RequestedAuthnContext != nil {
el.AddChild(r.RequestedAuthnContext.Element())
}
//if r.Scoping != nil {
// el.AddChild(r.Scoping.Element())
//}
// if r.Scoping != nil {
// el.AddChild(r.Scoping.Element())
// }
if r.ForceAuthn != nil {
el.CreateAttr("ForceAuthn", strconv.FormatBool(*r.ForceAuthn))
}
@@ -237,7 +237,7 @@ func (r *AuthnRequest) Element() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *AuthnRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *AuthnRequest) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias AuthnRequest
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
@@ -374,7 +374,7 @@ func (r *ArtifactResolve) SoapRequest() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *ArtifactResolve) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *ArtifactResolve) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias ArtifactResolve
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
@@ -448,7 +448,7 @@ func (r *ArtifactResponse) Element() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *ArtifactResponse) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *ArtifactResponse) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias ArtifactResponse
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
@@ -542,7 +542,7 @@ func (r *Response) Element() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *Response) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *Response) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias Response
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
@@ -667,7 +667,7 @@ const (
StatusRequestUnsupported = "urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported"
// StatusRequestVersionDeprecated means the SAML responder cannot process any requests with the protocol version specified in the request.
StatusRequestVersionDeprecated = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated"
StatusRequestVersionDeprecated = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated" //nolint:gosec
// StatusRequestVersionTooHigh means the SAML responder cannot process the request because the protocol version specified in the request message is a major upgrade from the highest protocol version supported by the responder.
StatusRequestVersionTooHigh = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooHigh"
@@ -1153,9 +1153,9 @@ func (a *SubjectLocality) Element() *etree.Element {
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.2.2
type AuthnContext struct {
AuthnContextClassRef *AuthnContextClassRef
//AuthnContextDecl *AuthnContextDecl ... TODO
//AuthnContextDeclRef *AuthnContextDeclRef ... TODO
//AuthenticatingAuthorities []AuthenticatingAuthority... TODO
// AuthnContextDecl *AuthnContextDecl ... TODO
// AuthnContextDeclRef *AuthnContextDeclRef ... TODO
// AuthenticatingAuthorities []AuthenticatingAuthority... TODO
}
// Element returns an etree.Element representing the object in XML form.
@@ -1292,7 +1292,7 @@ func (r *LogoutResponse) Element() *etree.Element {
}
// MarshalXML implements xml.Marshaler
func (r *LogoutResponse) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (r *LogoutResponse) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
type Alias LogoutResponse
aux := &struct {
IssueInstant RelaxedTime `xml:",attr"`
+79 -65
View File
@@ -12,7 +12,7 @@ import (
"errors"
"fmt"
"html/template"
"io/ioutil"
"io"
"net/http"
"net/url"
"regexp"
@@ -23,6 +23,7 @@ import (
dsig "github.com/russellhaering/goxmldsig"
"github.com/russellhaering/goxmldsig/etreeutils"
"github.com/crewjam/saml/logger"
"github.com/crewjam/saml/xmlenc"
)
@@ -189,13 +190,13 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor {
}
}
var sloEndpoints []Endpoint
for _, binding := range sp.LogoutBindings {
sloEndpoints = append(sloEndpoints, Endpoint{
sloEndpoints := make([]Endpoint, len(sp.LogoutBindings))
for i, binding := range sp.LogoutBindings {
sloEndpoints[i] = Endpoint{
Binding: binding,
Location: sp.SloURL.String(),
ResponseLocation: sp.SloURL.String(),
})
}
}
return &EntityDescriptor{
@@ -245,25 +246,29 @@ func (sp *ServiceProvider) MakeRedirectAuthenticationRequest(relayState string)
}
// Redirect returns a URL suitable for using the redirect binding with the request
func (req *AuthnRequest) Redirect(relayState string, sp *ServiceProvider) (*url.URL, error) {
func (r *AuthnRequest) Redirect(relayState string, sp *ServiceProvider) (*url.URL, error) {
w := &bytes.Buffer{}
w1 := base64.NewEncoder(base64.StdEncoding, w)
w2, _ := flate.NewWriter(w1, 9)
doc := etree.NewDocument()
doc.SetRoot(req.Element())
doc.SetRoot(r.Element())
if _, err := doc.WriteTo(w2); err != nil {
panic(err)
}
w2.Close()
w1.Close()
if err := w2.Close(); err != nil {
panic(err)
}
if err := w1.Close(); err != nil {
panic(err)
}
rv, _ := url.Parse(req.Destination)
rv, _ := url.Parse(r.Destination)
// We can't depend on Query().set() as order matters for signing
query := rv.RawQuery
if len(query) > 0 {
query += "&SAMLRequest=" + url.QueryEscape(string(w.Bytes()))
query += "&SAMLRequest=" + url.QueryEscape(w.String())
} else {
query += "SAMLRequest=" + url.QueryEscape(string(w.Bytes()))
query += "SAMLRequest=" + url.QueryEscape(w.String())
}
if relayState != "" {
@@ -352,11 +357,11 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) {
return nil, errors.New("cannot find any signing certificate in the IDP SSO descriptor")
}
var certs []*x509.Certificate
certs := make([]*x509.Certificate, len(certStrs))
// cleanup whitespace
regex := regexp.MustCompile(`\s+`)
for _, certStr := range certStrs {
for i, certStr := range certStrs {
certStr = regex.ReplaceAllString(certStr, "")
certBytes, err := base64.StdEncoding.DecodeString(certStr)
if err != nil {
@@ -367,7 +372,7 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) {
if err != nil {
return nil, err
}
certs = append(certs, parsedCert)
certs[i] = parsedCert
}
return certs, nil
@@ -439,9 +444,9 @@ func GetSigningContext(sp *ServiceProvider) (*dsig.SigningContext, error) {
Leaf: sp.Certificate,
}
// TODO: add intermediates for SP
//for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
//}
// for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
// }
keyStore := dsig.TLSCertKeyStore(keyPair)
if sp.SignatureMethod != dsig.RSASHA1SignatureMethod &&
@@ -508,9 +513,9 @@ func (sp *ServiceProvider) MakePostAuthenticationRequest(relayState string) ([]b
}
// Post returns an HTML form suitable for using the HTTP-POST binding with the request
func (req *AuthnRequest) Post(relayState string) []byte {
func (r *AuthnRequest) Post(relayState string) []byte {
doc := etree.NewDocument()
doc.SetRoot(req.Element())
doc.SetRoot(r.Element())
reqBuf, err := doc.WriteToBytes()
if err != nil {
panic(err)
@@ -530,7 +535,7 @@ func (req *AuthnRequest) Post(relayState string) []byte {
SAMLRequest string
RelayState string
}{
URL: req.Destination,
URL: r.Destination,
SAMLRequest: encodedReqBuf,
RelayState: relayState,
}
@@ -579,7 +584,7 @@ type InvalidResponseError struct {
}
func (ivr *InvalidResponseError) Error() string {
return fmt.Sprintf("Authentication failed")
return "Authentication failed"
}
// ErrBadStatus is returned when the assertion provided is valid but the
@@ -606,7 +611,7 @@ func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID
artifactResolveRequest, err := sp.MakeArtifactResolveRequest(artifactID)
if err != nil {
retErr.PrivateErr = fmt.Errorf("Cannot generate artifact resolution request: %s", err)
retErr.PrivateErr = fmt.Errorf("cannot generate artifact resolution request: %s", err)
return nil, retErr
}
@@ -633,12 +638,16 @@ func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID
retErr.PrivateErr = fmt.Errorf("cannot resolve artifact: %s", err)
return nil, retErr
}
defer response.Body.Close()
defer func() {
if err := response.Body.Close(); err != nil {
logger.DefaultLogger.Printf("Error while closing response body during artifact resolution: %v", err)
}
}()
if response.StatusCode != 200 {
retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: HTTP status %d (%s)", response.StatusCode, response.Status)
return nil, retErr
}
responseBody, err := ioutil.ReadAll(response.Body)
responseBody, err := io.ReadAll(response.Body)
if err != nil {
retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err)
return nil, retErr
@@ -753,11 +762,12 @@ func (sp *ServiceProvider) parseArtifactResponse(artifactResponseEl *etree.Eleme
var signatureRequirement signatureRequirement
sigErr := sp.validateSignature(artifactResponseEl)
if sigErr == nil {
switch sigErr {
case nil:
signatureRequirement = signatureNotRequired
} else if sigErr == errSignatureElementNotPresent {
case errSignatureElementNotPresent:
signatureRequirement = signatureRequired
} else {
default:
retErr.PrivateErr = sigErr
return nil, retErr
}
@@ -888,13 +898,14 @@ func (sp *ServiceProvider) parseResponse(responseEl *etree.Element, possibleRequ
}
if signatureRequirement == signatureRequired {
if responseSignatureErr == nil {
switch responseSignatureErr {
case nil:
// since the request has a signature, none of the Assertions need one
signatureRequirement = signatureNotRequired
} else if responseSignatureErr == errSignatureElementNotPresent {
case errSignatureElementNotPresent:
// the request has no signature, so assertions must be signed
signatureRequirement = signatureRequired // nop
} else {
default:
return nil, responseSignatureErr
}
}
@@ -1078,7 +1089,7 @@ func (sp *ServiceProvider) validateAssertion(assertion *Assertion, possibleReque
return nil
}
var errSignatureElementNotPresent = errors.New("Signature element not present")
var errSignatureElementNotPresent = errors.New("signature element not present")
// validateSignature returns nil iff the Signature embedded in the element is valid
func (sp *ServiceProvider) validateSignature(el *etree.Element) error {
@@ -1154,9 +1165,9 @@ func (sp *ServiceProvider) SignLogoutRequest(req *LogoutRequest) error {
Leaf: sp.Certificate,
}
// TODO: add intermediates for SP
//for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
//}
// for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
// }
keyStore := dsig.TLSCertKeyStore(keyPair)
if sp.SignatureMethod != dsig.RSASHA1SignatureMethod &&
@@ -1222,22 +1233,26 @@ func (sp *ServiceProvider) MakeRedirectLogoutRequest(nameID, relayState string)
}
// Redirect returns a URL suitable for using the redirect binding with the request
func (req *LogoutRequest) Redirect(relayState string) *url.URL {
func (r *LogoutRequest) Redirect(relayState string) *url.URL {
w := &bytes.Buffer{}
w1 := base64.NewEncoder(base64.StdEncoding, w)
w2, _ := flate.NewWriter(w1, 9)
doc := etree.NewDocument()
doc.SetRoot(req.Element())
doc.SetRoot(r.Element())
if _, err := doc.WriteTo(w2); err != nil {
panic(err)
}
w2.Close()
w1.Close()
if err := w2.Close(); err != nil {
panic(err)
}
if err := w1.Close(); err != nil {
panic(err)
}
rv, _ := url.Parse(req.Destination)
rv, _ := url.Parse(r.Destination)
query := rv.Query()
query.Set("SAMLRequest", string(w.Bytes()))
query.Set("SAMLRequest", w.String())
if relayState != "" {
query.Set("RelayState", relayState)
}
@@ -1258,9 +1273,9 @@ func (sp *ServiceProvider) MakePostLogoutRequest(nameID, relayState string) ([]b
}
// Post returns an HTML form suitable for using the HTTP-POST binding with the request
func (req *LogoutRequest) Post(relayState string) []byte {
func (r *LogoutRequest) Post(relayState string) []byte {
doc := etree.NewDocument()
doc.SetRoot(req.Element())
doc.SetRoot(r.Element())
reqBuf, err := doc.WriteToBytes()
if err != nil {
panic(err)
@@ -1280,7 +1295,7 @@ func (req *LogoutRequest) Post(relayState string) []byte {
SAMLRequest string
RelayState string
}{
URL: req.Destination,
URL: r.Destination,
SAMLRequest: encodedReqBuf,
RelayState: relayState,
}
@@ -1332,22 +1347,26 @@ func (sp *ServiceProvider) MakeRedirectLogoutResponse(logoutRequestID, relayStat
}
// Redirect returns a URL suitable for using the redirect binding with the LogoutResponse.
func (resp *LogoutResponse) Redirect(relayState string) *url.URL {
func (r *LogoutResponse) Redirect(relayState string) *url.URL {
w := &bytes.Buffer{}
w1 := base64.NewEncoder(base64.StdEncoding, w)
w2, _ := flate.NewWriter(w1, 9)
doc := etree.NewDocument()
doc.SetRoot(resp.Element())
doc.SetRoot(r.Element())
if _, err := doc.WriteTo(w2); err != nil {
panic(err)
}
w2.Close()
w1.Close()
if err := w2.Close(); err != nil {
panic(err)
}
if err := w1.Close(); err != nil {
panic(err)
}
rv, _ := url.Parse(resp.Destination)
rv, _ := url.Parse(r.Destination)
query := rv.Query()
query.Set("SAMLResponse", string(w.Bytes()))
query.Set("SAMLResponse", w.String())
if relayState != "" {
query.Set("RelayState", relayState)
}
@@ -1368,9 +1387,9 @@ func (sp *ServiceProvider) MakePostLogoutResponse(logoutRequestID, relayState st
}
// Post returns an HTML form suitable for using the HTTP-POST binding with the LogoutResponse.
func (resp *LogoutResponse) Post(relayState string) []byte {
func (r *LogoutResponse) Post(relayState string) []byte {
doc := etree.NewDocument()
doc.SetRoot(resp.Element())
doc.SetRoot(r.Element())
reqBuf, err := doc.WriteToBytes()
if err != nil {
panic(err)
@@ -1390,7 +1409,7 @@ func (resp *LogoutResponse) Post(relayState string) []byte {
SAMLResponse string
RelayState string
}{
URL: resp.Destination,
URL: r.Destination,
SAMLResponse: encodedReqBuf,
RelayState: relayState,
}
@@ -1411,9 +1430,9 @@ func (sp *ServiceProvider) SignLogoutResponse(resp *LogoutResponse) error {
Leaf: sp.Certificate,
}
// TODO: add intermediates for SP
//for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
//}
// for _, cert := range sp.Intermediates {
// keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
// }
keyStore := dsig.TLSCertKeyStore(keyPair)
if sp.SignatureMethod != dsig.RSASHA1SignatureMethod &&
@@ -1502,10 +1521,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseForm(postFormData string) error
retErr.PrivateErr = err
return retErr
}
if err := sp.validateLogoutResponse(&resp); err != nil {
return err
}
return nil
return sp.validateLogoutResponse(&resp)
}
// ValidateLogoutResponseRedirect returns a nil error if the logout response is valid.
@@ -1524,7 +1540,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str
}
retErr.Response = string(rawResponseBuf)
gr, err := ioutil.ReadAll(newSaferFlateReader(bytes.NewBuffer(rawResponseBuf)))
gr, err := io.ReadAll(newSaferFlateReader(bytes.NewBuffer(rawResponseBuf)))
if err != nil {
retErr.PrivateErr = err
return retErr
@@ -1550,10 +1566,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str
retErr.PrivateErr = err
return retErr
}
if err := sp.validateLogoutResponse(&resp); err != nil {
return err
}
return nil
return sp.validateLogoutResponse(&resp)
}
// validateLogoutResponse validates the LogoutResponse fields. Returns a nil error if the LogoutResponse is valid.
@@ -1585,6 +1598,7 @@ func firstSet(a, b string) string {
// findChildren returns all the elements matching childNS/childTag that are direct children of parentEl.
func findChildren(parentEl *etree.Element, childNS string, childTag string) ([]*etree.Element, error) {
//nolint:prealloc // We don't know how many child elements we'll actually put into this array.
var rv []*etree.Element
for _, childEl := range parentEl.ChildElements() {
if childEl.Tag != childTag {
+1
View File
@@ -21,6 +21,7 @@ var Clock *dsig.Clock
// rand.Reader, but it can be replaced for testing.
var RandReader = rand.Reader
//nolint:unparam // This always receives 20, but we want the option to do more or less if needed.
func randomBytes(n int) []byte {
rv := make([]byte, n)
+1 -1
View File
@@ -31,7 +31,7 @@ func (e CBC) Algorithm() string {
// Encrypt encrypts plaintext with key, which should be a []byte of length KeySize().
// It returns an xenc:EncryptedData element.
func (e CBC) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
func (e CBC) Encrypt(key interface{}, plaintext []byte, _ []byte) (*etree.Element, error) {
keyBuf, ok := key.([]byte)
if !ok {
return nil, ErrIncorrectKeyType("[]byte")
+1
View File
@@ -90,6 +90,7 @@ func validateRSAKeyIfPresent(key interface{}, encryptedKey *etree.Element) (*rsa
// if the key will work, or let the service provider know which key
// to use to decrypt the message. Either way, verification is not
// security-critical.
//nolint:revive,staticcheck // Keep the later empty branch so that we know to address this at a later date.
if el := encryptedKey.FindElement("./KeyInfo/X509Data/X509Certificate"); el != nil {
certPEMbuf := el.Text()
certPEMbuf = "-----BEGIN CERTIFICATE-----\n" + certPEMbuf + "\n-----END CERTIFICATE-----\n"
+1
View File
@@ -6,6 +6,7 @@ import (
"crypto/sha512"
"hash"
//nolint:staticcheck // We should support this for legacy reasons.
"golang.org/x/crypto/ripemd160"
)