diff --git a/go.mod b/go.mod index 8d684f17f84bffdafb4a0fe51ea62064f1c0befc..747c43a81166e74661390bc7a24a49049c9c5e20 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( git.autistici.org/id/go-sso v0.0.0-20230822064459-ed921a53bb33 git.autistici.org/id/keystore v0.0.0-20230901162242-63f23c4799e9 git.autistici.org/id/usermetadb v0.0.0-20240906121505-52bc1650d93d - github.com/crewjam/saml v0.4.13 + github.com/crewjam/saml v0.4.14 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/go-webauthn/webauthn v0.10.2 github.com/gorilla/csrf v1.7.2 @@ -52,7 +52,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/russellhaering/goxmldsig v1.2.0 // indirect + github.com/russellhaering/goxmldsig v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.9.0 // indirect diff --git a/go.sum b/go.sum index 6e0c0456c7d4c4553ffc939ab630d5d88362fddd..10445ab8cabed191082f4fd5b53670dd89e76a16 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= +github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= +github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -734,6 +736,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= +github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/vendor/github.com/crewjam/saml/.golangci.yml b/vendor/github.com/crewjam/saml/.golangci.yml index 4fbc0405df2e767914f38275bb0cac9f9017b78f..23f37cbf54209237c01a33760341ecc55a7972cc 100644 --- a/vendor/github.com/crewjam/saml/.golangci.yml +++ b/vendor/github.com/crewjam/saml/.golangci.yml @@ -7,41 +7,35 @@ linters: enable: - - 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] + - 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] - 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 diff --git a/vendor/github.com/crewjam/saml/identity_provider.go b/vendor/github.com/crewjam/saml/identity_provider.go index bcea5828f56f9912bcda591ef09c197c74a9f768..abaaad68e27dd60965d50c0485d1cccb0ec962ce 100644 --- a/vendor/github.com/crewjam/saml/identity_provider.go +++ b/vendor/github.com/crewjam/saml/identity_provider.go @@ -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 +} diff --git a/vendor/github.com/crewjam/saml/logger/logger.go b/vendor/github.com/crewjam/saml/logger/logger.go index c211aba60fafef6d006a7b6e821416656e6a2b3a..03bb0bdef92da70a135fbe159b023922a7bce692 100644 --- a/vendor/github.com/crewjam/saml/logger/logger.go +++ b/vendor/github.com/crewjam/saml/logger/logger.go @@ -1,3 +1,4 @@ +// Package logger provides a logging interface. package logger import ( diff --git a/vendor/github.com/crewjam/saml/metadata.go b/vendor/github.com/crewjam/saml/metadata.go index f7486d380076106c5fa93ff869f9cabea923916d..006a9e67a586a60487eb74add81c6da11c9937c5 100644 --- a/vendor/github.com/crewjam/saml/metadata.go +++ b/vendor/github.com/crewjam/saml/metadata.go @@ -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 diff --git a/vendor/github.com/crewjam/saml/schema.go b/vendor/github.com/crewjam/saml/schema.go index 13ea2ef60e93b0dcc533aae182d78a86e8f37010..23cddbcabb7fdefed2ecb902b4c392330cea93cc 100644 --- a/vendor/github.com/crewjam/saml/schema.go +++ b/vendor/github.com/crewjam/saml/schema.go @@ -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"` diff --git a/vendor/github.com/crewjam/saml/service_provider.go b/vendor/github.com/crewjam/saml/service_provider.go index 6f6e7f4fc81c85808e919661665f8ae7c804c3c9..30b35670d64704ce29618ad41596dd5595f28aff 100644 --- a/vendor/github.com/crewjam/saml/service_provider.go +++ b/vendor/github.com/crewjam/saml/service_provider.go @@ -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 { diff --git a/vendor/github.com/crewjam/saml/util.go b/vendor/github.com/crewjam/saml/util.go index c9731b1b187324bd1daae4ffab82c845df461873..eda053eebd7610a2623673ce9087580076d61ada 100644 --- a/vendor/github.com/crewjam/saml/util.go +++ b/vendor/github.com/crewjam/saml/util.go @@ -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) diff --git a/vendor/github.com/crewjam/saml/xmlenc/cbc.go b/vendor/github.com/crewjam/saml/xmlenc/cbc.go index 77ddb3b2fd91e8e6c016804816294921d97fea36..991ba1ebdcb39a268c106088ee6a25b84ec919cd 100644 --- a/vendor/github.com/crewjam/saml/xmlenc/cbc.go +++ b/vendor/github.com/crewjam/saml/xmlenc/cbc.go @@ -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") diff --git a/vendor/github.com/crewjam/saml/xmlenc/decrypt.go b/vendor/github.com/crewjam/saml/xmlenc/decrypt.go index 93991f9f5f754e7e3cc5a80a12ae5c453e7774bf..98a575da981ee56a3ad2b52fff07fcbbe5ff68e1 100644 --- a/vendor/github.com/crewjam/saml/xmlenc/decrypt.go +++ b/vendor/github.com/crewjam/saml/xmlenc/decrypt.go @@ -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" diff --git a/vendor/github.com/crewjam/saml/xmlenc/digest.go b/vendor/github.com/crewjam/saml/xmlenc/digest.go index 801347f26f57aae4bea1304db1eb9c02fcf5d8df..3eaaf7bc7871aa07ded496d0b9e0998d9209993e 100644 --- a/vendor/github.com/crewjam/saml/xmlenc/digest.go +++ b/vendor/github.com/crewjam/saml/xmlenc/digest.go @@ -6,6 +6,7 @@ import ( "crypto/sha512" "hash" + //nolint:staticcheck // We should support this for legacy reasons. "golang.org/x/crypto/ripemd160" ) diff --git a/vendor/github.com/russellhaering/goxmldsig/README.md b/vendor/github.com/russellhaering/goxmldsig/README.md index a7758875156484209dc3960c4120d7c2c09bbb5e..c572e8c75ca90102316509723391d60cf940a785 100644 --- a/vendor/github.com/russellhaering/goxmldsig/README.md +++ b/vendor/github.com/russellhaering/goxmldsig/README.md @@ -15,6 +15,19 @@ $ go get github.com/russellhaering/goxmldsig ## Usage +Include the [`types.Signature`](https://pkg.go.dev/github.com/russellhaering/goxmldsig/types#Signature) struct from this package in your application messages. + +```go +import ( + sigtypes "github.com/russellhaering/goxmldsig/types" +) + +type AppHdr struct { + ... + Signature *sigtypes.Signature +} +``` + ### Signing ```go diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go index 8437fe4037a254281bf904c586649d299c5131bb..82ceb0a2a517617d82244e45bd6ba09f040184ea 100644 --- a/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go +++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go @@ -16,7 +16,8 @@ func TransformExcC14n(el *etree.Element, inclusiveNamespacesPrefixList string, c prefixSet[prefix] = struct{}{} } - err := transformExcC14n(DefaultNSContext, DefaultNSContext, el, prefixSet, comments) + ctx := NewDefaultNSContext() + err := transformExcC14n(ctx, ctx, el, prefixSet, comments) if err != nil { return err } @@ -31,7 +32,7 @@ func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNames } visiblyUtilizedPrefixes := map[string]struct{}{ - el.Space: struct{}{}, + el.Space: {}, } filteredAttrs := []etree.Attr{} diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go index baf1124f66e08e3c1e8e9d841fd4e25fb75d0d70..6a5be036304350b7efdda9de03009f6c3afeff5a 100644 --- a/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go +++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go @@ -2,9 +2,7 @@ package etreeutils import ( "errors" - "fmt" - "sort" "github.com/beevik/etree" @@ -19,20 +17,25 @@ const ( XMLNSNamespace = "http://www.w3.org/2000/xmlns/" ) -var ( - DefaultNSContext = NSContext{ +func NewDefaultNSContext() NSContext { + defaultLimit := 1000 + return NSContext{ prefixes: map[string]string{ defaultPrefix: XMLNamespace, xmlPrefix: XMLNamespace, xmlnsPrefix: XMLNSNamespace, }, + limit: &defaultLimit, } +} +var ( EmptyNSContext = NSContext{} ErrReservedNamespace = errors.New("disallowed declaration of reserved namespace") ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration") ErrTraversalHalted = errors.New("traversal halted") + ErrTraversalLimit = errors.New("traversal limit reached") ) type ErrUndeclaredNSPrefix struct { @@ -45,6 +48,17 @@ func (e ErrUndeclaredNSPrefix) Error() string { type NSContext struct { prefixes map[string]string + limit *int +} + +// CheckLimit checks the traversal limit before calling the handler function +func (ctx NSContext) CheckLimit() error { + if *ctx.limit <= 0 { + return ErrTraversalLimit + } + *ctx.limit-- + + return nil } func (ctx NSContext) Copy() NSContext { @@ -53,7 +67,7 @@ func (ctx NSContext) Copy() NSContext { prefixes[k] = v } - return NSContext{prefixes: prefixes} + return NSContext{prefixes: prefixes, limit: ctx.limit} } func (ctx NSContext) declare(prefix, namespace string) etree.Attr { @@ -140,7 +154,12 @@ type NSIterHandler func(NSContext, *etree.Element) error // NSTraverse traverses an element tree, invoking the passed handler for each element // in the tree. func NSTraverse(ctx NSContext, el *etree.Element, handle NSIterHandler) error { - ctx, err := ctx.SubContext(el) + err := ctx.CheckLimit() + if err != nil { + return err + } + + ctx, err = ctx.SubContext(el) if err != nil { return err } @@ -223,7 +242,7 @@ func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) { // NSSelectOne behaves identically to NSSelectOneCtx, but uses DefaultNSContext as the // surrounding context. func NSSelectOne(el *etree.Element, namespace, tag string) (*etree.Element, error) { - return NSSelectOneCtx(DefaultNSContext, el, namespace, tag) + return NSSelectOneCtx(NewDefaultNSContext(), el, namespace, tag) } // NSSelectOneCtx conducts a depth-first search for an element with the specified namespace @@ -243,7 +262,6 @@ func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*e return ErrTraversalHalted }) - if err != nil { return nil, err } @@ -254,7 +272,7 @@ func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*e // NSFindIterate behaves identically to NSFindIterateCtx, but uses DefaultNSContext // as the surrounding context. func NSFindIterate(el *etree.Element, namespace, tag string, handle NSIterHandler) error { - return NSFindIterateCtx(DefaultNSContext, el, namespace, tag, handle) + return NSFindIterateCtx(NewDefaultNSContext(), el, namespace, tag, handle) } // NSFindIterateCtx conducts a depth-first traversal searching for elements with the @@ -294,7 +312,7 @@ func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, h // NSFindOne behaves identically to NSFindOneCtx, but uses DefaultNSContext for // context. func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error) { - return NSFindOneCtx(DefaultNSContext, el, namespace, tag) + return NSFindOneCtx(NewDefaultNSContext(), el, namespace, tag) } // NSFindOneCtx conducts a depth-first search for the specified element. If such an element @@ -306,7 +324,6 @@ func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etr found = el return ErrTraversalHalted }) - if err != nil { return nil, err } @@ -325,6 +342,11 @@ func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) e // Iterate the child elements. for _, child := range el.ChildElements() { + err := ctx.CheckLimit() + if err != nil { + return err + } + err = handle(ctx, child) if err != nil { return err @@ -368,7 +390,7 @@ func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag s // NSFindOneChild behaves identically to NSFindOneChildCtx, but uses // DefaultNSContext for context. func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) { - return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag) + return NSFindOneChildCtx(NewDefaultNSContext(), el, namespace, tag) } // NSFindOneCtx conducts a depth-first search for the specified element. If such an @@ -394,11 +416,10 @@ func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) func NSBuildParentContext(el *etree.Element) (NSContext, error) { parent := el.Parent() if parent == nil { - return DefaultNSContext, nil + return NewDefaultNSContext(), nil } ctx, err := NSBuildParentContext(parent) - if err != nil { return ctx, err } diff --git a/vendor/github.com/russellhaering/goxmldsig/sign.go b/vendor/github.com/russellhaering/goxmldsig/sign.go index 2be34b7f63aebcd0e6f9b0f9202e272783d930f2..cc77f790a37bbdb2ed8bf741f5b14ca7bfb81b6d 100644 --- a/vendor/github.com/russellhaering/goxmldsig/sign.go +++ b/vendor/github.com/russellhaering/goxmldsig/sign.go @@ -2,10 +2,12 @@ package dsig import ( "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" _ "crypto/sha1" _ "crypto/sha256" + "crypto/x509" "encoding/base64" "errors" "fmt" @@ -15,11 +17,18 @@ import ( ) type SigningContext struct { - Hash crypto.Hash + Hash crypto.Hash + + // This field will be nil and unused if the SigningContext is created with + // NewSigningContext KeyStore X509KeyStore IdAttribute string Prefix string Canonicalizer Canonicalizer + + // KeyStore is mutually exclusive with signer and certs + signer crypto.Signer + certs [][]byte } func NewDefaultSigningContext(ks X509KeyStore) *SigningContext { @@ -32,13 +41,54 @@ func NewDefaultSigningContext(ks X509KeyStore) *SigningContext { } } +// NewSigningContext creates a new signing context with the given signer and certificate chain. +// Note that e.g. rsa.PrivateKey implements the crypto.Signer interface. +// The certificate chain is a slice of ASN.1 DER-encoded X.509 certificates. +// A SigningContext created with this function should not use the KeyStore field. +// It will return error if passed a nil crypto.Signer +func NewSigningContext(signer crypto.Signer, certs [][]byte) (*SigningContext, error) { + if signer == nil { + return nil, errors.New("signer cannot be nil for NewSigningContext") + } + ctx := &SigningContext{ + Hash: crypto.SHA256, + IdAttribute: DefaultIdAttr, + Prefix: DefaultPrefix, + Canonicalizer: MakeC14N11Canonicalizer(), + + signer: signer, + certs: certs, + } + return ctx, nil +} + +func (ctx *SigningContext) getPublicKeyAlgorithm() x509.PublicKeyAlgorithm { + if ctx.KeyStore != nil { + return x509.RSA + } else { + switch ctx.signer.Public().(type) { + case *ecdsa.PublicKey: + return x509.ECDSA + case *rsa.PublicKey: + return x509.RSA + } + } + + return x509.UnknownPublicKeyAlgorithm +} + func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error { - hash, ok := signatureMethodsByIdentifier[algorithmID] + info, ok := signatureMethodsByIdentifier[algorithmID] if !ok { - return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID) + return fmt.Errorf("unknown SignatureMethod: %s", algorithmID) } - ctx.Hash = hash + algo := ctx.getPublicKeyAlgorithm() + if info.PublicKeyAlgorithm != algo { + return fmt.Errorf("SignatureMethod %s is incompatible with %s key", algorithmID, algo) + } + + ctx.Hash = info.Hash return nil } @@ -58,6 +108,46 @@ func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) { return hash.Sum(nil), nil } +func (ctx *SigningContext) signDigest(digest []byte) ([]byte, error) { + if ctx.KeyStore != nil { + key, _, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest) + if err != nil { + return nil, err + } + + return rawSignature, nil + } else { + rawSignature, err := ctx.signer.Sign(rand.Reader, digest, ctx.Hash) + if err != nil { + return nil, err + } + + return rawSignature, nil + } +} + +func (ctx *SigningContext) getCerts() ([][]byte, error) { + if ctx.KeyStore != nil { + if cs, ok := ctx.KeyStore.(X509ChainStore); ok { + return cs.GetChain() + } + + _, cert, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + return [][]byte{cert}, nil + } else { + return ctx.certs, nil + } +} + func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() if digestAlgorithmIdentifier == "" { @@ -97,7 +187,6 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool reference.CreateAttr(URIAttr, "#"+dataId) } - // /SignedInfo/Reference/Transforms transforms := ctx.createNamespacedElement(reference, TransformsTag) if enveloped { @@ -172,20 +261,12 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) return nil, err } - key, cert, err := ctx.KeyStore.GetKeyPair() + rawSignature, err := ctx.signDigest(digest) if err != nil { return nil, err } - certs := [][]byte{cert} - if cs, ok := ctx.KeyStore.(X509ChainStore); ok { - certs, err = cs.GetChain() - if err != nil { - return nil, err - } - } - - rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest) + certs, err := ctx.getCerts() if err != nil { return nil, err } @@ -222,7 +303,9 @@ func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, err } func (ctx *SigningContext) GetSignatureMethodIdentifier() string { - if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok { + algo := ctx.getPublicKeyAlgorithm() + + if ident, ok := signatureMethodIdentifiers[algo][ctx.Hash]; ok { return ident } return "" @@ -247,11 +330,5 @@ func (ctx *SigningContext) SignString(content string) ([]byte, error) { } digest := hash.Sum(nil) - var signature []byte - if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil { - return nil, fmt.Errorf("unable to fetch key for signing: %v", err) - } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil { - return nil, fmt.Errorf("error signing: %v", err) - } - return signature, nil + return ctx.signDigest(digest) } diff --git a/vendor/github.com/russellhaering/goxmldsig/validate.go b/vendor/github.com/russellhaering/goxmldsig/validate.go index 2c65ca1c16f5427e028fed11a043d1144aa08ed9..45e301382b18209ae3487db1272df0d9cb57d457 100644 --- a/vendor/github.com/russellhaering/goxmldsig/validate.go +++ b/vendor/github.com/russellhaering/goxmldsig/validate.go @@ -2,7 +2,6 @@ package dsig import ( "bytes" - "crypto/rsa" "crypto/x509" "encoding/base64" "errors" @@ -213,26 +212,12 @@ func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicaliz return err } - signatureAlgorithm, ok := signatureMethodsByIdentifier[signatureMethodId] + algo, ok := x509SignatureAlgorithmByIdentifier[signatureMethodId] if !ok { return errors.New("Unknown signature method: " + signatureMethodId) } - hash := signatureAlgorithm.New() - _, err = hash.Write(canonical) - if err != nil { - return err - } - - hashed := hash.Sum(nil) - - pubKey, ok := cert.PublicKey.(*rsa.PublicKey) - if !ok { - return errors.New("Invalid public key") - } - - // Verify that the private key matching the public key from the cert was what was used to sign the 'SignedInfo' and produce the 'SignatureValue' - err = rsa.VerifyPKCS1v15(pubKey, signatureAlgorithm, hashed[:], decodedSignature) + err = cert.CheckSignature(algo, canonical, decodedSignature) if err != nil { return err } diff --git a/vendor/github.com/russellhaering/goxmldsig/xml_constants.go b/vendor/github.com/russellhaering/goxmldsig/xml_constants.go index d2b98e257a645ef20eb0bfc700c8e1e94019629e..0526062e94259f9db2dc2094468808a25fd11356 100644 --- a/vendor/github.com/russellhaering/goxmldsig/xml_constants.go +++ b/vendor/github.com/russellhaering/goxmldsig/xml_constants.go @@ -1,6 +1,9 @@ package dsig -import "crypto" +import ( + "crypto" + "crypto/x509" +) const ( DefaultPrefix = "ds" @@ -39,12 +42,17 @@ func (id AlgorithmID) String() string { } const ( - RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" + RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" + RSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" + RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" + ECDSASHA1SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1" + ECDSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" + ECDSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" + ECDSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" ) -//Well-known signature algorithms +// Well-known signature algorithms const ( // Supported canonicalization algorithms CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#" @@ -62,23 +70,54 @@ const ( var digestAlgorithmIdentifiers = map[crypto.Hash]string{ crypto.SHA1: "http://www.w3.org/2000/09/xmldsig#sha1", crypto.SHA256: "http://www.w3.org/2001/04/xmlenc#sha256", + crypto.SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384", crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512", } +type signatureMethodInfo struct { + PublicKeyAlgorithm x509.PublicKeyAlgorithm + Hash crypto.Hash +} + var digestAlgorithmsByIdentifier = map[string]crypto.Hash{} -var signatureMethodsByIdentifier = map[string]crypto.Hash{} +var signatureMethodsByIdentifier = map[string]signatureMethodInfo{} func init() { for hash, id := range digestAlgorithmIdentifiers { digestAlgorithmsByIdentifier[id] = hash } - for hash, id := range signatureMethodIdentifiers { - signatureMethodsByIdentifier[id] = hash + for algo, hashToMethod := range signatureMethodIdentifiers { + for hash, method := range hashToMethod { + signatureMethodsByIdentifier[method] = signatureMethodInfo{ + PublicKeyAlgorithm: algo, + Hash: hash, + } + } } } -var signatureMethodIdentifiers = map[crypto.Hash]string{ - crypto.SHA1: RSASHA1SignatureMethod, - crypto.SHA256: RSASHA256SignatureMethod, - crypto.SHA512: RSASHA512SignatureMethod, +var signatureMethodIdentifiers = map[x509.PublicKeyAlgorithm]map[crypto.Hash]string{ + x509.RSA: { + crypto.SHA1: RSASHA1SignatureMethod, + crypto.SHA256: RSASHA256SignatureMethod, + crypto.SHA384: RSASHA384SignatureMethod, + crypto.SHA512: RSASHA512SignatureMethod, + }, + x509.ECDSA: { + crypto.SHA1: ECDSASHA1SignatureMethod, + crypto.SHA256: ECDSASHA256SignatureMethod, + crypto.SHA384: ECDSASHA384SignatureMethod, + crypto.SHA512: ECDSASHA512SignatureMethod, + }, +} + +var x509SignatureAlgorithmByIdentifier = map[string]x509.SignatureAlgorithm{ + RSASHA1SignatureMethod: x509.SHA1WithRSA, + RSASHA256SignatureMethod: x509.SHA256WithRSA, + RSASHA384SignatureMethod: x509.SHA384WithRSA, + RSASHA512SignatureMethod: x509.SHA512WithRSA, + ECDSASHA1SignatureMethod: x509.ECDSAWithSHA1, + ECDSASHA256SignatureMethod: x509.ECDSAWithSHA256, + ECDSASHA384SignatureMethod: x509.ECDSAWithSHA384, + ECDSASHA512SignatureMethod: x509.ECDSAWithSHA512, } diff --git a/vendor/modules.txt b/vendor/modules.txt index 368dcbafb827add6251ca5c364ac378d12ccc588..471160a6f8ba2e9dfee85c06f82e1a72925b40c2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -38,8 +38,8 @@ github.com/cespare/xxhash/v2 # github.com/coreos/go-systemd/v22 v22.5.0 ## explicit; go 1.12 github.com/coreos/go-systemd/v22/daemon -# github.com/crewjam/saml v0.4.13 -## explicit; go 1.16 +# github.com/crewjam/saml v0.4.14 +## explicit; go 1.19 github.com/crewjam/saml github.com/crewjam/saml/logger github.com/crewjam/saml/xmlenc @@ -145,7 +145,7 @@ github.com/prometheus/procfs/internal/util ## explicit; go 1.13 github.com/rs/cors github.com/rs/cors/internal -# github.com/russellhaering/goxmldsig v1.2.0 +# github.com/russellhaering/goxmldsig v1.3.0 ## explicit; go 1.15 github.com/russellhaering/goxmldsig github.com/russellhaering/goxmldsig/etreeutils